[place recognition] VisitDetector class logic 60/51760/1
authorTomasz Toczyski <t.toczyski@samsung.com>
Fri, 13 Nov 2015 13:44:19 +0000 (14:44 +0100)
committerTomasz Toczyski <t.toczyski@samsung.com>
Fri, 13 Nov 2015 13:44:19 +0000 (14:44 +0100)
Change-Id: I0fa7919dfd5d82610a3800124fd63ec8e0b387d8
Signed-off-by: Tomasz Toczyski <t.toczyski@samsung.com>
src/place_recognition/place_recognition_types.h
src/place_recognition/user_places/median.cpp [new file with mode: 0644]
src/place_recognition/user_places/median.h [new file with mode: 0644]
src/place_recognition/user_places/similar.h [new file with mode: 0644]
src/place_recognition/user_places/user_places_params.h
src/place_recognition/user_places/user_places_types.cpp
src/place_recognition/user_places/user_places_types.h
src/place_recognition/user_places/visit_detector.cpp
src/place_recognition/user_places/visit_detector.h

index acf6ad7..2a7cce6 100644 (file)
 #define PLACE_PRIV_RECOGNITION                              "location"
 
 // Database
+#define VISIT_TABLE                            "place_status_user_place_visit"
+#define VISIT_COLUMN_START_TIME                "start_time"
+#define VISIT_COLUMN_END_TIME                  "end_time"
+#define VISIT_COLUMN_WIFI_APS                  "wifi_aps"
+#define VISIT_COLUMN_CATEGORY                  "category"
+#define VISIT_COLUMN_START_TIME_HUMAN          "start_time_human" // only for debug: human readable time data:
+#define VISIT_COLUMN_END_TIME_HUMAN            "end_time_human" // only for debug: human readable time data:
+#define VISIT_COLUMN_LOCATION_VALID            "geo_valid"
+#define VISIT_COLUMN_LOCATION_LATITUDE         "geo_latitude"
+#define VISIT_COLUMN_LOCATION_LONGITUDE        "geo_longitude"
+#define VISIT_COLUMN_CATEG_HOME                "categ_home"
+#define VISIT_COLUMN_CATEG_WORK                "categ_work"
+#define VISIT_COLUMN_CATEG_OTHER               "categ_other"
 
 #define WIFI_TABLE_NAME                        "place_status_user_place_wifi"
 #define WIFI_COLUMN_TIMESTAMP                  "timestamp"
diff --git a/src/place_recognition/user_places/median.cpp b/src/place_recognition/user_places/median.cpp
new file mode 100644 (file)
index 0000000..557c294
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <types_internal.h>
+#include "median.h"
+
+ctx::num_t ctx::median(std::vector<ctx::num_t> &v)
+{
+       if (v.empty()) {
+               _E("Median of empty set");
+               return 0; // this value does not make any sense
+       }
+       size_t n = v.size() / 2;
+       std::nth_element(v.begin(), v.begin() + n, v.end());
+       num_t vn = v[n];
+       if (v.size() % 2 == 1) {
+               return vn;
+       } else {
+               std::nth_element(v.begin(), v.begin() + n - 1, v.end());
+               return 0.5 * (vn + v[n - 1]);
+       }
+}
diff --git a/src/place_recognition/user_places/median.h b/src/place_recognition/user_places/median.h
new file mode 100644 (file)
index 0000000..c6612dc
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __CONTEXT_PLACE_STATUS_MEDIAN__
+#define __CONTEXT_PLACE_STATUS_MEDIAN__
+
+#include "user_places_types.h"
+#include <vector>
+#include "../place_recognition_types.h"
+
+namespace ctx {
+
+       num_t median(std::vector<num_t> &values); // caution: the input vector will be sorted
+
+} /* namespace ctx */
+
+#endif /* __CONTEXT_PLACE_STATUS_MEDIAN__ */
diff --git a/src/place_recognition/user_places/similar.h b/src/place_recognition/user_places/similar.h
new file mode 100644 (file)
index 0000000..1495de8
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __CONTEXT_PLACE_STATUS_SIMILAR_H__
+#define __CONTEXT_PLACE_STATUS_SIMILAR_H__
+
+#include "user_places_types.h"
+
+namespace ctx {
+
+       /* similarity functions */
+
+       template <class T> ctx::share_t overlap_first_over_second(const T &s1, const T &s2)
+       {
+               if (s2.empty()) {
+                       return 0;
+               }
+               int count = 0;
+               for (auto e : s2) {
+                       if (s1.find(e) != s1.end()) {
+                               count++;
+                       }
+               }
+               return (ctx::share_t) count / s2.size();
+       }
+
+       template <class T> ctx::share_t overlap_bigger_over_smaller(const T &s1, const T &s2)
+       {
+               if (s1.size() > s2.size()) {
+                       return overlap_first_over_second(s1, s2);
+               } else {
+                       return overlap_first_over_second(s2, s1);
+               }
+       }
+
+       template <class T> bool is_joint(const T &s1, const T &s2)
+       {
+               for (auto e : s2) {
+                       if (s1.find(e) != s1.end()) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+}      /* namespace ctx */
+
+#endif /* __CONTEXT_PLACE_STATUS_SIMILAR_H__ */
index a8450af..5e5a7af 100644 (file)
  */
 #define WIFI_LOGGER_INTERVAL_MINUTES 3
 
+/*
+ * Time window taken into consideration (in seconds).
+ */
+#define VISIT_DETECTOR_PERIOD_SECONDS 360
+
+/*
+ * Overlap threshold between two sets of mac addresses (overlap
+ * coefficient for two sets should be higher than this threshold
+ * in order to detect stable radio environment); =< 1.0
+ * New parameter in algorithm compared to original version of PlaceSense!
+ */
+#define VISIT_DETECTOR_OVERLAP 0.8f
+
+/*
+ * Specifies how many stable intervals must be seen to
+ * indicate an entrance to a place; >= 1
+ */
+#define VISIT_DETECTOR_STABLE_DEPTH 1
+
+/*
+ * Representatives threshold (representatnive beacon
+ * response rate should be higher than this threshold); =< 1.0
+ */
+#define VISIT_DETECTOR_REP_THRESHOLD 0.9f
+
+/*
+ * Specifies how long scans must be unstable to indicate a leave form a place; >= 1
+ */
+#define VISIT_DETECTOR_TOLERANCE_DEPTH 3
+
 
 /*
  * Number of digits after decimal point used in geo coordinates.
index bb6aa55..8083327 100644 (file)
@@ -111,6 +111,43 @@ bool ctx::operator<(const Mac &m1, const Mac &m2)
        return false; // they are equal
 }
 
+std::istream& ctx::operator>>(std::istream &input, ctx::mac_set_t &mac_set)
+{
+       Mac mac;
+       char delimeter;
+       while (!input.eof()) {
+               input >> mac;
+               mac_set.insert(mac);
+               if (input.eof()) {
+                       break;
+               }
+               delimeter = input.get();
+               if (delimeter != MAC_SET_STRING_DELIMITER) {
+                       input.unget();
+                       break;
+               }
+       }
+       return input;
+}
+
+std::ostream& ctx::operator<<(std::ostream &output, const ctx::mac_set_t &mac_set)
+{
+       std::vector<Mac> mac_vec(mac_set.size());
+       std::copy(mac_set.begin(), mac_set.end(), mac_vec.begin());
+       std::sort(mac_vec.begin(), mac_vec.end());
+
+       bool first = true;
+       for (auto &mac: mac_vec) {
+               if (first) {
+                       first = false;
+               } else {
+                       output << MAC_SET_STRING_DELIMITER;
+               }
+               output << mac;
+       }
+       return output;
+}
+
 void ctx::location_event_s::log()
 {
        std::string time_str = DebugUtils::human_readable_date_time(timestamp, "%T", 9);
@@ -122,3 +159,68 @@ void ctx::location_event_s::log()
                        method);
 }
 
+void ctx::visit_s::set_location(location_s location_)
+{
+       location_valid = true;
+       location = location_;
+}
+
+void ctx::visit_s::print_short_to_stream(std::ostream &out) const
+{
+       // print only valid visits
+       if (interval.end != 0) {
+               float duration = ((float) (interval.end - interval.start)) / 3600; // [h]
+               out << "__VISIT " << duration << "h: ";
+                       out << DebugUtils::human_readable_date_time(interval.start, "%m/%d %H:%M", 15) << " รท ";
+                       out << DebugUtils::human_readable_date_time(interval.end, "%m/%d %H:%M", 15) << std::endl;
+       }
+}
+
+bool ctx::operator==(const ctx::visit_s &v1, const ctx::visit_s &v2)
+{
+       return v1.interval.start == v2.interval.start
+                       && v1.interval.end == v2.interval.end
+                       && v1.location.latitude == v2.location.latitude
+                       && v1.location.longitude == v2.location.longitude
+                       && v1.location.accuracy == v2.location.accuracy
+                       && v1.location_valid == v2.location_valid
+                       && v1.mac_set == v2.mac_set;
+}
+
+ctx::mac_set_t ctx::mac_set_from_string(const std::string &str)
+{
+       mac_set_t mac_set;
+       std::stringstream ss;
+       ss << str;
+       ss >> mac_set;
+       return mac_set;
+}
+
+bool ctx::operator>(const Mac &m1, const Mac &m2)
+{
+       return m2 < m1;
+}
+
+std::shared_ptr<ctx::mac_set_t> ctx::mac_set_from_mac_counts(const mac_counts_t &mac_counts)
+{
+       std::shared_ptr<mac_set_t> mac_set(std::make_shared<mac_set_t>());
+       for (auto &c: mac_counts) {
+               mac_set->insert(c.first);
+       }
+       return mac_set;
+}
+
+std::shared_ptr<ctx::mac_set_t> ctx::mac_sets_union(const std::vector<std::shared_ptr<mac_set_t>> &mac_sets)
+{
+       std::shared_ptr<mac_set_t> union_set = std::make_shared<mac_set_t>();
+       for (std::shared_ptr<mac_set_t> mac_set : mac_sets) {
+               union_set->insert(mac_set->begin(), mac_set->end());
+       }
+       return union_set;
+}
+
+ctx::interval_s::interval_s(time_t start_, time_t end_) : start(start_), end(end_) {
+       if (end_ < start_) {
+               _E("Negative interval, start=%d, end=%d", start_, end_);
+       }
+}
index 48e1bca..7917b26 100644 (file)
@@ -73,6 +73,51 @@ namespace std {
 
 namespace ctx {
 
+       typedef float share_t;
+       typedef int count_t;
+
+       typedef std::unordered_map<ctx::Mac, ctx::count_t> mac_counts_t;
+       typedef std::unordered_map<ctx::Mac, ctx::share_t> mac_shares_t;
+
+       typedef std::unordered_set<ctx::Mac> mac_set_t;
+
+       std::istream &operator>>(std::istream &input, ctx::mac_set_t &mac_set);
+       std::ostream &operator<<(std::ostream &output, const ctx::mac_set_t &mac_set);
+       ctx::mac_set_t mac_set_from_string(const std::string &str);
+
+       std::shared_ptr<mac_set_t> mac_sets_union(const std::vector<std::shared_ptr<mac_set_t>> &mac_sets);
+
+       struct interval_s {
+               time_t start;
+               time_t end;
+
+               interval_s(time_t start_, time_t end_);
+       };
+
+}      /* namespace ctx */
+
+namespace std {
+
+       template <> struct hash<ctx::interval_s> {
+               size_t operator()(const ctx::interval_s & interval) const {
+                       return interval.end * interval.start;
+               }
+       };
+
+}      /* namespace std */
+
+namespace ctx {
+
+       /*
+        * fully describes interval data after the interval is finished
+        */
+       struct frame_s {
+               interval_s interval;
+               count_t no_timestamps;
+               mac_counts_t mac_counts;
+
+               frame_s(interval_s interval_) : interval(interval_), no_timestamps(0) {};
+       };
 
        /*
         * mac address + its timestamp
@@ -114,6 +159,27 @@ namespace ctx {
 
        };      /* struct location_event_s */
 
+       struct visit_s {
+               interval_s interval;
+               std::shared_ptr<mac_set_t> mac_set;
+               bool location_valid;
+               location_s location; // makes sense if location_valid == true;
+
+               visit_s(interval_s interval_, std::shared_ptr<mac_set_t> mac_set_ = std::make_shared<mac_set_t>()) :
+                       interval(interval_),
+                       mac_set(mac_set_),
+                       location_valid(false) {}
+               void set_location(location_s location);
+               void print_short_to_stream(std::ostream &out) const;
+
+       };      /* struct visit_s */
+
+       bool operator==(const visit_s &v1, const visit_s &v2);
+       typedef std::vector<visit_s> visits_t;
+       typedef std::vector<mac_event_s> mac_events; // used to store current interval logs
+
+       std::shared_ptr<mac_set_t> mac_set_from_mac_counts(const mac_counts_t &mac_counts);
+
 }      /* namespace ctx */
 
 #endif /*__CONTEXT_PLACE_STATUS_USER_PLACES_TYPES_H__*/
index 1601b0e..724ac98 100644 (file)
 #include <json.h>
 #include "../place_recognition_types.h"
 #include "visit_detector.h"
+#include "user_places_params.h"
+#include "similar.h"
+#include "median.h"
 #include "debug_utils.h"
 
+#define VISIT_TABLE_COLUMNS \
+       VISIT_COLUMN_WIFI_APS " TEXT, "\
+       VISIT_COLUMN_START_TIME " timestamp, "\
+       VISIT_COLUMN_END_TIME " timestamp, "\
+       VISIT_COLUMN_START_TIME_HUMAN " TEXT, "\
+       VISIT_COLUMN_END_TIME_HUMAN " TEXT, "\
+       VISIT_COLUMN_LOCATION_VALID " INTEGER, "\
+       VISIT_COLUMN_LOCATION_LATITUDE " REAL, "\
+       VISIT_COLUMN_LOCATION_LONGITUDE " REAL, "\
+       VISIT_COLUMN_CATEG_HOME " REAL, "\
+       VISIT_COLUMN_CATEG_WORK " REAL, "\
+       VISIT_COLUMN_CATEG_OTHER " REAL"
+
 ctx::VisitDetector::VisitDetector(time_t t_start_scan, bool test_mode_)
        : test_mode(test_mode_)
        , location_logger(this, test_mode_)
        , wifi_logger(this, test_mode_)
+       , current_interval(t_start_scan, t_start_scan + VISIT_DETECTOR_PERIOD_SECONDS)
 {
-       // @to_be_added visit detecor logic
+       current_logger = std::make_shared<mac_events>();
+       stay_macs = std::make_shared<mac_set_t>();
+       history_frames.push_back(std::make_shared<frame_s>(interval_s(0, VISIT_DETECTOR_PERIOD_SECONDS))); // prehistoric period
+       entrance_to_place = false;
+       stable_counter = 0;
+       tolerance = VISIT_DETECTOR_TOLERANCE_DEPTH;
+       departure_time = 0; // prehistoric time
+
+       if (test_mode) {
+               detected_visits = std::make_shared<ctx::visits_t>();
+               return;
+       }
 
+       listeners.push_back(&location_logger);
+       listeners.push_back(&wifi_logger);
+       db_create_table();
        wifi_logger.start_logging();
 }
 
@@ -39,12 +70,329 @@ ctx::VisitDetector::~VisitDetector()
 {
 }
 
+ctx::interval_s ctx::VisitDetector::get_current_interval()
+{
+       return current_interval;
+}
+
+bool ctx::VisitDetector::is_valid(const ctx::Mac &mac)
+{
+       return mac != "00:00:00:00:00:00";
+}
+
 void ctx::VisitDetector::on_wifi_scan(ctx::mac_event_s e)
 {
-       // @to_be_added visit detecor logic
+       _D("timestamp=%d, curent_interval.end=%d, mac=%s", e.timestamp, current_interval.end, std::string(e.mac).c_str());
+       if (is_valid(e.mac)) {
+               while (e.timestamp > current_interval.end) {
+                       process_current_logger();
+                       shift_current_interval();
+               }
+               current_logger->push_back(e);
+       }
+}
+
+void ctx::VisitDetector::process_current_logger()
+{
+       _D("");
+       std::shared_ptr<ctx::frame_s> frame = make_frame(this->current_logger, this->current_interval);
+       detect_entrance_or_departure(frame);
+       current_logger->clear();
+}
+
+std::shared_ptr<ctx::frame_s> ctx::VisitDetector::make_frame(std::shared_ptr<ctx::mac_events> logger, ctx::interval_s interval)
+{
+       std::set<time_t> timestamps;
+       std::shared_ptr<frame_s> frame = std::make_shared<frame_s>(interval);
+       for (auto log : *logger) {
+               timestamps.insert(log.timestamp);
+               if (frame->mac_counts.find(log.mac) == frame->mac_counts.end()) {
+                       frame->mac_counts[log.mac] = 1;
+               } else {
+                       frame->mac_counts[log.mac] += 1;
+               }
+       }
+       frame->no_timestamps = timestamps.size();
+       return frame;
+}
+
+void ctx::VisitDetector::shift_current_interval()
+{
+       current_interval.end   += VISIT_DETECTOR_PERIOD_SECONDS;
+       current_interval.start += VISIT_DETECTOR_PERIOD_SECONDS;
+}
+
+void ctx::VisitDetector::detect_entrance_or_departure(std::shared_ptr<ctx::frame_s> frame)
+{
+       entrance_to_place ? detect_departure(frame) : detect_entrance(frame);
+}
+
+bool ctx::VisitDetector::is_disjoint(const ctx::mac_counts_t &mac_counts, const ctx::mac_set_t &mac_set)
+{
+       for (auto &mac : mac_set) {
+               if (mac_counts.find(mac) != mac_counts.end()) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+bool ctx::VisitDetector::protrudes_from(const ctx::mac_counts_t &mac_counts, const ctx::mac_set_t &mac_set)
+{
+       for (auto &m : mac_counts) {
+               if (mac_set.find(m.first) == mac_set.end()) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+void ctx::VisitDetector::detect_departure(std::shared_ptr<ctx::frame_s> frame)
+{
+       if (tolerance == VISIT_DETECTOR_TOLERANCE_DEPTH) {
+               departure_time = frame->interval.start;
+               buffered_frames.clear();
+       } else { // tolerance < VISIT_DETECTOR_TOLERANCE_DEPTH
+               buffered_frames.push_back(frame);
+       }
+       if (is_disjoint(frame->mac_counts, *rep_macs)) {
+               if (frame->mac_counts.empty() || protrudes_from(frame->mac_counts, *stay_macs)) {
+                       tolerance--;
+               } else { // no new macs
+                       buffered_frames.clear();
+               }
+               if (tolerance == 0) { // departure detected
+                       visit_end_detected();
+                       buffer_processing(frame);
+               }
+       } else if (tolerance < VISIT_DETECTOR_TOLERANCE_DEPTH) {
+               tolerance++;
+       }
+}
+
+void ctx::VisitDetector::visit_start_detected()
+{
+       entrance_to_place = true;
+
+       locations.clear();
+       if (!test_mode) {
+               for (IVisitListener* listener : listeners) {
+                       listener->on_visit_start();
+               }
+       }
+       rep_macs = select_representatives(history_frames);
+       entrance_time = history_frames[0]->interval.start;
+       _I("Entrance detected, timestamp: %d", entrance_time);
+       history_reset();
+}
+
+void ctx::VisitDetector::visit_end_detected()
+{
+       if (!test_mode) {
+               for (IVisitListener* listener : listeners) {
+                       listener->on_visit_end();
+               }
+       }
+       _I("Departure detected, timestamp: %d", departure_time);
+
+       interval_s interval(entrance_time, departure_time);
+       visit_s visit(interval, rep_macs);
+       // @to_be_added catagorize visit
+
+       put_visit_location(visit);
+
+       if (test_mode) {
+               detected_visits->push_back(visit);
+       } else {
+               db_insert_visit(visit);
+       }
+
+       // cleaning
+       entrance_to_place = false;
+       rep_macs.reset();
+       tolerance = VISIT_DETECTOR_TOLERANCE_DEPTH;
+}
+
+void ctx::VisitDetector::put_visit_location(ctx::visit_s &visit)
+{
+       // TODO: remove small accuracy locations from vectors?
+       std::vector<double> latitudes;
+       std::vector<double> longitudes;
+       visit.location_valid = false;
+       for (location_event_s location : locations) {
+               if (location.timestamp >= entrance_time && location.timestamp <= departure_time) {
+                       latitudes.push_back(location.coordinates.latitude);
+                       longitudes.push_back(location.coordinates.longitude);
+                       visit.location_valid = true;
+               }
+       }
+       if (visit.location_valid) {
+               visit.location.latitude = median(latitudes);
+               visit.location.longitude = median(longitudes);
+               _D("visit location set: lat=%.8f, lon=%.8f", visit.location.latitude, visit.location.longitude);
+       } else {
+               _D("visit location not set");
+       }
+}
+
+void ctx::VisitDetector::buffer_processing(std::shared_ptr<ctx::frame_s> frame)
+{
+       if (buffered_frames.empty()) {
+               history_frames.push_back(frame);
+       } else {
+               history_frames.push_back(buffered_frames[0]);
+               for (size_t i = 1; i < buffered_frames.size(); i++) {
+                       detect_entrance(buffered_frames[i]);
+                       if (entrance_to_place) {
+                               break;
+                       }
+               }
+       }
+}
+
+void ctx::VisitDetector::detect_entrance(std::shared_ptr<ctx::frame_s> current_frame)
+{
+       if (current_frame->mac_counts.empty()) { // empty frame
+               history_reset(current_frame);
+               return;
+       }
+
+       if (stable_counter == 0) {
+               std::shared_ptr<frame_s>  oldest_history_frame = history_frames[0];
+               stay_macs = mac_set_from_mac_counts(oldest_history_frame->mac_counts);
+       }
+
+       std::shared_ptr<mac_set_t> current_beacons = mac_set_from_mac_counts(current_frame->mac_counts);
+
+       if (overlap_bigger_over_smaller(*current_beacons, *stay_macs) > VISIT_DETECTOR_OVERLAP) {
+               stable_counter++;
+               history_frames.push_back(current_frame);
+
+               if (stable_counter == VISIT_DETECTOR_STABLE_DEPTH) { // entrance detected
+                       visit_start_detected();
+               }
+       } else {
+               history_reset(current_frame);
+       }
+       return;
+}
+
+void ctx::VisitDetector::history_reset()
+{
+       stable_counter = 0;
+       history_frames.clear();
+}
+
+void ctx::VisitDetector::history_reset(std::shared_ptr<frame_s> frame)
+{
+       history_reset();
+       history_frames.push_back(frame);
+}
+
+std::shared_ptr<ctx::mac_set_t> ctx::VisitDetector::select_representatives(const std::vector<std::shared_ptr<frame_s>> &frames)
+{
+       mac_counts_t repr_counts;
+       count_t all_count = 0;
+
+       /* in python there were also:
+       if (frames.empty()) {
+               return make_shared<mac_set_t>();
+       } */
+
+       for (auto frame : frames) {
+               all_count += frame->no_timestamps;
+               for (auto &c : frame->mac_counts) {
+                       repr_counts[c.first] += c.second;
+               }
+       }
+
+       std::shared_ptr<mac_shares_t> repr_shares = mac_shares_from_counts(repr_counts, all_count);
+
+       share_t max_share = calc_max_share(*repr_shares);
+       share_t threshold = max_share < VISIT_DETECTOR_REP_THRESHOLD ?
+               max_share : VISIT_DETECTOR_REP_THRESHOLD;
+
+       std::shared_ptr<mac_set_t> repr_mac_set = mac_set_of_greater_or_equal_share(*repr_shares, threshold);
+
+       return repr_mac_set;
+}
+
+ctx::share_t ctx::VisitDetector::calc_max_share(const ctx::mac_shares_t &mac_shares)
+{
+       ctx::share_t max_value = 0.0;
+       for (auto &ms : mac_shares) {
+               if (ms.second > max_value) {
+                       max_value = ms.second;
+               }
+       }
+       return max_value;
+}
+
+std::shared_ptr<ctx::mac_set_t> ctx::VisitDetector::mac_set_of_greater_or_equal_share(const ctx::mac_shares_t &mac_shares, ctx::share_t threshold)
+{
+       std::shared_ptr<mac_set_t> mac_set = std::make_shared<mac_set_t>();
+       for (auto &ms : mac_shares) {
+               if (ms.second >= threshold) {
+                       mac_set->insert(ms.first);
+               }
+       }
+       return mac_set;
+}
+
+std::shared_ptr<ctx::mac_shares_t> ctx::VisitDetector::mac_shares_from_counts(ctx::mac_counts_t const &mac_counts, ctx::count_t denominator)
+{
+       std::shared_ptr<mac_shares_t> mac_shares(std::make_shared<mac_shares_t>());
+       for (auto mac_count : mac_counts) {
+               (*mac_shares)[mac_count.first] = (share_t) mac_count.second / denominator;
+       }
+       return mac_shares;
+}
+
+std::shared_ptr<ctx::visits_t> ctx::VisitDetector::get_visits()
+{
+       return detected_visits;
+}
+
+void ctx::VisitDetector::db_create_table()
+{
+       bool ret = db_manager::create_table(0, VISIT_TABLE, VISIT_TABLE_COLUMNS);
+       _D("db: visit Table Creation Result: %s", ret ? "SUCCESS" : "FAIL");
+}
+
+int ctx::VisitDetector::db_insert_visit(visit_s visit)
+{
+       std::stringstream macs_ss;
+       macs_ss << *visit.mac_set;
+
+       std::string start_time_human = DebugUtils::human_readable_date_time(visit.interval.start, "%F %T", 80);
+       std::string end_time_human = DebugUtils::human_readable_date_time(visit.interval.end, "%F %T", 80);
+
+       json data;
+       data.set(NULL, VISIT_COLUMN_WIFI_APS, macs_ss.str().c_str());
+
+       data.set(NULL, VISIT_COLUMN_LOCATION_VALID, visit.location_valid);
+       data.set(NULL, VISIT_COLUMN_LOCATION_LATITUDE, visit.location.latitude, GEO_LOCATION_PRECISION);
+       data.set(NULL, VISIT_COLUMN_LOCATION_LONGITUDE, visit.location.longitude, GEO_LOCATION_PRECISION);
+
+       data.set(NULL, VISIT_COLUMN_START_TIME, static_cast<int>(visit.interval.start));
+       data.set(NULL, VISIT_COLUMN_END_TIME, static_cast<int>(visit.interval.end));
+       // for debug:
+       data.set(NULL, VISIT_COLUMN_START_TIME_HUMAN, start_time_human.c_str());
+       data.set(NULL, VISIT_COLUMN_END_TIME_HUMAN, end_time_human.c_str());
+
+       // @to_be_added set visit categories
+
+       _D("db: visit table insert interval: (%d, %d): (%s, %s)",
+                       visit.interval.start, visit.interval.end,
+                       start_time_human.c_str(), end_time_human.c_str());
+       bool ret = db_manager::insert(0, VISIT_TABLE, data);
+       _D("db: visit table insert result: %s", ret ? "SUCCESS" : "FAIL");
+       return ret;
 }
 
 void ctx::VisitDetector::on_new_location(location_event_s location_event)
 {
-       // @to_be_added vistit detector logic
+       _D("");
+       location_event.log();
+       locations.push_back(location_event);
 };
index 812d70d..d7dcdfc 100644 (file)
@@ -35,17 +35,58 @@ namespace ctx {
        class VisitDetector : public IWifiListener, ILocationListener {
        private:
                bool test_mode;
+               std::shared_ptr<visits_t> detected_visits;   // only used in test mode
                LocationLogger location_logger;
                WifiLogger wifi_logger;
+               std::vector<IVisitListener*> listeners;
 
-               // @to_be_added visit detector logic methods
+               std::shared_ptr<mac_events> current_logger;
+               interval_s current_interval;
 
-       public:
+               std::vector<std::shared_ptr<frame_s>> history_frames;  // python: history_scans  + history_times
+               std::vector<std::shared_ptr<frame_s>> buffered_frames; // python: buffered_scans + buffered_times
+
+               int stable_counter;
+               int tolerance;
+               bool entrance_to_place;
+
+               // fields that  are used only in case of entrance detection
+               std::shared_ptr<mac_set_t> rep_macs; // mac that represent the current place
+               std::shared_ptr<mac_set_t> stay_macs; // macs that can appear in the current place
+               time_t entrance_time;
+               time_t departure_time;
+
+               std::vector<location_event_s> locations;
 
-               // @to_be_added visit detector logic methods
+               bool is_valid(const Mac &mac);
+               void shift_current_interval();
+               void detect_entrance_or_departure(std::shared_ptr<frame_s> frame);
+               void detect_entrance(std::shared_ptr<frame_s> frame);
+               void detect_departure(std::shared_ptr<frame_s> frame);
+               void buffer_processing(std::shared_ptr<frame_s> frame); // python: buffer_anaysing
+               std::shared_ptr<frame_s> make_frame(std::shared_ptr<mac_events> mac_events, interval_s interval);  // python: scans2fingerprint
+               void history_reset();
+               void history_reset(std::shared_ptr<frame_s> frame);
+               void visit_start_detected();
+               void visit_end_detected();
+               void put_visit_location(visit_s &visit);
+               std::shared_ptr<mac_set_t> select_representatives(const std::vector<std::shared_ptr<frame_s>> &frames);
+               std::shared_ptr<mac_set_t> mac_set_of_greater_or_equal_share(const mac_shares_t &mac_shares, share_t threshold);
+               std::shared_ptr<mac_shares_t> mac_shares_from_counts(mac_counts_t const &mac_counts, count_t denominator); // python: response_rate
+               share_t calc_max_share(const mac_shares_t &mac_shares);
+               bool is_disjoint(const mac_counts_t &mac_counts, const mac_set_t &mac_set);
+               bool protrudes_from(const mac_counts_t &mac_counts, const mac_set_t &mac_set);
+
+               void db_create_table();
+               int db_insert_visit(visit_s visit);
+
+       public:
                VisitDetector(time_t _t_start_scan, bool test_mode_ = false);
                ~VisitDetector();
+               interval_s get_current_interval();
                void on_wifi_scan(mac_event_s event);
+               void process_current_logger();
+               std::shared_ptr<visits_t> get_visits(); // only used in test mode
                void on_new_location(location_event_s location);
 
        };      /* class VisitDetector */