2 * Copyright (c) 2016 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
23 #include <UserPlacesTypes.h>
24 #include "VisitDetector.h"
25 #include <UserPlacesParams.h>
26 #include <Similarity.h>
28 #include <DebugUtils.h>
30 #include <DatabaseManager.h>
32 #define SO_PATH _LIBDIR_ "/context-service/libctx-prvd-my-place-visit-categer.so"
34 typedef void (*visit_categer_t)(ctx::Visit &visit);
36 #ifdef TIZEN_ENGINEER_MODE
37 #define __VISIT_TABLE_COLUMNS \
38 VISIT_COLUMN_WIFI_APS " TEXT, "\
39 VISIT_COLUMN_START_TIME " timestamp, "\
40 VISIT_COLUMN_END_TIME " timestamp, "\
41 VISIT_COLUMN_START_TIME_HUMAN " TEXT, "\
42 VISIT_COLUMN_END_TIME_HUMAN " TEXT, "\
43 VISIT_COLUMN_LOCATION_VALID " INTEGER, "\
44 VISIT_COLUMN_LOCATION_LATITUDE " REAL, "\
45 VISIT_COLUMN_LOCATION_LONGITUDE " REAL, "\
46 VISIT_COLUMN_LOCATION_ACCURACY " REAL, "\
47 VISIT_COLUMN_CATEG_HOME " REAL, "\
48 VISIT_COLUMN_CATEG_WORK " REAL, "\
49 VISIT_COLUMN_CATEG_OTHER " REAL"
50 #else /* TIZEN_ENGINEER_MODE */
51 #define __VISIT_TABLE_COLUMNS \
52 VISIT_COLUMN_WIFI_APS " TEXT, "\
53 VISIT_COLUMN_START_TIME " timestamp, "\
54 VISIT_COLUMN_END_TIME " timestamp, "\
55 VISIT_COLUMN_LOCATION_VALID " INTEGER, "\
56 VISIT_COLUMN_LOCATION_LATITUDE " REAL, "\
57 VISIT_COLUMN_LOCATION_LONGITUDE " REAL, "\
58 VISIT_COLUMN_LOCATION_ACCURACY " REAL, "\
59 VISIT_COLUMN_CATEG_HOME " REAL, "\
60 VISIT_COLUMN_CATEG_WORK " REAL, "\
61 VISIT_COLUMN_CATEG_OTHER " REAL"
62 #endif /* TIZEN_ENGINEER_MODE */
64 #define __WIFI_APS_MAP_TABLE_COLUMNS \
65 WIFI_APS_MAP_COLUMN_MAC " TEXT NOT NULL UNIQUE, "\
66 WIFI_APS_MAP_COLUMN_NETWORK_NAME " TEXT NOT NULL, "\
67 WIFI_APS_MAP_COLUMN_INSERT_TIME " timestamp"
69 ctx::VisitDetector::VisitDetector(time_t startScan, PlaceRecogMode energyMode, bool testMode) :
71 __locationLogger(testMode ? nullptr : new(std::nothrow) LocationLogger(this)),
72 __wifiLogger(testMode ? nullptr : new(std::nothrow) WifiLogger(this, energyMode)),
73 __currentInterval(startScan, startScan + VISIT_DETECTOR_PERIOD_SECONDS_HIGH_ACCURACY),
75 __tolerance(VISIT_DETECTOR_TOLERANCE_DEPTH),
76 __entranceToPlace(false),
77 __periodSeconds(VISIT_DETECTOR_PERIOD_SECONDS_HIGH_ACCURACY),
82 __setPeriod(energyMode);
83 __currentInterval = Interval(startScan, startScan + __periodSeconds);
84 __currentMacEvents = std::make_shared<MacEvents>();
85 __stayMacs = std::make_shared<MacSet>();
88 __detectedVisits = std::make_shared<ctx::Visits>();
92 __listeners.push_back(__locationLogger);
93 __listeners.push_back(__wifiLogger);
96 __wifiLogger->startLogging();
99 ctx::VisitDetector::~VisitDetector()
102 if (__locationLogger)
103 delete __locationLogger;
108 bool ctx::VisitDetector::__isValid(const ctx::Mac &mac)
110 return mac != "00:00:00:00:00:00";
113 void ctx::VisitDetector::onWifiScan(ctx::MacEvent e)
115 _D("timestamp=%d, current_interval.end=%d, mac=%s, network=%s",
117 __currentInterval.end,
118 std::string(e.mac).c_str(),
119 e.networkName.c_str());
120 if (__isValid(e.mac)) {
121 while (e.timestamp > __currentInterval.end) {
122 __processCurrentLogger();
123 __shiftCurrentInterval();
125 __currentMacEvents->push_back(e);
129 void ctx::VisitDetector::__processCurrentLogger()
132 std::shared_ptr<ctx::Frame> frame = __makeFrame(__currentMacEvents, __currentInterval);
133 __detectEntranceOrDeparture(frame);
134 __currentMacEvents->clear();
137 std::shared_ptr<ctx::Frame> ctx::VisitDetector::__makeFrame(std::shared_ptr<ctx::MacEvents> logger, ctx::Interval interval)
139 std::set<time_t> timestamps;
140 std::shared_ptr<Frame> frame = std::make_shared<Frame>(interval);
141 for (auto log : *logger) {
142 timestamps.insert(log.timestamp);
143 if (frame->macs2Counts.find(log.mac) == frame->macs2Counts.end()) {
144 frame->macs2Counts[log.mac] = 1;
146 frame->macs2Counts[log.mac] += 1;
149 frame->numberOfTimestamps = timestamps.size();
153 void ctx::VisitDetector::__shiftCurrentInterval()
155 __currentInterval.end += __periodSeconds;
156 __currentInterval.start += __periodSeconds;
159 void ctx::VisitDetector::__detectEntranceOrDeparture(std::shared_ptr<ctx::Frame> frame)
161 __entranceToPlace ? __detectDeparture(frame) : __detectEntrance(frame);
162 if (__entranceToPlace) {
163 for (MacEvent e : *__currentMacEvents) {
164 __wifiAPsMap.insert(std::pair<std::string, std::string>(e.mac, e.networkName));
169 bool ctx::VisitDetector::__isDisjoint(const ctx::Macs2Counts &macs2Counts, const ctx::MacSet &macSet)
171 for (auto &mac : macSet) {
172 if (macs2Counts.find(mac) != macs2Counts.end())
178 bool ctx::VisitDetector::__protrudesFrom(const ctx::Macs2Counts &macs2Counts, const ctx::MacSet &macSet)
180 for (auto &macCount : macs2Counts) {
181 if (macSet.find(macCount.first) == macSet.end())
187 void ctx::VisitDetector::__detectDeparture(std::shared_ptr<ctx::Frame> frame)
189 if (__tolerance == VISIT_DETECTOR_TOLERANCE_DEPTH) {
190 __departureTime = frame->interval.start;
191 __bufferedFrames.clear();
192 } else { // __tolerance < VISIT_DETECTOR_TOLERANCE_DEPTH
193 __bufferedFrames.push_back(frame);
195 if (__isDisjoint(frame->macs2Counts, *__representativesMacs)) {
196 if (frame->macs2Counts.empty() || __protrudesFrom(frame->macs2Counts, *__stayMacs)) {
198 } else { // no new macs
199 __bufferedFrames.clear();
201 if (__tolerance == 0) { // departure detected
202 __visitEndDetected();
203 __processBuffer(frame);
205 } else if (__tolerance < VISIT_DETECTOR_TOLERANCE_DEPTH) {
210 void ctx::VisitDetector::__visitStartDetected()
212 __entranceToPlace = true;
214 __locationEvents.clear();
216 for (IVisitListener* listener : __listeners) {
217 listener->onVisitStart();
220 __representativesMacs = __selectRepresentatives(__historyFrames);
221 __entranceTime = __historyFrames[0]->interval.start;
222 _D("Entrance detected, timestamp: %d", __entranceTime);
226 void ctx::VisitDetector::__visitEndDetected()
229 for (IVisitListener* listener : __listeners) {
230 listener->onVisitEnd();
233 _D("Departure detected, timestamp: %d", __departureTime);
235 Interval interval(__entranceTime, __departureTime);
236 Visit visit(interval, __representativesMacs);
239 __putLocationToVisit(visit);
242 __detectedVisits->push_back(visit);
244 __dbInsertVisit(visit);
245 __dbInsertWifiAPsMap(visit);
249 __entranceToPlace = false;
250 __representativesMacs.reset();
251 __tolerance = VISIT_DETECTOR_TOLERANCE_DEPTH;
254 void ctx::VisitDetector::__putLocationToVisit(ctx::Visit &visit)
256 // TODO: filter out small accuracy locations?
257 std::vector<double> latitudes;
258 std::vector<double> longitudes;
259 std::vector<double> accuracy;
260 visit.locationValid = false;
261 for (LocationEvent &location : __locationEvents) {
262 if (location.timestamp >= __entranceTime && location.timestamp <= __departureTime) {
263 latitudes.push_back(location.coordinates.latitude);
264 longitudes.push_back(location.coordinates.longitude);
265 accuracy.push_back(location.coordinates.accuracy);
266 visit.locationValid = true;
269 if (visit.locationValid) {
270 visit.location = medianLocation(latitudes, longitudes, accuracy);
271 _D("visit location set: lat=%.8f, lon=%.8f, acc=%.8f",
272 visit.location.latitude,
273 visit.location.longitude,
274 visit.location.accuracy);
276 _D("visit location not set");
280 void ctx::VisitDetector::__processBuffer(std::shared_ptr<ctx::Frame> frame)
282 if (__bufferedFrames.empty()) {
283 __historyFrames.push_back(frame);
285 __historyFrames.push_back(__bufferedFrames[0]);
286 for (size_t i = 1; i < __bufferedFrames.size(); i++) {
287 __detectEntrance(__bufferedFrames[i]);
288 if (__entranceToPlace)
294 void ctx::VisitDetector::__detectEntrance(std::shared_ptr<ctx::Frame> currentFrame)
296 if (currentFrame->macs2Counts.empty() || __historyFrames.empty()) {
297 __resetHistory(currentFrame);
301 if (__stableCounter == 0) {
302 std::shared_ptr<Frame> oldestHistoryFrame = __historyFrames[0];
303 __stayMacs = macSetFromMacs2Counts(oldestHistoryFrame->macs2Counts);
306 std::shared_ptr<MacSet> currentBeacons = macSetFromMacs2Counts(currentFrame->macs2Counts);
308 if (similarity::overlapBiggerOverSmaller(*currentBeacons, *__stayMacs) > VISIT_DETECTOR_OVERLAP) {
310 __historyFrames.push_back(currentFrame);
311 if (__stableCounter == VISIT_DETECTOR_STABLE_DEPTH) // entrance detected
312 __visitStartDetected();
314 __resetHistory(currentFrame);
319 void ctx::VisitDetector::__resetHistory()
322 __historyFrames.clear();
325 void ctx::VisitDetector::__resetHistory(std::shared_ptr<Frame> frame)
328 __historyFrames.push_back(frame);
331 std::shared_ptr<ctx::MacSet> ctx::VisitDetector::__selectRepresentatives(const std::vector<std::shared_ptr<Frame>> &frames)
333 Macs2Counts reprs2Counts;
334 count_t allCount = 0;
336 for (auto frame : frames) {
337 allCount += frame->numberOfTimestamps;
338 for (auto &c : frame->macs2Counts) {
339 reprs2Counts[c.first] += c.second;
343 std::shared_ptr<Macs2Shares> reprs2Shares = __macSharesFromCounts(reprs2Counts, allCount);
345 share_t maxShare = __calcMaxShare(*reprs2Shares);
346 share_t threshold = maxShare < VISIT_DETECTOR_REP_THRESHOLD ? maxShare : VISIT_DETECTOR_REP_THRESHOLD;
348 std::shared_ptr<MacSet> reprsMacSet = __macSetOfGreaterOrEqualShare(*reprs2Shares, threshold);
353 ctx::share_t ctx::VisitDetector::__calcMaxShare(const ctx::Macs2Shares &macs2Shares)
355 ctx::share_t maxShare = 0.0;
356 for (auto &macShare : macs2Shares) {
357 if (macShare.second > maxShare)
358 maxShare = macShare.second;
363 std::shared_ptr<ctx::MacSet> ctx::VisitDetector::__macSetOfGreaterOrEqualShare(const ctx::Macs2Shares &macs2Shares, ctx::share_t threshold)
365 std::shared_ptr<MacSet> macSet = std::make_shared<MacSet>();
366 for (auto &macShare : macs2Shares) {
367 if (macShare.second >= threshold)
368 macSet->insert(macShare.first);
373 std::shared_ptr<ctx::Macs2Shares> ctx::VisitDetector::__macSharesFromCounts(ctx::Macs2Counts const &macs2Counts, ctx::count_t denominator)
375 std::shared_ptr<Macs2Shares> macs2Shares(std::make_shared<Macs2Shares>());
376 for (auto macCount : macs2Counts) {
377 (*macs2Shares)[macCount.first] = (share_t) macCount.second / denominator;
382 std::shared_ptr<ctx::Visits> ctx::VisitDetector::__getVisits()
384 return __detectedVisits;
387 void ctx::VisitDetector::__dbCreateTables()
389 DatabaseManager dbManager;
390 bool ret = dbManager.createTableSync(VISIT_TABLE, __VISIT_TABLE_COLUMNS);
391 _D("db: Visit Table Creation Result: %s", ret ? "SUCCESS" : "FAIL");
393 ret = dbManager.createTableSync(WIFI_APS_MAP_TABLE, __WIFI_APS_MAP_TABLE_COLUMNS);
394 _D("db: Wifi AP Map Table Creation Result: %s", ret ? "SUCCESS" : "FAIL");
397 void ctx::VisitDetector::__putVisitCategToJson(const char* key, const Categs &categs, int categType, Json &data)
399 auto categ = categs.find(categType);
400 if (categ == categs.end()) {
401 _E("json_put_visit no type %d in categs", categType);
403 data.set(NULL, key, categ->second);
407 void ctx::VisitDetector::__putVisitCategsToJson(const Categs &categs, Json &data)
409 __putVisitCategToJson(VISIT_COLUMN_CATEG_HOME, categs, PLACE_CATEG_ID_HOME, data);
410 __putVisitCategToJson(VISIT_COLUMN_CATEG_WORK, categs, PLACE_CATEG_ID_WORK, data);
411 __putVisitCategToJson(VISIT_COLUMN_CATEG_OTHER, categs, PLACE_CATEG_ID_OTHER, data);
414 int ctx::VisitDetector::__dbInsertVisit(Visit visit)
416 std::stringstream ss;
420 data.set(NULL, VISIT_COLUMN_WIFI_APS, ss.str().c_str());
422 data.set(NULL, VISIT_COLUMN_LOCATION_VALID, visit.locationValid);
423 data.set(NULL, VISIT_COLUMN_LOCATION_LATITUDE, visit.location.latitude);
424 data.set(NULL, VISIT_COLUMN_LOCATION_LONGITUDE, visit.location.longitude);
425 data.set(NULL, VISIT_COLUMN_LOCATION_ACCURACY, visit.location.accuracy);
427 data.set(NULL, VISIT_COLUMN_START_TIME, static_cast<int>(visit.interval.start));
428 data.set(NULL, VISIT_COLUMN_END_TIME, static_cast<int>(visit.interval.end));
430 #ifdef TIZEN_ENGINEER_MODE
431 std::string startTimeHuman = DebugUtils::humanReadableDateTime(visit.interval.start, "%F %T", 80);
432 std::string endTimeHuman = DebugUtils::humanReadableDateTime(visit.interval.end, "%F %T", 80);
433 data.set(NULL, VISIT_COLUMN_START_TIME_HUMAN, startTimeHuman.c_str());
434 data.set(NULL, VISIT_COLUMN_END_TIME_HUMAN, endTimeHuman.c_str());
435 _D("db: visit table insert interval: (%d, %d): (%s, %s)",
436 visit.interval.start, visit.interval.end, startTimeHuman.c_str(), endTimeHuman.c_str());
438 _D("db: visit table insert interval: (%d, %d)", visit.interval.start, visit.interval.end);
439 #endif /* TIZEN_ENGINEER_MODE */
441 __putVisitCategsToJson(visit.categs, data);
444 DatabaseManager dbManager;
445 bool ret = dbManager.insertSync(VISIT_TABLE, data, &rowId);
446 _D("db: visit table insert result: %s", ret ? "SUCCESS" : "FAIL");
450 int ctx::VisitDetector::__dbInsertWifiAPsMap(Visit visit)
452 std::stringstream query;
453 time_t now = time(nullptr);
454 const char* separator = " ";
455 query << "BEGIN TRANSACTION; \
456 REPLACE INTO " WIFI_APS_MAP_TABLE " \
457 ( " WIFI_APS_MAP_COLUMN_MAC ", " WIFI_APS_MAP_COLUMN_NETWORK_NAME ", " WIFI_APS_MAP_COLUMN_INSERT_TIME " ) \
459 for (Mac mac : *visit.macSet) {
460 // TODO: Add protection from SQL injection in network name!!
461 query << separator << "( '" << mac << "', '" << __wifiAPsMap.find(mac)->second << "', '" << now << "' )";
464 __wifiAPsMap.clear();
467 DatabaseManager dbManager;
468 bool ret = dbManager.execute(0, query.str().c_str(), NULL);
469 _D("DB Wifi APs map insert request: %s", ret ? "SUCCESS" : "FAIL");
473 void ctx::VisitDetector::onNewLocation(LocationEvent locationEvent)
477 __locationEvents.push_back(locationEvent);
480 void ctx::VisitDetector::__setPeriod(PlaceRecogMode energyMode)
482 switch (energyMode) {
483 case PLACE_RECOG_LOW_POWER_MODE:
484 __periodSeconds = VISIT_DETECTOR_PERIOD_SECONDS_LOW_POWER;
486 case PLACE_RECOG_HIGH_ACCURACY_MODE:
487 __periodSeconds = VISIT_DETECTOR_PERIOD_SECONDS_HIGH_ACCURACY;
490 _E("Incorrect energy mode");
494 void ctx::VisitDetector::setMode(PlaceRecogMode energyMode)
497 __setPeriod(energyMode);
499 __wifiLogger->setMode(energyMode);
502 void ctx::VisitDetector::__categorize(ctx::Visit &visit)
505 GModule *soHandle = g_module_open(SO_PATH, G_MODULE_BIND_LAZY);
506 IF_FAIL_VOID_TAG(soHandle, _E, "%s", g_module_error());
509 if (!g_module_symbol(soHandle, "categorize", &symbol) || symbol == NULL) {
510 _E("%s", g_module_error());
511 g_module_close(soHandle);
515 visit_categer_t categorize = reinterpret_cast<visit_categer_t>(symbol);
518 g_module_close(soHandle);