-
-
Notifications
You must be signed in to change notification settings - Fork 625
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added weather station app with 18 protocol parsers (#1607)
* Added weather station app with 18 protocol parsers * Fix button and formatting * Set BW to 1.75m, changed to us in dsp part
- Loading branch information
Showing
32 changed files
with
3,428 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
/* | ||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. | ||
* Copyright (C) 2017 Furrtek | ||
* | ||
* This file is part of PortaPack. | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2, or (at your option) | ||
* any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program; see the file COPYING. If not, write to | ||
* the Free Software Foundation, Inc., 51 Franklin Street, | ||
* Boston, MA 02110-1301, USA. | ||
*/ | ||
|
||
#include "ui_weatherstation.hpp" | ||
#include "modems.hpp" | ||
#include "audio.hpp" | ||
#include "baseband_api.hpp" | ||
#include "string_format.hpp" | ||
#include "portapack_persistent_memory.hpp" | ||
|
||
using namespace portapack; | ||
using namespace ui; | ||
|
||
namespace ui { | ||
|
||
void WeatherRecentEntryDetailView::update_data() { | ||
// set text elements | ||
text_type.set(WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry_.sensorType)); | ||
text_id.set("0x" + to_string_hex(entry_.id)); | ||
text_temp.set(to_string_decimal(entry_.temp, 2)); | ||
text_hum.set(to_string_dec_uint(entry_.humidity) + "%"); | ||
text_ch.set(to_string_dec_uint(entry_.channel)); | ||
text_batt.set(to_string_dec_uint(entry_.battery_low) + " " + ((entry_.battery_low == 0) ? "OK" : "LOW")); | ||
} | ||
|
||
void WeatherRecentEntryDetailView::set_entry(const WeatherRecentEntry& entry) { | ||
entry_ = entry; | ||
update_data(); | ||
set_dirty(); | ||
} | ||
|
||
WeatherRecentEntryDetailView::WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry) | ||
: nav_{nav}, | ||
entry_{entry} { | ||
add_children({&button_done, | ||
&text_type, | ||
&text_id, | ||
&text_temp, | ||
&text_hum, | ||
&text_ch, | ||
&text_batt, | ||
&labels}); | ||
|
||
button_done.on_select = [&nav](const ui::Button&) { | ||
nav.pop(); | ||
}; | ||
update_data(); | ||
} | ||
|
||
void WeatherRecentEntryDetailView::focus() { | ||
button_done.focus(); | ||
} | ||
|
||
void WeatherView::focus() { | ||
field_frequency.focus(); | ||
} | ||
|
||
WeatherView::WeatherView(NavigationView& nav) | ||
: nav_{nav} { | ||
add_children({&rssi, | ||
&field_rf_amp, | ||
&field_lna, | ||
&field_vga, | ||
&field_frequency, | ||
&button_clear_list, | ||
&recent_entries_view}); | ||
|
||
baseband::run_image(portapack::spi_flash::image_tag_weather); | ||
|
||
button_clear_list.on_select = [this](Button&) { | ||
recent.clear(); | ||
recent_entries_view.set_dirty(); | ||
}; | ||
field_frequency.set_value(433920000); | ||
field_frequency.set_step(1000); | ||
const Rect content_rect{0, header_height, screen_width, screen_height - header_height}; | ||
recent_entries_view.set_parent_rect(content_rect); | ||
recent_entries_view.on_select = [this](const WeatherRecentEntry& entry) { | ||
nav_.push<WeatherRecentEntryDetailView>(entry); | ||
}; | ||
receiver_model.set_target_frequency(433'920'000); | ||
receiver_model.set_sampling_rate(2'000'000); | ||
receiver_model.set_baseband_bandwidth(1'750'000); | ||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); | ||
baseband::set_weather(); | ||
receiver_model.enable(); | ||
} | ||
|
||
void WeatherView::on_data(const WeatherDataMessage* data) { | ||
WeatherRecentEntry key{data->sensorType, data->id, data->temp, data->humidity, data->channel, data->battery_low}; | ||
auto matching_recent = find(recent, key.key()); | ||
if (matching_recent != std::end(recent)) { | ||
// Found within. Move to front of list, increment counter. | ||
recent.push_front(*matching_recent); | ||
recent.erase(matching_recent); | ||
} else { | ||
recent.emplace_front(key); | ||
truncate_entries(recent, 64); | ||
} | ||
recent_entries_view.set_dirty(); | ||
} | ||
|
||
WeatherView::~WeatherView() { | ||
receiver_model.disable(); | ||
baseband::shutdown(); | ||
} | ||
|
||
const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) { | ||
switch (type) { | ||
case FPW_NexusTH: | ||
return "NexusTH"; | ||
case FPW_Acurite592TXR: | ||
return "Acurite592TXR"; | ||
case FPW_Acurite606TX: | ||
return "Acurite606TX"; | ||
case FPW_Acurite609TX: | ||
return "Acurite609TX"; | ||
case FPW_Ambient: | ||
return "Ambient"; | ||
case FPW_AuriolAhfl: | ||
return "AuriolAhfl"; | ||
case FPW_AuriolTH: | ||
return "AuriolTH"; | ||
case FPW_GTWT02: | ||
return "GT-WT02"; | ||
case FPW_GTWT03: | ||
return "GT-WT03"; | ||
case FPW_INFACTORY: | ||
return "InFactory"; | ||
case FPW_LACROSSETX: | ||
return "LaCrosse TX"; | ||
case FPW_LACROSSETX141thbv2: | ||
return "LaCrosse TX141THBv2"; | ||
case FPW_OREGON2: | ||
return "Oregon2"; | ||
case FPW_OREGON3: | ||
return "Oregon3"; | ||
case FPW_OREGONv1: | ||
return "OregonV1"; | ||
case FPW_THERMOPROTX4: | ||
return "ThermoPro TX4"; | ||
case FPW_TX_8300: | ||
return "TX 8300"; | ||
case FPW_WENDOX_W6726: | ||
return "Wendox W6726"; | ||
|
||
case FPW_Invalid: | ||
default: | ||
return "Unknown"; | ||
} | ||
} | ||
|
||
std::string WeatherView::pad_string_with_spaces(int snakes) { | ||
std::string paddedStr(snakes, ' '); | ||
return paddedStr; | ||
} | ||
|
||
template <> | ||
void RecentEntriesTable<ui::WeatherRecentEntries>::draw( | ||
const Entry& entry, | ||
const Rect& target_rect, | ||
Painter& painter, | ||
const Style& style) { | ||
std::string line{}; | ||
line.reserve(30); | ||
|
||
line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType); | ||
if (line.length() < 13) { | ||
line += WeatherView::pad_string_with_spaces(13 - line.length()); | ||
} else { | ||
line = truncate(line, 13); | ||
} | ||
|
||
std::string temp = to_string_decimal(entry.temp, 2); | ||
std::string humStr = to_string_dec_uint(entry.humidity) + "%"; | ||
std::string chStr = to_string_dec_uint(entry.channel); | ||
|
||
line += WeatherView::pad_string_with_spaces(7 - temp.length()) + temp; | ||
line += WeatherView::pad_string_with_spaces(5 - humStr.length()) + humStr; | ||
line += WeatherView::pad_string_with_spaces(4 - chStr.length()) + chStr; | ||
|
||
line.resize(target_rect.width() / 8, ' '); | ||
painter.draw_string(target_rect.location(), style, line); | ||
} | ||
|
||
} // namespace ui |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/* | ||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. | ||
* Copyright (C) 2017 Furrtek | ||
* | ||
* This file is part of PortaPack. | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2, or (at your option) | ||
* any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program; see the file COPYING. If not, write to | ||
* the Free Software Foundation, Inc., 51 Franklin Street, | ||
* Boston, MA 02110-1301, USA. | ||
*/ | ||
|
||
#ifndef __UI_WEATHER_H__ | ||
#define __UI_WEATHER_H__ | ||
|
||
#include "ui.hpp" | ||
#include "ui_navigation.hpp" | ||
#include "ui_receiver.hpp" | ||
#include "ui_freq_field.hpp" | ||
#include "app_settings.hpp" | ||
#include "radio_state.hpp" | ||
#include "utility.hpp" | ||
#include "recent_entries.hpp" | ||
|
||
#include "../baseband/fprotos/weathertypes.hpp" | ||
using namespace ui; | ||
|
||
namespace ui { | ||
|
||
struct WeatherRecentEntry { | ||
using Key = uint64_t; | ||
static constexpr Key invalid_key = 0x0fffffff; // todo calc the invalid all | ||
uint8_t sensorType = FPW_Invalid; | ||
uint32_t id = 0xFFFFFFFF; | ||
float temp = -273.0f; | ||
uint8_t humidity = 0xFF; | ||
uint8_t battery_low = 0xFF; | ||
uint8_t channel = 0xFF; | ||
|
||
WeatherRecentEntry() {} | ||
WeatherRecentEntry( | ||
uint8_t sensorType, | ||
uint32_t id, | ||
float temp, | ||
uint8_t humidity, | ||
uint8_t channel, | ||
uint8_t battery_low = 0xff) | ||
: sensorType{sensorType}, | ||
id{id}, | ||
temp{temp}, | ||
humidity{humidity}, | ||
battery_low{battery_low}, | ||
channel{channel} { | ||
} | ||
Key key() const { | ||
return (((static_cast<uint64_t>(temp * 10) & 0xFFFF) << 48) ^ static_cast<uint64_t>(id) << 24) | | ||
(static_cast<uint64_t>(sensorType) & 0xFF) << 16 | | ||
(static_cast<uint64_t>(humidity) & 0xFF) << 8 | | ||
(static_cast<uint64_t>(battery_low) & 0xF) << 4 | | ||
(static_cast<uint64_t>(channel) & 0xF); | ||
} | ||
}; | ||
using WeatherRecentEntries = RecentEntries<WeatherRecentEntry>; | ||
using WeatherRecentEntriesView = RecentEntriesView<WeatherRecentEntries>; | ||
|
||
class WeatherView : public View { | ||
public: | ||
WeatherView(NavigationView& nav); | ||
~WeatherView(); | ||
|
||
void focus() override; | ||
|
||
std::string title() const override { return "Weather"; }; | ||
static const char* getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type); | ||
static std::string pad_string_with_spaces(int snakes); | ||
|
||
private: | ||
void on_data(const WeatherDataMessage* data); | ||
|
||
NavigationView& nav_; | ||
RxRadioState radio_state_{ | ||
433'920'000 /* frequency */, | ||
1'750'000 /* bandwidth */, | ||
2'000'000 /* sampling rate */, | ||
ReceiverModel::Mode::SpectrumAnalysis}; | ||
app_settings::SettingsManager settings_{ | ||
"rx_weather", app_settings::Mode::RX}; | ||
|
||
WeatherRecentEntries recent{}; | ||
|
||
RFAmpField field_rf_amp{ | ||
{13 * 8, 0 * 16}}; | ||
LNAGainField field_lna{ | ||
{15 * 8, 0 * 16}}; | ||
VGAGainField field_vga{ | ||
{18 * 8, 0 * 16}}; | ||
RSSI rssi{ | ||
{21 * 8, 0, 6 * 8, 4}}; | ||
RxFrequencyField field_frequency{ | ||
{0 * 8, 0 * 16}, | ||
nav_}; | ||
|
||
Button button_clear_list{ | ||
{0, 16, 7 * 8, 32}, | ||
"Clear"}; | ||
|
||
static constexpr auto header_height = 3 * 16; | ||
|
||
const RecentEntriesColumns columns{{ | ||
{"Type", 13}, | ||
{"Temp", 6}, | ||
{"Hum", 4}, | ||
{"Ch", 3}, | ||
|
||
}}; | ||
WeatherRecentEntriesView recent_entries_view{columns, recent}; | ||
|
||
MessageHandlerRegistration message_handler_packet{ | ||
Message::ID::WeatherData, | ||
[this](Message* const p) { | ||
const auto message = static_cast<const WeatherDataMessage*>(p); | ||
this->on_data(message); | ||
}}; | ||
}; | ||
|
||
class WeatherRecentEntryDetailView : public View { | ||
public: | ||
WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry); | ||
|
||
void set_entry(const WeatherRecentEntry& new_entry); | ||
const WeatherRecentEntry& entry() const { return entry_; }; | ||
|
||
void update_data(); | ||
void focus() override; | ||
|
||
private: | ||
NavigationView& nav_; | ||
WeatherRecentEntry entry_{}; | ||
Text text_type{{0 * 8, 1 * 16, 15 * 8, 16}, "?"}; | ||
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"}; | ||
Text text_temp{{6 * 8, 3 * 16, 8 * 8, 16}, "?"}; | ||
Text text_hum{{11 * 8, 4 * 16, 6 * 8, 16}, "?"}; | ||
Text text_ch{{11 * 8, 5 * 16, 6 * 8, 16}, "?"}; | ||
Text text_batt{{11 * 8, 6 * 16, 6 * 8, 16}, "?"}; | ||
|
||
Labels labels{ | ||
{{0 * 8, 0 * 16}, "Weather station type:", Color::light_grey()}, | ||
{{0 * 8, 2 * 16}, "Id: ", Color::light_grey()}, | ||
{{0 * 8, 3 * 16}, "Temp:", Color::light_grey()}, | ||
{{0 * 8, 4 * 16}, "Humidity:", Color::light_grey()}, | ||
{{0 * 8, 5 * 16}, "Channel:", Color::light_grey()}, | ||
{{0 * 8, 6 * 16}, "Battery:", Color::light_grey()}, | ||
}; | ||
|
||
Button button_done{ | ||
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32}, | ||
"Done"}; | ||
}; | ||
|
||
} // namespace ui | ||
|
||
#endif /*__UI_WEATHER_H__*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.