568 lines
15 KiB
C
568 lines
15 KiB
C
/*
|
|
* This file is part of the Owl Positioning System (OwlPS) project.
|
|
* It is subject to the copyright notice and license terms in the
|
|
* COPYRIGHT.t2t file found in the top-level directory of this
|
|
* distribution and at
|
|
* http://code.lm7.fr/p/owlps/source/tree/master/COPYRIGHT.t2t
|
|
* No part of the OwlPS Project, including this file, may be copied,
|
|
* modified, propagated, or distributed except according to the terms
|
|
* contained in the COPYRIGHT.t2t file; the COPYRIGHT.t2t file must be
|
|
* distributed along with this file, either separately or by replacing
|
|
* this notice by the COPYRIGHT.t2t file's contents.
|
|
*
|
|
***********************************************************************
|
|
*
|
|
* This is the main source file of libowlps-resultreader.
|
|
*/
|
|
|
|
|
|
#define _GNU_SOURCE // for strndup()
|
|
|
|
|
|
#include "owlps-resultreader.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
#define CSV_DELIMITER ";"
|
|
|
|
|
|
/**
|
|
* Receives a result from the socket of file descriptor `sockfd`, fills
|
|
* an `owl_result` structure with the received information, and returns a
|
|
* pointer to it.
|
|
*
|
|
* Note that the new owl_result is allocated with `malloc()` and must be
|
|
* deleted using `owl_free_result()`.
|
|
*
|
|
* In case of error, a message is printed, except if `owl_run` (from
|
|
* owlps.h) is `false`, and `NULL` is returned.
|
|
*
|
|
* @returns A pointer to a new `owl_result`.
|
|
* @returns `NULL` in case of error.
|
|
*/
|
|
owl_result* owl_receive_position(const int sockfd)
|
|
{
|
|
ssize_t nread ; // recvfrom return value
|
|
char csv[OWL_CSV_RESULT_STRLEN] ; // Read string
|
|
|
|
nread = recvfrom(sockfd, &csv, OWL_CSV_RESULT_STRLEN,
|
|
0, NULL, NULL) ;
|
|
|
|
if (nread <= 0)
|
|
{
|
|
if (owl_run)
|
|
perror("No request received from listener") ;
|
|
return NULL ;
|
|
}
|
|
|
|
return owl_fill_result(csv) ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Splits the `csv` string received from OwlPS Positioner and stores
|
|
* the fields in a new `owl_result`, and returns a pointer to it (or
|
|
* `NULL` in case of error). Because this function uses strsep(), the
|
|
* `csv` string is modified; it should be copied in the calling function
|
|
* if preservation of the original value is desired.
|
|
*
|
|
* Note that the new `owl_result` is allocated with `malloc()` and must
|
|
* be deleted using `owl_free_result()`.
|
|
*
|
|
* Handled CSV format:
|
|
*
|
|
* Mobile_MAC;Request_type;Request_timestamp;Algorithm;X;Y;Z;Error;Area
|
|
*
|
|
* The Request_timestamp format is:
|
|
*
|
|
* seconds.nanoseconds
|
|
*
|
|
* The *Area* field can be empty.
|
|
*
|
|
* @returns A pointer to a new `owl_result`.
|
|
* @returns `NULL` in case of error.
|
|
*/
|
|
owl_result* owl_fill_result(char *csv)
|
|
{
|
|
char *csv_field = NULL ;
|
|
long longfield ; // Return value of owl_read_long_field()
|
|
owl_result *result = NULL ;
|
|
|
|
result = malloc(sizeof(*result)) ;
|
|
if (! result)
|
|
{
|
|
perror("Cannot allocate memory") ;
|
|
return NULL ;
|
|
}
|
|
memset(result, 0, sizeof(*result)) ;
|
|
|
|
/* Mobile MAC address */
|
|
csv_field = strsep(&csv, CSV_DELIMITER) ;
|
|
if (! csv_field)
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the mobile's MAC address from the CSV"
|
|
" string (empty string?)!\n") ;
|
|
goto error ;
|
|
}
|
|
if (strnlen(csv_field, OWL_ETHER_ADDR_STRLEN)
|
|
!= OWL_ETHER_ADDR_STRLEN - 1)
|
|
{
|
|
csv_field[OWL_ETHER_ADDR_STRLEN-1] = '\0' ;
|
|
fprintf(stderr,
|
|
"The string \"%s\" is not a valid MAC address!\n",
|
|
csv_field) ;
|
|
goto error ;
|
|
}
|
|
strncpy(result->mobile_mac_addr, csv_field, OWL_ETHER_ADDR_STRLEN) ;
|
|
|
|
/* Request type */
|
|
if (owl_read_long_field(&csv, CSV_DELIMITER, &longfield))
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the request type from the CSV string!\n") ;
|
|
goto error ;
|
|
}
|
|
if (! OWL_IS_REQUEST_TYPE(longfield))
|
|
{
|
|
fprintf(stderr,
|
|
"The request type read (%ld) is invalid!\n", longfield) ;
|
|
goto error ;
|
|
}
|
|
result->request_type = longfield ;
|
|
|
|
/* Timestamp */
|
|
// Seconds
|
|
if (owl_read_long_field(&csv, ".", &longfield))
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the timestamp (seconds) from the CSV"
|
|
" string!\n") ;
|
|
goto error ;
|
|
}
|
|
result->mobile_timestamp.tv_sec = longfield ;
|
|
// Nanoseconds
|
|
if (owl_read_long_field(&csv, CSV_DELIMITER, &longfield))
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the timestamp (nanoseconds) from the CSV"
|
|
" string!\n") ;
|
|
goto error ;
|
|
}
|
|
result->mobile_timestamp.tv_nsec = longfield ;
|
|
|
|
/* Algorithm results */
|
|
do
|
|
{
|
|
owl_algorithm_result *current_algo =
|
|
owl_fill_algorithm_result(&csv) ;
|
|
if (current_algo == NULL)
|
|
{
|
|
fprintf(stderr, "Error reading the algorithm #%d!\n",
|
|
result->nb_results + 1) ;
|
|
break ;
|
|
}
|
|
// Insert the current algorithm at the begining of the list
|
|
current_algo->next = result->results ;
|
|
result->results = current_algo ;
|
|
++result->nb_results ;
|
|
}
|
|
while (csv) ;
|
|
|
|
return result ; // Success
|
|
|
|
error:
|
|
owl_free_result(result) ;
|
|
return NULL ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Splits the `csv` string, stores the fields in a new
|
|
* `owl_algorithm_result`, and returns a pointer to it (or `NULL`
|
|
* in case of error). Because this function uses strsep(), the
|
|
* `csv` string is modified; it should be copied in the calling function
|
|
* if preservation of the original value is desired.
|
|
*
|
|
* Note that the new owl_algorithm_result is allocated with `malloc()`
|
|
* and must be deleted using `owl_free_algorithm_result()`.
|
|
*
|
|
* Handled CSV format:
|
|
*
|
|
* Algorithm;X;Y;Z;Error;Area
|
|
*
|
|
* The *Area* field can be empty.
|
|
*
|
|
* @returns A pointer to a new `owl_algorithm_result`.
|
|
* @returns `NULL` in case of error.
|
|
*/
|
|
owl_algorithm_result* owl_fill_algorithm_result(char **csv)
|
|
{
|
|
owl_algorithm_result *algo ;
|
|
char *csv_field = NULL ;
|
|
|
|
algo = malloc(sizeof(owl_algorithm_result)) ;
|
|
if (! algo)
|
|
{
|
|
perror("Cannot allocate memory") ;
|
|
return NULL ;
|
|
}
|
|
memset(algo, 0, sizeof(*algo)) ;
|
|
|
|
// Algorithm name
|
|
csv_field = strsep(csv, CSV_DELIMITER) ;
|
|
if (! csv_field)
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the algorithm name from the CSV string!\n") ;
|
|
goto error ;
|
|
}
|
|
algo->algorithm =
|
|
strndup(csv_field, OWL_ALGORITHM_STRLEN) ;
|
|
|
|
// X coordinate
|
|
if (owl_read_float_field(csv, &algo->x))
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the X coordinate from the CSV string!\n") ;
|
|
goto error ;
|
|
}
|
|
|
|
// Y coordinate
|
|
if (owl_read_float_field(csv, &algo->y))
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the Y coordinate from the CSV string!\n") ;
|
|
goto error ;
|
|
}
|
|
|
|
// Z coordinate
|
|
if (owl_read_float_field(csv, &algo->z))
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the Z coordinate from the CSV string!\n") ;
|
|
goto error ;
|
|
}
|
|
|
|
// Distance error
|
|
if (owl_read_float_field(csv, &algo->error))
|
|
{
|
|
fprintf(stderr,
|
|
"Error reading the distance error from the CSV string!\n") ;
|
|
goto error ;
|
|
}
|
|
|
|
// Area name (optional)
|
|
csv_field = strsep(csv, CSV_DELIMITER) ;
|
|
if (csv_field)
|
|
algo->area = strndup(csv_field, OWL_AREA_STRLEN) ;
|
|
|
|
return algo ; // Success
|
|
|
|
error:
|
|
owl_free_algorithm_result(algo) ;
|
|
return NULL ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Reads the first field from the `csv` string as a long integer and
|
|
* stores it in `ret`. The fields of `csv` are separated by the symbols
|
|
* in the `delim` string. `csv` will be modified to point to the next
|
|
* field.
|
|
* Upon error, the value pointed by `ret` may or may not be modified.
|
|
*
|
|
* @param[in,out] csv The CSV string.
|
|
* @param[in] delim The characters delimiting the fields in the string.
|
|
* @param[out] ret The integer value read from the field.
|
|
*
|
|
* @returns 0 in case of success.
|
|
* @returns 1 if the field could not be read.
|
|
* @returns 2 if the field could be read but did not contain a valid
|
|
* value.
|
|
*/
|
|
int owl_read_long_field(char **const csv, const char *const delim,
|
|
long *const ret)
|
|
{
|
|
char *endptr = NULL, *csv_field = NULL ;
|
|
|
|
csv_field = strsep(csv, delim) ;
|
|
if (! csv_field)
|
|
return 1 ;
|
|
|
|
*ret = strtol(csv_field, &endptr, 10) ;
|
|
if (endptr == csv_field)
|
|
return 2 ;
|
|
|
|
return 0 ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Reads the first field from the `csv` string as a float and stores it
|
|
* in `ret`. `csv` will be modified to point to the next field.
|
|
* Upon error, the value pointed by `ret` may or may not be modified.
|
|
*
|
|
* @param[in,out] csv The CSV string.
|
|
* @param[out] ret The float value read from the field.
|
|
*
|
|
* @returns 0 in case of success.
|
|
* @returns 1 if the field could not be read.
|
|
* @returns 2 if the field could be read but did not contain a valid
|
|
* value.
|
|
*/
|
|
int owl_read_float_field(char **const csv, float *const ret)
|
|
{
|
|
char *endptr = NULL, *csv_field = NULL ;
|
|
|
|
csv_field = strsep(csv, CSV_DELIMITER) ;
|
|
if (! csv_field)
|
|
return 1 ;
|
|
|
|
*ret = strtof(csv_field, &endptr) ;
|
|
if (endptr == csv_field)
|
|
return 2 ;
|
|
|
|
return 0 ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Converts an `owl_result` back to a CSV string.
|
|
* `dst` must be an allocated string of at least `OWL_CSV_RESULT_STRLEN`
|
|
* characters.
|
|
*
|
|
* CSV format:
|
|
*
|
|
* Mobile_MAC;Request_type;Request_timestamp;Nb_algo;Algo_1;…;Algo_n
|
|
*
|
|
* *Nb_algo* is the number of algorithms in the result.
|
|
* The format of *Algo_i* is documented in the documentation of the
|
|
* owl_algorithm_result_to_csv() function.
|
|
*/
|
|
void owl_result_to_csv(char dst[OWL_CSV_RESULT_STRLEN],
|
|
const owl_result *const src)
|
|
{
|
|
size_t dst_len ;
|
|
char timestamp_str[OWL_TIMESTAMP_STRLEN] ;
|
|
|
|
assert(src) ;
|
|
|
|
owl_timestamp_to_string(&src->mobile_timestamp, timestamp_str) ;
|
|
snprintf(dst, OWL_CSV_RESULT_REQUEST_STRLEN,
|
|
"%s;%"PRIu8";%s;%u",
|
|
src->mobile_mac_addr,
|
|
src->request_type,
|
|
timestamp_str,
|
|
src->nb_results) ;
|
|
dst_len = strlen(dst) ;
|
|
|
|
owl_algorithm_result *algo = src->results ;
|
|
while (algo)
|
|
{
|
|
char algo_str[OWL_CSV_ALGORITHM_RESULT_STRLEN] ;
|
|
owl_algorithm_result_to_csv(algo_str, algo) ;
|
|
dst[dst_len++] = ';' ;
|
|
strncpy(dst + dst_len, algo_str, OWL_CSV_ALGORITHM_RESULT_STRLEN) ;
|
|
dst_len += strlen(algo_str) ;
|
|
algo = algo->next ;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Converts an `owl_algorithm_result` back to a CSV string.
|
|
* `dst` must be an allocated string of at least
|
|
* `OWL_CSV_ALGORITHM_RESULT_STRLEN` characters.
|
|
*
|
|
* CSV format:
|
|
*
|
|
* Algorithm_name;X;Y;Z;Error;Area_name
|
|
*
|
|
* *Error* is the distance from the true coordinates of the mobile, if
|
|
* known; if unknown, *Error* is set to -1.
|
|
* *Area_name* is the name of the area or room in which the mobile is
|
|
* (may be empty).
|
|
*/
|
|
void
|
|
owl_algorithm_result_to_csv(char dst[OWL_CSV_ALGORITHM_RESULT_STRLEN],
|
|
const owl_algorithm_result *const src)
|
|
{
|
|
assert(src) ;
|
|
|
|
snprintf(dst, OWL_CSV_ALGORITHM_RESULT_STRLEN,
|
|
"%s;%f;%f;%f;%f;%s",
|
|
src->algorithm,
|
|
src->x,
|
|
src->y,
|
|
src->z,
|
|
src->error,
|
|
src->area ? src->area : "") ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Converts an `owl_result` back to a CSV string, in a simplified format:
|
|
* only the *first* algorithm in the result's algorithm list will be
|
|
* included in the string.
|
|
* `dst` must be an allocated string of at least
|
|
* `OWL_CSV_RESULT_SIMPLE_STRLEN` characters.
|
|
*
|
|
* CSV format:
|
|
*
|
|
* Mobile_MAC;First_algorithm
|
|
*
|
|
* *First_algorithm* is the first algorithm in `src->results`. Its format
|
|
* is documented in the documentation of the
|
|
* owl_algorithm_result_to_csv_simple() function.
|
|
*/
|
|
void owl_result_to_csv_simple(char dst[OWL_CSV_RESULT_SIMPLE_STRLEN],
|
|
const owl_result *const src)
|
|
{
|
|
size_t dst_len ;
|
|
char algo_str[OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN] ;
|
|
|
|
assert(src) ;
|
|
|
|
strncpy(dst, src->mobile_mac_addr, OWL_ETHER_ADDR_STRLEN) ;
|
|
dst[OWL_ETHER_ADDR_STRLEN - 1] = ';' ;
|
|
dst_len = OWL_ETHER_ADDR_STRLEN ;
|
|
|
|
if (! src->results)
|
|
return ;
|
|
|
|
owl_algorithm_result_to_csv_simple(algo_str, src->results) ;
|
|
strncpy(dst + dst_len, algo_str,
|
|
OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN) ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Converts an `owl_algorithm_result` back to a CSV string, in a
|
|
* simplified format.
|
|
* `dst` must be an allocated string of at least
|
|
* `OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN` characters.
|
|
*
|
|
* CSV format:
|
|
*
|
|
* X;Y;Z;Area_name
|
|
*
|
|
* *Area_name* is the name of the area or room in which the mobile is
|
|
* (may be empty).
|
|
*/
|
|
void owl_algorithm_result_to_csv_simple
|
|
(char dst[OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN],
|
|
const owl_algorithm_result *const src)
|
|
{
|
|
assert(src) ;
|
|
|
|
snprintf(dst, OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN,
|
|
"%f;%f;%f;%s",
|
|
src->x,
|
|
src->y,
|
|
src->z,
|
|
src->area ? src->area : "") ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Prints an `owl_result` to the given stream, using `fprintf()`.
|
|
* `src` must not be `NULL`.
|
|
*/
|
|
void owl_fprint_result(FILE *const stream, const owl_result *const src)
|
|
{
|
|
char timestamp_str[OWL_TIMESTAMP_STRLEN] ;
|
|
|
|
assert(src) ;
|
|
|
|
owl_timestamp_to_string(&src->mobile_timestamp, timestamp_str) ;
|
|
fprintf(stream,
|
|
"Mobile MAC: %s\n"
|
|
"Request type: %"PRIu8"\n"
|
|
"Mobile timestamp: %s\n"
|
|
"%u results:\n"
|
|
,
|
|
src->mobile_mac_addr,
|
|
src->request_type,
|
|
timestamp_str,
|
|
src->nb_results) ;
|
|
|
|
owl_algorithm_result *algo = src->results ;
|
|
while (algo)
|
|
{
|
|
owl_fprint_algorithm_result(stream, algo) ;
|
|
algo = algo->next ;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Prints an `owl_algorithm_result` to the given stream, using
|
|
* `fprintf()`.
|
|
* `src` must not be `NULL`.
|
|
*/
|
|
void owl_fprint_algorithm_result(FILE *const stream,
|
|
const owl_algorithm_result *const src)
|
|
{
|
|
assert(src) ;
|
|
|
|
fprintf(stream,
|
|
"* Algorithm: %s\n"
|
|
" X: %f\n"
|
|
" Y: %f\n"
|
|
" Z: %f\n"
|
|
" Error: %f\n"
|
|
" Area: %s\n"
|
|
,
|
|
src->algorithm,
|
|
src->x,
|
|
src->y,
|
|
src->z,
|
|
src->error,
|
|
src->area ? src->area : ""
|
|
) ;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Frees the memory allocated for an `owl_result`. The `results` field
|
|
* of `result` *must* be defined, either to `NULL` or to a valid memory
|
|
* block allocated with `malloc()`.
|
|
* Note that `result` will not be set to `NULL`.
|
|
*/
|
|
void owl_free_result(owl_result *const result)
|
|
{
|
|
if (! result)
|
|
return ;
|
|
while (result->results)
|
|
{
|
|
owl_algorithm_result *algo = result->results ;
|
|
result->results = algo->next ;
|
|
owl_free_algorithm_result(algo) ;
|
|
}
|
|
free(result) ;
|
|
}
|
|
|
|
|
|
/**
|
|
* Frees the memory allocated for a single `owl_algorithm_result` (*not*
|
|
* recursively). The `algorithm` and `area` fields of `algo` *must* be
|
|
* defined, either to `NULL` or to a valid memory block allocated with
|
|
* `malloc()`.
|
|
* Note that `algo` will not be set to `NULL`.
|
|
*/
|
|
void owl_free_algorithm_result(owl_algorithm_result *const algo)
|
|
{
|
|
if (! algo)
|
|
return ;
|
|
free(algo->algorithm) ;
|
|
free(algo->area) ;
|
|
free(algo) ;
|
|
}
|