/*
Handle Qstarz BL-1000 .BIN files.
Saved in the SESSION/GPSLog folder. There will 3 files sharing the same base name: .BIN, .POI, and .DAT.
Only the .BIN file is of interest to us.
Copyright (C) 2020 Pierre Bernard, pierre.bernard@houdah.com
Copyright (C) 2001-2020 Robert Lipe, robertlipe+source@gpsbabel.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "qstarz_bl_1000.h"
#include <cmath> // for round
#include <QChar> // for QChar
#include <QDataStream> // for QDataStream, QDataStream::SinglePrecision, QDataStream::DoublePrecision, QDataStream::LittleEndian, QDataStream::Ok
#include <QDebug> // for QDebug
#include <QFile> // for QFile
#include <QIODevice> // for QIODevice, QIODevice::ReadOnly
#include "defs.h" // for Waypoint, ddmm2degrees, route_head, track_add_head, track_add_wpt, waypt_add, waypt_count, wp_flags, fix_unknown, fix_2d, fix_3d, fix_dgps, fix_none, fix_pps, fix_type, global_options, global_opts
#include "src/core/logging.h" // for Fatal
#define MYNAME "Qstarz BL-1000"
enum BL1000_POINT_TYPE {
BL1000_POINT_TYPE_UNKNOWN = '-',
BL1000_POINT_TYPE_WAY_POINT = 'B', // Button push
BL1000_POINT_TYPE_TRACK_POINT_BY_TIME = 'T',
BL1000_POINT_TYPE_TRACK_POINT_BY_DISTANCE = 'D',
BL1000_POINT_TYPE_TRACK_POINT_BY_SPEED = 'S',
};
/***************************************************************************
* local helper functions *
***************************************************************************/
inline bool qstarz_bl_1000_is_waypoint_type(BL1000_POINT_TYPE type)
{
return (type == BL1000_POINT_TYPE_WAY_POINT);
}
inline bool qstarz_bl_1000_is_trackpoint_type(BL1000_POINT_TYPE type)
{
return (
(type == BL1000_POINT_TYPE_TRACK_POINT_BY_TIME) ||
(type == BL1000_POINT_TYPE_TRACK_POINT_BY_DISTANCE) ||
(type == BL1000_POINT_TYPE_TRACK_POINT_BY_SPEED)
);
}
void
QstarzBL1000Format::qstarz_bl_1000_read(QDataStream& stream)
{
auto* track_route = new route_head;
track_add_head(track_route);
while (!stream.atEnd()) {
qstarz_bl_1000_read_record(stream, track_route);
}
}
void
QstarzBL1000Format::qstarz_bl_1000_read_record(QDataStream& stream, route_head* track_route)
{
// Value fields in 64-byte log entry
quint8 fixStatus; // 0
qint8 rcr; // 1
quint16 milliseconds; // 2-3
double dLatitude; // 4-11
double dLongitude; // 12-19
quint32 time; // 20-23
float speed; // 24-27
float altitude; // 28-31
float heading; // 32-35
qint16 gx; // 36-37
qint16 gy; // 38-39
qint16 gz; // 40-41
quint16 maxSNR; // 42-43
float hdop; // 44-47
float vdop; // 48-51
qint8 satelliteCountView; // 52
qint8 satelliteCountUsed; // 53
quint8 fixQuality; // 54
quint8 batteryPercent; // 55
quint32 unused1; // 56-59
quint32 unused2; // 60-64
stream >> fixStatus;
stream >> rcr;
stream >> milliseconds;
stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
stream >> dLatitude;
stream >> dLongitude;
stream >> time;
stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
stream >> speed;
stream >> altitude;
stream >> heading;
stream >> gx;
stream >> gy;
stream >> gz;
stream >> maxSNR;
stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
stream >> hdop;
stream >> vdop;
stream >> satelliteCountView;
stream >> satelliteCountUsed;
stream >> fixQuality;
stream >> batteryPercent;
stream >> unused1;
stream >> unused2;
if (stream.status() != QDataStream::Ok) {
fatal(FatalMsg() << MYNAME << ": File format error on " << read_fname << ". Perhaps this isn't a Qstarz BL-1000 file");
}
BL1000_POINT_TYPE type;
switch (rcr) {
case 'B':
type = BL1000_POINT_TYPE_WAY_POINT;
break;
case 'T':
type = BL1000_POINT_TYPE_TRACK_POINT_BY_TIME;
break;
case 'D':
type = BL1000_POINT_TYPE_TRACK_POINT_BY_DISTANCE;
break;
case 'S':
type = BL1000_POINT_TYPE_TRACK_POINT_BY_SPEED;
break;
case 'W': // Power On
case 'X': // Power Off
case 'Y': // USB plug in
case 'Z': // USB removed
case 'C': // Cali G-sensor
case 'A': // Enter Smart Mode(Power Saving)
case 'R': // Reserved
case 'N': // Reserved
case 'M': // Reserved
case 'G': // Reserved
case 'H': // Reserved
case 'I': // Reserved
case 'J': // Reserved
type = BL1000_POINT_TYPE_UNKNOWN;
break;
default:
type = BL1000_POINT_TYPE_UNKNOWN;
fatal(FatalMsg() << MYNAME << ": File format error on " << read_fname << ". Unexpected value for RCR (record reason): " << rcr);
break;
}
fix_type fix;
switch (fixQuality) {
case 1:
switch (fixStatus) {
case 1:
fix = fix_none;
break;
case 2:
fix = fix_2d;
break;
case 3:
fix = fix_3d;
break;
default:
fix = fix_unknown;
break;
}
break;
case 2:
fix = fix_dgps;
break;
case 3:
fix = fix_pps;
break;
default:
fix = fix_unknown;
if (type != BL1000_POINT_TYPE_UNKNOWN) {
fatal(FatalMsg() << MYNAME << ": File format error on " << read_fname << ". Unexpected value for fix quality: " << fixQuality);
}
break;
}
auto* waypoint = new Waypoint();
waypoint->latitude = ddmm2degrees(dLatitude);
waypoint->longitude = ddmm2degrees(dLongitude);
// qDebug(waypoint)
if ((waypoint->latitude < -90) || (waypoint->latitude > 90)) {
fatal(FatalMsg() << MYNAME << ": File format error on " << read_fname << ". Unexpected value for latitude: " << waypoint->latitude);
}
if ((waypoint->longitude < -180) || (waypoint->longitude > 180)) {
fatal(FatalMsg() << MYNAME << ": File format error on " << read_fname << ". Unexpected value for longitude: " << waypoint->longitude);
}
waypoint->altitude = altitude;
waypoint->hdop = round(hdop * 1000) / 1000;
waypoint->vdop = round(vdop * 1000) / 1000;
waypoint->fix = fix;
waypoint->sat = satelliteCountUsed;
waypoint->speed = KPH_TO_MPS(speed);
waypoint->wpt_flags.speed = 1;
waypoint->course = heading;
waypoint->wpt_flags.course = 1;
waypoint->SetCreationTime(time, milliseconds);
auto* fsdata = new qstarz_bl_1000_fsdata;
waypoint->fs.FsChainAdd(fsdata);
fsdata->accelerationX = gx / 256.0;
fsdata->accelerationY = gy / 256.0;
fsdata->accelerationZ = gz / 256.0;
fsdata->satTotal = satelliteCountView;
fsdata->maxSNR = maxSNR;
fsdata->batteryPercent = batteryPercent;
if (qstarz_bl_1000_is_waypoint_type(type)) {
if (global_opts.synthesize_shortnames) {
waypoint->shortname = QString("WP%2").arg(waypt_count() + 1, 3, 10, QChar('0'));
waypoint->wpt_flags.shortname_is_synthetic = 1;
}
waypt_add(waypoint);
} else if (qstarz_bl_1000_is_trackpoint_type(type)) {
track_add_wpt(track_route, waypoint, "TP", 3);
} else {
delete waypoint;
}
}
/***************************************************************************
* entry points called by gpsbabel main process *
***************************************************************************/
void
QstarzBL1000Format::rd_init(const QString& fname)
{
read_fname = fname;
}
void
QstarzBL1000Format::rd_deinit()
{
read_fname.clear();
}
void
QstarzBL1000Format::read()
{
QFile file(read_fname);
if (!file.open(QIODevice::ReadOnly)) {
fatal(FatalMsg() << MYNAME << ": Error opening file " << read_fname);
}
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
qstarz_bl_1000_read(stream);
file.close();
}