Skip to content

Commit

Permalink
Added weather station app with 18 protocol parsers (#1607)
Browse files Browse the repository at this point in the history
* 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
htotoo authored Nov 28, 2023
1 parent c486572 commit 02251ee
Show file tree
Hide file tree
Showing 32 changed files with 3,428 additions and 0 deletions.
1 change: 1 addition & 0 deletions firmware/application/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ set(CPPSRC
apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
apps/ui_weatherstation.cpp
apps/ui_whipcalc.cpp
protocols/aprs.cpp
protocols/ax25.cpp
Expand Down
205 changes: 205 additions & 0 deletions firmware/application/apps/ui_weatherstation.cpp
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
172 changes: 172 additions & 0 deletions firmware/application/apps/ui_weatherstation.hpp
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__*/
5 changes: 5 additions & 0 deletions firmware/application/baseband_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,11 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
send_message(&message);
}

void set_weather() {
const WeatherRxConfigureMessage message{};
send_message(&message);
}

static bool baseband_image_running = false;

void run_image(const spi_flash::image_tag_t image_tag) {
Expand Down
Loading

0 comments on commit 02251ee

Please sign in to comment.