From 9b7bef0abe35498a6b45da7498515bbeeb0c40f0 Mon Sep 17 00:00:00 2001 From: Linar Yusupov Date: Tue, 6 Oct 2020 09:33:52 +0300 Subject: [PATCH] Status, radar, text and clock screens for Badge Edition's e-Paper UI --- README.md | 2 +- .../source/SkyView/View_Radar_EPD.cpp | 2 +- .../source/SoftRF/BluetoothHelper.cpp | 2 +- software/firmware/source/SoftRF/EPDHelper.cpp | 256 +++++++++-- software/firmware/source/SoftRF/EPDHelper.h | 161 +++++++ .../firmware/source/SoftRF/Platform_nRF52.cpp | 18 + .../firmware/source/SoftRF/Platform_nRF52.h | 43 +- .../firmware/source/SoftRF/TrafficHelper.cpp | 24 + .../firmware/source/SoftRF/TrafficHelper.h | 9 + .../firmware/source/SoftRF/View_Radar_EPD.cpp | 349 +++++++++++++++ .../source/SoftRF/View_Status_EPD.cpp | 419 ++++++++++++++++++ .../firmware/source/SoftRF/View_Text_EPD.cpp | 291 ++++++++++++ .../firmware/source/SoftRF/View_Time_EPD.cpp | 95 ++++ .../libraries/GxEPD2/src/GxEPD2_EPD.cpp | 2 + .../source/libraries/GxEPD2/src/GxEPD2_EPD.h | 1 - .../arduino-basicmac/src/hal/target-config.h | 6 + 16 files changed, 1636 insertions(+), 44 deletions(-) create mode 100644 software/firmware/source/SoftRF/View_Radar_EPD.cpp create mode 100644 software/firmware/source/SoftRF/View_Status_EPD.cpp create mode 100644 software/firmware/source/SoftRF/View_Text_EPD.cpp create mode 100644 software/firmware/source/SoftRF/View_Time_EPD.cpp diff --git a/README.md b/README.md index e7b22cb05..b04378077 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Data|FLARM NMEA|

![](https://github.com/lyusupov/SoftRF/raw/mas 1 - it is necessary for a reader to distinguish the difference between statement "**compatible**" and statement "**fully compatible**".
     SoftRF implements only a reasonable minimum of the protocols specs. No "bells and whistles" so far.
2 - FANET+ can not receive FLARM. However it is able to transmit it.
-3 - valid for [**Prime Mark II**](https://github.com/lyusupov/SoftRF/wiki/Prime-Edition-MkII) and [**Dongle**](https://github.com/lyusupov/SoftRF/wiki/Dongle-Edition) **Editions**; valid for [**Standalone**](https://github.com/lyusupov/SoftRF/wiki/Standalone-Edition) and [**UAV**](https://github.com/lyusupov/SoftRF/wiki/UAV-Edition) **Editions** with optional DIY [SoftRF LoRa RF module](https://github.com/lyusupov/SoftRF/wiki/SoftRF-LoRa-module)
+3 - valid for [**Prime Mark II**](https://github.com/lyusupov/SoftRF/wiki/Prime-Edition-MkII) , [**Dongle**](https://github.com/lyusupov/SoftRF/wiki/Dongle-Edition) and [**Uni**](https://github.com/lyusupov/SoftRF/wiki/Uni-Edition) **Editions**; valid for [**Standalone**](https://github.com/lyusupov/SoftRF/wiki/Standalone-Edition) and [**UAV**](https://github.com/lyusupov/SoftRF/wiki/UAV-Edition) **Editions** with optional DIY [SoftRF LoRa RF module](https://github.com/lyusupov/SoftRF/wiki/SoftRF-LoRa-module)
4 - [**Reception**](https://github.com/lyusupov/SoftRF/tree/master/software/firmware/source/UATbridge) of traffic 'downlink' frames only. Valid for [**Uni Edition**](https://github.com/lyusupov/SoftRF/wiki/Uni-Edition) alone and for [**Standalone Edition**](https://github.com/lyusupov/SoftRF/wiki/Standalone-Edition) with optional DIY [SoftRF UAT module](https://github.com/lyusupov/UAT-test-signal#variant-2-advanced)
5 - also known as "raw ADS-B"
diff --git a/software/firmware/source/SkyView/View_Radar_EPD.cpp b/software/firmware/source/SkyView/View_Radar_EPD.cpp index e1f7f6a42..e31207acb 100644 --- a/software/firmware/source/SkyView/View_Radar_EPD.cpp +++ b/software/firmware/source/SkyView/View_Radar_EPD.cpp @@ -96,7 +96,7 @@ static void EPD_Draw_NavBoxes() uint16_t bottom_navboxes_x = navbox3.x; uint16_t bottom_navboxes_y = navbox3.y; uint16_t bottom_navboxes_w = navbox3.width + navbox4.width; - uint16_t bottom_navboxes_h = maxof2(navbox3.height, navbox3.height); + uint16_t bottom_navboxes_h = maxof2(navbox3.height, navbox4.height); display->setPartialWindow(bottom_navboxes_x, bottom_navboxes_y, diff --git a/software/firmware/source/SoftRF/BluetoothHelper.cpp b/software/firmware/source/SoftRF/BluetoothHelper.cpp index 35e29fa8f..65f710f1f 100644 --- a/software/firmware/source/SoftRF/BluetoothHelper.cpp +++ b/software/firmware/source/SoftRF/BluetoothHelper.cpp @@ -1096,7 +1096,7 @@ void nRF52_Bluetooth_setup() } /********************************************************************* - End of Adafruit license text + End of Adafruit licensed text *********************************************************************/ static void nRF52_Bluetooth_loop() diff --git a/software/firmware/source/SoftRF/EPDHelper.cpp b/software/firmware/source/SoftRF/EPDHelper.cpp index 8aae56bb2..a6cfa0cba 100644 --- a/software/firmware/source/SoftRF/EPDHelper.cpp +++ b/software/firmware/source/SoftRF/EPDHelper.cpp @@ -22,24 +22,46 @@ #if defined(ARDUINO_ARCH_NRF52) -#define ENABLE_GxEPD2_GFX 0 +#include "EPDHelper.h" +#include "LEDHelper.h" -#include #include -#include "LEDHelper.h" +#include -GxEPD2_BW display(GxEPD2_154_D67( +GxEPD2_BW epd_ttgo_txx(GxEPD2_154_D67( SOC_GPIO_PIN_EPD_SS, SOC_GPIO_PIN_EPD_DC, SOC_GPIO_PIN_EPD_RST, SOC_GPIO_PIN_EPD_BUSY)); +GxEPD2_BW *display; + const char EPD_SoftRF_text1[] = "SoftRF"; const char EPD_SoftRF_text2[] = "and"; const char EPD_SoftRF_text3[] = "LilyGO"; +unsigned long EPDTimeMarker = 0; +bool EPD_display_frontpage = false; + +static int EPD_view_mode = 0; +bool EPD_vmode_updated = true; + +void EPD_Clear_Screen() +{ + display->setFullWindow(); + + display->firstPage(); + do + { + display->fillScreen(GxEPD_WHITE); + } + while (display->nextPage()); +} + bool EPD_setup(bool splash_screen) { + bool rval = false; + int16_t tbx1, tby1; uint16_t tbw1, tbh1; int16_t tbx2, tby2; @@ -48,63 +70,221 @@ bool EPD_setup(bool splash_screen) uint16_t tbw3, tbh3; uint16_t x, y; - display.init( /* 38400 */ ); + display = &epd_ttgo_txx; - // first update should be full refresh - display.setRotation(0); - display.setFont(&FreeMonoBold24pt7b); - display.setTextColor(GxEPD_BLACK); + display->init( /* 38400 */ ); - display.getTextBounds(EPD_SoftRF_text1, 0, 0, &tbx1, &tby1, &tbw1, &tbh1); - display.getTextBounds(EPD_SoftRF_text2, 0, 0, &tbx2, &tby2, &tbw2, &tbh2); - display.getTextBounds(EPD_SoftRF_text3, 0, 0, &tbx3, &tby3, &tbw3, &tbh3); - display.setFullWindow(); - display.firstPage(); + // first update should be full refresh + display->setRotation(0); + display->setFont(&FreeMonoBold24pt7b); + display->setTextColor(GxEPD_BLACK); + display->setTextWrap(false); + + display->getTextBounds(EPD_SoftRF_text1, 0, 0, &tbx1, &tby1, &tbw1, &tbh1); + display->getTextBounds(EPD_SoftRF_text2, 0, 0, &tbx2, &tby2, &tbw2, &tbh2); + display->getTextBounds(EPD_SoftRF_text3, 0, 0, &tbx3, &tby3, &tbw3, &tbh3); + display->setFullWindow(); + display->firstPage(); do { - display.fillScreen(GxEPD_WHITE); + display->fillScreen(GxEPD_WHITE); if (hw_info.model == SOFTRF_MODEL_BADGE) { - x = (display.width() - tbw1) / 2; - y = (display.height() + tbh1) / 2 - tbh3; - display.setCursor(x, y); - display.print(EPD_SoftRF_text1); - x = (display.width() - tbw2) / 2; - y = (display.height() + tbh2) / 2; - display.setCursor(x, y); - display.print(EPD_SoftRF_text2); - x = (display.width() - tbw3) / 2; - y = (display.height() + tbh3) / 2 + tbh3; - display.setCursor(x, y); - display.print(EPD_SoftRF_text3); + x = (display->width() - tbw1) / 2; + y = (display->height() + tbh1) / 2 - tbh3; + display->setCursor(x, y); + display->print(EPD_SoftRF_text1); + x = (display->width() - tbw2) / 2; + y = (display->height() + tbh2) / 2; + display->setCursor(x, y); + display->print(EPD_SoftRF_text2); + x = (display->width() - tbw3) / 2; + y = (display->height() + tbh3) / 2 + tbh3; + display->setCursor(x, y); + display->print(EPD_SoftRF_text3); } else { - x = (display.width() - tbw1) / 2; - y = (display.height() + tbh1) / 2; - display.setCursor(x, y); - display.print(EPD_SoftRF_text1); + x = (display->width() - tbw1) / 2; + y = (display->height() + tbh1) / 2; + display->setCursor(x, y); + display->print(EPD_SoftRF_text1); } } - while (display.nextPage()); + while (display->nextPage()); delay(1000); -// display.powerOff(); -// display.hibernate(); +// display->powerOff(); +// display->hibernate(); - return display.epd2.probe(); + rval = display->epd2.probe(); + + EPD_view_mode = ui->vmode;; + + EPD_status_setup(); + EPD_radar_setup(); + EPD_text_setup(); + EPD_time_setup(); + + EPDTimeMarker = millis(); + + return rval; } void EPD_loop() { - if (hw_info.display == DISPLAY_EPD_1_54) { - /* TODO */ + switch (hw_info.display) + { + case DISPLAY_EPD_1_54: + + if (isTimeToDisplay()) { + switch (EPD_view_mode) + { + case VIEW_MODE_RADAR: + EPD_radar_loop(); + break; + case VIEW_MODE_TEXT: + EPD_text_loop(); + break; + case VIEW_MODE_TIME: + EPD_time_loop(); + break; + case VIEW_MODE_STATUS: + default: + EPD_status_loop(); + break; + } + + EPDTimeMarker = millis(); + } + break; + + case DISPLAY_NONE: + default: + break; } } void EPD_fini(const char *msg) +{ + int16_t tbx1, tby1; + uint16_t tbw1, tbh1; + uint16_t x, y; + + switch (hw_info.display) + { + case DISPLAY_EPD_1_54: + + display->getTextBounds(msg, 0, 0, &tbx1, &tby1, &tbw1, &tbh1); + + display->setFullWindow(); + display->firstPage(); + do + { + display->fillScreen(GxEPD_WHITE); + + x = (display->width() - tbw1) / 2; + y = (display->height() + tbh1) / 2; + display->setCursor(x, y); + display->print(EPD_SoftRF_text1); + } + while (display->nextPage()); + + display->powerOff(); + break; + + case DISPLAY_NONE: + default: + break; + } +} + +void EPD_Up() +{ + if (hw_info.display == DISPLAY_EPD_1_54) { + switch (EPD_view_mode) + { + case VIEW_MODE_RADAR: + EPD_radar_unzoom(); + break; + case VIEW_MODE_TEXT: + EPD_text_prev(); + break; + case VIEW_MODE_TIME: + EPD_time_prev(); + break; + case VIEW_MODE_STATUS: + default: + EPD_status_prev(); + break; + } + } +} + +void EPD_Down() { if (hw_info.display == DISPLAY_EPD_1_54) { - /* TODO */ + switch (EPD_view_mode) + { + case VIEW_MODE_RADAR: + EPD_radar_zoom(); + break; + case VIEW_MODE_TEXT: + EPD_text_next(); + break; + case VIEW_MODE_TIME: + EPD_time_next(); + break; + case VIEW_MODE_STATUS: + default: + EPD_status_next(); + break; + } + } +} + +void EPD_Message(const char *msg1, const char *msg2) +{ + int16_t tbx, tby; + uint16_t tbw, tbh; + uint16_t x, y; + + if (msg1 != NULL && strlen(msg1) != 0) { + + display->setPartialWindow(0, 0, display->width(), display->height()); + + display->setFont(&FreeMonoBold18pt7b); + + display->firstPage(); + do + { + display->fillScreen(GxEPD_WHITE); + + if (msg2 == NULL) { + + display->getTextBounds(msg1, 0, 0, &tbx, &tby, &tbw, &tbh); + x = (display->width() - tbw) / 2; + y = (display->height() + tbh) / 2; + display->setCursor(x, y); + display->print(msg1); + + } else { + + display->getTextBounds(msg1, 0, 0, &tbx, &tby, &tbw, &tbh); + x = (display->width() - tbw) / 2; + y = display->height() / 2 - tbh; + display->setCursor(x, y); + display->print(msg1); + + display->getTextBounds(msg2, 0, 0, &tbx, &tby, &tbw, &tbh); + x = (display->width() - tbw) / 2; + y = display->height() / 2 + tbh; + display->setCursor(x, y); + display->print(msg2); + } + } + while (display->nextPage()); + + display->hibernate(); } } diff --git a/software/firmware/source/SoftRF/EPDHelper.h b/software/firmware/source/SoftRF/EPDHelper.h index ef87e34b9..046bd7e9e 100644 --- a/software/firmware/source/SoftRF/EPDHelper.h +++ b/software/firmware/source/SoftRF/EPDHelper.h @@ -19,8 +19,169 @@ #ifndef EPDHELPER_H #define EPDHELPER_H +#define ENABLE_GxEPD2_GFX 0 + +#include + +#define EPD_EXPIRATION_TIME 5 /* seconds */ + +#define NO_DATA_TEXT "NO DATA" +#define NO_FIX_TEXT "NO FIX" + +#define NAVBOX1_TITLE "ACFTS" +#define NAVBOX2_TITLE "BAT" +#define NAVBOX3_TITLE "ID" +#define NAVBOX4_TITLE "PROTOCOL" +#define NAVBOX5_TITLE "RX" +#define NAVBOX6_TITLE "TX" + +#define isTimeToDisplay() (millis() - EPDTimeMarker > 1000) +#define maxof2(a,b) (a > b ? a : b) + +#define EPD_RADAR_V_THRESHOLD 50 /* metres */ + +#define TEXT_VIEW_LINE_LENGTH 13 /* characters */ +#define TEXT_VIEW_LINE_SPACING 5 /* pixels */ + +enum +{ + VIEW_MODE_STATUS, + VIEW_MODE_RADAR, + VIEW_MODE_TEXT, + VIEW_MODE_TIME +}; + +/* + * 'Radar view' scale factor (outer circle diameter) + * + * Metric and Mixed: + * LOWEST - 20 KM diameter (10 KM radius) + * LOW - 10 KM diameter ( 5 KM radius) + * MEDIUM - 4 KM diameter ( 2 KM radius) + * HIGH - 2 KM diameter ( 1 KM radius) + * + * Imperial: + * LOWEST - 10 NM diameter ( 5 NM radius) + * LOW - 5 NM diameter (2.5 NM radius) + * MEDIUM - 2 NM diameter ( 1 NM radius) + * HIGH - 1 NM diameter (0.5 NM radius) + */ +enum +{ + ZOOM_LOWEST, + ZOOM_LOW, + ZOOM_MEDIUM, + ZOOM_HIGH +}; + +enum +{ + UNITS_METRIC, + UNITS_IMPERIAL, + UNITS_MIXED // almost the same as metric, but all the altitudes are in feet +}; + +enum +{ + PROTOCOL_NONE, + PROTOCOL_NMEA, /* FTD-12 */ + PROTOCOL_GDL90, + PROTOCOL_MAVLINK_1, + PROTOCOL_MAVLINK_2, + PROTOCOL_D1090, + PROTOCOL_UATRADIO +}; + +enum +{ + ID_REG, + ID_TAIL, + ID_MAM +}; + +enum +{ + VOICE_OFF, + VOICE_1, + VOICE_2, + VOICE_3 +}; + +enum +{ + ANTI_GHOSTING_OFF, + ANTI_GHOSTING_AUTO, + ANTI_GHOSTING_2MIN, + ANTI_GHOSTING_5MIN, + ANTI_GHOSTING_10MIN +}; + +enum +{ + TRAFFIC_FILTER_OFF, + TRAFFIC_FILTER_500M, + TRAFFIC_FILTER_1500M +}; + +enum +{ + DB_NONE, + DB_AUTO, + DB_FLN, + DB_OGN, + DB_ICAO +}; + +typedef struct navbox_struct +{ + char title[9]; + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; + int32_t value; + int32_t prev_value; + uint32_t timestamp; +} navbox_t; + +void EPD_Clear_Screen(); bool EPD_setup(bool); void EPD_loop(); void EPD_fini(const char *); +void EPD_Up(); +void EPD_Down(); +void EPD_Message(const char *, const char *); + +void EPD_status_setup(); +void EPD_status_loop(); +void EPD_status_next(); +void EPD_status_prev(); + +void EPD_radar_setup(); +void EPD_radar_loop(); +void EPD_radar_zoom(); +void EPD_radar_unzoom(); + +void EPD_text_setup(); +void EPD_text_loop(); +void EPD_text_next(); +void EPD_text_prev(); + +void EPD_time_setup(); +void EPD_time_loop(); +void EPD_time_next(); +void EPD_time_prev(); + +extern GxEPD2_BW *display; +extern unsigned long EPDTimeMarker; +extern bool EPD_display_frontpage; +extern bool EPD_vmode_updated; + +#include "SoCHelper.h" + +#if defined(ARDUINO_ARCH_NRF52) +extern ui_settings_t *ui; +#endif /* ARDUINO_ARCH_NRF52 */ + #endif /* EPDHELPER_H */ diff --git a/software/firmware/source/SoftRF/Platform_nRF52.cpp b/software/firmware/source/SoftRF/Platform_nRF52.cpp index ae44b8a30..d66a017f0 100644 --- a/software/firmware/source/SoftRF/Platform_nRF52.cpp +++ b/software/firmware/source/SoftRF/Platform_nRF52.cpp @@ -82,6 +82,22 @@ I2CBus *i2c = nullptr; SoftSPI SPI2(SOC_GPIO_PIN_SFL_MOSI, SOC_GPIO_PIN_SFL_MISO, SOC_GPIO_PIN_SFL_SCK); +ui_settings_t ui_settings = { + .units = UNITS_METRIC, + .zoom = ZOOM_MEDIUM, + .protocol = PROTOCOL_NMEA, + .orientation = DIRECTION_NORTH_UP, + .adb = DB_NONE, + .idpref = ID_REG, + .vmode = VIEW_MODE_STATUS, + .voice = VOICE_OFF, + .aghost = ANTI_GHOSTING_OFF, + .filter = TRAFFIC_FILTER_OFF, + .team = 0 +}; + +ui_settings_t *ui; + #if !defined(PIN_SERIAL2_RX) && !defined(PIN_SERIAL2_TX) Uart Serial2( NRF_UARTE1, UARTE1_IRQn, SOC_GPIO_PIN_SWSER_RX, SOC_GPIO_PIN_SWSER_TX ); @@ -106,6 +122,8 @@ static void nRF52_setup() bool has_rtc = false; bool has_spiflash = false ; + ui = &ui_settings; + // uint32_t u32Reset_reason = NRF_POWER->RESETREAS; // reset_info.reason = u32Reset_reason; diff --git a/software/firmware/source/SoftRF/Platform_nRF52.h b/software/firmware/source/SoftRF/Platform_nRF52.h index 4c17c558a..22cbfc86d 100644 --- a/software/firmware/source/SoftRF/Platform_nRF52.h +++ b/software/firmware/source/SoftRF/Platform_nRF52.h @@ -21,6 +21,7 @@ #define PLATFORM_NRF52_H #include +#include /* Maximum of tracked flying objects is now SoC-specific constant */ #define MAX_TRACKING_OBJECTS 8 @@ -71,7 +72,7 @@ struct rst_info { #define REAL_VBAT_MV_PER_LSB (SOC_ADC_VOLTAGE_DIV * VBAT_MV_PER_LSB) #if !defined(_PINNUM) -#define _PINNUM(port, pin) ((port)*32 + (pin)) +#define _PINNUM(port, pin) ((port)*32 + (pin)) #endif /* Peripherals */ @@ -90,7 +91,7 @@ struct rst_info { #define SOC_GPIO_LED_RED _PINNUM(0, 14) // P0.14 (Red) #define SOC_GPIO_LED_BLUE _PINNUM(0, 15) // P0.15 (Blue) -#define SOC_GPIO_PIN_STATUS SOC_GPIO_LED_GREEN +#define SOC_GPIO_PIN_STATUS SOC_GPIO_LED_GREEN #define SOC_GPIO_PIN_BUZZER SOC_UNUSED_PIN #define SOC_GPIO_PIN_BATTERY _PINNUM(0, 4) // P0.04 (AIN2) @@ -180,6 +181,44 @@ extern Adafruit_NeoPixel strip; extern Uart Serial2; #endif +typedef struct UI_Settings { + uint8_t adapter; + + uint8_t connection:4; + uint8_t units:2; + uint8_t zoom:2; + + uint8_t protocol; + uint8_t baudrate; + char server [18]; + char key [18]; + + uint8_t resvd1:2; + uint8_t orientation:1; + uint8_t adb:3; + uint8_t idpref:2; + + uint8_t vmode:2; + uint8_t voice:3; + uint8_t aghost:3; + + uint8_t filter:4; + uint8_t power_save:4; + + uint32_t team; + + uint8_t resvd2; + uint8_t resvd3; + uint8_t resvd4; + uint8_t resvd5; + uint8_t resvd6; + uint8_t resvd7; + uint8_t resvd8; + uint8_t resvd9; +} __attribute__((packed)) ui_settings_t; + +extern PCF8563_Class *rtc; + #endif /* PLATFORM_NRF52_H */ #endif /* ARDUINO_ARCH_NRF52 */ diff --git a/software/firmware/source/SoftRF/TrafficHelper.cpp b/software/firmware/source/SoftRF/TrafficHelper.cpp index b79d1f601..1fcb1aacb 100644 --- a/software/firmware/source/SoftRF/TrafficHelper.cpp +++ b/software/firmware/source/SoftRF/TrafficHelper.cpp @@ -26,6 +26,7 @@ unsigned long UpdateTrafficTimeMarker = 0; ufo_t fo, Container[MAX_TRACKING_OBJECTS], EmptyFO; +traffic_by_dist_t traffic_by_dist[MAX_TRACKING_OBJECTS]; static int8_t (*Alarm_Level)(ufo_t *, ufo_t *); @@ -220,3 +221,26 @@ void ClearExpired() } } } + +int Traffic_Count() +{ + int count = 0; + + for (int i=0; i < MAX_TRACKING_OBJECTS; i++) { + if (Container[i].addr) { + count++; + } + } + + return count; +} + +int traffic_cmp_by_distance(const void *a, const void *b) +{ + traffic_by_dist_t *ta = (traffic_by_dist_t *)a; + traffic_by_dist_t *tb = (traffic_by_dist_t *)b; + + if (ta->distance > tb->distance) return 1; + if (ta->distance == tb->distance) return 0; + if (ta->distance < tb->distance) return -1; +} diff --git a/software/firmware/source/SoftRF/TrafficHelper.h b/software/firmware/source/SoftRF/TrafficHelper.h index ab9881c3d..d1104dfe6 100644 --- a/software/firmware/source/SoftRF/TrafficHelper.h +++ b/software/firmware/source/SoftRF/TrafficHelper.h @@ -34,6 +34,11 @@ #define isTimeToUpdateTraffic() (millis() - UpdateTrafficTimeMarker > \ TRAFFIC_UPDATE_INTERVAL_MS) +typedef struct traffic_by_dist_struct { + ufo_t *fop; + float distance; +} traffic_by_dist_t; + enum { TRAFFIC_ALARM_NONE, @@ -47,7 +52,11 @@ void Traffic_setup(void); void Traffic_loop(void); void ClearExpired(void); void Traffic_Update(int); +int Traffic_Count(void); + +int traffic_cmp_by_distance(const void *, const void *); extern ufo_t fo, Container[MAX_TRACKING_OBJECTS], EmptyFO; +extern traffic_by_dist_t traffic_by_dist[MAX_TRACKING_OBJECTS]; #endif /* TRAFFICHELPER_H */ diff --git a/software/firmware/source/SoftRF/View_Radar_EPD.cpp b/software/firmware/source/SoftRF/View_Radar_EPD.cpp new file mode 100644 index 000000000..e73d18020 --- /dev/null +++ b/software/firmware/source/SoftRF/View_Radar_EPD.cpp @@ -0,0 +1,349 @@ +/* + * View_Radar_EPD.cpp + * Copyright (C) 2019-2020 Linar Yusupov + * + * 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 3 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, see . + */ + +#include "SoCHelper.h" + +#if defined(USE_EPAPER) + +#if defined(ARDUINO_ARCH_NRF52) + +#include "EPDHelper.h" + +#include + +#include "TrafficHelper.h" +#include "BatteryHelper.h" +#include "EEPROMHelper.h" +#include "NMEAHelper.h" +#include "GDL90Helper.h" +#include "LEDHelper.h" + +#include +#include + +static int EPD_zoom = ZOOM_MEDIUM; + +enum { + STATE_RVIEW_NONE, + STATE_RVIEW_RADAR, + STATE_RVIEW_NOFIX, + STATE_RVIEW_NODATA +}; + +static int view_state_curr = STATE_RVIEW_NONE; +static int view_state_prev = STATE_RVIEW_NONE; + +static void EPD_Draw_Radar() +{ + int16_t tbx, tby; + uint16_t tbw, tbh; + uint16_t x; + uint16_t y; + char cog_text[6]; + + /* divider is a half of full scale */ + int32_t divider = 2000; + + display->setFont(&FreeMono9pt7b); + display->getTextBounds("N", 0, 0, &tbx, &tby, &tbw, &tbh); + + uint16_t radar_x = 0; + uint16_t radar_y = (display->height() - display->width()) / 2; + uint16_t radar_w = display->width(); + + display->setPartialWindow(radar_x, radar_y, radar_w, radar_w); + + uint16_t radar_center_x = radar_w / 2; + uint16_t radar_center_y = radar_y + radar_w / 2; + uint16_t radius = radar_w / 2 - 2; + + if (ui->units == UNITS_METRIC || ui->units == UNITS_MIXED) { + switch(EPD_zoom) + { + case ZOOM_LOWEST: + divider = 10000; /* 20 KM */ + break; + case ZOOM_LOW: + divider = 5000; /* 10 KM */ + break; + case ZOOM_HIGH: + divider = 1000; /* 2 KM */ + break; + case ZOOM_MEDIUM: + default: + divider = 2000; /* 4 KM */ + break; + } + } else { + switch(EPD_zoom) + { + case ZOOM_LOWEST: + divider = 9260; /* 10 NM */ + break; + case ZOOM_LOW: + divider = 4630; /* 5 NM */ + break; + case ZOOM_HIGH: + divider = 926; /* 1 NM */ + break; + case ZOOM_MEDIUM: /* 2 NM */ + default: + divider = 1852; + break; + } + } + + display->firstPage(); + do + { + for (int i=0; i < MAX_TRACKING_OBJECTS; i++) { + if (Container[i].addr && (now() - Container[i].timestamp) <= EPD_EXPIRATION_TIME) { + + int16_t rel_x; + int16_t rel_y; + float distance; + float bearing; + + bool isTeam = (Container[i].addr == ui->team) ; + + distance = Container[i].distance; + bearing = Container[i].bearing; + + switch (ui->orientation) + { + case DIRECTION_NORTH_UP: + break; + case DIRECTION_TRACK_UP: + bearing -= ThisAircraft.course; + break; + default: + /* TBD */ + break; + } + + rel_x = constrain(distance * sin(radians(bearing)), + -32768, 32767); + rel_y = constrain(distance * cos(radians(bearing)), + -32768, 32767); + + int16_t x = ((int32_t) rel_x * (int32_t) radius) / divider; + int16_t y = ((int32_t) rel_y * (int32_t) radius) / divider; + + + float RelativeVertical = Container[i].altitude - ThisAircraft.altitude; + + if (RelativeVertical > EPD_RADAR_V_THRESHOLD) { + if (isTeam) { + display->drawTriangle(radar_center_x + x - 5, radar_center_y - y + 4, + radar_center_x + x , radar_center_y - y - 6, + radar_center_x + x + 5, radar_center_y - y + 4, + GxEPD_BLACK); + display->drawTriangle(radar_center_x + x - 6, radar_center_y - y + 5, + radar_center_x + x , radar_center_y - y - 7, + radar_center_x + x + 6, radar_center_y - y + 5, + GxEPD_BLACK); + } else { + display->fillTriangle(radar_center_x + x - 4, radar_center_y - y + 3, + radar_center_x + x , radar_center_y - y - 5, + radar_center_x + x + 4, radar_center_y - y + 3, + GxEPD_BLACK); + } + } else if (RelativeVertical < - EPD_RADAR_V_THRESHOLD) { + if (isTeam) { + display->drawTriangle(radar_center_x + x - 5, radar_center_y - y - 4, + radar_center_x + x , radar_center_y - y + 6, + radar_center_x + x + 5, radar_center_y - y - 4, + GxEPD_BLACK); + display->drawTriangle(radar_center_x + x - 6, radar_center_y - y - 5, + radar_center_x + x , radar_center_y - y + 7, + radar_center_x + x + 6, radar_center_y - y - 5, + GxEPD_BLACK); + } else { + display->fillTriangle(radar_center_x + x - 4, radar_center_y - y - 3, + radar_center_x + x , radar_center_y - y + 5, + radar_center_x + x + 4, radar_center_y - y - 3, + GxEPD_BLACK); + } + } else { + if (isTeam) { + display->drawCircle(radar_center_x + x, + radar_center_y - y, + 6, GxEPD_BLACK); + display->drawCircle(radar_center_x + x, + radar_center_y - y, + 7, GxEPD_BLACK); + } else { + display->fillCircle(radar_center_x + x, + radar_center_y - y, + 5, GxEPD_BLACK); + } + } + } + } + + display->drawCircle( radar_center_x, radar_center_y, + radius, GxEPD_BLACK); + display->drawCircle( radar_center_x, radar_center_y, + radius / 2, GxEPD_BLACK); + +#if 0 + /* arrow tip */ + display->fillTriangle(radar_center_x - 7, radar_center_y + 5, + radar_center_x , radar_center_y - 5, + radar_center_x + 7, radar_center_y + 5, + GxEPD_BLACK); + display->fillTriangle(radar_center_x - 7, radar_center_y + 5, + radar_center_x , radar_center_y + 2, + radar_center_x + 7, radar_center_y + 5, + GxEPD_WHITE); +#else + /* little airplane */ + display->drawFastVLine(radar_center_x, radar_center_y - 4, 14, GxEPD_BLACK); + display->drawFastVLine(radar_center_x + 1, radar_center_y - 4, 14, GxEPD_BLACK); + + display->drawFastHLine(radar_center_x - 8, radar_center_y, 18, GxEPD_BLACK); + display->drawFastHLine(radar_center_x - 10, radar_center_y + 1, 22, GxEPD_BLACK); + + display->drawFastHLine(radar_center_x - 3, radar_center_y + 8, 8, GxEPD_BLACK); + display->drawFastHLine(radar_center_x - 2, radar_center_y + 9, 6, GxEPD_BLACK); +#endif + + switch (ui->orientation) + { + case DIRECTION_NORTH_UP: + x = radar_x + radar_w / 2 - radius + tbw/2; + y = radar_y + (radar_w + tbh) / 2; + display->setCursor(x , y); + display->print("W"); + x = radar_x + radar_w / 2 + radius - (3 * tbw)/2; + y = radar_y + (radar_w + tbh) / 2; + display->setCursor(x , y); + display->print("E"); + x = radar_x + (radar_w - tbw) / 2; + y = radar_y + radar_w/2 - radius + (3 * tbh)/2; + display->setCursor(x , y); + display->print("N"); + x = radar_x + (radar_w - tbw) / 2; + y = radar_y + radar_w/2 + radius - tbh/2; + display->setCursor(x , y); + display->print("S"); + break; + case DIRECTION_TRACK_UP: + x = radar_x + radar_w / 2 - radius + tbw/2; + y = radar_y + (radar_w + tbh) / 2; + display->setCursor(x , y); + display->print("L"); + x = radar_x + radar_w / 2 + radius - (3 * tbw)/2; + y = radar_y + (radar_w + tbh) / 2; + display->setCursor(x , y); + display->print("R"); + x = radar_x + (radar_w - tbw) / 2; + y = radar_y + radar_w/2 + radius - tbh/2; + display->setCursor(x , y); + display->print("B"); + + display->setFont(&FreeMonoBold9pt7b); + snprintf(cog_text, sizeof(cog_text), "%03d", ThisAircraft.course); + display->getTextBounds(cog_text, 0, 0, &tbx, &tby, &tbw, &tbh); + + x = radar_x + (radar_w - tbw) / 2; + y = radar_y + radar_w/2 - radius + (3 * tbh)/2; + display->setCursor(x , y); + display->print(cog_text); + display->drawRoundRect( x - 2, y - tbh - 2, + tbw + 8, tbh + 6, + 4, GxEPD_BLACK); + break; + default: + /* TBD */ + break; + } + + display->setFont(&FreeMonoBold9pt7b); + display->getTextBounds("00 NM", 0, 0, &tbx, &tby, &tbw, &tbh); + + x = radar_x; + y = radar_y + radar_w - tbh; + display->setCursor(x, y); + + if (ui->units == UNITS_METRIC || ui->units == UNITS_MIXED) { + display->print(EPD_zoom == ZOOM_LOWEST ? "20 KM" : + EPD_zoom == ZOOM_LOW ? "10 KM" : + EPD_zoom == ZOOM_MEDIUM ? " 4 KM" : + EPD_zoom == ZOOM_HIGH ? " 2 KM" : ""); + } else { + display->print(EPD_zoom == ZOOM_LOWEST ? "10 NM" : + EPD_zoom == ZOOM_LOW ? " 5 NM" : + EPD_zoom == ZOOM_MEDIUM ? " 2 NM" : + EPD_zoom == ZOOM_HIGH ? " 1 NM" : ""); + } + } + while (display->nextPage()); + +// display->hibernate(); +} + +void EPD_radar_setup() +{ + EPD_zoom = ui->zoom; +} + +void EPD_radar_loop() +{ + bool hasFix = true; // isValidGNSSFix(); + + if (hasFix) { + view_state_curr = STATE_RVIEW_RADAR; + } else { + view_state_curr = STATE_RVIEW_NOFIX; + } + + if (EPD_vmode_updated) { + view_state_prev = STATE_RVIEW_NONE; + EPD_vmode_updated = false; + } + + if (view_state_curr != view_state_prev && + view_state_curr == STATE_RVIEW_NOFIX) { + EPD_Clear_Screen(); + EPD_Message(NO_FIX_TEXT, NULL); + view_state_prev = view_state_curr; + } + + if (view_state_curr == STATE_RVIEW_RADAR) { + if (view_state_curr != view_state_prev) { + EPD_Clear_Screen(); + view_state_prev = view_state_curr; + } + EPD_Draw_Radar(); + } +} + +void EPD_radar_zoom() +{ + if (EPD_zoom < ZOOM_HIGH) EPD_zoom++; +} + +void EPD_radar_unzoom() +{ + if (EPD_zoom > ZOOM_LOWEST) EPD_zoom--; +} + +#endif /* ARDUINO_ARCH_NRF52 */ + +#endif /* USE_EPAPER */ diff --git a/software/firmware/source/SoftRF/View_Status_EPD.cpp b/software/firmware/source/SoftRF/View_Status_EPD.cpp new file mode 100644 index 000000000..6fed6b2a3 --- /dev/null +++ b/software/firmware/source/SoftRF/View_Status_EPD.cpp @@ -0,0 +1,419 @@ +/* + * View_Status_EPD.cpp + * Copyright (C) 2019-2020 Linar Yusupov + * + * 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 3 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, see . + */ + +#include "SoCHelper.h" + +#if defined(USE_EPAPER) + +#if defined(ARDUINO_ARCH_NRF52) + +#include "EPDHelper.h" +#include "TrafficHelper.h" +#include "BatteryHelper.h" +#include + +#include +#include +#include + +extern uint32_t tx_packets_counter, rx_packets_counter; + +const char *EPD_Protocol_ID[] = { + [RF_PROTOCOL_LEGACY] = "LEG", + [RF_PROTOCOL_OGNTP] = "OGN", + [RF_PROTOCOL_P3I] = "P3I", + [RF_PROTOCOL_ADSB_1090] = "ADS", + [RF_PROTOCOL_ADSB_UAT] = "UAT", + [RF_PROTOCOL_FANET] = "FAN" +}; + +const char ID_text[] = "ID"; +const char PROTOCOL_text[] = "PROTOCOL"; +const char RX_text[] = "RX"; +const char TX_text[] = "TX"; +const char ACFTS_text[] = "ACFTS"; +const char BAT_text[] = "BAT"; + +static navbox_t navbox1; +static navbox_t navbox2; +static navbox_t navbox3; +static navbox_t navbox4; +static navbox_t navbox5; +static navbox_t navbox6; + +void EPD_status_setup() +{ + memcpy(navbox1.title, NAVBOX1_TITLE, strlen(NAVBOX1_TITLE)); + navbox1.x = 0; + navbox1.y = 0; + navbox1.width = display->width() / 2; + navbox1.height = display->height() / 3; + navbox1.value = 0; + navbox1.prev_value = navbox1.value; + navbox1.timestamp = millis(); + + memcpy(navbox2.title, NAVBOX2_TITLE, strlen(NAVBOX2_TITLE)); + navbox2.x = navbox1.width; + navbox2.y = navbox1.y; + navbox2.width = navbox1.width; + navbox2.height = navbox1.height; + navbox2.value = 0; + navbox2.prev_value = navbox2.value; + navbox2.timestamp = millis(); + + memcpy(navbox3.title, NAVBOX3_TITLE, strlen(NAVBOX3_TITLE)); + navbox3.x = navbox1.x; + navbox3.y = navbox1.y + navbox1.height; + navbox3.width = navbox1.width; + navbox3.height = navbox1.height; + navbox3.value = ThisAircraft.addr & 0xFFFFFF; + navbox3.prev_value = navbox3.value; + navbox3.timestamp = millis(); + + memcpy(navbox4.title, NAVBOX4_TITLE, strlen(NAVBOX4_TITLE)); + navbox4.x = navbox3.width; + navbox4.y = navbox3.y; + navbox4.width = navbox3.width; + navbox4.height = navbox3.height; + navbox4.value = ThisAircraft.protocol; + navbox4.prev_value = navbox4.value; + navbox4.timestamp = millis(); + + memcpy(navbox5.title, NAVBOX5_TITLE, strlen(NAVBOX5_TITLE)); + navbox5.x = navbox3.x; + navbox5.y = navbox3.y + navbox3.height; + navbox5.width = navbox3.width; + navbox5.height = navbox3.height; + navbox5.value = rx_packets_counter % 1000; + navbox5.prev_value = navbox5.value; + navbox5.timestamp = millis(); + + memcpy(navbox6.title, NAVBOX6_TITLE, strlen(NAVBOX6_TITLE)); + navbox6.x = navbox5.width; + navbox6.y = navbox5.y; + navbox6.width = navbox5.width; + navbox6.height = navbox5.height; + navbox6.value = tx_packets_counter % 1000; + navbox6.prev_value = navbox6.value; + navbox6.timestamp = millis(); +} + +static void EPD_Draw_NavBoxes() +{ + char buf[16]; + uint32_t disp_value; + + int16_t tbx, tby; + uint16_t tbw, tbh; + + uint16_t top_navboxes_x = navbox1.x; + uint16_t top_navboxes_y = navbox1.y; + uint16_t top_navboxes_w = navbox1.width + navbox2.width; + uint16_t top_navboxes_h = maxof2(navbox1.height, navbox2.height); + + display->setPartialWindow(top_navboxes_x, top_navboxes_y, + top_navboxes_w, top_navboxes_h); + + display->firstPage(); + do + { + display->drawRoundRect( navbox1.x + 1, navbox1.y + 1, + navbox1.width - 2, navbox1.height - 2, + 4, GxEPD_BLACK); + + display->drawRoundRect( navbox2.x + 1, navbox2.y + 1, + navbox2.width - 2, navbox2.height - 2, + 4, GxEPD_BLACK); + + display->setFont(&FreeMono9pt7b); + + display->getTextBounds(navbox1.title, 0, 0, &tbx, &tby, &tbw, &tbh); + display->setCursor(navbox1.x + 5, navbox1.y + 5 + tbh); + display->print(navbox1.title); + + display->getTextBounds(navbox2.title, 0, 0, &tbx, &tby, &tbw, &tbh); + display->setCursor(navbox2.x + 5, navbox2.y + 5 + tbh); + display->print(navbox2.title); + + display->setFont(&FreeMonoBold18pt7b); + + display->setCursor(navbox1.x + 60, navbox1.y + 52); + display->print(navbox1.value); + + display->setCursor(navbox2.x + 5, navbox2.y + 52); + display->print((float) navbox2.value / 10); + + } + while (display->nextPage()); + + yield(); + + uint16_t middle_navboxes_x = navbox3.x; + uint16_t middle_navboxes_y = navbox3.y; + uint16_t middle_navboxes_w = navbox3.width + navbox4.width; + uint16_t middle_navboxes_h = maxof2(navbox3.height, navbox4.height); + + + display->setPartialWindow(middle_navboxes_x, middle_navboxes_y, + middle_navboxes_w, middle_navboxes_h); + + display->firstPage(); + do + { + display->drawRoundRect( navbox3.x + 1, navbox3.y + 1, + navbox3.width - 2, navbox3.height - 2, + 4, GxEPD_BLACK); + display->drawRoundRect( navbox4.x + 1, navbox4.y + 1, + navbox4.width - 2, navbox4.height - 2, + 4, GxEPD_BLACK); + + display->setFont(&FreeMono9pt7b); + + display->getTextBounds(navbox3.title, 0, 0, &tbx, &tby, &tbw, &tbh); + display->setCursor(navbox3.x + 5, navbox3.y + 5 + tbh); + display->print(navbox3.title); + + display->getTextBounds(navbox4.title, 0, 0, &tbx, &tby, &tbw, &tbh); + display->setCursor(navbox4.x + 5, navbox4.y + 5 + tbh); + display->print(navbox4.title); + + display->setFont(&FreeSerifBold12pt7b); + + display->setCursor(navbox3.x + 10, navbox3.y + 50); + + snprintf(buf, sizeof(buf), "%06X", navbox3.value); + + display->print(buf); + + display->setFont(&FreeMonoBold18pt7b); + + display->setCursor(navbox4.x + 15, navbox4.y + 50); + display->print(EPD_Protocol_ID[navbox4.value]); + } + while (display->nextPage()); + + yield(); + + uint16_t bottom_navboxes_x = navbox5.x; + uint16_t bottom_navboxes_y = navbox5.y; + uint16_t bottom_navboxes_w = navbox5.width + navbox4.width; + uint16_t bottom_navboxes_h = maxof2(navbox5.height, navbox6.height); + + display->setPartialWindow(bottom_navboxes_x, bottom_navboxes_y, + bottom_navboxes_w, bottom_navboxes_h); + + display->firstPage(); + do + { + display->drawRoundRect( navbox5.x + 1, navbox5.y + 1, + navbox5.width - 2, navbox5.height - 2, + 4, GxEPD_BLACK); + display->drawRoundRect( navbox6.x + 1, navbox6.y + 1, + navbox6.width - 2, navbox6.height - 2, + 4, GxEPD_BLACK); + + display->setFont(&FreeMono9pt7b); + + display->getTextBounds(navbox5.title, 0, 0, &tbx, &tby, &tbw, &tbh); + display->setCursor(navbox5.x + 5, navbox5.y + 5 + tbh); + display->print(navbox5.title); + + display->getTextBounds(navbox6.title, 0, 0, &tbx, &tby, &tbw, &tbh); + display->setCursor(navbox6.x + 5, navbox6.y + 5 + tbh); + display->print(navbox6.title); + + display->setFont(&FreeMonoBold18pt7b); + + display->setCursor(navbox5.x + 25, navbox5.y + 50); + display->print(navbox5.value); + + display->setCursor(navbox6.x + 25, navbox6.y + 50); + display->print(navbox6.value); + } + while (display->nextPage()); + + display->hibernate(); +} + +static void EPD_Update_NavBoxes() +{ + char buf[16]; + int16_t tbx, tby; + uint16_t tbw, tbh; + + if (navbox1.value != navbox1.prev_value) { + + display->setFont(&FreeMonoBold18pt7b); + display->getTextBounds("00", 0, 0, &tbx, &tby, &tbw, &tbh); + display->setPartialWindow(navbox1.x + 60, navbox1.y + 53 - tbh, + tbw, tbh + 1); + display->firstPage(); + do + { + display->fillRect(navbox1.x + 60, navbox1.y + 53 - tbh, + tbw, tbh + 1, GxEPD_WHITE); + display->setCursor(navbox1.x + 60, navbox1.y + 52); + display->print(navbox1.value); + } + while (display->nextPage()); + + navbox1.prev_value = navbox1.value; + } + + if (navbox2.value != navbox2.prev_value) { + + display->setFont(&FreeMonoBold18pt7b); + display->getTextBounds("0.00", 0, 0, &tbx, &tby, &tbw, &tbh); + display->setPartialWindow(navbox2.x + 5, navbox2.y + 53 - tbh, + tbw, tbh + 1); + display->firstPage(); + do + { + display->fillRect(navbox2.x + 5, navbox2.y + 53 - tbh, + tbw, tbh + 1, GxEPD_WHITE); + display->setCursor(navbox2.x + 5, navbox2.y + 52); + display->print((float) navbox2.value / 10); + } + while (display->nextPage()); + + navbox2.prev_value = navbox2.value; + } + + if (navbox3.value != navbox3.prev_value) { + + display->setFont(&FreeSerifBold12pt7b); + display->getTextBounds("FFFFFF", 0, 0, &tbx, &tby, &tbw, &tbh); + display->setPartialWindow(navbox3.x + 11, navbox3.y + 51 - tbh, + tbw, tbh + 1); + display->firstPage(); + do + { + display->fillRect(navbox3.x + 10, navbox3.y + 51 - tbh, + tbw, tbh + 1, GxEPD_WHITE); + display->setCursor(navbox3.x + 10, navbox3.y + 50); + + snprintf(buf, sizeof(buf), "%06X", navbox3.value); + + display->print(buf); + } + while (display->nextPage()); + + navbox3.prev_value = navbox3.value; + } + + if (navbox4.value != navbox4.prev_value) { + + display->setFont(&FreeMonoBold18pt7b); + display->getTextBounds("UUU", 0, 0, &tbx, &tby, &tbw, &tbh); + display->setPartialWindow(navbox4.x + 16, navbox4.y + 51 - tbh, + tbw, tbh + 1); + display->firstPage(); + do + { + display->fillRect (navbox4.x + 15, navbox4.y + 51 - tbh, + tbw, tbh + 1, GxEPD_WHITE); + display->setCursor(navbox4.x + 15, navbox4.y + 50); + display->print(EPD_Protocol_ID[navbox4.value]); + } + while (display->nextPage()); + + navbox4.prev_value = navbox4.value; + } + + if (navbox5.value != navbox5.prev_value) { + + display->setFont(&FreeMonoBold18pt7b); + display->getTextBounds("000", 0, 0, &tbx, &tby, &tbw, &tbh); + display->setPartialWindow(navbox5.x + 26, navbox5.y + 51 - tbh, + tbw, tbh + 1); + display->firstPage(); + do + { + display->fillRect (navbox5.x + 25, navbox5.y + 51 - tbh, + tbw, tbh + 1, GxEPD_WHITE); + display->setCursor(navbox5.x + 25, navbox5.y + 50); + display->print(navbox5.value); + } + while (display->nextPage()); + + navbox5.prev_value = navbox5.value; + } + + if (navbox6.value != navbox6.prev_value) { + + display->setFont(&FreeSerifBold12pt7b); + display->getTextBounds("000", 0, 0, &tbx, &tby, &tbw, &tbh); + display->setPartialWindow(navbox6.x + 26, navbox6.y + 51 - tbh, + tbw, tbh + 1); + display->firstPage(); + do + { + display->fillRect(navbox6.x + 25, navbox6.y + 51 - tbh, + tbw, tbh + 1, GxEPD_WHITE); + display->setCursor(navbox6.x + 25, navbox6.y + 50); + display->print(navbox6.value); + } + while (display->nextPage()); + + navbox6.prev_value = navbox6.value; + } + + display->hibernate(); +} + +void EPD_status_loop() +{ + if (!EPD_display_frontpage) { + + EPD_Clear_Screen(); + + yield(); + + EPD_Draw_NavBoxes(); + + EPD_display_frontpage = true; + + } else { + + if (isTimeToDisplay()) { + + navbox1.value = Traffic_Count(); + navbox2.value = (int) (Battery_voltage() * 10.0); + navbox5.value = rx_packets_counter % 1000; + navbox6.value = tx_packets_counter % 1000; + + EPD_Update_NavBoxes(); + + EPDTimeMarker = millis(); + } + } +} + +void EPD_status_next() +{ + +} + +void EPD_status_prev() +{ + +} + +#endif /* ARDUINO_ARCH_NRF52 */ + +#endif /* USE_EPAPER */ diff --git a/software/firmware/source/SoftRF/View_Text_EPD.cpp b/software/firmware/source/SoftRF/View_Text_EPD.cpp new file mode 100644 index 000000000..05efcb5d6 --- /dev/null +++ b/software/firmware/source/SoftRF/View_Text_EPD.cpp @@ -0,0 +1,291 @@ +/* + * View_Text_EPD.cpp + * Copyright (C) 2019-2020 Linar Yusupov + * + * 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 3 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, see . + */ + +#include "SoCHelper.h" + +#if defined(USE_EPAPER) + +#if defined(ARDUINO_ARCH_NRF52) + +#include "EPDHelper.h" + +#include + +#include "TrafficHelper.h" +#include "EEPROMHelper.h" +#include "NMEAHelper.h" +#include "GDL90Helper.h" +#include "GNSSHelper.h" +#include "LEDHelper.h" + +#include + +static int EPD_current = 1; + +enum { + STATE_TVIEW_NONE, + STATE_TVIEW_TEXT, + STATE_TVIEW_NOFIX, + STATE_TVIEW_NODATA, + STATE_TVIEW_NOTRAFFIC +}; + +static int view_state_curr = STATE_TVIEW_NONE; +static int view_state_prev = STATE_TVIEW_NONE; + +static void EPD_Draw_Text() +{ + int j=0; + int bearing; + char info_line [TEXT_VIEW_LINE_LENGTH]; + char id_text [TEXT_VIEW_LINE_LENGTH]; + + for (int i=0; i < MAX_TRACKING_OBJECTS; i++) { + if (Container[i].addr && (now() - Container[i].timestamp) <= EPD_EXPIRATION_TIME) { + + traffic_by_dist[j].fop = &Container[i]; + traffic_by_dist[j].distance = Container[i].distance; + j++; + } + } + + if (j > 0) { + + uint8_t db; + const char *u_dist, *u_alt, *u_spd; + float disp_dist; + int disp_alt, disp_spd; + + qsort(traffic_by_dist, j, sizeof(traffic_by_dist_t), traffic_cmp_by_distance); + + if (EPD_current > j) { + EPD_current = j; + } + + bearing = (int) traffic_by_dist[EPD_current - 1].fop->bearing; + + /* This bearing is always relative to current ground track */ +// if (ui->orientation == DIRECTION_TRACK_UP) { + bearing -= ThisAircraft.course; +// } + + if (bearing < 0) { + bearing += 360; + } + + int oclock = ((bearing + 15) % 360) / 30; + float RelativeVertical = traffic_by_dist[EPD_current - 1].fop->altitude - + ThisAircraft.altitude; + + switch (ui->units) + { + case UNITS_IMPERIAL: + u_dist = "nm"; + u_alt = "f"; + u_spd = "kts"; + disp_dist = (traffic_by_dist[EPD_current - 1].distance * _GPS_MILES_PER_METER) / + _GPS_MPH_PER_KNOT; + disp_alt = abs((int) (RelativeVertical * _GPS_FEET_PER_METER)); + disp_spd = traffic_by_dist[EPD_current - 1].fop->speed; + break; + case UNITS_MIXED: + u_dist = "km"; + u_alt = "f"; + u_spd = "kph"; + disp_dist = traffic_by_dist[EPD_current - 1].distance / 1000.0; + disp_alt = abs((int) (RelativeVertical * _GPS_FEET_PER_METER)); + disp_spd = traffic_by_dist[EPD_current - 1].fop->speed * _GPS_KMPH_PER_KNOT; + break; + case UNITS_METRIC: + default: + u_dist = "km"; + u_alt = "m"; + u_spd = "kph"; + disp_dist = traffic_by_dist[EPD_current - 1].distance / 1000.0; + disp_alt = abs((int) RelativeVertical); + disp_spd = traffic_by_dist[EPD_current - 1].fop->speed * _GPS_KMPH_PER_KNOT; + break; + } + + uint32_t id = traffic_by_dist[EPD_current - 1].fop->addr; + + snprintf(id_text, sizeof(id_text), "ID: %06X", id); + + display->setPartialWindow(0, 0, display->width(), display->height()); + + display->setFont(&FreeMonoBold12pt7b); + + display->firstPage(); + do + { + uint16_t x = 5; + uint16_t y = 5; + + int16_t tbx, tby; + uint16_t tbw, tbh; + + display->fillScreen(GxEPD_WHITE); + +// Serial.println(); + + snprintf(info_line, sizeof(info_line), "Traffic %d/%d", EPD_current, j); + display->getTextBounds(info_line, 0, 0, &tbx, &tby, &tbw, &tbh); + y += tbh; + display->setCursor(x, y); + display->print(info_line); +// Serial.println(info_line); + + y += TEXT_VIEW_LINE_SPACING; + + if (oclock == 0) { + strcpy(info_line, " ahead"); + } else { + snprintf(info_line, sizeof(info_line), " %2d o'clock", oclock); + } + display->getTextBounds(info_line, 0, 0, &tbx, &tby, &tbw, &tbh); + y += tbh; + display->setCursor(x, y); + display->print(info_line); +// Serial.println(info_line); + + y += TEXT_VIEW_LINE_SPACING; + + snprintf(info_line, sizeof(info_line), "%4.1f %s out", disp_dist, u_dist); + display->getTextBounds(info_line, 0, 0, &tbx, &tby, &tbw, &tbh); + y += tbh; + display->setCursor(x, y); + display->print(info_line); +// Serial.println(info_line); + + y += TEXT_VIEW_LINE_SPACING; + + snprintf(info_line, sizeof(info_line), "%4d %s ", disp_alt, u_alt); + + if ((int) RelativeVertical > 50) { + strcat(info_line, "above"); + } else if ((int) RelativeVertical < -50) { + strcat(info_line, "below"); + } else { + strcpy(info_line, " same alt."); + } + + display->getTextBounds(info_line, 0, 0, &tbx, &tby, &tbw, &tbh); + y += tbh; + display->setCursor(x, y); + display->print(info_line); +// Serial.println(info_line); + + y += TEXT_VIEW_LINE_SPACING; + + snprintf(info_line, sizeof(info_line), "CoG %3d deg", + traffic_by_dist[EPD_current - 1].fop->course); + display->getTextBounds(info_line, 0, 0, &tbx, &tby, &tbw, &tbh); + y += tbh; + display->setCursor(x, y); + display->print(info_line); +// Serial.println(info_line); + + y += TEXT_VIEW_LINE_SPACING; + + snprintf(info_line, sizeof(info_line), "GS %3d %s", disp_spd, u_spd); + display->getTextBounds(info_line, 0, 0, &tbx, &tby, &tbw, &tbh); + y += tbh; + display->setCursor(x, y); + display->print(info_line); +// Serial.println(info_line); + + y += TEXT_VIEW_LINE_SPACING; + + display->getTextBounds(id_text, 0, 0, &tbx, &tby, &tbw, &tbh); + y += tbh; + display->setCursor(x, y); + display->print(id_text); +// Serial.println(id_text); + +// Serial.println(); + } + while (display->nextPage()); + + display->hibernate(); + } +} + +void EPD_text_setup() +{ + +} + +void EPD_text_loop() +{ + bool hasFix = isValidGNSSFix(); + + if (hasFix) { + if (Traffic_Count() > 0) { + view_state_curr = STATE_TVIEW_TEXT; + } else { + view_state_curr = STATE_TVIEW_NOTRAFFIC; + } + } else { + view_state_curr = STATE_TVIEW_NOFIX; + } + + if (EPD_vmode_updated) { + view_state_prev = STATE_TVIEW_NONE; + EPD_vmode_updated = false; + } + + if (view_state_curr != view_state_prev && + view_state_curr == STATE_TVIEW_NOFIX) { + EPD_Clear_Screen(); + EPD_Message(NO_FIX_TEXT, NULL); + view_state_prev = view_state_curr; + } + + if (view_state_curr != view_state_prev && + view_state_curr == STATE_TVIEW_NOTRAFFIC) { + EPD_Clear_Screen(); + EPD_Message("NO", "TRAFFIC"); + view_state_prev = view_state_curr; + } + + if (view_state_curr == STATE_TVIEW_TEXT) { + if (view_state_curr != view_state_prev) { + EPD_Clear_Screen(); + view_state_prev = view_state_curr; + } + EPD_Draw_Text(); + } +} + +void EPD_text_next() +{ + if (EPD_current < MAX_TRACKING_OBJECTS) { + EPD_current++; + } +} + +void EPD_text_prev() +{ + if (EPD_current > 1) { + EPD_current--; + } +} + +#endif /* ARDUINO_ARCH_NRF52 */ + +#endif /* USE_EPAPER */ diff --git a/software/firmware/source/SoftRF/View_Time_EPD.cpp b/software/firmware/source/SoftRF/View_Time_EPD.cpp new file mode 100644 index 000000000..70ae16f8b --- /dev/null +++ b/software/firmware/source/SoftRF/View_Time_EPD.cpp @@ -0,0 +1,95 @@ +/* + * View_Time_EPD.cpp + * Copyright (C) 2019-2020 Linar Yusupov + * + * 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 3 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, see . + */ + +#include "SoCHelper.h" + +#if defined(USE_EPAPER) + +#if defined(ARDUINO_ARCH_NRF52) + +#include "EPDHelper.h" +#include "TrafficHelper.h" +#include "BatteryHelper.h" +#include +#include + +#include + +const char NOTIME_text[] = "-- : -- : --"; +const char TZ_text[] = "UTC"; + +void EPD_time_setup() +{ + +} + +void EPD_time_loop() +{ + char buf[16]; + + int16_t tbx, tby; + uint16_t tbw, tbh; + + RTC_Date now; + + if (rtc) { + now = rtc->getDateTime(); + } + + if (now.year < 2019 || now.year > 2029) { + strcpy(buf, NOTIME_text); + } else { + snprintf(buf, sizeof(buf), "%2d : %02d : %02d", + now.hour, now.minute, now.second); + } + + display->setPartialWindow(0, 0, display->width(), display->height()); + display->setFont(&FreeMonoBold12pt7b); + display->getTextBounds(buf, 0, 0, &tbx, &tby, &tbw, &tbh); + + display->firstPage(); + do + { + display->fillScreen(GxEPD_WHITE); + + display->setCursor((display->width() - tbw) / 2, (display->height() - tbh) / 2); + display->print(buf); + + display->getTextBounds(TZ_text, 0, 0, &tbx, &tby, &tbw, &tbh); + + display->setCursor((display->width() - tbw) / 2, (2 * display->height()) / 3); + display->print(TZ_text); + } + while (display->nextPage()); + + display->hibernate(); +} + +void EPD_time_next() +{ + +} + +void EPD_time_prev() +{ + +} + +#endif /* ARDUINO_ARCH_NRF52 */ + +#endif /* USE_EPAPER */ diff --git a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.cpp b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.cpp index c76fae608..d6cf7cd7f 100644 --- a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.cpp +++ b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.cpp @@ -19,6 +19,8 @@ #if defined(ARDUINO_ARCH_NRF52) && (SPI_INTERFACES_COUNT >= 2) #define SPI SPI1 #endif /* ARDUINO_ARCH_NRF52 */ +#else +#define SPI SPI0 #endif /* RASPBERRY_PI */ #endif diff --git a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.h b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.h index a72bf1728..3fc00ec7b 100644 --- a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.h +++ b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.h @@ -16,7 +16,6 @@ #include #include #else -#define SPI SPI0 #include #endif /* RASPBERRY_PI */ diff --git a/software/firmware/source/libraries/arduino-basicmac/src/hal/target-config.h b/software/firmware/source/libraries/arduino-basicmac/src/hal/target-config.h index 2fb8d9f22..ead5c5c55 100644 --- a/software/firmware/source/libraries/arduino-basicmac/src/hal/target-config.h +++ b/software/firmware/source/libraries/arduino-basicmac/src/hal/target-config.h @@ -77,6 +77,12 @@ #endif #endif +#if defined(ARDUINO_ARCH_NRF52) +#ifdef CFG_DEBUG +#undef CFG_DEBUG +#endif +#endif + #if defined(CFG_eu868) enum _dr_eu868_t { DR_SF12=0, DR_SF11, DR_SF10, DR_SF9, DR_SF8, DR_SF7, DR_SF7B, DR_FSK, DR_NONE };