Compare commits

...

46 Commits

Author SHA1 Message Date
HappyZ ee680b6255 misc backup (emulating localization via 11mc based on error model) 2018-02-24 15:02:27 -06:00
HappyZ 9ca0389b30 misc backup 2018-02-24 11:21:42 -06:00
HappyZ 9f36baa83c misc backup 2018-02-14 12:03:24 -06:00
HappyZ d06eb344e9 correct behavior when Device Busy error occurs 2018-02-14 11:57:04 -06:00
HappyZ 59f4688d8d device busy error causes exit, try to wait and see if problem solves 2018-02-14 09:32:41 -06:00
HappyZ ef8923bcdd little improvement 2018-02-12 13:53:29 -05:00
HappyZ 2f59bb1b99 bug fix 2018-02-08 15:48:02 -06:00
HappyZ 5471f77e89 add option to plot location 2018-02-08 15:32:43 -06:00
HappyZ 73b2050919 only try to improve results when there is no very valid results 2018-02-08 14:17:12 -06:00
HappyZ 9a90d50b6d prevent negative circle radius 2018-02-08 14:10:59 -06:00
HappyZ a8c2c025e1 minor bug fix 2018-02-08 14:10:06 -06:00
HappyZ dd31f3a2ba bug fix 2018-02-08 14:05:10 -06:00
HappyZ 31828a6088 improve localization trilateration 2018-02-08 14:03:26 -06:00
HappyZ d58044615d add capability to localize the target when multiple 11mc devices are used
via trilateration
2018-02-06 14:57:26 -06:00
HappyZ 110cc00483 extract data from different mac addresses 2018-02-06 13:49:15 -06:00
HappyZ 989f9f2ee9 bug fix 2018-02-05 13:33:35 -06:00
HappyZ a79dc82f5b add indoor option; by default will assume outdoor calibration params 2018-02-04 18:36:44 -06:00
HappyZ a1642bf6ec matlab backup 2018-02-04 18:36:19 -06:00
HappyZ ad16d16273 log the actual start and end time 2018-02-03 15:08:50 -06:00
HappyZ 71e01db3ca better fit for outdoor 2018-02-03 14:56:58 -06:00
HappyZ 9dc53180b7 typo fix 2018-02-02 11:09:11 -06:00
HappyZ fea445349c bug fix 2018-02-02 11:02:42 -06:00
HappyZ bb3ea4c6f6 bug fix 2018-02-02 11:02:09 -06:00
HappyZ 232b1ff259 remove useless files 2018-02-02 02:20:06 -06:00
HappyZ 057244c1af add to read config entry in file (+ default config entry); option to print out more details 2018-02-02 02:17:19 -06:00
HappyZ d90cf875df re-calibrate based on new indoor data 2018-02-01 22:46:09 -06:00
HappyZ 514d27ca6e validation on orientation of device 2018-02-01 22:44:10 -06:00
HappyZ 2742e2fe10 adapt to new data format 2018-02-01 21:30:16 -06:00
HappyZ 7d8adf67d6 bug fix 2018-02-01 15:53:48 -06:00
HappyZ 040a880d68 mod parser and config entry for new format 2018-02-01 15:51:38 -06:00
HappyZ 418921b6d3 encoding fix 2018-02-01 15:37:37 -06:00
HappyZ 5894ccc163 bug fix; now parsing new output 2018-02-01 15:35:30 -06:00
HappyZ 97bcb841d4 add samples per burst 2018-02-01 15:29:18 -06:00
HappyZ bd4a18ac26 add time of exec in file path to distinguish between multiple rounds 2018-02-01 11:03:08 -06:00
HappyZ 7a0a4dbc48 exit immediately to prevent indefinite invalid runs 2018-02-01 10:35:03 -06:00
HappyZ 62ff707663 bug fix 2018-02-01 10:32:36 -06:00
HappyZ 11c6ed2a00 calibrate outdoor 2018-01-31 15:55:51 -06:00
HappyZ e59e90de11 remove unnecessary files; add mean/median fitting comparison 2018-01-31 11:03:39 -06:00
HappyZ 705997bc3a speed up calibration data collection 2018-01-31 10:51:07 -06:00
HappyZ 7258fb18b6 bug fix 2018-01-31 10:49:40 -06:00
HappyZ c2c8bca829 try new fit param 2018-01-31 10:37:06 -06:00
HappyZ 232d4cf98c add a validity check for iw command 2018-01-30 22:31:50 -06:00
HappyZ ab41f16168 add option to change rounds & write to file; print results indefinitely 2018-01-30 22:20:52 -06:00
HappyZ bc5495786b add measurement lib 2018-01-30 17:08:29 -06:00
HappyZ 8c6a73a01f init commit 2018-01-30 15:36:32 -06:00
HappyZ ae9e489224 move to subfolder 2018-01-30 15:34:55 -06:00
57 changed files with 2044 additions and 11 deletions

16
.gitignore vendored
View File

@ -1,7 +1,9 @@
iw calibration_data/
*~ iw/iw
*.o iw/*~
.config iw/*.o
version.c iw/.config
iw.8.gz iw/version.c
*-stamp iw/iw.8.gz
iw/*-stamp
*.pyc

453
allfitdist.m Normal file
View File

@ -0,0 +1,453 @@
function [D PD] = allfitdist(data,sortby,varargin)
%ALLFITDIST Fit all valid parametric probability distributions to data.
% [D PD] = ALLFITDIST(DATA) fits all valid parametric probability
% distributions to the data in vector DATA, and returns a struct D of
% fitted distributions and parameters and a struct of objects PD
% representing the fitted distributions. PD is an object in a class
% derived from the ProbDist class.
%
% [...] = ALLFITDIST(DATA,SORTBY) returns the struct of valid distributions
% sorted by the parameter SORTBY
% NLogL - Negative of the log likelihood
% BIC - Bayesian information criterion (default)
% AIC - Akaike information criterion
% AICc - AIC with a correction for finite sample sizes
%
% [...] = ALLFITDIST(...,'DISCRETE') specifies it is a discrete
% distribution and does not attempt to fit a continuous distribution
% to the data
%
% [...] = ALLFITDIST(...,'PDF') or (...,'CDF') plots either the PDF or CDF
% of a subset of the fitted distribution. The distributions are plotted in
% order of fit, according to SORTBY.
%
% List of distributions it will try to fit
% Continuous (default)
% Beta
% Birnbaum-Saunders
% Exponential
% Extreme value
% Gamma
% Generalized extreme value
% Generalized Pareto
% Inverse Gaussian
% Logistic
% Log-logistic
% Lognormal
% Nakagami
% Normal
% Rayleigh
% Rician
% t location-scale
% Weibull
%
% Discrete ('DISCRETE')
% Binomial
% Negative binomial
% Poisson
%
% Optional inputs:
% [...] = ALLFITDIST(...,'n',N,...)
% For the 'binomial' distribution only:
% 'n' A positive integer specifying the N parameter (number
% of trials). Not allowed for other distributions. If
% 'n' is not given it is estimate by Method of Moments.
% If the estimated 'n' is negative then the maximum
% value of data will be used as the estimated value.
% [...] = ALLFITDIST(...,'theta',THETA,...)
% For the 'generalized pareto' distribution only:
% 'theta' The value of the THETA (threshold) parameter for
% the generalized Pareto distribution. Not allowed for
% other distributions. If 'theta' is not given it is
% estimated by the minimum value of the data.
%
% Note: ALLFITDIST does not handle nonparametric kernel-smoothing,
% use FITDIST directly instead.
%
%
% EXAMPLE 1
% Given random data from an unknown continuous distribution, find the
% best distribution which fits that data, and plot the PDFs to compare
% graphically.
% data = normrnd(5,3,1e4,1); %Assumed from unknown distribution
% [D PD] = allfitdist(data,'PDF'); %Compute and plot results
% D(1) %Show output from best fit
%
% EXAMPLE 2
% Given random data from a discrete unknown distribution, with frequency
% data, find the best discrete distribution which would fit that data,
% sorted by 'NLogL', and plot the PDFs to compare graphically.
% data = nbinrnd(20,.3,1e4,1);
% values=unique(data); freq=histc(data,values);
% [D PD] = allfitdist(values,'NLogL','frequency',freq,'PDF','DISCRETE');
% PD{1}
%
% EXAMPLE 3
% Although the Geometric Distribution is not listed, it is a special
% case of fitting the more general Negative Binomial Distribution. The
% parameter 'r' should be close to 1. Show by example.
% data=geornd(.7,1e4,1); %Random from Geometric
% [D PD]= allfitdist(data,'PDF','DISCRETE');
% PD{1}
%
% EXAMPLE 4
% Compare the resulting distributions under two different assumptions
% of discrete data. The first, that it is known to be derived from a
% Binomial Distribution with known 'n'. The second, that it may be
% Binomial but 'n' is unknown and should be estimated. Note the second
% scenario may not yield a Binomial Distribution as the best fit, if
% 'n' is estimated incorrectly. (Best to run example a couple times
% to see effect)
% data = binornd(10,.3,1e2,1);
% [D1 PD1] = allfitdist(data,'n',10,'DISCRETE','PDF'); %Force binomial
% [D2 PD2] = allfitdist(data,'DISCRETE','PDF'); %May be binomial
% PD1{1}, PD2{1} %Compare distributions
%
% Mike Sheppard
% Last Modified: 17-Feb-2012
%% Check Inputs
if nargin == 0
data = 10.^((normrnd(2,10,1e4,1))/10);
sortby='BIC';
varargin={'CDF'};
end
if nargin==1
sortby='BIC';
end
sortbyname={'NLogL','BIC','AIC','AICc'};
if ~any(ismember(lower(sortby),lower(sortbyname)))
oldvar=sortby; %May be 'PDF' or 'CDF' or other commands
if isempty(varargin)
varargin={oldvar};
else
varargin=[oldvar varargin];
end
sortby='BIC';
end
if nargin < 2, sortby='BIC'; end
distname={'beta', 'birnbaumsaunders', 'exponential', ...
'extreme value', 'gamma', 'generalized extreme value', ...
'generalized pareto', 'inversegaussian', 'logistic', 'loglogistic', ...
'lognormal', 'nakagami', 'normal', ...
'rayleigh', 'rician', 'tlocationscale', 'weibull'};
if ~any(strcmpi(sortby,sortbyname))
error('allfitdist:SortBy','Sorting must be either NLogL, BIC, AIC, or AICc');
end
%Input may be mixed of numeric and strings, find only strings
vin=varargin;
strs=find(cellfun(@(vs)ischar(vs),vin));
vin(strs)=lower(vin(strs));
%Next check to see if 'PDF' or 'CDF' is listed
numplots=sum(ismember(vin(strs),{'pdf' 'cdf'}));
if numplots>=2
error('ALLFITDIST:PlotType','Either PDF or CDF must be given');
end
if numplots==1
plotind=true; %plot indicator
indxpdf=ismember(vin(strs),'pdf');
plotpdf=any(indxpdf);
indxcdf=ismember(vin(strs),'cdf');
vin(strs(indxpdf|indxcdf))=[]; %Delete 'PDF' and 'CDF' in vin
else
plotind=false;
end
%Check to see if discrete
strs=find(cellfun(@(vs)ischar(vs),vin));
indxdis=ismember(vin(strs),'discrete');
discind=false;
if any(indxdis)
discind=true;
distname={'binomial', 'negative binomial', 'poisson'};
vin(strs(indxdis))=[]; %Delete 'DISCRETE' in vin
end
strs=find(cellfun(@(vs)ischar(vs),vin));
n=numel(data); %Number of data points
data = data(:);
D=[];
%Check for NaN's to delete
deldatanan=isnan(data);
%Check to see if frequency is given
indxf=ismember(vin(strs),'frequency');
if any(indxf)
freq=vin{1+strs((indxf))}; freq=freq(:);
if numel(freq)~=numel(data)
error('ALLFITDIST:PlotType','Matrix dimensions must agree');
end
delfnan=isnan(freq);
data(deldatanan|delfnan)=[]; freq(deldatanan|delfnan)=[];
%Save back into vin
vin{1+strs((indxf))}=freq;
else
data(deldatanan)=[];
end
%% Run through all distributions in FITDIST function
warning('off','all'); %Turn off all future warnings
for indx=1:length(distname)
try
dname=distname{indx};
switch dname
case 'binomial'
PD=fitbinocase(data,vin,strs); %Special case
case 'generalized pareto'
PD=fitgpcase(data,vin,strs); %Special case
otherwise
%Built-in distribution using FITDIST
PD = fitdist(data,dname,vin{:});
end
NLL=PD.NLogL; % -Log(L)
%If NLL is non-finite number, produce error to ignore distribution
if ~isfinite(NLL)
error('non-finite NLL');
end
num=length(D)+1;
PDs(num) = {PD}; %#ok<*AGROW>
k=numel(PD.Params); %Number of parameters
D(num).DistName=PD.DistName;
D(num).NLogL=NLL;
D(num).BIC=-2*(-NLL)+k*log(n);
D(num).AIC=-2*(-NLL)+2*k;
D(num).AICc=(D(num).AIC)+((2*k*(k+1))/(n-k-1));
D(num).ParamNames=PD.ParamNames;
D(num).ParamDescription=PD.ParamDescription;
D(num).Params=PD.Params;
D(num).Paramci=PD.paramci;
D(num).ParamCov=PD.ParamCov;
D(num).Support=PD.Support;
catch err %#ok<NASGU>
%Ignore distribution
end
end
warning('on','all'); %Turn back on warnings
if numel(D)==0
error('ALLFITDIST:NoDist','No distributions were found');
end
%% Sort distributions
indx1=1:length(D); %Identity Map
sortbyindx=find(strcmpi(sortby,sortbyname));
switch sortbyindx
case 1
[~,indx1]=sort([D.NLogL]);
case 2
[~,indx1]=sort([D.BIC]);
case 3
[~,indx1]=sort([D.AIC]);
case 4
[~,indx1]=sort([D.AICc]);
end
%Sort
D=D(indx1); PD = PDs(indx1);
%% Plot if requested
if plotind;
plotfigs(data,D,PD,vin,strs,plotpdf,discind)
end
end
function PD=fitbinocase(data,vin,strs)
%% Special Case for Binomial
% 'n' is estimated if not given
vinbino=vin;
%Check to see if 'n' is given
indxn=any(ismember(vin(strs),'n'));
%Check to see if 'frequency' is given
indxfreq=ismember(vin(strs),'frequency');
if ~indxn
%Use Method of Moment estimator
%E[x]=np, V[x]=np(1-p) -> nhat=E/(1-(V/E));
if isempty(indxfreq)||~any(indxfreq)
%Raw data
mnx=mean(data);
nhat=round(mnx/(1-(var(data)/mnx)));
else
%Frequency data
freq=vin{1+strs(indxfreq)};
m1=dot(data,freq)/sum(freq);
m2=dot(data.^2,freq)/sum(freq);
mnx=m1; vx=m2-(m1^2);
nhat=round(mnx/(1-(vx/mnx)));
end
%If nhat is negative, use maximum value of data
if nhat<=0, nhat=max(data(:)); end
vinbino{end+1}='n'; vinbino{end+1}=nhat;
end
PD = fitdist(data,'binomial',vinbino{:});
end
function PD=fitgpcase(data,vin,strs)
%% Special Case for Generalized Pareto
% 'theta' is estimated if not given
vingp=vin;
%Check to see if 'theta' is given
indxtheta=any(ismember(vin(strs),'theta'));
if ~indxtheta
%Use minimum value for theta, minus small part
thetahat=min(data(:))-10*eps;
vingp{end+1}='theta'; vingp{end+1}=thetahat;
end
PD = fitdist(data,'generalized pareto',vingp{:});
end
function plotfigs(data,D,PD,vin,strs,plotpdf,discind)
%Plot functionality for continuous case due to Jonathan Sullivan
%Modified by author for discrete case
%Maximum number of distributions to include
%max_num_dist=Inf; %All valid distributions
max_num_dist=4;
%Check to see if frequency is given
indxf=ismember(vin(strs),'frequency');
if any(indxf)
freq=vin{1+strs((indxf))};
end
figure
%% Probability Density / Mass Plot
if plotpdf
if ~discind
%Continuous Data
nbins = max(min(length(data)./10,100),50);
xi = linspace(min(data),max(data),nbins);
dx = mean(diff(xi));
xi2 = linspace(min(data),max(data),nbins*10)';
fi = histc(data,xi-dx);
fi = fi./sum(fi)./dx;
inds = 1:min([max_num_dist,numel(PD)]);
ys = cellfun(@(PD) pdf(PD,xi2),PD(inds),'UniformOutput',0);
ys = cat(2,ys{:});
bar(xi,fi,'FaceColor',[160 188 254]/255,'EdgeColor','k');
hold on;
plot(xi2,ys,'LineWidth',1.5)
legend(['empirical',{D(inds).DistName}],'Location','NE')
xlabel('Value');
ylabel('Probability Density');
title('Probability Density Function');
grid on
else
%Discrete Data
xi2=min(data):max(data);
%xi2=unique(x)'; %If only want observed x-values to be shown
indxf=ismember(vin(strs),'frequency');
if any(indxf)
fi=zeros(size(xi2));
fi((ismember(xi2,data)))=freq; fi=fi'./sum(fi);
else
fi=histc(data,xi2); fi=fi./sum(fi);
end
inds = 1:min([max_num_dist,numel(PD)]);
ys = cellfun(@(PD) pdf(PD,xi2),PD(inds),'UniformOutput',0);
ys=cat(1,ys{:})';
bar(xi2,[fi ys]);
legend(['empirical',{D(inds).DistName}],'Location','NE')
xlabel('Value');
ylabel('Probability Mass');
title('Probability Mass Function');
grid on
end
else
%Cumulative Distribution
if ~discind
%Continuous Data
[fi xi] = ecdf(data);
inds = 1:min([max_num_dist,numel(PD)]);
ys = cellfun(@(PD) cdf(PD,xi),PD(inds),'UniformOutput',0);
ys = cat(2,ys{:});
if max(xi)/min(xi) > 1e4; lgx = true; else lgx = false; end
subplot(2,1,1)
if lgx
semilogx(xi,fi,'k',xi,ys)
else
plot(xi,fi,'k',xi,ys)
end
legend(['empirical',{D(inds).DistName}],'Location','NE')
xlabel('Value');
ylabel('Cumulative Probability');
title('Cumulative Distribution Function');
grid on
subplot(2,1,2)
y = 1.1*bsxfun(@minus,ys,fi);
if lgx
semilogx(xi,bsxfun(@minus,ys,fi))
else
plot(xi,bsxfun(@minus,ys,fi))
end
ybnds = max(abs(y(:)));
ax = axis;
axis([ax(1:2) -ybnds ybnds]);
legend({D(inds).DistName},'Location','NE')
xlabel('Value');
ylabel('Error');
title('CDF Error');
grid on
else
%Discrete Data
indxf=ismember(vin(strs),'frequency');
if any(indxf)
[fi xi] = ecdf(data,'frequency',freq);
else
[fi xi] = ecdf(data);
end
%Check unique xi, combine fi
[xi,ign,indx]=unique(xi); %#ok<ASGLU>
fi=accumarray(indx,fi);
inds = 1:min([max_num_dist,numel(PD)]);
ys = cellfun(@(PD) cdf(PD,xi),PD(inds),'UniformOutput',0);
ys=cat(2,ys{:});
subplot(2,1,1)
stairs(xi,[fi ys]);
legend(['empirical',{D(inds).DistName}],'Location','NE')
xlabel('Value');
ylabel('Cumulative Probability');
title('Cumulative Distribution Function');
grid on
subplot(2,1,2)
y = 1.1*bsxfun(@minus,ys,fi);
stairs(xi,bsxfun(@minus,ys,fi))
ybnds = max(abs(y(:)));
ax = axis;
axis([ax(1:2) -ybnds ybnds]);
legend({D(inds).DistName},'Location','NE')
xlabel('Value');
ylabel('Error');
title('CDF Error');
grid on
end
end
end

208
calibration_plot.m Normal file
View File

@ -0,0 +1,208 @@
% clear all; %close all;
param_linear = [0.8927, 553.3157];
folder = 'calibration_data/outdoor/';
files = dir(folder); files = files(3:end); % remove . and ..
targets = zeros(1, length(files));
for i = length(files):-1:1
if (~contains(files(i).name, 'result') ||...
contains(files(i).name, 'left') ||...
contains(files(i).name, 'right') ||...
contains(files(i).name, 'ap') ||...
contains(files(i).name, 'down') ||...
contains(files(i).name, 'up'))
files(i) = [];
targets(i) = [];
else
targets(i) = sscanf(files(i).name, 'result_%dcm.txt');
end
end
[targets, orderI] = sort(targets);
files = files(orderI);
median_result = zeros(1, length(files));
mean_result = zeros(1, length(files));
median_rssi = zeros(1, length(files));
median_diststd = zeros(1, length(files));
all_data = [];
figure(1); clf; hold on;
% figure(2); clf; hold on;
for i = 1:length(files)
filename = [folder, files(i).name];
fileID = fopen(filename, 'r');
formatSpec = [...
'Target: %x:%x:%x:%x:%x:%x, status: %d, ',...
'rtt: %d psec, distance: %d cm\n'...
];
data = fscanf(fileID, formatSpec, [9 Inf]);
fclose(fileID);
if isempty(data)
data = readtable(filename, 'ReadVariableNames', 0);
if isempty(data)
continue
end
data = data(2:end, :);
caliDist = str2double(table2array(data(:, 2)))';
rawRTT = str2double(table2array(data(:, 3)))';
rawRTTVar = str2double(table2array(data(:, 4)))';
rawDist = str2double(table2array(data(:, 5)))';
rawDistVar = str2double(table2array(data(:, 6)))';
rssi = str2double(table2array(data(:, 7)))';
time = str2double(table2array(data(:, 8)))';
else
% get rid of invalid data
data(:, data(7, :) ~= 0) = [];
data(:, data(9, :) < -1000) = [];
rawDist = data(9, :);
rawDistVar = zeros(size(rawDist));
rssi = zeros(size(rawDist));
end
mean_result(i) = mean(rawDist);
median_result(i) = median(rawDist);
median_diststd(i) = median(sqrt(rawDistVar));
median_rssi(i) = median(rssi);
fprintf('distance: %d:\n', targets(i));
fprintf('* mean: %.2f (uncalibrated)\n', mean_result(i));
fprintf('* median: %.2f (uncalibrated)\n', median_result(i));
fprintf('* std: %.2f (uncalibrated)\n', std(rawDist));
figure(1); cdfplot(rawDist);
diffs_each = param_linear(1) * rawDist + param_linear(2) - targets(i);
pd = fitdist(diffs_each(~isnan(diffs_each))', 'Normal');
fit_pd_each(i) = pd;
% figure(2);
% scatter3(...
% sqrt(rawDistVar),...
% rssi,...
% rawDist - targets(i));
all_data = [...
all_data,...
[rawDist;...
targets(i) * ones(1, size(rawDist, 2));...
sqrt(rawDistVar);...
rssi
]...
];
end
% % shuffle
% shuffled_data = all_data(:, randperm(size(all_data, 2)));
%
% % 10-fold cross validation
% step = floor(size(shuffled_data, 2) / 20);
% params = zeros(2, 20);
% mse = zeros(1, 20);
% for i = 1:20
% from = step * (i - 1) + 1;
% to = step * i;
% train_data = shuffled_data;
% test_data = train_data(:, from:to);
% train_data(:, from:to) = [];
% params(:, i) = polyfit(train_data(1, :), train_data(2, :), 1);
% test_est = params(1, i) * test_data(1, :) + params(2, i);
% mse(i) = sum((test_est - test_data(2, :)).^2) / size(test_data, 2);
% end
% param(1) = sum(params(1, :)) / size(mse, 2);
% % mse ./ sum(mse) * params(1, :)';
% param(2) = sum(params(2, :)) / size(mse, 2);
% % mse ./ sum(mse) * params(2, :)';
% validated_fit_data = param(1) * all_data(1, :) + param(2);
% mstd_1 = sqrt(sum((validated_fit_data - all_data(2, :)).^2) /...
% size(all_data, 2));
figure(3); clf; hold on;
scatter(all_data(1, :), all_data(2, :), 'b.');
plot(median_result, targets, 'r', 'LineWidth', 2)
% linear fit
param_linear = polyfit(all_data(1, :), all_data(2, :), 1);
data_linear = param_linear(1) * all_data(1, :) + param_linear(2);
mstd_linear = sqrt(...
sum((data_linear - all_data(2, :)).^2) / size(all_data, 2));
scatter(all_data(1, :), data_linear, 'c.');
diffs = data_linear - all_data(2, :); diffs_std = [];
figure(4); clf; hold on;
cdfplot(diffs)
% for i = 1:size(targets, 2)
% diffs_std(i) = std(diffs(all_data(2, :) == targets(i)));
% % pause;
% end
% scatter(median_rssi, diffs_std)
figure(3);
% parabolic fit
param_parabolic = polyfit(all_data(1, :), all_data(2, :), 2);
data_parabolic = ...
param_parabolic(1) * all_data(1, :).^2 +...
param_parabolic(2) * all_data(1, :) + ...
param_parabolic(3);
mstd_parabolic = sqrt(...
sum((data_parabolic - all_data(2, :)).^2) / size(all_data, 2));
scatter(all_data(1, :), data_parabolic, 'g.');
xlabel('Raw Distance');
ylabel('Target Distance');
legend('all data', 'median val', 'linear fit', 'parabolic fit', 'location', 'best')
fprintf('Std Err:\n');
fprintf(' linear mode: %.6f\n', mstd_linear);
fprintf(' parabolic mode: %.6f\n', mstd_parabolic);
%
% diffs = [];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_1517706485.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_1517706584.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_1517706685.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_1517706784.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_1517706881.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_1517706976.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_1517707172.txt')];
%
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_r_1517706433.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_r_1517706537.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_r_1517706636.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_r_1517706737.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_r_1517706835.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_r_1517706930.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_outdoor/result_walking_3800_to_100cm_r_1517707073.txt')];
% pd = fitdist(diffs(~isnan(diffs))', 'Normal');
% prob_fit = cdf(pd, diffs(~isnan(diffs))');
% figure(10); clf; hold on;
% scatter(diffs(~isnan(diffs))', prob_fit);
% cdfplot(diffs); xlim([-200, 200])
% xlabel('Distance Err (cm)')
% title('Distribution of Distance Err When Walking')
% legend('fitted \mu=53.5043, \sigma=72.1852', 'actual dist error', 'location', 'best')
% %
% diffs = [];
% diffs = [diffs, calibration_walking('calibration_data/walking_indoor/result_walking_3600_to_100cm_1517693155.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_indoor/result_walking_3600_to_100cm_1517693287.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_indoor/result_walking_3600_to_100cm_1517693408.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_indoor/result_walking_3600_to_100cm_1517693534.txt')];
%
% diffs = [];
% diffs = [diffs, calibration_walking('calibration_data/walking_indoor/result_walking_3600_to_100cm_r_1517693224.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_indoor/result_walking_3600_to_100cm_r_1517693350.txt')];
% diffs = [diffs, calibration_walking('calibration_data/walking_indoor/result_walking_3600_to_100cm_r_1517693475.txt')];
% pd = fitdist(diffs(~isnan(diffs))', 'Normal');
% prob_fit = cdf(pd, diffs(~isnan(diffs))');
% figure(11); clf; hold on;
% scatter(diffs(~isnan(diffs))', prob_fit);
% cdfplot(diffs); xlim([-200, 200])
% xlabel('Distance Err (cm)')
% title('Distribution of Distance Err When Walking')
% legend('fitted \mu=28.8055, \sigma=50.1228', 'actual dist error', 'location', 'best')

View File

@ -0,0 +1,105 @@
clear all;
folder = 'calibration_data/new_indoor/';
files = dir(folder); files = files(3:end); % remove . and ..
files_left = files;
files_right = files;
targets = zeros(1, length(files));
for i = length(files):-1:1
if contains(files(i).name, 'left') || contains(files(i).name, 'right')
if contains(files(i).name, 'left')
files_right(i) = [];
% remove only once
targets(i) = sscanf(files(i).name, 'result_%dcm.txt');
else
targets(i) = [];
files_left(i) = [];
end
else
files_right(i) = [];
files_left(i) = [];
targets(i) = [];
end
end
for i = length(files):-1:1
target = sscanf(files(i).name, 'result_%dcm.txt');
if isempty(target)
continue
end
if ~ismember(target, targets) || ...
contains(files(i).name, 'left') || ...
contains(files(i).name, 'right') || ...
contains(files(i).name, 'ap') || ...
contains(files(i).name, 'down') || ...
contains(files(i).name, 'up')
files(i) = [];
end
end
[targets, orderI] = sort(targets);
files_left = files_left(orderI);
files_right = files_right(orderI);
files = files(orderI);
medians = zeros(3, length(targets));
figure(1); clf;
subplot(2, 1, 1); hold on;
for i = 1:length(targets)
% normal
filename = [folder, files(i).name];
data = readtable(filename, 'ReadVariableNames', 0);
if isempty(data)
continue
end
data = data(2:end, :);
rawDist_ori = str2double(table2array(data(:, 5)))';
rawDistVar_ori = str2double(table2array(data(:, 6)))';
rssi_ori = str2double(table2array(data(:, 7)))';
time_ori = str2double(table2array(data(:, 8)))';
medians(1, i) = median(rawDist_ori);
% left
filename = [folder, files_left(i).name];
data = readtable(filename, 'ReadVariableNames', 0);
if isempty(data)
continue
end
data = data(2:end, :);
rawDist_left = str2double(table2array(data(:, 5)))';
rawDistVar_left = str2double(table2array(data(:, 6)))';
rssi_left = str2double(table2array(data(:, 7)))';
time_left = str2double(table2array(data(:, 8)))';
medians(2, i) = median(rawDist_left);
% right
filename = [folder, files_right(i).name];
data = readtable(filename, 'ReadVariableNames', 0);
if isempty(data)
continue
end
data = data(2:end, :);
rawDist_right = str2double(table2array(data(:, 5)))';
rawDistVar_right = str2double(table2array(data(:, 6)))';
rssi_right = str2double(table2array(data(:, 7)))';
time_right = str2double(table2array(data(:, 8)))';
medians(3, i) = median(rawDist_right);
% plot rssi relation
subplot(2, 1, 1);
scatter(rawDist_ori, rssi_ori, 'ro');
scatter(rawDist_left, rssi_left, 'cs');
scatter(rawDist_right, rssi_right, 'b*');
end
subplot(2, 1, 1);
title('Signal propagation variation');
legend('Face2face', 'Left2Face', 'Right2Face', 'Location', 'best');
xlabel('Reported Raw Distance (cm)');
ylabel('RSSI (dBm)');
subplot(2, 1, 2); hold on;
title('Validation: No Significant Variation in Orientation :)');
scatter(targets, medians(1, :), 'ro', 'LineWidth', 2);
scatter(targets, medians(2, :), 'cs', 'LineWidth', 2);
scatter(targets, medians(3, :), 'b*', 'LineWidth', 2);
legend('Face2face', 'Left2Face', 'Right2Face', 'Location', 'best');
xlabel('Groundtruth (cm)');
ylabel({'(Median) Reported', 'Raw Distance (cm)'});

70
calibration_walking.m Normal file
View File

@ -0,0 +1,70 @@
function diffs = calibration_walking(filename)
startI = 3600;
endI = 100;
if contains(filename, '_r_')
startI = 100;
endI = 3600;
end
% param_linear = [0.6927, 400.3157]; % medical_try_0b
% param_linear = [0.7927,483.3157]; % medical_try_1f
param_linear = [0.8927,553.3157]; % outdoor
% param_linear = [0.9376, 558.0551]; % indoor
data = readtable(filename, 'ReadVariableNames', 0);
data = data(2:end, :);
caliDist = str2double(table2array(data(:, 2)))';
rawRTT = str2double(table2array(data(:, 3)))';
rawRTTVar = str2double(table2array(data(:, 4)))';
rawDist = str2double(table2array(data(:, 5)))';
rawDistVar = str2double(table2array(data(:, 6)))';
rssi = str2double(table2array(data(:, 7)))';
time = str2double(table2array(data(:, 8)))';
% clear invalid data
logistics = rawDist < -1000 | rawDist > 10000 | isnan(time);
caliDist(logistics) = [];
rawRTT(logistics) = [];
rawRTTVar(logistics) = [];
rawDist(logistics) = [];
rawDistVar(logistics) = [];
rssi(logistics) = [];
time(logistics) = [];
% normalize time
time = (time - time(1)) ./ (time(end) - time(1));
% rawDist = movmean(rawDist, 10, 'omitnan');
% rssi = movmean(rssi, 10, 'omitnan');
% for i = size(caliDist, 2) - 3: -4: 1
% rawDist(i) = nanmean(rawDist(i: i + 3));
% rawDist(i + 1: i + 3) = [];
% rssi(i) = nanmean(rssi(i: i + 3));
% rssi(i + 1: i + 3) = [];
% time(i + 1: i + 3) = [];
% end
dist = rawDist * param_linear(1) + param_linear(2);
targets = (time - time(1)) * (endI - startI) / (time(end) - time(1)) + startI;
diffs = (targets - dist);
fprintf("** mean diff: %.4f cm\n", nanmean(diffs));
fprintf("** median diff: %.4f cm\n", nanmedian(diffs));
figure(1); % clf;
scatter3(time, dist, rssi); hold on; scatter(time, dist, '.')
% plot([time(1), time(end)], [startI, endI]);
view([0, 90]);
% if contains(filename, '_r_')
% title('back')
% figure(9); hold on;
% plot(flip((time - time(1)) ./ (time(end) - time(1))), rssi)
% else
% title('front')
% figure(9); hold on;
% plot(((time - time(1)) ./ (time(end) - time(1))), rssi)
% end
% figure(2); clf; hold on;
% pd = fitdist(diffs(~isnan(diffs))', 'Normal')
% prob_fit = cdf(pd, diffs(~isnan(diffs))');
% scatter(diffs(~isnan(diffs))', prob_fit);
% cdfplot(diffs);
% % histogram(diffs, 'BinWidth', 1, 'Normalization', 'pdf');
end

16
config_entry.default Executable file
View File

@ -0,0 +1,16 @@
{
"34:f6:4b:5e:69:1f": {
"bw": 20,
"cf": 2462,
"spb": 255,
"retries": 3,
"location": "500,0"
},
"34:f6:4b:5e:69:0b": {
"bw": 20,
"cf": 2437,
"spb": 255,
"retries": 3,
"location": "0,0"
}
}

97
distribution_loc.m Normal file
View File

@ -0,0 +1,97 @@
% pd = makedist('normal', 'mu', 0, 'sigma', 53.8836); % cm
pd = makedist('tlocationscale', 'mu', 0, 'sigma', 42.8875, 'nu', 5.3095);
% params
plotflag = 0;
samples = 100000;
farthest_dist = [500, 2000]; % cm
devLocations = [0, 0;
500, 0;
500, 2000;
0, 2000];
% setup
for i = size(devLocations, 1):-1:1
for j = size(devLocations, 1):-1:(i+1)
diff = devLocations(i, :) - devLocations(j, :);
pairwiseDist(i, j) = sqrt(sum(diff.^2));
end
end
targetLocs = rand(samples, 2) .* farthest_dist;
% targetLocs = [250, 100; 250, 200; 250, 300; 250, 400; 250, 500];
% samples = size(targetLocs, 1);
trueDist = zeros(size(devLocations, 1), samples);
estDist = zeros(size(devLocations, 1), samples);
% % estimate measurements
for i = 1:size(devLocations, 1)
trueDist(i, :) = sqrt(sum((targetLocs - devLocations(i, :)).^2, 2));
if isa(pd, 'prob.NormalDistribution')
randvar_vec = random(pd, size(trueDist(i, :)));
else
signs = (rand(size(trueDist(i, :))) < 0.5) * 2 - 1;
randvar_vec = random(pd, size(trueDist(i, :))) .* signs;
end
estDist(i, :) = trueDist(i, :) + randvar_vec;
end
% estimate locations
estLocs = zeros(size(targetLocs));
if plotflag
figure(3); clf; hold on;
scatter(devLocations(:, 1), devLocations(:, 2), 'b*');
end
for i = 1:samples
points = [];
for j = 1:size(devLocations, 1)
for k = (j+1):size(devLocations, 1)
d = pairwiseDist(j, k);
if (d > estDist(j, i) + estDist(k, i)) ||...
(d < abs(estDist(j, i) - estDist(k, i)))
continue
end
a = (estDist(j, i) * estDist(j, i) -...
estDist(k, i) * estDist(k, i) +...
d * d) / (2 * d);
h = sqrt(estDist(j, i) * estDist(j, i) - a * a);
x0 = devLocations(j, 1) +...
a * (devLocations(k, 1) - devLocations(j, 1)) / d;
y0 = devLocations(j, 2) +...
a * (devLocations(k, 2) - devLocations(j, 2)) / d;
rx = -(devLocations(k, 2) - devLocations(j, 2)) * (h / d);
ry = -(devLocations(k, 1) - devLocations(j, 1)) * (h / d);
points = [points; x0 + rx, y0 - ry; x0 - rx, y0 + ry];
end
end
if ~isempty(points) && size(devLocations, 1) == 2
if devLocations(1, 2) == devLocations(2, 2)
points(points(:, 2) < devLocations(1, 2), :) = [];
elseif devLocations(1, 1) == devLocations(2, 1)
points(points(:, 1) < devLocations(1, 1), :) = [];
end
end
estLoc = median(points, 1);
if isempty(estLoc)
estLocs(i, :) = nan;
else
estLocs(i, :) = estLoc;
if plotflag
scatter(targetLocs(i, 1), targetLocs(i, 2), 'ks');
scatter(estLocs(i, 1), estLocs(i, 2), 'r.');
pause(0.001)
end
end
end
logistics = isnan(estLocs(:, 1));
estLocs(logistics, :) = [];
targetLocs(logistics, :) = [];
fprintf('* %d cannot est locations\n', sum(logistics));
err = sqrt(sum((estLocs - targetLocs).^2, 2));
figure(4); hold on; cdfplot(err);
fitresult = allfitdist(err, 'pdf');
% essentially, we are getting a gamma distribution in generall
% the result should be validated in real-life experiments also

167
distribution_range.m Normal file
View File

@ -0,0 +1,167 @@
files0b = {}; files1f = {};
targets0b = []; targets1f = [];
% dataset A
folder = 'calibration_data/02222018_outdoor_500/';
files = dir(folder); files = files(3:end); % remove . and ..
for i = length(files):-1:1
if (~contains(files(i).name, 'result') ||...
~contains(files(i).name, 'extract') ||...
contains(files(i).name, 'locs'))
elseif contains(files(i).name, '34-f6-4b-5e-69-1f')
files1f = [files1f, [folder, files(i).name]];
loc = sscanf(files(i).name, 'result_static_%f_%f_extract.txt');
targets1f = [targets1f, sqrt(sum((loc - [5;0]).^2)) * 100];
else
files0b = [files0b, [folder, files(i).name]];
loc = sscanf(files(i).name, 'result_static_%f_%f_extract.txt');
targets0b = [targets0b, sqrt(sum((loc - [0;0]).^2)) * 100];
end
end
% dataset B
folder = 'calibration_data/02142018_outdoor_480/';
files = dir(folder); files = files(3:end); % remove . and ..
for i = length(files):-1:1
if (~contains(files(i).name, 'result') ||...
~contains(files(i).name, 'extract') ||...
contains(files(i).name, 'locs'))
elseif contains(files(i).name, '34-f6-4b-5e-69-1f')
files1f = [files1f, [folder, files(i).name]];
loc = sscanf(files(i).name, 'result_static_%f_%f_extract.txt');
targets1f = [targets1f, sqrt(sum((loc - [4.8;0]).^2)) * 100];
else
files0b = [files0b, [folder, files(i).name]];
loc = sscanf(files(i).name, 'result_static_%f_%f_extract.txt');
targets0b = [targets0b, sqrt(sum((loc - [0;0]).^2)) * 100];
end
end
% % dataset C
% folder = 'calibration_data/outdoor/';
% files = dir(folder); files = files(3:end); % remove . and ..
% for i = length(files):-1:1
% if (~contains(files(i).name, 'result'))
%
% else
% files1f = [files1f, [folder, files(i).name]];
% targets1f = [targets1f, sscanf(files(i).name, 'result_%dcm.txt')];
% end
% end
[targets0b, orderI] = sort(targets0b);
files0b = files0b(orderI);
[targets1f, orderI] = sort(targets1f);
files1f = files1f(orderI);
all_data0b = [];
all_data1f = [];
% figure(1); clf;
for i = 1:length(files0b)
filename = files0b{i};
fileID = fopen(filename, 'r');
formatSpec = [...
'Target: %x:%x:%x:%x:%x:%x, status: %d, ',...
'rtt: %d psec, distance: %d cm\n'...
];
data = fscanf(fileID, formatSpec, [9 Inf]);
fclose(fileID);
if isempty(data)
data = readtable(filename, 'ReadVariableNames', 0);
if isempty(data)
continue
end
data = data(2:end, :);
caliDist = str2double(table2array(data(:, 2)))';
rawRTT = str2double(table2array(data(:, 3)))';
rawRTTStd = sqrt(str2double(table2array(data(:, 4)))');
rawDist = str2double(table2array(data(:, 5)))';
rawDistStd = sqrt(str2double(table2array(data(:, 6)))');
rssi = str2double(table2array(data(:, 7)))';
time = str2double(table2array(data(:, 8)))';
logistics = isnan(caliDist) | isnan(time);
else
% get rid of invalid data
data(:, data(7, :) ~= 0) = [];
data(:, data(9, :) < -1000) = [];
rawDist = data(9, :);
rawDistStd = zeros(size(rawDist));
rssi = zeros(size(rawDist));
caliDist = 0.8927 * rawDist + 553.3157;
logistics = isnan(caliDist);
end
caliDist(logistics) = [];
rssi(logistics) = [];
% figure(1); cdfplot(caliDist);
err = nanmedian(caliDist) - targets0b(i);
fprintf(...
'%.2f: rssi %.2f sig std %.2f dist std %.2f err %.2f\n',...
targets0b(i), nanmedian(rssi), nanstd(rssi), nanstd(caliDist), err)
% all_data0b = [all_data0b;...
% [nanmedian(rssi), nanstd(rssi),...
% nanmedian(rawDistStd), nanstd(rawDistStd), err]];
all_data0b = [all_data0b, caliDist - targets0b(i)];
end
for i = 1:length(files1f)
filename = files1f{i};
fileID = fopen(filename, 'r');
formatSpec = [...
'Target: %x:%x:%x:%x:%x:%x, status: %d, ',...
'rtt: %d psec, distance: %d cm\n'...
];
data = fscanf(fileID, formatSpec, [9 Inf]);
fclose(fileID);
if isempty(data)
data = readtable(filename, 'ReadVariableNames', 0);
if isempty(data)
continue
end
data = data(2:end, :);
caliDist = str2double(table2array(data(:, 2)))';
rawRTT = str2double(table2array(data(:, 3)))';
rawRTTStd = sqrt(str2double(table2array(data(:, 4)))');
rawDist = str2double(table2array(data(:, 5)))';
rawDistStd = sqrt(str2double(table2array(data(:, 6)))');
rssi = str2double(table2array(data(:, 7)))';
time = str2double(table2array(data(:, 8)))';
logistics = isnan(caliDist) | isnan(time);
else
% get rid of invalid data
data(:, data(7, :) ~= 0) = [];
data(:, data(9, :) < -1000) = [];
rawDist = data(9, :);
rawDistStd = zeros(size(rawDist));
rssi = zeros(size(rawDist));
caliDist = 0.8927 * rawDist + 553.3157;
logistics = isnan(caliDist);
end
caliDist(logistics) = [];
rssi(logistics) = [];
% figure(1); cdfplot(caliDist - targets1f(i));
err = nanmedian(caliDist) - targets1f(i);
fprintf(...
'%.2f: rssi %.2f sig std %.2f dist std %.2f err %.2f\n',...
targets1f(i), nanmedian(rssi), nanstd(rssi), nanstd(caliDist), err)
% all_data1f = [all_data1f;...
% [nanmedian(rssi), nanstd(rssi),...
% nanmedian(rawDistStd), nanstd(rawDistStd), err]];
all_data1f = [all_data1f, caliDist - targets1f(i)];
end
figure(2); clf; hold on;
cdfplot(all_data0b); cdfplot(all_data1f);
hold off; legend('0b', '1f');
pd1f = fitdist(all_data1f', 'Normal');
pd0b = fitdist(all_data0b', 'Normal');
pd = fitdist([all_data0b, all_data1f]', 'Normal');
% Normal distribution
% mu = -5.77619 [-7.52889, -4.02349] <- measurement error
% sigma = 52.8182 [51.6078, 54.0872]
% The sigma is consistent across observations in multiple ranging locations

View File

View File

View File

View File

View File

@ -317,7 +317,8 @@ static void parse_ftm_results(struct nlattr **attrs, int status,
struct nlattr *tb[NL80211_FTM_RESP_ENTRY_ATTR_MAX + 1]; struct nlattr *tb[NL80211_FTM_RESP_ENTRY_ATTR_MAX + 1];
unsigned char bssid[ETH_ALEN]; unsigned char bssid[ETH_ALEN];
char macbuf[6 * 3]; char macbuf[6 * 3];
long long int rtt, dist; long long int rtt, rtt_var, dist, dist_var;
signed int rssi;
int err; int err;
printf("FTM result! Status: %d\n", status); printf("FTM result! Status: %d\n", status);
@ -352,13 +353,22 @@ static void parse_ftm_results(struct nlattr **attrs, int status,
mac_addr_n2a(macbuf, bssid); mac_addr_n2a(macbuf, bssid);
rtt = (long long int)nla_get_u64(tb[NL80211_FTM_RESP_ENTRY_ATTR_RTT]); rtt = (long long int)nla_get_u64(tb[NL80211_FTM_RESP_ENTRY_ATTR_RTT]);
rtt_var = tb[NL80211_FTM_RESP_ENTRY_ATTR_RTT_VAR] ?
(long long int)nla_get_u64(
tb[NL80211_FTM_RESP_ENTRY_ATTR_RTT_VAR]) : 0;
dist = tb[NL80211_FTM_RESP_ENTRY_ATTR_DISTANCE] ? dist = tb[NL80211_FTM_RESP_ENTRY_ATTR_DISTANCE] ?
(long long int)nla_get_u64(tb[NL80211_FTM_RESP_ENTRY_ATTR_DISTANCE]) : (long long int)nla_get_u64(tb[NL80211_FTM_RESP_ENTRY_ATTR_DISTANCE]) :
rtt * SOL_CM_PSEC; rtt * SOL_CM_PSEC;
dist_var = tb[NL80211_FTM_RESP_ENTRY_ATTR_DISTANCE_VAR] ?
(long long int)nla_get_u64(
tb[NL80211_FTM_RESP_ENTRY_ATTR_DISTANCE_VAR]) : 0;
rssi = tb[NL80211_FTM_RESP_ENTRY_ATTR_RSSI] ?
(signed int)nla_get_s8(tb[NL80211_FTM_RESP_ENTRY_ATTR_RSSI]) : 0;
printf("Target: %s, status: %d, rtt: %lld psec, distance: %lld cm", printf(
macbuf, nla_get_u8(tb[NL80211_FTM_RESP_ENTRY_ATTR_STATUS]), rtt, "Target: %s, status: %d, rtt: %lld (±%lld) psec, distance: %lld (±%lld) cm, rssi: %d dBm",
dist); macbuf, nla_get_u8(tb[NL80211_FTM_RESP_ENTRY_ATTR_STATUS]),
rtt, rtt_var, dist, dist_var, rssi);
if (tb[NL80211_FTM_RESP_ENTRY_ATTR_LCI]) if (tb[NL80211_FTM_RESP_ENTRY_ATTR_LCI])
iw_hexdump("LCI", iw_hexdump("LCI",

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

93
iw_parser.py Executable file
View File

@ -0,0 +1,93 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import re
import argparse
from numpy import min, max, median, mean, std
from numpy.random import choice
def wrapper(args):
if not args['filepath'] or not os.path.isfile(args['filepath']):
return
results = []
regex = (
r"Target: (([0-9a-f]{2}:*){6}), " +
r"status: ([0-9]), rtt: ([0-9\-]+) psec, " +
r"distance: ([0-9\-]+) cm"
)
regex_new = (
r"Target: (([0-9a-f]{2}:*){6}), " +
r"status: ([0-9]), rtt: ([0-9\-]+) \(±([0-9\-]+)\) psec, " +
r"distance: ([0-9\-]+) \(±([0-9\-]+)\) cm, rssi: ([0-9\-]+) dBm"
)
with open(args['filepath']) as f:
data_ori = f.readlines()
if args['sample'] is None:
data = data_ori
else:
data = choice(data_ori, size=args['sample'], replace=False)
for line in data:
match = re.search(regex_new, line)
if match:
mac = match.group(1)
status = int(match.group(3))
rtt = int(match.group(4))
rtt_var = int(match.group(5))
raw_distance = int(match.group(6))
raw_distance_var = int(match.group(7))
rssi = int(match.group(8))
else:
match = re.search(regex, line)
if match:
mac = match.group(1)
status = int(match.group(3))
rtt = int(match.group(4))
raw_distance = int(match.group(5))
else:
continue
if status is not 0 or raw_distance < -1000:
continue
results.append(raw_distance * args['cali'][0] + args['cali'][1])
print('statics of results')
print('* num of valid data: {0}'.format(len(results)))
print('* min: {0:.2f}cm'.format(min(results)))
print('* max: {0:.2f}cm'.format(max(results)))
print('* mean: {0:.2f}cm'.format(mean(results)))
print('* median: {0:.2f}cm'.format(median(results)))
print('* std: {0:.2f}cm'.format(std(results)))
def main():
p = argparse.ArgumentParser(description='iw parser')
p.add_argument(
'filepath',
help="input file path for result"
)
p.add_argument(
'--cali',
nargs=2,
# default=(0.9376, 558.0551), # indoor
default=(0.8927, 553.3157), # outdoor
type=float,
help="calibrate final result"
)
p.add_argument(
'--sample',
default=None,
type=int,
help="if set (an integer), sample data for accuracy testing"
)
try:
args = vars(p.parse_args())
except Exception as e:
print(e)
sys.exit()
wrapper(args)
if __name__ == '__main__':
main()

81
libExtraction.py Executable file
View File

@ -0,0 +1,81 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import time
import argparse
def separateMAC(fp):
results = {}
nameLine = None
startLine = None
endLine = None
with open(fp, 'r') as f:
for line in f:
if nameLine is None:
nameLine = line
continue
if 'ff:ff:ff:ff:ff:ff' in line:
if startLine is None:
startLine = line
elif endLine is None:
endLine = line
continue
mac = line.split(',')[0]
if mac not in results:
print('* Find MAC: {0}'.format(mac))
results[mac] = []
results[mac].append(line)
return results, nameLine, startLine, endLine
def extract_each(fp):
results, nameLine, startLine, endLine = separateMAC(fp)
base, ext = os.path.splitext(fp)
base = '_'.join(base.split('_')[:-1]) # remove time stamp
t = int(time.time())
for mac in results:
identifier = mac.replace(':', '-')
with open(
'{0}_extract_{1}_{2}{3}'
.format(base, identifier, t, ext), 'w'
) as f:
f.write(nameLine)
if startLine:
f.write(startLine)
for line in results[mac]:
f.write(line)
if endLine:
f.write(endLine)
def wrapper(args):
if not os.path.isfile(args['filepath']):
return
extract_each(args['filepath'])
def main():
p = argparse.ArgumentParser(description='separate data from MAC addr')
p.add_argument(
'filepath',
help="file path"
)
p.add_argument(
'--verbose', '-v',
default=False,
action="store_true",
help="if set, show detailed messages"
)
try:
args = vars(p.parse_args())
except Exception as e:
print(str(e))
sys.exit()
args['time_of_exec'] = int(time.time())
wrapper(args)
if __name__ == '__main__':
main()

268
libLocalization.py Executable file
View File

@ -0,0 +1,268 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import time
import math
import argparse
from math import sin, cos, sqrt, atan2, radians
from numpy import median, arange
R = 6373000.0 # unit: meter
# ======= start =======
# excerpt from
# https://github.com/noomrevlis/trilateration/blob/master/trilateration2D.py
class Point(object):
def __init__(self, x, y):
self.x = round(float(x), 6)
self.y = round(float(y), 6)
class Circle(object):
def __init__(self, p, radius):
if isinstance(p, Point):
self.center = p
elif isinstance(p, list):
self.center = Point(p[0], p[1])
self.radius = round(float(radius), 6)
def get_distance(p1, p2):
if isinstance(p1, Point) and isinstance(p2, Point):
return math.sqrt(
(p1.x - p2.x) * (p1.x - p2.x) +
(p1.y - p2.y) * (p1.y - p2.y)
)
elif (
(isinstance(p1, list) or isinstance(p1, tuple)) and
(isinstance(p2, list) or isinstance(p2, tuple))
):
return math.sqrt(
(p1[0] - p2[0]) * (p1[0] - p2[0]) +
(p1[1] - p2[1]) * (p1[1] - p2[1])
)
return -1
def get_distance_gps(p1, p2, isDeg=True):
# format: p1 ~ p2 = [lat, lon] in deg
if (
(isinstance(p1, list) or isinstance(p1, tuple)) and
(isinstance(p2, list) or isinstance(p2, tuple))
):
if isDeg:
lat1 = radians(p1[0])
lon1 = radians(p1[1])
lat2 = radians(p2[0])
lon2 = radians(p2[1])
else:
lat1 = (p1[0])
lon1 = (p1[1])
lat2 = (p2[0])
lon2 = (p2[1])
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
return R * c
return -1
def get_two_circles_intersecting_points(c1, c2):
p1 = c1.center
p2 = c2.center
r1 = c1.radius
r2 = c2.radius
d = get_distance(p1, p2)
# if to far away, or self contained - can't be done
if d >= (r1 + r2) or d <= math.fabs(r1 - r2):
return None
a = (r1 * r1 - r2 * r2 + d * d) / (2 * d)
h = math.sqrt(pow(r1, 2) - pow(a, 2))
x0 = p1.x + a*(p2.x - p1.x)/d
y0 = p1.y + a*(p2.y - p1.y)/d
rx = -(p2.y - p1.y) * (h/d)
ry = -(p2.x - p1.x) * (h / d)
return [
Point(x0 + rx, y0 - ry),
Point(x0 - rx, y0 + ry)
]
def get_intersecting_points(circles):
points = []
num = len(circles)
for i in range(num):
j = i + 1
for k in range(j, num):
res = get_two_circles_intersecting_points(circles[i], circles[k])
if res:
points.extend(res)
return points
def is_contained_in_circles(point, circles):
for i in range(len(circles)):
if (
get_distance(point, circles[i].center) > circles[i].radius
):
return False
return True
def get_polygon_center(points):
center = Point(float('nan'), float('nan'))
xs = []
ys = []
num = len(points)
if num is 0:
return center
for i in range(num):
xs.append(points[i].x)
ys.append(points[i].y)
center.x = median(xs)
center.y = median(ys)
return center
# ======= end =======
def calcInnerPoints(circles, bounds):
inner_points = []
for p in get_intersecting_points(circles):
# if not is_contained_in_circles(p, circles):
# continue
if bounds is not None:
if bounds.get('x_min', None) is not None and bounds['x_min'] > p.x:
continue
if bounds.get('x_max', None) is not None and bounds['x_max'] < p.x:
continue
if bounds.get('y_min', None) is not None and bounds['y_min'] > p.y:
continue
if bounds.get('y_max', None) is not None and bounds['y_max'] < p.y:
continue
inner_points.append(p)
return inner_points
def trilateration2d(mydict, bounds=None, verbose=False):
'''
mydict format: {
location: (radius, std),...
}
bound format: {
'x_min': float, 'x_max': float,
'y_min': float, 'y_max': float
}
'''
points = []
circles = []
for loc in mydict:
tmp = loc.split(',')
p = Point(tmp[0], tmp[1])
points.append(p)
if mydict[loc][1]:
for r in arange(
max(mydict[loc][0] - mydict[loc][1], 0.001),
mydict[loc][0] + mydict[loc][1],
1
):
circles.append(Circle(p, r))
else:
circles.append(Circle(p, mydict[loc][0]))
inner_points = calcInnerPoints(circles, bounds)
if verbose:
print('* Inner points:')
if len(inner_points) is 0:
print('*** No inner points detected!!')
for point in inner_points:
print('*** ({0:.3f}, {1:.3f})'.format(point.x, point.y))
center = get_polygon_center(inner_points)
return (center.x, center.y)
def deriveLocation(args, results):
# TODO: currently assume 2D
loc_distance = {}
for mac in results:
if mac not in args['config_entry']:
continue
loc = args['config_entry'][mac].get('location', None)
if loc is None:
continue
loc_distance[loc] = results[mac]
loc = trilateration2d(
loc_distance,
bounds=args.get('loc_bounds', None),
verbose=args.get('verbose', False)
)
if args.get('outfp', False):
with open("{0}_locs".format(args['outfp']), 'a') as f:
f.write(
"{0:.6f},{1:.4f},{2:.4f}\n"
.format(time.time(), loc[0], loc[1])
)
return loc
def plotLocation(loc):
handler = None
try:
import matplotlib.pyplot as plt
handler = plt.scatter(loc[0], loc[1])
plt.pause(0.01)
except Exception:
pass
return handler
if __name__ == '__main__':
dist = get_distance_gps([41.790366, -87.601111], [41.790609, -87.601411])
print(dist)
# loc = deriveLocation(
# {
# 'config_entry': {
# '34:f6:4b:5e:69:1f': {'location': '0,0'},
# '34:f6:4b:5e:69:1e': {'location': '180,0'},
# '34:f6:4b:5e:69:1d': {'location': '0,2'},
# '34:f6:4b:5e:69:1a': {'location': '1,2'}
# },
# 'verbose': True,
# 'outfp': '',
# 'loc_bounds': {
# 'y_min': 0
# }
# },
# {
# '34:f6:4b:5e:69:1f': (257, 0),
# '34:f6:4b:5e:69:1e': (50, 50)
# }
# )
# flagPlot = False
# try:
# import matplotlib.pyplot as plt
# flagPlot = True
# except Exception:
# pass
# if flagPlot:
# fig = plt.figure()
# plt.ion()
# plt.xlim([-100, 300])
# plt.ylim([-10, 500])
# while 1:
# try:
# handler = plotLocation(loc)
# if handler is None:
# plt.close(fig)
# break
# handler.remove()
# except KeyboardInterrupt:
# plt.close(fig)
# break
# except Exception:
# raise

335
libMeasurement.py Executable file
View File

@ -0,0 +1,335 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import re
import os
import time
import json
import argparse
import subprocess
from numpy import median, sqrt
from libLocalization import deriveLocation
def which(program):
'''
check if a certain program exists
'''
def is_executable(fp):
return os.path.isfile(fp) and os.access(fp, os.X_OK)
fp, fn = os.path.split(program)
if fp:
if is_executable(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exec_file = os.path.join(path, program)
if is_executable(exec_file):
return exec_file
return None
class Measurement(object):
def __init__(self, interface, ofp=None, cali=(1.0, 0.0)):
self.outf = None
self.interface = interface
# default file path for config for iw ftm_request
self.config_fp = '/tmp/config_entry'
if ofp:
try:
self.outf = open(ofp, 'w')
self.outf.write(
'MAC,caliDist(cm),rawRTT(psec),rawRTTVar,rawDist(cm),' +
'rawDistVar,rssi(dBm),time(sec)\n'
)
self.outf.write(
"ff:ff:ff:ff:ff:ff,nan,nan,nan,nan,nan,nan,{0:.6f}\n"
.format(time.time())
)
except Exception as e:
print(str(e))
self.regex = (
r"Target: (([0-9a-f]{2}:*){6}), " +
r"status: ([0-9]), rtt: ([0-9\-]+) \(±([0-9\-]+)\) psec, " +
r"distance: ([0-9\-]+) \(±([0-9\-]+)\) cm, rssi: ([0-9\-]+) dBm"
)
self.cali = cali
if not self.check_iw_validity():
exit(127) # command not found
def check_iw_validity(self):
'''
check if iw exists and support FTM commands
'''
iwPath = which('iw')
if iwPath is None:
print('Err: iw command not found!')
return False
p = subprocess.Popen(
"iw --help | grep FTM",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True
)
out, err = p.communicate()
if err:
print('Err: {0}'.format(err))
return False
if 'FTM' not in out:
print('Err: iw command does not support FTM')
return False
return True
def prepare_config_file(self, targets):
if not isinstance(targets, dict):
return False
with open(self.config_fp, 'w') as of:
for bssid in targets:
of.write(
"{0} bw={1} cf={2} retries={3} asap spb={4}\n"
.format(
bssid,
targets[bssid]['bw'],
targets[bssid]['cf'],
targets[bssid]['retries'],
targets[bssid]['spb'],
)
)
return True
def get_distance_once(self, verbose=False):
p = subprocess.Popen(
"iw wlp58s0 measurement ftm_request " +
"{0}".format(self.config_fp),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True
)
out, err = p.communicate()
if err:
print(err)
exit(13)
matches = re.finditer(self.regex, out)
if not matches:
return []
result = []
mytime = time.time()
for match in matches:
mac = match.group(1)
status = int(match.group(3))
rtt = int(match.group(4))
rtt_var = int(match.group(5))
raw_distance = int(match.group(6))
raw_distance_var = int(match.group(7))
rssi = int(match.group(8))
if status is not 0 or raw_distance < -1000 or raw_distance > 10000:
continue
distance = self.cali[0] * raw_distance + self.cali[1]
result.append(
(mac, distance, rtt, rtt_var,
raw_distance, raw_distance_var, rssi)
)
if verbose:
print(
'*** {0} - {1}dBm - {2}{3:.2f}cm)'
.format(mac, rssi, raw_distance, sqrt(raw_distance_var))
)
if self.outf is not None:
self.outf.write(
"{0},{1:.2f},{2},{3},{4},{5},{6},{7:.6f}\n"
.format(
mac, distance, rtt, rtt_var,
raw_distance, raw_distance_var,
rssi, mytime
)
)
return result
def get_distance_median(self, rounds=1, verbose=False):
'''
use median instead of mean for less bias with small number of rounds
'''
result = {}
median_result = {}
if rounds < 1:
rounds = 1
for i in range(rounds):
# no guarantee that all rounds are successful
for each in self.get_distance_once(verbose=verbose):
if each[0] not in result:
result[each[0]] = []
result[each[0]].append(each[1:])
for mac in result:
median_result[mac] = (
median([x[0] for x in result[mac]]),
median(
[sqrt(x[4]) * self.cali[0] for x in result[mac]]
)
)
return median_result
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
# properly close the file when destroying the object
if self.outf is not None:
self.outf.write(
"ff:ff:ff:ff:ff:ff,nan,nan,nan,nan,nan,nan,{0:.6f}\n"
.format(time.time())
)
self.outf.close()
def wrapper(args):
if os.path.isfile(args['json']):
args['config_entry'] = json.load(open(args['json'], 'r'))
print('Successfully loaded {0}!'.format(args['json']))
else: # default config
args['config_entry'] = {
'34:f6:4b:5e:69:1f': {
'bw': 20,
'cf': 2462,
'spb': 255,
'retries': 3
}
}
counter = 1
if args['plot']:
try:
import matplotlib.pyplot as plt
from libLocalization import plotLocation
handler = None
fig = plt.figure()
plt.ion()
plt.xlim([-200, 500])
plt.ylim([-10, 1000])
except Exception:
args['plot'] = False
print('Cannot plot because lacking matplotlib!')
with Measurement(
args['interface'],
ofp=args['outfp'], cali=args['cali']
) as m:
while 1:
print('Round {0}'.format(counter))
try:
m.prepare_config_file(args['config_entry'])
# only print out results
results = m.get_distance_median(
rounds=args['rounds'], verbose=args['verbose']
)
for mac in results:
print(
'* {0} is {1:.4f}cm (±{2:.2f}) away.'
.format(mac, results[mac][0], results[mac][1])
)
# calculate location info
if args['locs']:
loc = deriveLocation(args, results)
print(
'* Derived location: ({0:.3f}, {1:.3f})'
.format(loc[0], loc[1])
)
if args['plot']:
try:
if handler is not None:
handler.remove()
handler = plotLocation(loc)
if handler is None:
plt.close(fig)
except KeyboardInterrupt:
plt.close(fig)
break
except Exception:
raise
except KeyboardInterrupt:
break
except Exception as e:
err = str(e)
print(err)
if 'Busy' in err:
break
time.sleep(10)
counter += 1
def main():
p = argparse.ArgumentParser(description='iw measurement tool')
p.add_argument(
'--cali',
nargs=2,
# default=(0.9376, 558.0551), # indoor
default=(0.8927, 553.3157), # outdoor
type=float,
help="calibrate calibration params (pre-defined outdoor by default)"
)
p.add_argument(
'--outfp', '-f',
default=None,
help="if set, will write raw fetched data to file"
)
p.add_argument(
'--rounds',
default=1,
type=int,
help="how many rounds to run one command; default is 1"
)
p.add_argument(
'--interface', '-i',
default='wlp58s0',
help="set the wireless interface"
)
p.add_argument(
'--json', '-j',
default='config_entry.default',
help="load a config json file"
)
p.add_argument(
'--verbose', '-v',
default=False,
action="store_true",
help="if set, show detailed messages"
)
p.add_argument(
'--indoor',
default=False,
action="store_true",
help=(
"if set, use default indoor calibration params " +
"(will be ignored if `cali` is being used)"
)
)
p.add_argument(
'--locs',
default=False,
action="store_true",
help="if set, derive location and store it to file"
)
p.add_argument(
'--plot',
default=False,
action="store_true",
help="if set, will plot the derived location in realtime"
)
try:
args = vars(p.parse_args())
except Exception as e:
print(str(e))
sys.exit()
if args['indoor'] and args['cali'] == (0.8927, 553.3157):
args['cali'] = (0.9376, 558.0551)
args['time_of_exec'] = int(time.time())
# TODO: add option to change loc bounds, currently force y_min = 0
args['loc_bounds'] = {'y_min': 0}
# rename file path by adding time of exec
if args['outfp']:
fp, ext = os.path.splitext(args['outfp'])
args['outfp'] = "{0}_{1}{2}".format(fp, args['time_of_exec'], ext)
wrapper(args)
if __name__ == '__main__':
main()

128
localization_static.py Executable file
View File

@ -0,0 +1,128 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import re
import os
import time
import json
import argparse
from numpy import nanstd, nanmedian, sqrt
from libExtraction import separateMAC
from libLocalization import deriveLocation, get_distance
def get_known_locs(results, args):
database = json.load(open(args['json'], 'r'))
for mac in database.keys():
if mac not in results:
del database[mac]
for mac in results.keys():
if mac not in database:
del results[mac]
return database
def wrapper(args):
if not os.path.isfile(args['filepath']):
return
results, nameLine, startLine, endLine = separateMAC(args['filepath'])
args['config_entry'] = get_known_locs(results, args)
# method 1: average and then compute single loc
final_result = {}
for mac in results:
dists = []
for line in results[mac]:
try:
# tmp = float(line.split(',')[4])
# ugly hack
# if mac == '34:f6:4b:5e:69:1f':
# tmp = tmp * 0.7927 + 483.3157 # med school fit for 1f
# elif mac == '34:f6:4b:5e:69:0b':
# tmp = tmp * 0.6927 + 400.3157 # med school fit for 0b
tmp = float(line.split(',')[1]) # already fitted result
dists.append(tmp)
except Exception as e:
print(e)
final_result[mac] = (nanmedian(dists), nanstd(dists))
print('est:', final_result)
loc = deriveLocation(args, final_result)
print('loc:', loc)
# # method 2: do localization first for every pair and then average
# all_locs = {}
# if results:
# keys = results.keys()
# idxs = [0] * len(keys)
# locs = []
# while all([idxs[i] < len(results[keys[i]]) for i in range(len(idxs))]):
# lines = [results[keys[i]][idxs[i]] for i in range(len(idxs))]
# times = [float(x.split(',')[7]) for x in lines]
# maxT = max(times)
# if all([abs(t - maxT) < 0.01 for t in times]):
# dists = [
# (float(x.split(',')[1]), sqrt(float(x.split(',')[5])))
# for x in lines
# ]
# loc = deriveLocation(args, dict(zip(keys, dists)))
# print('{0:.4f},{1:.4f}'.format(loc[0], loc[1]))
# locs.append(loc)
# for i in range(len(idxs)):
# idxs[i] += 1
# else:
# for i in range(len(idxs)):
# if abs(times[i] - maxT) > 0.01:
# idxs[i] += 1
# x, y = zip(*locs)
# loc = (nanmedian(x), nanmedian(y))
# locstd = (nanstd(x), nanstd(y))
# print(loc)
# print(locstd)
match = re.search(r"static_([0-9.]+)_([0-9.]+)_", args['filepath'])
if match:
trueX = float(match.group(1)) * 100
trueY = float(match.group(2)) * 100
true_result = {}
for mac in args['config_entry']:
mac_loc = args['config_entry'][mac]['location'].split(',')
mac_loc = (float(mac_loc[0]), float(mac_loc[1]))
true_result[mac] = (get_distance(mac_loc, (trueX, trueY)), 0)
print('true:', true_result)
err = get_distance(loc, (trueX, trueY))
print('err:', err)
def main():
p = argparse.ArgumentParser(description='separate data from MAC addr')
p.add_argument(
'filepath',
help="file path"
)
p.add_argument(
'--outfp', '-f',
default=None,
help="if set, will write raw fetched data to file"
)
p.add_argument(
'--json', '-j',
default='config_entry.default',
help="load a config json file"
)
p.add_argument(
'--verbose', '-v',
default=False,
action="store_true",
help="if set, show detailed messages"
)
try:
args = vars(p.parse_args())
except Exception as e:
print(str(e))
sys.exit()
args['time_of_exec'] = int(time.time())
# TODO: manually add bounds
args['loc_bounds'] = {'y_min': 0}
wrapper(args)
if __name__ == '__main__':
main()