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);
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_);
+ }
+}
#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();
}
{
}
+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);
};
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 */