#include "packet_tx_handler.h" #include "wx_handler.h" #include "backup_registers.h" #include "rte_wx.h" #include "rte_pv.h" #include "rte_main.h" #include "station_config.h" #include "station_config_target_hw.h" #include "./aprs/beacon.h" #include "./aprs/status.h" #include "./aprs/wx.h" #include "./aprs/telemetry.h" #include "./drivers/serial.h" #include "./umb_master/umb_master.h" #include "../include/etc/rtu_configuration.h" #ifdef STM32L471xx #include "aprsis.h" #include "api/api.h" #include "pwr_save.h" #include "nvm.h" #include "gsm/sim800c_gprs.h" #endif #include "main.h" #include "delay.h" #include "io.h" #define _TELEM_DESCR_INTERVAL 150 uint8_t packet_tx_beacon_interval = 0; uint8_t packet_tx_beacon_counter = 0; uint8_t packet_tx_error_status_interval = 2; uint8_t packet_tx_error_status_counter = 0; uint8_t packet_tx_meteo_interval = 0; uint8_t packet_tx_meteo_counter = 0; uint8_t packet_tx_meteo_kiss_interval = 2; uint8_t packet_tx_meteo_kiss_counter = 0; uint8_t packet_tx_telemetry_interval = 10; uint8_t packet_tx_telemetry_counter = 0; uint8_t packet_tx_telemetry_descr_interval = 155; // 155 uint8_t packet_tx_telemetry_descr_counter = 10; uint8_t packet_tx_modbus_raw_values = (uint8_t)(_TELEM_DESCR_INTERVAL - _WX_INTERVAL * (uint8_t)(_TELEM_DESCR_INTERVAL / 38)); uint8_t packet_tx_modbus_status = (uint8_t)(_TELEM_DESCR_INTERVAL - _WX_INTERVAL * (uint8_t)(_TELEM_DESCR_INTERVAL / 5)); uint8_t packet_tx_more_than_one = 0; #ifdef PARAMETEO uint8_t packet_tx_trigger_tcp = 0; uint8_t packet_tx_meteo_gsm_interval = 2; uint8_t packet_tx_meteo_gsm_counter = 0; //!< Flag set to one after weather packet has been just send to APRS-IS via GPRS modem and the modem is probably transmitting now uint8_t packet_tx_meteo_gsm_has_been_sent = 0; #define API_TRIGGER_STATUS (1 << 1) #define API_TRIGGER_METEO (1 << 2) #define APRSIS_TRIGGER_METEO (1 << 3) #define RECONNECT_APRSIS (1 << 7) nvm_measurement_t packet_tx_nvm; //!< Flag set to one after the gsm status message is sent over radio. uint8_t packet_tx_gsm_status_sent = 0; #endif void packet_tx_send_wx_frame(void) { main_wait_for_tx_complete(); SendWXFrame(rte_wx_average_windspeed, rte_wx_max_windspeed, rte_wx_average_winddirection, rte_wx_temperature_average_external_valid, rte_wx_pressure_valid, rte_wx_humidity_valid); } void packet_tx_init(uint8_t meteo_interval, uint8_t beacon_interval, config_data_powersave_mode_t powersave) { packet_tx_meteo_interval = meteo_interval; packet_tx_beacon_interval = beacon_interval; backup_reg_get_packet_counters(&packet_tx_beacon_counter, &packet_tx_meteo_counter, &packet_tx_meteo_gsm_counter); if (powersave == PWSAVE_AGGRESV) { // if user selected aggressive powersave mode the meteo counter must be set back to zero // to prevent quirks with waking from sleep mode packet_tx_meteo_counter = 0; } } void packet_tx_restore_from_backupregs(void) { } /** * This function is called from the inside of 'packet_rx_handler' to put an extra wait * if more than one packet is sent from the single call to that function. This is required * to protect against jamming own frames when any path is configured. * */ inline void packet_tx_multi_per_call_handler(void) { // if this consecutive frame sent from one call to this function if (packet_tx_more_than_one > 0) { // wait for previous transmission to complete main_wait_for_tx_complete(); // wait for any possible retransmission to kick in delay_fixed(2000); // reload watchdog counter main_reload_internal_wdg(); } else { packet_tx_more_than_one = 1; } } // this shall be called in 10 seconds interval void packet_tx_tcp_handler(void) { #ifdef STM32L471xx // TODO: fixme currently there is no way to have APRS-IS and rest api // client working at the same time aprsis_return_t aprsis_result = APRSIS_UNKNOWN; if ((packet_tx_trigger_tcp & APRSIS_TRIGGER_METEO) != 0) { if (gsm_sim800_tcpip_tx_busy() == 0 && aprsis_connected == 1) { aprsis_send_wx_frame( rte_wx_average_windspeed, rte_wx_max_windspeed, rte_wx_average_winddirection, rte_wx_temperature_average_external_valid, rte_wx_pressure_valid, rte_wx_humidity_valid, main_callsign_with_ssid, main_string_latitude, main_string_longitude, main_config_data_basic); // clear the flag requesting weather packet transmission packet_tx_trigger_tcp ^= APRSIS_TRIGGER_METEO; // set this flag to one to inhibit power saving state machine // for a while (10 seconds) when GPRS modem is communicating // with the GSM radio network and sending the data independently // from the controller packet_tx_meteo_gsm_has_been_sent = 1; } } else if ((packet_tx_trigger_tcp & API_TRIGGER_STATUS) != 0) { // TODO: fixme // check if APRS-IS is connected if (aprsis_connected != 0) { // disconnect it before call to API - this disconnection has blocking IO aprsis_disconnect(); // remember to reconnect APRSIS after all API comm will be done packet_tx_trigger_tcp |= RECONNECT_APRSIS; } // TODO: fixme // send status (async) api_send_json_status(); // clear the bit packet_tx_trigger_tcp ^= API_TRIGGER_STATUS; } else if ((packet_tx_trigger_tcp & API_TRIGGER_METEO) != 0) { // TODO: fixme // check if APRS-IS is connected if (aprsis_connected != 0) { // disconnect it before call to API - this disconnection has blocking IO aprsis_disconnect(); // remember to reconnect APRSIS after all API comm will be done packet_tx_trigger_tcp |= RECONNECT_APRSIS; } // TODO: fixme api_calculate_mac(); api_send_json_measuremenets(); // clear the bit packet_tx_trigger_tcp ^= API_TRIGGER_METEO; } else if ((packet_tx_trigger_tcp & RECONNECT_APRSIS) != 0) { // TODO: fixme // result = aprsis_connect_and_login_default(1); // // if (result == APRSIS_OK) { packet_tx_trigger_tcp ^= RECONNECT_APRSIS; // } } else { // after 10 second from setting this flag the packet should be // sent packet_tx_meteo_gsm_has_been_sent = 0; } #endif } // this shall be called in 60 seconds intervals void packet_tx_handler(const config_data_basic_t * const config_basic, const config_data_mode_t * const config_mode) { dallas_qf_t dallas_qf = DALLAS_QF_UNKNOWN; pressure_qf_t pressure_qf = PRESSURE_QF_UNKNOWN; humidity_qf_t humidity_qf = HUMIDITY_QF_UNKNOWN; wind_qf_t wind_qf = WIND_QF_UNKNOWN; uint16_t ln = 0; // set to one if more than one packet will be send from this function at once (like beacon + telemetry) packet_tx_more_than_one = 0; // increase beacon transmit counters packet_tx_beacon_counter++; packet_tx_error_status_counter++; packet_tx_telemetry_counter++; packet_tx_telemetry_descr_counter++; if ((main_config_data_mode->wx & WX_ENABLED) == WX_ENABLED) { // increase these counters only when WX is enabled packet_tx_meteo_counter++; packet_tx_meteo_kiss_counter++; packet_tx_meteo_gsm_counter++; } // check if there is a time to send own beacon if (packet_tx_beacon_counter >= packet_tx_beacon_interval && packet_tx_beacon_interval != 0) { packet_tx_multi_per_call_handler(); beacon_send_own(0, 0); packet_tx_beacon_counter = 0; } // if WX is enabled if ((main_config_data_mode->wx & WX_ENABLED) == WX_ENABLED) { // check if there is a time to send meteo packet through RF if (packet_tx_meteo_counter >= packet_tx_meteo_interval && packet_tx_meteo_interval != 0) { #ifdef STM32L471xx if (config_mode->nvm_logger != 0) { packet_tx_nvm.temperature_humidity = wx_get_nvm_record_temperature(); packet_tx_nvm.wind = wx_get_nvm_record_wind(); packet_tx_nvm.timestamp = main_get_nvm_timestamp(); // write to NVM if it is enabled nvm_measurement_store(&packet_tx_nvm); } #endif // this function is required if more than one RF frame will be send from this function at once // it waits for transmission completion and add some delay to let digipeaters do theris job packet_tx_multi_per_call_handler(); // send WX frame through RF SendWXFrame(rte_wx_average_windspeed, rte_wx_max_windspeed, rte_wx_average_winddirection, rte_wx_temperature_average_external_valid, rte_wx_pressure_valid, rte_wx_humidity_valid); /** * debug * * #define REGISTER_LAST_SLEEP RTC->BKP1R #define REGISTER_LAST_WKUP RTC->BKP2R #define REGISTER_COUNTERS RTC->BKP4R * */ // service external watchdog while sending weather frame io_ext_watchdog_service(); // check if user want's to send two wx packets one after another if (main_config_data_basic->wx_double_transmit == 1) { rte_main_trigger_wx_packet = 1; } packet_tx_meteo_counter = 0; } // if modus RTU client is enabled with debugging if ((main_config_data_mode->wx_modbus & WX_MODBUS_DEBUG) == WX_MODBUS_DEBUG) { // send the status packet with raw values of all requested modbus-RTU registers if (packet_tx_meteo_counter == (packet_tx_meteo_interval - 1) && packet_tx_telemetry_descr_counter >= packet_tx_modbus_raw_values) { packet_tx_multi_per_call_handler(); status_send_raw_values_modbus(); } } // there is no sense to include support for Victron VE.Direct in parameteo // which has its own charging controller #ifndef PARAMETEO // check if Victron VE.Direct serial protocol client is enabled and it is // a time to send the status message if (config_mode->victron == 1 && packet_tx_meteo_counter == (packet_tx_meteo_interval - 1) && packet_tx_telemetry_descr_counter >= packet_tx_modbus_raw_values) { packet_tx_multi_per_call_handler(); telemetry_send_status_pv(&rte_pv_average, &rte_pv_last_error, rte_pv_struct.system_state, master_time, rte_pv_messages_count, rte_pv_corrupted_messages_count); } #endif #ifdef PARAMETEO if (packet_tx_meteo_gsm_counter >= packet_tx_meteo_gsm_interval && gsm_sim800_gprs_ready == 1) { if (main_config_data_gsm->aprsis_enable == 0 && main_config_data_gsm->api_enable == 1) { // and trigger API wx packet transmission packet_tx_trigger_tcp |= API_TRIGGER_METEO; } else if (main_config_data_gsm->aprsis_enable == 1 && main_config_data_gsm->api_enable == 0) { packet_tx_trigger_tcp |= APRSIS_TRIGGER_METEO; } else { packet_tx_trigger_tcp = 0; } packet_tx_meteo_gsm_counter = 0; } #endif // send wx frame to KISS host once every two minutes if (packet_tx_meteo_kiss_counter >= packet_tx_meteo_kiss_interval && main_kiss_enabled == 1) { // wait if serial port is currently used srl_wait_for_tx_completion(main_kiss_srl_ctx_ptr); // create wx data packet into specified buffer SendWXFrameToKissBuffer(rte_wx_average_windspeed, rte_wx_max_windspeed, rte_wx_average_winddirection, rte_wx_temperature_average_external_valid, rte_wx_pressure_valid, rte_wx_humidity_valid, main_kiss_srl_ctx_ptr->srl_tx_buf_pointer, main_kiss_srl_ctx_ptr->srl_tx_buf_ln, &ln); srl_start_tx(main_kiss_srl_ctx_ptr, ln); packet_tx_meteo_kiss_counter = 0; } } if (packet_tx_telemetry_counter >= packet_tx_telemetry_interval) { packet_tx_multi_per_call_handler(); // ASSEMBLY QUALITY FACTORS // if there weren't any errors related to the communication with DS12B20 from previous function call if (rte_wx_error_dallas_qf == DALLAS_QF_UNKNOWN) { dallas_qf = rte_wx_current_dallas_qf; // it might be DEGRADATED so we need to copy a value directly // reset current QF to check if there will be at least one successfull readout of temperature rte_wx_current_dallas_qf = DALLAS_QF_UNKNOWN; } // if there were any errors else { // if we had at least one successfull communication with the sensor if (rte_wx_current_dallas_qf == DALLAS_QF_FULL || rte_wx_current_dallas_qf == DALLAS_QF_DEGRADATED) { // set the error reason dallas_qf = DALLAS_QF_DEGRADATED; } // if they wasn't any successfull comm else { // set that it is totally dead and not avaliable dallas_qf = DALLAS_QF_NOT_AVALIABLE; } // and reset the error rte_wx_error_dallas_qf = DALLAS_QF_UNKNOWN; rte_wx_current_dallas_qf = DALLAS_QF_UNKNOWN; } // get quality factors for internal pressure and humidity sensors if (config_mode->wx_ms5611_or_bme == 0) { // pressure sensors quality factors switch (rte_wx_ms5611_qf) { case MS5611_QF_FULL: pressure_qf = PRESSURE_QF_FULL; break; case MS5611_QF_NOT_AVALIABLE: pressure_qf = PRESSURE_QF_NOT_AVALIABLE; break; case MS5611_QF_DEGRADATED: pressure_qf = PRESSURE_QF_DEGRADATED; break; case MS5611_QF_UNKNOWN: pressure_qf = PRESSURE_QF_UNKNOWN; break; } } else if (config_mode->wx_ms5611_or_bme == 1) { //#elif defined(_SENSOR_BME280) // humidity sensors quality factors if (rte_wx_bme280_qf == BME280_QF_UKNOWN) { ; } else { // use BME280 switch (rte_wx_bme280_qf) { case BME280_QF_FULL: case BME280_QF_PRESSURE_DEGRADED: humidity_qf = HUMIDITY_QF_FULL; break; case BME280_QF_UKNOWN: case BME280_QF_NOT_AVAILABLE: humidity_qf = HUMIDITY_QF_NOT_AVALIABLE; break; case BME280_QF_HUMIDITY_DEGRADED: case BME280_QF_GEN_DEGRADED: humidity_qf = HUMIDITY_QF_DEGRADATED; break; } switch (rte_wx_bme280_qf) { case BME280_QF_FULL: case BME280_QF_HUMIDITY_DEGRADED: pressure_qf = PRESSURE_QF_FULL; break; case BME280_QF_UKNOWN: case BME280_QF_NOT_AVAILABLE: pressure_qf = PRESSURE_QF_NOT_AVALIABLE; break; case BME280_QF_PRESSURE_DEGRADED: case BME280_QF_GEN_DEGRADED: pressure_qf = PRESSURE_QF_DEGRADATED; break; } } } else { pressure_qf = PRESSURE_QF_NOT_AVALIABLE; humidity_qf = HUMIDITY_QF_NOT_AVALIABLE; } // additional check for modbus RTU humidity sensor if (config_mode->wx_modbus == 1) { // for sake of simplicity there is another variable // which holds common information if valid humidity // is available or not. This is kinda-sorta duplication of // quality factors for each sensor types, although it // simplifies few things and omits handling separate // Modbus-RTU configuration here if (rte_wx_humidity_available == 0) { humidity_qf = HUMIDITY_QF_NOT_AVALIABLE; } else if (rte_wx_humidity_available == 1) { humidity_qf = HUMIDITY_QF_FULL; } } // wind quality factor if (rte_wx_wind_qf == AN_WIND_QF_UNKNOWN) { ; } else { switch (rte_wx_wind_qf) { case AN_WIND_QF_FULL: wind_qf = WIND_QF_FULL; break; case AN_WIND_QF_DEGRADED_DEBOUNCE: case AN_WIND_QF_DEGRADED_SLEW: case AN_WIND_QF_DEGRADED: wind_qf = WIND_QF_DEGRADATED; break; case AN_WIND_QF_NOT_AVALIABLE: case AN_WIND_QF_UNKNOWN: wind_qf = WIND_QF_NOT_AVALIABLE; break; } rte_wx_wind_qf = AN_WIND_QF_UNKNOWN; } if (config_mode->victron == 1) { telemetry_send_values_pv(rx10m, digi10m, rte_pv_battery_current, rte_pv_battery_voltage, rte_pv_cell_voltage, dallas_qf, pressure_qf, humidity_qf, wind_qf); } else { #ifdef PARAMETEO // if _DALLAS_AS_TELEM will be enabled the fifth channel will be set to temperature measured by DS12B20 //telemetry_send_values(rx10m, tx10m, digi10m, kiss10m, rte_wx_temperature_dallas_valid, dallas_qf, rte_wx_ms5611_qf, rte_wx_dht_valid.qf, rte_wx_umb_qf); if (config_mode->wx == 1) { // if _METEO will be enabled, but without _DALLAS_AS_TELEM the fifth channel will be used to transmit temperature from MS5611 // which may be treated then as 'rack/cabinet internal temperature'. Dallas DS12B10 will be used for ragular WX frames telemetry_send_values(rx10m, tx10m, digi10m, rte_main_average_battery_voltage, digidrop10m, rte_wx_temperature_internal_valid, dallas_qf, pressure_qf, humidity_qf, wind_qf, pwr_save_currently_cutoff, config_mode); } else { // if user will disable both _METEO and _DALLAS_AS_TELEM value will be zeroed internally anyway telemetry_send_values(rx10m, tx10m, digi10m, rte_main_average_battery_voltage, digidrop10m, 0.0f, dallas_qf, pressure_qf, humidity_qf, wind_qf, pwr_save_currently_cutoff, config_mode); } #else // if _DALLAS_AS_TELEM will be enabled the fifth channel will be set to temperature measured by DS12B20 //telemetry_send_values(rx10m, tx10m, digi10m, kiss10m, rte_wx_temperature_dallas_valid, dallas_qf, rte_wx_ms5611_qf, rte_wx_dht_valid.qf, rte_wx_umb_qf); if (config_mode->wx == 1) { // if _METEO will be enabled, but without _DALLAS_AS_TELEM the fifth channel will be used to transmit temperature from MS5611 // which may be treated then as 'rack/cabinet internal temperature'. Dallas DS12B10 will be used for ragular WX frames telemetry_send_values(rx10m, tx10m, digi10m, kiss10m, digidrop10m, rte_wx_temperature_internal_valid, dallas_qf, pressure_qf, humidity_qf, wind_qf, config_mode); } else { // if user will disable both _METEO and _DALLAS_AS_TELEM value will be zeroed internally anyway telemetry_send_values(rx10m, tx10m, digi10m, kiss10m, digidrop10m, 0.0f, dallas_qf, pressure_qf, humidity_qf, wind_qf, config_mode); } #endif } packet_tx_telemetry_counter = 0; // service external watchdog while sending telemetry io_ext_watchdog_service(); #ifdef PARAMETEO aprsis_prepare_telemetry( telemetry_get_counter(), rx10m, tx10m, digi10m, telemetry_scaled_vbatt_voltage, digidrop10m, telemetry_scaled_temperature, telemetry_qf, telemetry_degr, telemetry_nav, telemetry_pressure_qf_navaliable, telemetry_humidity_qf_navaliable, telemetry_anemometer_degradated, telemetry_anemometer_navble, telemetry_vbatt_low, config_mode); #endif rx10m = 0, tx10m = 0, digi10m = 0, kiss10m = 0, digidrop10m = 0; } if (packet_tx_telemetry_descr_counter >= packet_tx_telemetry_descr_interval) { #ifdef PARAMETEO rte_main_trigger_gsm_telemetry_descriptions = 1; status_send_powersave_registers(); #endif packet_tx_multi_per_call_handler(); if (config_mode->victron == 1) { telemetry_send_chns_description_pv(config_basic); if (rte_pv_battery_voltage == 0) { rte_main_reboot_req = 1; } } else { telemetry_send_chns_description(config_basic, config_mode); packet_tx_multi_per_call_handler(); } status_send(); if (config_mode->wx_umb == 1) { umb_clear_error_history(&rte_wx_umb_context); } #ifdef STM32L471xx if (main_config_data_gsm->aprsis_enable == 0 && main_config_data_gsm->api_enable == 1) { // and trigger API wx packet transmission packet_tx_trigger_tcp |= API_TRIGGER_STATUS; } else { packet_tx_trigger_tcp = 0; } #endif packet_tx_telemetry_descr_counter = 0; } // store counters in backup registers backup_reg_set_packet_counters(packet_tx_beacon_counter, packet_tx_meteo_counter, packet_tx_meteo_gsm_counter); #ifdef STM32L471xx // if powersave mode allow to sent extensive status messages if (rte_main_curret_powersave_mode != PWSAVE_AGGRESV) { // if gsm modem is enabled if (main_config_data_mode->gsm != 0) { // if gprs is connected if (gsm_sim800_gprs_ready == 1) { // if no gsm status packet has been sent so far if (packet_tx_gsm_status_sent == 0) { // send a status status_send_gsm(); // network parameters are not queries while APRS-IS connection is pending // so no sense to send status more than once after the initialization packet_tx_gsm_status_sent = 1; } } else { packet_tx_gsm_status_sent = 0; } } } #endif } void packet_tx_get_current_counters(packet_tx_counter_values_t * out) { if (out != 0x00) { out->beacon_counter = packet_tx_beacon_counter; out->wx_counter = packet_tx_meteo_counter; out->gsm_wx_counter = packet_tx_meteo_gsm_counter; out->telemetry_counter = packet_tx_telemetry_counter; out->telemetry_desc_counter = packet_tx_telemetry_descr_counter; out->kiss_counter = packet_tx_meteo_kiss_counter; } } void packet_tx_set_current_counters(packet_tx_counter_values_t * in) { if (in != 0x00) { if (in->beacon_counter != 0) packet_tx_beacon_counter = in->beacon_counter; if (in->wx_counter != 0) packet_tx_meteo_counter = in->wx_counter; if (in->telemetry_counter != 0) packet_tx_telemetry_counter = in->telemetry_counter; if (in->telemetry_desc_counter != 0) packet_tx_telemetry_descr_counter = in->telemetry_desc_counter; if (in->kiss_counter != 0) packet_tx_meteo_kiss_counter = in->kiss_counter; if (in->gsm_wx_counter != 0) packet_tx_meteo_gsm_counter = in->gsm_wx_counter; } else { packet_tx_beacon_counter = 0; packet_tx_meteo_counter = 2; packet_tx_meteo_gsm_counter = 0; packet_tx_telemetry_counter = 0; packet_tx_telemetry_descr_counter = 10; packet_tx_meteo_kiss_counter = 0; } } /** * Returns how many minutes is left to next weather packet (on radio network!!). * Used in power saving state machine and to periodically reset VHF radio * if this feature is enabled in configuration * @return */ int16_t packet_tx_get_minutes_to_next_wx(void) { if (packet_tx_meteo_interval != 0) { return packet_tx_meteo_interval - packet_tx_meteo_counter; } else { return -1; } } /** * This function checks if at the moment any weather packet is scheduled to be sent * to APRS-IS server, or this packet has been just sent and GPRS module probably * communicate with GSM network now. The result of this check is used to * inhibit power saving state machine temporary, not to turn off or disable * GSM modem while it is talking with GSM radio network. * @return */ uint8_t packet_tx_is_gsm_meteo_pending(void) { uint8_t out = 0; #ifdef STM32L471xx if (gsm_sim800_gprs_ready == 1 && (packet_tx_trigger_tcp & APRSIS_TRIGGER_METEO) != 0) { out = 1; } if (packet_tx_meteo_gsm_has_been_sent != 0) { out = 1; } #endif return out; } void packet_tx_force_gsm_status(void) { #ifdef STM32L471xx packet_tx_gsm_status_sent = 0; #endif }