diff --git a/tracker/software/cfg/pp10a/chconf.h b/tracker/software/cfg/pp10a/chconf.h index 6c1f56f6..aafad186 100644 --- a/tracker/software/cfg/pp10a/chconf.h +++ b/tracker/software/cfg/pp10a/chconf.h @@ -29,7 +29,6 @@ #define _CHCONF_H_ #define _CHIBIOS_RT_CONF_ -#define _CHIBIOS_RT_CONF_VER_5_0_ #define _CHIBIOS_RT_CONF_VER_6_0_ /*===========================================================================*/ diff --git a/tracker/software/cfg/pp10a/portab.c b/tracker/software/cfg/pp10a/portab.c index 3e12d2b2..ad2b7ff0 100644 --- a/tracker/software/cfg/pp10a/portab.c +++ b/tracker/software/cfg/pp10a/portab.c @@ -86,10 +86,12 @@ const SerialConfig debug_config = { /*===========================================================================*/ void pktConfigSerialDiag(void) { +#if ENABLE_EXTERNAL_I2C == FALSE /* USART3 TX. */ palSetLineMode(LINE_USART3_TX, PAL_MODE_ALTERNATE(7)); /* USART3 RX. */ palSetLineMode(LINE_USART3_RX, PAL_MODE_ALTERNATE(7)); +#endif } void pktConfigSerialPkt(void) { diff --git a/tracker/software/cfg/pp10a/portab.h b/tracker/software/cfg/pp10a/portab.h index 6f856683..2573d81c 100644 --- a/tracker/software/cfg/pp10a/portab.h +++ b/tracker/software/cfg/pp10a/portab.h @@ -81,7 +81,7 @@ //#define LINE_UART4_RX PAL_LINE(GPIOA, 11U) /* The external port can be used for bit bang I2C. */ -#define ENABLE_EXTERNAL_I2C FALSE +#define ENABLE_EXTERNAL_I2C TRUE #if ENABLE_EXTERNAL_I2C == FALSE #define LINE_USART3_TX LINE_IO_TXD diff --git a/tracker/software/cfg/pp10b/chconf.h b/tracker/software/cfg/pp10b/chconf.h index 6c1f56f6..aafad186 100644 --- a/tracker/software/cfg/pp10b/chconf.h +++ b/tracker/software/cfg/pp10b/chconf.h @@ -29,7 +29,6 @@ #define _CHCONF_H_ #define _CHIBIOS_RT_CONF_ -#define _CHIBIOS_RT_CONF_VER_5_0_ #define _CHIBIOS_RT_CONF_VER_6_0_ /*===========================================================================*/ diff --git a/tracker/software/source/config/config.c b/tracker/software/source/config/config.c index 60c59db8..e3444b14 100644 --- a/tracker/software/source/config/config.c +++ b/tracker/software/source/config/config.c @@ -12,7 +12,7 @@ conf_t conf_sram; const conf_t conf_flash_default = { // Primary position app .pos_pri = { - .thread_conf = { + .svc_conf = { .active = false, .cycle = TIME_S2I(60 * 5), .init_delay = TIME_S2I(30) @@ -29,12 +29,12 @@ const conf_t conf_flash_default = { .symbol = SYM_ANTENNA, .aprs_msg = true, // How often to send telemetry config - .tel_enc_cycle = TIME_S2I(60 * 60) + //.tel_enc_cycle = TIME_S2I(60 * 60) }, // Secondary position app .pos_sec = { - .thread_conf = { + .svc_conf = { .active = false, .cycle = TIME_S2I(180), .init_delay = TIME_S2I(60) @@ -51,12 +51,12 @@ const conf_t conf_flash_default = { .symbol = SYM_BALLOON, .aprs_msg = true, - .tel_enc_cycle = TIME_S2I(0) + //.tel_enc_cycle = TIME_S2I(0) }, // Primary image app .img_pri = { - .thread_conf = { + .svc_conf = { .active = false, .cycle = CYCLE_CONTINUOUSLY, .init_delay = TIME_S2I(90), @@ -82,7 +82,7 @@ const conf_t conf_flash_default = { // Secondary image app .img_sec = { - .thread_conf = { + .svc_conf = { .active = false, .cycle = TIME_S2I(60 * 30), .init_delay = TIME_S2I(60 * 1), @@ -107,7 +107,7 @@ const conf_t conf_flash_default = { // Log app .log = { - .thread_conf = { + .svc_conf = { .active = false, .cycle = TIME_S2I(10), .init_delay = TIME_S2I(5) @@ -126,14 +126,12 @@ const conf_t conf_flash_default = { // APRS app .aprs = { - .thread_conf = { + .svc_conf = { // The packet receive service is enabled if true // Receive is resumed after any transmission .active = true, .init_delay = TIME_S2I(20) }, - // The default APRS frequency when geofence is not resolved - .freq = APRS_FREQ_AUSTRALIA, // The receive identity for APRS .rx = { // Receive radio configuration @@ -156,48 +154,48 @@ const conf_t conf_flash_default = { .mod = MOD_AFSK, .cca = 0x4F }, - // Digipeat identity + .beacon = { + .svc_conf = { + // The telemetry beacon service is enabled if true + // Receive is resumed after any transmission + .active = true, + .init_delay = TIME_S2I(20), + .cycle = TIME_S2I(60 * 30), // Beacon interval + }, + .fixed = true, // Use fixed position data if true + .lat = -337331175, // Degrees (expressed in 1e-7 form) + .lon = 1511143478, // Degrees (expressed in 1e-7 form) + .alt = 144, // Altitude in metres + }, + // Digipeat transmission identity .call = "VK2GJ-5", .path = "WIDE2-1", - .symbol = SYM_DIGIPEATER, - // Set to have digi beacon position, telemetry & APRSD information. - // This starts a BCN thread specifically for digi - .beacon = true, - .cycle = TIME_S2I(60 * 30), // Beacon interval - // Set true to have digi use GPS for position - // If valid position is not stored then default lat, lon and alt will be used. - // If RTC time is invalid then GPS will be enabled to get time. - // Once RTC is set then GPS is released and can be switched off. - // This will be the case if no other position thread is using GPS. - .gps = true, - // How often to send telemetry config (TODO: Move out to global level) - .tel_enc_cycle = TIME_S2I(60 * 60 * 2) - }, - // The base station identity - .base = { - // Tracker originated messages will be sent to this call sign if enabled - .enabled = true, - .call = "VK2GJ-7", - .path = "WIDE2-1", + .symbol = SYM_DIGIPEATER }, }, // Global controls // Power control .keep_cam_switched_on = false, - .gps_on_vbat = 1000, // mV - .gps_off_vbat = 1000, // mV - .gps_onper_vbat = 1000, // mV + .gps_on_vbat = 3800, // mV + .gps_off_vbat = 3600, // mV + .gps_onper_vbat = 4000, // mV // GPS altitude model control (air pressure determined by on-board BME280) .gps_pressure = 90000, // Air pressure (Pa) threshold for alt model switch .gps_low_alt = GPS_STATIONARY, .gps_high_alt = GPS_AIRBORNE_1G, - // A pre-set location if GPS never enabled or unable to acquire lock. - .lat = -337331175, // Degrees (expressed in 1e-7 form) - .lon = 1511143478, // Degrees (expressed in 1e-7 form) - .alt = 144, // Altitude in metres - + // How often to send telemetry config + .tel_enc_cycle = TIME_S2I(60 * 60 * 2), + // The default APRS frequency when geofence is not resolved + .freq = APRS_FREQ_AUSTRALIA, + // The base station identity + .base = { + // Tracker originated messages will be addressed to this call sign if enabled + .enabled = true, + .call = "VK2GJ-7", + .path = "WIDE2-1", + }, .magic = CONFIG_MAGIC_DEFAULT // Do not remove. This is the activation bit. }; diff --git a/tracker/software/source/config/types.h b/tracker/software/source/config/types.h index 695bcd18..0c978a10 100644 --- a/tracker/software/source/config/types.h +++ b/tracker/software/source/config/types.h @@ -1,238 +1,223 @@ -#ifndef __TYPES_H__ -#define __TYPES_H__ - -#include "ch.h" -#include "ax25_pad.h" -#include "ublox.h" - -typedef enum { -FREQ_RADIO_INVALID = 0, -FREQ_APRS_DYNAMIC, /* Geofencing frequency (144.8 default). */ -FREQ_APRS_SCAN, /* Frequency last found in RX scan. - TBI */ -FREQ_APRS_RECEIVE, /* Active RX frequency - fall back to DYNAMIC. */ -FREQ_CMDC_RECEIVE, /* Frequency used for command and control. TBI */ -FREQ_APRS_DEFAULT, /* Default frequency specified in configuration */ -FREQ_CODES_END -} freq_codes_t; - -#define FREQ_RADIO_INVALID 0 -#define FREQ_APRS_DYNAMIC 1 /* Geofencing frequency (144.8 default). */ -#define FREQ_APRS_SCAN 2 /* Frequency based on band base + channel scan. */ -#define FREQ_APRS_RECEIVE 3 /* Active RX frequency - fall back to DYNAMIC. */ -#define FREQ_CMDC_RECEIVE 4 /* Frequency used for command and control. TBI */ -#define FREQ_APRS_DEFAULT 5 /* Default frequency specified in configuration */ -#define FREQ_CODES_END 6 - -#define CYCLE_CONTINUOUSLY 0 - -#define TYPE_NULL 0 -#define TYPE_INT 1 -#define TYPE_TIME 2 -#define TYPE_STR 3 - -typedef enum { - SLEEP_DISABLED = 0, - SLEEP_WHEN_VBAT_BELOW_THRES, - SLEEP_WHEN_VSOL_BELOW_THRES, - SLEEP_WHEN_VBAT_ABOVE_THRES, - SLEEP_WHEN_VSOL_ABOVE_THRES, - SLEEP_WHEN_DISCHARGING, - SLEEP_WHEN_CHARGING -} sleep_type_t; - -typedef struct { - sleep_type_t type; - volt_level_t vbat_thres; - volt_level_t vsol_thres; -} sleep_conf_t; - -typedef enum { // Modulation type - MOD_NONE, - MOD_AFSK, - MOD_2FSK -} mod_t; - -typedef enum { - RES_NONE = 0, - RES_QQVGA, - RES_QVGA, - RES_VGA, - RES_VGA_ZOOMED, - RES_XGA, - RES_UXGA, - RES_MAX -} resolution_t; - -/*typedef union { - radio_squelch_t cca; - radio_squelch_t rssi; -} radio_sq_t;*/ - -typedef struct { - radio_pwr_t pwr; - radio_freq_t freq; - mod_t mod; - link_speed_t speed; - union { - radio_squelch_t cca; - radio_squelch_t rssi; - }; -} radio_conf_t; - -typedef struct { - radio_pwr_t pwr; - radio_freq_t freq; - mod_t mod; - link_speed_t speed; - radio_squelch_t cca; -} radio_tx_conf_t; // Radio / Modulation - -typedef struct { - radio_freq_t freq; - mod_t mod; - link_speed_t speed; - radio_squelch_t rssi; -} radio_rx_conf_t; // Radio / Modulation - -typedef struct { - bool active; - sysinterval_t init_delay; - sysinterval_t send_spacing; - sleep_conf_t sleep_conf; - sysinterval_t cycle; // Cycle time (0: continously) - sysinterval_t duration; -} thread_conf_t; // Thread - -typedef struct { - thread_conf_t thread_conf; - radio_tx_conf_t radio_conf; - - // Protocol - char call[AX25_MAX_ADDR_LEN]; - char path[16]; - aprs_sym_t symbol; - bool aprs_msg; - sysinterval_t tel_enc_cycle; -} thd_pos_conf_t; - -typedef struct { - thread_conf_t thread_conf; - radio_tx_conf_t radio_conf; - bool redundantTx; - // Protocol - char call[AX25_MAX_ADDR_LEN]; - char path[16]; - - resolution_t res; // Picture resolution - uint8_t quality; // SSDV Quality ranging from 0-7 - uint32_t buf_size; // SRAM buffer size for the picture -} thd_img_conf_t; - -typedef struct { - thread_conf_t thread_conf; - radio_tx_conf_t radio_conf; - - // Protocol - char call[AX25_MAX_ADDR_LEN]; - char path[16]; - - uint8_t density; // Density of log points being sent out in 1/x (value 10 => 10%) -} thd_log_conf_t; - - - -typedef struct { - radio_rx_conf_t radio_conf; - aprs_sym_t symbol; - // Protocol - char call[AX25_MAX_ADDR_LEN]; -} thd_rx_conf_t; - -typedef struct { - radio_tx_conf_t radio_conf; - - // Protocol - char call[AX25_MAX_ADDR_LEN]; - char path[16]; - aprs_sym_t symbol; -} thd_tx_conf_t; - -typedef struct { - radio_tx_conf_t radio_conf; - - // Protocol - char call[AX25_MAX_ADDR_LEN]; - char path[16]; - aprs_sym_t symbol; - bool enabled; -} thd_base_conf_t; - -typedef struct { - bool active; // Digipeater active flag - radio_tx_conf_t radio_conf; - - // Protocol - char call[AX25_MAX_ADDR_LEN]; - char path[16]; - aprs_sym_t symbol; - bool enabled; - bool beacon; - bool gps; -/* gps_coord_t lat; - gps_coord_t lon; - gps_alt_t alt;*/ - sysinterval_t cycle; // Beacon interval (0: continously) - sysinterval_t tel_enc_cycle; - -} thd_digi_conf_t; - -/* APRS configuration. */ -typedef struct { - thread_conf_t thread_conf; - thd_rx_conf_t rx; - thd_digi_conf_t digi; - // Base station call sign for receipt of tracker initiated sends - // These are sends by the tracker which are not in response to a query. - thd_base_conf_t base; - // Default APRS frequency if geolocation is not available (GPS offline) - radio_freq_t freq; -} thd_aprs_conf_t; - -typedef struct { - thd_pos_conf_t pos_pri; // Primary position thread configuration - thd_pos_conf_t pos_sec; // Secondary position thread configuration - - thd_img_conf_t img_pri; // Primary image thread configuration - thd_img_conf_t img_sec; // Secondary image thread configuration - - thd_log_conf_t log; // Log transmission configuration - thd_aprs_conf_t aprs; - - bool keep_cam_switched_on; // Keep camera switched on and initialized, this makes image capturing faster but takes a lot of power over long time - - volt_level_t gps_on_vbat; // Battery voltage threshold at which GPS is switched on - volt_level_t gps_off_vbat; // Battery voltage threshold at which GPS is switched off - volt_level_t gps_onper_vbat; // Battery voltage threshold at which GPS is kept switched on all time. This value must be larger - // When gps_on_vbat and gps_off_vbat otherwise this value has no effect. Value 0 disables this feature - uint32_t gps_pressure; // Air pressure below which GPS is switched to airborne mode - gps_hp_model_t gps_low_alt; // Model to use when air pressure is above gps_pa_threshold - gps_lp_model_t gps_high_alt; // Model to use when air pressure is below gps_pa_threshold - - // Default lat, lon and alt when GPS is not enabled or not operable - gps_coord_t lat; - gps_coord_t lon; - gps_alt_t alt; - - uint32_t magic; // Key that indicates if the flash is loaded or has been updated - uint16_t crc; // CRC to verify content -} conf_t; - -typedef struct { - uint8_t type; - char name[64]; - size_t size; - void *ptr; -} conf_command_t; - -#endif /* __TYPES_H__ */ - +#ifndef __TYPES_H__ +#define __TYPES_H__ + +#include "ch.h" +#include "ax25_pad.h" +#include "ublox.h" + +typedef enum { +FREQ_RADIO_INVALID = 0, +FREQ_APRS_DYNAMIC, /* Geofencing frequency (144.8 default). */ +FREQ_APRS_SCAN, /* Frequency last found in RX scan. - TBI */ +FREQ_APRS_RECEIVE, /* Active RX frequency - fall back to DYNAMIC. */ +FREQ_CMDC_RECEIVE, /* Frequency used for command and control. TBI */ +FREQ_APRS_DEFAULT, /* Default frequency specified in configuration */ +FREQ_CODES_END +} freq_codes_t; + +#define FREQ_RADIO_INVALID 0 +#define FREQ_APRS_DYNAMIC 1 /* Geofencing frequency (144.8 default). */ +#define FREQ_APRS_SCAN 2 /* Frequency based on band base + channel scan. */ +#define FREQ_APRS_RECEIVE 3 /* Active RX frequency - fall back to DYNAMIC. */ +#define FREQ_CMDC_RECEIVE 4 /* Frequency used for command and control. TBI */ +#define FREQ_APRS_DEFAULT 5 /* Default frequency specified in configuration */ +#define FREQ_CODES_END 6 + +#define CYCLE_CONTINUOUSLY 0 + +#define TYPE_NULL 0 +#define TYPE_INT 1 +#define TYPE_TIME 2 +#define TYPE_STR 3 + +typedef enum { + SLEEP_DISABLED = 0, + SLEEP_WHEN_VBAT_BELOW_THRES, + SLEEP_WHEN_VSOL_BELOW_THRES, + SLEEP_WHEN_VBAT_ABOVE_THRES, + SLEEP_WHEN_VSOL_ABOVE_THRES, + SLEEP_WHEN_DISCHARGING, + SLEEP_WHEN_CHARGING +} sleep_type_t; + +typedef struct { + sleep_type_t type; + volt_level_t vbat_thres; + volt_level_t vsol_thres; +} sleep_conf_t; + +typedef enum { // Modulation type + MOD_NONE, + MOD_AFSK, + MOD_2FSK +} mod_t; + +typedef enum { + RES_NONE = 0, + RES_QQVGA, + RES_QVGA, + RES_VGA, + RES_VGA_ZOOMED, + RES_XGA, + RES_UXGA, + RES_MAX +} resolution_t; + +typedef struct { + radio_pwr_t pwr; + radio_freq_t freq; + mod_t mod; + link_speed_t speed; + union { + radio_squelch_t cca; + radio_squelch_t rssi; + }; +} radio_conf_t; + +typedef struct { + radio_pwr_t pwr; + radio_freq_t freq; + mod_t mod; + link_speed_t speed; + radio_squelch_t cca; +} radio_tx_conf_t; // Radio / Modulation + +typedef struct { + radio_freq_t freq; + mod_t mod; + link_speed_t speed; + radio_squelch_t rssi; +} radio_rx_conf_t; // Radio / Modulation + +typedef struct { + bool active; + sysinterval_t init_delay; + sysinterval_t send_spacing; + sleep_conf_t sleep_conf; + sysinterval_t cycle; // Cycle time (0: continuously) + sysinterval_t duration; +} thread_conf_t; // Thread + +typedef struct { + thread_conf_t svc_conf; + radio_tx_conf_t radio_conf; + // Protocol + char call[AX25_MAX_ADDR_LEN]; + char path[16]; + aprs_sym_t symbol; + bool aprs_msg; + // Default lat, lon and alt when fixed is enabled + bool fixed; + gps_coord_t lat; + gps_coord_t lon; + gps_alt_t alt; +} thd_pos_conf_t; + +typedef struct { + thread_conf_t svc_conf; + radio_tx_conf_t radio_conf; + bool redundantTx; + // Protocol + char call[AX25_MAX_ADDR_LEN]; + char path[16]; + resolution_t res; // Picture resolution + uint8_t quality; // SSDV Quality ranging from 0-7 + uint32_t buf_size; // SRAM buffer size for the picture +} thd_img_conf_t; + +typedef struct { + thread_conf_t svc_conf; + radio_tx_conf_t radio_conf; + // Protocol + char call[AX25_MAX_ADDR_LEN]; + char path[16]; + uint8_t density; // Density of log points being sent out in 1/x (value 10 => 10%) +} thd_log_conf_t; + +typedef struct { + radio_rx_conf_t radio_conf; + aprs_sym_t symbol; + // Protocol + char call[AX25_MAX_ADDR_LEN]; +} thd_rx_conf_t; + +typedef struct { + radio_tx_conf_t radio_conf; + // Protocol + char call[AX25_MAX_ADDR_LEN]; + char path[16]; + aprs_sym_t symbol; +} thd_tx_conf_t; + +typedef struct { + radio_tx_conf_t radio_conf; + // Protocol + char call[AX25_MAX_ADDR_LEN]; + char path[16]; + aprs_sym_t symbol; + bool enabled; +} thd_base_conf_t; + +typedef struct { + bool active; // Digipeater active flag + radio_tx_conf_t radio_conf; + // Protocol + char call[AX25_MAX_ADDR_LEN]; + char path[16]; + aprs_sym_t symbol; + thd_pos_conf_t beacon; + //sysinterval_t tel_enc_cycle; +} thd_digi_conf_t; + +/* APRS configuration. */ +typedef struct { + thread_conf_t svc_conf; + thd_rx_conf_t rx; + thd_digi_conf_t digi; + // Base station call sign for receipt of tracker initiated sends + // These are sends by the tracker which are not in response to a query. + //thd_base_conf_t base; + // Default APRS frequency if geolocation is not available (GPS offline) + //radio_freq_t freq; +} thd_aprs_conf_t; + +typedef struct { + thd_pos_conf_t pos_pri; // Primary position thread configuration + thd_pos_conf_t pos_sec; // Secondary position thread configuration + + thd_img_conf_t img_pri; // Primary image thread configuration + thd_img_conf_t img_sec; // Secondary image thread configuration + + thd_log_conf_t log; // Log transmission configuration + thd_aprs_conf_t aprs; + + bool keep_cam_switched_on; // Keep camera switched on and initialized, this makes image capturing faster but takes a lot of power over long time + + volt_level_t gps_on_vbat; // Battery voltage threshold at which GPS is switched on + volt_level_t gps_off_vbat; // Battery voltage threshold at which GPS is switched off + volt_level_t gps_onper_vbat; // Battery voltage threshold at which GPS is kept switched on all time. This value must be larger + // When gps_on_vbat and gps_off_vbat otherwise this value has no effect. Value 0 disables this feature + uint32_t gps_pressure; // Air pressure below which GPS is switched to airborne mode + gps_hp_model_t gps_low_alt; // Model to use when air pressure is above gps_pa_threshold + gps_lp_model_t gps_high_alt; // Model to use when air pressure is below gps_pa_threshold + + //APRS global + sysinterval_t tel_enc_cycle; // Cycle for sending of telemetry config headers + radio_freq_t freq; // Default APRS frequency if geolocation not available + // Base station call sign for receipt of tracker initiated sends + // These are sends by the tracker which are not in response to a query. + thd_base_conf_t base; + + uint32_t magic; // Key that indicates if the flash is loaded or has been updated + uint16_t crc; // CRC to verify content +} conf_t; + +typedef struct { + uint8_t type; + char name[64]; + size_t size; + void *ptr; +} conf_command_t; + +#endif /* __TYPES_H__ */ + diff --git a/tracker/software/source/drivers/ublox.c b/tracker/software/source/drivers/ublox.c index 449e3fe5..69e35d34 100644 --- a/tracker/software/source/drivers/ublox.c +++ b/tracker/software/source/drivers/ublox.c @@ -1,650 +1,649 @@ -/** - * @see https://github.com/thasti/utrak - */ - -#include "ch.h" -#include "hal.h" - -#include "ublox.h" -#include "pi2c.h" -#include "debug.h" -#include "config.h" -#include "collector.h" - -bool gps_enabled = false; -int8_t gps_model = GPS_MODEL_UNSET; - -#if defined(UBLOX_USE_UART) -// Serial driver configuration for GPS -const SerialConfig gps_config = -{ - 9600, // baud rate - 0, // CR1 register - 0, // CR2 register - 0 // CR3 register -}; -#endif - -/** - * Transmits a string of bytes to the GPS - */ -void gps_transmit_string(uint8_t *cmd, uint8_t length) { - gps_calc_ubx_csum(cmd, length); -#if UBLOX_USE_I2C == TRUE - I2C_writeN(UBLOX_MAX_ADDRESS, cmd, length); -#elif defined(UBLOX_USE_UART) - sdWrite(&SD5, cmd, length); -#endif -} - -/** - * Receives a single byte from the GPS and assigns to supplied pointer. - * Returns false is there is no byte available else true - */ -bool gps_receive_byte(uint8_t *data) { -#if UBLOX_USE_I2C == TRUE - uint16_t len; - I2C_read16(UBLOX_MAX_ADDRESS, 0xFD, &len); - if(len) { - I2C_read8(UBLOX_MAX_ADDRESS, 0xFF, data); - return true; - } -#else - if((*data = sdGetTimeout(&SD5, TIME_IMMEDIATE)) != MSG_TIMEOUT) { - return true; - } -#endif - return false; -} - -/** - * gps_receive_ack - * - * waits for transmission of an ACK/NAK message from the GPS. - * - * returns 1 if ACK was received, 0 if NAK was received or timeout - * - */ -uint8_t gps_receive_ack(uint8_t class_id, uint8_t msg_id, uint16_t timeout) { - int match_count = 0; - int msg_ack = 0; - uint8_t rx_byte; - uint8_t ack[] = {0xB5, 0x62, 0x05, 0x01, 0x02, 0x00, 0x00, 0x00}; - uint8_t nak[] = {0xB5, 0x62, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00}; - ack[6] = class_id; - nak[6] = class_id; - ack[7] = msg_id; - nak[7] = msg_id; - - // runs until ACK/NAK packet is received - sysinterval_t sTimeout = chVTGetSystemTimeX() + TIME_MS2I(timeout); - while(sTimeout >= chVTGetSystemTimeX()) { - - // Receive one byte - if(!gps_receive_byte(&rx_byte)) { - chThdSleep(TIME_MS2I(10)); - continue; - } - - // Process one byte - if (rx_byte == ack[match_count] || rx_byte == nak[match_count]) { - if (match_count == 3) { /* test ACK/NAK byte */ - if (rx_byte == ack[match_count]) { - msg_ack = 1; - } else { - msg_ack = 0; - } - } - if (match_count == 7) { - return msg_ack; - } - match_count++; - } else { - match_count = 0; - } - - } - - return 0; -} - -/** - * gps_receive_payload - * - * retrieves the payload of a packet with a given class and message-id with the retrieved length. - * the caller has to ensure suitable buffer length! - * - * returns the length of the payload - * - */ -uint16_t gps_receive_payload(uint8_t class_id, uint8_t msg_id, - unsigned char *payload, size_t size, - uint16_t timeout) { - uint8_t rx_byte; - enum {UBX_A, UBX_B, CLASSID, MSGID, LEN_A, LEN_B, PAYLOAD} state = UBX_A; - uint16_t payload_cnt = 0; - uint16_t payload_len = 0; - - sysinterval_t sNow = chVTGetSystemTime(); - - while(chVTIsSystemTimeWithin(sNow, sNow + TIME_MS2I(timeout))) { - - // Receive one byte - if(!gps_receive_byte(&rx_byte)) { - chThdSleep(TIME_MS2I(1)); - continue; - } - - // Process one byte - switch (state) { - case UBX_A: - if(rx_byte == 0xB5) state = UBX_B; - //else state = UBX_A; - break; - case UBX_B: - if(rx_byte == 0x62) state = CLASSID; - else state = UBX_A; - break; - case CLASSID: - if(rx_byte == class_id) state = MSGID; - else state = UBX_A; - break; - case MSGID: - if(rx_byte == msg_id) state = LEN_A; - else state = UBX_A; - break; - case LEN_A: - payload_len = rx_byte; - state = LEN_B; - break; - case LEN_B: - payload_len |= ((uint16_t)rx_byte << 8); - state = PAYLOAD; - break; - case PAYLOAD: - payload[payload_cnt++] = rx_byte; - //payload_cnt++; - if(payload_cnt == payload_len) - return payload_len; - if(payload_cnt > size) - return 0; - break; - default: - state = UBX_A; - } - } - return 0; -} - -/** - * gps_get_sv_info - * - * - */ -bool gps_get_sv_info(gps_svinfo_t *svinfo, size_t size) { - if(!gps_enabled) - return false; - - // Transmit request - uint8_t navsvinfo_req[] = {0xB5, 0x62, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00}; - gps_transmit_string(navsvinfo_req, sizeof(navsvinfo_req)); - - if(!gps_receive_payload(0x01, 0x30, (unsigned char*)svinfo, size, 3000)) { // Receive request - TRACE_ERROR("GPS > NAV-SVINFO Polling FAILED"); - return false; - } - return true; -} - -/** - * gps_get_fix - * - * retrieves a GPS fix from the module. - * if validity flag is not set, date/time and position/altitude are - * assumed not to be reliable! - * - */ -bool gps_get_fix(gpsFix_t *fix) { - static uint8_t navpvt[128]; - static uint8_t navstatus[32]; - - // Transmit request - uint8_t navpvt_req[] = {0xB5, 0x62, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00}; - gps_transmit_string(navpvt_req, sizeof(navpvt_req)); - - if(!gps_receive_payload(0x01, 0x07, navpvt, sizeof(navpvt), 3000)) { // Receive request - TRACE_ERROR("GPS > NAV-PVT Polling FAILED"); - return false; - } - - uint8_t navstatus_req[] = {0xB5, 0x62, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00}; - gps_transmit_string(navstatus_req, sizeof(navstatus_req)); - - if(!gps_receive_payload(0x01, 0x03, navstatus, sizeof(navstatus), 3000)) { // Receive request - TRACE_ERROR("GPS > NAV-STATUS Polling FAILED"); - return false; - } - - // Extract data from message - fix->fixOK = navstatus[5] & 0x1; - fix->pdop = navpvt[76] + (navpvt[77] << 8); - - fix->num_svs = navpvt[23]; - fix->type = navpvt[20]; - - fix->time.year = navpvt[4] + (navpvt[5] << 8); - fix->time.month = navpvt[6]; - fix->time.day = navpvt[7]; - fix->time.hour = navpvt[8]; - fix->time.minute = navpvt[9]; - fix->time.second = navpvt[10]; - - fix->lat = (int32_t) ( - (uint32_t)(navpvt[28]) - + ((uint32_t)(navpvt[29]) << 8) - + ((uint32_t)(navpvt[30]) << 16) - + ((uint32_t)(navpvt[31]) << 24) - ); - fix->lon = (int32_t) ( - (uint32_t)(navpvt[24]) - + ((uint32_t)(navpvt[25]) << 8) - + ((uint32_t)(navpvt[26]) << 16) - + ((uint32_t)(navpvt[27]) << 24) - ); - int32_t alt_tmp = (((int32_t) - ((uint32_t)(navpvt[36]) - + ((uint32_t)(navpvt[37]) << 8) - + ((uint32_t)(navpvt[38]) << 16) - + ((uint32_t)(navpvt[39]) << 24)) - ) / 1000); - if (alt_tmp <= 0) { - fix->alt = 1; - } else if (alt_tmp > 50000) { - fix->alt = 50000; - } else { - fix->alt = (uint16_t)alt_tmp; - } -/* }*/ - TRACE_INFO("GPS > Polling OK time=%04d-%02d-%02d %02d:%02d:%02d lat=%d.%05d lon=%d.%05d alt=%dm sats=%d fixOK=%d pDOP=%02d.%02d model=%d", - fix->time.year, fix->time.month, fix->time.day, fix->time.hour, fix->time.minute, fix->time.second, - fix->lat/10000000, (fix->lat > 0 ? 1:-1)*(fix->lat/100)%100000, fix->lon/10000000, (fix->lon > 0 ? 1:-1)*(fix->lon/100)%100000, - fix->alt, fix->num_svs, fix->fixOK, fix->pdop/100, fix->pdop%100, gps_model - ); - - return true; -} - -/** - * gps_disable_nmea_output - * - * disables all NMEA messages to be output from the GPS. - * even though the parser can cope with NMEA messages and ignores them, it - * may save power to disable them completely. - * - * returns ACK/NAK result - * - */ -uint8_t gps_disable_nmea_output(void) { - uint8_t nonmea[] = { - 0xB5, 0x62, 0x06, 0x00, 20, 0x00, // UBX-CFG-PRT - 0x01, 0x00, 0x00, 0x00, // UART1, reserved, no TX ready - 0xe0, 0x08, 0x00, 0x00, // UART mode (8N1) - 0x80, 0x25, 0x00, 0x00, // UART baud rate (9600) - 0x01, 0x00, // input protocols (uBx only) - 0x01, 0x00, // output protocols (uBx only) - 0x00, 0x00, // flags - 0x00, 0x00, // reserved - 0x00, 0x00 // CRC place holders - }; - - gps_transmit_string(nonmea, sizeof(nonmea)); - return gps_receive_ack(0x06, 0x00, 1000); -} - -/** - * gps_set_stationary_model - * - * tells the GPS to use the stationary positioning model. - * - * returns ACK/NAK result - * - */ -uint8_t gps_set_stationary_model(void) { - uint8_t model6[] = { - 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 - 0xFF, 0xFF, // parameter bitmask - GPS_MODEL_STATIONARY, // dynamic model - 0x03, // fix mode - 0x00, 0x00, 0x00, 0x00, // 2D fix altitude - 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance - 0x05, // minimum elevation - 0x00, // reserved - 0xFA, 0x00, // position DOP - 0xFA, 0x00, // time DOP - 0x64, 0x00, // position accuracy - 0x2C, 0x01, // time accuracy - 0x00, // static hold threshold - 0x3C, // DGPS timeout - 0x00, // min. SVs above C/No thresh - 0x00, // C/No threshold - 0x00, 0x00, // reserved - 0xc8, 0x00, // static hold max. distance - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00 // CRC place holders - }; - - gps_transmit_string(model6, sizeof(model6)); - return gps_receive_ack(0x06, 0x24, 1000); -} - -/** - * gps_set_low_alt_model - * - * tells the GPS to use the low altitude positioning model. - * - * returns ACK/NAK result - * - */ -uint8_t gps_set_low_alt_model(gps_hp_model_t model) { - uint8_t model6[] = { - 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 - 0xFF, 0xFF, // parameter bitmask - model, // dynamic model - 0x03, // fix mode - 0x00, 0x00, 0x00, 0x00, // 2D fix altitude - 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance - 0x05, // minimum elevation - 0x00, // reserved - 0xFA, 0x00, // position DOP - 0xFA, 0x00, // time DOP - 0x64, 0x00, // position accuracy - 0x2C, 0x01, // time accuracy - 0x00, // static hold threshold - 0x3C, // DGPS timeout - 0x00, // min. SVs above C/No thresh - 0x00, // C/No threshold - 0x00, 0x00, // reserved - 0xc8, 0x00, // static hold max. distance - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00 // CRC place holders - }; - - gps_transmit_string(model6, sizeof(model6)); - return gps_receive_ack(0x06, 0x24, 1000); -} - -/** - * gps_set_high_alt_model - * - * tells the GPS to use the high altitude positioning model. - * - * returns ACK/NAK result - * - */ -uint8_t gps_set_high_alt_model(gps_lp_model_t model) { - uint8_t model6[] = { - 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 - 0xFF, 0xFF, // parameter bitmask - model, // dynamic model - 0x03, // fix mode - 0x00, 0x00, 0x00, 0x00, // 2D fix altitude - 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance - 0x05, // minimum elevation - 0x00, // reserved - 0xFA, 0x00, // position DOP - 0xFA, 0x00, // time DOP - 0x64, 0x00, // position accuracy - 0x2C, 0x01, // time accuracy - 0x00, // static hold threshold - 0x3C, // DGPS timeout - 0x00, // min. SVs above C/No thresh - 0x00, // C/No threshold - 0x00, 0x00, // reserved - 0xc8, 0x00, // static hold max. distance - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00 // CRC place holders - }; - - gps_transmit_string(model6, sizeof(model6)); - return gps_receive_ack(0x06, 0x24, 1000); -} - -/** - * gps_set_portable_model - * - * tells the GPS to use the portable positioning model. - * - * returns ACK/NAK result - * - */ -uint8_t gps_set_portable_model(void) { - uint8_t model6[] = { - 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 - 0xFF, 0xFF, // parameter bitmask - GPS_MODEL_PORTABLE, // dynamic model - 0x03, // fix mode - 0x00, 0x00, 0x00, 0x00, // 2D fix altitude - 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance - 0x05, // minimum elevation - 0x00, // reserved - 0xFA, 0x00, // position DOP - 0xFA, 0x00, // time DOP - 0x64, 0x00, // position accuracy - 0x2C, 0x01, // time accuracy - 0x00, // static hold threshold - 0x3C, // DGPS timeout - 0x00, // min. SVs above C/No thresh - 0x00, // C/No threshold - 0x00, 0x00, // reserved - 0xc8, 0x00, // static hold max. distance - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00 // CRC place holders - }; - - gps_transmit_string(model6, sizeof(model6)); - return gps_receive_ack(0x06, 0x24, 1000); -} - -/** - * gps_set_airborne_model - * - * tells the GPS to use the airborne positioning model. Should be used to - * get stable lock up to 50km altitude - * - * returns ACK/NAK result - * - */ -uint8_t gps_set_airborne_model(void) { - uint8_t model6[] = { - 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 - 0xFF, 0xFF, // parameter bitmask - GPS_MODEL_AIRBORNE1G, // dynamic model - 0x03, // fix mode - 0x00, 0x00, 0x00, 0x00, // 2D fix altitude - 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance - 0x05, // minimum elevation - 0x00, // reserved - 0xFA, 0x00, // position DOP - 0xFA, 0x00, // time DOP - 0x64, 0x00, // position accuracy - 0x2C, 0x01, // time accuracy - 0x00, // static hold threshold - 0x3C, // DGPS timeout - 0x00, // min. SVs above C/No thresh - 0x00, // C/No threshold - 0x00, 0x00, // reserved - 0xc8, 0x00, // static hold max. distance - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00 // CRC place holders - }; - - gps_transmit_string(model6, sizeof(model6)); - return gps_receive_ack(0x06, 0x24, 1000); -} - -/** - * gps_set_power_save - * - * enables cyclic tracking on the uBlox M8Q - * - * returns ACK/NAK result - * - */ -uint8_t gps_set_power_save(void) { - uint8_t powersave[] = { - 0xB5, 0x62, 0x06, 0x3B, 44, 0, // UBX-CFG-PM2 - 0x01, 0x00, 0x00, 0x00, // v1, reserved 1..3 - 0x00, 0b00010000, 0b00000010, 0x00, // cyclic tracking, update ephemeris - 0x10, 0x27, 0x00, 0x00, // update period, ms - 0x10, 0x27, 0x00, 0x00, // search period, ms - 0x00, 0x00, 0x00, 0x00, // grid offset - 0x00, 0x00, // on-time after first fix - 0x01, 0x00, // minimum acquisition time - 0x00, 0x00, 0x00, 0x00, // reserved 4,5 - 0x00, 0x00, 0x00, 0x00, // reserved 6 - 0x00, 0x00, 0x00, 0x00, // reserved 7 - 0x00, 0x00, 0x00, 0x00, // reserved 8,9,10 - 0x00, 0x00, 0x00, 0x00, // reserved 11 - 0x00, 0x00 // CRC place holders - }; - - gps_transmit_string(powersave, sizeof(powersave)); - return gps_receive_ack(0x06, 0x3B, 1000); -} - -/** - * gps_power_save - * - * enables or disables the power save mode (which was configured before) - * - * returns ACK/NAK result - */ -uint8_t gps_power_save(int on) { - uint8_t recvmgmt[] = { - 0xB5, 0x62, 0x06, 0x11, 2, 0, // UBX-CFG-RXM - 0x08, on ? 0x01 : 0x00, // reserved, enable power save mode - 0x00, 0x00 // CRC place holders - }; - - gps_transmit_string(recvmgmt, sizeof(recvmgmt)); - return gps_receive_ack(0x06, 0x11, 1000); -} - -/** - * gps_set_model - * - * Selects nav model based on air pressure - */ -bool gps_set_model(bool dynamic) { - uint8_t cntr; - bool status; - - dataPoint_t tp; - getSensors(&tp); - setSystemStatus(&tp); - if(dynamic && ((tp.sys_error & BMEI1_STATUS_MASK) == BME_OK_VALUE) - && (tp.sen_i1_press/10 > conf_sram.gps_pressure)) { - if(gps_model == conf_sram.gps_low_alt) - return true; - /* Set low altitude model. */ - cntr = 3; - while((status = - gps_set_low_alt_model(conf_sram.gps_low_alt)) == false && cntr--); - if(status) { - gps_model = conf_sram.gps_low_alt; - return true; - } - TRACE_ERROR("GPS > Communication Error [set low altitude model]"); - return false; - } /* Else use high altitude specified or BMEi1 not functional. */ - - /* Default to high altitude model. */ - if(gps_model == conf_sram.gps_high_alt) - return true; - cntr = 3; - while((status = - gps_set_high_alt_model(conf_sram.gps_high_alt)) == false && cntr--); - if(status) { - gps_model = conf_sram.gps_high_alt; - return true; - } - TRACE_ERROR("GPS > Communication Error [set high altitude model]"); - return false; -} - -/* - * - */ -bool GPS_Init() { - // Initialize pins - TRACE_INFO("GPS > Init GPS pins"); - gps_enabled = true; - palSetLineMode(LINE_GPS_RESET, PAL_MODE_OUTPUT_PUSHPULL); // GPS reset - palSetLineMode(LINE_GPS_EN, PAL_MODE_OUTPUT_PUSHPULL); // GPS off - - - // Init UART - #if defined(UBLOX_USE_UART) - palSetLineMode(LINE_GPS_RXD, PAL_MODE_ALTERNATE(11)); // UART RXD - palSetLineMode(LINE_GPS_TXD, PAL_MODE_ALTERNATE(11)); // UART TXD - TRACE_INFO("GPS > Init GPS UART"); - sdStart(&SD5, &gps_config); - #endif - - // Switch MOSFET - TRACE_INFO("GPS > Power up GPS"); - palSetLine(LINE_GPS_RESET); // Pull up GPS reset - palSetLine(LINE_GPS_EN); // Switch on GPS - - // Wait for GPS startup - chThdSleep(TIME_S2I(1)); - - gps_model = GPS_MODEL_UNSET; - // Configure GPS - TRACE_INFO("GPS > Transmit config to GPS"); - - uint8_t cntr = 3; - bool status; - while((status = gps_disable_nmea_output()) == false && cntr--); - if(status) { - TRACE_INFO("GPS > ... Disable NMEA output OK"); - } else { - TRACE_ERROR("GPS > Communication Error [disable NMEA]"); - return false; - } - return true; -} - -/* - * - */ -void GPS_Deinit(void) -{ - // Switch MOSFET - TRACE_INFO("GPS > Power down GPS"); - palClearLine(LINE_GPS_EN); - gps_model = GPS_MODEL_UNSET; - gps_enabled = false; -} - -/* - * Calculate checksum and inserts into buffer. - * Calling function must allocate space in message buff for csum. - * - */ -bool gps_calc_ubx_csum(uint8_t *mbuf, uint16_t mlen) { - - uint16_t i; - uint8_t ck_a = 0, ck_b = 0; - /* Counting sync bytes there must be at least one byte to checksum. */ - if(mlen < 5) - return false; - - for (i = 2; i < mlen - 2; i++) { - ck_b += (ck_a += mbuf[i]); - } - mbuf[mlen - 2] = ck_a; - mbuf[mlen - 1] = ck_b; - -return true; -} +/** + * @see https://github.com/thasti/utrak + */ + +#include "ch.h" +#include "hal.h" + +#include "ublox.h" +#include "pi2c.h" +#include "debug.h" +#include "config.h" +#include "collector.h" + +bool gps_enabled = false; +int8_t gps_model = GPS_MODEL_UNSET; + +#if defined(UBLOX_USE_UART) +// Serial driver configuration for GPS +const SerialConfig gps_config = +{ + 9600, // baud rate + 0, // CR1 register + 0, // CR2 register + 0 // CR3 register +}; +#endif + +/** + * Transmits a string of bytes to the GPS + */ +void gps_transmit_string(uint8_t *cmd, uint8_t length) { + gps_calc_ubx_csum(cmd, length); +#if UBLOX_USE_I2C == TRUE + I2C_writeN(UBLOX_MAX_ADDRESS, cmd, length); +#elif defined(UBLOX_USE_UART) + sdWrite(&SD5, cmd, length); +#endif +} + +/** + * Receives a single byte from the GPS and assigns to supplied pointer. + * Returns false is there is no byte available else true + */ +bool gps_receive_byte(uint8_t *data) { +#if UBLOX_USE_I2C == TRUE + uint16_t len; + I2C_read16(UBLOX_MAX_ADDRESS, 0xFD, &len); + if(len) { + I2C_read8(UBLOX_MAX_ADDRESS, 0xFF, data); + return true; + } +#else + if((*data = sdGetTimeout(&SD5, TIME_IMMEDIATE)) != MSG_TIMEOUT) { + return true; + } +#endif + return false; +} + +/** + * gps_receive_ack + * + * waits for transmission of an ACK/NAK message from the GPS. + * + * returns 1 if ACK was received, 0 if NAK was received or timeout + * + */ +uint8_t gps_receive_ack(uint8_t class_id, uint8_t msg_id, uint16_t timeout) { + int match_count = 0; + int msg_ack = 0; + uint8_t rx_byte; + uint8_t ack[] = {0xB5, 0x62, 0x05, 0x01, 0x02, 0x00, 0x00, 0x00}; + uint8_t nak[] = {0xB5, 0x62, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00}; + ack[6] = class_id; + nak[6] = class_id; + ack[7] = msg_id; + nak[7] = msg_id; + + // runs until ACK/NAK packet is received + sysinterval_t sTimeout = chVTGetSystemTimeX() + TIME_MS2I(timeout); + while(sTimeout >= chVTGetSystemTimeX()) { + + // Receive one byte + if(!gps_receive_byte(&rx_byte)) { + chThdSleep(TIME_MS2I(10)); + continue; + } + + // Process one byte + if (rx_byte == ack[match_count] || rx_byte == nak[match_count]) { + if (match_count == 3) { /* test ACK/NAK byte */ + if (rx_byte == ack[match_count]) { + msg_ack = 1; + } else { + msg_ack = 0; + } + } + if (match_count == 7) { + return msg_ack; + } + match_count++; + } else { + match_count = 0; + } + + } + + return 0; +} + +/** + * gps_receive_payload + * + * retrieves the payload of a packet with a given class and message-id with the retrieved length. + * the caller has to ensure suitable buffer length! + * + * returns the length of the payload + * + */ +uint16_t gps_receive_payload(uint8_t class_id, uint8_t msg_id, + unsigned char *payload, size_t size, + uint16_t timeout) { + uint8_t rx_byte; + enum {UBX_A, UBX_B, CLASSID, MSGID, LEN_A, LEN_B, PAYLOAD} state = UBX_A; + uint16_t payload_cnt = 0; + uint16_t payload_len = 0; + + sysinterval_t sNow = chVTGetSystemTime(); + + while(chVTIsSystemTimeWithin(sNow, sNow + TIME_MS2I(timeout))) { + + // Receive one byte + if(!gps_receive_byte(&rx_byte)) { + chThdSleep(TIME_MS2I(1)); + continue; + } + + // Process one byte + switch (state) { + case UBX_A: + if(rx_byte == 0xB5) state = UBX_B; + //else state = UBX_A; + break; + case UBX_B: + if(rx_byte == 0x62) state = CLASSID; + else state = UBX_A; + break; + case CLASSID: + if(rx_byte == class_id) state = MSGID; + else state = UBX_A; + break; + case MSGID: + if(rx_byte == msg_id) state = LEN_A; + else state = UBX_A; + break; + case LEN_A: + payload_len = rx_byte; + state = LEN_B; + break; + case LEN_B: + payload_len |= ((uint16_t)rx_byte << 8); + state = PAYLOAD; + break; + case PAYLOAD: + payload[payload_cnt++] = rx_byte; + //payload_cnt++; + if(payload_cnt == payload_len) + return payload_len; + if(payload_cnt > size) + return 0; + break; + default: + state = UBX_A; + } + } + return 0; +} + +/** + * gps_get_sv_info + * + * + */ +bool gps_get_sv_info(gps_svinfo_t *svinfo, size_t size) { + if(!gps_enabled) + return false; + + // Transmit request + uint8_t navsvinfo_req[] = {0xB5, 0x62, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00}; + gps_transmit_string(navsvinfo_req, sizeof(navsvinfo_req)); + + if(!gps_receive_payload(0x01, 0x30, (unsigned char*)svinfo, size, 3000)) { // Receive request + TRACE_ERROR("GPS > NAV-SVINFO Polling FAILED"); + return false; + } + return true; +} + +/** + * gps_get_fix + * + * retrieves a GPS fix from the module. + * if validity flag is not set, date/time and position/altitude are + * assumed not to be reliable! + * + */ +bool gps_get_fix(gpsFix_t *fix) { + static uint8_t navpvt[128]; + static uint8_t navstatus[32]; + + // Transmit request + uint8_t navpvt_req[] = {0xB5, 0x62, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00}; + gps_transmit_string(navpvt_req, sizeof(navpvt_req)); + + if(!gps_receive_payload(0x01, 0x07, navpvt, sizeof(navpvt), 3000)) { // Receive request + TRACE_ERROR("GPS > NAV-PVT Polling FAILED"); + return false; + } + + uint8_t navstatus_req[] = {0xB5, 0x62, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00}; + gps_transmit_string(navstatus_req, sizeof(navstatus_req)); + + if(!gps_receive_payload(0x01, 0x03, navstatus, sizeof(navstatus), 3000)) { // Receive request + TRACE_ERROR("GPS > NAV-STATUS Polling FAILED"); + return false; + } + + // Extract data from message + fix->fixOK = navstatus[5] & 0x1; + fix->pdop = navpvt[76] + (navpvt[77] << 8); + + fix->num_svs = navpvt[23]; + fix->type = navpvt[20]; + + fix->time.year = navpvt[4] + (navpvt[5] << 8); + fix->time.month = navpvt[6]; + fix->time.day = navpvt[7]; + fix->time.hour = navpvt[8]; + fix->time.minute = navpvt[9]; + fix->time.second = navpvt[10]; + + fix->lat = (int32_t) ( + (uint32_t)(navpvt[28]) + + ((uint32_t)(navpvt[29]) << 8) + + ((uint32_t)(navpvt[30]) << 16) + + ((uint32_t)(navpvt[31]) << 24) + ); + fix->lon = (int32_t) ( + (uint32_t)(navpvt[24]) + + ((uint32_t)(navpvt[25]) << 8) + + ((uint32_t)(navpvt[26]) << 16) + + ((uint32_t)(navpvt[27]) << 24) + ); + int32_t alt_tmp = (((int32_t) + ((uint32_t)(navpvt[36]) + + ((uint32_t)(navpvt[37]) << 8) + + ((uint32_t)(navpvt[38]) << 16) + + ((uint32_t)(navpvt[39]) << 24)) + ) / 1000); + if (alt_tmp <= 0) { + fix->alt = 1; + } else if (alt_tmp > 50000) { + fix->alt = 50000; + } else { + fix->alt = (uint16_t)alt_tmp; + } +/* }*/ + TRACE_INFO("GPS > Polling OK time=%04d-%02d-%02d %02d:%02d:%02d lat=%d.%05d lon=%d.%05d alt=%dm sats=%d fixOK=%d pDOP=%02d.%02d model=%d", + fix->time.year, fix->time.month, fix->time.day, fix->time.hour, fix->time.minute, fix->time.second, + fix->lat/10000000, (fix->lat > 0 ? 1:-1)*(fix->lat/100)%100000, fix->lon/10000000, (fix->lon > 0 ? 1:-1)*(fix->lon/100)%100000, + fix->alt, fix->num_svs, fix->fixOK, fix->pdop/100, fix->pdop%100, gps_model + ); + + return true; +} + +/** + * gps_disable_nmea_output + * + * disables all NMEA messages to be output from the GPS. + * even though the parser can cope with NMEA messages and ignores them, it + * may save power to disable them completely. + * + * returns ACK/NAK result + * + */ +uint8_t gps_disable_nmea_output(void) { + uint8_t nonmea[] = { + 0xB5, 0x62, 0x06, 0x00, 20, 0x00, // UBX-CFG-PRT + 0x01, 0x00, 0x00, 0x00, // UART1, reserved, no TX ready + 0xe0, 0x08, 0x00, 0x00, // UART mode (8N1) + 0x80, 0x25, 0x00, 0x00, // UART baud rate (9600) + 0x01, 0x00, // input protocols (uBx only) + 0x01, 0x00, // output protocols (uBx only) + 0x00, 0x00, // flags + 0x00, 0x00, // reserved + 0x00, 0x00 // CRC place holders + }; + + gps_transmit_string(nonmea, sizeof(nonmea)); + return gps_receive_ack(0x06, 0x00, 1000); +} + +/** + * gps_set_stationary_model + * + * tells the GPS to use the stationary positioning model. + * + * returns ACK/NAK result + * + */ +uint8_t gps_set_stationary_model(void) { + uint8_t model6[] = { + 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 + 0xFF, 0xFF, // parameter bitmask + GPS_MODEL_STATIONARY, // dynamic model + 0x03, // fix mode + 0x00, 0x00, 0x00, 0x00, // 2D fix altitude + 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance + 0x05, // minimum elevation + 0x00, // reserved + 0xFA, 0x00, // position DOP + 0xFA, 0x00, // time DOP + 0x64, 0x00, // position accuracy + 0x2C, 0x01, // time accuracy + 0x00, // static hold threshold + 0x3C, // DGPS timeout + 0x00, // min. SVs above C/No thresh + 0x00, // C/No threshold + 0x00, 0x00, // reserved + 0xc8, 0x00, // static hold max. distance + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00 // CRC place holders + }; + + gps_transmit_string(model6, sizeof(model6)); + return gps_receive_ack(0x06, 0x24, 1000); +} + +/** + * gps_set_low_alt_model + * + * tells the GPS to use the low altitude positioning model. + * + * returns ACK/NAK result + * + */ +uint8_t gps_set_low_alt_model(gps_hp_model_t model) { + uint8_t model6[] = { + 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 + 0xFF, 0xFF, // parameter bitmask + model, // dynamic model + 0x03, // fix mode + 0x00, 0x00, 0x00, 0x00, // 2D fix altitude + 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance + 0x05, // minimum elevation + 0x00, // reserved + 0xFA, 0x00, // position DOP + 0xFA, 0x00, // time DOP + 0x64, 0x00, // position accuracy + 0x2C, 0x01, // time accuracy + 0x00, // static hold threshold + 0x3C, // DGPS timeout + 0x00, // min. SVs above C/No thresh + 0x00, // C/No threshold + 0x00, 0x00, // reserved + 0xc8, 0x00, // static hold max. distance + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00 // CRC place holders + }; + + gps_transmit_string(model6, sizeof(model6)); + return gps_receive_ack(0x06, 0x24, 1000); +} + +/** + * gps_set_high_alt_model + * + * tells the GPS to use the high altitude positioning model. + * + * returns ACK/NAK result + * + */ +uint8_t gps_set_high_alt_model(gps_lp_model_t model) { + uint8_t model6[] = { + 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 + 0xFF, 0xFF, // parameter bitmask + model, // dynamic model + 0x03, // fix mode + 0x00, 0x00, 0x00, 0x00, // 2D fix altitude + 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance + 0x05, // minimum elevation + 0x00, // reserved + 0xFA, 0x00, // position DOP + 0xFA, 0x00, // time DOP + 0x64, 0x00, // position accuracy + 0x2C, 0x01, // time accuracy + 0x00, // static hold threshold + 0x3C, // DGPS timeout + 0x00, // min. SVs above C/No thresh + 0x00, // C/No threshold + 0x00, 0x00, // reserved + 0xc8, 0x00, // static hold max. distance + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00 // CRC place holders + }; + + gps_transmit_string(model6, sizeof(model6)); + return gps_receive_ack(0x06, 0x24, 1000); +} + +/** + * gps_set_portable_model + * + * tells the GPS to use the portable positioning model. + * + * returns ACK/NAK result + * + */ +uint8_t gps_set_portable_model(void) { + uint8_t model6[] = { + 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 + 0xFF, 0xFF, // parameter bitmask + GPS_MODEL_PORTABLE, // dynamic model + 0x03, // fix mode + 0x00, 0x00, 0x00, 0x00, // 2D fix altitude + 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance + 0x05, // minimum elevation + 0x00, // reserved + 0xFA, 0x00, // position DOP + 0xFA, 0x00, // time DOP + 0x64, 0x00, // position accuracy + 0x2C, 0x01, // time accuracy + 0x00, // static hold threshold + 0x3C, // DGPS timeout + 0x00, // min. SVs above C/No thresh + 0x00, // C/No threshold + 0x00, 0x00, // reserved + 0xc8, 0x00, // static hold max. distance + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00 // CRC place holders + }; + + gps_transmit_string(model6, sizeof(model6)); + return gps_receive_ack(0x06, 0x24, 1000); +} + +/** + * gps_set_airborne_model + * + * tells the GPS to use the airborne positioning model. Should be used to + * get stable lock up to 50km altitude + * + * returns ACK/NAK result + * + */ +uint8_t gps_set_airborne_model(void) { + uint8_t model6[] = { + 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, // UBX-CFG-NAV5 + 0xFF, 0xFF, // parameter bitmask + GPS_MODEL_AIRBORNE1G, // dynamic model + 0x03, // fix mode + 0x00, 0x00, 0x00, 0x00, // 2D fix altitude + 0x10, 0x27, 0x00, 0x00, // 2D fix altitude variance + 0x05, // minimum elevation + 0x00, // reserved + 0xFA, 0x00, // position DOP + 0xFA, 0x00, // time DOP + 0x64, 0x00, // position accuracy + 0x2C, 0x01, // time accuracy + 0x00, // static hold threshold + 0x3C, // DGPS timeout + 0x00, // min. SVs above C/No thresh + 0x00, // C/No threshold + 0x00, 0x00, // reserved + 0xc8, 0x00, // static hold max. distance + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00 // CRC place holders + }; + + gps_transmit_string(model6, sizeof(model6)); + return gps_receive_ack(0x06, 0x24, 1000); +} + +/** + * gps_set_power_save + * + * enables cyclic tracking on the uBlox M8Q + * + * returns ACK/NAK result + * + */ +uint8_t gps_set_power_save(void) { + uint8_t powersave[] = { + 0xB5, 0x62, 0x06, 0x3B, 44, 0, // UBX-CFG-PM2 + 0x01, 0x00, 0x00, 0x00, // v1, reserved 1..3 + 0x00, 0b00010000, 0b00000010, 0x00, // cyclic tracking, update ephemeris + 0x10, 0x27, 0x00, 0x00, // update period, ms + 0x10, 0x27, 0x00, 0x00, // search period, ms + 0x00, 0x00, 0x00, 0x00, // grid offset + 0x00, 0x00, // on-time after first fix + 0x01, 0x00, // minimum acquisition time + 0x00, 0x00, 0x00, 0x00, // reserved 4,5 + 0x00, 0x00, 0x00, 0x00, // reserved 6 + 0x00, 0x00, 0x00, 0x00, // reserved 7 + 0x00, 0x00, 0x00, 0x00, // reserved 8,9,10 + 0x00, 0x00, 0x00, 0x00, // reserved 11 + 0x00, 0x00 // CRC place holders + }; + + gps_transmit_string(powersave, sizeof(powersave)); + return gps_receive_ack(0x06, 0x3B, 1000); +} + +/** + * gps_power_save + * + * enables or disables the power save mode (which was configured before) + * + * returns ACK/NAK result + */ +uint8_t gps_power_save(int on) { + uint8_t recvmgmt[] = { + 0xB5, 0x62, 0x06, 0x11, 2, 0, // UBX-CFG-RXM + 0x08, on ? 0x01 : 0x00, // reserved, enable power save mode + 0x00, 0x00 // CRC place holders + }; + + gps_transmit_string(recvmgmt, sizeof(recvmgmt)); + return gps_receive_ack(0x06, 0x11, 1000); +} + +/** + * gps_set_model + * + * Selects nav model based on air pressure + */ +bool gps_set_model(bool dynamic) { + uint8_t cntr; + bool status; + + dataPoint_t tp; + getSensors(&tp); + setSystemStatus(&tp); + if(dynamic && ((tp.sys_error & BMEI1_STATUS_MASK) == BME_OK_VALUE) + && (tp.sen_i1_press/10 > conf_sram.gps_pressure)) { + if(gps_model == conf_sram.gps_low_alt) + return true; + /* Set low altitude model. */ + cntr = 3; + while((status = + gps_set_low_alt_model(conf_sram.gps_low_alt)) == false && cntr--); + if(status) { + gps_model = conf_sram.gps_low_alt; + return true; + } + TRACE_ERROR("GPS > Communication Error [set low altitude model]"); + return false; + } /* Else use high altitude specified or BMEi1 not functional. */ + + /* Default to high altitude model. */ + if(gps_model == conf_sram.gps_high_alt) + return true; + cntr = 3; + while((status = + gps_set_high_alt_model(conf_sram.gps_high_alt)) == false && cntr--); + if(status) { + gps_model = conf_sram.gps_high_alt; + return true; + } + TRACE_ERROR("GPS > Communication Error [set high altitude model]"); + return false; +} + +/* + * + */ +bool GPS_Init() { + // Initialize pins + TRACE_INFO("GPS > Init GPS pins"); + palSetLineMode(LINE_GPS_RESET, PAL_MODE_OUTPUT_PUSHPULL); // GPS reset + palSetLineMode(LINE_GPS_EN, PAL_MODE_OUTPUT_PUSHPULL); // GPS off + + // Init UART + #if defined(UBLOX_USE_UART) + palSetLineMode(LINE_GPS_RXD, PAL_MODE_ALTERNATE(11)); // UART RXD + palSetLineMode(LINE_GPS_TXD, PAL_MODE_ALTERNATE(11)); // UART TXD + TRACE_INFO("GPS > Init GPS UART"); + sdStart(&SD5, &gps_config); + #endif + + // Switch MOSFET + TRACE_INFO("GPS > Power up GPS"); + palSetLine(LINE_GPS_RESET); // Pull up GPS reset + palSetLine(LINE_GPS_EN); // Switch on GPS + + // Wait for GPS startup + chThdSleep(TIME_S2I(1)); + + gps_model = GPS_MODEL_UNSET; + // Configure GPS + TRACE_INFO("GPS > Transmit config to GPS"); + + uint8_t cntr = 3; + bool status; + while((status = gps_disable_nmea_output()) == false && cntr--); + if(status) { + TRACE_INFO("GPS > ... Disable NMEA output OK"); + } else { + TRACE_ERROR("GPS > Communication Error [disable NMEA]"); + return false; + } + gps_enabled = true; + return true; +} + +/* + * + */ +void GPS_Deinit(void) +{ + // Switch MOSFET + TRACE_INFO("GPS > Power down GPS"); + palClearLine(LINE_GPS_EN); + gps_model = GPS_MODEL_UNSET; + gps_enabled = false; +} + +/* + * Calculate checksum and inserts into buffer. + * Calling function must allocate space in message buff for csum. + * + */ +bool gps_calc_ubx_csum(uint8_t *mbuf, uint16_t mlen) { + + uint16_t i; + uint8_t ck_a = 0, ck_b = 0; + /* Counting sync bytes there must be at least one byte to checksum. */ + if(mlen < 5) + return false; + + for (i = 2; i < mlen - 2; i++) { + ck_b += (ck_a += mbuf[i]); + } + mbuf[mlen - 2] = ck_a; + mbuf[mlen - 1] = ck_b; + +return true; +} diff --git a/tracker/software/source/math/geofence.c b/tracker/software/source/math/geofence.c index 13fc7dcb..7b6a1065 100644 --- a/tracker/software/source/math/geofence.c +++ b/tracker/software/source/math/geofence.c @@ -1,701 +1,701 @@ -/** - * Geofencing algorithm and geofencing data - */ - -#include "ch.h" -#include "hal.h" -#include "geofence.h" -#include "collector.h" -#include "config.h" - -static const coord_t america[] = { - // Latitude Longitude (in deg*10000000) - { 602803500,-1800000000}, - {-250833370,-1800000000}, - {-350000000,-1725193600}, - {-444551600,-1725356300}, - {-510311600,-1800000000}, - {-624631500,-1800000000}, - {-622917200, -805076100}, - {-593631200, -532159100}, - {-613268000, -216598500}, - {-523586500, -216531900}, - {-158237300, -216398800}, - { -26137000, -256562300}, - { 116521800, -379076800}, - { 250055500, -471176200}, - { 437701700, -474320100}, - { 529477400, -475106100}, - { 592874700, -556751500}, - { 649970500, -579417200}, - { 677211100, -595630600}, - { 703992100, -625906400}, - { 748557600, -742708000}, - { 900000000, -749253300}, - { 900000000,-1800000000}, - { 751492400,-1800000000}, - { 684708700,-1696228400}, - { 655163500,-1697060800}, - { 641285100,-1733928400} -}; -static const coord_t china[] = { - // Latitude Longitude (in deg*10000000) - {451738100, 825642300}, - {452352200, 819625200}, - {453544900, 817013800}, - {451756000, 809593900}, - {449717800, 799587900}, - {446579400, 803864200}, - {440643900, 804054400}, - {434967200, 806881200}, - {431645300, 806192500}, - {428305300, 803306500}, - {421086600, 801075700}, - {410720900, 778157100}, - {410197600, 769014100}, - {405847000, 766682700}, - {403815200, 762593500}, - {404379000, 749752300}, - {395074200, 736814000}, - {371897300, 748730300}, - {315530900, 791019900}, - {301780600, 820775400}, - {283590400, 859320000}, - {279363900, 892463900}, - {284084300, 897228300}, - {279579500, 916425000}, - {286829100, 946908200}, - {278888600, 977112800}, - {240215100, 974360200}, - {212269300,1010279300}, - {226236400,1052672100}, - {207971800,1073531600}, - {174983800,1071305300}, - {161966200,1121345000}, - {203309900,1169129400}, - {209227300,1244251400}, - {236120900,1277185900}, - {261685400,1256067700}, - {326694600,1240792500}, - {349869300,1233594400}, - {373804200,1239579800}, - {385913100,1232100700}, - {390679600,1240433600}, - {397587600,1312690000}, - {423981400,1305152200}, - {427504600,1304090500}, - {428757700,1310060100}, - {431897400,1311892000}, - {440344300,1312479400}, - {447908400,1309259800}, - {449651300,1313950300}, - {452936700,1317762000}, - {450233100,1329138800}, - {462123900,1336656800}, - {473018100,1341318300}, - {477423200,1346395600}, - {484421800,1344441700}, - {482578200,1337457700}, - {481039700,1329297000}, - {477062600,1324401400}, - {476285700,1316887800}, - {477875100,1309374300}, - {488034100,1304674300}, - {495930500,1287364200}, - {495253500,1279267800}, - {498558300,1274247600}, - {504821100,1272556800}, - {520086700,1263022800}, - {529189300,1257138500}, - {535240400,1233065100}, - {533144800,1208991700}, - {527595400,1199859800}, - {520423200,1205267200}, - {510068000,1196984600}, - {500606200,1193096500}, - {495287200,1176531300}, - {498308400,1166490900}, - {479328400,1157076200}, - {480844300,1180933300}, - {472134400,1196377400}, - {467122600,1196628800}, - {463993300,1169068900}, - {455875700,1158663400}, - {453936600,1146868800}, - {448262800,1138589800}, - {450878400,1119834600}, - {445395200,1115141900}, - {436638100,1117473200}, - {426459800,1096952900}, - {422650000,1069401300}, - {416851000,1049759900}, - {420205800,1025418600}, - {426135800,1010745200}, - {424921600, 990358900}, - {428878200, 963380800}, - {442724800, 951612100}, - {450616000, 931665300}, - {450499500, 918176200}, - {452861800, 909960600}, - {454215300, 906749700}, - {458940100, 907493900}, - {465557200, 910300600}, - {474407800, 904927700}, - {479146500, 898286200}, - {481795100, 888129100}, - {485449300, 880388900}, - {491957400, 877043300}, - {491705700, 873129800}, - {491552500, 870409600}, - {490895800, 868238800}, - {489435600, 867192900}, - {488602800, 868287300}, - {485646500, 866003400}, - {485116800, 864543400}, - {484311000, 862090400}, - {484087600, 858099400}, - {481666000, 855665300}, - {475765800, 856453300}, - {472641400, 857286700}, - {470697000, 855483500}, - {470438500, 852250700}, - {468830000, 849457300}, - {469921200, 848366900}, - {470261800, 845298900}, - {469969200, 839931900}, - {470275800, 837201600}, - {472396400, 830235700}, - {462430400, 825576900}, - {459932800, 824555100}, - {459642900, 823423500}, - {456150100, 822588400}, - {455358200, 822665200}, - {454102700, 825928100} -}; -static const coord_t southkorea[] = { - // Latitude Longitude (in deg*10000000) - {326694600, 1240792500}, - {349869300, 1233594400}, - {373804200, 1239579800}, - {385913100, 1232100700}, - {390679600, 1240433600}, - {397587600, 1312690000}, - {375193480, 1318293030}, - {359878230, 1310822320}, - {344441030, 1293903380}, - {325570380, 1265338930} -}; -static const coord_t japan[] = { - // Latitude Longitude (in deg*10000000) - {236120900, 1277185900}, - {261685400, 1256067700}, - {326694600, 1240792500}, - {325570380, 1265338930}, - {344441030, 1293903380}, - {359878230, 1310822320}, - {375193480, 1318293030}, - {397587600, 1312690000}, - {410553650, 1354328180}, - {431098400, 1372785210}, - {482766430, 1410578180}, - {480274380, 1457599670}, - {448956070, 1531153130}, - {314577590, 1453589660}, - {259887040, 1356909970} -}; -static const coord_t southeastAsia[] = { - // Latitude Longitude (in deg*10000000) - { 279579500, 916425000}, - { 286829100, 946908200}, - { 278888600, 977112800}, - { 240215100, 974360200}, - { 212269300, 1010279300}, - { 226236400, 1052672100}, - { 207971800, 1073531600}, - { 174983800, 1071305300}, - { 161966200, 1121345000}, - { 203309900, 1169129400}, - { 209227300, 1244251400}, - { 209227300, 1300000000}, - { 209227300, 1400000000}, - { 209227300, 1500000000}, - { 209227300, 1600000000}, - { 209227300, 1700000000}, - { 209227300, 1800000000}, - {-250833370, 1800000000}, - {-250833370, 1730000000}, - {-250833370, 1675354920}, - {-141767640, 1558460380}, - {-123697580, 1466175230}, - {-100204890, 1448816830}, - { -99988510, 1400476990}, - { -94574350, 1348621520}, - { -94357600, 1306434020}, - { -110142370, 1266663510}, - { -124555940, 1234803160}, - { -142726100, 1179432060}, - { -149730480, 1084949640}, - { -127772250, 1016834410}, - { -48805060, 911365660}, - { 53013140, 884119560}, - { 145541020, 886756280}, - { 217668310, 903455880}, - { 279579500, 916425000} -}; -static const coord_t australia[] = { - // Latitude Longitude (in deg*10000000) - {-250833370, 1675354920}, - {-141767640, 1558460380}, - {-123697580, 1466175230}, - {-100204890, 1448816830}, - { -99988510, 1400476990}, - { -94574350, 1348621520}, - { -94357600, 1306434020}, - {-110142370, 1266663510}, - {-124555940, 1234803160}, - {-142726100, 1179432060}, - {-149730480, 1084949640}, - {-171651090, 1060999440}, - {-208656160, 1061658620}, - {-273587200, 1062537530}, - {-331988240, 1066932060}, - {-377069690, 1085389090}, - {-405694260, 1138123470}, - {-421525130, 1215467220}, - {-436969730, 1297205500}, - {-451406920, 1355213310}, - {-464882790, 1417615660}, - {-472692610, 1478260190}, - {-466695150, 1545057060}, - {-447050730, 1570545340}, - {-414317130, 1594275810}, - {-362324240, 1614490660}, - {-323861110, 1632947690}, - {-285233680, 1654041440} -}; -static const coord_t newzealand[] = { - // Latitude Longitude (in deg*10000000) - {-466695150, 1545057060}, - {-447050730, 1570545340}, - {-414317130, 1594275810}, - {-362324240, 1614490660}, - {-323861110, 1632947690}, - {-285233680, 1654041440}, - {-250833370, 1675354920}, - {-250833370, 1675354920}, - {-250833370, 1730000000}, - {-250833370, 1800000000}, - {-510311600, 1800000000}, - {-553577240, 1730000000}, - {-553577240, 1663161210}, - {-545772000, 1615700270}, - {-511849000, 1576149490}, - {-489283240, 1557692460} -}; -static const coord_t newzealand2[] = { - // Latitude Longitude (in deg*10000000) - {-250833370,-1800000000}, - {-350000000,-1725193600}, - {-444551600,-1725356300}, - {-510311600,-1800000000} -}; -static const coord_t argentina[] = { // Also includes Uruguay and Paraguay - // Latitude Longitude (in deg*10000000) - {-338230310, -532566330}, - {-335121510, -536081950}, - {-327947270, -530808520}, - {-320714680, -537839770}, - {-314549730, -547947190}, - {-308155250, -559812420}, - {-301717910, -568381760}, - {-299054910, -572776290}, - {-286212420, -560911060}, - {-278469150, -551682540}, - {-272234400, -539377850}, - {-263013630, -536741130}, - {-257683040, -538718670}, - {-256098970, -542234300}, - {-255702620, -545969650}, - {-245751710, -543113200}, - {-239541980, -543772380}, - {-238537580, -547727460}, - {-239742760, -551023360}, - {-239140310, -553879810}, - {-233705670, -555198170}, - {-224192240, -557834880}, - {-221956130, -563108320}, - {-222362960, -571018480}, - {-220938540, -579368090}, - {-210312570, -578708910}, - {-202086660, -580686450}, - {-198163940, -582224530}, - {-193609750, -591233320}, - {-193195090, -599802660}, - {-196095450, -617161060}, - {-205176580, -622873950}, - {-210517650, -622434490}, - {-222769680, -626389570}, - {-219920220, -628367110}, - {-220123940, -639353440}, - {-228856080, -643088790}, - {-222769680, -645725510}, - {-220734940, -657151290}, - {-218289380, -661765550}, - {-225004480, -668796800}, - {-230070120, -670554610}, - {-240144240, -673630780}, - {-244152110, -682639570}, - {-249542570, -683957930}, - {-261633950, -683518480}, - {-269887290, -682859300}, - {-272820400, -688352460}, - {-281379450, -693406170}, - {-290639190, -697580980}, - {-294179270, -699228930}, - {-298102100, -699119060}, - {-302857000, -698459880}, - {-303994770, -700657150}, - {-311357880, -703953050}, - {-317170180, -704612230}, - {-324616360, -701316330}, - {-331265750, -698899340}, - {-340600040, -697800700}, - {-346766490, -701536060}, - {-352887370, -703953050}, - {-360029570, -703513590}, - {-364637660, -708127850}, - {-369569760, -711643480}, - {-376212530, -711863200}, - {-381933540, -710105390}, - {-387267210, -708567310}, - {-389321250, -714060470}, - {-394260690, -714060470}, - {-399670750, -716916920}, - {-406706970, -718235270}, - {-414164240, -718015550}, - {-421048000, -717795820}, - {-421536870, -721091720}, - {-425434240, -720432540}, - {-429146530, -720432540}, - {-432196440, -717356370}, - {-434753010, -719114180}, - {-437140020, -715818280}, - {-443144700, -718015550}, - {-444401100, -712302660}, - {-447218130, -712961840}, - {-447530290, -719993090}, - {-449088570, -720432540}, - {-450332160, -714939380}, - {-452811250, -712742110}, - {-454971600, -716916920}, - {-458349760, -717136640}, - {-461403130, -718015550}, - {-466404380, -716477460}, - {-468962900, -719993090}, - {-472405090, -719333910}, - {-475231880, -723728440}, - {-479222830, -724607340}, - {-483913290, -721970630}, - {-484933620, -725486250}, - {-487982300, -725486250}, - {-490436790, -730320240}, - {-496733650, -730539960}, - {-503090380, -732737230}, - {-507837610, -731638590}, - {-506167570, -726584880}, - {-506864140, -723069260}, - {-511159850, -723069260}, - {-515005840, -723288990}, - {-517597280, -721970630}, - {-519361840, -718455000}, - {-519903400, -709665940}, - {-519903400, -700876880}, - {-521524130, -695163990}, - {-522870250, -686374920}, - {-526351090, -686814380}, - {-538058860, -686814380}, - {-549132950, -686155200}, - {-548809170, -681609600}, - {-548967140, -676061510}, - {-550260190, -667382310}, - {-553552720, -660625720}, - {-558486880, -643596910}, - {-554893240, -619646710}, - {-541989660, -615032450}, - {-534205450, -633489490}, - {-517928660, -655901600}, - {-498640640, -651726790}, - {-472171950, -625359600}, - {-431465440, -609099840}, - {-403438810, -562957260}, - {-369119700, -522527570}, - {-352248180, -506267810}, - {-345036770, -520330310} -}; -static const coord_t brazil[] = { - // Latitude Longitude (in deg*10000000) - {-345036770, -520330310}, - {-352248180, -506267810}, - {-338230310, -532566330}, - {-335121510, -536081950}, - {-327947270, -530808520}, - {-320714680, -537839770}, - {-314549730, -547947190}, - {-308155250, -559812420}, - {-301717910, -568381760}, - {-299054910, -572776290}, - {-286212420, -560911060}, - {-278469150, -551682540}, - {-272234400, -539377850}, - {-263013630, -536741130}, - {-257683040, -538718670}, - {-256098970, -542234300}, - {-255702620, -545969650}, - {-245751710, -543113200}, - {-239541980, -543772380}, - {-238537580, -547727460}, - {-239742760, -551023360}, - {-239140310, -553879810}, - {-233705670, -555198170}, - {-224192240, -557834880}, - {-221956130, -563108320}, - {-222362960, -571018480}, - {-220938540, -579368090}, - {-210312570, -578708910}, - {-202086660, -580686450}, - {-198163940, -582224530}, - {-182169010, -576017230}, - {-175686980, -578214500}, - {-170022330, -585026020}, - {-163708150, -583707660}, - {-162653770, -601505510}, - {-147834540, -602604150}, - {-139320080, -604361960}, - {-135691790, -610514300}, - {-135264570, -617545550}, - {-131630080, -621720360}, - {-129703740, -628751610}, - {-126489880, -631827780}, - {-124559610, -638859030}, - {-120694780, -649625630}, - {-115102430, -653141250}, - {-106477260, -653360980}, - { -98260640, -653580710}, - { -97394510, -658854150}, - { -99775810, -666324850}, - {-103236400, -672916650}, - {-107340910, -677750630}, - {-110577380, -685221330}, - {-109930370, -692032860}, - {-109714670, -705436180}, - { -94794790, -705436180}, - { -99559400, -712906880}, - { -99775810, -721036760}, - { -95011510, -724112930}, - { -94144550, -731583640}, - { -90457570, -729386370}, - { -82202640, -736197900}, - { -75237690, -739054340}, - { -70224660, -737516250}, - { -65424520, -731583640}, - { -60619760, -732462540}, - { -57122690, -730265280}, - { -51654270, -728507470}, - { -48589870, -723673480}, - { -44866940, -717740860}, - { -43333410, -709610980}, - { -42018700, -705655900}, - { -43552500, -700162740}, - { -32371210, -697306290}, - { -21836290, -695548480}, - { -9975840, -694449850}, - { -5142290, -697086570}, - { -02725350, -700602190}, - { 5404440, -700382470}, - { 6942430, -695548480}, - { 6283300, -692032860}, - { 10237960, -691593400}, - { 11116710, -698185200}, - { 17486760, -698624650}, - { 17047510, -689615860}, - { 16827880, -681705710}, - { 19902470, -681485980}, - { 20780820, -676212540}, - { 22317820, -673795550}, - { 17267140, -671818010}, - { 11556080, -670939110}, - { 11995440, -668741840}, - { 7381850, -663688130}, - { 9139480, -657096330}, - { 9578880, -650943990}, - { 14411790, -645011370}, - { 19024070, -640836570}, - { 21439550, -634025040}, - { 24293720, -633805320}, - { 25391320, -640397110}, - { 30439020, -641715470}, - { 35703650, -641935200}, - { 42280160, -648307270}, - { 40746110, -641715470}, - { 38773330, -639078750}, - { 40088570, -633805320}, - { 35922950, -630069970}, - { 36580810, -627652970}, - { 40307750, -627872700}, - { 41184440, -621280900}, - { 43375720, -615348290}, - { 46661450, -608317040}, - { 49288930, -606119770}, - { 51958300, -606892750}, - { 52395930, -600630550}, - { 45828370, -601399590}, - { 43856890, -597554370}, - { 39364380, -595796560}, - { 35746680, -598213550}, - { 27053170, -599751640}, - { 17612210, -596016290}, - { 12889780, -589314630}, - { 13109450, -585139820}, - { 15635510, -580855160}, - { 18490680, -574153500}, - { 19369110, -566572930}, - { 18600490, -559871270}, - { 20686660, -559431820}, - { 22882340, -561519220}, - { 25406950, -559871270}, - { 24089820, -557124690}, - { 24419120, -553938650}, - { 26504460, -549983570}, - { 22882340, -545808770}, - { 21125820, -541743830}, - { 23321440, -537898610}, - { 22113890, -533613940}, - { 22004110, -529329280}, - { 25187440, -525813650}, - { 34951690, -520430350}, - { 43719960, -515486500}, - { 80004680, -481758480}, - { 111065640, -443635920}, - { 85955290, -393977710}, - { 65918790, -362337090}, - { 37473500, -312239430}, - { 586180, -281038260}, - { -46384550, -267854670}, - { -86099650, -266096850}, - {-121109190, -270491390}, - {-157781340, -285432790}, - {-195459640, -303010920}, - {-229853450, -315755060}, - {-261422670, -327620290}, - {-295224630, -345637870}, - {-322003860, -361018730}, - {-349459690, -377717950}, - {-374980860, -390901540}, - {-364801770, -452864430} -}; - -// http://stackoverflowcom/questions/924171/geo-fencing-point-inside-outside-polygon -/** - * Determines is location is located in polygon - * @param poly Polygon - * @param lat Latitude - * @param lat Longitude - */ -static bool isPointInPolygon(const coord_t *poly, uint32_t size, int32_t lat, int32_t lon) { - bool c = false; - uint32_t j = size-1; - - for(uint32_t i=0; igps_lat == 0 && point->gps_lon == 0)) - return conf_sram.aprs.freq; - - // America 144.390 MHz - if(isPointInAmerica(point->gps_lat, point->gps_lon)) - return APRS_FREQ_AMERICA; - - // China 144.640 MHz - if(isPointInChina(point->gps_lat, point->gps_lon)) - return APRS_FREQ_CHINA; - - // Japan 144.660 MHz - if(isPointInJapan(point->gps_lat, point->gps_lon)) - return APRS_FREQ_JAPAN; - - // Southkorea 144.620 MHz - if(isPointInSouthkorea(point->gps_lat, point->gps_lon)) - return APRS_FREQ_SOUTHKOREA; - - // Southkorea 144.620 MHz - if(isPointInSoutheastAsia(point->gps_lat, point->gps_lon)) - return APRS_FREQ_SOUTHEASTASIA; - - // Australia 145.175 MHz - if(isPointInAustralia(point->gps_lat, point->gps_lon)) - return APRS_FREQ_AUSTRALIA; - - // Australia 144.575 MHz - if(isPointInNewZealand(point->gps_lat, point->gps_lon)) - return APRS_FREQ_NEWZEALAND; - - // Argentina/Paraguay/Uruguay 144.930 MHz - if(isPointInArgentina(point->gps_lat, point->gps_lon)) - return APRS_FREQ_ARGENTINA; - - // Brazil 145.575 MHz - if(isPointInBrazil(point->gps_lat, point->gps_lon)) - return APRS_FREQ_BRAZIL; - - return FREQ_APRS_DEFAULT; -} - +/** + * Geofencing algorithm and geofencing data + */ + +#include "ch.h" +#include "hal.h" +#include "geofence.h" +#include "collector.h" +#include "config.h" + +static const coord_t america[] = { + // Latitude Longitude (in deg*10000000) + { 602803500,-1800000000}, + {-250833370,-1800000000}, + {-350000000,-1725193600}, + {-444551600,-1725356300}, + {-510311600,-1800000000}, + {-624631500,-1800000000}, + {-622917200, -805076100}, + {-593631200, -532159100}, + {-613268000, -216598500}, + {-523586500, -216531900}, + {-158237300, -216398800}, + { -26137000, -256562300}, + { 116521800, -379076800}, + { 250055500, -471176200}, + { 437701700, -474320100}, + { 529477400, -475106100}, + { 592874700, -556751500}, + { 649970500, -579417200}, + { 677211100, -595630600}, + { 703992100, -625906400}, + { 748557600, -742708000}, + { 900000000, -749253300}, + { 900000000,-1800000000}, + { 751492400,-1800000000}, + { 684708700,-1696228400}, + { 655163500,-1697060800}, + { 641285100,-1733928400} +}; +static const coord_t china[] = { + // Latitude Longitude (in deg*10000000) + {451738100, 825642300}, + {452352200, 819625200}, + {453544900, 817013800}, + {451756000, 809593900}, + {449717800, 799587900}, + {446579400, 803864200}, + {440643900, 804054400}, + {434967200, 806881200}, + {431645300, 806192500}, + {428305300, 803306500}, + {421086600, 801075700}, + {410720900, 778157100}, + {410197600, 769014100}, + {405847000, 766682700}, + {403815200, 762593500}, + {404379000, 749752300}, + {395074200, 736814000}, + {371897300, 748730300}, + {315530900, 791019900}, + {301780600, 820775400}, + {283590400, 859320000}, + {279363900, 892463900}, + {284084300, 897228300}, + {279579500, 916425000}, + {286829100, 946908200}, + {278888600, 977112800}, + {240215100, 974360200}, + {212269300,1010279300}, + {226236400,1052672100}, + {207971800,1073531600}, + {174983800,1071305300}, + {161966200,1121345000}, + {203309900,1169129400}, + {209227300,1244251400}, + {236120900,1277185900}, + {261685400,1256067700}, + {326694600,1240792500}, + {349869300,1233594400}, + {373804200,1239579800}, + {385913100,1232100700}, + {390679600,1240433600}, + {397587600,1312690000}, + {423981400,1305152200}, + {427504600,1304090500}, + {428757700,1310060100}, + {431897400,1311892000}, + {440344300,1312479400}, + {447908400,1309259800}, + {449651300,1313950300}, + {452936700,1317762000}, + {450233100,1329138800}, + {462123900,1336656800}, + {473018100,1341318300}, + {477423200,1346395600}, + {484421800,1344441700}, + {482578200,1337457700}, + {481039700,1329297000}, + {477062600,1324401400}, + {476285700,1316887800}, + {477875100,1309374300}, + {488034100,1304674300}, + {495930500,1287364200}, + {495253500,1279267800}, + {498558300,1274247600}, + {504821100,1272556800}, + {520086700,1263022800}, + {529189300,1257138500}, + {535240400,1233065100}, + {533144800,1208991700}, + {527595400,1199859800}, + {520423200,1205267200}, + {510068000,1196984600}, + {500606200,1193096500}, + {495287200,1176531300}, + {498308400,1166490900}, + {479328400,1157076200}, + {480844300,1180933300}, + {472134400,1196377400}, + {467122600,1196628800}, + {463993300,1169068900}, + {455875700,1158663400}, + {453936600,1146868800}, + {448262800,1138589800}, + {450878400,1119834600}, + {445395200,1115141900}, + {436638100,1117473200}, + {426459800,1096952900}, + {422650000,1069401300}, + {416851000,1049759900}, + {420205800,1025418600}, + {426135800,1010745200}, + {424921600, 990358900}, + {428878200, 963380800}, + {442724800, 951612100}, + {450616000, 931665300}, + {450499500, 918176200}, + {452861800, 909960600}, + {454215300, 906749700}, + {458940100, 907493900}, + {465557200, 910300600}, + {474407800, 904927700}, + {479146500, 898286200}, + {481795100, 888129100}, + {485449300, 880388900}, + {491957400, 877043300}, + {491705700, 873129800}, + {491552500, 870409600}, + {490895800, 868238800}, + {489435600, 867192900}, + {488602800, 868287300}, + {485646500, 866003400}, + {485116800, 864543400}, + {484311000, 862090400}, + {484087600, 858099400}, + {481666000, 855665300}, + {475765800, 856453300}, + {472641400, 857286700}, + {470697000, 855483500}, + {470438500, 852250700}, + {468830000, 849457300}, + {469921200, 848366900}, + {470261800, 845298900}, + {469969200, 839931900}, + {470275800, 837201600}, + {472396400, 830235700}, + {462430400, 825576900}, + {459932800, 824555100}, + {459642900, 823423500}, + {456150100, 822588400}, + {455358200, 822665200}, + {454102700, 825928100} +}; +static const coord_t southkorea[] = { + // Latitude Longitude (in deg*10000000) + {326694600, 1240792500}, + {349869300, 1233594400}, + {373804200, 1239579800}, + {385913100, 1232100700}, + {390679600, 1240433600}, + {397587600, 1312690000}, + {375193480, 1318293030}, + {359878230, 1310822320}, + {344441030, 1293903380}, + {325570380, 1265338930} +}; +static const coord_t japan[] = { + // Latitude Longitude (in deg*10000000) + {236120900, 1277185900}, + {261685400, 1256067700}, + {326694600, 1240792500}, + {325570380, 1265338930}, + {344441030, 1293903380}, + {359878230, 1310822320}, + {375193480, 1318293030}, + {397587600, 1312690000}, + {410553650, 1354328180}, + {431098400, 1372785210}, + {482766430, 1410578180}, + {480274380, 1457599670}, + {448956070, 1531153130}, + {314577590, 1453589660}, + {259887040, 1356909970} +}; +static const coord_t southeastAsia[] = { + // Latitude Longitude (in deg*10000000) + { 279579500, 916425000}, + { 286829100, 946908200}, + { 278888600, 977112800}, + { 240215100, 974360200}, + { 212269300, 1010279300}, + { 226236400, 1052672100}, + { 207971800, 1073531600}, + { 174983800, 1071305300}, + { 161966200, 1121345000}, + { 203309900, 1169129400}, + { 209227300, 1244251400}, + { 209227300, 1300000000}, + { 209227300, 1400000000}, + { 209227300, 1500000000}, + { 209227300, 1600000000}, + { 209227300, 1700000000}, + { 209227300, 1800000000}, + {-250833370, 1800000000}, + {-250833370, 1730000000}, + {-250833370, 1675354920}, + {-141767640, 1558460380}, + {-123697580, 1466175230}, + {-100204890, 1448816830}, + { -99988510, 1400476990}, + { -94574350, 1348621520}, + { -94357600, 1306434020}, + { -110142370, 1266663510}, + { -124555940, 1234803160}, + { -142726100, 1179432060}, + { -149730480, 1084949640}, + { -127772250, 1016834410}, + { -48805060, 911365660}, + { 53013140, 884119560}, + { 145541020, 886756280}, + { 217668310, 903455880}, + { 279579500, 916425000} +}; +static const coord_t australia[] = { + // Latitude Longitude (in deg*10000000) + {-250833370, 1675354920}, + {-141767640, 1558460380}, + {-123697580, 1466175230}, + {-100204890, 1448816830}, + { -99988510, 1400476990}, + { -94574350, 1348621520}, + { -94357600, 1306434020}, + {-110142370, 1266663510}, + {-124555940, 1234803160}, + {-142726100, 1179432060}, + {-149730480, 1084949640}, + {-171651090, 1060999440}, + {-208656160, 1061658620}, + {-273587200, 1062537530}, + {-331988240, 1066932060}, + {-377069690, 1085389090}, + {-405694260, 1138123470}, + {-421525130, 1215467220}, + {-436969730, 1297205500}, + {-451406920, 1355213310}, + {-464882790, 1417615660}, + {-472692610, 1478260190}, + {-466695150, 1545057060}, + {-447050730, 1570545340}, + {-414317130, 1594275810}, + {-362324240, 1614490660}, + {-323861110, 1632947690}, + {-285233680, 1654041440} +}; +static const coord_t newzealand[] = { + // Latitude Longitude (in deg*10000000) + {-466695150, 1545057060}, + {-447050730, 1570545340}, + {-414317130, 1594275810}, + {-362324240, 1614490660}, + {-323861110, 1632947690}, + {-285233680, 1654041440}, + {-250833370, 1675354920}, + {-250833370, 1675354920}, + {-250833370, 1730000000}, + {-250833370, 1800000000}, + {-510311600, 1800000000}, + {-553577240, 1730000000}, + {-553577240, 1663161210}, + {-545772000, 1615700270}, + {-511849000, 1576149490}, + {-489283240, 1557692460} +}; +static const coord_t newzealand2[] = { + // Latitude Longitude (in deg*10000000) + {-250833370,-1800000000}, + {-350000000,-1725193600}, + {-444551600,-1725356300}, + {-510311600,-1800000000} +}; +static const coord_t argentina[] = { // Also includes Uruguay and Paraguay + // Latitude Longitude (in deg*10000000) + {-338230310, -532566330}, + {-335121510, -536081950}, + {-327947270, -530808520}, + {-320714680, -537839770}, + {-314549730, -547947190}, + {-308155250, -559812420}, + {-301717910, -568381760}, + {-299054910, -572776290}, + {-286212420, -560911060}, + {-278469150, -551682540}, + {-272234400, -539377850}, + {-263013630, -536741130}, + {-257683040, -538718670}, + {-256098970, -542234300}, + {-255702620, -545969650}, + {-245751710, -543113200}, + {-239541980, -543772380}, + {-238537580, -547727460}, + {-239742760, -551023360}, + {-239140310, -553879810}, + {-233705670, -555198170}, + {-224192240, -557834880}, + {-221956130, -563108320}, + {-222362960, -571018480}, + {-220938540, -579368090}, + {-210312570, -578708910}, + {-202086660, -580686450}, + {-198163940, -582224530}, + {-193609750, -591233320}, + {-193195090, -599802660}, + {-196095450, -617161060}, + {-205176580, -622873950}, + {-210517650, -622434490}, + {-222769680, -626389570}, + {-219920220, -628367110}, + {-220123940, -639353440}, + {-228856080, -643088790}, + {-222769680, -645725510}, + {-220734940, -657151290}, + {-218289380, -661765550}, + {-225004480, -668796800}, + {-230070120, -670554610}, + {-240144240, -673630780}, + {-244152110, -682639570}, + {-249542570, -683957930}, + {-261633950, -683518480}, + {-269887290, -682859300}, + {-272820400, -688352460}, + {-281379450, -693406170}, + {-290639190, -697580980}, + {-294179270, -699228930}, + {-298102100, -699119060}, + {-302857000, -698459880}, + {-303994770, -700657150}, + {-311357880, -703953050}, + {-317170180, -704612230}, + {-324616360, -701316330}, + {-331265750, -698899340}, + {-340600040, -697800700}, + {-346766490, -701536060}, + {-352887370, -703953050}, + {-360029570, -703513590}, + {-364637660, -708127850}, + {-369569760, -711643480}, + {-376212530, -711863200}, + {-381933540, -710105390}, + {-387267210, -708567310}, + {-389321250, -714060470}, + {-394260690, -714060470}, + {-399670750, -716916920}, + {-406706970, -718235270}, + {-414164240, -718015550}, + {-421048000, -717795820}, + {-421536870, -721091720}, + {-425434240, -720432540}, + {-429146530, -720432540}, + {-432196440, -717356370}, + {-434753010, -719114180}, + {-437140020, -715818280}, + {-443144700, -718015550}, + {-444401100, -712302660}, + {-447218130, -712961840}, + {-447530290, -719993090}, + {-449088570, -720432540}, + {-450332160, -714939380}, + {-452811250, -712742110}, + {-454971600, -716916920}, + {-458349760, -717136640}, + {-461403130, -718015550}, + {-466404380, -716477460}, + {-468962900, -719993090}, + {-472405090, -719333910}, + {-475231880, -723728440}, + {-479222830, -724607340}, + {-483913290, -721970630}, + {-484933620, -725486250}, + {-487982300, -725486250}, + {-490436790, -730320240}, + {-496733650, -730539960}, + {-503090380, -732737230}, + {-507837610, -731638590}, + {-506167570, -726584880}, + {-506864140, -723069260}, + {-511159850, -723069260}, + {-515005840, -723288990}, + {-517597280, -721970630}, + {-519361840, -718455000}, + {-519903400, -709665940}, + {-519903400, -700876880}, + {-521524130, -695163990}, + {-522870250, -686374920}, + {-526351090, -686814380}, + {-538058860, -686814380}, + {-549132950, -686155200}, + {-548809170, -681609600}, + {-548967140, -676061510}, + {-550260190, -667382310}, + {-553552720, -660625720}, + {-558486880, -643596910}, + {-554893240, -619646710}, + {-541989660, -615032450}, + {-534205450, -633489490}, + {-517928660, -655901600}, + {-498640640, -651726790}, + {-472171950, -625359600}, + {-431465440, -609099840}, + {-403438810, -562957260}, + {-369119700, -522527570}, + {-352248180, -506267810}, + {-345036770, -520330310} +}; +static const coord_t brazil[] = { + // Latitude Longitude (in deg*10000000) + {-345036770, -520330310}, + {-352248180, -506267810}, + {-338230310, -532566330}, + {-335121510, -536081950}, + {-327947270, -530808520}, + {-320714680, -537839770}, + {-314549730, -547947190}, + {-308155250, -559812420}, + {-301717910, -568381760}, + {-299054910, -572776290}, + {-286212420, -560911060}, + {-278469150, -551682540}, + {-272234400, -539377850}, + {-263013630, -536741130}, + {-257683040, -538718670}, + {-256098970, -542234300}, + {-255702620, -545969650}, + {-245751710, -543113200}, + {-239541980, -543772380}, + {-238537580, -547727460}, + {-239742760, -551023360}, + {-239140310, -553879810}, + {-233705670, -555198170}, + {-224192240, -557834880}, + {-221956130, -563108320}, + {-222362960, -571018480}, + {-220938540, -579368090}, + {-210312570, -578708910}, + {-202086660, -580686450}, + {-198163940, -582224530}, + {-182169010, -576017230}, + {-175686980, -578214500}, + {-170022330, -585026020}, + {-163708150, -583707660}, + {-162653770, -601505510}, + {-147834540, -602604150}, + {-139320080, -604361960}, + {-135691790, -610514300}, + {-135264570, -617545550}, + {-131630080, -621720360}, + {-129703740, -628751610}, + {-126489880, -631827780}, + {-124559610, -638859030}, + {-120694780, -649625630}, + {-115102430, -653141250}, + {-106477260, -653360980}, + { -98260640, -653580710}, + { -97394510, -658854150}, + { -99775810, -666324850}, + {-103236400, -672916650}, + {-107340910, -677750630}, + {-110577380, -685221330}, + {-109930370, -692032860}, + {-109714670, -705436180}, + { -94794790, -705436180}, + { -99559400, -712906880}, + { -99775810, -721036760}, + { -95011510, -724112930}, + { -94144550, -731583640}, + { -90457570, -729386370}, + { -82202640, -736197900}, + { -75237690, -739054340}, + { -70224660, -737516250}, + { -65424520, -731583640}, + { -60619760, -732462540}, + { -57122690, -730265280}, + { -51654270, -728507470}, + { -48589870, -723673480}, + { -44866940, -717740860}, + { -43333410, -709610980}, + { -42018700, -705655900}, + { -43552500, -700162740}, + { -32371210, -697306290}, + { -21836290, -695548480}, + { -9975840, -694449850}, + { -5142290, -697086570}, + { -02725350, -700602190}, + { 5404440, -700382470}, + { 6942430, -695548480}, + { 6283300, -692032860}, + { 10237960, -691593400}, + { 11116710, -698185200}, + { 17486760, -698624650}, + { 17047510, -689615860}, + { 16827880, -681705710}, + { 19902470, -681485980}, + { 20780820, -676212540}, + { 22317820, -673795550}, + { 17267140, -671818010}, + { 11556080, -670939110}, + { 11995440, -668741840}, + { 7381850, -663688130}, + { 9139480, -657096330}, + { 9578880, -650943990}, + { 14411790, -645011370}, + { 19024070, -640836570}, + { 21439550, -634025040}, + { 24293720, -633805320}, + { 25391320, -640397110}, + { 30439020, -641715470}, + { 35703650, -641935200}, + { 42280160, -648307270}, + { 40746110, -641715470}, + { 38773330, -639078750}, + { 40088570, -633805320}, + { 35922950, -630069970}, + { 36580810, -627652970}, + { 40307750, -627872700}, + { 41184440, -621280900}, + { 43375720, -615348290}, + { 46661450, -608317040}, + { 49288930, -606119770}, + { 51958300, -606892750}, + { 52395930, -600630550}, + { 45828370, -601399590}, + { 43856890, -597554370}, + { 39364380, -595796560}, + { 35746680, -598213550}, + { 27053170, -599751640}, + { 17612210, -596016290}, + { 12889780, -589314630}, + { 13109450, -585139820}, + { 15635510, -580855160}, + { 18490680, -574153500}, + { 19369110, -566572930}, + { 18600490, -559871270}, + { 20686660, -559431820}, + { 22882340, -561519220}, + { 25406950, -559871270}, + { 24089820, -557124690}, + { 24419120, -553938650}, + { 26504460, -549983570}, + { 22882340, -545808770}, + { 21125820, -541743830}, + { 23321440, -537898610}, + { 22113890, -533613940}, + { 22004110, -529329280}, + { 25187440, -525813650}, + { 34951690, -520430350}, + { 43719960, -515486500}, + { 80004680, -481758480}, + { 111065640, -443635920}, + { 85955290, -393977710}, + { 65918790, -362337090}, + { 37473500, -312239430}, + { 586180, -281038260}, + { -46384550, -267854670}, + { -86099650, -266096850}, + {-121109190, -270491390}, + {-157781340, -285432790}, + {-195459640, -303010920}, + {-229853450, -315755060}, + {-261422670, -327620290}, + {-295224630, -345637870}, + {-322003860, -361018730}, + {-349459690, -377717950}, + {-374980860, -390901540}, + {-364801770, -452864430} +}; + +// http://stackoverflowcom/questions/924171/geo-fencing-point-inside-outside-polygon +/** + * Determines is location is located in polygon + * @param poly Polygon + * @param lat Latitude + * @param lat Longitude + */ +static bool isPointInPolygon(const coord_t *poly, uint32_t size, int32_t lat, int32_t lon) { + bool c = false; + uint32_t j = size-1; + + for(uint32_t i=0; igps_lat == 0 && point->gps_lon == 0)) + return conf_sram.freq; + + // America 144.390 MHz + if(isPointInAmerica(point->gps_lat, point->gps_lon)) + return APRS_FREQ_AMERICA; + + // China 144.640 MHz + if(isPointInChina(point->gps_lat, point->gps_lon)) + return APRS_FREQ_CHINA; + + // Japan 144.660 MHz + if(isPointInJapan(point->gps_lat, point->gps_lon)) + return APRS_FREQ_JAPAN; + + // Southkorea 144.620 MHz + if(isPointInSouthkorea(point->gps_lat, point->gps_lon)) + return APRS_FREQ_SOUTHKOREA; + + // Southkorea 144.620 MHz + if(isPointInSoutheastAsia(point->gps_lat, point->gps_lon)) + return APRS_FREQ_SOUTHEASTASIA; + + // Australia 145.175 MHz + if(isPointInAustralia(point->gps_lat, point->gps_lon)) + return APRS_FREQ_AUSTRALIA; + + // Australia 144.575 MHz + if(isPointInNewZealand(point->gps_lat, point->gps_lon)) + return APRS_FREQ_NEWZEALAND; + + // Argentina/Paraguay/Uruguay 144.930 MHz + if(isPointInArgentina(point->gps_lat, point->gps_lon)) + return APRS_FREQ_ARGENTINA; + + // Brazil 145.575 MHz + if(isPointInBrazil(point->gps_lat, point->gps_lon)) + return APRS_FREQ_BRAZIL; + + return FREQ_APRS_DEFAULT; +} + diff --git a/tracker/software/source/pkt/managers/pktradio.c b/tracker/software/source/pkt/managers/pktradio.c index 19a91759..2a2865a8 100644 --- a/tracker/software/source/pkt/managers/pktradio.c +++ b/tracker/software/source/pkt/managers/pktradio.c @@ -647,8 +647,8 @@ radio_freq_t pktGetDefaultOperatingFrequency(const radio_unit_t radio) { /* FIXME: Default frequency in config to be per radio. */ (void)radio; /* FIXME: INVALID relies on 0 in conf if no default set. */ - if(conf_sram.aprs.freq != FREQ_RADIO_INVALID) - return conf_sram.aprs.freq; + if(conf_sram.freq != FREQ_RADIO_INVALID) + return conf_sram.freq; else return DEFAULT_OPERATING_FREQ; } diff --git a/tracker/software/source/protocols/packet/aprs.c b/tracker/software/source/protocols/packet/aprs.c index cbb7a8b6..f0b62b2e 100644 --- a/tracker/software/source/protocols/packet/aprs.c +++ b/tracker/software/source/protocols/packet/aprs.c @@ -43,12 +43,12 @@ static heard_t heard_list[APRS_HEARD_LIST_SIZE]; static bool dedupe_initialized; const conf_command_t command_list[] = { - {TYPE_INT, "pos_pri.active", sizeof(conf_sram.pos_pri.thread_conf.active), &conf_sram.pos_pri.thread_conf.active }, - {TYPE_TIME, "pos_pri.init_delay", sizeof(conf_sram.pos_pri.thread_conf.init_delay), &conf_sram.pos_pri.thread_conf.init_delay }, - {TYPE_INT, "pos_pri.sleep_conf.type", sizeof(conf_sram.pos_pri.thread_conf.sleep_conf.type), &conf_sram.pos_pri.thread_conf.sleep_conf.type }, - {TYPE_INT, "pos_pri.sleep_conf.vbat_thres", sizeof(conf_sram.pos_pri.thread_conf.sleep_conf.vbat_thres), &conf_sram.pos_pri.thread_conf.sleep_conf.vbat_thres}, - {TYPE_INT, "pos_pri.sleep_conf.vsol_thres", sizeof(conf_sram.pos_pri.thread_conf.sleep_conf.vsol_thres), &conf_sram.pos_pri.thread_conf.sleep_conf.vsol_thres}, - {TYPE_TIME, "pos_pri.cycle", sizeof(conf_sram.pos_pri.thread_conf.cycle), &conf_sram.pos_pri.thread_conf.cycle }, + {TYPE_INT, "pos_pri.active", sizeof(conf_sram.pos_pri.svc_conf.active), &conf_sram.pos_pri.svc_conf.active }, + {TYPE_TIME, "pos_pri.init_delay", sizeof(conf_sram.pos_pri.svc_conf.init_delay), &conf_sram.pos_pri.svc_conf.init_delay }, + {TYPE_INT, "pos_pri.sleep_conf.type", sizeof(conf_sram.pos_pri.svc_conf.sleep_conf.type), &conf_sram.pos_pri.svc_conf.sleep_conf.type }, + {TYPE_INT, "pos_pri.sleep_conf.vbat_thres", sizeof(conf_sram.pos_pri.svc_conf.sleep_conf.vbat_thres), &conf_sram.pos_pri.svc_conf.sleep_conf.vbat_thres }, + {TYPE_INT, "pos_pri.sleep_conf.vsol_thres", sizeof(conf_sram.pos_pri.svc_conf.sleep_conf.vsol_thres), &conf_sram.pos_pri.svc_conf.sleep_conf.vsol_thres }, + {TYPE_TIME, "pos_pri.cycle", sizeof(conf_sram.pos_pri.svc_conf.cycle), &conf_sram.pos_pri.svc_conf.cycle }, {TYPE_INT, "pos_pri.pwr", sizeof(conf_sram.pos_pri.radio_conf.pwr), &conf_sram.pos_pri.radio_conf.pwr }, {TYPE_INT, "pos_pri.freq", sizeof(conf_sram.pos_pri.radio_conf.freq), &conf_sram.pos_pri.radio_conf.freq }, {TYPE_INT, "pos_pri.mod", sizeof(conf_sram.pos_pri.radio_conf.mod), &conf_sram.pos_pri.radio_conf.mod }, @@ -57,14 +57,13 @@ const conf_command_t command_list[] = { {TYPE_STR, "pos_pri.path", sizeof(conf_sram.pos_pri.path), &conf_sram.pos_pri.path }, {TYPE_INT, "pos_pri.symbol", sizeof(conf_sram.pos_pri.symbol), &conf_sram.pos_pri.symbol }, {TYPE_INT, "pos_pri.aprs_msg", sizeof(conf_sram.pos_pri.aprs_msg), &conf_sram.pos_pri.aprs_msg }, - {TYPE_TIME, "pos_pri.tel_enc_cycle", sizeof(conf_sram.pos_pri.tel_enc_cycle), &conf_sram.pos_pri.tel_enc_cycle }, - {TYPE_INT, "pos_sec.active", sizeof(conf_sram.pos_sec.thread_conf.active), &conf_sram.pos_sec.thread_conf.active }, - {TYPE_TIME, "pos_sec.init_delay", sizeof(conf_sram.pos_sec.thread_conf.init_delay), &conf_sram.pos_sec.thread_conf.init_delay }, - {TYPE_INT, "pos_sec.sleep_conf.type", sizeof(conf_sram.pos_sec.thread_conf.sleep_conf.type), &conf_sram.pos_sec.thread_conf.sleep_conf.type }, - {TYPE_INT, "pos_sec.sleep_conf.vbat_thres", sizeof(conf_sram.pos_sec.thread_conf.sleep_conf.vbat_thres), &conf_sram.pos_sec.thread_conf.sleep_conf.vbat_thres}, - {TYPE_INT, "pos_sec.sleep_conf.vsol_thres", sizeof(conf_sram.pos_sec.thread_conf.sleep_conf.vsol_thres), &conf_sram.pos_sec.thread_conf.sleep_conf.vsol_thres}, - {TYPE_TIME, "pos_sec.cycle", sizeof(conf_sram.pos_sec.thread_conf.cycle), &conf_sram.pos_sec.thread_conf.cycle }, + {TYPE_INT, "pos_sec.active", sizeof(conf_sram.pos_sec.svc_conf.active), &conf_sram.pos_sec.svc_conf.active }, + {TYPE_TIME, "pos_sec.init_delay", sizeof(conf_sram.pos_sec.svc_conf.init_delay), &conf_sram.pos_sec.svc_conf.init_delay }, + {TYPE_INT, "pos_sec.sleep_conf.type", sizeof(conf_sram.pos_sec.svc_conf.sleep_conf.type), &conf_sram.pos_sec.svc_conf.sleep_conf.type }, + {TYPE_INT, "pos_sec.sleep_conf.vbat_thres", sizeof(conf_sram.pos_sec.svc_conf.sleep_conf.vbat_thres), &conf_sram.pos_sec.svc_conf.sleep_conf.vbat_thres }, + {TYPE_INT, "pos_sec.sleep_conf.vsol_thres", sizeof(conf_sram.pos_sec.svc_conf.sleep_conf.vsol_thres), &conf_sram.pos_sec.svc_conf.sleep_conf.vsol_thres }, + {TYPE_TIME, "pos_sec.cycle", sizeof(conf_sram.pos_sec.svc_conf.cycle), &conf_sram.pos_sec.svc_conf.cycle }, {TYPE_INT, "pos_sec.pwr", sizeof(conf_sram.pos_sec.radio_conf.pwr), &conf_sram.pos_sec.radio_conf.pwr }, {TYPE_INT, "pos_sec.freq", sizeof(conf_sram.pos_sec.radio_conf.freq), &conf_sram.pos_sec.radio_conf.freq }, {TYPE_INT, "pos_sec.mod", sizeof(conf_sram.pos_sec.radio_conf.mod), &conf_sram.pos_sec.radio_conf.mod }, @@ -73,15 +72,14 @@ const conf_command_t command_list[] = { {TYPE_STR, "pos_sec.path", sizeof(conf_sram.pos_sec.path), &conf_sram.pos_sec.path }, {TYPE_INT, "pos_sec.symbol", sizeof(conf_sram.pos_sec.symbol), &conf_sram.pos_sec.symbol }, {TYPE_INT, "pos_sec.aprs_msg", sizeof(conf_sram.pos_sec.aprs_msg), &conf_sram.pos_sec.aprs_msg }, - {TYPE_TIME, "pos_sec.tel_enc_cycle", sizeof(conf_sram.pos_sec.tel_enc_cycle), &conf_sram.pos_sec.tel_enc_cycle }, - {TYPE_INT, "img_pri.active", sizeof(conf_sram.img_pri.thread_conf.active), &conf_sram.img_pri.thread_conf.active }, - {TYPE_TIME, "img_pri.init_delay", sizeof(conf_sram.img_pri.thread_conf.init_delay), &conf_sram.img_pri.thread_conf.init_delay }, - {TYPE_TIME, "img_pri.send_spacing", sizeof(conf_sram.img_pri.thread_conf.send_spacing), &conf_sram.img_pri.thread_conf.send_spacing }, - {TYPE_INT, "img_pri.sleep_conf.type", sizeof(conf_sram.img_pri.thread_conf.sleep_conf.type), &conf_sram.img_pri.thread_conf.sleep_conf.type }, - {TYPE_INT, "img_pri.sleep_conf.vbat_thres", sizeof(conf_sram.img_pri.thread_conf.sleep_conf.vbat_thres), &conf_sram.img_pri.thread_conf.sleep_conf.vbat_thres}, - {TYPE_INT, "img_pri.sleep_conf.vsol_thres", sizeof(conf_sram.img_pri.thread_conf.sleep_conf.vsol_thres), &conf_sram.img_pri.thread_conf.sleep_conf.vsol_thres}, - {TYPE_TIME, "img_pri.cycle", sizeof(conf_sram.img_pri.thread_conf.cycle), &conf_sram.img_pri.thread_conf.cycle }, + {TYPE_INT, "img_pri.active", sizeof(conf_sram.img_pri.svc_conf.active), &conf_sram.img_pri.svc_conf.active }, + {TYPE_TIME, "img_pri.init_delay", sizeof(conf_sram.img_pri.svc_conf.init_delay), &conf_sram.img_pri.svc_conf.init_delay }, + {TYPE_TIME, "img_pri.send_spacing", sizeof(conf_sram.img_pri.svc_conf.send_spacing), &conf_sram.img_pri.svc_conf.send_spacing }, + {TYPE_INT, "img_pri.sleep_conf.type", sizeof(conf_sram.img_pri.svc_conf.sleep_conf.type), &conf_sram.img_pri.svc_conf.sleep_conf.type }, + {TYPE_INT, "img_pri.sleep_conf.vbat_thres", sizeof(conf_sram.img_pri.svc_conf.sleep_conf.vbat_thres), &conf_sram.img_pri.svc_conf.sleep_conf.vbat_thres }, + {TYPE_INT, "img_pri.sleep_conf.vsol_thres", sizeof(conf_sram.img_pri.svc_conf.sleep_conf.vsol_thres), &conf_sram.img_pri.svc_conf.sleep_conf.vsol_thres }, + {TYPE_TIME, "img_pri.cycle", sizeof(conf_sram.img_pri.svc_conf.cycle), &conf_sram.img_pri.svc_conf.cycle }, {TYPE_INT, "img_pri.pwr", sizeof(conf_sram.img_pri.radio_conf.pwr), &conf_sram.img_pri.radio_conf.pwr }, {TYPE_INT, "img_pri.freq", sizeof(conf_sram.img_pri.radio_conf.freq), &conf_sram.img_pri.radio_conf.freq }, {TYPE_INT, "img_pri.mod", sizeof(conf_sram.img_pri.radio_conf.mod), &conf_sram.img_pri.radio_conf.mod }, @@ -94,13 +92,13 @@ const conf_command_t command_list[] = { {TYPE_INT, "img_pri.quality", sizeof(conf_sram.img_pri.quality), &conf_sram.img_pri.quality }, {TYPE_INT, "img_pri.buf_size", sizeof(conf_sram.img_pri.buf_size), &conf_sram.img_pri.buf_size }, - {TYPE_INT, "img_sec.active", sizeof(conf_sram.img_sec.thread_conf.active), &conf_sram.img_sec.thread_conf.active }, - {TYPE_TIME, "img_sec.init_delay", sizeof(conf_sram.img_sec.thread_conf.init_delay), &conf_sram.img_sec.thread_conf.init_delay }, - {TYPE_TIME, "img_sec.send_spacing", sizeof(conf_sram.img_sec.thread_conf.send_spacing), &conf_sram.img_sec.thread_conf.send_spacing }, - {TYPE_INT, "img_sec.sleep_conf.type", sizeof(conf_sram.img_sec.thread_conf.sleep_conf.type), &conf_sram.img_sec.thread_conf.sleep_conf.type }, - {TYPE_INT, "img_sec.sleep_conf.vbat_thres", sizeof(conf_sram.img_sec.thread_conf.sleep_conf.vbat_thres), &conf_sram.img_sec.thread_conf.sleep_conf.vbat_thres}, - {TYPE_INT, "img_sec.sleep_conf.vsol_thres", sizeof(conf_sram.img_sec.thread_conf.sleep_conf.vsol_thres), &conf_sram.img_sec.thread_conf.sleep_conf.vsol_thres}, - {TYPE_TIME, "img_sec.cycle", sizeof(conf_sram.img_sec.thread_conf.cycle), &conf_sram.img_sec.thread_conf.cycle }, + {TYPE_INT, "img_sec.active", sizeof(conf_sram.img_sec.svc_conf.active), &conf_sram.img_sec.svc_conf.active }, + {TYPE_TIME, "img_sec.init_delay", sizeof(conf_sram.img_sec.svc_conf.init_delay), &conf_sram.img_sec.svc_conf.init_delay }, + {TYPE_TIME, "img_sec.send_spacing", sizeof(conf_sram.img_sec.svc_conf.send_spacing), &conf_sram.img_sec.svc_conf.send_spacing }, + {TYPE_INT, "img_sec.sleep_conf.type", sizeof(conf_sram.img_sec.svc_conf.sleep_conf.type), &conf_sram.img_sec.svc_conf.sleep_conf.type }, + {TYPE_INT, "img_sec.sleep_conf.vbat_thres", sizeof(conf_sram.img_sec.svc_conf.sleep_conf.vbat_thres), &conf_sram.img_sec.svc_conf.sleep_conf.vbat_thres }, + {TYPE_INT, "img_sec.sleep_conf.vsol_thres", sizeof(conf_sram.img_sec.svc_conf.sleep_conf.vsol_thres), &conf_sram.img_sec.svc_conf.sleep_conf.vsol_thres }, + {TYPE_TIME, "img_sec.cycle", sizeof(conf_sram.img_sec.svc_conf.cycle), &conf_sram.img_sec.svc_conf.cycle }, {TYPE_INT, "img_sec.pwr", sizeof(conf_sram.img_sec.radio_conf.pwr), &conf_sram.img_sec.radio_conf.pwr }, {TYPE_INT, "img_sec.freq", sizeof(conf_sram.img_sec.radio_conf.freq), &conf_sram.img_sec.radio_conf.freq }, {TYPE_INT, "img_sec.mod", sizeof(conf_sram.img_sec.radio_conf.mod), &conf_sram.img_sec.radio_conf.mod }, @@ -113,13 +111,13 @@ const conf_command_t command_list[] = { {TYPE_INT, "img_sec.quality", sizeof(conf_sram.img_sec.quality), &conf_sram.img_sec.quality }, {TYPE_INT, "img_sec.buf_size", sizeof(conf_sram.img_sec.buf_size), &conf_sram.img_sec.buf_size }, - {TYPE_INT, "log.active", sizeof(conf_sram.log.thread_conf.active), &conf_sram.log.thread_conf.active }, - {TYPE_TIME, "log.init_delay", sizeof(conf_sram.log.thread_conf.init_delay), &conf_sram.log.thread_conf.init_delay }, - {TYPE_TIME, "log.send_spacing", sizeof(conf_sram.log.thread_conf.send_spacing), &conf_sram.log.thread_conf.send_spacing }, - {TYPE_INT, "log.sleep_conf.type", sizeof(conf_sram.log.thread_conf.sleep_conf.type), &conf_sram.log.thread_conf.sleep_conf.type }, - {TYPE_INT, "log.sleep_conf.vbat_thres", sizeof(conf_sram.log.thread_conf.sleep_conf.vbat_thres), &conf_sram.log.thread_conf.sleep_conf.vbat_thres }, - {TYPE_INT, "log.sleep_conf.vsol_thres", sizeof(conf_sram.log.thread_conf.sleep_conf.vsol_thres), &conf_sram.log.thread_conf.sleep_conf.vsol_thres }, - {TYPE_TIME, "log.cycle", sizeof(conf_sram.log.thread_conf.cycle), &conf_sram.log.thread_conf.cycle }, + {TYPE_INT, "log.active", sizeof(conf_sram.log.svc_conf.active), &conf_sram.log.svc_conf.active }, + {TYPE_TIME, "log.init_delay", sizeof(conf_sram.log.svc_conf.init_delay), &conf_sram.log.svc_conf.init_delay }, + {TYPE_TIME, "log.send_spacing", sizeof(conf_sram.log.svc_conf.send_spacing), &conf_sram.log.svc_conf.send_spacing }, + {TYPE_INT, "log.sleep_conf.type", sizeof(conf_sram.log.svc_conf.sleep_conf.type), &conf_sram.log.svc_conf.sleep_conf.type }, + {TYPE_INT, "log.sleep_conf.vbat_thres", sizeof(conf_sram.log.svc_conf.sleep_conf.vbat_thres), &conf_sram.log.svc_conf.sleep_conf.vbat_thres }, + {TYPE_INT, "log.sleep_conf.vsol_thres", sizeof(conf_sram.log.svc_conf.sleep_conf.vsol_thres), &conf_sram.log.svc_conf.sleep_conf.vsol_thres }, + {TYPE_TIME, "log.cycle", sizeof(conf_sram.log.svc_conf.cycle), &conf_sram.log.svc_conf.cycle }, {TYPE_INT, "log.pwr", sizeof(conf_sram.log.radio_conf.pwr), &conf_sram.log.radio_conf.pwr }, {TYPE_INT, "log.freq", sizeof(conf_sram.log.radio_conf.freq), &conf_sram.log.radio_conf.freq }, {TYPE_INT, "log.mod", sizeof(conf_sram.log.radio_conf.mod), &conf_sram.log.radio_conf.mod }, @@ -129,20 +127,15 @@ const conf_command_t command_list[] = { {TYPE_STR, "log.path", sizeof(conf_sram.log.path), &conf_sram.log.path }, {TYPE_INT, "log.density", sizeof(conf_sram.log.density), &conf_sram.log.density }, - {TYPE_INT, "aprs.active", sizeof(conf_sram.aprs.thread_conf.active), &conf_sram.aprs.thread_conf.active }, - {TYPE_TIME, "aprs.init_delay", sizeof(conf_sram.aprs.thread_conf.init_delay), &conf_sram.aprs.thread_conf.init_delay }, + {TYPE_INT, "aprs.active", sizeof(conf_sram.aprs.svc_conf.active), &conf_sram.aprs.svc_conf.active }, + {TYPE_TIME, "aprs.init_delay", sizeof(conf_sram.aprs.svc_conf.init_delay), &conf_sram.aprs.svc_conf.init_delay }, {TYPE_INT, "aprs.rx.freq", sizeof(conf_sram.aprs.rx.radio_conf.freq), &conf_sram.aprs.rx.radio_conf.freq }, {TYPE_INT, "aprs.rx.mod", sizeof(conf_sram.aprs.rx.radio_conf.mod), &conf_sram.aprs.rx.radio_conf.mod }, {TYPE_INT, "aprs.rx.speed", sizeof(conf_sram.aprs.rx.radio_conf.speed), &conf_sram.aprs.rx.radio_conf.speed }, {TYPE_STR, "aprs.rx.call", sizeof(conf_sram.aprs.rx.call), &conf_sram.aprs.rx.call }, - {TYPE_INT, "aprs.base.freq", sizeof(conf_sram.aprs.base.radio_conf.freq), &conf_sram.aprs.base.radio_conf.freq }, - {TYPE_INT, "aprs.base.pwr", sizeof(conf_sram.aprs.base.radio_conf.pwr), &conf_sram.aprs.base.radio_conf.pwr }, - {TYPE_INT, "aprs.base.mod", sizeof(conf_sram.aprs.base.radio_conf.mod), &conf_sram.aprs.base.radio_conf.mod }, - {TYPE_INT, "aprs.base.cca", sizeof(conf_sram.aprs.base.radio_conf.cca), &conf_sram.aprs.base.radio_conf.cca }, - {TYPE_STR, "aprs.base.call", sizeof(conf_sram.aprs.base.call), &conf_sram.aprs.base.call }, - + {TYPE_INT, "aprs.digi.active", sizeof(conf_sram.aprs.digi.active), &conf_sram.aprs.digi.active }, {TYPE_INT, "aprs.digi.freq", sizeof(conf_sram.aprs.digi.radio_conf.freq), &conf_sram.aprs.digi.radio_conf.freq }, {TYPE_INT, "aprs.digi.pwr", sizeof(conf_sram.aprs.digi.radio_conf.pwr), &conf_sram.aprs.digi.radio_conf.pwr }, {TYPE_INT, "aprs.digi.mod", sizeof(conf_sram.aprs.digi.radio_conf.mod), &conf_sram.aprs.digi.radio_conf.mod }, @@ -150,14 +143,13 @@ const conf_command_t command_list[] = { {TYPE_STR, "aprs.digi.call", sizeof(conf_sram.aprs.digi.call), &conf_sram.aprs.digi.call }, {TYPE_STR, "aprs.digi.path", sizeof(conf_sram.aprs.digi.path), &conf_sram.aprs.digi.path }, {TYPE_INT, "aprs.digi.symbol", sizeof(conf_sram.aprs.digi.symbol), &conf_sram.aprs.digi.symbol }, - {TYPE_INT, "aprs.digi.beacon", sizeof(conf_sram.aprs.digi.beacon), &conf_sram.aprs.digi.beacon }, - {TYPE_INT, "aprs.digi.gps", sizeof(conf_sram.aprs.digi.gps), &conf_sram.aprs.digi.gps }, - {TYPE_INT, "lat", sizeof(conf_sram.lat), &conf_sram.lat }, - {TYPE_INT, "lon", sizeof(conf_sram.lon), &conf_sram.lon }, - {TYPE_INT, "alt", sizeof(conf_sram.alt), &conf_sram.alt }, - {TYPE_INT, "aprs.digi.cycle", sizeof(conf_sram.aprs.digi.cycle), &conf_sram.aprs.digi.cycle }, - {TYPE_INT, "aprs.digi.digi_active", sizeof(conf_sram.aprs.digi.active), &conf_sram.aprs.digi.active }, - {TYPE_INT, "aprs.freq", sizeof(conf_sram.aprs.freq), &conf_sram.aprs.freq }, + {TYPE_INT, "aprs.digi.beacon", sizeof(conf_sram.aprs.digi.beacon.svc_conf.active), &conf_sram.aprs.digi.beacon.svc_conf.active }, + + {TYPE_INT, "aprs.beacon.lat", sizeof(conf_sram.aprs.digi.beacon.lat), &conf_sram.aprs.digi.beacon.lat }, + {TYPE_INT, "aprs.beacon.lon", sizeof(conf_sram.aprs.digi.beacon.lon), &conf_sram.aprs.digi.beacon.lon }, + {TYPE_INT, "aprs.beacon.alt", sizeof(conf_sram.aprs.digi.beacon.alt), &conf_sram.aprs.digi.beacon.alt }, + {TYPE_INT, "aprs.beacon.cycle", sizeof(conf_sram.aprs.digi.beacon.svc_conf.cycle), &conf_sram.aprs.digi.beacon.svc_conf.cycle }, + {TYPE_INT, "keep_cam_switched_on", sizeof(conf_sram.keep_cam_switched_on), &conf_sram.keep_cam_switched_on }, {TYPE_INT, "gps_on_vbat", sizeof(conf_sram.gps_on_vbat), &conf_sram.gps_on_vbat }, {TYPE_INT, "gps_off_vbat", sizeof(conf_sram.gps_off_vbat), &conf_sram.gps_off_vbat }, @@ -166,6 +158,16 @@ const conf_command_t command_list[] = { {TYPE_INT, "gps_low_alt", sizeof(conf_sram.gps_low_alt), &conf_sram.gps_low_alt }, {TYPE_INT, "gps_high_alt", sizeof(conf_sram.gps_high_alt), &conf_sram.gps_high_alt }, + {TYPE_INT, "default.freq", sizeof(conf_sram.freq), &conf_sram.freq }, + + {TYPE_INT, "base.freq", sizeof(conf_sram.base.radio_conf.freq), &conf_sram.base.radio_conf.freq }, + {TYPE_INT, "base.pwr", sizeof(conf_sram.base.radio_conf.pwr), &conf_sram.base.radio_conf.pwr }, + {TYPE_INT, "base.mod", sizeof(conf_sram.base.radio_conf.mod), &conf_sram.base.radio_conf.mod }, + {TYPE_INT, "base.cca", sizeof(conf_sram.base.radio_conf.cca), &conf_sram.base.radio_conf.cca }, + {TYPE_STR, "base.call", sizeof(conf_sram.base.call), &conf_sram.base.call }, + + {TYPE_TIME, "tel_enc_cycle", sizeof(conf_sram.tel_enc_cycle), &conf_sram.tel_enc_cycle }, + {TYPE_NULL} }; @@ -1136,7 +1138,7 @@ static bool aprs_decode_message(packet_t pp) { /* Check which apps are enabled to accept APRS messages. */ bool pos_pri = (strcmp(conf_sram.pos_pri.call, dest) == 0) && (conf_sram.pos_pri.aprs_msg) - && (conf_sram.pos_pri.thread_conf.active); + && (conf_sram.pos_pri.svc_conf.active); if(pos_pri) { strcpy(identity.call, conf_sram.pos_pri.call); @@ -1146,7 +1148,7 @@ static bool aprs_decode_message(packet_t pp) { bool pos_sec = (strcmp(conf_sram.pos_sec.call, dest) == 0) && (conf_sram.pos_sec.aprs_msg) - && (conf_sram.pos_sec.thread_conf.active); + && (conf_sram.pos_sec.svc_conf.active); if(pos_sec) { strcpy(identity.call, conf_sram.pos_sec.call); strcpy(identity.path, conf_sram.pos_sec.path); @@ -1154,7 +1156,7 @@ static bool aprs_decode_message(packet_t pp) { } bool aprs_rx = (strcmp(conf_sram.aprs.rx.call, dest) == 0) - && (conf_sram.aprs.thread_conf.active); + && (conf_sram.aprs.svc_conf.active); if(aprs_rx) { strcpy(identity.call, conf_sram.aprs.rx.call); identity.symbol = conf_sram.aprs.rx.symbol; @@ -1163,7 +1165,7 @@ static bool aprs_decode_message(packet_t pp) { /* Even if the digi is not active respond on the digi (TX) call. */ bool aprs_tx = (strcmp(conf_sram.aprs.digi.call, dest) == 0) - && (conf_sram.aprs.thread_conf.active) + && (conf_sram.aprs.svc_conf.active) /*&& (conf_sram.aprs.digi.active)*/; /* Default already set tx parameters. */ diff --git a/tracker/software/source/threads/collector.c b/tracker/software/source/threads/collector.c index b66b352b..28a98cfd 100644 --- a/tracker/software/source/threads/collector.c +++ b/tracker/software/source/threads/collector.c @@ -31,10 +31,17 @@ static dataPoint_t dataPoints[2]; static dataPoint_t* lastDataPoint; static bool threadStarted = false; +#if USE_NEW_COLLECTOR != TRUE static uint8_t useGPS = 0; +#endif static uint8_t useCFG = 0; static uint8_t bme280_error; +/*===========================================================================*/ +/* Module external variables. */ +/*===========================================================================*/ +thread_t *collector_thd; + /** * Returns most recent data point which is complete. */ @@ -45,11 +52,11 @@ dataPoint_t* getLastDataPoint(void) { /** * */ -void waitForNewDataPoint(void) { +/*void waitForNewDataPoint(void) { uint32_t old_id = getLastDataPoint()->id; while(old_id == getLastDataPoint()->id) chThdSleep(TIME_S2I(1)); -} +}*/ /** * @brief Determine best fallback data when GPS not operable. @@ -107,8 +114,7 @@ static void aquirePosition(dataPoint_t* tp, dataPoint_t* ltp, gpsFix_t gpsFix = {0}; /* - * Switch on GPS if... - * position/time is requested by a service and power is available. + * Is there enough battery voltage for GPS? */ uint16_t batt = stm32_get_vbat(); if(batt < conf_sram.gps_on_vbat) { @@ -144,8 +150,8 @@ static void aquirePosition(dataPoint_t* tp, dataPoint_t* ltp, if(batt < conf_sram.gps_off_vbat) { /* - * GPS was switched on but battery fell below threshold. - * Switch off GPS. + * GPS was switched on but battery fell below threshold during acquisition. + * Switch off GPS and get fallback position data. */ GPS_Deinit(); @@ -156,7 +162,7 @@ static void aquirePosition(dataPoint_t* tp, dataPoint_t* ltp, } if(!isGPSLocked(&gpsFix)) { /* - * GPS was switched on but it failed to get a lock in timeout period. + * GPS was switched on but it failed to get a lock within timeout period. * Keep GPS switched on. */ TRACE_WARN("COLL > GPS sampling finished GPS LOSS"); @@ -190,8 +196,10 @@ static void aquirePosition(dataPoint_t* tp, dataPoint_t* ltp, if(timeout < TIME_S2I(60)) { TRACE_INFO("COLL > Keep GPS switched on because cycle < 60sec"); tp->gps_state = GPS_LOCKED2; - } else if(conf_sram.gps_onper_vbat != 0 && batt >= conf_sram.gps_onper_vbat) { - TRACE_INFO("COLL > Keep GPS switched on because VBAT >= %dmV", conf_sram.gps_onper_vbat); + } else if(conf_sram.gps_onper_vbat != 0 + && batt >= conf_sram.gps_onper_vbat) { + TRACE_INFO("COLL > Keep GPS switched on because VBAT >= %dmV", + conf_sram.gps_onper_vbat); tp->gps_state = GPS_LOCKED2; } else { TRACE_INFO("COLL > Switching off GPS"); @@ -441,18 +449,20 @@ THD_FUNCTION(collectorThread, arg) { // Determine cycle time and if GPS should be used. sysinterval_t data_cycle_time = TIME_S2I(600); // Default. - if(conf_sram.pos_pri.thread_conf.active && conf_sram.pos_sec.thread_conf.active) { // Both position threads are active - data_cycle_time = conf_sram.pos_pri.thread_conf.cycle < conf_sram.pos_sec.thread_conf.cycle ? conf_sram.pos_pri.thread_conf.cycle : conf_sram.pos_sec.thread_conf.cycle; // Choose the smallest cycle + if(conf_sram.pos_pri.svc_conf.active && conf_sram.pos_sec.svc_conf.active) { // Both position threads are active + data_cycle_time = conf_sram.pos_pri.svc_conf.cycle < conf_sram.pos_sec.svc_conf.cycle ? conf_sram.pos_pri.svc_conf.cycle : conf_sram.pos_sec.svc_conf.cycle; // Choose the smallest cycle (*useGPS)++; - } else if(conf_sram.pos_pri.thread_conf.active) { // Only primary position thread is active - data_cycle_time = conf_sram.pos_pri.thread_conf.cycle; + } else if(conf_sram.pos_pri.svc_conf.active) { // Only primary position thread is active + data_cycle_time = conf_sram.pos_pri.svc_conf.cycle; (*useGPS)++; - } else if(conf_sram.pos_sec.thread_conf.active) { // Only secondary position thread is active - data_cycle_time = conf_sram.pos_sec.thread_conf.cycle; + } else if(conf_sram.pos_sec.svc_conf.active) { // Only secondary position thread is active + data_cycle_time = conf_sram.pos_sec.svc_conf.cycle; (*useGPS)++; - } else if(conf_sram.aprs.thread_conf.active && conf_sram.aprs.digi.beacon) { // DIGI beacon is active - data_cycle_time = conf_sram.aprs.digi.cycle; - if(conf_sram.aprs.digi.gps) { + } else if(conf_sram.aprs.svc_conf.active + && conf_sram.aprs.digi.active + && conf_sram.aprs.digi.beacon.svc_conf.active) { // DIGI beacon is active + data_cycle_time = conf_sram.aprs.digi.beacon.svc_conf.cycle; + if(conf_sram.aprs.digi.beacon.svc_conf.active) { (*useGPS)++; } } else { // There must be an error @@ -495,7 +505,7 @@ THD_FUNCTION(collectorThread, arg) { continue; } TRACE_INFO("COLL > Time acquired from GPS"); - /* Switch GPS off. */ + /* Switch GPS off since there are no other users. */ GPS_Deinit(); } @@ -513,9 +523,9 @@ THD_FUNCTION(collectorThread, arg) { TRACE_INFO("COLL > Using fixed location"); getTime(&time); unixTimestamp2Date(&time, tp->gps_time); - tp->gps_alt = conf_sram.alt; - tp->gps_lat = conf_sram.lat; - tp->gps_lon = conf_sram.lon; + //tp->gps_alt = conf_sram.alt; + //tp->gps_lat = conf_sram.lat; + //tp->gps_lon = conf_sram.lon; tp->gps_state = GPS_FIXED; } @@ -556,7 +566,6 @@ THD_FUNCTION(collectorThread, arg) { * Any thread sending telemetry would request this service be started. */ THD_FUNCTION(configThread, arg) { - //uint8_t *useCFG = arg; (void)arg; while(true) chThdSleep(TIME_S2I(1)); } @@ -566,9 +575,167 @@ THD_FUNCTION(configThread, arg) { * TODO: To provide GPS status and data. */ THD_FUNCTION(gpsThread, arg) { - //uint8_t *useCFG = arg; (void)arg; - while(true) chThdSleep(TIME_S2I(1)); + + uint32_t id = 0; + //msg_t result; + + // Read time from RTC + ptime_t time; + getTime(&time); + dataPoints[0].gps_time = date2UnixTimestamp(&time); + dataPoints[1].gps_time = date2UnixTimestamp(&time); + + lastDataPoint = &dataPoints[0]; + dataPoint_t *newDataPoint = lastDataPoint++; + + // Get last data point from memory + TRACE_INFO("COLL > Read last data point from flash memory"); + dataPoint_t* lastLogPoint = flash_getNewestLogEntry(); + + if(lastLogPoint != NULL) { // If there is stored data point, then get it. + dataPoints[0].reset = lastLogPoint->reset+1; + dataPoints[1].reset = lastLogPoint->reset+1; + unixTimestamp2Date(&time, lastDataPoint->gps_time); + lastDataPoint->gps_lat = lastLogPoint->gps_lat; + lastDataPoint->gps_lon = lastLogPoint->gps_lon; + lastDataPoint->gps_alt = lastLogPoint->gps_alt; + lastDataPoint->gps_sats = lastLogPoint->gps_sats; + lastDataPoint->gps_ttff = lastLogPoint->gps_ttff; + + TRACE_INFO( + "COLL > Last data point (from memory)\r\n" + "%s Reset %d ID %d\r\n" + "%s Time %04d-%02d-%02d %02d:%02d:%02d\r\n" + "%s Latitude: %d.%07ddeg\r\n" + "%s Longitude: %d.%07ddeg\r\n" + "%s Altitude: %d Meter", + TRACE_TAB, lastLogPoint->reset, lastLogPoint->id, + TRACE_TAB, time.year, time.month, time.day, time.hour, + time.minute, time.day, + TRACE_TAB, lastDataPoint->gps_lat/10000000, + (lastDataPoint->gps_lat > 0 + ? 1:-1)*lastDataPoint->gps_lat%10000000, + TRACE_TAB, lastDataPoint->gps_lon/10000000, + (lastDataPoint->gps_lon > 0 + ? 1:-1)*lastDataPoint->gps_lon%10000000, + TRACE_TAB, lastDataPoint->gps_alt + ); + lastDataPoint->gps_state = GPS_LOG; // Mark dataPoint as LOG packet + //result = MSG_OK; + } else { + TRACE_INFO("COLL > No data point found in flash memory"); + /* State indicates that no valid stored position is available. */ + lastDataPoint->gps_state = GPS_OFF; + //result = MSG_TIMEOUT; + } + + // Measure telemetry + measureVoltage(lastDataPoint); + getSensors(lastDataPoint); + getGPIO(lastDataPoint); + setSystemStatus(lastDataPoint); + + // Write data point to Flash memory + flash_writeLogDataPoint(lastDataPoint); + + // Wait for position threads to start + //chThdSleep(TIME_MS2I(500)); + + //sysinterval_t cycle_time = chVTGetSystemTime(); + + // Determine cycle time and if GPS should be used. + sysinterval_t data_cycle_time = TIME_S2I(600); // Default. + + getTime(&time); + if(time.year == RTC_BASE_YEAR) { + /* + * The RTC is not set. + * Enable the GPS and attempt a lock which results in setting the RTC. + */ + TRACE_INFO("COLL > Acquire time using GPS"); + aquirePosition(newDataPoint, lastDataPoint, data_cycle_time - TIME_S2I(3)); + /* RTC is set in aquirePosition(...). */ + if(hasGPSacquiredLock(newDataPoint)) { + /* Acquisition succeeded. */ + TRACE_INFO("COLL > Time acquisition from GPS succeeded"); + /* Update with freshly acquired data. */ + lastDataPoint = newDataPoint; + GPS_Deinit(); + //result = MSG_OK; + } else { + TRACE_INFO("COLL > Time not acquired from GPS"); + //result = MSG_TIMEOUT; + } + } + + while(true) { /* Primary loop. */ + /* Wait for a request from a client. */ + thread_t *caller = chMsgWait(); + /* Fetch the message. */ + thd_pos_conf_t *config; + config = (thd_pos_conf_t *)chMsgGet(caller); + + TRACE_INFO("COLL > Respond to request for DATA COLLECTOR cycle"); + + dataPoint_t* tp = &dataPoints[(id+1) % 2]; // Current data point (the one which is processed now) + dataPoint_t* ltp = &dataPoints[ id % 2]; // Last data point + + /* Clear remnant data. */ + memset(tp, 0, sizeof(dataPoint_t)); + + /* Gather telemetry and system status data. */ + measureVoltage(tp); + getSensors(tp); + getGPIO(tp); + setSystemStatus(tp); + + if(!config->fixed) { + TRACE_INFO("COLL > Acquire position using GPS"); + aquirePosition(tp, ltp, data_cycle_time - TIME_S2I(3)); + //result = MSG_OK; + } else { + /* + * Update datapoint time from RTC. + * Set fixed location. + */ + TRACE_INFO("COLL > Using fixed location"); + getTime(&time); + unixTimestamp2Date(&time, tp->gps_time); + tp->gps_alt = config->alt; + tp->gps_lat = config->lat; + tp->gps_lon = config->lon; + tp->gps_state = GPS_FIXED; + //result = MSG_OK; + } + tp->id = ++id; // Serial ID + + // Trace data + unixTimestamp2Date(&time, tp->gps_time); + TRACE_INFO( "COLL > New data point available (ID=%d)\r\n" + "%s Time %04d-%02d-%02d %02d:%02d:%02d\r\n" + "%s Pos %d.%05d %d.%05d Alt %dm\r\n" + "%s Sats %d TTFF %dsec\r\n" + "%s ADC Vbat=%d.%03dV Vsol=%d.%03dV Pbat=%dmW\r\n" + "%s AIR p=%d.%01dPa T=%d.%02ddegC phi=%d.%01d%%\r\n" + "%s IOP IO1=%d IO2=%d IO3=%d IO4=%d", + tp->id, + TRACE_TAB, time.year, time.month, time.day, time.hour, time.minute, time.day, + TRACE_TAB, tp->gps_lat/10000000, (tp->gps_lat > 0 ? 1:-1)*(tp->gps_lat/100)%100000, tp->gps_lon/10000000, (tp->gps_lon > 0 ? 1:-1)*(tp->gps_lon/100)%100000, tp->gps_alt, + TRACE_TAB, tp->gps_sats, tp->gps_ttff, + TRACE_TAB, tp->adc_vbat/1000, (tp->adc_vbat%1000), tp->adc_vsol/1000, (tp->adc_vsol%1000), tp->pac_pbat, + TRACE_TAB, tp->sen_i1_press/10, tp->sen_i1_press%10, tp->sen_i1_temp/100, tp->sen_i1_temp%100, tp->sen_i1_hum/10, tp->sen_i1_hum%10, + TRACE_TAB, tp->gpio & 1, (tp->gpio >> 1) & 1, (tp->gpio >> 2) & 1, (tp->gpio >> 3) & 1 + ); + + // Write data point to Flash memory + flash_writeLogDataPoint(tp); + + // Switch last data point + lastDataPoint = tp; + /* Reply to the calling thread. */ + chMsgRelease(caller, (msg_t)tp); + } } /** @@ -578,7 +745,7 @@ void init_data_collector() { if(!threadStarted) { threadStarted = true; - +#if USE_NEW_COLLECTOR != TRUE TRACE_INFO("COLL > Startup data collector thread"); thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(10*1024), @@ -591,7 +758,9 @@ void init_data_collector() { } else { chThdSleep(TIME_MS2I(300)); // Wait a little bit until data collector has initialized first dataset } - +#else + thread_t *th; +#endif TRACE_INFO("CFG > Startup telemetry config thread"); th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(2*1024), @@ -615,6 +784,7 @@ void init_data_collector() { TRACE_ERROR("GPS > Could not start" " thread (not enough memory available)"); } else { + collector_thd = th; chThdSleep(TIME_MS2I(300)); } } diff --git a/tracker/software/source/threads/collector.h b/tracker/software/source/threads/collector.h index a5eeae67..23193eab 100644 --- a/tracker/software/source/threads/collector.h +++ b/tracker/software/source/threads/collector.h @@ -1,139 +1,148 @@ -#ifndef __COLLECTOR_H__ -#define __COLLECTOR_H__ - -#include "ch.h" -#include "hal.h" -#include "ptime.h" - -#define BME_STATUS_BITS 2 -#define BME_STATUS_MASK 0x3 -#define BME_OK_VALUE 0x0 -#define BME_FAIL_VALUE 0x1 -#define BME_NOT_FITTED_VALUE 0x2 - -#define BME_ALL_STATUS_MASK 0x3F -#define BME_ALL_STATUS_SHIFT 8 - -#define BMEI1_STATUS_SHIFT BME_ALL_STATUS_SHIFT -#define BMEI1_STATUS_MASK (BME_STATUS_MASK << BMEI1_STATUS_SHIFT) - -#define BMEE1_STATUS_SHIFT BMEI1_STATUS_SHIFT + BME_STATUS_BITS -#define BMEE1_STATUS_MASK (BME_STATUS_MASK << BMEE1_STATUS_SHIFT) - -#define BMEE2_STATUS_SHIFT BMEI1_STATUS_SHIFT + BME_STATUS)BITS -#define BMEE2_STATUS_MASK (BME_STATUS_MASK << BMEE2_STATUS_SHIFT) - -typedef enum { - GPS_LOCKED1, // The GPS is locked, the GPS has been switched off - GPS_LOCKED2, // The GPS is locked, the GPS has been kept switched on - GPS_LOSS, // The GPS was switched on all time but it couln't acquire a fix - GPS_LOWBATT1, // The GPS wasn't switched on because the battery has not enough energy - GPS_LOWBATT2, // The GPS was switched on but has been switched off prematurely while the battery has not enough energy (or is too cold) - GPS_LOG, // The tracker has just been switched on and the position has been taken from the log - GPS_OFF, // There was no prior acquisition by GPS - GPS_ERROR, // The GPS has a communication error - GPS_FIXED // Fixed location data used from APRS location -} gpsState_t; - -typedef struct { - // Voltage and current measurement - uint16_t adc_vsol; // Current solar voltage in mV - uint16_t adc_vbat; // Current battery voltage in mV - uint16_t pac_vsol; - uint16_t pac_vbat; - int16_t pac_pbat; - int16_t pac_psol; - - uint16_t light_intensity; - - // GPS - gpsState_t gps_state; // GPS state - uint8_t gps_sats; // Satellites used for solution - uint8_t gps_ttff; // Time to first fix in seconds - uint8_t gps_pdop; // Position DOP in 0.05 per arbitrary unit - uint16_t gps_alt; // Altitude in meter - int32_t gps_lat; // Latitude in 10^(-7)° per unit - int32_t gps_lon; // Longitude in 10^(-7)° per unit - - // BME280 (on board i1 + off board e1 & e2) - uint32_t sen_i1_press; // Airpressure in Pa*10 (in 0.1Pa) - uint32_t sen_e1_press; // Airpressure in Pa*10 (in 0.1Pa) - uint32_t sen_e2_press; // Airpressure in Pa*10 (in 0.1Pa) - - int16_t sen_i1_temp; // Temperature in 0.01°C per unit - int16_t sen_e1_temp; // Temperature in 0.01°C per unit - int16_t sen_e2_temp; // Temperature in 0.01°C per unit - - uint8_t sen_i1_hum; // Rel. humidity in % - uint8_t sen_e1_hum; // Rel. humidity in % - uint8_t sen_e2_hum; // Rel. humidity in % - - uint8_t dummy2; - - int16_t stm32_temp; - int16_t si446x_temp; - - uint16_t reset; - uint32_t id; // Serial ID - uint32_t gps_time; // GPS time - - uint32_t sys_time; // System time (in seconds) - uint32_t sys_error; // System error flags - /* - * Set system errors. - * - * Bit usage: - * - 0:1 I2C status - * - 2:2 GPS status - * - 3:4 pac1720 status - * - 5:7 OV5640 status - * - 8:9 BMEi1 status (0 = OK, 1 = Fail, 2 = Not fitted) - * - 10:11 BMEe1 status (0 = OK, 1 = Fail, 2 = Not fitted) - * - 12:13 BMEe2 status (0 = OK, 1 = Fail, 2 = Not fitted) - */ - - uint8_t gpio; // GPIO states -} dataPoint_t; - -void waitForNewDataPoint(void); -dataPoint_t* getLastDataPoint(void); -void getSensors(dataPoint_t* tp); -void setSystemStatus(dataPoint_t* tp); -void init_data_collector(void); - -/*===========================================================================*/ -/* Module inline functions. */ -/*===========================================================================*/ - -/** - * @brief Has GPS achieved lock (even if now switched off). - * - * @param[in] pointer to data point - * - * @returns result of check - * @retval true if lock has been achieved - * @retval false if lock has not yet been achieved - * - * @api - */ -#define hasGPSacquiredLock(dp) (dp->gps_state == GPS_LOCKED1 \ - || dp->gps_state == GPS_LOCKED2) - -/** - * @brief Is position valid. - * - * @param[in] pointer to data point - * - * @returns result of check - * @retval true if position data is valid - * @retval false if position not valid - * - * @api - */ -#define isPositionValid(dp) (dp->gps_state == GPS_LOCKED1 \ - || dp->gps_state == GPS_LOCKED2 \ - || dp->gps_state == GPS_FIXED \ - || dp->gps_state == GPS_LOG) - -#endif /* __COLLECTOR_H__ */ - +#ifndef __COLLECTOR_H__ +#define __COLLECTOR_H__ + +#include "ch.h" +#include "hal.h" +#include "ptime.h" +#include "types.h" + +#define BME_STATUS_BITS 2 +#define BME_STATUS_MASK 0x3 +#define BME_OK_VALUE 0x0 +#define BME_FAIL_VALUE 0x1 +#define BME_NOT_FITTED_VALUE 0x2 + +#define BME_ALL_STATUS_MASK 0x3F +#define BME_ALL_STATUS_SHIFT 8 + +#define BMEI1_STATUS_SHIFT BME_ALL_STATUS_SHIFT +#define BMEI1_STATUS_MASK (BME_STATUS_MASK << BMEI1_STATUS_SHIFT) + +#define BMEE1_STATUS_SHIFT BMEI1_STATUS_SHIFT + BME_STATUS_BITS +#define BMEE1_STATUS_MASK (BME_STATUS_MASK << BMEE1_STATUS_SHIFT) + +#define BMEE2_STATUS_SHIFT BMEI1_STATUS_SHIFT + BME_STATUS)BITS +#define BMEE2_STATUS_MASK (BME_STATUS_MASK << BMEE2_STATUS_SHIFT) + +#define USE_NEW_COLLECTOR TRUE + +typedef enum { + GPS_LOCKED1, // The GPS is locked, the GPS has been switched off + GPS_LOCKED2, // The GPS is locked, the GPS has been kept switched on + GPS_LOSS, // The GPS was switched on all time but it couln't acquire a fix + GPS_LOWBATT1, // The GPS wasn't switched on because the battery has not enough energy + GPS_LOWBATT2, // The GPS was switched on but has been switched off prematurely while the battery has not enough energy (or is too cold) + GPS_LOG, // The tracker has just been switched on and the position has been taken from the log + GPS_OFF, // There was no prior acquisition by GPS + GPS_ERROR, // The GPS has a communication error + GPS_FIXED // Fixed location data used from APRS location +} gpsState_t; + +typedef struct { + // Voltage and current measurement + uint16_t adc_vsol; // Current solar voltage in mV + uint16_t adc_vbat; // Current battery voltage in mV + uint16_t pac_vsol; + uint16_t pac_vbat; + int16_t pac_pbat; + int16_t pac_psol; + + uint16_t light_intensity; + + // GPS + gpsState_t gps_state; // GPS state + uint8_t gps_sats; // Satellites used for solution + uint8_t gps_ttff; // Time to first fix in seconds + uint8_t gps_pdop; // Position DOP in 0.05 per arbitrary unit + uint16_t gps_alt; // Altitude in meter + int32_t gps_lat; // Latitude in 10^(-7)° per unit + int32_t gps_lon; // Longitude in 10^(-7)° per unit + + // BME280 (on board i1 + off board e1 & e2) + uint32_t sen_i1_press; // Airpressure in Pa*10 (in 0.1Pa) + uint32_t sen_e1_press; // Airpressure in Pa*10 (in 0.1Pa) + uint32_t sen_e2_press; // Airpressure in Pa*10 (in 0.1Pa) + + int16_t sen_i1_temp; // Temperature in 0.01°C per unit + int16_t sen_e1_temp; // Temperature in 0.01°C per unit + int16_t sen_e2_temp; // Temperature in 0.01°C per unit + + uint8_t sen_i1_hum; // Rel. humidity in % + uint8_t sen_e1_hum; // Rel. humidity in % + uint8_t sen_e2_hum; // Rel. humidity in % + + uint8_t dummy2; + + int16_t stm32_temp; + int16_t si446x_temp; + + uint16_t reset; + uint32_t id; // Serial ID + uint32_t gps_time; // GPS time + + uint32_t sys_time; // System time (in seconds) + uint32_t sys_error; // System error flags + /* + * Set system errors. + * + * Bit usage: + * - 0:1 I2C status + * - 2:2 GPS status + * - 3:4 pac1720 status + * - 5:7 OV5640 status + * - 8:9 BMEi1 status (0 = OK, 1 = Fail, 2 = Not fitted) + * - 10:11 BMEe1 status (0 = OK, 1 = Fail, 2 = Not fitted) + * - 12:13 BMEe2 status (0 = OK, 1 = Fail, 2 = Not fitted) + */ + + uint8_t gpio; // GPIO states +} dataPoint_t; + + +/*typedef struct telemRequest { + dataPoint_t dp; + thd_pos_conf_t *conf; +} telem_request_t;*/ + +//void waitForNewDataPoint(void); +dataPoint_t* getLastDataPoint(void); +void getSensors(dataPoint_t* tp); +void setSystemStatus(dataPoint_t* tp); +void init_data_collector(void); + +/*===========================================================================*/ +/* Module inline functions. */ +/*===========================================================================*/ + +/** + * @brief Has GPS achieved lock (even if now switched off). + * + * @param[in] pointer to data point + * + * @returns result of check + * @retval true if lock has been achieved + * @retval false if lock has not yet been achieved + * + * @api + */ +#define hasGPSacquiredLock(dp) (dp->gps_state == GPS_LOCKED1 \ + || dp->gps_state == GPS_LOCKED2) + +/** + * @brief Is position valid. + * + * @param[in] pointer to data point + * + * @returns result of check + * @retval true if position data is valid + * @retval false if position not valid + * + * @api + */ +#define isPositionValid(dp) (dp->gps_state == GPS_LOCKED1 \ + || dp->gps_state == GPS_LOCKED2 \ + || dp->gps_state == GPS_FIXED \ + || dp->gps_state == GPS_LOG) + +#endif /* __COLLECTOR_H__ */ + diff --git a/tracker/software/source/threads/rxtx/beacon.c b/tracker/software/source/threads/rxtx/beacon.c index 13e4405d..ecd55851 100644 --- a/tracker/software/source/threads/rxtx/beacon.c +++ b/tracker/software/source/threads/rxtx/beacon.c @@ -1,157 +1,177 @@ -#include "ch.h" -#include "hal.h" - -#include "debug.h" -#include "threads.h" -#include "config.h" -#include "radio.h" -#include "aprs.h" -#include "sleep.h" -#include "chprintf.h" -#include -#include -#include "watchdog.h" - -/* - * - */ -THD_FUNCTION(bcnThread, arg) { - thd_aprs_conf_t* conf = (thd_aprs_conf_t *)arg; - - // Wait - if(conf->thread_conf.init_delay) chThdSleep(conf->thread_conf.init_delay); - - // Start data collector (if not running yet) - init_data_collector(); - - // Start position thread - TRACE_INFO("BCN > Startup beacon thread"); - - // Set telemetry configuration transmission variables - sysinterval_t last_conf_transmission = - chVTGetSystemTime() - conf->digi.tel_enc_cycle; - sysinterval_t time = chVTGetSystemTime(); - - while(true) { - char code_s[100]; - pktDisplayFrequencyCode(conf->digi.radio_conf.freq, - code_s, sizeof(code_s)); - TRACE_INFO("POS > Do module BEACON cycle for %s on %s", - conf->digi.call, code_s); - - dataPoint_t* dataPoint = getLastDataPoint(); - if(!p_sleep(&conf->thread_conf.sleep_conf)) { - - // Telemetry encoding parameter transmissions - if(conf->digi.tel_enc_cycle != 0 && last_conf_transmission - + conf->digi.tel_enc_cycle < chVTGetSystemTime()) { - - TRACE_INFO("BCN > Transmit telemetry configuration"); - - // Encode and transmit telemetry config packet - for(uint8_t type = 0; type < APRS_NUM_TELEM_GROUPS; type++) { - packet_t packet = aprs_encode_telemetry_configuration( - conf->digi.call, - conf->digi.path, - conf->digi.call, - type); - if(packet == NULL) { - TRACE_WARN("BCN > No free packet objects for" - " telemetry config transmission"); - } else { - if(!transmitOnRadio(packet, - conf->digi.radio_conf.freq, - 0, - 0, - conf->digi.radio_conf.pwr, - conf->digi.radio_conf.mod, - conf->digi.radio_conf.cca)) { - TRACE_ERROR("BCN > Failed to transmit telemetry config"); - } - } - chThdSleep(TIME_S2I(5)); - } - last_conf_transmission += conf->digi.tel_enc_cycle; - } - - while(!isPositionValid(dataPoint)) { - TRACE_INFO("BCN > Waiting for position data for beacon"); - chThdSleep(TIME_S2I(60)); - continue; - } - - TRACE_INFO("BCN > Transmit position and telemetry"); - - // Encode/Transmit position packet - packet_t packet = aprs_encode_position_and_telemetry(conf->digi.call, - conf->digi.path, - conf->digi.symbol, - dataPoint, true); - if(packet == NULL) { - TRACE_WARN("BCN > No free packet objects" - " for position transmission"); - } else { - if(!transmitOnRadio(packet, - conf->digi.radio_conf.freq, - 0, - 0, - conf->digi.radio_conf.pwr, - conf->digi.radio_conf.mod, - conf->digi.radio_conf.cca)) { - TRACE_ERROR("BCN > failed to transmit beacon data"); - } - chThdSleep(TIME_S2I(5)); - } - - TRACE_INFO("BCN > Transmit recently heard direct"); - /* - * Encode/Transmit APRSD packet. - * This is a tracker originated message (not a reply to a request). - * The message will be addressed to the base station if set. - * Else send it to device identity. - */ - char *call = conf_sram.aprs.base.enabled - ? conf_sram.aprs.base.call : conf->digi.call; - - /* - * Send message from this device. - * Use call sign and path as specified in base config. - * There is no acknowledgment requested. - */ - packet = aprs_compose_aprsd_message( - conf->digi.call, - conf->base.path, - call); - if(packet == NULL) { - TRACE_WARN("BCN > No free packet objects " - "or badly formed APRSD message"); - } else { - if(!transmitOnRadio(packet, - conf->digi.radio_conf.freq, - 0, - 0, - conf->digi.radio_conf.pwr, - conf->digi.radio_conf.mod, - conf->digi.radio_conf.cca - )) { - TRACE_ERROR("BCN > Failed to transmit APRSD data"); - } - chThdSleep(TIME_S2I(5)); - } - } /* psleep */ - time = waitForTrigger(time, conf->digi.cycle); - } -} - -/* - * - */ -void start_beacon_thread(thd_aprs_conf_t *conf) { - thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(10*1024), - "BCN", LOWPRIO, bcnThread, conf); - if(!th) { - // Print startup error, do not start watchdog for this thread - TRACE_ERROR("BCN > Could not start thread (not enough memory available)"); - } -} - +#include "ch.h" +#include "hal.h" + +#include "debug.h" +#include "threads.h" +#include "config.h" +#include "radio.h" +#include "aprs.h" +#include "sleep.h" +#include "chprintf.h" +#include +#include +#include "watchdog.h" + +/* + * + */ +THD_FUNCTION(bcnThread, arg) { + thd_aprs_conf_t* conf = (thd_aprs_conf_t *)arg; + + // Wait + if(conf->svc_conf.init_delay) chThdSleep(conf->svc_conf.init_delay); + + // Start data collector (if not running yet) + init_data_collector(); + + // Start position thread + TRACE_INFO("BCN > Startup beacon thread"); + + // Set telemetry configuration transmission variables + sysinterval_t last_conf_transmission = + chVTGetSystemTime() - conf_sram.tel_enc_cycle; + sysinterval_t time = chVTGetSystemTime(); + + /* + * Already waited for APRS start delay. + * Now wait for our further delay before starting. + */ + sysinterval_t delay = conf->digi.beacon.svc_conf.init_delay; + + chThdSleep(delay); + + while(true) { + + char code_s[100]; + pktDisplayFrequencyCode(conf->digi.radio_conf.freq, + code_s, sizeof(code_s)); + TRACE_INFO("POS > Do module BEACON cycle for %s on %s", + conf->digi.call, code_s); +#if USE_NEW_COLLECTOR == TRUE + extern thread_t *collector_thd; + /* + * Pass pointer to beacon config to the collector thread. + * TODO: return message should be pointer to latest updated datapoint? + */ + dataPoint_t * dataPoint = + (dataPoint_t *)chMsgSend(collector_thd, (msg_t)&conf->digi.beacon); +#else + dataPoint_t* dataPoint = getLastDataPoint(); +#endif + if(!p_sleep(&conf->svc_conf.sleep_conf)) { + + // Telemetry encoding parameter transmissions + if(conf_sram.tel_enc_cycle != 0 && last_conf_transmission + + conf_sram.tel_enc_cycle < chVTGetSystemTime()) { + + TRACE_INFO("BCN > Transmit telemetry configuration"); + + // Encode and transmit telemetry config packet + for(uint8_t type = 0; type < APRS_NUM_TELEM_GROUPS; type++) { + packet_t packet = aprs_encode_telemetry_configuration( + conf->digi.call, + conf->digi.path, + conf->digi.call, + type); + if(packet == NULL) { + TRACE_WARN("BCN > No free packet objects for" + " telemetry config transmission"); + } else { + if(!transmitOnRadio(packet, + conf->digi.radio_conf.freq, + 0, + 0, + conf->digi.radio_conf.pwr, + conf->digi.radio_conf.mod, + conf->digi.radio_conf.cca)) { + TRACE_ERROR("BCN > Failed to transmit telemetry config"); + } + } + chThdSleep(TIME_S2I(5)); + } + last_conf_transmission += conf_sram.tel_enc_cycle; + } + + while(!isPositionValid(dataPoint)) { + TRACE_INFO("BCN > Waiting for position data for beacon"); + chThdSleep(TIME_S2I(60)); + continue; + } + + TRACE_INFO("BCN > Transmit position and telemetry"); + + // Encode/Transmit position packet + packet_t packet = aprs_encode_position_and_telemetry(conf->digi.call, + conf->digi.path, + conf->digi.symbol, + dataPoint, true); + if(packet == NULL) { + TRACE_WARN("BCN > No free packet objects" + " for position transmission"); + } else { + if(!transmitOnRadio(packet, + conf->digi.radio_conf.freq, + 0, + 0, + conf->digi.radio_conf.pwr, + conf->digi.radio_conf.mod, + conf->digi.radio_conf.cca)) { + TRACE_ERROR("BCN > failed to transmit beacon data"); + } + chThdSleep(TIME_S2I(5)); + } + + TRACE_INFO("BCN > Transmit recently heard direct"); + /* + * Encode/Transmit APRSD packet. + * This is a tracker originated message (not a reply to a request). + * The message will be addressed to the base station if set. + * Else send it to device identity. + */ + char *call = conf_sram.base.enabled + ? conf_sram.base.call : conf->digi.call; + + /* + * Send message from this device. + * Use call sign and path as specified in base config. + * There is no acknowledgment requested. + */ + packet = aprs_compose_aprsd_message( + conf_sram.aprs.digi.call, + //conf->digi.call, + conf_sram.base.path, + //conf->base.path, + call); + if(packet == NULL) { + TRACE_WARN("BCN > No free packet objects " + "or badly formed APRSD message"); + } else { + if(!transmitOnRadio(packet, + conf->digi.radio_conf.freq, + 0, + 0, + conf->digi.radio_conf.pwr, + conf->digi.radio_conf.mod, + conf->digi.radio_conf.cca + )) { + TRACE_ERROR("BCN > Failed to transmit APRSD data"); + } + chThdSleep(TIME_S2I(5)); + } + } /* psleep */ + time = waitForTrigger(time, conf->digi.beacon.svc_conf.cycle); + } +} + +/* + * + */ +void start_beacon_thread(thd_aprs_conf_t *conf) { + thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(10*1024), + "BCN", LOWPRIO, bcnThread, conf); + if(!th) { + // Print startup error, do not start watchdog for this thread + TRACE_ERROR("BCN > Could not start thread (not enough memory available)"); + } +} + diff --git a/tracker/software/source/threads/rxtx/image.c b/tracker/software/source/threads/rxtx/image.c index 99ef85cc..495937cd 100644 --- a/tracker/software/source/threads/rxtx/image.c +++ b/tracker/software/source/threads/rxtx/image.c @@ -1,758 +1,758 @@ -#include "ch.h" -#include "hal.h" -#include "chprintf.h" - -#include "debug.h" -#include "threads.h" -#include "ov5640.h" -#include "pi2c.h" -#include "ssdv.h" -#include "aprs.h" -#include "radio.h" -#include "base91.h" -#include -#include "types.h" -#include "sleep.h" -#include "watchdog.h" -#include "flash.h" -#include "sd.h" -#include "collector.h" -#include "image.h" - -const uint8_t noCameraFound[4071] = { - 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48, - 0x00, 0x48, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0x10, 0x0B, 0x0C, 0x0E, 0x0C, 0x0A, 0x10, - 0x0E, 0x0D, 0x0E, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, 0x1A, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, - 0x25, 0x1D, 0x28, 0x3A, 0x33, 0x3D, 0x3C, 0x39, 0x33, 0x38, 0x37, 0x40, 0x48, 0x5C, 0x4E, 0x40, - 0x44, 0x57, 0x45, 0x37, 0x38, 0x50, 0x6D, 0x51, 0x57, 0x5F, 0x62, 0x67, 0x68, 0x67, 0x3E, 0x4D, - 0x71, 0x79, 0x70, 0x64, 0x78, 0x5C, 0x65, 0x67, 0x63, 0xFF, 0xDB, 0x00, 0x43, 0x01, 0x11, 0x12, - 0x12, 0x18, 0x15, 0x18, 0x2F, 0x1A, 0x1A, 0x2F, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, 0x63, 0x63, - 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, - 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, - 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0xFF, 0xC0, - 0x00, 0x11, 0x08, 0x00, 0x80, 0x00, 0xA0, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, - 0x01, 0xFF, 0xC4, 0x00, 0x1B, 0x00, 0x00, 0x02, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x04, 0x05, 0x07, 0x06, 0x01, 0xFF, 0xC4, - 0x00, 0x47, 0x10, 0x00, 0x00, 0x05, 0x02, 0x02, 0x05, 0x05, 0x0C, 0x06, 0x09, 0x05, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x11, 0x12, 0x21, 0x06, 0x13, 0x31, 0x41, - 0x81, 0x14, 0x15, 0x16, 0x42, 0x51, 0x22, 0x32, 0x53, 0x54, 0x61, 0x71, 0x91, 0x92, 0xA1, 0xC1, - 0xE1, 0xE2, 0x36, 0x55, 0x63, 0x93, 0xA3, 0xB3, 0x23, 0x24, 0x52, 0x62, 0x64, 0x65, 0xA2, 0xB1, - 0xD1, 0x07, 0x35, 0x43, 0x74, 0x82, 0xB2, 0xFF, 0xC4, 0x00, 0x1A, 0x01, 0x01, 0x01, 0x00, 0x03, - 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0x03, - 0x04, 0x06, 0x01, 0xFF, 0xC4, 0x00, 0x39, 0x11, 0x01, 0x00, 0x00, 0x03, 0x04, 0x06, 0x07, 0x05, - 0x08, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x11, 0x05, 0x12, 0x51, 0xC1, - 0x03, 0x04, 0x13, 0x31, 0x42, 0x81, 0x14, 0x15, 0x21, 0x43, 0x52, 0xA1, 0xE1, 0x06, 0x32, 0x33, - 0x61, 0x62, 0x16, 0x22, 0x41, 0x63, 0x71, 0x82, 0xA2, 0xE2, 0x23, 0x24, 0xD1, 0x91, 0xFF, 0xDA, - 0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3F, 0x00, 0xF4, 0x00, 0x00, 0x0A, - 0x7F, 0xAB, 0xC4, 0x49, 0xB4, 0xF8, 0x39, 0xE4, 0xD9, 0x21, 0x22, 0x43, 0x60, 0x01, 0x5E, 0x66, - 0xC4, 0x71, 0xF7, 0x0B, 0xD6, 0x2F, 0x79, 0xCB, 0x34, 0x5B, 0x5F, 0x83, 0x9E, 0x4A, 0xC2, 0xF2, - 0x20, 0x00, 0x97, 0xFA, 0xBC, 0x44, 0x9B, 0x4F, 0x74, 0x9C, 0xF2, 0x67, 0x21, 0x22, 0x43, 0x60, - 0x00, 0x89, 0x7D, 0xEA, 0x3C, 0xE6, 0x2F, 0x58, 0xBD, 0xE7, 0x2C, 0xDE, 0x9B, 0xD9, 0xFE, 0xF7, - 0xF6, 0xE6, 0xAA, 0x2F, 0x3D, 0x30, 0x00, 0xA7, 0xFA, 0xBC, 0x44, 0x9B, 0x4F, 0x83, 0x9E, 0x4C, - 0xE4, 0x24, 0x48, 0x6C, 0x00, 0x2B, 0xCC, 0xD8, 0x8E, 0x3E, 0xE1, 0x7A, 0xC5, 0xEF, 0x39, 0x66, - 0x8B, 0x6B, 0xF0, 0x73, 0xC9, 0x58, 0x5E, 0x44, 0x00, 0x3A, 0xA8, 0xF2, 0x4E, 0x80, 0x01, 0x4F, - 0xF5, 0x78, 0x89, 0x36, 0x9F, 0x07, 0x3C, 0x9B, 0x24, 0x24, 0x48, 0x6C, 0x00, 0x2B, 0xCC, 0xD8, - 0x8E, 0x3E, 0xE1, 0x7A, 0xC5, 0xEF, 0x39, 0x66, 0x8B, 0x6B, 0xF0, 0x73, 0xC9, 0x58, 0x5E, 0x44, - 0x00, 0x12, 0xFF, 0x00, 0x57, 0x88, 0x93, 0x69, 0xEE, 0x93, 0x9E, 0x4C, 0xE4, 0x24, 0x48, 0x6C, - 0x00, 0x11, 0x2F, 0xBD, 0x47, 0x9C, 0xC5, 0xEB, 0x17, 0xBC, 0xE5, 0x9B, 0xD3, 0x7B, 0x3F, 0xDE, - 0xFE, 0xDC, 0xD5, 0x45, 0xE7, 0xA6, 0x00, 0x14, 0xFF, 0x00, 0x57, 0x88, 0x93, 0x69, 0xF0, 0x73, - 0xC9, 0x9C, 0x84, 0x89, 0x0D, 0x80, 0x05, 0x79, 0x9B, 0x11, 0xC7, 0xDC, 0x2F, 0x58, 0xBD, 0xE7, - 0x2C, 0xD1, 0x6D, 0x7E, 0x0E, 0x79, 0x2B, 0x0B, 0xC8, 0x80, 0x07, 0x55, 0x1E, 0x49, 0xD0, 0x00, - 0x29, 0xFE, 0xAF, 0x11, 0x26, 0xD3, 0xE0, 0xE7, 0x93, 0x64, 0x84, 0x89, 0x0D, 0x80, 0x05, 0x79, - 0x9B, 0x11, 0xC7, 0xDC, 0x2F, 0x58, 0xBD, 0xE7, 0x2C, 0xD1, 0x6D, 0x7E, 0x0E, 0x79, 0x2B, 0x0B, - 0xC8, 0x80, 0x02, 0x5F, 0xEA, 0xF1, 0x12, 0x6D, 0x3D, 0xD2, 0x73, 0xC9, 0x9C, 0x84, 0x89, 0x0D, - 0x80, 0x02, 0x25, 0xF7, 0xA8, 0xF3, 0x98, 0xBD, 0x62, 0xF7, 0x9C, 0xB3, 0x7A, 0x6F, 0x67, 0xFB, - 0xDF, 0xDB, 0x9A, 0xA8, 0xBC, 0xF4, 0xC0, 0x02, 0x9F, 0xEA, 0xF1, 0x12, 0x6D, 0x3E, 0x0E, 0x79, - 0x33, 0x90, 0x91, 0x21, 0xB0, 0x00, 0xAF, 0x33, 0x62, 0x38, 0xFB, 0x85, 0xEB, 0x17, 0xBC, 0xE5, - 0x9A, 0x2D, 0xAF, 0xC1, 0xCF, 0x25, 0x61, 0x79, 0x10, 0x00, 0xEA, 0xA3, 0xC9, 0x3A, 0x00, 0x05, - 0x3F, 0xD5, 0xE2, 0x24, 0xDA, 0x7C, 0x1C, 0xF2, 0x6C, 0x90, 0x91, 0x21, 0xB0, 0x00, 0xAF, 0x33, - 0x62, 0x38, 0xFB, 0x85, 0xEB, 0x17, 0xBC, 0xE5, 0x9A, 0x2D, 0xAF, 0xC1, 0xCF, 0x25, 0x61, 0x79, - 0x10, 0x00, 0x4B, 0xFD, 0x5E, 0x22, 0x4D, 0xA7, 0xBA, 0x4E, 0x79, 0x33, 0x90, 0x91, 0x21, 0xB0, - 0x00, 0x44, 0xBE, 0xF5, 0x1E, 0x73, 0x17, 0xAC, 0x5E, 0xF3, 0x96, 0x6F, 0x4D, 0xEC, 0xFF, 0x00, - 0x7B, 0xFB, 0x73, 0x55, 0x17, 0x9E, 0x98, 0x00, 0x53, 0xFD, 0x5E, 0x22, 0x4D, 0xA7, 0xC1, 0xCF, - 0x26, 0x72, 0x12, 0x24, 0x36, 0x00, 0x15, 0xE6, 0x6C, 0x47, 0x1F, 0x70, 0xBD, 0x62, 0xF7, 0x9C, - 0xB3, 0x45, 0xB5, 0xF8, 0x39, 0xE4, 0xAC, 0x2F, 0x22, 0x00, 0x1D, 0x54, 0x79, 0x27, 0x43, 0x0A, - 0xAF, 0xA4, 0xCC, 0xD2, 0xA6, 0x9C, 0x67, 0x23, 0xB8, 0xE2, 0x89, 0x24, 0xAC, 0x49, 0x32, 0x22, - 0xCC, 0x06, 0x7B, 0x9A, 0x6B, 0x1D, 0x76, 0xB4, 0x47, 0x4A, 0xDF, 0xBC, 0x43, 0x8F, 0x5B, 0xD5, - 0xA3, 0xA7, 0xBB, 0x48, 0xD2, 0x95, 0x65, 0x2C, 0xD4, 0x43, 0xA6, 0x31, 0xFC, 0x51, 0xDF, 0x58, - 0x87, 0x17, 0x56, 0x4F, 0xE2, 0x65, 0x7C, 0x74, 0xC6, 0x3F, 0x8A, 0x3B, 0xEB, 0x10, 0x75, 0x64, - 0xFE, 0x22, 0xF9, 0x4F, 0xE9, 0x6B, 0x0E, 0x92, 0x6D, 0x15, 0xC2, 0xB7, 0xEF, 0x10, 0xA3, 0xA8, - 0x68, 0x63, 0xAA, 0xDE, 0xAC, 0x6B, 0x5A, 0x79, 0x55, 0xC3, 0xAE, 0xEA, 0xF1, 0xD6, 0x2E, 0xD2, - 0x34, 0xA5, 0x72, 0x2B, 0xA4, 0xEC, 0xF8, 0xB3, 0x9E, 0xB1, 0x0A, 0x1B, 0x68, 0x60, 0xE0, 0xEA, - 0xC9, 0xFC, 0x43, 0xA4, 0xEC, 0xF8, 0xB3, 0x9E, 0xB1, 0x06, 0xDA, 0x18, 0x1D, 0x59, 0x3F, 0x89, - 0x07, 0x34, 0x91, 0x95, 0xDA, 0xD1, 0xD6, 0x56, 0xF2, 0x90, 0xE3, 0xD6, 0xE4, 0xDB, 0xDD, 0xA7, - 0x65, 0x2A, 0xCA, 0x5B, 0x3A, 0x68, 0x71, 0x21, 0xD2, 0x16, 0xBC, 0x02, 0xFD, 0x24, 0x38, 0xBA, - 0x1C, 0xD8, 0xB2, 0xE8, 0x13, 0x62, 0x3A, 0x42, 0xD7, 0x80, 0x5F, 0xA4, 0x83, 0xA1, 0xCD, 0x89, - 0xD0, 0x26, 0xC4, 0xB7, 0xAB, 0xAD, 0x3A, 0x49, 0x22, 0x65, 0x65, 0x6F, 0x29, 0x0A, 0x3A, 0x87, - 0xFA, 0xB7, 0xAB, 0xDB, 0x5A, 0x79, 0x55, 0x56, 0xCD, 0xFF, 0x00, 0x4E, 0xFD, 0xEE, 0xDA, 0xD3, - 0xCA, 0xBF, 0xF4, 0x9E, 0x77, 0x6F, 0xC1, 0x2B, 0xD2, 0x28, 0xF4, 0xC9, 0x70, 0x55, 0xE9, 0xF2, - 0xE0, 0x39, 0xDD, 0xBF, 0x04, 0xAF, 0x48, 0x74, 0xC9, 0x70, 0x3A, 0x7C, 0xB8, 0x20, 0xE5, 0x51, - 0x0B, 0xB5, 0x9B, 0x51, 0x5B, 0xCA, 0x38, 0xF5, 0xB9, 0xF6, 0xF7, 0x69, 0xD9, 0x4A, 0xB2, 0x96, - 0xD1, 0x96, 0x1C, 0x28, 0x73, 0x8A, 0x3C, 0x1A, 0xBD, 0x23, 0x8B, 0x63, 0x1C, 0x59, 0x75, 0x9C, - 0x9E, 0x11, 0xCE, 0x28, 0xF0, 0x6A, 0xF4, 0x86, 0xC6, 0x38, 0x9D, 0x67, 0x27, 0x84, 0xA7, 0xE6, - 0x25, 0xD2, 0x4D, 0x90, 0x65, 0x61, 0x47, 0x50, 0xD3, 0x43, 0x55, 0xBD, 0x58, 0x56, 0xB4, 0xF2, - 0xAB, 0x87, 0x5D, 0xD6, 0x21, 0xAC, 0x5D, 0xA4, 0x29, 0x4A, 0xE4, 0x5A, 0x5D, 0x25, 0x28, 0x8A, - 0xDB, 0x45, 0x4D, 0x16, 0xBF, 0x2E, 0x92, 0x78, 0x49, 0x08, 0x6F, 0x4F, 0x8C, 0x94, 0x30, 0x50, - 0x60, 0xEA, 0xA3, 0xC9, 0x3A, 0x18, 0x6C, 0x9B, 0x85, 0xA5, 0x55, 0x63, 0x65, 0xC4, 0xB4, 0xE9, - 0x52, 0x97, 0x81, 0xC5, 0x2B, 0x09, 0x20, 0xEE, 0x9B, 0x19, 0x99, 0xEC, 0xB7, 0x68, 0x08, 0xD3, - 0xA4, 0xB4, 0xFE, 0x93, 0xE8, 0xFA, 0x1C, 0x92, 0xC4, 0xBA, 0x8B, 0x68, 0x7C, 0xA5, 0x3E, 0xC9, - 0x92, 0x89, 0x57, 0x4A, 0xB0, 0x16, 0x22, 0xC8, 0xCC, 0x8A, 0xE0, 0x30, 0xE5, 0x4B, 0x4D, 0x5A, - 0x4C, 0x08, 0x4E, 0xD7, 0x64, 0xCE, 0x69, 0xD9, 0x48, 0x4A, 0xD0, 0xEB, 0x1A, 0xB2, 0x41, 0x19, - 0xDB, 0x15, 0xEE, 0x79, 0xE6, 0x60, 0x17, 0xA4, 0x95, 0x99, 0xBC, 0xB6, 0xA1, 0x4A, 0x49, 0xA5, - 0xA8, 0x08, 0x70, 0xDA, 0x44, 0x74, 0xA0, 0x89, 0x29, 0x24, 0x2B, 0x23, 0x2C, 0xB6, 0xE5, 0x7E, - 0x20, 0x3D, 0x0D, 0x6E, 0x94, 0xFA, 0x34, 0x35, 0x50, 0x8D, 0x85, 0xA5, 0x10, 0x18, 0x61, 0xF4, - 0x9D, 0xB6, 0xB8, 0x66, 0xAD, 0x6E, 0x7D, 0x84, 0x4A, 0xB8, 0x06, 0x9B, 0x69, 0x98, 0x5A, 0x38, - 0xDA, 0x52, 0x5A, 0xF8, 0x4C, 0x44, 0x90, 0x5B, 0x6E, 0xA6, 0xD6, 0x78, 0x17, 0x97, 0x90, 0xC9, - 0x07, 0xC4, 0xC0, 0x78, 0x5A, 0xE7, 0xFB, 0xED, 0x43, 0xFE, 0xCB, 0x9F, 0xFD, 0x18, 0x0A, 0x20, - 0x3D, 0x66, 0x88, 0x2A, 0x42, 0x28, 0x55, 0xD5, 0x43, 0x92, 0xDC, 0x57, 0xCB, 0x93, 0xE1, 0x75, - 0xC7, 0x09, 0x09, 0x4F, 0x74, 0xAB, 0xDC, 0xCF, 0x22, 0xCA, 0xE5, 0xC4, 0x05, 0x6D, 0x23, 0xA9, - 0x13, 0x55, 0x98, 0x72, 0xA9, 0xF2, 0x9B, 0x54, 0xC6, 0xA2, 0xA1, 0x2F, 0xC8, 0x8E, 0x45, 0x65, - 0x3B, 0x63, 0x25, 0x1D, 0xED, 0x63, 0xB9, 0x1E, 0xD0, 0x1A, 0xF5, 0xA2, 0x3A, 0xA7, 0xFA, 0x90, - 0xCD, 0x3A, 0x6B, 0xA6, 0xA8, 0x8D, 0x38, 0x93, 0x43, 0x6A, 0xD8, 0x57, 0x6D, 0x2A, 0x32, 0xE2, - 0x65, 0x6E, 0x20, 0x31, 0xF9, 0xDA, 0x65, 0x66, 0xB5, 0x1A, 0x9F, 0x38, 0xCB, 0x91, 0xAE, 0x6A, - 0x0B, 0x93, 0xE1, 0x24, 0x93, 0x65, 0x8B, 0x0E, 0x12, 0xB6, 0x65, 0x91, 0x99, 0x00, 0xD3, 0xA9, - 0xC8, 0x72, 0xA1, 0x07, 0x49, 0x99, 0x95, 0x85, 0x6D, 0xD3, 0xDF, 0x41, 0x45, 0x2C, 0x04, 0x5A, - 0x92, 0xD6, 0x1A, 0x6C, 0x56, 0x2D, 0x96, 0x22, 0x20, 0x1E, 0x28, 0x00, 0x03, 0xDA, 0x68, 0xCB, - 0x8E, 0x15, 0x1A, 0x22, 0x29, 0x8F, 0xC7, 0x69, 0xF3, 0x9B, 0xFA, 0xE9, 0x38, 0xB4, 0xA5, 0x4A, - 0x6F, 0x2B, 0x77, 0xDB, 0x53, 0x6D, 0xC5, 0xBC, 0x05, 0xEA, 0x04, 0x15, 0x40, 0xAF, 0xD7, 0x25, - 0xC2, 0x8E, 0x6B, 0x26, 0x1F, 0x4B, 0x0D, 0xB6, 0x82, 0xCB, 0x0A, 0x9C, 0x23, 0x59, 0x17, 0x99, - 0x24, 0x03, 0xC8, 0xD7, 0xA1, 0x73, 0x7E, 0x91, 0x4C, 0x8A, 0x45, 0x64, 0xA1, 0xD3, 0x34, 0x97, - 0x62, 0x4F, 0x32, 0xF6, 0x19, 0x0E, 0x8D, 0x53, 0xE3, 0x4A, 0xF9, 0x36, 0xE5, 0x61, 0xE9, 0x5A, - 0x1D, 0x54, 0x79, 0x27, 0x43, 0xC4, 0xE9, 0x1D, 0x49, 0xEA, 0x75, 0x7A, 0x66, 0xA5, 0x2D, 0xAB, - 0x94, 0xC3, 0xE4, 0xEB, 0xC6, 0x46, 0x76, 0x4A, 0xB6, 0x99, 0x58, 0xF6, 0xE4, 0x03, 0x06, 0x93, - 0x52, 0x7A, 0x91, 0x52, 0x6A, 0x74, 0x74, 0xB6, 0xA7, 0x5A, 0xBE, 0x12, 0x70, 0x8C, 0xD3, 0x99, - 0x19, 0x67, 0x63, 0x2E, 0xD0, 0x0C, 0x9B, 0x53, 0x4C, 0xB6, 0x92, 0x86, 0xE9, 0xD0, 0xA2, 0xA9, - 0x2A, 0x25, 0x6B, 0x23, 0xA5, 0x49, 0x57, 0x9A, 0xE6, 0xA3, 0xCB, 0xFC, 0x00, 0xB3, 0x23, 0x49, - 0x1F, 0x94, 0x93, 0x39, 0x30, 0xA0, 0xBA, 0xF2, 0xB0, 0x6B, 0x1F, 0x36, 0x8C, 0x9C, 0x70, 0x92, - 0x64, 0x76, 0x33, 0x23, 0xDF, 0x62, 0x23, 0xB1, 0x16, 0x40, 0x23, 0xD2, 0x29, 0x87, 0x53, 0x9B, - 0x3D, 0x6D, 0xB2, 0xB7, 0x26, 0xB4, 0xA6, 0x5D, 0x42, 0x92, 0x78, 0x70, 0x99, 0x11, 0x58, 0xB3, - 0xBE, 0xE2, 0xDE, 0x02, 0x71, 0xF4, 0x9E, 0x7C, 0x7A, 0x84, 0x39, 0x8D, 0xA5, 0x92, 0x72, 0x24, - 0x62, 0x8A, 0x94, 0xD9, 0x58, 0x56, 0x82, 0x23, 0xB6, 0x2C, 0xF3, 0x3C, 0xEF, 0xBB, 0x61, 0x00, - 0xCA, 0x97, 0x21, 0x72, 0xE5, 0xBD, 0x25, 0xC2, 0x49, 0x2D, 0xE7, 0x14, 0xE2, 0x89, 0x3B, 0x08, - 0xCC, 0xEF, 0x90, 0x05, 0x00, 0xB9, 0x1A, 0xA4, 0xF4, 0x6A, 0x6C, 0xD8, 0x28, 0x4B, 0x66, 0xD4, - 0xCC, 0x1A, 0xC3, 0x51, 0x1E, 0x22, 0xC0, 0x77, 0x2B, 0x66, 0x01, 0x31, 0x1F, 0x28, 0xD2, 0x50, - 0xF2, 0x98, 0x69, 0xF2, 0x4D, 0xFF, 0x00, 0x46, 0xF1, 0x19, 0xA5, 0x59, 0x5B, 0x3B, 0x19, 0x18, - 0x0B, 0xF5, 0x5A, 0xFC, 0x8A, 0x9C, 0xF6, 0xA7, 0x29, 0x88, 0xF1, 0xE5, 0x36, 0xA2, 0x5E, 0xB5, - 0x84, 0x99, 0x1A, 0x8C, 0xAD, 0x63, 0x3B, 0x99, 0xEC, 0xC2, 0x56, 0x00, 0x4E, 0xAF, 0x3F, 0x34, - 0x89, 0x47, 0x16, 0x23, 0x12, 0x35, 0x84, 0xEA, 0xA4, 0x30, 0xDE, 0x07, 0x14, 0xA2, 0xDE, 0x67, - 0x7E, 0xD3, 0xBE, 0x56, 0xCC, 0x04, 0xAA, 0x1A, 0x45, 0x2E, 0x7C, 0x57, 0x58, 0x53, 0x31, 0x98, - 0x27, 0xD4, 0x4B, 0x7D, 0x6C, 0x37, 0x85, 0x4F, 0x99, 0x6C, 0x35, 0x67, 0x9E, 0x79, 0xEE, 0xCC, - 0x06, 0x40, 0x00, 0x06, 0x95, 0x32, 0xB2, 0xBA, 0x6B, 0x64, 0x4D, 0x43, 0x86, 0xEB, 0x89, 0x5E, - 0xB1, 0x0F, 0x3A, 0xD6, 0x25, 0xB6, 0x76, 0x2D, 0x87, 0x7D, 0xD6, 0xB9, 0x5C, 0x8F, 0x30, 0x10, - 0x7A, 0xB1, 0x25, 0xFA, 0x62, 0xA0, 0xB9, 0x80, 0xD0, 0xB9, 0x27, 0x25, 0x6E, 0x67, 0x8D, 0x6B, - 0x32, 0xB6, 0x67, 0x7B, 0x7B, 0x00, 0x15, 0x1A, 0x9B, 0xD5, 0x69, 0x8D, 0xC8, 0x90, 0x86, 0xD2, - 0xEA, 0x5B, 0x4B, 0x66, 0x68, 0x23, 0x2C, 0x76, 0xDE, 0x77, 0x33, 0xCC, 0x74, 0x6A, 0x9F, 0x1A, - 0x57, 0xC9, 0xB7, 0x16, 0x3D, 0x2B, 0x43, 0xAA, 0x8F, 0x24, 0xE8, 0x79, 0xEE, 0x4D, 0x06, 0x4E, - 0x97, 0x4F, 0x3A, 0x93, 0x24, 0xEC, 0x76, 0x69, 0xE6, 0xE9, 0x91, 0xEE, 0xB1, 0xA7, 0x32, 0xF2, - 0xDA, 0xE0, 0x15, 0x0F, 0x46, 0x22, 0x25, 0xB8, 0xCC, 0x4C, 0x67, 0x12, 0x9A, 0x96, 0xF9, 0x3A, - 0xB4, 0x9D, 0x8D, 0xC4, 0x21, 0x37, 0x49, 0x5F, 0xB0, 0xF2, 0xE0, 0x60, 0x30, 0xEA, 0x4C, 0xC5, - 0x99, 0xA3, 0x6D, 0x55, 0x98, 0x88, 0xDC, 0x47, 0x53, 0x2C, 0xE3, 0x2D, 0x0D, 0x19, 0xE1, 0x51, - 0x61, 0xC4, 0x47, 0x63, 0x33, 0xB1, 0xEE, 0x01, 0x82, 0x03, 0xD2, 0x56, 0xCA, 0x05, 0x11, 0xE5, - 0xD2, 0x13, 0x4E, 0x65, 0xF7, 0x50, 0xC1, 0x13, 0xD2, 0x16, 0xA5, 0x63, 0xD6, 0xA9, 0x37, 0xBA, - 0x73, 0xB1, 0x11, 0x5C, 0xB2, 0xB6, 0xE0, 0x16, 0xE1, 0x51, 0xA2, 0x39, 0xA2, 0x26, 0x4A, 0x65, - 0x27, 0x3D, 0xF8, 0xEF, 0x4C, 0x43, 0xA7, 0xDF, 0x21, 0x2D, 0xA9, 0x25, 0x84, 0x8B, 0xCA, 0x57, - 0x01, 0xA7, 0x40, 0xD1, 0xEA, 0x74, 0xEA, 0x35, 0x16, 0x4B, 0xB1, 0x9B, 0x52, 0xCD, 0xC5, 0x9C, - 0x83, 0x33, 0xB6, 0x34, 0xF7, 0x64, 0x57, 0xED, 0xEE, 0xB0, 0x17, 0x10, 0x15, 0x91, 0x4E, 0x88, - 0xCC, 0x0A, 0x93, 0xAD, 0x44, 0xA6, 0x1B, 0x8D, 0xD5, 0x9D, 0x61, 0x27, 0x39, 0x78, 0x10, 0x96, - 0xC8, 0xAE, 0x49, 0x23, 0xB9, 0x67, 0xF1, 0x00, 0x8A, 0x5C, 0x02, 0x90, 0x8A, 0xE3, 0xAD, 0xD3, - 0x29, 0xF3, 0x25, 0xB1, 0xC9, 0xF5, 0x2C, 0xB0, 0x66, 0xB6, 0x73, 0xBE, 0x2C, 0x36, 0x57, 0x66, - 0x67, 0x9E, 0xD2, 0x01, 0x17, 0x68, 0xB0, 0xDD, 0xD3, 0x1A, 0x64, 0x24, 0xB2, 0xDB, 0x04, 0xB6, - 0x10, 0xF4, 0xC6, 0x09, 0x46, 0xA4, 0xB6, 0xB2, 0x23, 0x52, 0x93, 0xB4, 0xEC, 0x56, 0x22, 0x2D, - 0xBB, 0xC0, 0x64, 0xE9, 0x4C, 0x56, 0x19, 0x9B, 0x1A, 0x4C, 0x46, 0x92, 0xCC, 0x79, 0xB1, 0x9B, - 0x7D, 0x2D, 0xA7, 0x63, 0x66, 0x65, 0x63, 0x4D, 0xF7, 0xE6, 0x5E, 0xD0, 0x18, 0xA0, 0x3D, 0xAE, - 0x95, 0x45, 0x8F, 0x4E, 0x7A, 0x6B, 0x51, 0xA1, 0x51, 0x52, 0xCA, 0x52, 0x44, 0x92, 0x37, 0x3F, - 0x58, 0x2C, 0x49, 0x2C, 0xC9, 0x38, 0xB6, 0xDC, 0xEE, 0x59, 0x6C, 0x00, 0x4B, 0xA5, 0x53, 0x8A, - 0x75, 0x4A, 0x88, 0x88, 0x48, 0x4A, 0xA1, 0x42, 0xD7, 0x22, 0x51, 0x29, 0x5A, 0xC5, 0x38, 0x49, - 0x4A, 0x8E, 0xF9, 0xDA, 0xC7, 0x7B, 0x5A, 0xC0, 0x28, 0xBD, 0x4F, 0x88, 0x9A, 0x8E, 0x8C, 0x36, - 0x4C, 0x20, 0x91, 0x29, 0xB6, 0x4D, 0xE2, 0xFD, 0xB3, 0x35, 0xD8, 0xEF, 0xC0, 0x06, 0x8D, 0x56, - 0x89, 0x01, 0x54, 0x29, 0xBC, 0x9E, 0x3A, 0x1B, 0x96, 0xDC, 0x99, 0x0B, 0x6D, 0x49, 0xCA, 0xE8, - 0x6D, 0xC3, 0x23, 0x4F, 0x04, 0x9D, 0xFF, 0x00, 0xF2, 0x01, 0x95, 0xB8, 0x74, 0xEA, 0x52, 0x6A, - 0xEF, 0x35, 0x4C, 0x8A, 0xEE, 0xA6, 0x4B, 0x2D, 0xA1, 0x0E, 0x11, 0xD9, 0x29, 0x53, 0x69, 0x33, - 0xB5, 0x8C, 0xB7, 0x80, 0xF3, 0x9A, 0x41, 0x02, 0x3C, 0x2A, 0x84, 0x65, 0xC3, 0x4A, 0x91, 0x1E, - 0x5C, 0x66, 0xE4, 0xA1, 0xB5, 0x2A, 0xE6, 0x82, 0x51, 0x1F, 0x73, 0x7D, 0xFB, 0x07, 0x46, 0xA9, - 0xF1, 0xA5, 0x7C, 0x9B, 0x72, 0x88, 0xF4, 0xAD, 0x0E, 0xAA, 0x3C, 0x93, 0xA1, 0xE4, 0xEA, 0xF2, - 0xD7, 0x06, 0xB9, 0x51, 0x57, 0x26, 0x79, 0xE4, 0xCA, 0xA7, 0xAA, 0x32, 0x4D, 0x09, 0xC8, 0x8D, - 0x56, 0xCF, 0xD8, 0x01, 0x68, 0xD2, 0x89, 0x4D, 0xC4, 0xA5, 0xA4, 0xE9, 0xCE, 0xAD, 0xE8, 0x86, - 0x64, 0xE9, 0xA8, 0x8C, 0x89, 0xE4, 0xE1, 0xC1, 0xD9, 0x7B, 0xE1, 0xB6, 0x7E, 0x41, 0xF2, 0x33, - 0x42, 0x1B, 0xE2, 0x32, 0xEA, 0x52, 0xC9, 0xCA, 0x63, 0x54, 0xDA, 0x74, 0x09, 0x2C, 0xC5, 0x43, - 0xA6, 0xFA, 0x8D, 0xD3, 0xC4, 0xA5, 0xAC, 0xCA, 0xDB, 0x88, 0xAC, 0x44, 0x59, 0x0C, 0x76, 0x92, - 0x62, 0x51, 0x91, 0xC9, 0x64, 0x78, 0x07, 0x7D, 0x43, 0x0D, 0xA4, 0x98, 0x94, 0x6E, 0x4D, 0x9F, - 0x1E, 0xA2, 0xC9, 0x3D, 0x3E, 0x97, 0x28, 0xEA, 0x09, 0x63, 0x55, 0xAC, 0x42, 0xEC, 0x87, 0x0C, - 0x8A, 0xC9, 0x5A, 0x8A, 0xD7, 0xB9, 0x65, 0xB0, 0xF3, 0xB0, 0xCA, 0x58, 0xC2, 0x6D, 0xDD, 0xAF, - 0x91, 0x9A, 0x12, 0xEF, 0x68, 0x33, 0xA5, 0x92, 0xA3, 0xD4, 0x21, 0x6A, 0x63, 0xC9, 0xE6, 0xD8, - 0xEC, 0x25, 0x95, 0x46, 0x3F, 0xF9, 0x2C, 0x93, 0x2B, 0x9E, 0x5B, 0x73, 0x2F, 0x40, 0xCA, 0x91, - 0x63, 0xB4, 0x93, 0x15, 0x04, 0xD5, 0xD4, 0xDA, 0x28, 0xAD, 0xB5, 0x1A, 0x49, 0x22, 0x9C, 0xEA, - 0x96, 0xBF, 0xB4, 0x49, 0xB8, 0x4B, 0x22, 0xF6, 0x05, 0x22, 0x6D, 0x24, 0xC4, 0xE7, 0x2A, 0xD0, - 0xE5, 0xC2, 0x99, 0x16, 0x74, 0x19, 0xD8, 0x1F, 0xA8, 0x2E, 0x6A, 0x4D, 0x93, 0x24, 0x99, 0x5C, - 0xAC, 0x44, 0x77, 0x23, 0xED, 0x31, 0xF2, 0x31, 0xA6, 0xF2, 0xFC, 0xB8, 0xA9, 0xA2, 0x6B, 0x31, - 0xA9, 0xD5, 0x68, 0x31, 0x22, 0xCA, 0xD5, 0x4D, 0xD4, 0xEA, 0xCD, 0xD3, 0x23, 0x52, 0x30, 0x1D, - 0xCE, 0xF6, 0x22, 0xBD, 0xFC, 0x83, 0x1B, 0xD2, 0xE2, 0xFB, 0x7E, 0x5C, 0x5A, 0x48, 0xD2, 0x6C, - 0x3F, 0xAE, 0x14, 0x39, 0x25, 0x53, 0x38, 0x27, 0x11, 0x4F, 0x15, 0xAC, 0xA3, 0xCA, 0xCE, 0x1E, - 0x57, 0xC5, 0x90, 0x5E, 0x97, 0x12, 0xFC, 0xB8, 0xB3, 0x6A, 0xF5, 0x77, 0xEA, 0xD4, 0xA8, 0x2C, - 0xCB, 0x6D, 0xF5, 0xCB, 0x8C, 0xA5, 0xDD, 0xE5, 0x16, 0x4B, 0x4A, 0x8E, 0xFE, 0x92, 0xB1, 0x10, - 0xCA, 0x5F, 0xBD, 0xBB, 0xB5, 0x94, 0xBF, 0x7B, 0xDD, 0xED, 0x62, 0xEA, 0x9C, 0xFD, 0x85, 0x7A, - 0x06, 0x57, 0x66, 0xC1, 0x95, 0xC9, 0xB0, 0x69, 0x69, 0x14, 0xE3, 0xAC, 0x56, 0xE4, 0x4F, 0x6D, - 0x87, 0x1B, 0x4B, 0xB8, 0x6C, 0x95, 0x15, 0xCC, 0xAC, 0x92, 0x2F, 0x70, 0x5D, 0x9B, 0x02, 0xE4, - 0xD8, 0x34, 0xE4, 0x69, 0x1B, 0x6E, 0x72, 0xA9, 0x69, 0x80, 0xEA, 0x6A, 0x72, 0xE3, 0x72, 0x67, - 0x5C, 0x35, 0xFE, 0x8E, 0xD6, 0x22, 0x35, 0x12, 0x6D, 0x7B, 0x99, 0x11, 0x6F, 0xCB, 0xFB, 0xFC, - 0x8C, 0x29, 0xBD, 0xF6, 0xE4, 0xD8, 0x21, 0x12, 0xB9, 0x10, 0x9B, 0xA7, 0x39, 0x3A, 0x9F, 0x21, - 0xE9, 0x54, 0xD2, 0xB3, 0x2A, 0x6D, 0xDC, 0x29, 0x59, 0x11, 0xDD, 0x24, 0xA2, 0xB1, 0xEC, 0xF2, - 0x0C, 0x6B, 0x03, 0x67, 0x3E, 0x00, 0xB4, 0x95, 0xC3, 0x28, 0x2B, 0x54, 0x75, 0x1B, 0xCC, 0x4A, - 0x75, 0xF7, 0x72, 0xEE, 0x5C, 0x27, 0x0F, 0xBA, 0x49, 0x17, 0x94, 0x8C, 0xCB, 0x88, 0x56, 0x06, - 0xCE, 0x7C, 0x16, 0x6A, 0x1A, 0x49, 0x02, 0xA2, 0x75, 0x04, 0xCB, 0x85, 0x2C, 0x9A, 0x94, 0xF3, - 0x6E, 0xA4, 0x9B, 0x5A, 0x52, 0x65, 0x85, 0x04, 0x9B, 0x19, 0x99, 0x1F, 0x60, 0xCA, 0x58, 0x46, - 0x6D, 0xDD, 0xAC, 0x63, 0x2C, 0x65, 0xDF, 0x06, 0x25, 0x56, 0xA4, 0xBA, 0xAD, 0x41, 0x0F, 0x6A, - 0x49, 0x96, 0x9B, 0x42, 0x5A, 0x69, 0xA2, 0x3B, 0x93, 0x68, 0x49, 0x64, 0x57, 0xDE, 0x3A, 0xB5, - 0x59, 0x26, 0x86, 0x9A, 0x58, 0xC6, 0x0C, 0x26, 0x8F, 0x61, 0x03, 0xD1, 0x34, 0xBA, 0xA8, 0xF2, - 0x4E, 0x80, 0x01, 0x4F, 0xF5, 0x78, 0x89, 0x36, 0x9F, 0x07, 0x3C, 0x9B, 0x24, 0x24, 0x48, 0x6C, - 0x00, 0x2B, 0xCC, 0xD8, 0x8E, 0x3E, 0xE1, 0x7A, 0xC5, 0xEF, 0x39, 0x66, 0x8B, 0x6B, 0xF0, 0x73, - 0xC9, 0x58, 0x5E, 0x44, 0x00, 0x12, 0xFF, 0x00, 0x57, 0x88, 0x93, 0x69, 0xEE, 0x93, 0x9E, 0x4C, - 0xE4, 0x24, 0x48, 0x6C, 0x00, 0x11, 0x2F, 0xBD, 0x47, 0x9C, 0xC5, 0xEB, 0x17, 0xBC, 0xE5, 0x9B, - 0xD3, 0x7B, 0x3F, 0xDE, 0xFE, 0xDC, 0xD5, 0x45, 0xE7, 0xA6, 0x00, 0x14, 0xFF, 0x00, 0x57, 0x88, - 0x93, 0x69, 0xF0, 0x73, 0xC9, 0x9C, 0x84, 0x89, 0x0D, 0x80, 0x05, 0x79, 0x9B, 0x11, 0xC7, 0xDC, - 0x2F, 0x58, 0xBD, 0xE7, 0x2C, 0xD1, 0x6D, 0x7E, 0x0E, 0x79, 0x2B, 0x0B, 0xC8, 0x80, 0x07, 0xAC, - 0xE9, 0xA2, 0x7E, 0xAF, 0x3F, 0xBF, 0xF9, 0x44, 0x8E, 0xAC, 0x8F, 0x8F, 0xCB, 0xD5, 0xB2, 0xF8, - 0xE9, 0xA2, 0x7E, 0xAF, 0x3F, 0xBF, 0xF9, 0x43, 0xAB, 0x23, 0xE3, 0xF2, 0xF5, 0x2F, 0x96, 0xEE, - 0x98, 0x92, 0xAD, 0x6A, 0x7E, 0xCF, 0xB7, 0xF9, 0x44, 0x9B, 0x4E, 0xCC, 0xF7, 0x3E, 0xFE, 0x3F, - 0x87, 0xE9, 0xF3, 0x6C, 0x92, 0x72, 0xFA, 0x5D, 0xFC, 0xBF, 0xF1, 0xFE, 0x51, 0x23, 0xAA, 0xFE, - 0xBF, 0x2F, 0x56, 0xCB, 0xE3, 0xA5, 0xDF, 0xCB, 0xFF, 0x00, 0x1F, 0xE5, 0x0E, 0xAB, 0xFA, 0xFC, - 0xBD, 0x4B, 0xE6, 0x35, 0xA4, 0x05, 0x34, 0x8E, 0xF1, 0x4D, 0xBC, 0x1F, 0x6B, 0x7B, 0xDF, 0x81, - 0x76, 0x0A, 0xF6, 0x66, 0xA5, 0xB2, 0xBF, 0xF7, 0xAB, 0x5A, 0x7E, 0x1F, 0xAF, 0xCD, 0xAE, 0x7B, - 0x3F, 0xA7, 0xF1, 0x5D, 0xBB, 0xF2, 0xAE, 0xFE, 0x70, 0xC0, 0xCE, 0x71, 0x4F, 0x82, 0x3F, 0x5B, - 0xE0, 0x2B, 0x6C, 0x63, 0x8B, 0x5F, 0xD9, 0xBF, 0xCD, 0xFE, 0x3F, 0xD8, 0x73, 0x8A, 0x7C, 0x11, - 0xFA, 0xDF, 0x00, 0xD8, 0xC7, 0x13, 0xEC, 0xDF, 0xE6, 0xFF, 0x00, 0x1F, 0xEC, 0xA9, 0x3A, 0xAE, - 0x4D, 0x6A, 0xED, 0x1F, 0x15, 0xEF, 0xD7, 0xB7, 0x67, 0x90, 0x4D, 0xB4, 0x35, 0x5B, 0xF7, 0x7B, - 0x71, 0xC9, 0xA3, 0x4F, 0x62, 0xF4, 0x7A, 0x7F, 0x92, 0xB5, 0xF9, 0x7A, 0xAA, 0x73, 0xEF, 0xF0, - 0xBF, 0x89, 0xF0, 0x13, 0x3A, 0x17, 0xD5, 0xE5, 0xEA, 0xE7, 0xEA, 0xFF, 0x00, 0xAB, 0xCB, 0xD4, - 0x73, 0xEF, 0xF0, 0xBF, 0x89, 0xF0, 0x0E, 0x85, 0xF5, 0x79, 0x7A, 0x9D, 0x5F, 0xF5, 0x79, 0x7A, - 0xAD, 0xC1, 0x7C, 0xAA, 0xA4, 0xE1, 0x1A, 0x4D, 0x9D, 0x55, 0xB7, 0xE2, 0xBD, 0xEF, 0xE6, 0xEC, - 0x14, 0xAC, 0xF9, 0x3A, 0x3D, 0xEE, 0xDA, 0xD6, 0x99, 0xBA, 0x34, 0x3A, 0xD7, 0x55, 0xD7, 0xB2, - 0xF5, 0xEE, 0x54, 0xA7, 0xFE, 0xE2, 0xB7, 0xCD, 0xC9, 0xF0, 0xA7, 0xEA, 0xFC, 0x45, 0x3D, 0xB4, - 0x70, 0x6F, 0xFB, 0x49, 0xF9, 0x5F, 0xCB, 0xFA, 0x8E, 0x6E, 0x4F, 0x85, 0x3F, 0x57, 0xE2, 0x1B, - 0x68, 0xE0, 0x7D, 0xA4, 0xFC, 0xAF, 0xE5, 0xFD, 0x59, 0xB5, 0x84, 0x14, 0x2D, 0x4D, 0x8C, 0xDC, - 0xC7, 0x8B, 0xC9, 0x6B, 0x5B, 0xFC, 0x8E, 0x1D, 0x77, 0xFC, 0xB7, 0x7F, 0x0A, 0x57, 0x27, 0x6E, - 0xA9, 0x6E, 0x6D, 0xAF, 0x7F, 0x8E, 0x94, 0xA7, 0xE3, 0xFA, 0xFC, 0x99, 0xBC, 0xB3, 0xEC, 0xFF, - 0x00, 0xAB, 0xE0, 0x27, 0xEC, 0x3E, 0x6E, 0xEE, 0xB4, 0xFA, 0x3C, 0xFD, 0x07, 0x2C, 0xFB, 0x3F, - 0xEA, 0xF8, 0x06, 0xC3, 0xE6, 0x75, 0xA7, 0xD1, 0xE7, 0xE8, 0xD2, 0xA3, 0xD2, 0xCA, 0xBA, 0x4F, - 0x5D, 0xE3, 0x8F, 0xA8, 0xC3, 0xD4, 0xC7, 0x8B, 0x15, 0xFC, 0xA5, 0x6E, 0xF7, 0xDA, 0x3B, 0xF5, - 0x2D, 0x3F, 0x45, 0xBD, 0xD9, 0x5A, 0xD3, 0xE5, 0xBA, 0xBF, 0xAE, 0x2E, 0x2D, 0x6F, 0x58, 0xE9, - 0x37, 0x7B, 0x29, 0x4A, 0xFC, 0xFF, 0x00, 0xE3, 0x4B, 0xA1, 0x69, 0xFA, 0xC0, 0xFE, 0xE3, 0xE6, - 0x1D, 0xFD, 0x67, 0x1F, 0x07, 0x9F, 0xA3, 0x86, 0xE0, 0xE8, 0x5A, 0x7E, 0xB0, 0x3F, 0xB8, 0xF9, - 0x83, 0xAC, 0xE3, 0xE0, 0xF3, 0xF4, 0x2E, 0x3C, 0xA5, 0x8C, 0x51, 0xE9, 0x1A, 0x2F, 0x13, 0x58, - 0xB1, 0x87, 0x48, 0xD1, 0x78, 0x87, 0xC3, 0x23, 0xEC, 0x13, 0x6D, 0x09, 0xA1, 0xA4, 0xBB, 0x73, - 0xB6, 0x95, 0xC9, 0x9C, 0x91, 0x84, 0x17, 0xE2, 0xD0, 0xAA, 0x93, 0x23, 0xA5, 0xF8, 0xD0, 0x9D, - 0x75, 0xA5, 0xDF, 0x0A, 0xD2, 0x59, 0x1D, 0x8E, 0xC7, 0xED, 0x21, 0x2E, 0x30, 0xA6, 0xF6, 0xD3, - 0x7A, 0x33, 0x5A, 0xFA, 0xB5, 0xFF, 0x00, 0x40, 0xF8, 0x2E, 0xD3, 0xB4, 0x7A, 0xAE, 0xDE, 0xB3, - 0x1C, 0x07, 0x93, 0x7B, 0x5A, 0xE5, 0xE7, 0x1D, 0x9A, 0xA6, 0x92, 0x59, 0x2F, 0x5E, 0x8E, 0x19, - 0xBB, 0xF5, 0x2D, 0x24, 0x92, 0x5E, 0xBD, 0x1A, 0x6E, 0xCD, 0x73, 0x98, 0xAA, 0x9E, 0x24, 0xEF, - 0xA0, 0x76, 0x6D, 0xF4, 0x78, 0xA8, 0x74, 0x9D, 0x17, 0x89, 0x95, 0xCA, 0x59, 0xF0, 0x84, 0x36, - 0x5E, 0x83, 0xEE, 0xDF, 0x47, 0x8A, 0x9D, 0x41, 0xC4, 0x3B, 0xAB, 0xC0, 0xA2, 0x55, 0xAF, 0x7B, - 0x70, 0x1C, 0x7A, 0xDC, 0x23, 0x3D, 0xDB, 0xBF, 0x3C, 0x9C, 0x1A, 0xEC, 0xF2, 0xCF, 0x76, 0xEC, - 0x6B, 0xBF, 0x25, 0x2B, 0x1F, 0x60, 0xE3, 0xD9, 0x4F, 0x82, 0x7D, 0xD8, 0x8B, 0x18, 0xF9, 0x1D, - 0x1C, 0xD0, 0x85, 0x63, 0x02, 0x91, 0x83, 0x5F, 0x47, 0xE5, 0x33, 0x1B, 0x94, 0x6B, 0x9C, 0x4A, - 0x31, 0x60, 0xB5, 0xF7, 0xDB, 0x17, 0xF9, 0x19, 0xE8, 0xA6, 0x84, 0x2B, 0x54, 0xCB, 0x43, 0x45, - 0x3E, 0x92, 0xED, 0xC8, 0x56, 0x95, 0xC9, 0xB1, 0xCE, 0x70, 0xBC, 0x61, 0x03, 0x76, 0xD2, 0x5C, - 0x53, 0x3A, 0x2E, 0x9B, 0xC2, 0x39, 0xCE, 0x17, 0x8C, 0x20, 0x36, 0x92, 0xE2, 0x74, 0x5D, 0x37, - 0x85, 0x8F, 0xA4, 0x12, 0x98, 0x91, 0xC9, 0xB5, 0x2E, 0xA5, 0x78, 0x71, 0xDE, 0xDB, 0xBB, 0xD1, - 0xA7, 0x4B, 0x34, 0x23, 0x4A, 0x29, 0xD9, 0xFA, 0x29, 0xF4, 0x77, 0xAF, 0xC2, 0x95, 0xA6, 0x6C, - 0x72, 0x32, 0x3D, 0x83, 0x4A, 0x93, 0xE9, 0x11, 0x9E, 0xC0, 0x1E, 0x8F, 0x44, 0x66, 0xC6, 0x83, - 0xCB, 0x39, 0x53, 0xC9, 0x6B, 0x1E, 0xAF, 0x0E, 0x2D, 0xF6, 0xC5, 0x7F, 0xEE, 0x40, 0x3D, 0x1F, - 0x3E, 0x53, 0x3C, 0x75, 0xAF, 0x48, 0x03, 0x9F, 0x29, 0x9E, 0x3A, 0xD7, 0xA4, 0x07, 0x3D, 0x1D, - 0xAE, 0x60, 0x00, 0x01, 0xD5, 0x34, 0x23, 0xE8, 0xA4, 0x2F, 0x3B, 0x9F, 0x98, 0xA1, 0xCB, 0xA4, - 0xF7, 0xA2, 0xDF, 0x2E, 0xE6, 0xF0, 0xC1, 0x90, 0x00, 0x00, 0xE1, 0x85, 0xB0, 0x85, 0x56, 0xF8, - 0x00, 0x7D, 0x00, 0x05, 0x77, 0xA6, 0x35, 0x69, 0xFE, 0x1C, 0x58, 0xCD, 0xB8, 0xB1, 0x3D, 0xA4, - 0x00, 0x00, 0x45, 0x7B, 0x80, 0x08, 0xDE, 0x01, 0xA8, 0xDE, 0x00, 0x5E, 0xE0, 0x10, 0x00, 0x00, - 0x68, 0xED, 0x73, 0x00, 0x00, 0x0E, 0xA9, 0xA1, 0x1F, 0x45, 0x21, 0x79, 0xDC, 0xFC, 0xC5, 0x0E, - 0x5D, 0x27, 0xBD, 0x16, 0xF9, 0x77, 0x37, 0x86, 0x0C, 0x80, 0x00, 0x07, 0x0C, 0x2D, 0x84, 0x2A, - 0xB7, 0xC0, 0x03, 0xE8, 0x00, 0x2B, 0xBD, 0x31, 0xAB, 0x4F, 0xF0, 0xE2, 0xC6, 0x6D, 0xC5, 0x89, - 0xED, 0x20, 0x00, 0x02, 0x2B, 0xDC, 0x00, 0x46, 0xF0, 0x0D, 0x46, 0xF0, 0x02, 0xF7, 0x00, 0x80, - 0x00, 0x03, 0x47, 0x6B, 0x98, 0x00, 0x00, 0x75, 0x4D, 0x08, 0xFA, 0x29, 0x0B, 0xCE, 0xE7, 0xE6, - 0x28, 0x72, 0xE9, 0x3D, 0xE8, 0xB7, 0xCB, 0xB9, 0xBC, 0x30, 0x64, 0x00, 0x00, 0x38, 0x61, 0x6C, - 0x21, 0x55, 0xBE, 0x00, 0x1F, 0x40, 0x01, 0x5D, 0xE9, 0x8D, 0x5A, 0x7F, 0x87, 0x16, 0x33, 0x6E, - 0x2C, 0x4F, 0x69, 0x00, 0x00, 0x11, 0x5E, 0xE0, 0x02, 0x37, 0x80, 0x6A, 0x37, 0x80, 0x17, 0xB8, - 0x04, 0x00, 0x00, 0x1A, 0x3B, 0x5C, 0xC0, 0x00, 0x03, 0xAA, 0x68, 0x47, 0xD1, 0x48, 0x5E, 0x77, - 0x3F, 0x31, 0x43, 0x97, 0x49, 0xEF, 0x45, 0xBE, 0x5D, 0xCD, 0xE1, 0x83, 0x20, 0x00, 0x01, 0xC3, - 0x0B, 0x61, 0x0A, 0xAD, 0xF0, 0x00, 0xFA, 0x00, 0x0A, 0xEF, 0x4C, 0x6A, 0xD3, 0xFC, 0x38, 0xB1, - 0x9B, 0x71, 0x62, 0x7B, 0x48, 0x00, 0x00, 0x8A, 0xF7, 0x00, 0x11, 0xBC, 0x03, 0x51, 0xBC, 0x00, - 0xBD, 0xC0, 0x20, 0x00, 0x01, 0xFF, 0xD9 -}; - -uint32_t gimage_id; // Global image ID (for all image threads) -mutex_t camera_mtx; -bool camera_mtx_init = false; - -ssdv_packet_t packetRepeats[16]; -bool reject_pri; -bool reject_sec; - -static bool transmit_image_packet(const uint8_t *image, - uint32_t image_len, - thd_img_conf_t* conf, - uint8_t image_id, - uint16_t packet_id) { - ssdv_t ssdv; - uint8_t pkt[SSDV_PKT_SIZE]; - uint8_t pkt_base91[256] = {0}; - const uint8_t *b; - uint32_t bi = 0; - uint8_t c = SSDV_OK; - uint16_t i = 0; - - // Init SSDV (FEC at 2FSK, non FEC at APRS) - bi = 0; - ssdv_enc_init(&ssdv, SSDV_TYPE_PADDING, "N0CALL", image_id, conf->quality); - ssdv_enc_set_buffer(&ssdv, pkt); - - while(true) - { - while((c = ssdv_enc_get_packet(&ssdv)) == SSDV_FEED_ME) - { - b = &image[bi]; - uint8_t r = bi < image_len-128 ? 128 : image_len - bi; - bi += r; - - if(r <= 0) - { - TRACE_ERROR("SSDV > Premature end of file"); - return false; - } - ssdv_enc_feed(&ssdv, b, r); - } - - if(c == SSDV_EOI) { - return true; - } else if(c != SSDV_OK) { - return false; - } - - if(i == packet_id) { - // Sync byte, CRC and FEC of SSDV not transmitted (because its not necessary inside an APRS packet) - base91_encode(&pkt[6], pkt_base91, 174); - packet_t packet = aprs_encode_data_packet(conf->call, conf->path, 'I', pkt_base91); - if(packet == NULL) { - TRACE_WARN("IMG > No free packet objects for transmission"); - return false; - } - if(!transmitOnRadio(packet, - conf->radio_conf.freq, - 0, - 0, - conf->radio_conf.pwr, - conf->radio_conf.mod, - conf->radio_conf.cca)) { - - TRACE_ERROR("IMG > Unable to send image packet TX on radio"); - return false; - } - } - - chThdSleep(TIME_MS2I(10)); // Leave other threads some time - - i++; - } -} - -/* - * Transmit image packets. - * Return true if no SSDV encoding error or false on encoding error. - */ -static bool transmit_image_packets(const uint8_t *image, - uint32_t image_len, - thd_img_conf_t* conf, - uint8_t image_id) { - - uint8_t pkt[SSDV_PKT_SIZE]; - uint8_t pkt_base91[256] = {0}; - - /* FIXME: This doesn't work with burst mode packet sends. */ - // Process redundant transmission from last cycle - if(strlen((char*)pkt_base91) - && conf->redundantTx) { - packet_t packet = aprs_encode_data_packet(conf->call, conf->path, - 'I', pkt_base91); - if(packet == NULL) { - TRACE_ERROR("IMG > No available packet for redundant" - " image transmission"); - } else { - if(!transmitOnRadio(packet, - conf->radio_conf.freq, - 0, - 0, - conf->radio_conf.pwr, - conf->radio_conf.mod, - conf->radio_conf.cca)) { - /* Packet has been released by transmit. */ - TRACE_ERROR("IMG > Unable to send redundant image on radio"); - } - } - chThdSleep(TIME_MS2I(10)); // Leave other threads some time - } - - /* Prepare for new image encode and send. */ - ssdv_t ssdv; - const uint8_t *b; - uint32_t bi = 0; - uint8_t c = SSDV_OK; - - /* Initialize SSDV, output buffer and input buffer. */ - ssdv_enc_init(&ssdv, SSDV_TYPE_PADDING, "N0CALL", image_id, conf->quality); - ssdv_enc_set_buffer(&ssdv, pkt); - ssdv_enc_feed(&ssdv, image, 0); - - while(c != SSDV_EOI) { - - /* - * Next encode packets. - * Packet burst send is available if redundant TX is not requested. - */ - uint8_t buffers = fmin((NUMBER_COMMON_PKT_BUFFERS / 2), - MAX_BUFFERS_FOR_BURST_SEND); - uint8_t chain = (conf->radio_conf.mod == MOD_2FSK - && !conf->redundantTx) ? - buffers : 1; - TRACE_INFO("IMG > Encode %i APRS/SSDV packet%s", chain, - (chain > 1 ? " burst" : "")); - - /* Packet linking control. */ - packet_t head = NULL; - packet_t previous = NULL; - - while(chain-- > 0) { - while((c = ssdv_enc_get_packet(&ssdv)) == SSDV_FEED_ME) { - b = &image[bi++]; - if(bi > image_len) { - TRACE_ERROR("SSDV > Premature end of file"); - if(head != NULL) { - pktReleaseBufferChain(head); - } - return false; - } - ssdv_enc_feed(&ssdv, b, 1); - } - - if(c == SSDV_EOI) { - TRACE_INFO("SSDV > ssdv_enc_get_packet said EOI"); - break; - } else if(c != SSDV_OK) { - TRACE_ERROR("SSDV > ssdv_enc_get_packet failed: %i", c); - if(head != NULL) { - pktReleaseBufferChain(head); - } - return false; - } - - /* - * Sync byte, CRC and FEC of SSDV not transmitted. - * Not necessary inside an APRS packet. - */ - base91_encode(&pkt[6], pkt_base91, 174); - - packet_t packet = aprs_encode_data_packet(conf->call, conf->path, - 'I', pkt_base91); - if(packet == NULL) { - TRACE_ERROR("IMG > No available packet for image transmission"); - /* Error so release any linked packets. */ - if(head != NULL) { - pktReleaseBufferChain(head); - } - return false; - } - if(previous != NULL) - /* Link the next packet into the chain. */ - previous->nextp = packet; - else - /* This is the first packet. */ - head = packet; - /* Now set new packet as previous. */ - previous = packet; - } /* End while(chain-- > 0) */ - - /* If we have some image packet(s) to transmit then do it. */ - if(head != NULL) { - if(!transmitOnRadio(head, - conf->radio_conf.freq, - 0, - 0, - conf->radio_conf.pwr, - conf->radio_conf.mod, - conf->radio_conf.cca)) { - TRACE_ERROR("IMG > Unable to send image on radio"); - /* Transmit on radio will release the packet chain. */ - } else { - // Packet spacing (delay) - if(conf->thread_conf.send_spacing) - chThdSleep(conf->thread_conf.send_spacing); - } - } - chThdSleep(TIME_MS2I(10)); // Leave other threads some time - } /* End while(c!= SSDV_EOI) */ - - // Repeat packets - for(uint8_t i=0; i<16; i++) { - if(packetRepeats[i].n_done && image_id == packetRepeats[i].image_id) { - if(!transmit_image_packet(image, image_len, conf, - image_id, packetRepeats[i].packet_id)) { - TRACE_ERROR("IMG > Failed re-send of image %i", image_id); - } else { - packetRepeats[i].n_done = false; // Set done - } - } - chThdSleep(TIME_MS2I(10)); // Leave other threads some time - } - - // Handle image rejection flag - if((conf == &conf_sram.img_pri) && reject_pri) { // Image rejected - reject_pri = false; - } - if((conf == &conf_sram.img_sec) && reject_sec) { // Image rejected - reject_sec = false; - } - return true; -} - -/** - * Analyzes the image for JPEG errors. Returns true if the image is error free. - */ -static bool analyze_image(uint8_t *image, uint32_t image_len) { - -#if !OV5640_USE_DMA_DBM - if(image_len >= 65535) { - TRACE_ERROR("CAM > Camera has %d bytes allocated but DMA DBM not activated", image_len); - TRACE_ERROR("CAM > DMA can only use 65535 bytes only"); - image_len = 65535; - } -#endif - - ssdv_t ssdv; - uint8_t pkt[SSDV_PKT_SIZE]; - uint8_t *b; - uint32_t bi = 0; - uint32_t i = 0; - uint8_t c = SSDV_OK; - - ssdv_enc_init(&ssdv, SSDV_TYPE_NOFEC, "", 0, 7); - ssdv_enc_set_buffer(&ssdv, pkt); - - while(++i < image_len) { - while((c = ssdv_enc_get_packet(&ssdv)) == SSDV_FEED_ME) { - b = &image[bi++]; - if(bi > image_len) { - TRACE_ERROR("CAM > Error in image (Premature end of file %d)", i); - return false; - } - ssdv_enc_feed(&ssdv, b, 1); - } - - if(c == SSDV_EOI) // End of image - return true; - - if(c != SSDV_OK) { - TRACE_ERROR("CAM > Error in image (ssdv_enc_get_packet failed: %d %d)", c, i); - return false; - } - chThdSleep(TIME_MS2I(5)); - } /* End while. */ - return false; -} - -static bool camInitialized = false; - -uint32_t takePicture(uint8_t* buffer, uint32_t size, - resolution_t res, bool enableJpegValidation) { - uint32_t size_sampled = 0; - - // Initialize mutex - if(!camera_mtx_init) - chMtxObjectInit(&camera_mtx); - camera_mtx_init = true; - - // Lock camera - TRACE_INFO("IMG > Lock camera"); - chMtxLock(&camera_mtx); - - // Detect camera - if(camInitialized || OV5640_isAvailable()) { // OV5640 available - - TRACE_INFO("IMG > OV5640 found"); - - uint8_t cntr = 5; - bool jpegValid; - // Init camera - if(!camInitialized) { - OV5640_init(); - camInitialized = true; - } - do { - // Init camera -/* if(!camInitialized) { - OV5640_init(); - camInitialized = true; - }*/ - // Sample data from pseudo DCMI through DMA into RAM - size_sampled = OV5640_Snapshot2RAM(buffer, size, res); - if(size_sampled == 0) - continue; - // Switch off camera -/* if(!conf_sram.keep_cam_switched_on) { - OV5640_deinit(); - camInitialized = false; - }*/ - - // Validate JPEG image - if(enableJpegValidation) - { - TRACE_INFO("CAM > Validate integrity of JPEG"); - jpegValid = analyze_image(buffer, size); - TRACE_INFO("CAM > JPEG image %s", jpegValid ? "valid" : "invalid"); - } else { - jpegValid = true; - } - } while(!jpegValid && cntr--); - - } else { // Camera not found - - camInitialized = false; - TRACE_ERROR("IMG > No camera found"); - } - // Switch off camera - if(!conf_sram.keep_cam_switched_on) { - OV5640_deinit(); - camInitialized = false; - } - // Unlock camera - TRACE_INFO("IMG > Unlock camera"); - chMtxUnlock(&camera_mtx); - - return size_sampled; -} -/* - * - */ -THD_FUNCTION(imgThread, arg) { - thd_img_conf_t* conf = (thd_img_conf_t*)arg; - - if(conf->thread_conf.init_delay) chThdSleep(conf->thread_conf.init_delay); - TRACE_INFO("IMG > Startup image thread"); - - // Create buffer - //uint8_t buffer[conf->buf_size] __attribute__((aligned(DMA_FIFO_BURST_ALIGN))); - - sysinterval_t time = chVTGetSystemTime(); - while(true) { - char code_s[100]; - pktDisplayFrequencyCode(conf->radio_conf.freq, - code_s, sizeof(code_s)); - TRACE_INFO("POS > Do module IMAGE cycle for %s on %s", - conf->call, code_s); - if(p_sleep(&conf->thread_conf.sleep_conf)) { - /* Re-check every minute. */ - chThdSleep(TIME_S2I(60)); - continue; - } - uint32_t my_image_id = gimage_id++; - /* Create image capture buffer. */ - uint8_t *buffer = chHeapAllocAligned(NULL, conf->buf_size, - DMA_FIFO_BURST_ALIGN); - if(buffer == NULL) { - /* Could not get a capture buffer. */ - TRACE_WARN("IMG > Unable to get capture buffer for image %i", - my_image_id); - /* Allow time for other threads. */ - chThdSleep(TIME_MS2I(10)); - /* Try again at next run time. */ - time = waitForTrigger(time, conf->thread_conf.cycle); - continue; - } - /* - * History... compiler bug - * If size is > 65535 the compiled code wraps address around and kills CMM heap. - * Anyway clearing of the capture buffer is no longer needed. - * SOI is now aligned at index 0 and length > EOI is returned by DMA. - */ -/* uint32_t size = conf->buf_size; - for(uint32_t i = 0; i < size ; i++) - buffer[i] = 0;*/ - /* Take picture. */ - uint32_t size_sampled = takePicture(buffer, conf->buf_size, - conf->res, true); - /* Nothing captured? */ - if(size_sampled == 0) { - TRACE_INFO("IMG > Encode/Transmit SSDV (camera error) ID=%d", - my_image_id); - if(!transmit_image_packets(noCameraFound, sizeof(noCameraFound), - conf, (uint8_t)(my_image_id))) { - TRACE_ERROR("IMG > Error in encoding dummy image %i" - " - discarded", my_image_id); - } - /* Return the buffer to the heap. */ - chHeapFree(buffer); - /* Allow time for other threads. */ - chThdSleep(TIME_MS2I(10)); - /* Try again at next run time. */ - time = waitForTrigger(time, conf->thread_conf.cycle); - continue; - } - - /* Find SOI in image buffer. */ - uint32_t soi; - bool soi_found = false; - while(soi < (size_sampled - 1)) { - if(buffer[soi] == 0xFF && buffer[soi + 1] == 0xD8) { - /* Found an SOI. */ - soi_found = true; - TRACE_INFO("IMG > SOI at index %i of buffer", soi); - /* Write to SD if present. */ - if(initSD()) { - char filename[32]; - // Write picture to SD card - TRACE_INFO("IMG > Save image to SD card"); - - chsnprintf(filename, sizeof(filename), "r%02xi%04x.jpg", - getLastDataPoint()->reset % 0xFF, - (my_image_id) % 0xFFFF); - - writeBufferToFile(filename, &buffer[soi], size_sampled - soi); - } /* End initSD() */ - - /* Transmit on radio. */ - if(conf->radio_conf.mod == MOD_2FSK && conf->redundantTx) { - TRACE_WARN("IMG > Redundant TX disables 2FSK burst send mode"); - } - - /* Encode and transmit picture. */ - TRACE_INFO("IMG > Encode/Transmit SSDV ID=%d", my_image_id); - if(!transmit_image_packets(buffer, size_sampled, conf, - (uint8_t)(my_image_id))) { - TRACE_ERROR("IMG > Error in encoding snapshot image" - " %i - discarded", my_image_id); - } - break; - } /* End if SOI in buffer. */ - } /* End while soi < size_sampled - 1. */ - if(!soi_found) { /* No SOI found. */ - TRACE_INFO("IMG > No SOI found in image"); - } - /* Return the buffer to the heap. */ - chHeapFree(buffer); - /* Allow minimum time for other threads. */ - chThdSleep(TIME_MS2I(10)); - /* Update next run time. */ - time = waitForTrigger(time, conf->thread_conf.cycle); - } -} - -/* - * - */ -void start_image_thread(thd_img_conf_t *conf) -{ - thread_t *th = chThdCreateFromHeap(NULL, - THD_WORKING_AREA_SIZE(40 * 1024), - "IMG", LOWPRIO, imgThread, conf); - if(!th) { - // Print startup error, do not start watchdog for this thread - TRACE_ERROR("IMG > Could not startup thread" - " (not enough memory available)"); - } -} - +#include "ch.h" +#include "hal.h" +#include "chprintf.h" + +#include "debug.h" +#include "threads.h" +#include "ov5640.h" +#include "pi2c.h" +#include "ssdv.h" +#include "aprs.h" +#include "radio.h" +#include "base91.h" +#include +#include "types.h" +#include "sleep.h" +#include "watchdog.h" +#include "flash.h" +#include "sd.h" +#include "collector.h" +#include "image.h" + +const uint8_t noCameraFound[4071] = { + 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48, + 0x00, 0x48, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0x10, 0x0B, 0x0C, 0x0E, 0x0C, 0x0A, 0x10, + 0x0E, 0x0D, 0x0E, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, 0x1A, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, + 0x25, 0x1D, 0x28, 0x3A, 0x33, 0x3D, 0x3C, 0x39, 0x33, 0x38, 0x37, 0x40, 0x48, 0x5C, 0x4E, 0x40, + 0x44, 0x57, 0x45, 0x37, 0x38, 0x50, 0x6D, 0x51, 0x57, 0x5F, 0x62, 0x67, 0x68, 0x67, 0x3E, 0x4D, + 0x71, 0x79, 0x70, 0x64, 0x78, 0x5C, 0x65, 0x67, 0x63, 0xFF, 0xDB, 0x00, 0x43, 0x01, 0x11, 0x12, + 0x12, 0x18, 0x15, 0x18, 0x2F, 0x1A, 0x1A, 0x2F, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0xFF, 0xC0, + 0x00, 0x11, 0x08, 0x00, 0x80, 0x00, 0xA0, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, + 0x01, 0xFF, 0xC4, 0x00, 0x1B, 0x00, 0x00, 0x02, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x04, 0x05, 0x07, 0x06, 0x01, 0xFF, 0xC4, + 0x00, 0x47, 0x10, 0x00, 0x00, 0x05, 0x02, 0x02, 0x05, 0x05, 0x0C, 0x06, 0x09, 0x05, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x11, 0x12, 0x21, 0x06, 0x13, 0x31, 0x41, + 0x81, 0x14, 0x15, 0x16, 0x42, 0x51, 0x22, 0x32, 0x53, 0x54, 0x61, 0x71, 0x91, 0x92, 0xA1, 0xC1, + 0xE1, 0xE2, 0x36, 0x55, 0x63, 0x93, 0xA3, 0xB3, 0x23, 0x24, 0x52, 0x62, 0x64, 0x65, 0xA2, 0xB1, + 0xD1, 0x07, 0x35, 0x43, 0x74, 0x82, 0xB2, 0xFF, 0xC4, 0x00, 0x1A, 0x01, 0x01, 0x01, 0x00, 0x03, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0x03, + 0x04, 0x06, 0x01, 0xFF, 0xC4, 0x00, 0x39, 0x11, 0x01, 0x00, 0x00, 0x03, 0x04, 0x06, 0x07, 0x05, + 0x08, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x11, 0x05, 0x12, 0x51, 0xC1, + 0x03, 0x04, 0x13, 0x31, 0x42, 0x81, 0x14, 0x15, 0x21, 0x43, 0x52, 0xA1, 0xE1, 0x06, 0x32, 0x33, + 0x61, 0x62, 0x16, 0x22, 0x41, 0x63, 0x71, 0x82, 0xA2, 0xE2, 0x23, 0x24, 0xD1, 0x91, 0xFF, 0xDA, + 0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3F, 0x00, 0xF4, 0x00, 0x00, 0x0A, + 0x7F, 0xAB, 0xC4, 0x49, 0xB4, 0xF8, 0x39, 0xE4, 0xD9, 0x21, 0x22, 0x43, 0x60, 0x01, 0x5E, 0x66, + 0xC4, 0x71, 0xF7, 0x0B, 0xD6, 0x2F, 0x79, 0xCB, 0x34, 0x5B, 0x5F, 0x83, 0x9E, 0x4A, 0xC2, 0xF2, + 0x20, 0x00, 0x97, 0xFA, 0xBC, 0x44, 0x9B, 0x4F, 0x74, 0x9C, 0xF2, 0x67, 0x21, 0x22, 0x43, 0x60, + 0x00, 0x89, 0x7D, 0xEA, 0x3C, 0xE6, 0x2F, 0x58, 0xBD, 0xE7, 0x2C, 0xDE, 0x9B, 0xD9, 0xFE, 0xF7, + 0xF6, 0xE6, 0xAA, 0x2F, 0x3D, 0x30, 0x00, 0xA7, 0xFA, 0xBC, 0x44, 0x9B, 0x4F, 0x83, 0x9E, 0x4C, + 0xE4, 0x24, 0x48, 0x6C, 0x00, 0x2B, 0xCC, 0xD8, 0x8E, 0x3E, 0xE1, 0x7A, 0xC5, 0xEF, 0x39, 0x66, + 0x8B, 0x6B, 0xF0, 0x73, 0xC9, 0x58, 0x5E, 0x44, 0x00, 0x3A, 0xA8, 0xF2, 0x4E, 0x80, 0x01, 0x4F, + 0xF5, 0x78, 0x89, 0x36, 0x9F, 0x07, 0x3C, 0x9B, 0x24, 0x24, 0x48, 0x6C, 0x00, 0x2B, 0xCC, 0xD8, + 0x8E, 0x3E, 0xE1, 0x7A, 0xC5, 0xEF, 0x39, 0x66, 0x8B, 0x6B, 0xF0, 0x73, 0xC9, 0x58, 0x5E, 0x44, + 0x00, 0x12, 0xFF, 0x00, 0x57, 0x88, 0x93, 0x69, 0xEE, 0x93, 0x9E, 0x4C, 0xE4, 0x24, 0x48, 0x6C, + 0x00, 0x11, 0x2F, 0xBD, 0x47, 0x9C, 0xC5, 0xEB, 0x17, 0xBC, 0xE5, 0x9B, 0xD3, 0x7B, 0x3F, 0xDE, + 0xFE, 0xDC, 0xD5, 0x45, 0xE7, 0xA6, 0x00, 0x14, 0xFF, 0x00, 0x57, 0x88, 0x93, 0x69, 0xF0, 0x73, + 0xC9, 0x9C, 0x84, 0x89, 0x0D, 0x80, 0x05, 0x79, 0x9B, 0x11, 0xC7, 0xDC, 0x2F, 0x58, 0xBD, 0xE7, + 0x2C, 0xD1, 0x6D, 0x7E, 0x0E, 0x79, 0x2B, 0x0B, 0xC8, 0x80, 0x07, 0x55, 0x1E, 0x49, 0xD0, 0x00, + 0x29, 0xFE, 0xAF, 0x11, 0x26, 0xD3, 0xE0, 0xE7, 0x93, 0x64, 0x84, 0x89, 0x0D, 0x80, 0x05, 0x79, + 0x9B, 0x11, 0xC7, 0xDC, 0x2F, 0x58, 0xBD, 0xE7, 0x2C, 0xD1, 0x6D, 0x7E, 0x0E, 0x79, 0x2B, 0x0B, + 0xC8, 0x80, 0x02, 0x5F, 0xEA, 0xF1, 0x12, 0x6D, 0x3D, 0xD2, 0x73, 0xC9, 0x9C, 0x84, 0x89, 0x0D, + 0x80, 0x02, 0x25, 0xF7, 0xA8, 0xF3, 0x98, 0xBD, 0x62, 0xF7, 0x9C, 0xB3, 0x7A, 0x6F, 0x67, 0xFB, + 0xDF, 0xDB, 0x9A, 0xA8, 0xBC, 0xF4, 0xC0, 0x02, 0x9F, 0xEA, 0xF1, 0x12, 0x6D, 0x3E, 0x0E, 0x79, + 0x33, 0x90, 0x91, 0x21, 0xB0, 0x00, 0xAF, 0x33, 0x62, 0x38, 0xFB, 0x85, 0xEB, 0x17, 0xBC, 0xE5, + 0x9A, 0x2D, 0xAF, 0xC1, 0xCF, 0x25, 0x61, 0x79, 0x10, 0x00, 0xEA, 0xA3, 0xC9, 0x3A, 0x00, 0x05, + 0x3F, 0xD5, 0xE2, 0x24, 0xDA, 0x7C, 0x1C, 0xF2, 0x6C, 0x90, 0x91, 0x21, 0xB0, 0x00, 0xAF, 0x33, + 0x62, 0x38, 0xFB, 0x85, 0xEB, 0x17, 0xBC, 0xE5, 0x9A, 0x2D, 0xAF, 0xC1, 0xCF, 0x25, 0x61, 0x79, + 0x10, 0x00, 0x4B, 0xFD, 0x5E, 0x22, 0x4D, 0xA7, 0xBA, 0x4E, 0x79, 0x33, 0x90, 0x91, 0x21, 0xB0, + 0x00, 0x44, 0xBE, 0xF5, 0x1E, 0x73, 0x17, 0xAC, 0x5E, 0xF3, 0x96, 0x6F, 0x4D, 0xEC, 0xFF, 0x00, + 0x7B, 0xFB, 0x73, 0x55, 0x17, 0x9E, 0x98, 0x00, 0x53, 0xFD, 0x5E, 0x22, 0x4D, 0xA7, 0xC1, 0xCF, + 0x26, 0x72, 0x12, 0x24, 0x36, 0x00, 0x15, 0xE6, 0x6C, 0x47, 0x1F, 0x70, 0xBD, 0x62, 0xF7, 0x9C, + 0xB3, 0x45, 0xB5, 0xF8, 0x39, 0xE4, 0xAC, 0x2F, 0x22, 0x00, 0x1D, 0x54, 0x79, 0x27, 0x43, 0x0A, + 0xAF, 0xA4, 0xCC, 0xD2, 0xA6, 0x9C, 0x67, 0x23, 0xB8, 0xE2, 0x89, 0x24, 0xAC, 0x49, 0x32, 0x22, + 0xCC, 0x06, 0x7B, 0x9A, 0x6B, 0x1D, 0x76, 0xB4, 0x47, 0x4A, 0xDF, 0xBC, 0x43, 0x8F, 0x5B, 0xD5, + 0xA3, 0xA7, 0xBB, 0x48, 0xD2, 0x95, 0x65, 0x2C, 0xD4, 0x43, 0xA6, 0x31, 0xFC, 0x51, 0xDF, 0x58, + 0x87, 0x17, 0x56, 0x4F, 0xE2, 0x65, 0x7C, 0x74, 0xC6, 0x3F, 0x8A, 0x3B, 0xEB, 0x10, 0x75, 0x64, + 0xFE, 0x22, 0xF9, 0x4F, 0xE9, 0x6B, 0x0E, 0x92, 0x6D, 0x15, 0xC2, 0xB7, 0xEF, 0x10, 0xA3, 0xA8, + 0x68, 0x63, 0xAA, 0xDE, 0xAC, 0x6B, 0x5A, 0x79, 0x55, 0xC3, 0xAE, 0xEA, 0xF1, 0xD6, 0x2E, 0xD2, + 0x34, 0xA5, 0x72, 0x2B, 0xA4, 0xEC, 0xF8, 0xB3, 0x9E, 0xB1, 0x0A, 0x1B, 0x68, 0x60, 0xE0, 0xEA, + 0xC9, 0xFC, 0x43, 0xA4, 0xEC, 0xF8, 0xB3, 0x9E, 0xB1, 0x06, 0xDA, 0x18, 0x1D, 0x59, 0x3F, 0x89, + 0x07, 0x34, 0x91, 0x95, 0xDA, 0xD1, 0xD6, 0x56, 0xF2, 0x90, 0xE3, 0xD6, 0xE4, 0xDB, 0xDD, 0xA7, + 0x65, 0x2A, 0xCA, 0x5B, 0x3A, 0x68, 0x71, 0x21, 0xD2, 0x16, 0xBC, 0x02, 0xFD, 0x24, 0x38, 0xBA, + 0x1C, 0xD8, 0xB2, 0xE8, 0x13, 0x62, 0x3A, 0x42, 0xD7, 0x80, 0x5F, 0xA4, 0x83, 0xA1, 0xCD, 0x89, + 0xD0, 0x26, 0xC4, 0xB7, 0xAB, 0xAD, 0x3A, 0x49, 0x22, 0x65, 0x65, 0x6F, 0x29, 0x0A, 0x3A, 0x87, + 0xFA, 0xB7, 0xAB, 0xDB, 0x5A, 0x79, 0x55, 0x56, 0xCD, 0xFF, 0x00, 0x4E, 0xFD, 0xEE, 0xDA, 0xD3, + 0xCA, 0xBF, 0xF4, 0x9E, 0x77, 0x6F, 0xC1, 0x2B, 0xD2, 0x28, 0xF4, 0xC9, 0x70, 0x55, 0xE9, 0xF2, + 0xE0, 0x39, 0xDD, 0xBF, 0x04, 0xAF, 0x48, 0x74, 0xC9, 0x70, 0x3A, 0x7C, 0xB8, 0x20, 0xE5, 0x51, + 0x0B, 0xB5, 0x9B, 0x51, 0x5B, 0xCA, 0x38, 0xF5, 0xB9, 0xF6, 0xF7, 0x69, 0xD9, 0x4A, 0xB2, 0x96, + 0xD1, 0x96, 0x1C, 0x28, 0x73, 0x8A, 0x3C, 0x1A, 0xBD, 0x23, 0x8B, 0x63, 0x1C, 0x59, 0x75, 0x9C, + 0x9E, 0x11, 0xCE, 0x28, 0xF0, 0x6A, 0xF4, 0x86, 0xC6, 0x38, 0x9D, 0x67, 0x27, 0x84, 0xA7, 0xE6, + 0x25, 0xD2, 0x4D, 0x90, 0x65, 0x61, 0x47, 0x50, 0xD3, 0x43, 0x55, 0xBD, 0x58, 0x56, 0xB4, 0xF2, + 0xAB, 0x87, 0x5D, 0xD6, 0x21, 0xAC, 0x5D, 0xA4, 0x29, 0x4A, 0xE4, 0x5A, 0x5D, 0x25, 0x28, 0x8A, + 0xDB, 0x45, 0x4D, 0x16, 0xBF, 0x2E, 0x92, 0x78, 0x49, 0x08, 0x6F, 0x4F, 0x8C, 0x94, 0x30, 0x50, + 0x60, 0xEA, 0xA3, 0xC9, 0x3A, 0x18, 0x6C, 0x9B, 0x85, 0xA5, 0x55, 0x63, 0x65, 0xC4, 0xB4, 0xE9, + 0x52, 0x97, 0x81, 0xC5, 0x2B, 0x09, 0x20, 0xEE, 0x9B, 0x19, 0x99, 0xEC, 0xB7, 0x68, 0x08, 0xD3, + 0xA4, 0xB4, 0xFE, 0x93, 0xE8, 0xFA, 0x1C, 0x92, 0xC4, 0xBA, 0x8B, 0x68, 0x7C, 0xA5, 0x3E, 0xC9, + 0x92, 0x89, 0x57, 0x4A, 0xB0, 0x16, 0x22, 0xC8, 0xCC, 0x8A, 0xE0, 0x30, 0xE5, 0x4B, 0x4D, 0x5A, + 0x4C, 0x08, 0x4E, 0xD7, 0x64, 0xCE, 0x69, 0xD9, 0x48, 0x4A, 0xD0, 0xEB, 0x1A, 0xB2, 0x41, 0x19, + 0xDB, 0x15, 0xEE, 0x79, 0xE6, 0x60, 0x17, 0xA4, 0x95, 0x99, 0xBC, 0xB6, 0xA1, 0x4A, 0x49, 0xA5, + 0xA8, 0x08, 0x70, 0xDA, 0x44, 0x74, 0xA0, 0x89, 0x29, 0x24, 0x2B, 0x23, 0x2C, 0xB6, 0xE5, 0x7E, + 0x20, 0x3D, 0x0D, 0x6E, 0x94, 0xFA, 0x34, 0x35, 0x50, 0x8D, 0x85, 0xA5, 0x10, 0x18, 0x61, 0xF4, + 0x9D, 0xB6, 0xB8, 0x66, 0xAD, 0x6E, 0x7D, 0x84, 0x4A, 0xB8, 0x06, 0x9B, 0x69, 0x98, 0x5A, 0x38, + 0xDA, 0x52, 0x5A, 0xF8, 0x4C, 0x44, 0x90, 0x5B, 0x6E, 0xA6, 0xD6, 0x78, 0x17, 0x97, 0x90, 0xC9, + 0x07, 0xC4, 0xC0, 0x78, 0x5A, 0xE7, 0xFB, 0xED, 0x43, 0xFE, 0xCB, 0x9F, 0xFD, 0x18, 0x0A, 0x20, + 0x3D, 0x66, 0x88, 0x2A, 0x42, 0x28, 0x55, 0xD5, 0x43, 0x92, 0xDC, 0x57, 0xCB, 0x93, 0xE1, 0x75, + 0xC7, 0x09, 0x09, 0x4F, 0x74, 0xAB, 0xDC, 0xCF, 0x22, 0xCA, 0xE5, 0xC4, 0x05, 0x6D, 0x23, 0xA9, + 0x13, 0x55, 0x98, 0x72, 0xA9, 0xF2, 0x9B, 0x54, 0xC6, 0xA2, 0xA1, 0x2F, 0xC8, 0x8E, 0x45, 0x65, + 0x3B, 0x63, 0x25, 0x1D, 0xED, 0x63, 0xB9, 0x1E, 0xD0, 0x1A, 0xF5, 0xA2, 0x3A, 0xA7, 0xFA, 0x90, + 0xCD, 0x3A, 0x6B, 0xA6, 0xA8, 0x8D, 0x38, 0x93, 0x43, 0x6A, 0xD8, 0x57, 0x6D, 0x2A, 0x32, 0xE2, + 0x65, 0x6E, 0x20, 0x31, 0xF9, 0xDA, 0x65, 0x66, 0xB5, 0x1A, 0x9F, 0x38, 0xCB, 0x91, 0xAE, 0x6A, + 0x0B, 0x93, 0xE1, 0x24, 0x93, 0x65, 0x8B, 0x0E, 0x12, 0xB6, 0x65, 0x91, 0x99, 0x00, 0xD3, 0xA9, + 0xC8, 0x72, 0xA1, 0x07, 0x49, 0x99, 0x95, 0x85, 0x6D, 0xD3, 0xDF, 0x41, 0x45, 0x2C, 0x04, 0x5A, + 0x92, 0xD6, 0x1A, 0x6C, 0x56, 0x2D, 0x96, 0x22, 0x20, 0x1E, 0x28, 0x00, 0x03, 0xDA, 0x68, 0xCB, + 0x8E, 0x15, 0x1A, 0x22, 0x29, 0x8F, 0xC7, 0x69, 0xF3, 0x9B, 0xFA, 0xE9, 0x38, 0xB4, 0xA5, 0x4A, + 0x6F, 0x2B, 0x77, 0xDB, 0x53, 0x6D, 0xC5, 0xBC, 0x05, 0xEA, 0x04, 0x15, 0x40, 0xAF, 0xD7, 0x25, + 0xC2, 0x8E, 0x6B, 0x26, 0x1F, 0x4B, 0x0D, 0xB6, 0x82, 0xCB, 0x0A, 0x9C, 0x23, 0x59, 0x17, 0x99, + 0x24, 0x03, 0xC8, 0xD7, 0xA1, 0x73, 0x7E, 0x91, 0x4C, 0x8A, 0x45, 0x64, 0xA1, 0xD3, 0x34, 0x97, + 0x62, 0x4F, 0x32, 0xF6, 0x19, 0x0E, 0x8D, 0x53, 0xE3, 0x4A, 0xF9, 0x36, 0xE5, 0x61, 0xE9, 0x5A, + 0x1D, 0x54, 0x79, 0x27, 0x43, 0xC4, 0xE9, 0x1D, 0x49, 0xEA, 0x75, 0x7A, 0x66, 0xA5, 0x2D, 0xAB, + 0x94, 0xC3, 0xE4, 0xEB, 0xC6, 0x46, 0x76, 0x4A, 0xB6, 0x99, 0x58, 0xF6, 0xE4, 0x03, 0x06, 0x93, + 0x52, 0x7A, 0x91, 0x52, 0x6A, 0x74, 0x74, 0xB6, 0xA7, 0x5A, 0xBE, 0x12, 0x70, 0x8C, 0xD3, 0x99, + 0x19, 0x67, 0x63, 0x2E, 0xD0, 0x0C, 0x9B, 0x53, 0x4C, 0xB6, 0x92, 0x86, 0xE9, 0xD0, 0xA2, 0xA9, + 0x2A, 0x25, 0x6B, 0x23, 0xA5, 0x49, 0x57, 0x9A, 0xE6, 0xA3, 0xCB, 0xFC, 0x00, 0xB3, 0x23, 0x49, + 0x1F, 0x94, 0x93, 0x39, 0x30, 0xA0, 0xBA, 0xF2, 0xB0, 0x6B, 0x1F, 0x36, 0x8C, 0x9C, 0x70, 0x92, + 0x64, 0x76, 0x33, 0x23, 0xDF, 0x62, 0x23, 0xB1, 0x16, 0x40, 0x23, 0xD2, 0x29, 0x87, 0x53, 0x9B, + 0x3D, 0x6D, 0xB2, 0xB7, 0x26, 0xB4, 0xA6, 0x5D, 0x42, 0x92, 0x78, 0x70, 0x99, 0x11, 0x58, 0xB3, + 0xBE, 0xE2, 0xDE, 0x02, 0x71, 0xF4, 0x9E, 0x7C, 0x7A, 0x84, 0x39, 0x8D, 0xA5, 0x92, 0x72, 0x24, + 0x62, 0x8A, 0x94, 0xD9, 0x58, 0x56, 0x82, 0x23, 0xB6, 0x2C, 0xF3, 0x3C, 0xEF, 0xBB, 0x61, 0x00, + 0xCA, 0x97, 0x21, 0x72, 0xE5, 0xBD, 0x25, 0xC2, 0x49, 0x2D, 0xE7, 0x14, 0xE2, 0x89, 0x3B, 0x08, + 0xCC, 0xEF, 0x90, 0x05, 0x00, 0xB9, 0x1A, 0xA4, 0xF4, 0x6A, 0x6C, 0xD8, 0x28, 0x4B, 0x66, 0xD4, + 0xCC, 0x1A, 0xC3, 0x51, 0x1E, 0x22, 0xC0, 0x77, 0x2B, 0x66, 0x01, 0x31, 0x1F, 0x28, 0xD2, 0x50, + 0xF2, 0x98, 0x69, 0xF2, 0x4D, 0xFF, 0x00, 0x46, 0xF1, 0x19, 0xA5, 0x59, 0x5B, 0x3B, 0x19, 0x18, + 0x0B, 0xF5, 0x5A, 0xFC, 0x8A, 0x9C, 0xF6, 0xA7, 0x29, 0x88, 0xF1, 0xE5, 0x36, 0xA2, 0x5E, 0xB5, + 0x84, 0x99, 0x1A, 0x8C, 0xAD, 0x63, 0x3B, 0x99, 0xEC, 0xC2, 0x56, 0x00, 0x4E, 0xAF, 0x3F, 0x34, + 0x89, 0x47, 0x16, 0x23, 0x12, 0x35, 0x84, 0xEA, 0xA4, 0x30, 0xDE, 0x07, 0x14, 0xA2, 0xDE, 0x67, + 0x7E, 0xD3, 0xBE, 0x56, 0xCC, 0x04, 0xAA, 0x1A, 0x45, 0x2E, 0x7C, 0x57, 0x58, 0x53, 0x31, 0x98, + 0x27, 0xD4, 0x4B, 0x7D, 0x6C, 0x37, 0x85, 0x4F, 0x99, 0x6C, 0x35, 0x67, 0x9E, 0x79, 0xEE, 0xCC, + 0x06, 0x40, 0x00, 0x06, 0x95, 0x32, 0xB2, 0xBA, 0x6B, 0x64, 0x4D, 0x43, 0x86, 0xEB, 0x89, 0x5E, + 0xB1, 0x0F, 0x3A, 0xD6, 0x25, 0xB6, 0x76, 0x2D, 0x87, 0x7D, 0xD6, 0xB9, 0x5C, 0x8F, 0x30, 0x10, + 0x7A, 0xB1, 0x25, 0xFA, 0x62, 0xA0, 0xB9, 0x80, 0xD0, 0xB9, 0x27, 0x25, 0x6E, 0x67, 0x8D, 0x6B, + 0x32, 0xB6, 0x67, 0x7B, 0x7B, 0x00, 0x15, 0x1A, 0x9B, 0xD5, 0x69, 0x8D, 0xC8, 0x90, 0x86, 0xD2, + 0xEA, 0x5B, 0x4B, 0x66, 0x68, 0x23, 0x2C, 0x76, 0xDE, 0x77, 0x33, 0xCC, 0x74, 0x6A, 0x9F, 0x1A, + 0x57, 0xC9, 0xB7, 0x16, 0x3D, 0x2B, 0x43, 0xAA, 0x8F, 0x24, 0xE8, 0x79, 0xEE, 0x4D, 0x06, 0x4E, + 0x97, 0x4F, 0x3A, 0x93, 0x24, 0xEC, 0x76, 0x69, 0xE6, 0xE9, 0x91, 0xEE, 0xB1, 0xA7, 0x32, 0xF2, + 0xDA, 0xE0, 0x15, 0x0F, 0x46, 0x22, 0x25, 0xB8, 0xCC, 0x4C, 0x67, 0x12, 0x9A, 0x96, 0xF9, 0x3A, + 0xB4, 0x9D, 0x8D, 0xC4, 0x21, 0x37, 0x49, 0x5F, 0xB0, 0xF2, 0xE0, 0x60, 0x30, 0xEA, 0x4C, 0xC5, + 0x99, 0xA3, 0x6D, 0x55, 0x98, 0x88, 0xDC, 0x47, 0x53, 0x2C, 0xE3, 0x2D, 0x0D, 0x19, 0xE1, 0x51, + 0x61, 0xC4, 0x47, 0x63, 0x33, 0xB1, 0xEE, 0x01, 0x82, 0x03, 0xD2, 0x56, 0xCA, 0x05, 0x11, 0xE5, + 0xD2, 0x13, 0x4E, 0x65, 0xF7, 0x50, 0xC1, 0x13, 0xD2, 0x16, 0xA5, 0x63, 0xD6, 0xA9, 0x37, 0xBA, + 0x73, 0xB1, 0x11, 0x5C, 0xB2, 0xB6, 0xE0, 0x16, 0xE1, 0x51, 0xA2, 0x39, 0xA2, 0x26, 0x4A, 0x65, + 0x27, 0x3D, 0xF8, 0xEF, 0x4C, 0x43, 0xA7, 0xDF, 0x21, 0x2D, 0xA9, 0x25, 0x84, 0x8B, 0xCA, 0x57, + 0x01, 0xA7, 0x40, 0xD1, 0xEA, 0x74, 0xEA, 0x35, 0x16, 0x4B, 0xB1, 0x9B, 0x52, 0xCD, 0xC5, 0x9C, + 0x83, 0x33, 0xB6, 0x34, 0xF7, 0x64, 0x57, 0xED, 0xEE, 0xB0, 0x17, 0x10, 0x15, 0x91, 0x4E, 0x88, + 0xCC, 0x0A, 0x93, 0xAD, 0x44, 0xA6, 0x1B, 0x8D, 0xD5, 0x9D, 0x61, 0x27, 0x39, 0x78, 0x10, 0x96, + 0xC8, 0xAE, 0x49, 0x23, 0xB9, 0x67, 0xF1, 0x00, 0x8A, 0x5C, 0x02, 0x90, 0x8A, 0xE3, 0xAD, 0xD3, + 0x29, 0xF3, 0x25, 0xB1, 0xC9, 0xF5, 0x2C, 0xB0, 0x66, 0xB6, 0x73, 0xBE, 0x2C, 0x36, 0x57, 0x66, + 0x67, 0x9E, 0xD2, 0x01, 0x17, 0x68, 0xB0, 0xDD, 0xD3, 0x1A, 0x64, 0x24, 0xB2, 0xDB, 0x04, 0xB6, + 0x10, 0xF4, 0xC6, 0x09, 0x46, 0xA4, 0xB6, 0xB2, 0x23, 0x52, 0x93, 0xB4, 0xEC, 0x56, 0x22, 0x2D, + 0xBB, 0xC0, 0x64, 0xE9, 0x4C, 0x56, 0x19, 0x9B, 0x1A, 0x4C, 0x46, 0x92, 0xCC, 0x79, 0xB1, 0x9B, + 0x7D, 0x2D, 0xA7, 0x63, 0x66, 0x65, 0x63, 0x4D, 0xF7, 0xE6, 0x5E, 0xD0, 0x18, 0xA0, 0x3D, 0xAE, + 0x95, 0x45, 0x8F, 0x4E, 0x7A, 0x6B, 0x51, 0xA1, 0x51, 0x52, 0xCA, 0x52, 0x44, 0x92, 0x37, 0x3F, + 0x58, 0x2C, 0x49, 0x2C, 0xC9, 0x38, 0xB6, 0xDC, 0xEE, 0x59, 0x6C, 0x00, 0x4B, 0xA5, 0x53, 0x8A, + 0x75, 0x4A, 0x88, 0x88, 0x48, 0x4A, 0xA1, 0x42, 0xD7, 0x22, 0x51, 0x29, 0x5A, 0xC5, 0x38, 0x49, + 0x4A, 0x8E, 0xF9, 0xDA, 0xC7, 0x7B, 0x5A, 0xC0, 0x28, 0xBD, 0x4F, 0x88, 0x9A, 0x8E, 0x8C, 0x36, + 0x4C, 0x20, 0x91, 0x29, 0xB6, 0x4D, 0xE2, 0xFD, 0xB3, 0x35, 0xD8, 0xEF, 0xC0, 0x06, 0x8D, 0x56, + 0x89, 0x01, 0x54, 0x29, 0xBC, 0x9E, 0x3A, 0x1B, 0x96, 0xDC, 0x99, 0x0B, 0x6D, 0x49, 0xCA, 0xE8, + 0x6D, 0xC3, 0x23, 0x4F, 0x04, 0x9D, 0xFF, 0x00, 0xF2, 0x01, 0x95, 0xB8, 0x74, 0xEA, 0x52, 0x6A, + 0xEF, 0x35, 0x4C, 0x8A, 0xEE, 0xA6, 0x4B, 0x2D, 0xA1, 0x0E, 0x11, 0xD9, 0x29, 0x53, 0x69, 0x33, + 0xB5, 0x8C, 0xB7, 0x80, 0xF3, 0x9A, 0x41, 0x02, 0x3C, 0x2A, 0x84, 0x65, 0xC3, 0x4A, 0x91, 0x1E, + 0x5C, 0x66, 0xE4, 0xA1, 0xB5, 0x2A, 0xE6, 0x82, 0x51, 0x1F, 0x73, 0x7D, 0xFB, 0x07, 0x46, 0xA9, + 0xF1, 0xA5, 0x7C, 0x9B, 0x72, 0x88, 0xF4, 0xAD, 0x0E, 0xAA, 0x3C, 0x93, 0xA1, 0xE4, 0xEA, 0xF2, + 0xD7, 0x06, 0xB9, 0x51, 0x57, 0x26, 0x79, 0xE4, 0xCA, 0xA7, 0xAA, 0x32, 0x4D, 0x09, 0xC8, 0x8D, + 0x56, 0xCF, 0xD8, 0x01, 0x68, 0xD2, 0x89, 0x4D, 0xC4, 0xA5, 0xA4, 0xE9, 0xCE, 0xAD, 0xE8, 0x86, + 0x64, 0xE9, 0xA8, 0x8C, 0x89, 0xE4, 0xE1, 0xC1, 0xD9, 0x7B, 0xE1, 0xB6, 0x7E, 0x41, 0xF2, 0x33, + 0x42, 0x1B, 0xE2, 0x32, 0xEA, 0x52, 0xC9, 0xCA, 0x63, 0x54, 0xDA, 0x74, 0x09, 0x2C, 0xC5, 0x43, + 0xA6, 0xFA, 0x8D, 0xD3, 0xC4, 0xA5, 0xAC, 0xCA, 0xDB, 0x88, 0xAC, 0x44, 0x59, 0x0C, 0x76, 0x92, + 0x62, 0x51, 0x91, 0xC9, 0x64, 0x78, 0x07, 0x7D, 0x43, 0x0D, 0xA4, 0x98, 0x94, 0x6E, 0x4D, 0x9F, + 0x1E, 0xA2, 0xC9, 0x3D, 0x3E, 0x97, 0x28, 0xEA, 0x09, 0x63, 0x55, 0xAC, 0x42, 0xEC, 0x87, 0x0C, + 0x8A, 0xC9, 0x5A, 0x8A, 0xD7, 0xB9, 0x65, 0xB0, 0xF3, 0xB0, 0xCA, 0x58, 0xC2, 0x6D, 0xDD, 0xAF, + 0x91, 0x9A, 0x12, 0xEF, 0x68, 0x33, 0xA5, 0x92, 0xA3, 0xD4, 0x21, 0x6A, 0x63, 0xC9, 0xE6, 0xD8, + 0xEC, 0x25, 0x95, 0x46, 0x3F, 0xF9, 0x2C, 0x93, 0x2B, 0x9E, 0x5B, 0x73, 0x2F, 0x40, 0xCA, 0x91, + 0x63, 0xB4, 0x93, 0x15, 0x04, 0xD5, 0xD4, 0xDA, 0x28, 0xAD, 0xB5, 0x1A, 0x49, 0x22, 0x9C, 0xEA, + 0x96, 0xBF, 0xB4, 0x49, 0xB8, 0x4B, 0x22, 0xF6, 0x05, 0x22, 0x6D, 0x24, 0xC4, 0xE7, 0x2A, 0xD0, + 0xE5, 0xC2, 0x99, 0x16, 0x74, 0x19, 0xD8, 0x1F, 0xA8, 0x2E, 0x6A, 0x4D, 0x93, 0x24, 0x99, 0x5C, + 0xAC, 0x44, 0x77, 0x23, 0xED, 0x31, 0xF2, 0x31, 0xA6, 0xF2, 0xFC, 0xB8, 0xA9, 0xA2, 0x6B, 0x31, + 0xA9, 0xD5, 0x68, 0x31, 0x22, 0xCA, 0xD5, 0x4D, 0xD4, 0xEA, 0xCD, 0xD3, 0x23, 0x52, 0x30, 0x1D, + 0xCE, 0xF6, 0x22, 0xBD, 0xFC, 0x83, 0x1B, 0xD2, 0xE2, 0xFB, 0x7E, 0x5C, 0x5A, 0x48, 0xD2, 0x6C, + 0x3F, 0xAE, 0x14, 0x39, 0x25, 0x53, 0x38, 0x27, 0x11, 0x4F, 0x15, 0xAC, 0xA3, 0xCA, 0xCE, 0x1E, + 0x57, 0xC5, 0x90, 0x5E, 0x97, 0x12, 0xFC, 0xB8, 0xB3, 0x6A, 0xF5, 0x77, 0xEA, 0xD4, 0xA8, 0x2C, + 0xCB, 0x6D, 0xF5, 0xCB, 0x8C, 0xA5, 0xDD, 0xE5, 0x16, 0x4B, 0x4A, 0x8E, 0xFE, 0x92, 0xB1, 0x10, + 0xCA, 0x5F, 0xBD, 0xBB, 0xB5, 0x94, 0xBF, 0x7B, 0xDD, 0xED, 0x62, 0xEA, 0x9C, 0xFD, 0x85, 0x7A, + 0x06, 0x57, 0x66, 0xC1, 0x95, 0xC9, 0xB0, 0x69, 0x69, 0x14, 0xE3, 0xAC, 0x56, 0xE4, 0x4F, 0x6D, + 0x87, 0x1B, 0x4B, 0xB8, 0x6C, 0x95, 0x15, 0xCC, 0xAC, 0x92, 0x2F, 0x70, 0x5D, 0x9B, 0x02, 0xE4, + 0xD8, 0x34, 0xE4, 0x69, 0x1B, 0x6E, 0x72, 0xA9, 0x69, 0x80, 0xEA, 0x6A, 0x72, 0xE3, 0x72, 0x67, + 0x5C, 0x35, 0xFE, 0x8E, 0xD6, 0x22, 0x35, 0x12, 0x6D, 0x7B, 0x99, 0x11, 0x6F, 0xCB, 0xFB, 0xFC, + 0x8C, 0x29, 0xBD, 0xF6, 0xE4, 0xD8, 0x21, 0x12, 0xB9, 0x10, 0x9B, 0xA7, 0x39, 0x3A, 0x9F, 0x21, + 0xE9, 0x54, 0xD2, 0xB3, 0x2A, 0x6D, 0xDC, 0x29, 0x59, 0x11, 0xDD, 0x24, 0xA2, 0xB1, 0xEC, 0xF2, + 0x0C, 0x6B, 0x03, 0x67, 0x3E, 0x00, 0xB4, 0x95, 0xC3, 0x28, 0x2B, 0x54, 0x75, 0x1B, 0xCC, 0x4A, + 0x75, 0xF7, 0x72, 0xEE, 0x5C, 0x27, 0x0F, 0xBA, 0x49, 0x17, 0x94, 0x8C, 0xCB, 0x88, 0x56, 0x06, + 0xCE, 0x7C, 0x16, 0x6A, 0x1A, 0x49, 0x02, 0xA2, 0x75, 0x04, 0xCB, 0x85, 0x2C, 0x9A, 0x94, 0xF3, + 0x6E, 0xA4, 0x9B, 0x5A, 0x52, 0x65, 0x85, 0x04, 0x9B, 0x19, 0x99, 0x1F, 0x60, 0xCA, 0x58, 0x46, + 0x6D, 0xDD, 0xAC, 0x63, 0x2C, 0x65, 0xDF, 0x06, 0x25, 0x56, 0xA4, 0xBA, 0xAD, 0x41, 0x0F, 0x6A, + 0x49, 0x96, 0x9B, 0x42, 0x5A, 0x69, 0xA2, 0x3B, 0x93, 0x68, 0x49, 0x64, 0x57, 0xDE, 0x3A, 0xB5, + 0x59, 0x26, 0x86, 0x9A, 0x58, 0xC6, 0x0C, 0x26, 0x8F, 0x61, 0x03, 0xD1, 0x34, 0xBA, 0xA8, 0xF2, + 0x4E, 0x80, 0x01, 0x4F, 0xF5, 0x78, 0x89, 0x36, 0x9F, 0x07, 0x3C, 0x9B, 0x24, 0x24, 0x48, 0x6C, + 0x00, 0x2B, 0xCC, 0xD8, 0x8E, 0x3E, 0xE1, 0x7A, 0xC5, 0xEF, 0x39, 0x66, 0x8B, 0x6B, 0xF0, 0x73, + 0xC9, 0x58, 0x5E, 0x44, 0x00, 0x12, 0xFF, 0x00, 0x57, 0x88, 0x93, 0x69, 0xEE, 0x93, 0x9E, 0x4C, + 0xE4, 0x24, 0x48, 0x6C, 0x00, 0x11, 0x2F, 0xBD, 0x47, 0x9C, 0xC5, 0xEB, 0x17, 0xBC, 0xE5, 0x9B, + 0xD3, 0x7B, 0x3F, 0xDE, 0xFE, 0xDC, 0xD5, 0x45, 0xE7, 0xA6, 0x00, 0x14, 0xFF, 0x00, 0x57, 0x88, + 0x93, 0x69, 0xF0, 0x73, 0xC9, 0x9C, 0x84, 0x89, 0x0D, 0x80, 0x05, 0x79, 0x9B, 0x11, 0xC7, 0xDC, + 0x2F, 0x58, 0xBD, 0xE7, 0x2C, 0xD1, 0x6D, 0x7E, 0x0E, 0x79, 0x2B, 0x0B, 0xC8, 0x80, 0x07, 0xAC, + 0xE9, 0xA2, 0x7E, 0xAF, 0x3F, 0xBF, 0xF9, 0x44, 0x8E, 0xAC, 0x8F, 0x8F, 0xCB, 0xD5, 0xB2, 0xF8, + 0xE9, 0xA2, 0x7E, 0xAF, 0x3F, 0xBF, 0xF9, 0x43, 0xAB, 0x23, 0xE3, 0xF2, 0xF5, 0x2F, 0x96, 0xEE, + 0x98, 0x92, 0xAD, 0x6A, 0x7E, 0xCF, 0xB7, 0xF9, 0x44, 0x9B, 0x4E, 0xCC, 0xF7, 0x3E, 0xFE, 0x3F, + 0x87, 0xE9, 0xF3, 0x6C, 0x92, 0x72, 0xFA, 0x5D, 0xFC, 0xBF, 0xF1, 0xFE, 0x51, 0x23, 0xAA, 0xFE, + 0xBF, 0x2F, 0x56, 0xCB, 0xE3, 0xA5, 0xDF, 0xCB, 0xFF, 0x00, 0x1F, 0xE5, 0x0E, 0xAB, 0xFA, 0xFC, + 0xBD, 0x4B, 0xE6, 0x35, 0xA4, 0x05, 0x34, 0x8E, 0xF1, 0x4D, 0xBC, 0x1F, 0x6B, 0x7B, 0xDF, 0x81, + 0x76, 0x0A, 0xF6, 0x66, 0xA5, 0xB2, 0xBF, 0xF7, 0xAB, 0x5A, 0x7E, 0x1F, 0xAF, 0xCD, 0xAE, 0x7B, + 0x3F, 0xA7, 0xF1, 0x5D, 0xBB, 0xF2, 0xAE, 0xFE, 0x70, 0xC0, 0xCE, 0x71, 0x4F, 0x82, 0x3F, 0x5B, + 0xE0, 0x2B, 0x6C, 0x63, 0x8B, 0x5F, 0xD9, 0xBF, 0xCD, 0xFE, 0x3F, 0xD8, 0x73, 0x8A, 0x7C, 0x11, + 0xFA, 0xDF, 0x00, 0xD8, 0xC7, 0x13, 0xEC, 0xDF, 0xE6, 0xFF, 0x00, 0x1F, 0xEC, 0xA9, 0x3A, 0xAE, + 0x4D, 0x6A, 0xED, 0x1F, 0x15, 0xEF, 0xD7, 0xB7, 0x67, 0x90, 0x4D, 0xB4, 0x35, 0x5B, 0xF7, 0x7B, + 0x71, 0xC9, 0xA3, 0x4F, 0x62, 0xF4, 0x7A, 0x7F, 0x92, 0xB5, 0xF9, 0x7A, 0xAA, 0x73, 0xEF, 0xF0, + 0xBF, 0x89, 0xF0, 0x13, 0x3A, 0x17, 0xD5, 0xE5, 0xEA, 0xE7, 0xEA, 0xFF, 0x00, 0xAB, 0xCB, 0xD4, + 0x73, 0xEF, 0xF0, 0xBF, 0x89, 0xF0, 0x0E, 0x85, 0xF5, 0x79, 0x7A, 0x9D, 0x5F, 0xF5, 0x79, 0x7A, + 0xAD, 0xC1, 0x7C, 0xAA, 0xA4, 0xE1, 0x1A, 0x4D, 0x9D, 0x55, 0xB7, 0xE2, 0xBD, 0xEF, 0xE6, 0xEC, + 0x14, 0xAC, 0xF9, 0x3A, 0x3D, 0xEE, 0xDA, 0xD6, 0x99, 0xBA, 0x34, 0x3A, 0xD7, 0x55, 0xD7, 0xB2, + 0xF5, 0xEE, 0x54, 0xA7, 0xFE, 0xE2, 0xB7, 0xCD, 0xC9, 0xF0, 0xA7, 0xEA, 0xFC, 0x45, 0x3D, 0xB4, + 0x70, 0x6F, 0xFB, 0x49, 0xF9, 0x5F, 0xCB, 0xFA, 0x8E, 0x6E, 0x4F, 0x85, 0x3F, 0x57, 0xE2, 0x1B, + 0x68, 0xE0, 0x7D, 0xA4, 0xFC, 0xAF, 0xE5, 0xFD, 0x59, 0xB5, 0x84, 0x14, 0x2D, 0x4D, 0x8C, 0xDC, + 0xC7, 0x8B, 0xC9, 0x6B, 0x5B, 0xFC, 0x8E, 0x1D, 0x77, 0xFC, 0xB7, 0x7F, 0x0A, 0x57, 0x27, 0x6E, + 0xA9, 0x6E, 0x6D, 0xAF, 0x7F, 0x8E, 0x94, 0xA7, 0xE3, 0xFA, 0xFC, 0x99, 0xBC, 0xB3, 0xEC, 0xFF, + 0x00, 0xAB, 0xE0, 0x27, 0xEC, 0x3E, 0x6E, 0xEE, 0xB4, 0xFA, 0x3C, 0xFD, 0x07, 0x2C, 0xFB, 0x3F, + 0xEA, 0xF8, 0x06, 0xC3, 0xE6, 0x75, 0xA7, 0xD1, 0xE7, 0xE8, 0xD2, 0xA3, 0xD2, 0xCA, 0xBA, 0x4F, + 0x5D, 0xE3, 0x8F, 0xA8, 0xC3, 0xD4, 0xC7, 0x8B, 0x15, 0xFC, 0xA5, 0x6E, 0xF7, 0xDA, 0x3B, 0xF5, + 0x2D, 0x3F, 0x45, 0xBD, 0xD9, 0x5A, 0xD3, 0xE5, 0xBA, 0xBF, 0xAE, 0x2E, 0x2D, 0x6F, 0x58, 0xE9, + 0x37, 0x7B, 0x29, 0x4A, 0xFC, 0xFF, 0x00, 0xE3, 0x4B, 0xA1, 0x69, 0xFA, 0xC0, 0xFE, 0xE3, 0xE6, + 0x1D, 0xFD, 0x67, 0x1F, 0x07, 0x9F, 0xA3, 0x86, 0xE0, 0xE8, 0x5A, 0x7E, 0xB0, 0x3F, 0xB8, 0xF9, + 0x83, 0xAC, 0xE3, 0xE0, 0xF3, 0xF4, 0x2E, 0x3C, 0xA5, 0x8C, 0x51, 0xE9, 0x1A, 0x2F, 0x13, 0x58, + 0xB1, 0x87, 0x48, 0xD1, 0x78, 0x87, 0xC3, 0x23, 0xEC, 0x13, 0x6D, 0x09, 0xA1, 0xA4, 0xBB, 0x73, + 0xB6, 0x95, 0xC9, 0x9C, 0x91, 0x84, 0x17, 0xE2, 0xD0, 0xAA, 0x93, 0x23, 0xA5, 0xF8, 0xD0, 0x9D, + 0x75, 0xA5, 0xDF, 0x0A, 0xD2, 0x59, 0x1D, 0x8E, 0xC7, 0xED, 0x21, 0x2E, 0x30, 0xA6, 0xF6, 0xD3, + 0x7A, 0x33, 0x5A, 0xFA, 0xB5, 0xFF, 0x00, 0x40, 0xF8, 0x2E, 0xD3, 0xB4, 0x7A, 0xAE, 0xDE, 0xB3, + 0x1C, 0x07, 0x93, 0x7B, 0x5A, 0xE5, 0xE7, 0x1D, 0x9A, 0xA6, 0x92, 0x59, 0x2F, 0x5E, 0x8E, 0x19, + 0xBB, 0xF5, 0x2D, 0x24, 0x92, 0x5E, 0xBD, 0x1A, 0x6E, 0xCD, 0x73, 0x98, 0xAA, 0x9E, 0x24, 0xEF, + 0xA0, 0x76, 0x6D, 0xF4, 0x78, 0xA8, 0x74, 0x9D, 0x17, 0x89, 0x95, 0xCA, 0x59, 0xF0, 0x84, 0x36, + 0x5E, 0x83, 0xEE, 0xDF, 0x47, 0x8A, 0x9D, 0x41, 0xC4, 0x3B, 0xAB, 0xC0, 0xA2, 0x55, 0xAF, 0x7B, + 0x70, 0x1C, 0x7A, 0xDC, 0x23, 0x3D, 0xDB, 0xBF, 0x3C, 0x9C, 0x1A, 0xEC, 0xF2, 0xCF, 0x76, 0xEC, + 0x6B, 0xBF, 0x25, 0x2B, 0x1F, 0x60, 0xE3, 0xD9, 0x4F, 0x82, 0x7D, 0xD8, 0x8B, 0x18, 0xF9, 0x1D, + 0x1C, 0xD0, 0x85, 0x63, 0x02, 0x91, 0x83, 0x5F, 0x47, 0xE5, 0x33, 0x1B, 0x94, 0x6B, 0x9C, 0x4A, + 0x31, 0x60, 0xB5, 0xF7, 0xDB, 0x17, 0xF9, 0x19, 0xE8, 0xA6, 0x84, 0x2B, 0x54, 0xCB, 0x43, 0x45, + 0x3E, 0x92, 0xED, 0xC8, 0x56, 0x95, 0xC9, 0xB1, 0xCE, 0x70, 0xBC, 0x61, 0x03, 0x76, 0xD2, 0x5C, + 0x53, 0x3A, 0x2E, 0x9B, 0xC2, 0x39, 0xCE, 0x17, 0x8C, 0x20, 0x36, 0x92, 0xE2, 0x74, 0x5D, 0x37, + 0x85, 0x8F, 0xA4, 0x12, 0x98, 0x91, 0xC9, 0xB5, 0x2E, 0xA5, 0x78, 0x71, 0xDE, 0xDB, 0xBB, 0xD1, + 0xA7, 0x4B, 0x34, 0x23, 0x4A, 0x29, 0xD9, 0xFA, 0x29, 0xF4, 0x77, 0xAF, 0xC2, 0x95, 0xA6, 0x6C, + 0x72, 0x32, 0x3D, 0x83, 0x4A, 0x93, 0xE9, 0x11, 0x9E, 0xC0, 0x1E, 0x8F, 0x44, 0x66, 0xC6, 0x83, + 0xCB, 0x39, 0x53, 0xC9, 0x6B, 0x1E, 0xAF, 0x0E, 0x2D, 0xF6, 0xC5, 0x7F, 0xEE, 0x40, 0x3D, 0x1F, + 0x3E, 0x53, 0x3C, 0x75, 0xAF, 0x48, 0x03, 0x9F, 0x29, 0x9E, 0x3A, 0xD7, 0xA4, 0x07, 0x3D, 0x1D, + 0xAE, 0x60, 0x00, 0x01, 0xD5, 0x34, 0x23, 0xE8, 0xA4, 0x2F, 0x3B, 0x9F, 0x98, 0xA1, 0xCB, 0xA4, + 0xF7, 0xA2, 0xDF, 0x2E, 0xE6, 0xF0, 0xC1, 0x90, 0x00, 0x00, 0xE1, 0x85, 0xB0, 0x85, 0x56, 0xF8, + 0x00, 0x7D, 0x00, 0x05, 0x77, 0xA6, 0x35, 0x69, 0xFE, 0x1C, 0x58, 0xCD, 0xB8, 0xB1, 0x3D, 0xA4, + 0x00, 0x00, 0x45, 0x7B, 0x80, 0x08, 0xDE, 0x01, 0xA8, 0xDE, 0x00, 0x5E, 0xE0, 0x10, 0x00, 0x00, + 0x68, 0xED, 0x73, 0x00, 0x00, 0x0E, 0xA9, 0xA1, 0x1F, 0x45, 0x21, 0x79, 0xDC, 0xFC, 0xC5, 0x0E, + 0x5D, 0x27, 0xBD, 0x16, 0xF9, 0x77, 0x37, 0x86, 0x0C, 0x80, 0x00, 0x07, 0x0C, 0x2D, 0x84, 0x2A, + 0xB7, 0xC0, 0x03, 0xE8, 0x00, 0x2B, 0xBD, 0x31, 0xAB, 0x4F, 0xF0, 0xE2, 0xC6, 0x6D, 0xC5, 0x89, + 0xED, 0x20, 0x00, 0x02, 0x2B, 0xDC, 0x00, 0x46, 0xF0, 0x0D, 0x46, 0xF0, 0x02, 0xF7, 0x00, 0x80, + 0x00, 0x03, 0x47, 0x6B, 0x98, 0x00, 0x00, 0x75, 0x4D, 0x08, 0xFA, 0x29, 0x0B, 0xCE, 0xE7, 0xE6, + 0x28, 0x72, 0xE9, 0x3D, 0xE8, 0xB7, 0xCB, 0xB9, 0xBC, 0x30, 0x64, 0x00, 0x00, 0x38, 0x61, 0x6C, + 0x21, 0x55, 0xBE, 0x00, 0x1F, 0x40, 0x01, 0x5D, 0xE9, 0x8D, 0x5A, 0x7F, 0x87, 0x16, 0x33, 0x6E, + 0x2C, 0x4F, 0x69, 0x00, 0x00, 0x11, 0x5E, 0xE0, 0x02, 0x37, 0x80, 0x6A, 0x37, 0x80, 0x17, 0xB8, + 0x04, 0x00, 0x00, 0x1A, 0x3B, 0x5C, 0xC0, 0x00, 0x03, 0xAA, 0x68, 0x47, 0xD1, 0x48, 0x5E, 0x77, + 0x3F, 0x31, 0x43, 0x97, 0x49, 0xEF, 0x45, 0xBE, 0x5D, 0xCD, 0xE1, 0x83, 0x20, 0x00, 0x01, 0xC3, + 0x0B, 0x61, 0x0A, 0xAD, 0xF0, 0x00, 0xFA, 0x00, 0x0A, 0xEF, 0x4C, 0x6A, 0xD3, 0xFC, 0x38, 0xB1, + 0x9B, 0x71, 0x62, 0x7B, 0x48, 0x00, 0x00, 0x8A, 0xF7, 0x00, 0x11, 0xBC, 0x03, 0x51, 0xBC, 0x00, + 0xBD, 0xC0, 0x20, 0x00, 0x01, 0xFF, 0xD9 +}; + +uint32_t gimage_id; // Global image ID (for all image threads) +mutex_t camera_mtx; +bool camera_mtx_init = false; + +ssdv_packet_t packetRepeats[16]; +bool reject_pri; +bool reject_sec; + +static bool transmit_image_packet(const uint8_t *image, + uint32_t image_len, + thd_img_conf_t* conf, + uint8_t image_id, + uint16_t packet_id) { + ssdv_t ssdv; + uint8_t pkt[SSDV_PKT_SIZE]; + uint8_t pkt_base91[256] = {0}; + const uint8_t *b; + uint32_t bi = 0; + uint8_t c = SSDV_OK; + uint16_t i = 0; + + // Init SSDV (FEC at 2FSK, non FEC at APRS) + bi = 0; + ssdv_enc_init(&ssdv, SSDV_TYPE_PADDING, "N0CALL", image_id, conf->quality); + ssdv_enc_set_buffer(&ssdv, pkt); + + while(true) + { + while((c = ssdv_enc_get_packet(&ssdv)) == SSDV_FEED_ME) + { + b = &image[bi]; + uint8_t r = bi < image_len-128 ? 128 : image_len - bi; + bi += r; + + if(r <= 0) + { + TRACE_ERROR("SSDV > Premature end of file"); + return false; + } + ssdv_enc_feed(&ssdv, b, r); + } + + if(c == SSDV_EOI) { + return true; + } else if(c != SSDV_OK) { + return false; + } + + if(i == packet_id) { + // Sync byte, CRC and FEC of SSDV not transmitted (because its not necessary inside an APRS packet) + base91_encode(&pkt[6], pkt_base91, 174); + packet_t packet = aprs_encode_data_packet(conf->call, conf->path, 'I', pkt_base91); + if(packet == NULL) { + TRACE_WARN("IMG > No free packet objects for transmission"); + return false; + } + if(!transmitOnRadio(packet, + conf->radio_conf.freq, + 0, + 0, + conf->radio_conf.pwr, + conf->radio_conf.mod, + conf->radio_conf.cca)) { + + TRACE_ERROR("IMG > Unable to send image packet TX on radio"); + return false; + } + } + + chThdSleep(TIME_MS2I(10)); // Leave other threads some time + + i++; + } +} + +/* + * Transmit image packets. + * Return true if no SSDV encoding error or false on encoding error. + */ +static bool transmit_image_packets(const uint8_t *image, + uint32_t image_len, + thd_img_conf_t* conf, + uint8_t image_id) { + + uint8_t pkt[SSDV_PKT_SIZE]; + uint8_t pkt_base91[256] = {0}; + + /* FIXME: This doesn't work with burst mode packet sends. */ + // Process redundant transmission from last cycle + if(strlen((char*)pkt_base91) + && conf->redundantTx) { + packet_t packet = aprs_encode_data_packet(conf->call, conf->path, + 'I', pkt_base91); + if(packet == NULL) { + TRACE_ERROR("IMG > No available packet for redundant" + " image transmission"); + } else { + if(!transmitOnRadio(packet, + conf->radio_conf.freq, + 0, + 0, + conf->radio_conf.pwr, + conf->radio_conf.mod, + conf->radio_conf.cca)) { + /* Packet has been released by transmit. */ + TRACE_ERROR("IMG > Unable to send redundant image on radio"); + } + } + chThdSleep(TIME_MS2I(10)); // Leave other threads some time + } + + /* Prepare for new image encode and send. */ + ssdv_t ssdv; + const uint8_t *b; + uint32_t bi = 0; + uint8_t c = SSDV_OK; + + /* Initialize SSDV, output buffer and input buffer. */ + ssdv_enc_init(&ssdv, SSDV_TYPE_PADDING, "N0CALL", image_id, conf->quality); + ssdv_enc_set_buffer(&ssdv, pkt); + ssdv_enc_feed(&ssdv, image, 0); + + while(c != SSDV_EOI) { + + /* + * Next encode packets. + * Packet burst send is available if redundant TX is not requested. + */ + uint8_t buffers = fmin((NUMBER_COMMON_PKT_BUFFERS / 2), + MAX_BUFFERS_FOR_BURST_SEND); + uint8_t chain = (conf->radio_conf.mod == MOD_2FSK + && !conf->redundantTx) ? + buffers : 1; + TRACE_INFO("IMG > Encode %i APRS/SSDV packet%s", chain, + (chain > 1 ? " burst" : "")); + + /* Packet linking control. */ + packet_t head = NULL; + packet_t previous = NULL; + + while(chain-- > 0) { + while((c = ssdv_enc_get_packet(&ssdv)) == SSDV_FEED_ME) { + b = &image[bi++]; + if(bi > image_len) { + TRACE_ERROR("SSDV > Premature end of file"); + if(head != NULL) { + pktReleaseBufferChain(head); + } + return false; + } + ssdv_enc_feed(&ssdv, b, 1); + } + + if(c == SSDV_EOI) { + TRACE_INFO("SSDV > ssdv_enc_get_packet said EOI"); + break; + } else if(c != SSDV_OK) { + TRACE_ERROR("SSDV > ssdv_enc_get_packet failed: %i", c); + if(head != NULL) { + pktReleaseBufferChain(head); + } + return false; + } + + /* + * Sync byte, CRC and FEC of SSDV not transmitted. + * Not necessary inside an APRS packet. + */ + base91_encode(&pkt[6], pkt_base91, 174); + + packet_t packet = aprs_encode_data_packet(conf->call, conf->path, + 'I', pkt_base91); + if(packet == NULL) { + TRACE_ERROR("IMG > No available packet for image transmission"); + /* Error so release any linked packets. */ + if(head != NULL) { + pktReleaseBufferChain(head); + } + return false; + } + if(previous != NULL) + /* Link the next packet into the chain. */ + previous->nextp = packet; + else + /* This is the first packet. */ + head = packet; + /* Now set new packet as previous. */ + previous = packet; + } /* End while(chain-- > 0) */ + + /* If we have some image packet(s) to transmit then do it. */ + if(head != NULL) { + if(!transmitOnRadio(head, + conf->radio_conf.freq, + 0, + 0, + conf->radio_conf.pwr, + conf->radio_conf.mod, + conf->radio_conf.cca)) { + TRACE_ERROR("IMG > Unable to send image on radio"); + /* Transmit on radio will release the packet chain. */ + } else { + // Packet spacing (delay) + if(conf->svc_conf.send_spacing) + chThdSleep(conf->svc_conf.send_spacing); + } + } + chThdSleep(TIME_MS2I(10)); // Leave other threads some time + } /* End while(c!= SSDV_EOI) */ + + // Repeat packets + for(uint8_t i=0; i<16; i++) { + if(packetRepeats[i].n_done && image_id == packetRepeats[i].image_id) { + if(!transmit_image_packet(image, image_len, conf, + image_id, packetRepeats[i].packet_id)) { + TRACE_ERROR("IMG > Failed re-send of image %i", image_id); + } else { + packetRepeats[i].n_done = false; // Set done + } + } + chThdSleep(TIME_MS2I(10)); // Leave other threads some time + } + + // Handle image rejection flag + if((conf == &conf_sram.img_pri) && reject_pri) { // Image rejected + reject_pri = false; + } + if((conf == &conf_sram.img_sec) && reject_sec) { // Image rejected + reject_sec = false; + } + return true; +} + +/** + * Analyzes the image for JPEG errors. Returns true if the image is error free. + */ +static bool analyze_image(uint8_t *image, uint32_t image_len) { + +#if !OV5640_USE_DMA_DBM + if(image_len >= 65535) { + TRACE_ERROR("CAM > Camera has %d bytes allocated but DMA DBM not activated", image_len); + TRACE_ERROR("CAM > DMA can only use 65535 bytes only"); + image_len = 65535; + } +#endif + + ssdv_t ssdv; + uint8_t pkt[SSDV_PKT_SIZE]; + uint8_t *b; + uint32_t bi = 0; + uint32_t i = 0; + uint8_t c = SSDV_OK; + + ssdv_enc_init(&ssdv, SSDV_TYPE_NOFEC, "", 0, 7); + ssdv_enc_set_buffer(&ssdv, pkt); + + while(++i < image_len) { + while((c = ssdv_enc_get_packet(&ssdv)) == SSDV_FEED_ME) { + b = &image[bi++]; + if(bi > image_len) { + TRACE_ERROR("CAM > Error in image (Premature end of file %d)", i); + return false; + } + ssdv_enc_feed(&ssdv, b, 1); + } + + if(c == SSDV_EOI) // End of image + return true; + + if(c != SSDV_OK) { + TRACE_ERROR("CAM > Error in image (ssdv_enc_get_packet failed: %d %d)", c, i); + return false; + } + chThdSleep(TIME_MS2I(5)); + } /* End while. */ + return false; +} + +static bool camInitialized = false; + +uint32_t takePicture(uint8_t* buffer, uint32_t size, + resolution_t res, bool enableJpegValidation) { + uint32_t size_sampled = 0; + + // Initialize mutex + if(!camera_mtx_init) + chMtxObjectInit(&camera_mtx); + camera_mtx_init = true; + + // Lock camera + TRACE_INFO("IMG > Lock camera"); + chMtxLock(&camera_mtx); + + // Detect camera + if(camInitialized || OV5640_isAvailable()) { // OV5640 available + + TRACE_INFO("IMG > OV5640 found"); + + uint8_t cntr = 5; + bool jpegValid; + // Init camera + if(!camInitialized) { + OV5640_init(); + camInitialized = true; + } + do { + // Init camera +/* if(!camInitialized) { + OV5640_init(); + camInitialized = true; + }*/ + // Sample data from pseudo DCMI through DMA into RAM + size_sampled = OV5640_Snapshot2RAM(buffer, size, res); + if(size_sampled == 0) + continue; + // Switch off camera +/* if(!conf_sram.keep_cam_switched_on) { + OV5640_deinit(); + camInitialized = false; + }*/ + + // Validate JPEG image + if(enableJpegValidation) + { + TRACE_INFO("CAM > Validate integrity of JPEG"); + jpegValid = analyze_image(buffer, size); + TRACE_INFO("CAM > JPEG image %s", jpegValid ? "valid" : "invalid"); + } else { + jpegValid = true; + } + } while(!jpegValid && cntr--); + + } else { // Camera not found + + camInitialized = false; + TRACE_ERROR("IMG > No camera found"); + } + // Switch off camera + if(!conf_sram.keep_cam_switched_on) { + OV5640_deinit(); + camInitialized = false; + } + // Unlock camera + TRACE_INFO("IMG > Unlock camera"); + chMtxUnlock(&camera_mtx); + + return size_sampled; +} +/* + * + */ +THD_FUNCTION(imgThread, arg) { + thd_img_conf_t* conf = (thd_img_conf_t*)arg; + + if(conf->svc_conf.init_delay) chThdSleep(conf->svc_conf.init_delay); + TRACE_INFO("IMG > Startup image thread"); + + // Create buffer + //uint8_t buffer[conf->buf_size] __attribute__((aligned(DMA_FIFO_BURST_ALIGN))); + + sysinterval_t time = chVTGetSystemTime(); + while(true) { + char code_s[100]; + pktDisplayFrequencyCode(conf->radio_conf.freq, + code_s, sizeof(code_s)); + TRACE_INFO("POS > Do module IMAGE cycle for %s on %s", + conf->call, code_s); + if(p_sleep(&conf->svc_conf.sleep_conf)) { + /* Re-check every minute. */ + chThdSleep(TIME_S2I(60)); + continue; + } + uint32_t my_image_id = gimage_id++; + /* Create image capture buffer. */ + uint8_t *buffer = chHeapAllocAligned(NULL, conf->buf_size, + DMA_FIFO_BURST_ALIGN); + if(buffer == NULL) { + /* Could not get a capture buffer. */ + TRACE_WARN("IMG > Unable to get capture buffer for image %i", + my_image_id); + /* Allow time for other threads. */ + chThdSleep(TIME_MS2I(10)); + /* Try again at next run time. */ + time = waitForTrigger(time, conf->svc_conf.cycle); + continue; + } + /* + * History... compiler bug + * If size is > 65535 the compiled code wraps address around and kills CMM heap. + * Anyway clearing of the capture buffer is no longer needed. + * SOI is now aligned at index 0 and length > EOI is returned by DMA. + */ +/* uint32_t size = conf->buf_size; + for(uint32_t i = 0; i < size ; i++) + buffer[i] = 0;*/ + /* Take picture. */ + uint32_t size_sampled = takePicture(buffer, conf->buf_size, + conf->res, true); + /* Nothing captured? */ + if(size_sampled == 0) { + TRACE_INFO("IMG > Encode/Transmit SSDV (camera error) ID=%d", + my_image_id); + if(!transmit_image_packets(noCameraFound, sizeof(noCameraFound), + conf, (uint8_t)(my_image_id))) { + TRACE_ERROR("IMG > Error in encoding dummy image %i" + " - discarded", my_image_id); + } + /* Return the buffer to the heap. */ + chHeapFree(buffer); + /* Allow time for other threads. */ + chThdSleep(TIME_MS2I(10)); + /* Try again at next run time. */ + time = waitForTrigger(time, conf->svc_conf.cycle); + continue; + } + + /* Find SOI in image buffer. */ + uint32_t soi; + bool soi_found = false; + while(soi < (size_sampled - 1)) { + if(buffer[soi] == 0xFF && buffer[soi + 1] == 0xD8) { + /* Found an SOI. */ + soi_found = true; + TRACE_INFO("IMG > SOI at index %i of buffer", soi); + /* Write to SD if present. */ + if(initSD()) { + char filename[32]; + // Write picture to SD card + TRACE_INFO("IMG > Save image to SD card"); + + chsnprintf(filename, sizeof(filename), "r%02xi%04x.jpg", + getLastDataPoint()->reset % 0xFF, + (my_image_id) % 0xFFFF); + + writeBufferToFile(filename, &buffer[soi], size_sampled - soi); + } /* End initSD() */ + + /* Transmit on radio. */ + if(conf->radio_conf.mod == MOD_2FSK && conf->redundantTx) { + TRACE_WARN("IMG > Redundant TX disables 2FSK burst send mode"); + } + + /* Encode and transmit picture. */ + TRACE_INFO("IMG > Encode/Transmit SSDV ID=%d", my_image_id); + if(!transmit_image_packets(buffer, size_sampled, conf, + (uint8_t)(my_image_id))) { + TRACE_ERROR("IMG > Error in encoding snapshot image" + " %i - discarded", my_image_id); + } + break; + } /* End if SOI in buffer. */ + } /* End while soi < size_sampled - 1. */ + if(!soi_found) { /* No SOI found. */ + TRACE_INFO("IMG > No SOI found in image"); + } + /* Return the buffer to the heap. */ + chHeapFree(buffer); + /* Allow minimum time for other threads. */ + chThdSleep(TIME_MS2I(10)); + /* Update next run time. */ + time = waitForTrigger(time, conf->svc_conf.cycle); + } +} + +/* + * + */ +void start_image_thread(thd_img_conf_t *conf) +{ + thread_t *th = chThdCreateFromHeap(NULL, + THD_WORKING_AREA_SIZE(40 * 1024), + "IMG", LOWPRIO, imgThread, conf); + if(!th) { + // Print startup error, do not start watchdog for this thread + TRACE_ERROR("IMG > Could not startup thread" + " (not enough memory available)"); + } +} + diff --git a/tracker/software/source/threads/rxtx/log.c b/tracker/software/source/threads/rxtx/log.c index 25aa40ac..66039681 100644 --- a/tracker/software/source/threads/rxtx/log.c +++ b/tracker/software/source/threads/rxtx/log.c @@ -1,89 +1,89 @@ -/** - * Logging module - * - */ - -#include "ch.h" -#include "hal.h" - -#include "debug.h" -#include "threads.h" -#include "base91.h" -#include "aprs.h" -#include "sleep.h" -#include "radio.h" -#include "log.h" -#include "pflash.h" - -static uint16_t log_id = 0; - -static dataPoint_t* getNextLogDataPoint(uint8_t density) -{ - // Determine sector - dataPoint_t *tp; - uint32_t i = 0; - do { - if((tp = flash_getLogBuffer(log_id))) { - log_id += density; - } else { - log_id = 0; - tp = flash_getLogBuffer(0); - } - } while(LOG_IS_EMPTY(tp) && i++ < LOG_FLASH_SIZE / sizeof(dataPoint_t)); - - return LOG_IS_EMPTY(tp) ? NULL : tp; -} - -THD_FUNCTION(logThread, arg) -{ - thd_log_conf_t* conf = (thd_log_conf_t*)arg; - - if(conf->thread_conf.init_delay) chThdSleep(conf->thread_conf.init_delay); - TRACE_INFO("LOG > Startup logging thread"); - - sysinterval_t time = chVTGetSystemTime(); - while(true) - { - TRACE_INFO("LOG > Do module LOG cycle"); - - if(!p_sleep(&conf->thread_conf.sleep_conf)) - { - // Get log from memory - dataPoint_t *log = getNextLogDataPoint(conf->density); - - if(log) { - // Encode Base91 - uint8_t pkt_base91[BASE91LEN(sizeof(dataPoint_t))]; - base91_encode((uint8_t*)log, pkt_base91, sizeof(dataPoint_t)); - // Encode and transmit log packet - packet_t packet = aprs_encode_data_packet(conf->call, conf->path, 'L', pkt_base91); // Encode packet - if(packet == NULL) { - TRACE_WARN("LOG > No free packet objects for log transmission"); - } else { - // Transmit packet - transmitOnRadio(packet, - conf->radio_conf.freq, - 0, - 0, - conf->radio_conf.pwr, - conf->radio_conf.mod, - conf->radio_conf.cca); - } - } else { - TRACE_INFO("LOG > No log point in memory"); - } - } - - time = waitForTrigger(time, conf->thread_conf.cycle); - } -} - -void start_logging_thread(thd_log_conf_t *conf) -{ - thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(6*1024), "LOG", LOWPRIO, logThread, conf); - if(!th) { - // Print startup error, do not start watchdog for this thread - TRACE_ERROR("LOG > Could not startup thread (not enough memory available)"); - } -} - +/** + * Logging module + * + */ + +#include "ch.h" +#include "hal.h" + +#include "debug.h" +#include "threads.h" +#include "base91.h" +#include "aprs.h" +#include "sleep.h" +#include "radio.h" +#include "log.h" +#include "pflash.h" + +static uint16_t log_id = 0; + +static dataPoint_t* getNextLogDataPoint(uint8_t density) +{ + // Determine sector + dataPoint_t *tp; + uint32_t i = 0; + do { + if((tp = flash_getLogBuffer(log_id))) { + log_id += density; + } else { + log_id = 0; + tp = flash_getLogBuffer(0); + } + } while(LOG_IS_EMPTY(tp) && i++ < LOG_FLASH_SIZE / sizeof(dataPoint_t)); + + return LOG_IS_EMPTY(tp) ? NULL : tp; +} + +THD_FUNCTION(logThread, arg) +{ + thd_log_conf_t* conf = (thd_log_conf_t*)arg; + + if(conf->svc_conf.init_delay) chThdSleep(conf->svc_conf.init_delay); + TRACE_INFO("LOG > Startup logging thread"); + + sysinterval_t time = chVTGetSystemTime(); + while(true) + { + TRACE_INFO("LOG > Do module LOG cycle"); + + if(!p_sleep(&conf->svc_conf.sleep_conf)) + { + // Get log from memory + dataPoint_t *log = getNextLogDataPoint(conf->density); + + if(log) { + // Encode Base91 + uint8_t pkt_base91[BASE91LEN(sizeof(dataPoint_t))]; + base91_encode((uint8_t*)log, pkt_base91, sizeof(dataPoint_t)); + // Encode and transmit log packet + packet_t packet = aprs_encode_data_packet(conf->call, conf->path, 'L', pkt_base91); // Encode packet + if(packet == NULL) { + TRACE_WARN("LOG > No free packet objects for log transmission"); + } else { + // Transmit packet + transmitOnRadio(packet, + conf->radio_conf.freq, + 0, + 0, + conf->radio_conf.pwr, + conf->radio_conf.mod, + conf->radio_conf.cca); + } + } else { + TRACE_INFO("LOG > No log point in memory"); + } + } + + time = waitForTrigger(time, conf->svc_conf.cycle); + } +} + +void start_logging_thread(thd_log_conf_t *conf) +{ + thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(6*1024), "LOG", LOWPRIO, logThread, conf); + if(!th) { + // Print startup error, do not start watchdog for this thread + TRACE_ERROR("LOG > Could not startup thread (not enough memory available)"); + } +} + diff --git a/tracker/software/source/threads/rxtx/position.c b/tracker/software/source/threads/rxtx/position.c index 40924c51..34940467 100644 --- a/tracker/software/source/threads/rxtx/position.c +++ b/tracker/software/source/threads/rxtx/position.c @@ -1,153 +1,153 @@ -#include "ch.h" -#include "hal.h" - -#include "debug.h" -#include "threads.h" -#include "config.h" -#include "radio.h" -#include "aprs.h" -#include "sleep.h" -#include "chprintf.h" -#include -#include -#include "watchdog.h" - -/* - * - */ -THD_FUNCTION(posThread, arg) -{ - thd_pos_conf_t* conf = (thd_pos_conf_t*)arg; - - // Wait - if(conf->thread_conf.init_delay) chThdSleep(conf->thread_conf.init_delay); - - // Start data collector (if not running yet) - init_data_collector(); - - // Start position thread - TRACE_INFO("POS > Startup position thread"); - - // Set telemetry configuration transmission variables - sysinterval_t last_conf_transmission = - chVTGetSystemTime() - conf->tel_enc_cycle; - sysinterval_t time = chVTGetSystemTime(); - - while(true) { - char code_s[100]; - pktDisplayFrequencyCode(conf->radio_conf.freq, - code_s, sizeof(code_s)); - TRACE_INFO("POS > Do module POSITION cycle for %s on %s", - conf->call, code_s); - - TRACE_INFO("POS > Get last data point"); - dataPoint_t* dataPoint = getLastDataPoint(); - - if(!p_sleep(&conf->thread_conf.sleep_conf)) { - TRACE_INFO("POS > Transmit position"); - - // Telemetry encoding parameter transmission - if(conf->tel_enc_cycle != 0 && last_conf_transmission - + conf->tel_enc_cycle < chVTGetSystemTime()) { - - TRACE_INFO("POS > Transmit telemetry configuration"); - - // Encode and transmit telemetry config packet - for(uint8_t type = 0; type < APRS_NUM_TELEM_GROUPS; type++) { - packet_t packet = aprs_encode_telemetry_configuration( - conf->call, - conf->path, - conf->call, - type); - if(packet == NULL) { - TRACE_WARN("POS > No free packet objects for" - " telemetry transmission"); - } else { - if(!transmitOnRadio(packet, - conf->radio_conf.freq, - 0, - 0, - conf->radio_conf.pwr, - conf->radio_conf.mod, - conf->radio_conf.cca)) { - TRACE_ERROR("POS > Failed to transmit telemetry data"); - } - } - chThdSleep(TIME_S2I(5)); - } - - last_conf_transmission += conf->tel_enc_cycle; - } - // Encode/Transmit position packet - packet_t packet = aprs_encode_position_and_telemetry(conf->call, - conf->path, - conf->symbol, - dataPoint, - true); - if(packet == NULL) { - TRACE_WARN("POS > No free packet objects" - " for position transmission"); - } else { - if(!transmitOnRadio(packet, - conf->radio_conf.freq, - 0, - 0, - conf->radio_conf.pwr, - conf->radio_conf.mod, - conf->radio_conf.cca)) { - TRACE_ERROR("POS > failed to transmit position data"); - } - chThdSleep(TIME_S2I(5)); - } - - /* - * Encode/Transmit APRSD packet. - * This is a tracker originated message (not a reply to a request). - * The message will be sent to the base station if set. - * Else send it to device identity. - */ - char *call = conf_sram.aprs.base.enabled - ? conf_sram.aprs.base.call : conf->call; - /* - * Send message from this device. - * Use call sign and path as specified in base config. - * There is no acknowledgment requested. - */ - packet = aprs_compose_aprsd_message( - conf->call, - conf->path, - call); - if(packet == NULL) { - TRACE_WARN("POS > No free packet objects " - "or badly formed APRSD message"); - } else { - if(!transmitOnRadio(packet, - conf->radio_conf.freq, - 0, - 0, - conf->radio_conf.pwr, - conf->radio_conf.mod, - conf->radio_conf.cca - )) { - TRACE_ERROR("POS > Failed to transmit APRSD data"); - } - chThdSleep(TIME_S2I(5)); - } - } - time = waitForTrigger(time, conf->thread_conf.cycle); - } -} - -/* - * - */ -void start_position_thread(thd_pos_conf_t *conf) -{ - thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(10*1024), - "POS", LOWPRIO, posThread, conf); - if(!th) { - // Print startup error, do not start watchdog for this thread - TRACE_ERROR("POS > Could not startup thread (not enough memory available)"); - } -} - +#include "ch.h" +#include "hal.h" + +#include "debug.h" +#include "threads.h" +#include "config.h" +#include "radio.h" +#include "aprs.h" +#include "sleep.h" +#include "chprintf.h" +#include +#include +#include "watchdog.h" + +/* + * + */ +THD_FUNCTION(posThread, arg) +{ + thd_pos_conf_t* conf = (thd_pos_conf_t*)arg; + + // Wait + if(conf->svc_conf.init_delay) chThdSleep(conf->svc_conf.init_delay); + + // Start data collector (if not running yet) + init_data_collector(); + + // Start position thread + TRACE_INFO("POS > Startup position thread"); + + // Set telemetry configuration transmission variables + sysinterval_t last_conf_transmission = + chVTGetSystemTime() - conf_sram.tel_enc_cycle; + sysinterval_t time = chVTGetSystemTime(); + + while(true) { + char code_s[100]; + pktDisplayFrequencyCode(conf->radio_conf.freq, + code_s, sizeof(code_s)); + TRACE_INFO("POS > Do module POSITION cycle for %s on %s", + conf->call, code_s); + + TRACE_INFO("POS > Get last data point"); + dataPoint_t* dataPoint = getLastDataPoint(); + + if(!p_sleep(&conf->svc_conf.sleep_conf)) { + TRACE_INFO("POS > Transmit position"); + + // Telemetry encoding parameter transmission + if(conf_sram.tel_enc_cycle != 0 && last_conf_transmission + + conf_sram.tel_enc_cycle < chVTGetSystemTime()) { + + TRACE_INFO("POS > Transmit telemetry configuration"); + + // Encode and transmit telemetry config packet + for(uint8_t type = 0; type < APRS_NUM_TELEM_GROUPS; type++) { + packet_t packet = aprs_encode_telemetry_configuration( + conf->call, + conf->path, + conf->call, + type); + if(packet == NULL) { + TRACE_WARN("POS > No free packet objects for" + " telemetry transmission"); + } else { + if(!transmitOnRadio(packet, + conf->radio_conf.freq, + 0, + 0, + conf->radio_conf.pwr, + conf->radio_conf.mod, + conf->radio_conf.cca)) { + TRACE_ERROR("POS > Failed to transmit telemetry data"); + } + } + chThdSleep(TIME_S2I(5)); + } + + last_conf_transmission += conf_sram.tel_enc_cycle; + } + // Encode/Transmit position packet + packet_t packet = aprs_encode_position_and_telemetry(conf->call, + conf->path, + conf->symbol, + dataPoint, + true); + if(packet == NULL) { + TRACE_WARN("POS > No free packet objects" + " for position transmission"); + } else { + if(!transmitOnRadio(packet, + conf->radio_conf.freq, + 0, + 0, + conf->radio_conf.pwr, + conf->radio_conf.mod, + conf->radio_conf.cca)) { + TRACE_ERROR("POS > failed to transmit position data"); + } + chThdSleep(TIME_S2I(5)); + } + + /* + * Encode/Transmit APRSD packet. + * This is a tracker originated message (not a reply to a request). + * The message will be sent to the base station if set. + * Else send it to device identity. + */ + char *call = conf_sram.base.enabled + ? conf_sram.base.call : conf->call; + /* + * Send message from this device. + * Use call sign and path as specified in base config. + * There is no acknowledgment requested. + */ + packet = aprs_compose_aprsd_message( + conf->call, + conf->path, + call); + if(packet == NULL) { + TRACE_WARN("POS > No free packet objects " + "or badly formed APRSD message"); + } else { + if(!transmitOnRadio(packet, + conf->radio_conf.freq, + 0, + 0, + conf->radio_conf.pwr, + conf->radio_conf.mod, + conf->radio_conf.cca + )) { + TRACE_ERROR("POS > Failed to transmit APRSD data"); + } + chThdSleep(TIME_S2I(5)); + } + } + time = waitForTrigger(time, conf->svc_conf.cycle); + } +} + +/* + * + */ +void start_position_thread(thd_pos_conf_t *conf) +{ + thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(10*1024), + "POS", LOWPRIO, posThread, conf); + if(!th) { + // Print startup error, do not start watchdog for this thread + TRACE_ERROR("POS > Could not startup thread (not enough memory available)"); + } +} + diff --git a/tracker/software/source/threads/threads.c b/tracker/software/source/threads/threads.c index 56def309..60774a5e 100644 --- a/tracker/software/source/threads/threads.c +++ b/tracker/software/source/threads/threads.c @@ -1,64 +1,71 @@ -#include "ch.h" -#include "hal.h" - -#include "watchdog.h" -#include "pi2c.h" -#include "pac1720.h" -#include "radio.h" -#include "si446x.h" -#include "image.h" -#include "position.h" -#include "beacon.h" -#include "log.h" -#include "radio.h" -#include "ax25_pad.h" -#include "flash.h" - -sysinterval_t watchdog_tracking; - -void start_essential_threads(void) -{ - init_watchdog(); // Init watchdog - pac1720_init(); // Initialize current measurement - chThdSleep(TIME_MS2I(300)); // Wait for tracking manager to initialize -} - -void start_user_threads(void) -{ - conf_t *conf_flash = (conf_t*)0x08060000; - /* Check if a user update has been made to configuration in flash. */ - if(conf_flash->magic != CONFIG_MAGIC_UPDATED) { - if(conf_flash->magic != CONFIG_MAGIC_DEFAULT - || memcmp(&conf_flash, &conf_flash_default, sizeof(conf_t)) != 0) { - /* No configuration found in flash memory or default config has changed. */ - flashSectorBegin(flashSectorAt((uint32_t)conf_flash)); - flashErase((uint32_t)conf_flash, 0x20000); - flashWrite((uint32_t)conf_flash, (char*)&conf_flash_default, sizeof(conf_t)); - flashSectorEnd(flashSectorAt((uint32_t)conf_flash)); - } - } - - // Copy - memcpy(&conf_sram, conf_flash, sizeof(conf_t)); - - if(conf_sram.pos_pri.thread_conf.active) start_position_thread(&conf_sram.pos_pri); - if(conf_sram.pos_sec.thread_conf.active) start_position_thread(&conf_sram.pos_sec); - - if(conf_sram.img_pri.thread_conf.active) start_image_thread(&conf_sram.img_pri); - if(conf_sram.img_sec.thread_conf.active) start_image_thread(&conf_sram.img_sec); - - if(conf_sram.log.thread_conf.active) start_logging_thread(&conf_sram.log); - - if(conf_sram.aprs.thread_conf.active && conf_sram.aprs.digi.beacon) - start_beacon_thread(&conf_sram.aprs); - - if(conf_sram.aprs.thread_conf.active) { - chThdSleep(conf_sram.aprs.thread_conf.init_delay); - start_aprs_threads(PKT_RADIO_1, - conf_sram.aprs.rx.radio_conf.freq, - 0, - 0, - conf_sram.aprs.rx.radio_conf.rssi); - } -} - +#include "ch.h" +#include "hal.h" + +#include "watchdog.h" +#include "pi2c.h" +#include "pac1720.h" +#include "radio.h" +#include "si446x.h" +#include "image.h" +#include "position.h" +#include "beacon.h" +#include "log.h" +#include "radio.h" +#include "ax25_pad.h" +#include "flash.h" + +sysinterval_t watchdog_tracking; + +void start_essential_threads(void) +{ + init_watchdog(); // Init watchdog + pac1720_init(); // Initialize current measurement + chThdSleep(TIME_MS2I(300)); // Wait for tracking manager to initialize +} + +void start_user_threads(void) +{ + conf_t *conf_flash = (conf_t*)0x08060000; + /* Check if a user update has been made to configuration in flash. */ + if(conf_flash->magic != CONFIG_MAGIC_UPDATED) { + if(conf_flash->magic != CONFIG_MAGIC_DEFAULT + || memcmp(&conf_flash, &conf_flash_default, sizeof(conf_t)) != 0) { + /* No configuration found in flash memory or default config has changed. */ + flashSectorBegin(flashSectorAt((uint32_t)conf_flash)); + flashErase((uint32_t)conf_flash, 0x20000); + flashWrite((uint32_t)conf_flash, (char*)&conf_flash_default, sizeof(conf_t)); + flashSectorEnd(flashSectorAt((uint32_t)conf_flash)); + } + } + + // Copy + memcpy(&conf_sram, conf_flash, sizeof(conf_t)); + + if(conf_sram.pos_pri.svc_conf.active) + start_position_thread(&conf_sram.pos_pri); + if(conf_sram.pos_sec.svc_conf.active) + start_position_thread(&conf_sram.pos_sec); + + if(conf_sram.img_pri.svc_conf.active) + start_image_thread(&conf_sram.img_pri); + if(conf_sram.img_sec.svc_conf.active) + start_image_thread(&conf_sram.img_sec); + + if(conf_sram.log.svc_conf.active) + start_logging_thread(&conf_sram.log); + + if(conf_sram.aprs.svc_conf.active + && conf_sram.aprs.digi.beacon.svc_conf.active) { + start_beacon_thread(&conf_sram.aprs); + } + + if(conf_sram.aprs.svc_conf.active) { + chThdSleep(conf_sram.aprs.svc_conf.init_delay); + start_aprs_threads(PKT_RADIO_1, + conf_sram.aprs.rx.radio_conf.freq, + 0, + 0, + conf_sram.aprs.rx.radio_conf.rssi); + } +} +