/* * This file is part of the Owl Positioning System (OwlPS). * OwlPS is a project of the University of Franche-Comte * (Université de Franche-Comté), France. * * Copyright © Université de Franche-Comté 2007-2012. * * Corresponding author: Matteo Cypriani * *********************************************************************** * * This software is governed by the CeCILL license under French law and * abiding by the rules of distribution of free software. You can use, * modify and/or redistribute the software under the terms of the CeCILL * license as circulated by CEA, CNRS and INRIA at the following URL: * http://www.cecill.info * * As a counterpart to the access to the source code and rights to copy, * modify and redistribute granted by the license, users are provided * only with a limited warranty and the software's authors, the holder * of the economic rights, and the successive licensors have only * limited liability. * * In this respect, the user's attention is drawn to the risks * associated with loading, using, modifying and/or developing or * reproducing the software by the user in light of its specific status * of free software, that may mean that it is complicated to manipulate, * and that also therefore means that it is reserved for developers and * experienced professionals having in-depth computer knowledge. Users * are therefore encouraged to load and test the software's suitability * as regards their requirements in conditions enabling the security of * their systems and/or data to be ensured and, more generally, to use * and operate it in the same conditions as regards security. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL license and that you accept its terms. * *********************************************************************** */ #include "autocalibration.hh" #include "accesspoint.hh" #include "stock.hh" #include "configuration.hh" #include "posexcept.hh" #include #include #include #include using namespace std ; using std::tr1::unordered_map ; /* *** Static attribute definitions *** */ uint32_t Autocalibration::nb_virtual_mobiles = 0 ; /* *** Operations *** */ /** * The reference point P we want to generate will end-up containing * as many calibration requests as the total number of APs. * For each of these requests, we consider a transmitter TRX (which is * a virtual mobile located in P) and a receiver RX (the current AP). * To generate the signal strength (SS) received by RX from TRX, * we first select two reference APs REF1 and REF2. Then, we average * the real measurements REF1->RX and REF2->RX, weighting in function * of the angles REF-RX-TRX and the distance from RX to TRX to compute * the SS. * Hope this is clear enough… */ void Autocalibration::generate_reference_point() { // If the point is the coordinates of an existing AP, we don't // need to generate it if (Stock::is_ap_coordinate(point)) return ; /* Get/create the reference point */ const ReferencePoint ¤t_rp = Stock::find_create_reference_point(point) ; /* Prepare the virtual mobile */ string vmob_mac(PosUtil::int_to_mac(nb_virtual_mobiles++)) ; // The gain and trx power of the mobile will be the average of // those of all the known APs (could be better, probably) vmob_gain = 0 ; vmob_pow = 0 ; /* Generate the SS(s) for each AP */ for (rx = Stock::aps.begin() ; rx != Stock::aps.end() ; ++rx) generate_ss() ; assert(! measurements.empty()) ; /* Create the virtual mobile */ Mobile vmob("", vmob_mac, vmob_gain, vmob_pow) ; const Mobile &mobile = Stock::find_create_mobile(vmob) ; /* Create the calibration request */ CalibrationRequest cr(OWL_REQUEST_GENERATED) ; cr.set_time_sent(cr.get_time_received()) ; cr.set_mobile(&mobile) ; cr.set_measurements(measurements) ; cr.set_reference_point(¤t_rp) ; Stock::store_calibration_request(cr) ; } void Autocalibration::generate_ss() { /* Update the mobile's attributes */ vmob_gain += rx->second.get_antenna_gain() / Stock::aps.size() ; vmob_pow += rx->second.get_trx_power() / Stock::aps.size() ; /* Skip the RX AP if it is not at the same floor as the point * to generate */ const Point3D &rx_coord = rx->second.get_coordinates() ; if (rx_coord.get_z() != point.get_z()) return ; /* Compute the origin angle P-RX-O */ Point3D origin(rx_coord) ; origin.set_x(rx_coord.get_x() + 10) ; origin_angle = rx_coord.angle_2d(point, origin) ; if (point.get_y() > rx_coord.get_y()) origin_angle = -origin_angle ; /* Choose the nearest AP(s) in angle */ sort_reference_aps() ; // We need at least one reference AP: if (sorted_negative_angles.empty() && sorted_positive_angles.empty()) throw autocalibration_error("Not enough APs in coverage!") ; /* Compute the angle weight of the reference APs */ weight_aps() ; /* Compute the SS that would be received from a mobile at a * distance of RX-P, in the direction of each reference AP */ compute_ss() ; } /** * The reference (transmitter) APs are first filtered (floor, coverage), * then sorted according to their angles with RX and P. */ void Autocalibration::sort_reference_aps() { const Point3D &rx_coord = rx->second.get_coordinates() ; sorted_negative_angles.clear() ; sorted_positive_angles.clear() ; for (unordered_map::const_iterator ref = Stock::aps.begin() ; ref != Stock::aps.end() ; ++ref) { if (ref == rx) continue ; /* Skip the AP if it is not at the same floor than the * receiver AP */ const Point3D &ref_coord = ref->second.get_coordinates() ; if (ref_coord.get_z() != rx_coord.get_z()) continue ; /* Skip the AP if it is not in coverage with the receiver AP */ float coverage = rx->second.received_calibration_from_ap(ref->second) ; if (coverage < 1) // Less than 1% coverage is ridiculous! continue ; /* Angle P-RX-REF */ double angle = rx_coord.angle_2d(point, ref_coord) ; /* Weight in the APs' list according to coverage and angle */ double weight = angle / coverage ; /* Note: this weight is used only to sort the APs, it has nothing to do with the angle weight (see weight_aps()) used to compute the SSs. */ /* Create the list entry */ pair::const_iterator> angle_ap(angle, ref) ; pair::const_iterator> > weight_angle_ap(weight, angle_ap) ; /* Rotate the AP's coordinates to know the angle direction */ Point3D ref_coord_r(ref_coord) ; ref_coord_r.rotate_2d(rx_coord, origin_angle) ; // Insert the AP in the right list, according to the direction: if (ref_coord_r.get_y() < rx_coord.get_y()) sorted_negative_angles.insert(weight_angle_ap) ; else sorted_positive_angles.insert(weight_angle_ap) ; } } void Autocalibration::weight_aps() { /* Retrieve the reference APs & angles */ ref_aps.clear() ; map::const_iterator> > ::const_iterator s ; s = sorted_negative_angles.begin() ; if (s != sorted_negative_angles.end()) init_ap(s) ; s = sorted_positive_angles.begin() ; if (s != sorted_positive_angles.end()) init_ap(s) ; /* Weight the APs */ if (ref_aps.size() == 1) ref_aps[0].weight = 100 ; else if (ref_aps.size() == 2) weight_2_aps() ; else { ostringstream oss ; oss << "We should not be here... error when sorting the reference" << " APs? (" << ref_aps.size() << " APs sorted)" ; throw autocalibration_error(oss.str()) ; } } void Autocalibration::init_ap( map::const_iterator> >::const_iterator s) { struct ap ref ; ref.ap = &s->second.second->second ; ref.angle = s->second.first ; // Angle P-REF-RX ref_aps.push_back(ref) ; } void Autocalibration::weight_2_aps() { assert(ref_aps.size() > 1) ; /* Compute the angle REF1-RX-REF2 */ double reference_angle = ref_aps[0].angle + ref_aps[1].angle ; /* Note: the reference angle must be the sum of the two angles P-RX-REF1 and P-RX-REF2 instead of simply REF1-RX-REF2, to allow the angle to be within [0;360[° instead of [0;180[°. This is needed if P-RX-REF1 or P-RX-REF2 are greater than 90°, otherwise the weights would be wrong. */ /* Weight the two APs */ if (reference_angle == 0) ref_aps[0].weight = ref_aps[1].weight = 50 ; else { ref_aps[0].weight = ref_aps[0].angle * 100 / reference_angle ; if (ref_aps[0].weight > 100) ref_aps[0].weight = 100 ; ref_aps[1].weight = 100 - ref_aps[0].weight ; } } void Autocalibration::compute_ss() { if (Configuration:: bool_value("positioning.generate-multi-packet-reference-points")) compute_multi_packet_ss() ; else compute_single_packet_ss(0) ; } void Autocalibration::compute_multi_packet_ss() { const ap &ref1 = ref_aps[0] ; const ReferencePoint &ref1_rp = Stock::get_reference_point(ref1.ap->get_coordinates()) ; const vector &ref1_cr = ref1_rp.get_requests() ; /* Search for the first measurement made by RX in ref1_rp */ const Measurement *rx_measurement = NULL ; vector::const_iterator cr ; const string &rx_mac = rx->second.get_mac_addr() ; for (cr = ref1_cr.begin() ; cr != ref1_cr.end() ; ++cr) { rx_measurement = (*cr)->get_measurement(rx_mac) ; if (rx_measurement) break ; } // The selected reference APs are in coverage of RX, therefore // we should always find a measurement of RX in ref1_rp assert(rx_measurement) ; /* Generate the SS for each packet */ bool packet_generated = false ; for (pkt_id_t pkt_id = 1 ; pkt_id <= (*cr)->get_nb_packets() ; ++pkt_id) if (compute_single_packet_ss(pkt_id)) packet_generated = true ; if (! packet_generated) { if (Configuration::is_configured("verbose")) cerr << "No packet has been generated for RX AP " << rx_mac << "... failing back to single packet request. Calibration" << " request involved : " << **cr << "\n" ; compute_single_packet_ss(0) ; } } /** * @arg pkt_id The number of the packet to create. Must be 0 if all * the generated reference points contain only one packet. */ bool Autocalibration::compute_single_packet_ss(pkt_id_t pkt_id) { /* Distance RX-P */ const Point3D &rx_coord = rx->second.get_coordinates() ; float point_dst = rx_coord.distance(point) ; /* Compute the consolidated SS */ double rx_ss = 0 ; for (unsigned int i = 0 ; i < ref_aps.size() ; ++i) { double tmp_ss = compute_weighted_ss(i, pkt_id, point_dst) ; if (tmp_ss > 0) // error return false ; rx_ss += tmp_ss ; } /* Add the SS to the measurements' list */ const string &rx_mac = rx->second.get_mac_addr() ; // 0 is not a valid packet ID, so we use 1 if we generate only one // packet per reference point: if (pkt_id == 0) pkt_id = 1 ; // The Measurement measurements[rx_mac] is created if needed: measurements[rx_mac].add_ss(pkt_id, rx_ss) ; // Useful only if the measurement was just created, but it is not // worth a test: measurements[rx_mac].set_ap(&rx->second) ; return true ; } /** * @arg ap_idx The index of the AP in #ref_aps. * @arg pkt_id The number of the packet to generate, or 0 if we generate * only one packet per reference point. * @arg point_dst The distance RX-P. * * @returns The weighted SS value in dBm, or 1 in case of error. */ double Autocalibration::compute_weighted_ss( unsigned int ap_idx, pkt_id_t pkt_id, float point_dst) { const ap &ref = ref_aps[ap_idx] ; const ReferencePoint &rp = Stock::get_reference_point(ref.ap->get_coordinates()) ; /* Friis index */ const string &rx_mac = rx->second.get_mac_addr() ; float friis_idx ; if (pkt_id == 0) friis_idx = rp.friis_index_for_ap(rx_mac) ; else { friis_idx = rp.friis_index_for_ap(rx_mac, pkt_id) ; if (friis_idx == 0) return 1 ; // The packet pkt_id does not exist in rp } /* SS */ // Constant term double common_ss = rx->second.friis_constant_term() ; double ss = common_ss + ref.ap->get_trx_power() + ref.ap->get_antenna_gain() - 10 * friis_idx * log10(point_dst) ; return ss * ref.weight / 100 ; }