epoc-cellpos.cpp

Go to the documentation of this file.
00001 /*
00002 
00003 References:
00004 
00005 http://www.forum.nokia.com/document/Cpp_Developers_Library/GUID-759FBC7F-5384-4487-8457-A8D4B76F6AA6/html/Location_Acquisition_API4.html
00006 
00007 */
00008 
00009 #include "epoc-cellpos.hpp"
00010 
00011 #include "epoc-gps-module.hpp"
00012 #include "epoc-gps-positioner.hpp"
00013 
00014 #include "cf_query.h"
00015 #include "er_errors.h"
00016 #include "kr_controller_private.h"
00017 #include "sa_sensor_list_log_db.h"
00018 
00019 #include "common/assertions.h"
00020 #include "common/epoc-time.h"
00021 #include "common/error_list.h"
00022 #include "common/logging.h"
00023 #include "common/platform_error.h"
00024 #include "common/utilities.h"
00025 
00026 #include <e32math.h> 
00027 
00028 #include <stdio.h>
00029 #include <string.h>
00030 
00031 // -------------------------------------------------------------------
00032 // internal parameters...
00033 
00034 // We will never ask the positioner for service more frequently than
00035 // this. This value is runtime adjustable.
00036 #define MIN_SCAN_REQUEST_INTERVAL_SECS (3 * 60)
00037 
00038 // We will not drain the battery for longer than this if we do not get
00039 // a position fix, and we call Cancel. If we do get a position, it
00040 // should be enough to not call MakeRequest. This value is runtime
00041 // adjustable.
00042 #define SATELLITE_QUERY_TIMEOUT_SECS (60)
00043 
00044 // This is given as a parameter to the positioner, and it indicates
00045 // how frequently it should be prepared to provide periodic updates.
00046 // In our case such time periods are irregular, and it probably makes
00047 // sense not to set this to avoid possibly spending battery on
00048 // needless guarantees. It seems this must be shorter than the timeout
00049 // interval, and indeed anything else does not make much sense.
00050 #define POSITIONER_SCAN_INTERVAL_SECS (0)
00051 
00052 #define POSITIONER_MAX_AGE_SECS (60)
00053 
00054 // -------------------------------------------------------------------
00055 
00056 CSensor_cellpos* CSensor_cellpos::NewL(ac_AppContext* aAppContext)
00057 {
00058   CSensor_cellpos* obj = new (ELeave) CSensor_cellpos(aAppContext);
00059   CleanupStack::PushL(obj);
00060   obj->ConstructL();
00061   CleanupStack::Pop();
00062   return obj;
00063 }
00064 
00065 CSensor_cellpos::CSensor_cellpos(ac_AppContext* aAppContext) : 
00066   iAppContext(aAppContext),
00067   iModuleId(KPositionNullModuleId)
00068 {
00069   iLogDb = ac_LogDb(aAppContext);
00070 }
00071 
00072 void CSensor_cellpos::ConstructL()
00073 {
00074   ReadConfig();
00075 
00076   bb_Blackboard* bb = ac_get_Blackboard(iAppContext);
00077   bb_Board* bd = bb_Blackboard_board(bb);
00078   iAllowAssisted = bd->netpos_allowed;
00079 
00080   iRetryAo = CRetryAo::NewL(*this, 4, 5); // 4 tries, 5 secs
00081   iModuleAo = CPosModuleStatAo::NewL(*this);
00082   iCellChangeHandle.Register(bb, bb_dt_cell_id, this);
00083   iNetposChangeHandle.Register(bb, bb_dt_netpos_allowed, this);
00084 }
00085 
00086 CSensor_cellpos::~CSensor_cellpos()
00087 {
00088   Stop();
00089   delete iRetryAo;
00090   delete iModuleAo;
00091 }
00092 
00093 void CSensor_cellpos::Stop()
00094 {
00095   iRetryAo->Cancel();
00096   iModuleAo->Cancel();
00097   DELETE_Z(iSatPositioner);
00098   iState = EInactive;
00099 }
00100 
00101 void CSensor_cellpos::StartL()
00102 {
00103   if (!IsActive()) {
00104     iRetryAo->ResetFailures();
00105 
00106     iModuleId = KPositionNullModuleId;
00107 
00108     PosModChangeL();
00109 
00110     iState = EActive;
00111   }
00112 }
00113 
00114 void CSensor_cellpos::CreateSpecifiedPositionerL(TPositionModuleId bestId)
00115 {
00116   // This is just the module we last tried to use; we do not require
00117   // we actually succeed in creating the positioner for it.
00118   iModuleId = bestId;
00119 
00120   RPositionServer& server = iModuleAo->PositionServer();
00121   iSatPositioner = CPositioner_gps::NewL(server, *this, 
00122            bestId, 
00123            POSITIONER_SCAN_INTERVAL_SECS,
00124            iSatelliteQueryTimeoutSecs,
00125            iDataMaxAgeSecs);
00126 }
00127 
00128 void CSensor_cellpos::PosModChangeL()
00129 {
00130   TInt modif = 0;
00131   if (iAllowAssisted)
00132     modif |= CPosModuleStatAo::KAllowAssisted;
00133   TPositionModuleId bestId = iModuleAo->ChooseBestPositionerL(modif);
00134   if (bestId != iModuleId) {
00135     iRetryAo->Cancel();
00136     iRetryAo->ResetFailures();
00137     DELETE_Z(iSatPositioner);
00138 
00139     if (bestId != KPositionNullModuleId) {
00140       TRAPD(errCode, CreateSpecifiedPositionerL(bestId));
00141       if (errCode) {
00142   er_log_symbian(0, errCode, "WARNING: failed to create positioner");
00143       }
00144     }
00145   }
00146   iModuleAo->MakeRequest();
00147 }
00148 
00149 TBool CSensor_cellpos::PosModIsCurrent(TPositionModuleId id) const
00150 {
00151   return (iModuleId != KPositionNullModuleId) && (iModuleId == id);
00152 }
00153 
00154 void CSensor_cellpos::PosModErrorL(TInt errCode)
00155 {
00156   er_log_symbian(0, errCode, 
00157      "INACTIVATE: cellpos: positioning module status tracking error");
00158   Stop();
00159 }
00160 
00161 // Called if PosModChangeL or PosModErrorL leaves.
00162 void CSensor_cellpos::PosModLeave(TInt errCode)
00163 {
00164   er_log_symbian(er_FATAL, errCode, "cellpos: leave in positioning module status reporting handler");
00165 }
00166 
00167 void CSensor_cellpos::RetryTimerExpired(CRetryAo* src, TInt errCode)
00168 {
00169   if (errCode) {
00170     er_log_symbian(er_FATAL, errCode, "cellpos: retry timer error");
00171     return;
00172   }
00173   assert(iSatPositioner);
00174   iSatPositioner->MakeRequest();
00175 }
00176 
00177 void CSensor_cellpos::BbChangedL(bb::RHandle* self, enum bb_DataType dt,
00178          gpointer data, int len)
00179 {
00180   if (!IsActive()) 
00181     return; // stopped
00182   switch (dt)
00183     {
00184     case bb_dt_cell_id:
00185       {
00186   guilogf("cellpos: cell changed");
00187   if (!iSatPositioner) {
00188     guilogf("cellpos: no positioner");
00189     return; // no positioner
00190   }
00191   if (iSatPositioner->IsActive() || iRetryAo->IsActive()) {
00192     guilogf("cellpos: already positioning");
00193     return; // doing positioning
00194   }
00195   TTime now;
00196   now.UniversalTime();
00197   TTime earliestTime(iLastScanTime.Int64());
00198   earliestTime += TTimeIntervalSeconds(iMinScanRequestIntervalSecs);
00199   if (now <= earliestTime) {
00200     guilogf("cellpos: too early");
00201     return; // too early
00202   }
00203   guilogf("cellpos: positioning...");
00204   iSatPositioner->MakeRequest();
00205         break;
00206       }
00207 
00208     case bb_dt_netpos_allowed:
00209       {
00210         gboolean value = GPOINTER_TO_INT(data);
00211   guilogf("cellpos: %s network access", value ? "allow" : "disallow");
00212   if (value != iAllowAssisted) {
00213     iAllowAssisted = value;
00214     PosModChangeL();
00215   }
00216         break;
00217       }
00218 
00219     default:
00220       {
00221   assert(0);
00222         break;
00223       }
00224     }
00225   
00226 }
00227 
00228 // xxx this could be shared with other satellite sensors
00229 static void LogSatelliteInfoL(LogDb* aLogDb, const char* aSensorName,
00230             const TPositionSatelliteInfo& positionInfo)
00231 {
00232 #define SAT_STRING_MAX 300
00233 #define COU_STRING_MAX 300
00234   gchar satString[SAT_STRING_MAX];
00235   gchar couString[COU_STRING_MAX];
00236   satString[0] = '\0';
00237   couString[0] = '\0';
00238 
00239   if (positionInfo.PositionClassType() & EPositionSatelliteInfoClass) {
00240     TInt satellites = positionInfo.NumSatellitesInView();
00241     TInt used_satellites = positionInfo.NumSatellitesUsed();
00242     TReal32 horizontal_dop = positionInfo.HorizontalDoP();
00243     TReal32 vertical_dop = positionInfo.VerticalDoP();
00244     TReal32 time_dop = positionInfo.TimeDoP();
00245     TTime satTime = positionInfo.SatelliteTime();
00246 
00247 #if __DO_LOGGING__
00248 #define TIME_STRING_MAX 50
00249     _LIT(KDateTimeFormat, "%F%Y-%M-%D %H:%T:%S.%C");
00250     // Note that KMaxTimeFormatSpec is the max size of the format
00251     // spec, and not the result, as I understand it.
00252     TBuf<TIME_STRING_MAX> satTime16;
00253     // The leave will not happen if the format string and data are
00254     // valid and the destination buffer is large enough.
00255     satTime.FormatL(satTime16, KDateTimeFormat);
00256     gchar satTime8[TIME_STRING_MAX + 1];
00257     ConvToUtf8CString(satTime8, TIME_STRING_MAX, satTime16);
00258 
00259     logg("satellite info: num satellites in view %d, num used satellites %d, hdop %g, vdop %g, tdop %g, satellite time %s", satellites, used_satellites, horizontal_dop, vertical_dop, time_dop, satTime8);
00260 #endif
00261 
00262     // We convert Symbian time to Unix time for consistency.
00263     time_t satUnixTime = LocalEpocTimeToUnixTime(satTime); // assuming 32-bit int
00264 
00265     // Build 'satString' for database insertion.
00266     {
00267       // "satellites": {"horizontal_dop": 2.20000004768372,
00268       // "used_satellites": 4, "vertical_dop": 1.0, "time":
00269       // 1225980390.097, "satellites": 10, "time_dop": 1.0}
00270       GString* gs = NULL;
00271       SET_TRAP_OOM(if (gs) g_string_free(gs, TRUE);
00272        User::LeaveNoMemory());
00273       gs = g_string_sized_new(128);
00274       g_string_printf(gs, "{time: %d", (int)satUnixTime);
00275       if (!Math::IsNaN(satellites))
00276   g_string_append_printf(gs, ", satellites: %d", satellites);
00277       if (!Math::IsNaN(used_satellites))
00278   g_string_append_printf(gs, ", used_satellites: %d", used_satellites);
00279       if (!Math::IsNaN(horizontal_dop))
00280   g_string_append_printf_fix(gs, ", horizontal_dop: %.6f", (double)horizontal_dop);
00281       if (!Math::IsNaN(vertical_dop))
00282   // %f and %g seem to be a bit broken for this function at
00283   // least, and maybe also for the other GLib printf
00284   // functions. We can try a more recent Open C, or
00285   // implement a replacement using C99 or Symbian functions.
00286   // Or possibly we could backport a more recent version of
00287   // the function in question from GLib, if there are not
00288   // too many internal dependencies.
00289   g_string_append_printf_fix(gs, ", vertical_dop: %.6f", (double)vertical_dop);
00290       if (!Math::IsNaN(time_dop))
00291   g_string_append_printf_fix(gs, ", time_dop: %.6f", (double)time_dop);
00292       g_string_append_c(gs, '}');
00293       UNSET_TRAP_OOM();
00294 
00295       if (gs->len < SAT_STRING_MAX)
00296   // For easier memory management.
00297   strcpy(satString, gs->str);
00298       else
00299   // Did not fit fully, do not log partial data that may not
00300   // be parseable.
00301   satString[0] = '\0';
00302       g_string_free(gs, TRUE);
00303       logt(satString);
00304     }
00305   }
00306 
00307   if (positionInfo.PositionClassType() & EPositionCourseInfoClass) {
00308     TCourse course;
00309     positionInfo.GetCourse(course);
00310     TReal32 speed = course.Speed();
00311     TReal32 heading = course.Heading();
00312     TReal32 course_value = course.Course();
00313     TReal32 speed_accuracy = course.SpeedAccuracy();
00314     TReal32 heading_accuracy = course.HeadingAccuracy();
00315     TReal32 course_accuracy = course.CourseAccuracy();
00316     logg("course info: speed %g m/s, heading %g deg, course %g, speed_accuracy %g m/s, heading_accuracy %d deg, course_accuracy %g", speed, heading, course_value, speed_accuracy, heading_accuracy, course_accuracy);
00317 
00318     // "course": {"speed": 6.0, "heading": 305.880004882812, "heading_accuracy": 5.80999994277954, "speed_accuracy": 8.35999965667725
00319     {
00320 #define APPEND_STRING(s) { int slen = strlen(s); if (space <= slen) goto fail; space -= slen; strcpy(output, s);  output += slen; }
00321 #define APPEND_SEP { if (!first) APPEND_STRING(", "); }
00322 #define COU_UPDATE_STATE(r) { if (r < 0 || r >= space) goto fail; space -= r; output += r; first = EFalse; }
00323       int space = COU_STRING_MAX;
00324       int r;
00325       TBool first = ETrue;
00326       gchar* output = couString;
00327       APPEND_STRING("{");
00328       if (!Math::IsNaN(speed)) { r = g_snprintf_fix(output, space, "speed: %.6f", (double)speed); COU_UPDATE_STATE(r); }
00329       if (!Math::IsNaN(heading)) { APPEND_SEP; r = g_snprintf_fix(output, space, "heading: %.6f", (double)heading); COU_UPDATE_STATE(r); }
00330       if (!Math::IsNaN(course_value)) { APPEND_SEP; r = g_snprintf_fix(output, space, "course: %.6f", (double)course_value); COU_UPDATE_STATE(r); }
00331       if (!Math::IsNaN(speed_accuracy)) { APPEND_SEP; r = g_snprintf_fix(output, space, "speed_accuracy: %.6f", (double)speed_accuracy); COU_UPDATE_STATE(r); }
00332       if (!Math::IsNaN(heading_accuracy)) { APPEND_SEP; r = g_snprintf_fix(output, space, "heading_accuracy: %.6f", (double)heading_accuracy); COU_UPDATE_STATE(r); }
00333       if (!Math::IsNaN(course_accuracy)) { APPEND_SEP; r = g_snprintf_fix(output, space, "course_accuracy: %.6f", (double)course_accuracy); COU_UPDATE_STATE(r); }
00334       APPEND_STRING("}");
00335       goto done;
00336     fail:
00337       couString[0] = '\0';
00338     done: ;
00339       logt(couString);
00340     }
00341   }
00342 
00343   if (positionInfo.PositionClassType() & EPositionInfoClass) {
00344     TPosition position;
00345     positionInfo.GetPosition(position);
00346     // It seems that with network based positioning we only get
00347     // lat and lon and hacc, and the rest are NaN.
00348     TReal64 lat = position.Latitude();
00349     TReal64 lon = position.Longitude();
00350     // To use Google Maps to look these up, can use something like http://maps.google.com/maps?q=60.187537,+24.804940&hl=en
00351     TReal32 alt = position.Altitude();
00352     TReal32 vacc = position.VerticalAccuracy();
00353     TReal32 hacc = position.HorizontalAccuracy();
00354     //logg("position info: latitude %lg deg, longitude %lg deg, altitude %g m, vertical_accuracy %g m, horizontal_accuracy %g m", lat, lon, alt, vacc, hacc);
00355     guilogf("%s: lat %lg, lon %lg, alt %g, vacc %g m, hacc %g m", aSensorName, lat, lon, alt, vacc, hacc);
00356     if (!Math::IsNaN(lat) &&
00357   !Math::IsNaN(lon)) {
00358       log_db_log_position(aLogDb, lat, lon, alt, vacc, hacc, couString, satString, NULL);
00359     } else {
00360       logt("supposedly full reading has NaN value(s) in coordinates");
00361     }
00362   }
00363 }
00364 
00365 gboolean CSensor_cellpos::PositionerEventL(GError** error)
00366 {
00367   assert_error_unset(error);
00368   assert(iSatPositioner);
00369 
00370   iLastScanTime.UniversalTime();
00371 
00372   // See "lbserrors.h" for LBS-specific return codes for
00373   // NotifyPositionUpdate requests. Note that somewhat unusually there
00374   // are some positive values as well.
00375   TInt errCode = iSatPositioner->StatusCode();
00376 
00377   if (errCode < 0) {
00378     // Do not yet quite know what sort of errors might be getting, so
00379     // shall merely log errors and keep retrying. Do want to make
00380     // sure, however, that we do not get an awful lot of errors
00381     // growing our log file to an unreasonable size, and hence will
00382     // stop the scanner if there are lots of consecutive error
00383     // completions. We can be cleverer here once we know what sort of
00384     // errors we might be getting. But it is reasonable to assume that
00385     // there may be immediate error returns in cases such as a
00386     // positioning module being or having become unavailable.
00387     switch (errCode) { // see <lbserrors.h>
00388     case KErrTimedOut:
00389       // Can expect to get this after the time period specified with
00390       // SetUpdateTimeOut. If we didn't get the position in that time,
00391       // then we did not.
00392       guilogf("cellpos: GPS timeout");
00393       break;
00394 
00395     case KErrServerBusy:
00396       // If the server is busy, let us then just wait for the next
00397       // cell ID change to avoid burdening it further.
00398       guilogf("cellpos: positioner busy");
00399       break;
00400 
00401       guilogf("cellpos: positioning error");
00402 
00403       // Used positioning module became unavailable. (Before we had
00404       // time to stop using it.)
00405     case KErrNotFound:
00406       // Perhaps some capability thing.
00407     case KErrAccessDenied: 
00408       // Could apparently happen if the user refuses to connect an
00409       // external Bluetooth GPS device, for example.
00410     case KErrCancel:
00411       er_log_symbian(0, errCode, 
00412          "cellpos: positioner error, giving up on positioner");
00413       // We will not reset state further, lest we be asked to switch
00414       // back to the same module again.
00415       DELETE_Z(iSatPositioner);
00416       break;
00417 
00418     case KErrArgument:
00419     case KErrPositionBufferOverflow:
00420       er_log_symbian(er_FATAL, errCode, "cellpos: unexpected positioning error");
00421       break;
00422 
00423     case KErrBadHandle: // positioning service session dead
00424       // We should really recreate session, but why was it close in
00425       // the first place, something not right here.
00426       er_log_symbian(0, errCode, 
00427          "INACTIVATE: cellpos: position server session died");
00428       Stop();
00429       break;
00430 
00431     default:
00432       // We may not actually need the retry AO at all. The cell ID
00433       // changes will determine when to retry.
00434       er_log_symbian(0, errCode, 
00435          "cellpos: positioner error, not retrying");
00436       /*
00437       if (!iRetryAo->Retry()) {
00438   er_log_symbian(0, errCode, "INACTIVATE: cellpos: stopping scanning due to too many errors");
00439   Stop();
00440       }
00441       */
00442     }
00443   } else { // errCode >= 0 (yes, there are _positive_ codes as well)
00444     iRetryAo->ResetFailures();
00445 
00446     switch (errCode) { // see <lbserrors.h>
00447     case KPositionQualityLoss: // no data
00448       {
00449   guilogf("cellpos: got no GPS data");
00450   break;
00451       }
00452       
00453     case KErrNone: // full data
00454     case KPositionPartialUpdate: // partial data
00455       {
00456 #if __DO_LOGGING__
00457       TBool isPartial = (errCode == KPositionPartialUpdate);
00458       logg("got %s gps reading", isPartial ? "partial" : "full");
00459 #endif
00460 
00461       // Is it so that if some module does not support satellite info
00462       // then if we try to request it we get nothing? Well, we are
00463       // only choosing GPS modules, and GPS is based on satellites, so
00464       // surely there always is satellite info.
00465       const TPositionSatelliteInfo& positionInfo = iSatPositioner->PositionInfo();
00466 
00467       LogSatelliteInfoL(iLogDb, "cellpos", positionInfo);
00468       
00469       break;
00470       }
00471 
00472     default: // unknown completion code
00473       {
00474   guilogf("cellpos: unknown completion code %d", errCode);
00475   log_db_log_status(iLogDb, NULL, 
00476         "cellpos: unknown completion code %d", errCode);
00477       }
00478     }
00479   }
00480 
00481   return TRUE;
00482 }
00483 
00484 void CSensor_cellpos::Reconfigure(const gchar* name, const gchar* value)
00485 {
00486   if ((strcmp(name, "sensor.cellpos.frequency") == 0) ||
00487       (strcmp(name, "sensor.cellpos.timeout") == 0)) {
00488     ReadConfig();
00489   }
00490 }
00491 
00492 // The paremeter updates do not necessarily take effect immediately.
00493 void CSensor_cellpos::ReadConfig()
00494 {
00495   iMinScanRequestIntervalSecs =
00496     force_get_ConfigDb_int("sensor.cellpos.frequency", 
00497          MIN_SCAN_REQUEST_INTERVAL_SECS);
00498   iSatelliteQueryTimeoutSecs = 
00499     force_get_ConfigDb_int("sensor.cellpos.timeout", 
00500          SATELLITE_QUERY_TIMEOUT_SECS);
00501   iDataMaxAgeSecs = 
00502     force_get_ConfigDb_int("sensor.cellpos.ttl", 
00503          POSITIONER_MAX_AGE_SECS);
00504 }
00505 
00506 /**
00507 
00508 epoc-cellpos.cpp
00509 
00510 Copyright 2009-2011 Helsinki Institute for Information Technology
00511 (HIIT) and the authors. All rights reserved.
00512 
00513 Authors: Tero Hasu <tero.hasu@hut.fi>
00514 
00515 Permission is hereby granted, free of charge, to any person
00516 obtaining a copy of this software and associated documentation files
00517 (the "Software"), to deal in the Software without restriction,
00518 including without limitation the rights to use, copy, modify, merge,
00519 publish, distribute, sublicense, and/or sell copies of the Software,
00520 and to permit persons to whom the Software is furnished to do so,
00521 subject to the following conditions:
00522 
00523 The above copyright notice and this permission notice shall be
00524 included in all copies or substantial portions of the Software.
00525 
00526 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00527 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00528 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00529 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
00530 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
00531 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
00532 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00533 SOFTWARE.
00534 
00535  **/

ContextLogger2—ContextLogger2 Logger Daemon Internals—Generated on Mon May 2 13:49:52 2011 by Doxygen 1.6.1