mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-24 03:29:57 -07:00
Add GPS support for SubGHz
This commit is contained in:
640
applications/main/subghz/helpers/minmea.c
Normal file
640
applications/main/subghz/helpers/minmea.c
Normal file
@@ -0,0 +1,640 @@
|
||||
/*
|
||||
* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
|
||||
* This program is free software. It comes without any warranty, to the extent
|
||||
* permitted by applicable law. You can redistribute it and/or modify it under
|
||||
* the terms of the Do What The Fuck You Want To Public License, Version 2, as
|
||||
* published by Sam Hocevar. See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "minmea.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define boolstr(s) ((s) ? "true" : "false")
|
||||
|
||||
static int hex2int(char c) {
|
||||
if(c >= '0' && c <= '9') return c - '0';
|
||||
if(c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t minmea_checksum(const char* sentence) {
|
||||
// Support senteces with or without the starting dollar sign.
|
||||
if(*sentence == '$') sentence++;
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
|
||||
// The optional checksum is an XOR of all bytes between "$" and "*".
|
||||
while(*sentence && *sentence != '*') checksum ^= *sentence++;
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
bool minmea_check(const char* sentence, bool strict) {
|
||||
uint8_t checksum = 0x00;
|
||||
|
||||
// A valid sentence starts with "$".
|
||||
if(*sentence++ != '$') return false;
|
||||
|
||||
// The optional checksum is an XOR of all bytes between "$" and "*".
|
||||
while(*sentence && *sentence != '*' && isprint((unsigned char)*sentence))
|
||||
checksum ^= *sentence++;
|
||||
|
||||
// If checksum is present...
|
||||
if(*sentence == '*') {
|
||||
// Extract checksum.
|
||||
sentence++;
|
||||
int upper = hex2int(*sentence++);
|
||||
if(upper == -1) return false;
|
||||
int lower = hex2int(*sentence++);
|
||||
if(lower == -1) return false;
|
||||
int expected = upper << 4 | lower;
|
||||
|
||||
// Check for checksum mismatch.
|
||||
if(checksum != expected) return false;
|
||||
} else if(strict) {
|
||||
// Discard non-checksummed frames in strict mode.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The only stuff allowed at this point is a newline.
|
||||
while(*sentence == '\r' || *sentence == '\n') {
|
||||
sentence++;
|
||||
}
|
||||
|
||||
if(*sentence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_scan(const char* sentence, const char* format, ...) {
|
||||
bool result = false;
|
||||
bool optional = false;
|
||||
|
||||
if(sentence == NULL) return false;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
const char* field = sentence;
|
||||
#define next_field() \
|
||||
do { \
|
||||
/* Progress to the next field. */ \
|
||||
while(minmea_isfield(*sentence)) sentence++; \
|
||||
/* Make sure there is a field there. */ \
|
||||
if(*sentence == ',') { \
|
||||
sentence++; \
|
||||
field = sentence; \
|
||||
} else { \
|
||||
field = NULL; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
while(*format) {
|
||||
char type = *format++;
|
||||
|
||||
if(type == ';') {
|
||||
// All further fields are optional.
|
||||
optional = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!field && !optional) {
|
||||
// Field requested but we ran out if input. Bail out.
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case 'c': { // Single character field (char).
|
||||
char value = '\0';
|
||||
|
||||
if(field && minmea_isfield(*field)) value = *field;
|
||||
|
||||
*va_arg(ap, char*) = value;
|
||||
} break;
|
||||
|
||||
case 'd': { // Single character direction field (int).
|
||||
int value = 0;
|
||||
|
||||
if(field && minmea_isfield(*field)) {
|
||||
switch(*field) {
|
||||
case 'N':
|
||||
case 'E':
|
||||
value = 1;
|
||||
break;
|
||||
case 'S':
|
||||
case 'W':
|
||||
value = -1;
|
||||
break;
|
||||
default:
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
|
||||
*va_arg(ap, int*) = value;
|
||||
} break;
|
||||
|
||||
case 'f': { // Fractional value with scale (struct minmea_float).
|
||||
int sign = 0;
|
||||
int_least32_t value = -1;
|
||||
int_least32_t scale = 0;
|
||||
|
||||
if(field) {
|
||||
while(minmea_isfield(*field)) {
|
||||
if(*field == '+' && !sign && value == -1) {
|
||||
sign = 1;
|
||||
} else if(*field == '-' && !sign && value == -1) {
|
||||
sign = -1;
|
||||
} else if(isdigit((unsigned char)*field)) {
|
||||
int digit = *field - '0';
|
||||
if(value == -1) value = 0;
|
||||
if(value > (INT_LEAST32_MAX - digit) / 10) {
|
||||
/* we ran out of bits, what do we do? */
|
||||
if(scale) {
|
||||
/* truncate extra precision */
|
||||
break;
|
||||
} else {
|
||||
/* integer overflow. bail out. */
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
value = (10 * value) + digit;
|
||||
if(scale) scale *= 10;
|
||||
} else if(*field == '.' && scale == 0) {
|
||||
scale = 1;
|
||||
} else if(*field == ' ') {
|
||||
/* Allow spaces at the start of the field. Not NMEA
|
||||
* conformant, but some modules do this. PP */
|
||||
if(sign != 0 || value != -1 || scale != 0) goto parse_error;
|
||||
} else {
|
||||
goto parse_error;
|
||||
}
|
||||
field++;
|
||||
}
|
||||
}
|
||||
|
||||
if((sign || scale) && value == -1) goto parse_error;
|
||||
|
||||
if(value == -1) {
|
||||
/* No digits were scanned. */
|
||||
value = 0;
|
||||
scale = 0;
|
||||
} else if(scale == 0) {
|
||||
/* No decimal point. */
|
||||
scale = 1;
|
||||
}
|
||||
if(sign) value *= sign;
|
||||
|
||||
*va_arg(ap, struct minmea_float*) = (struct minmea_float){value, scale};
|
||||
} break;
|
||||
|
||||
case 'i': { // Integer value, default 0 (int).
|
||||
int value = 0;
|
||||
|
||||
if(field) {
|
||||
char* endptr;
|
||||
value = strtol(field, &endptr, 10);
|
||||
if(minmea_isfield(*endptr)) goto parse_error;
|
||||
}
|
||||
|
||||
*va_arg(ap, int*) = value;
|
||||
} break;
|
||||
|
||||
case 's': { // String value (char *).
|
||||
char* buf = va_arg(ap, char*);
|
||||
|
||||
if(field) {
|
||||
while(minmea_isfield(*field)) *buf++ = *field++;
|
||||
}
|
||||
|
||||
*buf = '\0';
|
||||
} break;
|
||||
|
||||
case 't': { // NMEA talker+sentence identifier (char *).
|
||||
// This field is always mandatory voss.
|
||||
if(!field) goto parse_error;
|
||||
|
||||
if(field[0] != '$') goto parse_error;
|
||||
for(int f = 0; f < 5; f++)
|
||||
if(!minmea_isfield(field[1 + f])) goto parse_error;
|
||||
|
||||
char* buf = va_arg(ap, char*);
|
||||
memcpy(buf, field + 1, 5);
|
||||
buf[5] = '\0';
|
||||
} break;
|
||||
|
||||
case 'D': { // Date (int, int, int), -1 if empty.
|
||||
struct minmea_date* date = va_arg(ap, struct minmea_date*);
|
||||
|
||||
int d = -1, m = -1, y = -1;
|
||||
|
||||
if(field && minmea_isfield(*field)) {
|
||||
// Always six digits.
|
||||
for(int f = 0; f < 6; f++)
|
||||
if(!isdigit((unsigned char)field[f])) goto parse_error;
|
||||
|
||||
char dArr[] = {field[0], field[1], '\0'};
|
||||
char mArr[] = {field[2], field[3], '\0'};
|
||||
char yArr[] = {field[4], field[5], '\0'};
|
||||
d = strtol(dArr, NULL, 10);
|
||||
m = strtol(mArr, NULL, 10);
|
||||
y = strtol(yArr, NULL, 10);
|
||||
}
|
||||
|
||||
date->day = d;
|
||||
date->month = m;
|
||||
date->year = y;
|
||||
} break;
|
||||
|
||||
case 'T': { // Time (int, int, int, int), -1 if empty.
|
||||
struct minmea_time* time_ = va_arg(ap, struct minmea_time*);
|
||||
|
||||
int h = -1, i = -1, s = -1, u = -1;
|
||||
|
||||
if(field && minmea_isfield(*field)) {
|
||||
// Minimum required: integer time.
|
||||
for(int f = 0; f < 6; f++)
|
||||
if(!isdigit((unsigned char)field[f])) goto parse_error;
|
||||
|
||||
char hArr[] = {field[0], field[1], '\0'};
|
||||
char iArr[] = {field[2], field[3], '\0'};
|
||||
char sArr[] = {field[4], field[5], '\0'};
|
||||
h = strtol(hArr, NULL, 10);
|
||||
i = strtol(iArr, NULL, 10);
|
||||
s = strtol(sArr, NULL, 10);
|
||||
field += 6;
|
||||
|
||||
// Extra: fractional time. Saved as microseconds.
|
||||
if(*field++ == '.') {
|
||||
uint32_t value = 0;
|
||||
uint32_t scale = 1000000LU;
|
||||
while(isdigit((unsigned char)*field) && scale > 1) {
|
||||
value = (value * 10) + (*field++ - '0');
|
||||
scale /= 10;
|
||||
}
|
||||
u = value * scale;
|
||||
} else {
|
||||
u = 0;
|
||||
}
|
||||
}
|
||||
|
||||
time_->hours = h;
|
||||
time_->minutes = i;
|
||||
time_->seconds = s;
|
||||
time_->microseconds = u;
|
||||
} break;
|
||||
|
||||
case '_': { // Ignore the field.
|
||||
} break;
|
||||
|
||||
default: { // Unknown.
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
|
||||
next_field();
|
||||
}
|
||||
|
||||
result = true;
|
||||
|
||||
parse_error:
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool minmea_talker_id(char talker[3], const char* sentence) {
|
||||
char type[6];
|
||||
if(!minmea_scan(sentence, "t", type)) return false;
|
||||
|
||||
talker[0] = type[0];
|
||||
talker[1] = type[1];
|
||||
talker[2] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum minmea_sentence_id minmea_sentence_id(const char* sentence, bool strict) {
|
||||
if(!minmea_check(sentence, strict)) return MINMEA_INVALID;
|
||||
|
||||
char type[6];
|
||||
if(!minmea_scan(sentence, "t", type)) return MINMEA_INVALID;
|
||||
|
||||
if(!strcmp(type + 2, "GBS")) return MINMEA_SENTENCE_GBS;
|
||||
if(!strcmp(type + 2, "GGA")) return MINMEA_SENTENCE_GGA;
|
||||
if(!strcmp(type + 2, "GLL")) return MINMEA_SENTENCE_GLL;
|
||||
if(!strcmp(type + 2, "GSA")) return MINMEA_SENTENCE_GSA;
|
||||
if(!strcmp(type + 2, "GST")) return MINMEA_SENTENCE_GST;
|
||||
if(!strcmp(type + 2, "GSV")) return MINMEA_SENTENCE_GSV;
|
||||
if(!strcmp(type + 2, "RMC")) return MINMEA_SENTENCE_RMC;
|
||||
if(!strcmp(type + 2, "VTG")) return MINMEA_SENTENCE_VTG;
|
||||
if(!strcmp(type + 2, "ZDA")) return MINMEA_SENTENCE_ZDA;
|
||||
|
||||
return MINMEA_UNKNOWN;
|
||||
}
|
||||
|
||||
bool minmea_parse_gbs(struct minmea_sentence_gbs* frame, const char* sentence) {
|
||||
// $GNGBS,170556.00,3.0,2.9,8.3,,,,*5C
|
||||
char type[6];
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTfffifff",
|
||||
type,
|
||||
&frame->time,
|
||||
&frame->err_latitude,
|
||||
&frame->err_longitude,
|
||||
&frame->err_altitude,
|
||||
&frame->svid,
|
||||
&frame->prob,
|
||||
&frame->bias,
|
||||
&frame->stddev))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GBS")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_rmc(struct minmea_sentence_rmc* frame, const char* sentence) {
|
||||
// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
|
||||
char type[6];
|
||||
char validity;
|
||||
int latitude_direction;
|
||||
int longitude_direction;
|
||||
int variation_direction;
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTcfdfdffDfd",
|
||||
type,
|
||||
&frame->time,
|
||||
&validity,
|
||||
&frame->latitude,
|
||||
&latitude_direction,
|
||||
&frame->longitude,
|
||||
&longitude_direction,
|
||||
&frame->speed,
|
||||
&frame->course,
|
||||
&frame->date,
|
||||
&frame->variation,
|
||||
&variation_direction))
|
||||
return false;
|
||||
if(strcmp(type + 2, "RMC")) return false;
|
||||
|
||||
frame->valid = (validity == 'A');
|
||||
frame->latitude.value *= latitude_direction;
|
||||
frame->longitude.value *= longitude_direction;
|
||||
frame->variation.value *= variation_direction;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gga(struct minmea_sentence_gga* frame, const char* sentence) {
|
||||
// $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
|
||||
char type[6];
|
||||
int latitude_direction;
|
||||
int longitude_direction;
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTfdfdiiffcfcf_",
|
||||
type,
|
||||
&frame->time,
|
||||
&frame->latitude,
|
||||
&latitude_direction,
|
||||
&frame->longitude,
|
||||
&longitude_direction,
|
||||
&frame->fix_quality,
|
||||
&frame->satellites_tracked,
|
||||
&frame->hdop,
|
||||
&frame->altitude,
|
||||
&frame->altitude_units,
|
||||
&frame->height,
|
||||
&frame->height_units,
|
||||
&frame->dgps_age))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GGA")) return false;
|
||||
|
||||
frame->latitude.value *= latitude_direction;
|
||||
frame->longitude.value *= longitude_direction;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gsa(struct minmea_sentence_gsa* frame, const char* sentence) {
|
||||
// $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
|
||||
char type[6];
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tciiiiiiiiiiiiifff",
|
||||
type,
|
||||
&frame->mode,
|
||||
&frame->fix_type,
|
||||
&frame->sats[0],
|
||||
&frame->sats[1],
|
||||
&frame->sats[2],
|
||||
&frame->sats[3],
|
||||
&frame->sats[4],
|
||||
&frame->sats[5],
|
||||
&frame->sats[6],
|
||||
&frame->sats[7],
|
||||
&frame->sats[8],
|
||||
&frame->sats[9],
|
||||
&frame->sats[10],
|
||||
&frame->sats[11],
|
||||
&frame->pdop,
|
||||
&frame->hdop,
|
||||
&frame->vdop))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GSA")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gll(struct minmea_sentence_gll* frame, const char* sentence) {
|
||||
// $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$;
|
||||
char type[6];
|
||||
int latitude_direction;
|
||||
int longitude_direction;
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tfdfdTc;c",
|
||||
type,
|
||||
&frame->latitude,
|
||||
&latitude_direction,
|
||||
&frame->longitude,
|
||||
&longitude_direction,
|
||||
&frame->time,
|
||||
&frame->status,
|
||||
&frame->mode))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GLL")) return false;
|
||||
|
||||
frame->latitude.value *= latitude_direction;
|
||||
frame->longitude.value *= longitude_direction;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gst(struct minmea_sentence_gst* frame, const char* sentence) {
|
||||
// $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58
|
||||
char type[6];
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTfffffff",
|
||||
type,
|
||||
&frame->time,
|
||||
&frame->rms_deviation,
|
||||
&frame->semi_major_deviation,
|
||||
&frame->semi_minor_deviation,
|
||||
&frame->semi_major_orientation,
|
||||
&frame->latitude_error_deviation,
|
||||
&frame->longitude_error_deviation,
|
||||
&frame->altitude_error_deviation))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GST")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gsv(struct minmea_sentence_gsv* frame, const char* sentence) {
|
||||
// $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
|
||||
// $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D
|
||||
// $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75
|
||||
// $GPGSV,4,4,13,39,31,170,27*40
|
||||
// $GPGSV,4,4,13*7B
|
||||
char type[6];
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tiii;iiiiiiiiiiiiiiii",
|
||||
type,
|
||||
&frame->total_msgs,
|
||||
&frame->msg_nr,
|
||||
&frame->total_sats,
|
||||
&frame->sats[0].nr,
|
||||
&frame->sats[0].elevation,
|
||||
&frame->sats[0].azimuth,
|
||||
&frame->sats[0].snr,
|
||||
&frame->sats[1].nr,
|
||||
&frame->sats[1].elevation,
|
||||
&frame->sats[1].azimuth,
|
||||
&frame->sats[1].snr,
|
||||
&frame->sats[2].nr,
|
||||
&frame->sats[2].elevation,
|
||||
&frame->sats[2].azimuth,
|
||||
&frame->sats[2].snr,
|
||||
&frame->sats[3].nr,
|
||||
&frame->sats[3].elevation,
|
||||
&frame->sats[3].azimuth,
|
||||
&frame->sats[3].snr)) {
|
||||
return false;
|
||||
}
|
||||
if(strcmp(type + 2, "GSV")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_vtg(struct minmea_sentence_vtg* frame, const char* sentence) {
|
||||
// $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48,S
|
||||
// $GPVTG,156.1,T,140.9,M,0.0,N,0.0,K*41,I
|
||||
// $GPVTG,096.5,T,083.5,M,0.0,N,0.0,K,D*22,L
|
||||
// $GPVTG,188.36,T,,M,0.820,N,1.519,K,A*3F,333033
|
||||
char type[6];
|
||||
char c_true, c_magnetic, c_knots, c_kph, c_faa_mode;
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"t;fcfcfcfcc",
|
||||
type,
|
||||
&frame->true_track_degrees,
|
||||
&c_true,
|
||||
&frame->magnetic_track_degrees,
|
||||
&c_magnetic,
|
||||
&frame->speed_knots,
|
||||
&c_knots,
|
||||
&frame->speed_kph,
|
||||
&c_kph,
|
||||
&c_faa_mode))
|
||||
return false;
|
||||
if(strcmp(type + 2, "VTG")) return false;
|
||||
// values are only valid with the accompanying characters
|
||||
if(c_true != 'T') frame->true_track_degrees.scale = 0;
|
||||
if(c_magnetic != 'M') frame->magnetic_track_degrees.scale = 0;
|
||||
if(c_knots != 'N') frame->speed_knots.scale = 0;
|
||||
if(c_kph != 'K') frame->speed_kph.scale = 0;
|
||||
frame->faa_mode = (enum minmea_faa_mode)c_faa_mode;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_zda(struct minmea_sentence_zda* frame, const char* sentence) {
|
||||
// $GPZDA,201530.00,04,07,2002,00,00*60
|
||||
char type[6];
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTiiiii",
|
||||
type,
|
||||
&frame->time,
|
||||
&frame->date.day,
|
||||
&frame->date.month,
|
||||
&frame->date.year,
|
||||
&frame->hour_offset,
|
||||
&frame->minute_offset))
|
||||
return false;
|
||||
if(strcmp(type + 2, "ZDA")) return false;
|
||||
|
||||
// check offsets
|
||||
if(abs(frame->hour_offset) > 13 || frame->minute_offset > 59 || frame->minute_offset < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int minmea_getdatetime(
|
||||
struct tm* tm,
|
||||
const struct minmea_date* date,
|
||||
const struct minmea_time* time_) {
|
||||
if(date->year == -1 || time_->hours == -1) return -1;
|
||||
|
||||
memset(tm, 0, sizeof(*tm));
|
||||
if(date->year < 80) {
|
||||
tm->tm_year = 2000 + date->year - 1900; // 2000-2079
|
||||
} else if(date->year >= 1900) {
|
||||
tm->tm_year = date->year - 1900; // 4 digit year, use directly
|
||||
} else {
|
||||
tm->tm_year = date->year; // 1980-1999
|
||||
}
|
||||
tm->tm_mon = date->month - 1;
|
||||
tm->tm_mday = date->day;
|
||||
tm->tm_hour = time_->hours;
|
||||
tm->tm_min = time_->minutes;
|
||||
tm->tm_sec = time_->seconds;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int minmea_gettime(
|
||||
struct timespec* ts,
|
||||
const struct minmea_date* date,
|
||||
const struct minmea_time* time_) {
|
||||
struct tm tm;
|
||||
if(minmea_getdatetime(&tm, date, time_)) return -1;
|
||||
|
||||
time_t timestamp = mktime(&tm); /* See README.md if your system lacks timegm(). */
|
||||
if(timestamp != (time_t)-1) {
|
||||
ts->tv_sec = timestamp;
|
||||
ts->tv_nsec = time_->microseconds * 1000;
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et: */
|
||||
295
applications/main/subghz/helpers/minmea.h
Normal file
295
applications/main/subghz/helpers/minmea.h
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
|
||||
* This program is free software. It comes without any warranty, to the extent
|
||||
* permitted by applicable law. You can redistribute it and/or modify it under
|
||||
* the terms of the Do What The Fuck You Want To Public License, Version 2, as
|
||||
* published by Sam Hocevar. See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#ifndef MINMEA_H
|
||||
#define MINMEA_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#ifdef MINMEA_INCLUDE_COMPAT
|
||||
#include <minmea_compat.h>
|
||||
#endif
|
||||
|
||||
#ifndef MINMEA_MAX_SENTENCE_LENGTH
|
||||
#define MINMEA_MAX_SENTENCE_LENGTH 80
|
||||
#endif
|
||||
|
||||
enum minmea_sentence_id {
|
||||
MINMEA_INVALID = -1,
|
||||
MINMEA_UNKNOWN = 0,
|
||||
MINMEA_SENTENCE_GBS,
|
||||
MINMEA_SENTENCE_GGA,
|
||||
MINMEA_SENTENCE_GLL,
|
||||
MINMEA_SENTENCE_GSA,
|
||||
MINMEA_SENTENCE_GST,
|
||||
MINMEA_SENTENCE_GSV,
|
||||
MINMEA_SENTENCE_RMC,
|
||||
MINMEA_SENTENCE_VTG,
|
||||
MINMEA_SENTENCE_ZDA,
|
||||
};
|
||||
|
||||
struct minmea_float {
|
||||
int_least32_t value;
|
||||
int_least32_t scale;
|
||||
};
|
||||
|
||||
struct minmea_date {
|
||||
int day;
|
||||
int month;
|
||||
int year;
|
||||
};
|
||||
|
||||
struct minmea_time {
|
||||
int hours;
|
||||
int minutes;
|
||||
int seconds;
|
||||
int microseconds;
|
||||
};
|
||||
|
||||
struct minmea_sentence_gbs {
|
||||
struct minmea_time time;
|
||||
struct minmea_float err_latitude;
|
||||
struct minmea_float err_longitude;
|
||||
struct minmea_float err_altitude;
|
||||
int svid;
|
||||
struct minmea_float prob;
|
||||
struct minmea_float bias;
|
||||
struct minmea_float stddev;
|
||||
};
|
||||
|
||||
struct minmea_sentence_rmc {
|
||||
struct minmea_time time;
|
||||
bool valid;
|
||||
struct minmea_float latitude;
|
||||
struct minmea_float longitude;
|
||||
struct minmea_float speed;
|
||||
struct minmea_float course;
|
||||
struct minmea_date date;
|
||||
struct minmea_float variation;
|
||||
};
|
||||
|
||||
struct minmea_sentence_gga {
|
||||
struct minmea_time time;
|
||||
struct minmea_float latitude;
|
||||
struct minmea_float longitude;
|
||||
int fix_quality;
|
||||
int satellites_tracked;
|
||||
struct minmea_float hdop;
|
||||
struct minmea_float altitude;
|
||||
char altitude_units;
|
||||
struct minmea_float height;
|
||||
char height_units;
|
||||
struct minmea_float dgps_age;
|
||||
};
|
||||
|
||||
enum minmea_gll_status {
|
||||
MINMEA_GLL_STATUS_DATA_VALID = 'A',
|
||||
MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V',
|
||||
};
|
||||
|
||||
// FAA mode added to some fields in NMEA 2.3.
|
||||
enum minmea_faa_mode {
|
||||
MINMEA_FAA_MODE_AUTONOMOUS = 'A',
|
||||
MINMEA_FAA_MODE_DIFFERENTIAL = 'D',
|
||||
MINMEA_FAA_MODE_ESTIMATED = 'E',
|
||||
MINMEA_FAA_MODE_MANUAL = 'M',
|
||||
MINMEA_FAA_MODE_SIMULATED = 'S',
|
||||
MINMEA_FAA_MODE_NOT_VALID = 'N',
|
||||
MINMEA_FAA_MODE_PRECISE = 'P',
|
||||
};
|
||||
|
||||
struct minmea_sentence_gll {
|
||||
struct minmea_float latitude;
|
||||
struct minmea_float longitude;
|
||||
struct minmea_time time;
|
||||
char status;
|
||||
char mode;
|
||||
};
|
||||
|
||||
struct minmea_sentence_gst {
|
||||
struct minmea_time time;
|
||||
struct minmea_float rms_deviation;
|
||||
struct minmea_float semi_major_deviation;
|
||||
struct minmea_float semi_minor_deviation;
|
||||
struct minmea_float semi_major_orientation;
|
||||
struct minmea_float latitude_error_deviation;
|
||||
struct minmea_float longitude_error_deviation;
|
||||
struct minmea_float altitude_error_deviation;
|
||||
};
|
||||
|
||||
enum minmea_gsa_mode {
|
||||
MINMEA_GPGSA_MODE_AUTO = 'A',
|
||||
MINMEA_GPGSA_MODE_FORCED = 'M',
|
||||
};
|
||||
|
||||
enum minmea_gsa_fix_type {
|
||||
MINMEA_GPGSA_FIX_NONE = 1,
|
||||
MINMEA_GPGSA_FIX_2D = 2,
|
||||
MINMEA_GPGSA_FIX_3D = 3,
|
||||
};
|
||||
|
||||
struct minmea_sentence_gsa {
|
||||
char mode;
|
||||
int fix_type;
|
||||
int sats[12];
|
||||
struct minmea_float pdop;
|
||||
struct minmea_float hdop;
|
||||
struct minmea_float vdop;
|
||||
};
|
||||
|
||||
struct minmea_sat_info {
|
||||
int nr;
|
||||
int elevation;
|
||||
int azimuth;
|
||||
int snr;
|
||||
};
|
||||
|
||||
struct minmea_sentence_gsv {
|
||||
int total_msgs;
|
||||
int msg_nr;
|
||||
int total_sats;
|
||||
struct minmea_sat_info sats[4];
|
||||
};
|
||||
|
||||
struct minmea_sentence_vtg {
|
||||
struct minmea_float true_track_degrees;
|
||||
struct minmea_float magnetic_track_degrees;
|
||||
struct minmea_float speed_knots;
|
||||
struct minmea_float speed_kph;
|
||||
enum minmea_faa_mode faa_mode;
|
||||
};
|
||||
|
||||
struct minmea_sentence_zda {
|
||||
struct minmea_time time;
|
||||
struct minmea_date date;
|
||||
int hour_offset;
|
||||
int minute_offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate raw sentence checksum. Does not check sentence integrity.
|
||||
*/
|
||||
uint8_t minmea_checksum(const char* sentence);
|
||||
|
||||
/**
|
||||
* Check sentence validity and checksum. Returns true for valid sentences.
|
||||
*/
|
||||
bool minmea_check(const char* sentence, bool strict);
|
||||
|
||||
/**
|
||||
* Determine talker identifier.
|
||||
*/
|
||||
bool minmea_talker_id(char talker[3], const char* sentence);
|
||||
|
||||
/**
|
||||
* Determine sentence identifier.
|
||||
*/
|
||||
enum minmea_sentence_id minmea_sentence_id(const char* sentence, bool strict);
|
||||
|
||||
/**
|
||||
* Scanf-like processor for NMEA sentences. Supports the following formats:
|
||||
* c - single character (char *)
|
||||
* d - direction, returned as 1/-1, default 0 (int *)
|
||||
* f - fractional, returned as value + scale (struct minmea_float *)
|
||||
* i - decimal, default zero (int *)
|
||||
* s - string (char *)
|
||||
* t - talker identifier and type (char *)
|
||||
* D - date (struct minmea_date *)
|
||||
* T - time stamp (struct minmea_time *)
|
||||
* _ - ignore this field
|
||||
* ; - following fields are optional
|
||||
* Returns true on success. See library source code for details.
|
||||
*/
|
||||
bool minmea_scan(const char* sentence, const char* format, ...);
|
||||
|
||||
/*
|
||||
* Parse a specific type of sentence. Return true on success.
|
||||
*/
|
||||
bool minmea_parse_gbs(struct minmea_sentence_gbs* frame, const char* sentence);
|
||||
bool minmea_parse_rmc(struct minmea_sentence_rmc* frame, const char* sentence);
|
||||
bool minmea_parse_gga(struct minmea_sentence_gga* frame, const char* sentence);
|
||||
bool minmea_parse_gsa(struct minmea_sentence_gsa* frame, const char* sentence);
|
||||
bool minmea_parse_gll(struct minmea_sentence_gll* frame, const char* sentence);
|
||||
bool minmea_parse_gst(struct minmea_sentence_gst* frame, const char* sentence);
|
||||
bool minmea_parse_gsv(struct minmea_sentence_gsv* frame, const char* sentence);
|
||||
bool minmea_parse_vtg(struct minmea_sentence_vtg* frame, const char* sentence);
|
||||
bool minmea_parse_zda(struct minmea_sentence_zda* frame, const char* sentence);
|
||||
|
||||
/**
|
||||
* Convert GPS UTC date/time representation to a UNIX calendar time.
|
||||
*/
|
||||
int minmea_getdatetime(
|
||||
struct tm* tm,
|
||||
const struct minmea_date* date,
|
||||
const struct minmea_time* time_);
|
||||
|
||||
/**
|
||||
* Convert GPS UTC date/time representation to a UNIX timestamp.
|
||||
*/
|
||||
int minmea_gettime(
|
||||
struct timespec* ts,
|
||||
const struct minmea_date* date,
|
||||
const struct minmea_time* time_);
|
||||
|
||||
/**
|
||||
* Rescale a fixed-point value to a different scale. Rounds towards zero.
|
||||
*/
|
||||
static inline int_least32_t minmea_rescale(const struct minmea_float* f, int_least32_t new_scale) {
|
||||
if(f->scale == 0) return 0;
|
||||
if(f->scale == new_scale) return f->value;
|
||||
if(f->scale > new_scale)
|
||||
return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale / new_scale / 2) /
|
||||
(f->scale / new_scale);
|
||||
else
|
||||
return f->value * (new_scale / f->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a fixed-point value to a floating-point value.
|
||||
* Returns NaN for "unknown" values.
|
||||
*/
|
||||
static inline float minmea_tofloat(const struct minmea_float* f) {
|
||||
if(f->scale == 0) return NAN;
|
||||
return (float)f->value / (float)f->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a raw coordinate to a floating point DD.DDD... value.
|
||||
* Returns NaN for "unknown" values.
|
||||
*/
|
||||
static inline float minmea_tocoord(const struct minmea_float* f) {
|
||||
if(f->scale == 0) return NAN;
|
||||
if(f->scale > (INT_LEAST32_MAX / 100)) return NAN;
|
||||
if(f->scale < (INT_LEAST32_MIN / 100)) return NAN;
|
||||
int_least32_t degrees = f->value / (f->scale * 100);
|
||||
int_least32_t minutes = f->value % (f->scale * 100);
|
||||
return (float)degrees + (float)minutes / (60 * f->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a character belongs to the set of characters allowed in a
|
||||
* sentence data field.
|
||||
*/
|
||||
static inline bool minmea_isfield(char c) {
|
||||
return isprint((unsigned char)c) && c != ',' && c != '*';
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MINMEA_H */
|
||||
|
||||
/* vim: set ts=4 sw=4 et: */
|
||||
@@ -68,6 +68,8 @@ typedef enum {
|
||||
SubGhzCustomEventSceneReceiverInfoTxStart,
|
||||
SubGhzCustomEventSceneReceiverInfoTxStop,
|
||||
SubGhzCustomEventSceneReceiverInfoSave,
|
||||
SubGhzCustomEventSceneReceiverInfoSats,
|
||||
SubGhzCustomEventSceneReceiverInfoBack,
|
||||
SubGhzCustomEventSceneSaveName,
|
||||
SubGhzCustomEventSceneSaveSuccess,
|
||||
SubGhzCustomEventSceneShowErrorBack,
|
||||
|
||||
171
applications/main/subghz/helpers/subghz_gps.c
Normal file
171
applications/main/subghz/helpers/subghz_gps.c
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "subghz_gps.h"
|
||||
|
||||
static void subghz_gps_uart_parse_nmea(SubGhzGPS* subghz_gps, char* line) {
|
||||
switch(minmea_sentence_id(line, false)) {
|
||||
case MINMEA_SENTENCE_RMC: {
|
||||
struct minmea_sentence_rmc frame;
|
||||
if(minmea_parse_rmc(&frame, line)) {
|
||||
subghz_gps->latitude = minmea_tocoord(&frame.latitude);
|
||||
subghz_gps->longitude = minmea_tocoord(&frame.longitude);
|
||||
subghz_gps->fix_second = frame.time.seconds;
|
||||
subghz_gps->fix_minute = frame.time.minutes;
|
||||
subghz_gps->fix_hour = frame.time.hours;
|
||||
}
|
||||
} break;
|
||||
|
||||
case MINMEA_SENTENCE_GGA: {
|
||||
struct minmea_sentence_gga frame;
|
||||
if(minmea_parse_gga(&frame, line)) {
|
||||
subghz_gps->latitude = minmea_tocoord(&frame.latitude);
|
||||
subghz_gps->longitude = minmea_tocoord(&frame.longitude);
|
||||
subghz_gps->satellites = frame.satellites_tracked;
|
||||
subghz_gps->fix_second = frame.time.seconds;
|
||||
subghz_gps->fix_minute = frame.time.minutes;
|
||||
subghz_gps->fix_hour = frame.time.hours;
|
||||
}
|
||||
} break;
|
||||
|
||||
case MINMEA_SENTENCE_GLL: {
|
||||
struct minmea_sentence_gll frame;
|
||||
if(minmea_parse_gll(&frame, line)) {
|
||||
subghz_gps->latitude = minmea_tocoord(&frame.latitude);
|
||||
subghz_gps->longitude = minmea_tocoord(&frame.longitude);
|
||||
subghz_gps->fix_second = frame.time.seconds;
|
||||
subghz_gps->fix_minute = frame.time.minutes;
|
||||
subghz_gps->fix_hour = frame.time.hours;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_gps_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
|
||||
SubGhzGPS* subghz_gps = (SubGhzGPS*)context;
|
||||
|
||||
if(ev == UartIrqEventRXNE) {
|
||||
furi_stream_buffer_send(subghz_gps->rx_stream, &data, 1, 0);
|
||||
furi_thread_flags_set(furi_thread_get_id(subghz_gps->thread), WorkerEvtRxDone);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t subghz_gps_uart_worker(void* context) {
|
||||
SubGhzGPS* subghz_gps = (SubGhzGPS*)context;
|
||||
|
||||
size_t rx_offset = 0;
|
||||
|
||||
while(1) {
|
||||
uint32_t events =
|
||||
furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_check((events & FuriFlagError) == 0);
|
||||
|
||||
if(events & WorkerEvtStop) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(events & WorkerEvtRxDone) {
|
||||
size_t len = 0;
|
||||
do {
|
||||
len = furi_stream_buffer_receive(
|
||||
subghz_gps->rx_stream,
|
||||
subghz_gps->rx_buf + rx_offset,
|
||||
RX_BUF_SIZE - rx_offset,
|
||||
0);
|
||||
|
||||
if(len > 0) {
|
||||
rx_offset += len;
|
||||
subghz_gps->rx_buf[rx_offset] = '\0';
|
||||
|
||||
char* current_line = (char*)subghz_gps->rx_buf;
|
||||
while(true) {
|
||||
while(*current_line == '\0' &&
|
||||
current_line < (char*)subghz_gps->rx_buf + rx_offset) {
|
||||
current_line++;
|
||||
}
|
||||
|
||||
char* next_line = strchr(current_line, '\n');
|
||||
if(next_line) {
|
||||
*next_line = '\0';
|
||||
subghz_gps_uart_parse_nmea(subghz_gps, current_line);
|
||||
current_line = next_line + 1;
|
||||
} else {
|
||||
if(current_line > (char*)subghz_gps->rx_buf) {
|
||||
rx_offset = 0;
|
||||
while(*current_line) {
|
||||
subghz_gps->rx_buf[rx_offset++] = *(current_line++);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(len > 0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SubGhzGPS* subghz_gps_init() {
|
||||
SubGhzGPS* subghz_gps = malloc(sizeof(SubGhzGPS));
|
||||
|
||||
subghz_gps->latitude = NAN;
|
||||
subghz_gps->longitude = NAN;
|
||||
subghz_gps->satellites = 0;
|
||||
|
||||
subghz_gps->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
|
||||
|
||||
subghz_gps->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(subghz_gps->thread, "SubGhzGPSWorker");
|
||||
furi_thread_set_stack_size(subghz_gps->thread, 1024);
|
||||
furi_thread_set_context(subghz_gps->thread, subghz_gps);
|
||||
furi_thread_set_callback(subghz_gps->thread, subghz_gps_uart_worker);
|
||||
// furi_thread_start(subghz_gps->thread);
|
||||
|
||||
if(UART_CH == FuriHalUartIdUSART1) {
|
||||
furi_hal_console_disable();
|
||||
} else if(UART_CH == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_init(UART_CH, 9600);
|
||||
}
|
||||
|
||||
furi_hal_uart_set_irq_cb(UART_CH, subghz_gps_uart_on_irq_cb, subghz_gps);
|
||||
furi_hal_uart_set_br(UART_CH, 9600);
|
||||
|
||||
return subghz_gps;
|
||||
}
|
||||
|
||||
void subghz_gps_deinit(SubGhzGPS* subghz_gps) {
|
||||
furi_assert(subghz_gps);
|
||||
// furi_thread_flags_set(furi_thread_get_id(subghz_gps->thread), WorkerEvtStop);
|
||||
// furi_thread_join(subghz_gps->thread);
|
||||
furi_thread_free(subghz_gps->thread);
|
||||
|
||||
free(subghz_gps);
|
||||
|
||||
furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL);
|
||||
if(UART_CH == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_deinit(UART_CH);
|
||||
} else {
|
||||
furi_hal_console_enable();
|
||||
}
|
||||
}
|
||||
|
||||
double subghz_gps_deg2rad(double deg) {
|
||||
return (deg * (double)M_PI / 180);
|
||||
}
|
||||
|
||||
double subghz_gps_calc_distance(double lat1d, double lon1d, double lat2d, double lon2d) {
|
||||
double lat1r, lon1r, lat2r, lon2r, u, v;
|
||||
lat1r = subghz_gps_deg2rad(lat1d);
|
||||
lon1r = subghz_gps_deg2rad(lon1d);
|
||||
lat2r = subghz_gps_deg2rad(lat2d);
|
||||
lon2r = subghz_gps_deg2rad(lon2d);
|
||||
u = sin((lat2r - lat1r) / 2);
|
||||
v = sin((lon2r - lon1r) / 2);
|
||||
return 2 * 6371 * asin(sqrt(u * u + cos((double)lat1r) * cos((double)lat2r) * v * v));
|
||||
}
|
||||
|
||||
double subghz_gps_calc_angle(double lat1, double lon1, double lat2, double lon2) {
|
||||
return atan2(lat1 - lat2, lon1 - lon2) * 180 / (double)M_PI;
|
||||
}
|
||||
65
applications/main/subghz/helpers/subghz_gps.h
Normal file
65
applications/main/subghz/helpers/subghz_gps.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <furi_hal.h>
|
||||
#include <xtreme.h>
|
||||
#include "minmea.h"
|
||||
|
||||
#define UART_CH \
|
||||
(XTREME_SETTINGS()->uart_nmea_channel == UARTDefault ? FuriHalUartIdUSART1 : \
|
||||
FuriHalUartIdLPUART1)
|
||||
|
||||
#define RX_BUF_SIZE 1024
|
||||
|
||||
typedef enum {
|
||||
WorkerEvtStop = (1 << 0),
|
||||
WorkerEvtRxDone = (1 << 1),
|
||||
} WorkerEvtFlags;
|
||||
|
||||
#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone)
|
||||
|
||||
typedef struct {
|
||||
FuriThread* thread;
|
||||
FuriStreamBuffer* rx_stream;
|
||||
uint8_t rx_buf[RX_BUF_SIZE];
|
||||
|
||||
FuriTimer* timer;
|
||||
|
||||
float latitude;
|
||||
float longitude;
|
||||
int satellites;
|
||||
uint8_t fix_second;
|
||||
uint8_t fix_minute;
|
||||
uint8_t fix_hour;
|
||||
} SubGhzGPS;
|
||||
|
||||
SubGhzGPS* subghz_gps_init();
|
||||
|
||||
void subghz_gps_deinit(SubGhzGPS* subghz_gps);
|
||||
|
||||
/**
|
||||
* Convert degree to radian
|
||||
*
|
||||
* @param deg Degree
|
||||
* @return double Radian
|
||||
*/
|
||||
double subghz_gps_deg2rad(double deg);
|
||||
|
||||
/**
|
||||
* Calculate distance between two coordinates
|
||||
*
|
||||
* @param lat1d Latitude 1
|
||||
* @param lon1d Longitude 1
|
||||
* @param lat2d Latitude 2
|
||||
* @param lon2d Longitude 2
|
||||
* @return double Distance in km
|
||||
*/
|
||||
double subghz_gps_calc_distance(double lat1d, double lon1d, double lat2d, double lon2d);
|
||||
|
||||
/**
|
||||
* Calculate angle between two coordinates
|
||||
*
|
||||
* @param lat1 Latitude 1
|
||||
* @param lon1 Longitude 1
|
||||
* @param lat2 Latitude 2
|
||||
* @param lon2 Longitude 2
|
||||
* @return double Angle in degree
|
||||
*/
|
||||
double subghz_gps_calc_angle(double lat1, double lon1, double lat2, double lon2);
|
||||
@@ -97,12 +97,16 @@ void subghz_txrx_set_preset(
|
||||
SubGhzTxRx* instance,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
float latitude,
|
||||
float longitude,
|
||||
uint8_t* preset_data,
|
||||
size_t preset_data_size) {
|
||||
furi_assert(instance);
|
||||
furi_string_set(instance->preset->name, preset_name);
|
||||
SubGhzRadioPreset* preset = instance->preset;
|
||||
preset->frequency = frequency;
|
||||
preset->latitude = latitude;
|
||||
preset->longitude = longitude;
|
||||
preset->data = preset_data;
|
||||
preset->data_size = preset_data_size;
|
||||
}
|
||||
@@ -154,6 +158,20 @@ void subghz_txrx_get_frequency_and_modulation(
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_txrx_get_latitude_and_longitude(
|
||||
SubGhzTxRx* instance,
|
||||
FuriString* latitude,
|
||||
FuriString* longitude) {
|
||||
furi_assert(instance);
|
||||
SubGhzRadioPreset* preset = instance->preset;
|
||||
if(latitude != NULL) {
|
||||
furi_string_printf(latitude, "%f", (double)preset->latitude);
|
||||
}
|
||||
if(longitude != NULL) {
|
||||
furi_string_printf(longitude, "%f", (double)preset->longitude);
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) {
|
||||
furi_assert(instance);
|
||||
subghz_devices_reset(instance->radio_device);
|
||||
@@ -680,7 +698,7 @@ void subghz_txrx_set_default_preset(SubGhzTxRx* instance, uint32_t frequency) {
|
||||
if(frequency == 0) {
|
||||
frequency = subghz_setting_get_default_frequency(subghz_txrx_get_setting(instance));
|
||||
}
|
||||
subghz_txrx_set_preset(instance, default_modulation, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(instance, default_modulation, frequency, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
const char*
|
||||
@@ -695,6 +713,8 @@ const char*
|
||||
instance,
|
||||
preset_name,
|
||||
frequency,
|
||||
0,
|
||||
0,
|
||||
subghz_setting_get_preset_data(setting, index),
|
||||
subghz_setting_get_preset_data_size(setting, index));
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance);
|
||||
* @param instance Pointer to a SubGhzTxRx
|
||||
* @param preset_name Name of preset
|
||||
* @param frequency Frequency in Hz
|
||||
* @param latitude Latitude in float
|
||||
* @param longitude Longitude in float
|
||||
* @param preset_data Data of preset
|
||||
* @param preset_data_size Size of preset data
|
||||
*/
|
||||
@@ -54,6 +56,8 @@ void subghz_txrx_set_preset(
|
||||
SubGhzTxRx* instance,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
float latitude,
|
||||
float longitude,
|
||||
uint8_t* preset_data,
|
||||
size_t preset_data_size);
|
||||
|
||||
@@ -87,6 +91,18 @@ void subghz_txrx_get_frequency_and_modulation(
|
||||
FuriString* modulation,
|
||||
bool long_name);
|
||||
|
||||
/**
|
||||
* Get string latitude and longitude
|
||||
*
|
||||
* @param instance Pointer to a SubGhzTxRx
|
||||
* @param latitude Pointer to a string latitude
|
||||
* @param longitude Pointer to a string longitude
|
||||
*/
|
||||
void subghz_txrx_get_latitude_and_longitude(
|
||||
SubGhzTxRx* instance,
|
||||
FuriString* latitude,
|
||||
FuriString* longitude);
|
||||
|
||||
/**
|
||||
* Start TX CC1101
|
||||
*
|
||||
|
||||
@@ -25,7 +25,7 @@ bool subghz_txrx_gen_data_protocol(
|
||||
|
||||
bool res = false;
|
||||
|
||||
subghz_txrx_set_preset(instance, preset_name, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(instance, preset_name, frequency, 0, 0, NULL, 0);
|
||||
instance->decoder_result =
|
||||
subghz_receiver_search_decoder_base_by_name(instance->receiver, protocol_name);
|
||||
|
||||
@@ -98,7 +98,7 @@ bool subghz_txrx_gen_keeloq_protocol( //TODO lead to a general appearance
|
||||
|
||||
instance->transmitter =
|
||||
subghz_transmitter_alloc_init(instance->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
|
||||
subghz_txrx_set_preset(instance, preset_name, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(instance, preset_name, frequency, 0, 0, NULL, 0);
|
||||
|
||||
if(instance->transmitter &&
|
||||
subghz_protocol_keeloq_create_data(
|
||||
@@ -131,7 +131,7 @@ bool subghz_txrx_gen_keeloq_bft_protocol(
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, 0, 0, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_keeloq_bft_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
@@ -175,7 +175,7 @@ bool subghz_txrx_gen_nice_flor_s_protocol(
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, 0, 0, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_nice_flor_s_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
@@ -208,7 +208,7 @@ bool subghz_txrx_gen_faac_slh_protocol(
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_FAAC_SLH_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, 0, 0, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_faac_slh_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
@@ -251,7 +251,7 @@ bool subghz_txrx_gen_alutech_at_4n_protocol(
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, 0, 0, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_alutech_at_4n_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
@@ -280,7 +280,7 @@ bool subghz_txrx_gen_came_atomo_protocol(
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_CAME_ATOMO_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, 0, 0, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_came_atomo_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
@@ -309,7 +309,7 @@ bool subghz_txrx_gen_somfy_telis_protocol(
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_SOMFY_TELIS_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, 0, 0, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_somfy_telis_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
@@ -338,7 +338,7 @@ bool subghz_txrx_gen_secplus_v2_protocol(
|
||||
bool ret = false;
|
||||
instance->transmitter =
|
||||
subghz_transmitter_alloc_init(instance->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME);
|
||||
subghz_txrx_set_preset(instance, name_preset, frequency, NULL, 0);
|
||||
subghz_txrx_set_preset(instance, name_preset, frequency, 0, 0, NULL, 0);
|
||||
if(instance->transmitter) {
|
||||
subghz_protocol_secplus_v2_create_data(
|
||||
subghz_transmitter_get_protocol_instance(instance->transmitter),
|
||||
|
||||
@@ -23,4 +23,6 @@ ADD_SCENE(subghz, more_raw, MoreRAW)
|
||||
ADD_SCENE(subghz, decode_raw, DecodeRAW)
|
||||
ADD_SCENE(subghz, delete_raw, DeleteRAW)
|
||||
ADD_SCENE(subghz, need_saving, NeedSaving)
|
||||
ADD_SCENE(subghz, rpc, Rpc)
|
||||
ADD_SCENE(subghz, rpc, Rpc)
|
||||
ADD_SCENE(subghz, show_gps, ShowGps)
|
||||
ADD_SCENE(subghz, saved_show_gps, SavedShowGps)
|
||||
@@ -6,7 +6,8 @@
|
||||
static void subghz_scene_receiver_update_statusbar(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
FuriString* history_stat_str = furi_string_alloc();
|
||||
if(!subghz_history_get_text_space_left(subghz->history, history_stat_str)) {
|
||||
if(!subghz_history_get_text_space_left(
|
||||
subghz->history, history_stat_str, subghz->gps->satellites)) {
|
||||
FuriString* frequency_str = furi_string_alloc();
|
||||
FuriString* modulation_str = furi_string_alloc();
|
||||
|
||||
@@ -51,6 +52,8 @@ static void subghz_scene_add_to_history_callback(
|
||||
FuriString* item_time = furi_string_alloc();
|
||||
uint16_t idx = subghz_history_get_item(subghz->history);
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
preset.latitude = subghz->gps->latitude;
|
||||
preset.longitude = subghz->gps->longitude;
|
||||
|
||||
if(subghz_history_add_to_history(subghz->history, decoder_base, &preset)) {
|
||||
furi_string_reset(item_name);
|
||||
|
||||
@@ -51,7 +51,7 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(state == SubGhzRxKeyStateExit) {
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0);
|
||||
subghz->txrx, "AM650", subghz->last_settings->frequency, 0, 0, NULL, 0);
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart);
|
||||
} else {
|
||||
|
||||
@@ -32,6 +32,12 @@ const char* const debug_pin_text[DEBUG_P_COUNT] = {
|
||||
"17(1W)",
|
||||
};
|
||||
|
||||
#define GPS_COUNT 2
|
||||
const char* const gps_text[GPS_COUNT] = {
|
||||
"OFF",
|
||||
"ON",
|
||||
};
|
||||
|
||||
#define DEBUG_COUNTER_COUNT 13
|
||||
const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = {
|
||||
"+1",
|
||||
@@ -115,6 +121,17 @@ static void subghz_scene_reciever_config_set_ext_mod_power_amp_text(VariableItem
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_gps(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
variable_item_set_current_value_text(item, gps_text[index]);
|
||||
|
||||
subghz->last_settings->gps_enabled = index == 1;
|
||||
subghz_last_settings_save(
|
||||
subghz->last_settings); //TODO, make it to choose baudrate. now it is 9600
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_timestamp_file_names(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
@@ -157,6 +174,12 @@ void subghz_scene_radio_settings_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, ext_mod_power_amp_text[value_index]);
|
||||
|
||||
item = variable_item_list_add(
|
||||
variable_item_list, "GPS", GPS_COUNT, subghz_scene_receiver_config_set_gps, subghz);
|
||||
value_index = subghz->last_settings->gps_enabled ? 1 : 0;
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, gps_text[value_index]);
|
||||
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Time In Names",
|
||||
|
||||
@@ -39,7 +39,8 @@ const NotificationSequence subghz_sequence_rx_locked = {
|
||||
static void subghz_scene_receiver_update_statusbar(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
FuriString* history_stat_str = furi_string_alloc();
|
||||
if(!subghz_history_get_text_space_left(subghz->history, history_stat_str)) {
|
||||
if(!subghz_history_get_text_space_left(
|
||||
subghz->history, history_stat_str, subghz->gps->satellites)) {
|
||||
FuriString* frequency_str = furi_string_alloc();
|
||||
FuriString* modulation_str = furi_string_alloc();
|
||||
|
||||
@@ -112,6 +113,9 @@ static void subghz_scene_add_to_history_callback(
|
||||
uint16_t idx = subghz_history_get_item(history);
|
||||
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
preset.latitude = subghz->gps->latitude;
|
||||
preset.longitude = subghz->gps->longitude;
|
||||
|
||||
if(subghz_history_add_to_history(history, decoder_base, &preset)) {
|
||||
furi_string_reset(item_name);
|
||||
furi_string_reset(item_time);
|
||||
@@ -127,7 +131,7 @@ static void subghz_scene_add_to_history_callback(
|
||||
subghz_history_get_type_protocol(history, idx));
|
||||
|
||||
subghz_scene_receiver_update_statusbar(subghz);
|
||||
if(subghz_history_get_text_space_left(subghz->history, NULL)) {
|
||||
if(subghz_history_get_text_space_left(subghz->history, NULL, 0)) {
|
||||
notification_message(subghz->notifications, &sequence_error);
|
||||
}
|
||||
}
|
||||
@@ -144,6 +148,10 @@ void subghz_scene_receiver_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
SubGhzHistory* history = subghz->history;
|
||||
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
furi_thread_start(subghz->gps->thread);
|
||||
}
|
||||
|
||||
FuriString* item_name = furi_string_alloc();
|
||||
FuriString* item_time = furi_string_alloc();
|
||||
|
||||
@@ -188,7 +196,7 @@ void subghz_scene_receiver_on_enter(void* context) {
|
||||
subghz->subghz_receiver, subghz_scene_receiver_callback, subghz);
|
||||
subghz_txrx_set_rx_callback(subghz->txrx, subghz_scene_add_to_history_callback, subghz);
|
||||
|
||||
if(!subghz_history_get_text_space_left(subghz->history, NULL)) {
|
||||
if(!subghz_history_get_text_space_left(subghz->history, NULL, 0)) {
|
||||
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||
}
|
||||
|
||||
@@ -286,13 +294,35 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data(
|
||||
subghz->threshold_rssi, subghz_txrx_radio_device_get_rssi(subghz->txrx));
|
||||
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
FuriHalRtcDateTime datetime;
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
if((datetime.second - subghz->gps->fix_second) > 15) {
|
||||
subghz->gps->latitude = NAN;
|
||||
subghz->gps->longitude = NAN;
|
||||
subghz->gps->satellites = 0;
|
||||
subghz->gps->fix_hour = 0;
|
||||
subghz->gps->fix_minute = 0;
|
||||
subghz->gps->fix_second = 0;
|
||||
}
|
||||
subghz_scene_receiver_update_statusbar(subghz);
|
||||
}
|
||||
|
||||
subghz_receiver_rssi(subghz->subghz_receiver, ret_rssi.rssi);
|
||||
subghz_protocol_decoder_bin_raw_data_input_rssi(
|
||||
(SubGhzProtocolDecoderBinRAW*)subghz_txrx_get_decoder(subghz->txrx), ret_rssi.rssi);
|
||||
|
||||
switch(subghz->state_notifications) {
|
||||
case SubGhzNotificationStateRx:
|
||||
notification_message(subghz->notifications, &sequence_blink_cyan_10);
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
if(subghz->gps->satellites > 0) {
|
||||
notification_message(subghz->notifications, &sequence_blink_green_10);
|
||||
} else {
|
||||
notification_message(subghz->notifications, &sequence_blink_red_10);
|
||||
}
|
||||
} else {
|
||||
notification_message(subghz->notifications, &sequence_blink_cyan_10);
|
||||
}
|
||||
break;
|
||||
case SubGhzNotificationStateRxDone:
|
||||
if(!subghz_is_locked(subghz)) {
|
||||
@@ -310,5 +340,9 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
void subghz_scene_receiver_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
SubGhz* subghz = context;
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
furi_thread_flags_set(furi_thread_get_id(subghz->gps->thread), WorkerEvtStop);
|
||||
furi_thread_join(subghz->gps->thread);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,8 @@ static void subghz_scene_receiver_config_set_frequency(VariableItem* item) {
|
||||
subghz->txrx,
|
||||
furi_string_get_cstr(preset.name),
|
||||
frequency,
|
||||
0,
|
||||
0,
|
||||
preset.data,
|
||||
preset.data_size);
|
||||
|
||||
@@ -181,6 +183,8 @@ static void subghz_scene_receiver_config_set_preset(VariableItem* item) {
|
||||
subghz->txrx,
|
||||
preset_name,
|
||||
preset.frequency,
|
||||
0,
|
||||
0,
|
||||
subghz_setting_get_preset_data(setting, index),
|
||||
subghz_setting_get_preset_data_size(setting, index));
|
||||
subghz->last_settings->preset_index = index;
|
||||
@@ -213,6 +217,8 @@ static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item)
|
||||
subghz->txrx,
|
||||
furi_string_get_cstr(preset.name),
|
||||
frequency,
|
||||
0,
|
||||
0,
|
||||
preset.data,
|
||||
preset.data_size);
|
||||
variable_item_set_current_value_index(
|
||||
|
||||
@@ -17,6 +17,11 @@ void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, v
|
||||
} else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) {
|
||||
view_dispatcher_send_custom_event(
|
||||
subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoSave);
|
||||
} else if(
|
||||
(result == GuiButtonTypeLeft) && (type == InputTypeShort) &&
|
||||
subghz->last_settings->gps_enabled) {
|
||||
view_dispatcher_send_custom_event(
|
||||
subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoSats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +42,8 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
|
||||
subghz->txrx,
|
||||
furi_string_get_cstr(preset->name),
|
||||
preset->frequency,
|
||||
0,
|
||||
0,
|
||||
preset->data,
|
||||
preset->data_size);
|
||||
|
||||
@@ -74,6 +81,15 @@ void subghz_scene_receiver_info_draw_widget(SubGhz* subghz) {
|
||||
widget_add_string_multiline_element(
|
||||
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text));
|
||||
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
widget_add_button_element(
|
||||
subghz->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Geo",
|
||||
subghz_scene_receiver_info_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
furi_string_free(frequency_str);
|
||||
furi_string_free(modulation_str);
|
||||
furi_string_free(text);
|
||||
@@ -111,7 +127,7 @@ void subghz_scene_receiver_info_on_enter(void* context) {
|
||||
|
||||
subghz_scene_receiver_info_draw_widget(subghz);
|
||||
|
||||
if(!subghz_history_get_text_space_left(subghz->history, NULL)) {
|
||||
if(!subghz_history_get_text_space_left(subghz->history, NULL, 0)) {
|
||||
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||
}
|
||||
}
|
||||
@@ -147,7 +163,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
|
||||
subghz_txrx_rx_start(subghz->txrx);
|
||||
|
||||
subghz_txrx_hopper_unpause(subghz->txrx);
|
||||
if(!subghz_history_get_text_space_left(subghz->history, NULL)) {
|
||||
if(!subghz_history_get_text_space_left(subghz->history, NULL, 0)) {
|
||||
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||
}
|
||||
}
|
||||
@@ -168,6 +184,13 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventSceneReceiverInfoSats) {
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowGps);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
if(subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF) {
|
||||
@@ -178,7 +201,15 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
|
||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||
break;
|
||||
case SubGhzNotificationStateRx:
|
||||
notification_message(subghz->notifications, &sequence_blink_cyan_10);
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
if(subghz->gps->satellites > 0) {
|
||||
notification_message(subghz->notifications, &sequence_blink_green_10);
|
||||
} else {
|
||||
notification_message(subghz->notifications, &sequence_blink_red_10);
|
||||
}
|
||||
} else {
|
||||
notification_message(subghz->notifications, &sequence_blink_cyan_10);
|
||||
}
|
||||
break;
|
||||
case SubGhzNotificationStateRxDone:
|
||||
notification_message(subghz->notifications, &sequence_blink_green_100);
|
||||
|
||||
@@ -4,6 +4,7 @@ enum SubmenuIndex {
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexGeo,
|
||||
};
|
||||
|
||||
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -34,6 +35,24 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
|
||||
FuriString* lat_str = furi_string_alloc();
|
||||
FuriString* lon_str = furi_string_alloc();
|
||||
|
||||
subghz_txrx_get_latitude_and_longitude(subghz->txrx, lat_str, lon_str);
|
||||
|
||||
if(strcmp(furi_string_get_cstr(lat_str), "nan") != 0 &&
|
||||
strcmp(furi_string_get_cstr(lon_str), "nan") != 0) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Geographic info",
|
||||
SubmenuIndexGeo,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
furi_string_free(lon_str);
|
||||
furi_string_free(lat_str);
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu,
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu));
|
||||
@@ -60,6 +79,11 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEdit);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexGeo) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexGeo);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSavedShowGps);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
110
applications/main/subghz/scenes/subghz_scene_saved_show_gps.c
Normal file
110
applications/main/subghz/scenes/subghz_scene_saved_show_gps.c
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "../subghz_i.h"
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
|
||||
void subghz_scene_saved_show_gps_draw_satellites(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
FuriString* text_str = furi_string_alloc();
|
||||
FuriString* lat_str = furi_string_alloc();
|
||||
FuriString* lon_str = furi_string_alloc();
|
||||
|
||||
subghz_txrx_get_latitude_and_longitude(subghz->txrx, lat_str, lon_str);
|
||||
|
||||
FuriHalRtcDateTime datetime;
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
if((datetime.second - subghz->gps->fix_second) > 15) {
|
||||
subghz->gps->latitude = NAN;
|
||||
subghz->gps->longitude = NAN;
|
||||
subghz->gps->satellites = 0;
|
||||
subghz->gps->fix_hour = 0;
|
||||
subghz->gps->fix_minute = 0;
|
||||
subghz->gps->fix_second = 0;
|
||||
}
|
||||
|
||||
double latitude = atof(furi_string_get_cstr(lat_str));
|
||||
double longitude = atof(furi_string_get_cstr(lon_str));
|
||||
|
||||
double distance = subghz_gps_calc_distance(
|
||||
latitude, longitude, (double)subghz->gps->latitude, (double)subghz->gps->longitude);
|
||||
|
||||
float angle = subghz_gps_calc_angle(
|
||||
latitude, longitude, (double)subghz->gps->latitude, (double)subghz->gps->longitude);
|
||||
|
||||
char* angle_str = "N";
|
||||
if(angle > 22.5 && angle <= 67.5) {
|
||||
angle_str = "NE";
|
||||
} else if(angle > 67.5 && angle <= 112.5) {
|
||||
angle_str = "E";
|
||||
} else if(angle > 112.5 && angle <= 157.5) {
|
||||
angle_str = "SE";
|
||||
} else if(angle > 157.5 && angle <= 202.5) {
|
||||
angle_str = "S";
|
||||
} else if(angle > 202.5 && angle <= 247.5) {
|
||||
angle_str = "SW";
|
||||
} else if(angle > 247.5 && angle <= 292.5) {
|
||||
angle_str = "W";
|
||||
} else if(angle > 292.5 && angle <= 337.5) {
|
||||
angle_str = "NW";
|
||||
}
|
||||
|
||||
furi_string_printf(
|
||||
text_str,
|
||||
"Captured at: %f,\r\n%f\r\n\r\nRealtime: Sats: %d\r\nDistance: %.2f%s Dir: %s\r\nGPS time: %d:%d:%d UTC",
|
||||
latitude,
|
||||
longitude,
|
||||
subghz->gps->satellites,
|
||||
subghz->gps->satellites > 0 ? distance > 1 ? distance : distance * 1000 : 0,
|
||||
distance > 1 ? "km" : "m",
|
||||
angle_str,
|
||||
subghz->gps->fix_hour,
|
||||
subghz->gps->fix_minute,
|
||||
subghz->gps->fix_second);
|
||||
|
||||
widget_add_text_scroll_element(subghz->widget, 0, 0, 128, 64, furi_string_get_cstr(text_str));
|
||||
|
||||
furi_string_free(text_str);
|
||||
furi_string_free(lat_str);
|
||||
furi_string_free(lon_str);
|
||||
}
|
||||
|
||||
void subghz_scene_saved_show_gps_refresh_screen(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
widget_reset(subghz->widget);
|
||||
subghz_scene_saved_show_gps_draw_satellites(subghz);
|
||||
}
|
||||
|
||||
void subghz_scene_saved_show_gps_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
furi_thread_start(subghz->gps->thread);
|
||||
}
|
||||
|
||||
subghz_scene_saved_show_gps_draw_satellites(subghz);
|
||||
|
||||
subghz->gps->timer = furi_timer_alloc(
|
||||
subghz_scene_saved_show_gps_refresh_screen, FuriTimerTypePeriodic, subghz);
|
||||
furi_timer_start(subghz->gps->timer, 1000);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
||||
}
|
||||
|
||||
bool subghz_scene_saved_show_gps_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_saved_show_gps_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
furi_thread_flags_set(furi_thread_get_id(subghz->gps->thread), WorkerEvtStop);
|
||||
furi_thread_join(subghz->gps->thread);
|
||||
}
|
||||
|
||||
furi_timer_stop(subghz->gps->timer);
|
||||
furi_timer_free(subghz->gps->timer);
|
||||
|
||||
widget_reset(subghz->widget);
|
||||
}
|
||||
143
applications/main/subghz/scenes/subghz_scene_show_gps.c
Normal file
143
applications/main/subghz/scenes/subghz_scene_show_gps.c
Normal file
@@ -0,0 +1,143 @@
|
||||
#include "../subghz_i.h"
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
|
||||
void subghz_scene_show_gps_draw_satellites(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
FuriString* text = furi_string_alloc();
|
||||
FuriString* time_text = furi_string_alloc();
|
||||
|
||||
FuriHalRtcDateTime datetime;
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
if((datetime.second - subghz->gps->fix_second) > 15) {
|
||||
subghz->gps->latitude = NAN;
|
||||
subghz->gps->longitude = NAN;
|
||||
subghz->gps->satellites = 0;
|
||||
subghz->gps->fix_hour = 0;
|
||||
subghz->gps->fix_minute = 0;
|
||||
subghz->gps->fix_second = 0;
|
||||
}
|
||||
|
||||
subghz_history_get_time_item_menu(subghz->history, time_text, subghz->idx_menu_chosen);
|
||||
|
||||
double distance = subghz_gps_calc_distance(
|
||||
(double)subghz_history_get_latitude(subghz->history, subghz->idx_menu_chosen),
|
||||
(double)subghz_history_get_longitude(subghz->history, subghz->idx_menu_chosen),
|
||||
(double)subghz->gps->latitude,
|
||||
(double)subghz->gps->longitude);
|
||||
|
||||
float angle = subghz_gps_calc_angle(
|
||||
(double)subghz_history_get_latitude(subghz->history, subghz->idx_menu_chosen),
|
||||
(double)subghz_history_get_longitude(subghz->history, subghz->idx_menu_chosen),
|
||||
(double)subghz->gps->latitude,
|
||||
(double)subghz->gps->longitude);
|
||||
|
||||
char* angle_str = "N";
|
||||
if(angle > 22.5 && angle <= 67.5) {
|
||||
angle_str = "NE";
|
||||
} else if(angle > 67.5 && angle <= 112.5) {
|
||||
angle_str = "E";
|
||||
} else if(angle > 112.5 && angle <= 157.5) {
|
||||
angle_str = "SE";
|
||||
} else if(angle > 157.5 && angle <= 202.5) {
|
||||
angle_str = "S";
|
||||
} else if(angle > 202.5 && angle <= 247.5) {
|
||||
angle_str = "SW";
|
||||
} else if(angle > 247.5 && angle <= 292.5) {
|
||||
angle_str = "W";
|
||||
} else if(angle > 292.5 && angle <= 337.5) {
|
||||
angle_str = "NW";
|
||||
}
|
||||
|
||||
FuriString* lat_str = furi_string_alloc();
|
||||
FuriString* lon_str = furi_string_alloc();
|
||||
|
||||
if(isnan(subghz->gps->latitude)) {
|
||||
furi_string_printf(lat_str, "N/A");
|
||||
} else {
|
||||
furi_string_printf(lat_str, "%f", (double)subghz->gps->latitude);
|
||||
}
|
||||
|
||||
if(isnan(subghz->gps->longitude)) {
|
||||
furi_string_printf(lon_str, "N/A");
|
||||
} else {
|
||||
furi_string_printf(lon_str, "%f", (double)subghz->gps->longitude);
|
||||
}
|
||||
|
||||
furi_string_printf(
|
||||
text,
|
||||
// "Captured at: %f,\r\n%f Time: %s\r\n\r\nRealtime: Sats: %d\r\nDistance: %.2f%s Dir: %s\r\nGPS time: %d:%d:%d UTC",
|
||||
// (double)subghz_history_get_latitude(subghz->history, subghz->idx_menu_chosen),
|
||||
// (double)subghz_history_get_longitude(subghz->history, subghz->idx_menu_chosen),
|
||||
"Captured at: %s,\r\n%s Time: %s\r\n\r\nRealtime: Sats: %d\r\nDistance: %.2f%s Dir: %s\r\nGPS time: %d:%d:%d UTC",
|
||||
furi_string_get_cstr(lat_str),
|
||||
furi_string_get_cstr(lon_str),
|
||||
furi_string_get_cstr(time_text),
|
||||
subghz->gps->satellites,
|
||||
subghz->gps->satellites > 0 ? distance > 1 ? distance : distance * 1000 : 0,
|
||||
distance > 1 ? "km" : "m",
|
||||
angle_str,
|
||||
subghz->gps->fix_hour,
|
||||
subghz->gps->fix_minute,
|
||||
subghz->gps->fix_second);
|
||||
|
||||
widget_add_text_scroll_element(subghz->widget, 0, 0, 128, 64, furi_string_get_cstr(text));
|
||||
|
||||
furi_string_free(lat_str);
|
||||
furi_string_free(lon_str);
|
||||
|
||||
furi_string_free(text);
|
||||
furi_string_free(time_text);
|
||||
}
|
||||
|
||||
void subghz_scene_show_gps_refresh_screen(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
widget_reset(subghz->widget);
|
||||
subghz_scene_show_gps_draw_satellites(subghz);
|
||||
}
|
||||
|
||||
void subghz_scene_show_gps_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
furi_thread_start(subghz->gps->thread);
|
||||
}
|
||||
|
||||
subghz_scene_show_gps_draw_satellites(subghz);
|
||||
|
||||
subghz->gps->timer =
|
||||
furi_timer_alloc(subghz_scene_show_gps_refresh_screen, FuriTimerTypePeriodic, subghz);
|
||||
furi_timer_start(subghz->gps->timer, 1000);
|
||||
}
|
||||
|
||||
bool subghz_scene_show_gps_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
if(event.type == SceneManagerEventTypeTick) {
|
||||
if(subghz->state_notifications == SubGhzNotificationStateRx) {
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
if(subghz->gps->satellites > 0) {
|
||||
notification_message(subghz->notifications, &sequence_blink_green_10);
|
||||
} else {
|
||||
notification_message(subghz->notifications, &sequence_blink_red_10);
|
||||
}
|
||||
} else {
|
||||
notification_message(subghz->notifications, &sequence_blink_cyan_10);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_show_gps_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(subghz->last_settings->gps_enabled) {
|
||||
furi_thread_flags_set(furi_thread_get_id(subghz->gps->thread), WorkerEvtStop);
|
||||
furi_thread_join(subghz->gps->thread);
|
||||
}
|
||||
|
||||
furi_timer_stop(subghz->gps->timer);
|
||||
furi_timer_free(subghz->gps->timer);
|
||||
|
||||
widget_reset(subghz->widget);
|
||||
}
|
||||
@@ -244,6 +244,8 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
//Init Error_str
|
||||
subghz->error_str = furi_string_alloc();
|
||||
|
||||
subghz->gps = subghz_gps_init();
|
||||
|
||||
return subghz;
|
||||
}
|
||||
|
||||
@@ -340,6 +342,8 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
furi_string_free(subghz->file_path);
|
||||
furi_string_free(subghz->file_path_tmp);
|
||||
|
||||
subghz_gps_deinit(subghz->gps);
|
||||
|
||||
// The rest
|
||||
free(subghz);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ typedef struct {
|
||||
uint8_t type;
|
||||
SubGhzRadioPreset* preset;
|
||||
FuriHalRtcDateTime datetime;
|
||||
float latitude;
|
||||
float longitude;
|
||||
} SubGhzHistoryItem;
|
||||
|
||||
ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST)
|
||||
@@ -73,6 +75,18 @@ const char* subghz_history_get_preset(SubGhzHistory* instance, uint16_t idx) {
|
||||
return furi_string_get_cstr(item->preset->name);
|
||||
}
|
||||
|
||||
float subghz_history_get_latitude(SubGhzHistory* instance, uint16_t idx) {
|
||||
furi_assert(instance);
|
||||
SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx);
|
||||
return item->latitude;
|
||||
}
|
||||
|
||||
float subghz_history_get_longitude(SubGhzHistory* instance, uint16_t idx) {
|
||||
furi_assert(instance);
|
||||
SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx);
|
||||
return item->longitude;
|
||||
}
|
||||
|
||||
void subghz_history_reset(SubGhzHistory* instance) {
|
||||
furi_assert(instance);
|
||||
furi_string_reset(instance->tmp_string);
|
||||
@@ -147,7 +161,7 @@ FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output) {
|
||||
bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output, uint8_t sats) {
|
||||
furi_assert(instance);
|
||||
if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) {
|
||||
if(output != NULL) furi_string_printf(output, " Free heap LOW");
|
||||
@@ -157,8 +171,23 @@ bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* out
|
||||
if(output != NULL) furi_string_printf(output, " Memory is FULL");
|
||||
return true;
|
||||
}
|
||||
if(output != NULL)
|
||||
furi_string_printf(output, "%02u/%02u", instance->last_index_write, SUBGHZ_HISTORY_MAX);
|
||||
if(output != NULL) {
|
||||
if(sats == 0) {
|
||||
furi_string_printf(
|
||||
output, "%02u/%02u", instance->last_index_write, SUBGHZ_HISTORY_MAX);
|
||||
return false;
|
||||
} else {
|
||||
FuriHalRtcDateTime datetime;
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
|
||||
if(furi_hal_rtc_datetime_to_timestamp(&datetime) % 2) {
|
||||
furi_string_printf(
|
||||
output, "%02u/%02u", instance->last_index_write, SUBGHZ_HISTORY_MAX);
|
||||
} else {
|
||||
furi_string_printf(output, "%d sats", sats);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -207,6 +236,8 @@ bool subghz_history_add_to_history(
|
||||
item->preset->data = preset->data;
|
||||
item->preset->data_size = preset->data_size;
|
||||
furi_hal_rtc_get_datetime(&item->datetime);
|
||||
item->latitude = preset->latitude;
|
||||
item->longitude = preset->longitude;
|
||||
|
||||
item->item_str = furi_string_alloc();
|
||||
item->flipper_string = flipper_format_string_alloc();
|
||||
|
||||
@@ -90,9 +90,10 @@ void subghz_history_get_time_item_menu(SubGhzHistory* instance, FuriString* outp
|
||||
*
|
||||
* @param instance - SubGhzHistory instance
|
||||
* @param output - FuriString* output
|
||||
* @param sats - Number of satellites
|
||||
* @return bool - is FULL
|
||||
*/
|
||||
bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output);
|
||||
bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output, uint8_t sats);
|
||||
|
||||
/** Return last index
|
||||
*
|
||||
@@ -120,3 +121,19 @@ bool subghz_history_add_to_history(
|
||||
* @return SubGhzProtocolCommonLoad*
|
||||
*/
|
||||
FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx);
|
||||
|
||||
/** Get latitude to history[idx]
|
||||
*
|
||||
* @param instance - SubGhzHistory instance
|
||||
* @param idx - Record index
|
||||
* @return latitude - Float
|
||||
*/
|
||||
float subghz_history_get_latitude(SubGhzHistory* instance, uint16_t idx);
|
||||
|
||||
/** Get longitude to history[idx]
|
||||
*
|
||||
* @param instance - SubGhzHistory instance
|
||||
* @param idx - Record index
|
||||
* @return longitude - Float
|
||||
*/
|
||||
float subghz_history_get_longitude(SubGhzHistory* instance, uint16_t idx);
|
||||
|
||||
@@ -71,6 +71,8 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
|
||||
SubGhzLoadKeyState load_key_state = SubGhzLoadKeyStateParseErr;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
uint32_t temp_data32;
|
||||
float temp_lat = NAN; // NAN or 0.0?? because 0.0 is valid value
|
||||
float temp_lon = NAN;
|
||||
|
||||
do {
|
||||
stream_clean(fff_data_stream);
|
||||
@@ -136,12 +138,26 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Load latitute and longitude if present
|
||||
if(!flipper_format_read_float(fff_data_file, "Latitute", (float*)&temp_lat, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing Latitude (optional)");
|
||||
}
|
||||
flipper_format_rewind(fff_data_file);
|
||||
|
||||
if(!flipper_format_read_float(fff_data_file, "Longitude", (float*)&temp_lon, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing Longitude (optional)");
|
||||
}
|
||||
flipper_format_rewind(fff_data_file);
|
||||
|
||||
size_t preset_index =
|
||||
subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str));
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx,
|
||||
furi_string_get_cstr(temp_str),
|
||||
temp_data32,
|
||||
temp_lat,
|
||||
temp_lon,
|
||||
subghz_setting_get_preset_data(setting, preset_index),
|
||||
subghz_setting_get_preset_data_size(setting, preset_index));
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "helpers/subghz_threshold_rssi.h"
|
||||
|
||||
#include "helpers/subghz_txrx.h"
|
||||
#include "helpers/subghz_gps.h"
|
||||
|
||||
#define SUBGHZ_MAX_LEN_NAME 64
|
||||
#define SUBGHZ_EXT_PRESET_NAME true
|
||||
@@ -93,6 +94,7 @@ struct SubGhz {
|
||||
SubGhzThresholdRssi* threshold_rssi;
|
||||
SubGhzRxKeyState rx_key_state;
|
||||
SubGhzHistory* history;
|
||||
SubGhzGPS* gps;
|
||||
|
||||
uint16_t idx_menu_chosen;
|
||||
SubGhzLoadTypeFile load_type_file;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER "ExtPower"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES "TimestampNames"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP "ExtPowerAmp"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_GPS "Gps"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE "Hopping"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER "IgnoreFilter"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_FILTER "Filter"
|
||||
@@ -55,6 +56,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
bool ignore_filter_was_read = false;
|
||||
bool frequency_analyzer_feedback_level_was_read = false;
|
||||
bool frequency_analyzer_trigger_was_read = false;
|
||||
bool temp_gps_enabled = false;
|
||||
|
||||
if(FSE_OK == storage_sd_status(storage) && SUBGHZ_LAST_SETTINGS_PATH &&
|
||||
flipper_format_file_open_existing(fff_data_file, SUBGHZ_LAST_SETTINGS_PATH)) {
|
||||
@@ -92,6 +94,8 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP,
|
||||
(bool*)&temp_external_module_power_amp,
|
||||
1);
|
||||
flipper_format_read_bool(
|
||||
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_GPS, (bool*)&temp_gps_enabled, 1);
|
||||
flipper_format_read_bool(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE,
|
||||
@@ -121,6 +125,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
instance->external_module_enabled = false;
|
||||
instance->timestamp_file_names = false;
|
||||
instance->external_module_power_amp = false;
|
||||
instance->gps_enabled = false;
|
||||
instance->enable_hopping = false;
|
||||
instance->ignore_filter = 0x00;
|
||||
// See bin_raw_value in applications/main/subghz/scenes/subghz_scene_receiver_config.c
|
||||
@@ -175,6 +180,8 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
#endif
|
||||
// Set globally in furi hal
|
||||
furi_hal_subghz_set_ext_power_amp(instance->external_module_power_amp);
|
||||
|
||||
instance->gps_enabled = temp_gps_enabled;
|
||||
}
|
||||
|
||||
flipper_format_file_close(fff_data_file);
|
||||
@@ -254,6 +261,10 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_insert_or_update_bool(
|
||||
file, SUBGHZ_LAST_SETTING_FIELD_GPS, &instance->gps_enabled, 1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_insert_or_update_bool(
|
||||
file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, &instance->enable_hopping, 1)) {
|
||||
break;
|
||||
|
||||
@@ -26,6 +26,7 @@ typedef struct {
|
||||
bool external_module_power_amp;
|
||||
// saved so as not to change the version
|
||||
bool timestamp_file_names;
|
||||
bool gps_enabled;
|
||||
bool enable_hopping;
|
||||
uint32_t ignore_filter;
|
||||
uint32_t filter;
|
||||
|
||||
@@ -64,6 +64,16 @@ SubGhzProtocolStatus subghz_block_generic_serialize(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!flipper_format_write_float(flipper_format, "Latitute", &preset->latitude, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Latitute");
|
||||
res = SubGhzProtocolStatusErrorParserLatitude;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_float(flipper_format, "Longitude", &preset->longitude, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Longitude");
|
||||
res = SubGhzProtocolStatusErrorParserLongitude;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Protocol");
|
||||
res = SubGhzProtocolStatusErrorParserProtocolName;
|
||||
|
||||
@@ -17,6 +17,8 @@ typedef struct SubGhzBlockGeneric SubGhzBlockGeneric;
|
||||
|
||||
struct SubGhzBlockGeneric {
|
||||
const char* protocol_name;
|
||||
float latitude;
|
||||
float longitude;
|
||||
uint64_t data;
|
||||
uint64_t data_2;
|
||||
uint32_t serial;
|
||||
|
||||
@@ -37,6 +37,8 @@ typedef struct {
|
||||
uint32_t frequency;
|
||||
uint8_t* data;
|
||||
size_t data_size;
|
||||
float latitude;
|
||||
float longitude;
|
||||
} SubGhzRadioPreset;
|
||||
|
||||
typedef enum {
|
||||
@@ -59,6 +61,8 @@ typedef enum {
|
||||
SubGhzProtocolStatusErrorEncoderGetUpload = (-12), ///< Payload encoder failure
|
||||
// Special Values
|
||||
SubGhzProtocolStatusErrorProtocolNotFound = (-13), ///< Protocol not found
|
||||
SubGhzProtocolStatusErrorParserLatitude = (-14), ///< Missing `Latitude`
|
||||
SubGhzProtocolStatusErrorParserLongitude = (-15), ///< Missing `Longitude`
|
||||
SubGhzProtocolStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization.
|
||||
} SubGhzProtocolStatus;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user