owlps/owlps-positioner/src/autocalibration.cc

398 lines
12 KiB
C++

/*
* 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 <mcy@lm7.fr>
*
***********************************************************************
*
* 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 <map>
#include <string>
#include <iostream>
#include <sstream>
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
&current_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(&current_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<string, AccessPoint>::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<double,
unordered_map<string, AccessPoint>::const_iterator>
angle_ap(angle, ref) ;
pair<double, pair<double,
unordered_map<string, AccessPoint>::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<double, pair<double,
unordered_map<string, AccessPoint>::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<double, pair<double, unordered_map<
string, AccessPoint>::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<CalibrationRequest*> &ref1_cr =
ref1_rp.get_requests() ;
/* Search for the first measurement made by RX in ref1_rp */
const Measurement *rx_measurement = NULL ;
vector<CalibrationRequest*>::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 ;
}