2022-10-20 18:02:27 +00:00
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "minmea.h"
|
|
|
|
#include "gps_uart.h"
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
WorkerEvtStop = (1 << 0),
|
|
|
|
WorkerEvtRxDone = (1 << 1),
|
|
|
|
} WorkerEvtFlags;
|
|
|
|
|
|
|
|
#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone)
|
|
|
|
|
|
|
|
static void gps_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
|
|
|
|
GpsUart* gps_uart = (GpsUart*)context;
|
|
|
|
|
|
|
|
if(ev == UartIrqEventRXNE) {
|
|
|
|
furi_stream_buffer_send(gps_uart->rx_stream, &data, 1, 0);
|
|
|
|
furi_thread_flags_set(furi_thread_get_id(gps_uart->thread), WorkerEvtRxDone);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gps_uart_serial_init(GpsUart* gps_uart) {
|
|
|
|
furi_hal_console_disable();
|
|
|
|
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, gps_uart_on_irq_cb, gps_uart);
|
2023-03-29 23:22:01 +00:00
|
|
|
furi_hal_uart_set_br(FuriHalUartIdUSART1, gps_uart->baudrate);
|
2022-10-20 18:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void gps_uart_serial_deinit(GpsUart* gps_uart) {
|
|
|
|
UNUSED(gps_uart);
|
|
|
|
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, NULL, NULL);
|
|
|
|
furi_hal_console_enable();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gps_uart_parse_nmea(GpsUart* gps_uart, char* line) {
|
|
|
|
switch(minmea_sentence_id(line, false)) {
|
|
|
|
case MINMEA_SENTENCE_RMC: {
|
|
|
|
struct minmea_sentence_rmc frame;
|
|
|
|
if(minmea_parse_rmc(&frame, line)) {
|
|
|
|
gps_uart->status.valid = frame.valid;
|
|
|
|
gps_uart->status.latitude = minmea_tocoord(&frame.latitude);
|
|
|
|
gps_uart->status.longitude = minmea_tocoord(&frame.longitude);
|
|
|
|
gps_uart->status.speed = minmea_tofloat(&frame.speed);
|
|
|
|
gps_uart->status.course = minmea_tofloat(&frame.course);
|
2022-10-28 16:23:02 +00:00
|
|
|
gps_uart->status.time_hours = frame.time.hours;
|
|
|
|
gps_uart->status.time_minutes = frame.time.minutes;
|
|
|
|
gps_uart->status.time_seconds = frame.time.seconds;
|
|
|
|
|
|
|
|
notification_message_block(gps_uart->notifications, &sequence_blink_green_10);
|
2022-10-20 18:02:27 +00:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case MINMEA_SENTENCE_GGA: {
|
|
|
|
struct minmea_sentence_gga frame;
|
|
|
|
if(minmea_parse_gga(&frame, line)) {
|
|
|
|
gps_uart->status.latitude = minmea_tocoord(&frame.latitude);
|
|
|
|
gps_uart->status.longitude = minmea_tocoord(&frame.longitude);
|
|
|
|
gps_uart->status.altitude = minmea_tofloat(&frame.altitude);
|
|
|
|
gps_uart->status.altitude_units = frame.altitude_units;
|
|
|
|
gps_uart->status.fix_quality = frame.fix_quality;
|
|
|
|
gps_uart->status.satellites_tracked = frame.satellites_tracked;
|
2022-10-28 16:23:02 +00:00
|
|
|
gps_uart->status.time_hours = frame.time.hours;
|
|
|
|
gps_uart->status.time_minutes = frame.time.minutes;
|
|
|
|
gps_uart->status.time_seconds = frame.time.seconds;
|
|
|
|
|
|
|
|
notification_message_block(gps_uart->notifications, &sequence_blink_magenta_10);
|
2022-10-20 18:02:27 +00:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
2023-05-24 04:24:01 +00:00
|
|
|
case MINMEA_SENTENCE_GLL: {
|
|
|
|
struct minmea_sentence_gll frame;
|
|
|
|
if(minmea_parse_gll(&frame, line)) {
|
|
|
|
gps_uart->status.latitude = minmea_tocoord(&frame.latitude);
|
|
|
|
gps_uart->status.longitude = minmea_tocoord(&frame.longitude);
|
|
|
|
gps_uart->status.time_hours = frame.time.hours;
|
|
|
|
gps_uart->status.time_minutes = frame.time.minutes;
|
|
|
|
gps_uart->status.time_seconds = frame.time.seconds;
|
|
|
|
|
|
|
|
notification_message_block(gps_uart->notifications, &sequence_blink_red_10);
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
2022-10-20 18:02:27 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int32_t gps_uart_worker(void* context) {
|
|
|
|
GpsUart* gps_uart = (GpsUart*)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 {
|
2023-05-24 04:24:01 +00:00
|
|
|
// receive serial bytes into rx_buf, starting at rx_offset from the start of the buffer
|
|
|
|
// the maximum we can receive is RX_BUF_SIZE - 1 - rx_offset
|
2022-10-20 18:02:27 +00:00
|
|
|
len = furi_stream_buffer_receive(
|
|
|
|
gps_uart->rx_stream,
|
|
|
|
gps_uart->rx_buf + rx_offset,
|
|
|
|
RX_BUF_SIZE - 1 - rx_offset,
|
|
|
|
0);
|
|
|
|
if(len > 0) {
|
2023-05-24 04:24:01 +00:00
|
|
|
// increase rx_offset by the number of bytes received, and null-terminate rx_buf
|
2022-10-20 18:02:27 +00:00
|
|
|
rx_offset += len;
|
|
|
|
gps_uart->rx_buf[rx_offset] = '\0';
|
|
|
|
|
2023-05-24 04:24:01 +00:00
|
|
|
// look for strings ending in newlines, starting at the start of rx_buf
|
2022-10-20 18:02:27 +00:00
|
|
|
char* line_current = (char*)gps_uart->rx_buf;
|
|
|
|
while(1) {
|
2023-05-24 04:24:01 +00:00
|
|
|
// skip null characters
|
2022-10-20 18:02:27 +00:00
|
|
|
while(*line_current == '\0' &&
|
|
|
|
line_current < (char*)gps_uart->rx_buf + rx_offset - 1) {
|
|
|
|
line_current++;
|
|
|
|
}
|
|
|
|
|
2023-05-24 04:24:01 +00:00
|
|
|
// find the next newline
|
2022-10-20 18:02:27 +00:00
|
|
|
char* newline = strchr(line_current, '\n');
|
2023-05-24 04:24:01 +00:00
|
|
|
if(newline) // newline found
|
|
|
|
{
|
|
|
|
// put a null terminator in place of the newline, to delimit the line string
|
2022-10-20 18:02:27 +00:00
|
|
|
*newline = '\0';
|
2023-05-24 04:24:01 +00:00
|
|
|
|
|
|
|
// attempt to parse the line as a NMEA sentence
|
2022-10-20 18:02:27 +00:00
|
|
|
gps_uart_parse_nmea(gps_uart, line_current);
|
2023-05-24 04:24:01 +00:00
|
|
|
|
|
|
|
// move the cursor to the character after the newline
|
2022-10-20 18:02:27 +00:00
|
|
|
line_current = newline + 1;
|
2023-05-24 04:24:01 +00:00
|
|
|
} else // no more newlines found
|
|
|
|
{
|
|
|
|
if(line_current >
|
|
|
|
(char*)gps_uart->rx_buf) // at least one line was found
|
|
|
|
{
|
|
|
|
// clear parsed lines, and move any leftover bytes to the start of rx_buf
|
2022-10-20 18:02:27 +00:00
|
|
|
rx_offset = 0;
|
2023-05-24 04:24:01 +00:00
|
|
|
while(
|
|
|
|
*line_current) // stop when the original rx_offset terminator is reached
|
|
|
|
{
|
2022-10-20 18:02:27 +00:00
|
|
|
gps_uart->rx_buf[rx_offset++] = *(line_current++);
|
|
|
|
}
|
|
|
|
}
|
2023-05-24 04:24:01 +00:00
|
|
|
break; // go back to receiving bytes from the serial stream
|
2022-10-20 18:02:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while(len > 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gps_uart_serial_deinit(gps_uart);
|
|
|
|
furi_stream_buffer_free(gps_uart->rx_stream);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-29 23:22:01 +00:00
|
|
|
void gps_uart_init_thread(GpsUart* gps_uart) {
|
|
|
|
furi_assert(gps_uart);
|
2022-10-20 18:02:27 +00:00
|
|
|
gps_uart->status.valid = false;
|
|
|
|
gps_uart->status.latitude = 0.0;
|
|
|
|
gps_uart->status.longitude = 0.0;
|
|
|
|
gps_uart->status.speed = 0.0;
|
|
|
|
gps_uart->status.course = 0.0;
|
|
|
|
gps_uart->status.altitude = 0.0;
|
|
|
|
gps_uart->status.altitude_units = ' ';
|
|
|
|
gps_uart->status.fix_quality = 0;
|
|
|
|
gps_uart->status.satellites_tracked = 0;
|
2022-10-28 16:23:02 +00:00
|
|
|
gps_uart->status.time_hours = 0;
|
|
|
|
gps_uart->status.time_minutes = 0;
|
|
|
|
gps_uart->status.time_seconds = 0;
|
|
|
|
|
2023-05-21 23:33:43 +00:00
|
|
|
gps_uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE * 5, 1);
|
|
|
|
|
2022-10-20 18:02:27 +00:00
|
|
|
gps_uart->thread = furi_thread_alloc();
|
|
|
|
furi_thread_set_name(gps_uart->thread, "GpsUartWorker");
|
|
|
|
furi_thread_set_stack_size(gps_uart->thread, 1024);
|
|
|
|
furi_thread_set_context(gps_uart->thread, gps_uart);
|
|
|
|
furi_thread_set_callback(gps_uart->thread, gps_uart_worker);
|
|
|
|
|
|
|
|
furi_thread_start(gps_uart->thread);
|
2023-05-21 23:33:43 +00:00
|
|
|
|
|
|
|
gps_uart_serial_init(gps_uart);
|
2022-10-20 18:02:27 +00:00
|
|
|
}
|
|
|
|
|
2023-03-29 23:22:01 +00:00
|
|
|
void gps_uart_deinit_thread(GpsUart* gps_uart) {
|
2022-10-20 18:02:27 +00:00
|
|
|
furi_assert(gps_uart);
|
|
|
|
furi_thread_flags_set(furi_thread_get_id(gps_uart->thread), WorkerEvtStop);
|
|
|
|
furi_thread_join(gps_uart->thread);
|
|
|
|
furi_thread_free(gps_uart->thread);
|
2023-03-29 23:22:01 +00:00
|
|
|
}
|
2022-10-28 16:23:02 +00:00
|
|
|
|
2023-03-29 23:22:01 +00:00
|
|
|
GpsUart* gps_uart_enable() {
|
|
|
|
GpsUart* gps_uart = malloc(sizeof(GpsUart));
|
|
|
|
|
|
|
|
gps_uart->notifications = furi_record_open(RECORD_NOTIFICATION);
|
|
|
|
|
2023-04-10 23:37:57 +00:00
|
|
|
gps_uart->baudrate = gps_baudrates[current_gps_baudrate];
|
2023-03-29 23:22:01 +00:00
|
|
|
|
|
|
|
gps_uart_init_thread(gps_uart);
|
|
|
|
|
|
|
|
return gps_uart;
|
|
|
|
}
|
|
|
|
|
|
|
|
void gps_uart_disable(GpsUart* gps_uart) {
|
|
|
|
furi_assert(gps_uart);
|
|
|
|
gps_uart_deinit_thread(gps_uart);
|
2022-10-28 16:23:02 +00:00
|
|
|
furi_record_close(RECORD_NOTIFICATION);
|
|
|
|
|
2022-10-20 18:02:27 +00:00
|
|
|
free(gps_uart);
|
|
|
|
}
|