Compare commits

...

10 Commits

Author SHA1 Message Date
Tyler Perkins
998f355460 Change type MBps to Mbps 2022-01-28 17:42:46 -05:00
Tyler Perkins
cbcba7d143 Add homeassistant network stats to wifi panel 2022-01-28 17:34:14 -05:00
Tyler Perkins
4e513a86d8 Fix trailing spaces 2022-01-28 17:33:24 -05:00
Tyler Perkins
fed9d23ed8 Add home assistant test script 2022-01-28 17:31:26 -05:00
Tyler Perkins
d857ae85cf Update README with goals 2022-01-28 16:42:52 -05:00
Tyler Perkins
1d8fa2600c Update internal tautulli api key 2022-01-18 13:51:43 -05:00
Tyler Perkins
9a2cf809ed Fix time in homeassistant.cpp to account for timezone 2022-01-17 15:13:05 -05:00
Tyler Perkins
9018d83868 Add home assistant bare bones panel 2022-01-17 15:03:34 -05:00
Tyler Perkins
05ae49976b Tweak plex panel code formatting 2022-01-17 15:03:13 -05:00
Tyler Perkins
5f06845b1c Add FAQ to DEVELOPMENT 2022-01-17 15:02:20 -05:00
17 changed files with 561 additions and 66 deletions

3
.gitignore vendored
View File

@ -13,3 +13,6 @@ src/config.hpp
#ignore wifi image #ignore wifi image
img/wifi.png img/wifi.png
#ignore json in tests
tests/*.json

View File

@ -66,3 +66,10 @@ Things you need to implement as a panel creator
- `const_resources` - `const_resources`
And be sure to inheret from dashboard::panel ! And be sure to inheret from dashboard::panel !
FAQ
===
- I wrote my pannel, yet i keep seeing that SCREEN_WIDTH, SCREEN_HEIGHT,
board::*, etc are not defined
- Be sure to add `#include "../board.hpp"` at the end of your mypanel.hpp

View File

@ -2,20 +2,21 @@ dashboard
========= =========
My dashboard, for my house. Display RSS feeds and other custom bits of info via My dashboard, for my house. Display RSS feeds and other custom bits of info via
direct SDL2 calls. direct SDL2 calls.
Features/TODO Features/TODO
============= =============
- (DONE) Write straight to framebuffer (sdl2) - (DONE) Write straight to framebuffer (sdl2)
- (DONE) Display Weather ~~rss~~ json feed - (DONE) Display Weather ~~rss~~ json feed
- Get UV Data from openUV - Get UV Data from openUV
- (DONE) Display Wifi qrcode - (DONE) Display Wifi qrcode
- Get wifispeed and number of clients from home assistant - Get wifispeed and number of clients from home assistant
- (DONE) Display plex currently playing - (DONE) Display plex currently playing
- Display camera feed - Display camera feed
- Integrate with Home assistant data (New panel) - Integrate with Home assistant data (New panel)
- https://developers.home-assistant.io/docs/api/rest/ - https://developers.home-assistant.io/docs/api/rest/
- (DONE) Presence
- Integrate with octoprint - Integrate with octoprint
Depends on Depends on

View File

@ -10,10 +10,12 @@
// A list of all panels to be displayed, in order // A list of all panels to be displayed, in order
dashboard::panel::panel* PANELS[] = { dashboard::panel::panel* PANELS[] = {
//new dashboard::panel::sample_panel(), //new dashboard::panel::sample_panel(),
new dashboard::panel::homeassistant(),
new dashboard::panel::plex(), new dashboard::panel::plex(),
new dashboard::panel::weather(), new dashboard::panel::weather(),
new dashboard::panel::wifi(), new dashboard::panel::wifi(),
}; };
size_t PANELS_LENGTH = sizeof(PANELS)/sizeof(PANELS[0]); size_t PANELS_LENGTH = sizeof(PANELS)/sizeof(PANELS[0]);
// OVERLAY // OVERLAY
@ -49,7 +51,8 @@ const char* IMAGE_LOCATIONS[] = {
//plex //plex
"plex_background.jpg", "plex_background.jpg",
//homeassistant
}; };
size_t IMAGE_LOCATIONS_LENGTH = sizeof(IMAGE_LOCATIONS)/sizeof(IMAGE_LOCATIONS[0]); size_t IMAGE_LOCATIONS_LENGTH = sizeof(IMAGE_LOCATIONS)/sizeof(IMAGE_LOCATIONS[0]);
@ -76,6 +79,7 @@ const FONT_SIZE_STRING CONST_STRINGS[] = {
{ "Weather", { "Roboto_Mono/RobotoMono-Medium.ttf", 50 } }, { "Weather", { "Roboto_Mono/RobotoMono-Medium.ttf", 50 } },
{ "Wireless", { "Roboto_Mono/RobotoMono-Medium.ttf", 50 } }, { "Wireless", { "Roboto_Mono/RobotoMono-Medium.ttf", 50 } },
{ "Plex", { "Roboto_Mono/RobotoMono-Medium.ttf", 50} }, { "Plex", { "Roboto_Mono/RobotoMono-Medium.ttf", 50} },
{ "Homeassistant", { "Roboto_Mono/RobotoMono-Medium.ttf", 50} },
//sample panel //sample panel
//{ "Sample Panel", { "Roboto_Mono/RobotoMono-Medium.ttf", 50} }, //{ "Sample Panel", { "Roboto_Mono/RobotoMono-Medium.ttf", 50} },
@ -101,5 +105,7 @@ const FONT_SIZE_STRING CONST_STRINGS[] = {
{ "Playing", {"Roboto_Mono/RobotoMono-Medium.ttf", 28} }, { "Playing", {"Roboto_Mono/RobotoMono-Medium.ttf", 28} },
{ "Top Users", {"Roboto_Mono/RobotoMono-Medium.ttf", 50} }, { "Top Users", {"Roboto_Mono/RobotoMono-Medium.ttf", 50} },
//Home assistant
{ "Home", {"Roboto_Mono/RobotoMono-Medium.ttf", 50} },
}; };
size_t CONST_STRINGS_LENGTH = sizeof(CONST_STRINGS)/sizeof(CONST_STRINGS[0]); size_t CONST_STRINGS_LENGTH = sizeof(CONST_STRINGS)/sizeof(CONST_STRINGS[0]);

View File

@ -79,6 +79,7 @@ constexpr int IMG_FLAGS = 0
#include "panel/def_overlay.hpp" #include "panel/def_overlay.hpp"
#include "panel/wifi.hpp" #include "panel/wifi.hpp"
#include "panel/plex.hpp" #include "panel/plex.hpp"
#include "panel/homeassistant.hpp"
//uncomment this to use the sample panel //uncomment this to use the sample panel
//#include "panel/sample_panel.hpp" //#include "panel/sample_panel.hpp"
extern dashboard::panel::panel* PANELS[]; extern dashboard::panel::panel* PANELS[];

View File

@ -16,7 +16,7 @@ int main(int argc, char** argv){
dashboard::board _board(false); dashboard::board _board(false);
if(_board.init() != 0){ if(_board.init() != 0){
std::cerr << "Due to errors, " << argv[0] std::cerr << "Due to errors, " << argv[0]
<< " was unable to start, quitting!" << std::endl; << " was unable to start, quitting!" << std::endl;
return -1; return -1;
} }

223
src/panel/homeassistant.cpp Normal file
View File

@ -0,0 +1,223 @@
///////////////////////////////////////////////////////////////////////////////
// Tyler Perkins
// 14-1-22
// homeassistant panel
//
#include "homeassistant.hpp"
#include "homeassistant_config.hpp"
using namespace dashboard::panel;
///////////////////////////////////////////////////////////////////////////////
// Constructors ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
homeassistant::homeassistant(){
std::cerr << "HOMEASSISTANT CONSTRUCTOR\n";
_time_on_screen = HOMEASSISTANT_DEFAULT_ON_SCREEN_TIME;
_update_interval = std::chrono::milliseconds{HOMEASSISTANT_UPDATE_INTERVAL};
_texture = nullptr;
_title = HOMEASSISTANT_TITLE;
std::string api_string = "Authorization: Bearer ";
api_string += HOMEASSISTANT_APIKEY;
headers = NULL;
headers = curl_slist_append(headers, api_string.c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
api_string.clear();
api_curl = curl_easy_init();
curl_easy_setopt(api_curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(api_curl, CURLOPT_URL, HOMEASSISTANT_URL);
curl_easy_setopt(api_curl, CURLOPT_WRITEFUNCTION,
dashboard::panel::homeassistant::curl_callback);
curl_easy_setopt(api_curl, CURLOPT_WRITEDATA, &json_string);
//Write request string in update function
}
homeassistant::~homeassistant(){
std::cerr << "HOMEASSISTANT DECONSTRUCTOR\n";
if(_texture != nullptr){
SDL_DestroyTexture(_texture);
}
if(api_curl != nullptr){
curl_easy_cleanup(api_curl);
}
}
///////////////////////////////////////////////////////////////////////////////
// Draw function //////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void homeassistant::draw(){
//create the texture if this is the first time running draw
if(_texture == nullptr){
initTexture();
update();
update_texture();
}
//check if its time to update
if((std::chrono::high_resolution_clock::now() - _last_update)
> _update_interval){
update();
update_texture();
}
SDL_RenderCopy(board::getRenderer(), _texture, NULL, NULL);
}
///////////////////////////////////////////////////////////////////////////////
// Helper functions ///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////
// Update the information of the obj
// This DOES NOT update the display
void homeassistant::update(){
std::cerr << "HOMEASSISTANT::UPDATE\n";
_last_update = std::chrono::high_resolution_clock::now();
//perform request
curl_easy_perform(api_curl);
//parse the result
json_doc.Parse(json_string.c_str());
//update internal state
for(rapidjson::SizeType i = 0; i < json_doc.Size(); i++){
//if is a person entry
if(strstr(json_doc[i]["entity_id"].GetString(), "person.") != NULL){
//get the last changed date and convert to a tm
std::string format_str = json_doc[i]["last_changed"].GetString();
std::time_t t = datestringToTm(format_str);
std::stringstream ss;
ss << std::put_time(std::localtime(&t), "%b %e, %I:%M %p");
struct home_entry entry = {
json_doc[i]["attributes"]["friendly_name"].GetString(),
strcmp(json_doc[i]["state"].GetString(), "home") == 0 ? true : false,
ss.str(),
};
home_entries.push_back(entry);
}
//TODO other entries from general home assistant?
}
json_string.clear();
}
///////////////////////////////////////
// Update the texture that is being
// displayed, based on data grabed from json
void homeassistant::update_texture(){
std::cerr << "HOMEASSISTANT::UPDATE_TEXTURE\n";
SDL_Rect tgt;
SDL_SetRenderTarget(board::getRenderer(), _texture);
SDL_RenderClear(board::getRenderer());
constexpr int GAP_SIZE = 10;
//draw each name and time last seen
{
for(size_t i = 0; i < home_entries.size(); ++i){
tgt.x = (SCREEN_WIDTH / (2 * home_entries.size()))
+ i * (SCREEN_WIDTH / home_entries.size());
tgt.y = DEF_OVERLAY_BAR_HEIGHT + GAP_SIZE;
TTF_SizeText(board::getFont( {"Roboto_Mono/RobotoMono-Medium.ttf", 50} ),
home_entries[i].name.c_str(),
&tgt.w, &tgt.h);
tgt.x -= tgt.w / 2;
SDL_RenderCopy(board::getRenderer(),
board::getString(home_entries[i].name,
{ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }),
NULL, &tgt);
//down a new line
tgt.y += tgt.h + GAP_SIZE;
tgt.x += tgt.w / 2;
std::string home = "";
//if is home
if(home_entries[i].home){
home = "Home";
} else {
home = home_entries[i].time;
}
TTF_SizeText(board::getFont( {"Roboto_Mono/RobotoMono-Medium.ttf", 50} ),
home.c_str(),
&tgt.w, &tgt.h);
tgt.x -= tgt.w / 2;
SDL_RenderCopy(board::getRenderer(),
board::getString(home,
{ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }),
NULL, &tgt);
}
}
home_entries.clear();
SDL_SetRenderTarget(board::getRenderer(), NULL);
}
///////////////////////////////////////
// Lazy load the texture object
// This is to make sure that all of SDL
// is init before we attempt to draw anything
void homeassistant::initTexture(){
if(_texture == nullptr){
_texture = SDL_CreateTexture(board::getRenderer(),
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET,
SCREEN_WIDTH, SCREEN_HEIGHT);
SDL_SetTextureBlendMode(_texture, SDL_BLENDMODE_BLEND);
}
}
///////////////////////////////////////
// Convert the homeassistant string
// format to a tm object
std::time_t homeassistant::datestringToTm(const std::string& date_string){
int year = 0, month = 0, day = 0, hour = 0, minuite = 0, second = 0;
std::tm tm_tmp{};
if(sscanf(date_string.c_str(), "%4d-%2d-%2dT%2d:%2d:%2d",
&year, &month, &day, &hour, &minuite, &second) == 6){
tm_tmp.tm_year = year - 1900;
tm_tmp.tm_mon = month - 1;
tm_tmp.tm_mday = day;
tm_tmp.tm_hour = hour;
tm_tmp.tm_min = minuite;
tm_tmp.tm_sec = second;
}
return std::mktime(&tm_tmp) - timezone;
}
///////////////////////////////////////
// Curl callback function
size_t dashboard::panel::homeassistant::curl_callback(void* contents, size_t size,
size_t nmemb, void* userp){
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}

View File

@ -0,0 +1,59 @@
///////////////////////////////////////////////////////////////////////////////
// Tyler Perkins
// 14-1-22
// Home assistant panel
//
#pragma once
#include "panel.hpp"
#include <SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>
#include <curl/curl.h>
#include "../util/rapidjson/document.h"
#include <iostream>
#include <string>
#include <string.h>
#include <chrono>
#include <vector>
#include <iomanip>
namespace dashboard::panel {
class homeassistant : public panel {
public:
homeassistant();
~homeassistant();
static size_t curl_callback(void*, size_t, size_t, void*);
void draw();
private:
void update();
void update_texture();
void initTexture();
std::time_t datestringToTm(const std::string&);
struct home_entry {
std::string name;
bool home;
std::string time;
};
std::vector<home_entry> home_entries;
CURL* api_curl;
struct curl_slist *headers;
std::string json_string;
rapidjson::Document json_doc;
std::chrono::time_point<std::chrono::high_resolution_clock> _last_update;
std::chrono::milliseconds _update_interval;
};
}
#include "../board.hpp"

View File

@ -0,0 +1,31 @@
///////////////////////////////////////////////////////////////////////////////
// Tyler Perkins
// 14-1-22
// homeassistant configuration
//
#pragma once
#include "def_overlay_config.hpp"
namespace dashboard::panel {
//This will be displayed at the top left on the status bar. Set to a blank
//string to not show anything
constexpr char HOMEASSISTANT_TITLE[] = "Home assistant";
//API key, you can get this in homeassistant under your profile
constexpr char HOMEASSISTANT_APIKEY[] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxYTNhNDhhYTBlZDM0MjkyOTk5OWZhNGVjZGRjMGUwMCIsImlhdCI6MTY0MjE4NDU0MCwiZXhwIjoxOTU3NTQ0NTQwfQ.Zdy7-ZwX0HuwhmUGeLhF5XCluotsKmpDqmi5o-hQZCc";
//This API endpoint. Sub the IP with the IP of the homeassistant server on
//your network. Be sure to enable the RESTful API in homeassistant's
//configuration.yaml
constexpr char HOMEASSISTANT_URL[] = "http://192.168.1.104:8123/api/states";
//Default time the slide is shown on screen, in ms
//Default 15s
constexpr size_t HOMEASSISTANT_DEFAULT_ON_SCREEN_TIME = 15000;
//How long should we wait between updates? in ms
//Default 15
constexpr size_t HOMEASSISTANT_UPDATE_INTERVAL = 15000;
}

View File

@ -50,7 +50,7 @@ void plex::draw(){
} }
//check if its time to update //check if its time to update
if((std::chrono::high_resolution_clock::now() - _last_update) if((std::chrono::high_resolution_clock::now() - _last_update)
> _update_interval){ > _update_interval){
update(); update();
@ -84,13 +84,13 @@ void plex::update(){
for(short i = 0; i < 4; ++i){ for(short i = 0; i < 4; ++i){
entries.at(i) = { entries.at(i) = {
truncate(curr_entry[i]["friendly_name"].GetString(), truncate(curr_entry[i]["friendly_name"].GetString(),
PLEX_MAX_STRING_LENGTH), PLEX_MAX_STRING_LENGTH),
truncate(curr_entry[i]["ip_address"].GetString(), truncate(curr_entry[i]["ip_address"].GetString(),
PLEX_MAX_STRING_LENGTH), PLEX_MAX_STRING_LENGTH),
truncate(curr_entry[i]["title"].GetString(), truncate(curr_entry[i]["title"].GetString(),
PLEX_MAX_STRING_LENGTH), PLEX_MAX_STRING_LENGTH),
truncate(curr_entry[i]["state"].IsNull() ? "Historical" : "Playing", truncate(curr_entry[i]["state"].IsNull() ? "Historical" : "Playing",
PLEX_MAX_STRING_LENGTH), PLEX_MAX_STRING_LENGTH),
}; };
@ -129,7 +129,7 @@ void plex::update(){
json_doc.Parse(json_string.c_str()); json_doc.Parse(json_string.c_str());
friendly_name = truncate(json_doc["response"]["data"].GetString(), PLEX_MAX_STRING_LENGTH - 10); friendly_name = truncate(json_doc["response"]["data"].GetString(), PLEX_MAX_STRING_LENGTH - 10);
json_string.clear(); json_string.clear();
} }
@ -144,13 +144,13 @@ void plex::update_texture(){
//save the old colors //save the old colors
SDL_GetRenderDrawColor(board::getRenderer(), &o_red, SDL_GetRenderDrawColor(board::getRenderer(), &o_red,
&o_green, &o_blue, &o_alpha); &o_green, &o_blue, &o_alpha);
SDL_SetRenderTarget(board::getRenderer(), _texture); SDL_SetRenderTarget(board::getRenderer(), _texture);
SDL_RenderClear(board::getRenderer()); SDL_RenderClear(board::getRenderer());
//set the new color //set the new color
SDL_SetRenderDrawColor(board::getRenderer(), SDL_SetRenderDrawColor(board::getRenderer(),
PLEX_BGBOX_RED, PLEX_BGBOX_GREEN, PLEX_BGBOX_RED, PLEX_BGBOX_GREEN,
PLEX_BGBOX_BLUE, PLEX_BGBOX_ALPHA); PLEX_BGBOX_BLUE, PLEX_BGBOX_ALPHA);
//background image //background image
@ -411,8 +411,8 @@ void plex::update_texture(){
void plex::initTexture(){ void plex::initTexture(){
if(_texture == nullptr){ if(_texture == nullptr){
_texture = SDL_CreateTexture(board::getRenderer(), _texture = SDL_CreateTexture(board::getRenderer(),
SDL_PIXELFORMAT_RGBA8888, SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET, SDL_TEXTUREACCESS_TARGET,
SCREEN_WIDTH, SCREEN_HEIGHT); SCREEN_WIDTH, SCREEN_HEIGHT);
SDL_SetTextureBlendMode(_texture, SDL_BLENDMODE_BLEND); SDL_SetTextureBlendMode(_texture, SDL_BLENDMODE_BLEND);

View File

@ -39,7 +39,6 @@ namespace dashboard::panel {
CURL* api_curl; CURL* api_curl;
std::string json_string; std::string json_string;
rapidjson::Document json_doc; rapidjson::Document json_doc;
struct plex_entry { struct plex_entry {
std::string friendly_name; std::string friendly_name;
std::string ip_address; std::string ip_address;

View File

@ -13,10 +13,10 @@ namespace dashboard::panel {
//string to not show anything //string to not show anything
constexpr char PLEX_TITLE[] = "Plex"; constexpr char PLEX_TITLE[] = "Plex";
//Tautili endpoint //Tautili endpoint
constexpr char PLEX_URL_SOURCE_HISTORY[] = "http://192.168.1.104:8181/api/v2?apikey=64af06e0497342f7a5862462ddbbd309&cmd=get_history&length=5"; constexpr char PLEX_URL_SOURCE_HISTORY[] = "http://192.168.1.104:8181/api/v2?apikey=62917cc0bea04aa69cbb85141e312e84&cmd=get_history&length=5";
constexpr char PLEX_URL_SOURCE_NAME[] = "http://192.168.1.104:8181/api/v2?apikey=64af06e0497342f7a5862462ddbbd309&cmd=get_server_friendly_name"; constexpr char PLEX_URL_SOURCE_NAME[] = "http://192.168.1.104:8181/api/v2?apikey=62917cc0bea04aa69cbb85141e312e84&cmd=get_server_friendly_name";
constexpr char PLEX_URL_SOURCE_TOP_USERS[] = "http://192.168.1.104:8181/api/v2?apikey=64af06e0497342f7a5862462ddbbd309&cmd=get_plays_by_top_10_users"; constexpr char PLEX_URL_SOURCE_TOP_USERS[] = "http://192.168.1.104:8181/api/v2?apikey=62917cc0bea04aa69cbb85141e312e84&cmd=get_plays_by_top_10_users";
//How many characters of a show title should we show? //How many characters of a show title should we show?
constexpr size_t PLEX_MAX_STRING_LENGTH = 35; constexpr size_t PLEX_MAX_STRING_LENGTH = 35;
@ -34,5 +34,4 @@ namespace dashboard::panel {
constexpr uint8_t PLEX_BGBOX_GREEN = 0x66; constexpr uint8_t PLEX_BGBOX_GREEN = 0x66;
constexpr uint8_t PLEX_BGBOX_BLUE = 0x66; constexpr uint8_t PLEX_BGBOX_BLUE = 0x66;
constexpr uint8_t PLEX_BGBOX_ALPHA = 0x99; constexpr uint8_t PLEX_BGBOX_ALPHA = 0x99;
} }

View File

@ -36,57 +36,57 @@ namespace dashboard::panel {
//with weather string being the string shown on screen, and weather image //with weather string being the string shown on screen, and weather image
//being the name of the preloaded image file to be shown. It is best that //being the name of the preloaded image file to be shown. It is best that
//these images be square. //these images be square.
const std::pair<std::string, std::string> WEATHER_CLEAR_DAY = const std::pair<std::string, std::string> WEATHER_CLEAR_DAY =
{"Clear skies", "clearday.png"}; {"Clear skies", "clearday.png"};
const std::pair<std::string, std::string> WEATHER_CLEAR_NIGHT = const std::pair<std::string, std::string> WEATHER_CLEAR_NIGHT =
{"Clear skies", "clearnight.png"}; {"Clear skies", "clearnight.png"};
const std::pair<std::string, std::string> WEATHER_PCLOUDY_DAY = const std::pair<std::string, std::string> WEATHER_PCLOUDY_DAY =
{"Slightly cloudy", "cloudyday.png"}; {"Slightly cloudy", "cloudyday.png"};
const std::pair<std::string, std::string> WEATHER_PCLOUDY_NIGHT = const std::pair<std::string, std::string> WEATHER_PCLOUDY_NIGHT =
{"Slightly cloudy", "cloudynight.png"}; {"Slightly cloudy", "cloudynight.png"};
const std::pair<std::string, std::string> WEATHER_MCLOUDY_DAY = const std::pair<std::string, std::string> WEATHER_MCLOUDY_DAY =
{"Moderately cloudy", "cloudyday.png"}; {"Moderately cloudy", "cloudyday.png"};
const std::pair<std::string, std::string> WEATHER_MCLOUDY_NIGHT = const std::pair<std::string, std::string> WEATHER_MCLOUDY_NIGHT =
{"Moderately cloudy", "cloudynight.png"}; {"Moderately cloudy", "cloudynight.png"};
const std::pair<std::string, std::string> WEATHER_CLOUDY_DAY = const std::pair<std::string, std::string> WEATHER_CLOUDY_DAY =
{"Very cloudy", "cloudyday.png"}; {"Very cloudy", "cloudyday.png"};
const std::pair<std::string, std::string> WEATHER_CLOUDY_NIGHT = const std::pair<std::string, std::string> WEATHER_CLOUDY_NIGHT =
{"Very cloudy", "cloudynight.png"}; {"Very cloudy", "cloudynight.png"};
const std::pair<std::string, std::string> WEATHER_HUMID_DAY = const std::pair<std::string, std::string> WEATHER_HUMID_DAY =
{"Very humid", "humidday.png"}; {"Very humid", "humidday.png"};
const std::pair<std::string, std::string> WEATHER_HUMID_NIGHT = const std::pair<std::string, std::string> WEATHER_HUMID_NIGHT =
{"Very humid", "humidnight.png"}; {"Very humid", "humidnight.png"};
const std::pair<std::string, std::string> WEATHER_LRAIN_DAY = const std::pair<std::string, std::string> WEATHER_LRAIN_DAY =
{"Light rain", "lrainday.png"}; {"Light rain", "lrainday.png"};
const std::pair<std::string, std::string> WEATHER_LRAIN_NIGHT = const std::pair<std::string, std::string> WEATHER_LRAIN_NIGHT =
{"Light rain", "lrainnight.png"}; {"Light rain", "lrainnight.png"};
const std::pair<std::string, std::string> WEATHER_RAIN_DAY = const std::pair<std::string, std::string> WEATHER_RAIN_DAY =
{"Rain", "rainday.png"}; {"Rain", "rainday.png"};
const std::pair<std::string, std::string> WEATHER_RAIN_NIGHT = const std::pair<std::string, std::string> WEATHER_RAIN_NIGHT =
{"Rain", "rainnight.png"}; {"Rain", "rainnight.png"};
const std::pair<std::string, std::string> WEATHER_OSHOWER_DAY = const std::pair<std::string, std::string> WEATHER_OSHOWER_DAY =
{"Overcast with showers", "shower.png"}; {"Overcast with showers", "shower.png"};
const std::pair<std::string, std::string> WEATHER_OSHOWER_NIGHT = const std::pair<std::string, std::string> WEATHER_OSHOWER_NIGHT =
{"Overcast with showers", "shower.png"}; {"Overcast with showers", "shower.png"};
const std::pair<std::string, std::string> WEATHER_ISHOWER_DAY = const std::pair<std::string, std::string> WEATHER_ISHOWER_DAY =
{"Moderate showers", "shower.png"}; {"Moderate showers", "shower.png"};
const std::pair<std::string, std::string> WEATHER_ISHOWER_NIGHT = const std::pair<std::string, std::string> WEATHER_ISHOWER_NIGHT =
{"Moderate showers", "shower.png"}; {"Moderate showers", "shower.png"};
const std::pair<std::string, std::string> WEATHER_LSNOW_DAY = const std::pair<std::string, std::string> WEATHER_LSNOW_DAY =
{"Light snow", "snowday.png"}; {"Light snow", "snowday.png"};
const std::pair<std::string, std::string> WEATHER_LSNOW_NIGHT = const std::pair<std::string, std::string> WEATHER_LSNOW_NIGHT =
{"Light snow", "snownight.png"}; {"Light snow", "snownight.png"};
const std::pair<std::string, std::string> WEATHER_SNOW_DAY = const std::pair<std::string, std::string> WEATHER_SNOW_DAY =
{"Moderate snow", "snowday.png"}; {"Moderate snow", "snowday.png"};
const std::pair<std::string, std::string> WEATHER_SNOW_NIGHT = const std::pair<std::string, std::string> WEATHER_SNOW_NIGHT =
{"Moderate snow", "snownight.png"}; {"Moderate snow", "snownight.png"};

View File

@ -19,13 +19,21 @@ wifi::wifi(){
std::cerr << "WIFI CONSTRUCTOR\n"; std::cerr << "WIFI CONSTRUCTOR\n";
_texture = nullptr; _texture = nullptr;
_time_on_screen = WIFI_DEFAULT_TIME_ON_SCREEN; _time_on_screen = WIFI_DEFAULT_TIME_ON_SCREEN;
_update_interval = std::chrono::milliseconds{WIFI_UPDATE_INTERVAL};
_title = WIFI_TITLE; _title = WIFI_TITLE;
api_curl = curl_easy_init();
curl_easy_setopt(api_curl, CURLOPT_WRITEFUNCTION,
dashboard::panel::wifi::curl_callback);
curl_easy_setopt(api_curl, CURLOPT_WRITEDATA, &json_string);
} }
wifi::~wifi(){ wifi::~wifi(){
std::cerr << "WIFI DECONSTRUCTOR\n"; std::cerr << "WIFI DECONSTRUCTOR\n";
if(_texture != nullptr) if(_texture != nullptr)
SDL_DestroyTexture(_texture); SDL_DestroyTexture(_texture);
if(api_curl != nullptr)
curl_easy_cleanup(api_curl);
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -40,6 +48,14 @@ void wifi::draw(){
update_texture(); update_texture();
} }
//check if its time to update
if((std::chrono::high_resolution_clock::now() - _last_update)
> _update_interval){
update();
update_texture();
}
SDL_RenderCopy(board::getRenderer(), _texture, NULL, NULL); SDL_RenderCopy(board::getRenderer(), _texture, NULL, NULL);
} }
@ -51,12 +67,91 @@ void wifi::draw(){
// Update the information of wifi // Update the information of wifi
// This DOES NOT update the display // This DOES NOT update the display
void wifi::update() { void wifi::update() {
//this shows static info on the wifi network, which does need to change std::cerr << "WIFI::UPDATE\n";
//therefore, no update _last_update = std::chrono::high_resolution_clock::now();
if(WIFI_HOMEASSISTANT){
curl_easy_setopt(api_curl, CURLOPT_URL,
WIFI_HOMEASSISTANT_URL);
std::string api_string = "Authorization: Bearer ";
api_string += WIFI_HOMEASSISTANT_APIKEY;
struct curl_slist *headers;
headers = NULL;
headers = curl_slist_append(headers, api_string.c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(api_curl, CURLOPT_HTTPHEADER, headers);
api_string.clear();
//perform request
curl_easy_perform(api_curl);
//parse the result
json_doc.Parse(json_string.c_str());
//update internal state
for(rapidjson::SizeType i = 0; i < json_doc.Size(); i++){
//if router external IP
if(strcmp(json_doc[i]["entity_id"].GetString(),
WIFI_HOMEASSISTANT_ROUTER_EXTERNAL_IP) == 0
&& WIFI_SHOW_PUBLIC_IP){
//store it in public IP field
public_ip = "WAN IP: ";
public_ip += json_doc[i]["state"].GetString();
} //if router WAN status
else if (strcmp(json_doc[i]["entity_id"].GetString(),
WIFI_HOMEASSISTANT_ROUTER_WAN_STATUS) == 0){
if(strcmp(json_doc[i]["state"].GetString(), "Connected") == 0)
wan_status = true;
else
wan_status = false;
} //speedtest up
else if (strcmp(json_doc[i]["entity_id"].GetString(),
WIFI_HOMEASSISTANT_SPEEDTEST_UP) == 0
&& WIFI_HOMEASSISTANT_SPEEDTEST){
speedtest_up = "Upload: ";
speedtest_up += json_doc[i]["state"].GetString();
speedtest_up += "Mbps";
} //speedtest down
else if (strcmp(json_doc[i]["entity_id"].GetString(),
WIFI_HOMEASSISTANT_SPEEDTEST_DOWN) == 0
&& WIFI_HOMEASSISTANT_SPEEDTEST){
speedtest_down = "Download: ";
speedtest_down += json_doc[i]["state"].GetString();
speedtest_down += "Mbps";
} //speedtest ping
else if (strcmp(json_doc[i]["entity_id"].GetString(),
WIFI_HOMEASSISTANT_SPEEDTEST_PING) == 0
&& WIFI_HOMEASSISTANT_SPEEDTEST){
speedtest_ping = "Ping: ";
speedtest_ping += json_doc[i]["state"].GetString();
speedtest_ping += "ms";
}
}
}
//if not using home assistant, grab it via normal method
if(WIFI_SHOW_PUBLIC_IP){
//get the string from normal url
if(!WIFI_HOMEASSISTANT){
public_ip = "WAN IP: ";
curl_easy_setopt(api_curl, CURLOPT_URL,
WIFI_PUBLIC_IP_URL);
//peform request
curl_easy_perform(api_curl);
//parse the result
public_ip += json_string;
}
//the public IP should have already been grabbed above
}
} }
/////////////////////////////////////// ///////////////////////////////////////
// Update the texture that is being // Update the texture that is being
// displayed. Due to the nature of what // displayed. Due to the nature of what
// is being displayed, this will only // is being displayed, this will only
// ever be called once, as the wifi // ever be called once, as the wifi
@ -110,24 +205,15 @@ void wifi::update_texture(){
board::getString(password_string.c_str(), board::getString(password_string.c_str(),
{ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), NULL, &tgt); { "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), NULL, &tgt);
tgt.y += tgt.h + 25;
TTF_SizeText(board::getFont({ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }),
wan_status ? "Internet is up" : "Internet is down; reconnecting", &tgt.w, &tgt.h);
SDL_RenderCopy(board::getRenderer(),
board::getString(wan_status ? "Internet is up" : "Internet is down; reconnecting",
{ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), NULL, &tgt);
//Get public ip address and display it //Get public ip address and display it
if(WIFI_SHOW_PUBLIC_IP){ if(WIFI_SHOW_PUBLIC_IP){
std::string public_ip = "WAN IP: ";
CURL* curl;
curl = curl_easy_init();
if(curl){
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, WIFI_PUBLIC_IP_URL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
dashboard::panel::wifi::curl_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &public_ip);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
} else {
public_ip += "Unkown";
}
tgt.y += tgt.h + 25; tgt.y += tgt.h + 25;
TTF_SizeText(board::getFont({ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), TTF_SizeText(board::getFont({ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }),
public_ip.c_str(), &tgt.w, &tgt.h); public_ip.c_str(), &tgt.w, &tgt.h);
@ -136,6 +222,29 @@ void wifi::update_texture(){
{ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), NULL, &tgt); { "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), NULL, &tgt);
} }
//TODO display speedtest info
tgt.y += tgt.h + 25;
TTF_SizeText(board::getFont({ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }),
speedtest_ping.c_str(), &tgt.w, &tgt.h);
SDL_RenderCopy(board::getRenderer(),
board::getString(speedtest_ping.c_str(),
{ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), NULL, &tgt);
tgt.y += tgt.h + 25;
TTF_SizeText(board::getFont({ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }),
speedtest_up.c_str(), &tgt.w, &tgt.h);
SDL_RenderCopy(board::getRenderer(),
board::getString(speedtest_up.c_str(),
{ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), NULL, &tgt);
tgt.y += tgt.h + 25;
TTF_SizeText(board::getFont({ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }),
speedtest_down.c_str(), &tgt.w, &tgt.h);
SDL_RenderCopy(board::getRenderer(),
board::getString(speedtest_down.c_str(),
{ "Roboto_Mono/RobotoMono-Medium.ttf", 50 }), NULL, &tgt);
SDL_SetRenderTarget(board::getRenderer(), NULL); SDL_SetRenderTarget(board::getRenderer(), NULL);
} }
@ -148,8 +257,8 @@ void wifi::initTexture(){
std::cerr << "WIFI INIT TEXTURE\n"; std::cerr << "WIFI INIT TEXTURE\n";
if(_texture == nullptr){ if(_texture == nullptr){
_texture = SDL_CreateTexture(board::getRenderer(), _texture = SDL_CreateTexture(board::getRenderer(),
SDL_PIXELFORMAT_RGBA8888, SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET, SDL_TEXTUREACCESS_TARGET,
SCREEN_WIDTH, SCREEN_HEIGHT); SCREEN_WIDTH, SCREEN_HEIGHT);
SDL_SetTextureBlendMode(_texture, SDL_BLENDMODE_BLEND); SDL_SetTextureBlendMode(_texture, SDL_BLENDMODE_BLEND);
@ -158,7 +267,7 @@ void wifi::initTexture(){
/////////////////////////////////////// ///////////////////////////////////////
// Curl callback function // Curl callback function
size_t dashboard::panel::wifi::curl_callback(void* contents, size_t size, size_t dashboard::panel::wifi::curl_callback(void* contents, size_t size,
size_t nmemb, void* userp){ size_t nmemb, void* userp){
((std::string*)userp)->append((char*)contents, size * nmemb); ((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb; return size * nmemb;

View File

@ -13,8 +13,10 @@
#include <SDL2/SDL_ttf.h> #include <SDL2/SDL_ttf.h>
#include <curl/curl.h> #include <curl/curl.h>
#include "../util/rapidjson/document.h"
#include <iostream> #include <iostream>
#include <chrono>
namespace dashboard::panel { namespace dashboard::panel {
class wifi : public panel { class wifi : public panel {
@ -29,6 +31,19 @@ namespace dashboard::panel {
void update(); void update();
void update_texture(); void update_texture();
void initTexture(); void initTexture();
std::string public_ip;
bool wan_status;
std::string speedtest_up;
std::string speedtest_down;
std::string speedtest_ping;
CURL* api_curl;
std::string json_string;
rapidjson::Document json_doc;
std::chrono::time_point<std::chrono::high_resolution_clock> _last_update;
std::chrono::milliseconds _update_interval;
}; };
} }

View File

@ -16,11 +16,11 @@ namespace dashboard::panel {
constexpr size_t WIFI_DEFAULT_TIME_ON_SCREEN = 15000; constexpr size_t WIFI_DEFAULT_TIME_ON_SCREEN = 15000;
//How long should we wait between updates? in ms //How long should we wait between updates? in ms
//Due to the nature of this panel, this value is ignored //Default 60s
constexpr size_t WIFI_UPDATE_INTERVAL = 60000; constexpr size_t WIFI_UPDATE_INTERVAL = 60000;
//Set to true to have your public IP be shown on the wifi page //Set to true to have your public IP be shown on the wifi page
//Will make a curl request to WIFI_PUBLIC_IP_URL when the frame //Will make a curl request to WIFI_PUBLIC_IP_URL when the frame
//is rendered. This this is a privacy concern disable this option //is rendered. This this is a privacy concern disable this option
constexpr bool WIFI_SHOW_PUBLIC_IP = true; constexpr bool WIFI_SHOW_PUBLIC_IP = true;
constexpr char WIFI_PUBLIC_IP_URL[] = "http://ipinfo.io/ip"; constexpr char WIFI_PUBLIC_IP_URL[] = "http://ipinfo.io/ip";
@ -31,4 +31,36 @@ namespace dashboard::panel {
constexpr char WIFI_NETWORK_NAME[] = "MyNetwork"; constexpr char WIFI_NETWORK_NAME[] = "MyNetwork";
constexpr char WIFI_NETWORK_PASS[] = "MyPassword"; constexpr char WIFI_NETWORK_PASS[] = "MyPassword";
// Set to true if you have home assistant on your network
// If you do, you can do alot more with it, including showing number of
// clients, network speed, etc
constexpr bool WIFI_HOMEASSISTANT = true;
//API and URL for home assistant. If using the home assistant panel, this
//should be the same
constexpr char WIFI_HOMEASSISTANT_APIKEY[] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxYTNhNDhhYTBlZDM0MjkyOTk5OWZhNGVjZGRjMGUwMCIsImlhdCI6MTY0MjE4NDU0MCwiZXhwIjoxOTU3NTQ0NTQwfQ.Zdy7-ZwX0HuwhmUGeLhF5XCluotsKmpDqmi5o-hQZCc";
constexpr char WIFI_HOMEASSISTANT_URL[] = "http://192.168.1.104:8123/api/states";
//Name of the external IP sensor entry in home assistant
//If your router integrates with it, then you should be able to find it in
//integrations
constexpr char WIFI_HOMEASSISTANT_ROUTER_EXTERNAL_IP[] = "sensor.r7000p_gateway_external_ip";
//Name of WAN status sensor in home assistant
constexpr char WIFI_HOMEASSISTANT_ROUTER_WAN_STATUS[] = "sensor.r7000p_gateway_wan_status";
//Enable this if you have the ookla speedtest module on home assistant
constexpr bool WIFI_HOMEASSISTANT_SPEEDTEST = true;
//Name of the entity id string used by the speedtest upload sensor in home
//assistant
constexpr char WIFI_HOMEASSISTANT_SPEEDTEST_UP[] = "sensor.speedtest_upload";
//Name of the entity id string used by the speedtest download sensor in home
//assistant
constexpr char WIFI_HOMEASSISTANT_SPEEDTEST_DOWN[] = "sensor.speedtest_download";
//Name of the entity id string used by the speedtest ping sensor in home
//assistant
constexpr char WIFI_HOMEASSISTANT_SPEEDTEST_PING[] = "sensor.speedtest_ping";
} }

10
tests/getHA.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
API_KEY=$(pass gluttony/homeassistant/API)
IP_ADDR="192.168.1.104"
curl -X GET \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-o out.json \
http://$IP_ADDR:8123/api/states