Notation like Home Assistant, each screen in its own language, and the language in every build
From the end-to-end test on the bench boards and an independent review: - Units spaced as Home Assistant does (54% / 54 % in German and French, 18°, 21.5 °C), in the firmware and the app. - Grouping from five digits where the language does (1234 but 12.345 in Spanish, Polish, Portuguese): the layout message carries group_min and percent_space; an unknown number style no longer refuses a layout. - Automatic follows Home Assistant's own language for the clock and numbers, also without a translation (Swedish: 24 hours, 1 234,5; pt-BR: 1.234,5): regions.json from the CLDR for all 63 languages Home Assistant offers. - 12-hour clock: the top bar says 3:48 PM, the clock card 3:48 (not 03:48), Last runs follow the clock. - The climate setpoint font has the decimal comma; the value fonts have ², ³ and µ (m³ was a box). - Weather days: at most three letters without the dot (mié, sáb, czw); the screen took three bytes. - Texts fall back per key through the base language (pt-BR -> pt -> en) in the firmware and the app. - Each screen gets the app's words in the language its firmware speaks; firmware from before the languages gets English and its own letters, not boxes. - set_language re-reads what it writes (indentation, comments, quoted keys, CRLF, !include) and every build writes the language first; the 24-hour pin no longer hits a fresh install and waits for the old clock switches. - Tests: region and notation, plural parity with the C++ rules, set_language edge cases, the pin, PUT /api/language. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,7 @@ inline std::string number_text(float value) {
|
||||
if (text.back() == '.') text.pop_back();
|
||||
return text;
|
||||
}
|
||||
inline std::string percent_text(int percent) { return std::to_string(percent) + " %"; }
|
||||
inline std::string percent_text(int percent) { return screen_text::percent(percent); }
|
||||
// The roller takes its names as one string, a line per name.
|
||||
inline std::string joined(const std::vector<std::string> &names) {
|
||||
std::string out;
|
||||
|
||||
@@ -65,8 +65,7 @@ inline std::string duration(uint32_t seconds) {
|
||||
}
|
||||
|
||||
// A value as Home Assistant writes it: fixed decimals, the language's separator between thousands and its decimal
|
||||
// mark (screen.number: "1,234.5" in English, "1.234,5" in Dutch), "%" and "°" on the number and any other unit after
|
||||
// a space.
|
||||
// mark (screen.number: "1,234.5" in English, "1.234,5" in Dutch), the unit spaced as Home Assistant spaces it.
|
||||
inline std::string number(float value, int decimals, const std::string &unit) {
|
||||
if (!std::isfinite(value)) return "--";
|
||||
char b[40];
|
||||
@@ -77,9 +76,7 @@ inline std::string number(float value, int decimals, const std::string &unit) {
|
||||
std::string whole = text.substr(0, dot), rest = dot == std::string::npos ? "" : text.substr(dot);
|
||||
bool zero = whole.find_first_not_of('0') == std::string::npos && rest.find_first_not_of(".0") == std::string::npos;
|
||||
text = (zero ? "" : sign) + screen_text::write_number(whole, rest.empty() ? std::string() : rest.substr(1));
|
||||
if (unit.empty()) return text;
|
||||
if (unit == "%" || unit == "°") return text + unit;
|
||||
return text + " " + unit;
|
||||
return screen_text::with_unit(text, unit);
|
||||
}
|
||||
|
||||
// Where part `i` of 24 is drawn and read: the middle of its stretch of the range, as a fraction of the width.
|
||||
|
||||
@@ -95,7 +95,7 @@ inline void preview(Row &row) {
|
||||
lv_obj_set_style_bg_opa(row.slider, row.off ? LV_OPA_TRANSP : LV_OPA_COVER, LV_PART_KNOB);
|
||||
lv_obj_set_style_bg_color(row.slider, theme::color(row.off ? theme::TRACK : theme::AMBER_TRACK), LV_PART_MAIN);
|
||||
if (row.off) lv_label_set_text(row.value, screen_text::tr(screen_text::txt::ha_off));
|
||||
else lv_label_set_text_fmt(row.value, "%d %%", value);
|
||||
else lv_label_set_text(row.value, screen_text::percent(value).c_str());
|
||||
}
|
||||
}
|
||||
inline void event(lv_event_t *e) {
|
||||
|
||||
@@ -44,8 +44,17 @@ inline esphome::ESPPreferenceObject buttons_preference;
|
||||
inline esphome::ESPPreferenceObject swipe_preference;
|
||||
inline esphome::ESPPreferenceObject home_preference;
|
||||
inline esphome::ESPPreferenceObject dark_preference;
|
||||
// The number format of Settings -> Language & region (app 0.2.90), kept for the next start: screen_text::number_style.
|
||||
// The number format of Settings -> Language & region (app 0.2.90), kept for the next start: screen_text::number_style
|
||||
// (bits 0-3), number_group_min (4-5) and number_percent (6-7) in one word.
|
||||
inline esphome::ESPPreferenceObject numbers_preference;
|
||||
inline uint32_t screen_text_numbers() {
|
||||
return screen_text::number_style | (uint32_t) screen_text::number_group_min << 4 | (uint32_t) screen_text::number_percent << 6;
|
||||
}
|
||||
inline void screen_text_numbers(uint32_t word) {
|
||||
screen_text::number_style = (word & 0xF) <= 3 ? word & 0xF : 0;
|
||||
screen_text::number_group_min = (word >> 4 & 3) <= 2 ? word >> 4 & 3 : 0;
|
||||
screen_text::number_percent = (word >> 6 & 3) <= 2 ? word >> 6 & 3 : 0;
|
||||
}
|
||||
inline Model model;
|
||||
inline std::string inbox;
|
||||
inline const lv_font_t *watch_font = nullptr;
|
||||
@@ -91,6 +100,11 @@ inline const char *weather_icon(const std::string &condition);
|
||||
inline const char *weather_text(const std::string &condition);
|
||||
inline std::string timer_text(const Tile &t);
|
||||
inline std::string last_run_text(uint32_t epoch);
|
||||
// "07:12": a time of day as Home Assistant and ESP Screens send it, for screen_text::clock_text.
|
||||
inline std::string hhmm(const esphome::ESPTime &time) {
|
||||
char b[8]; snprintf(b, sizeof(b), "%02d:%02d", time.hour, time.minute);
|
||||
return b;
|
||||
}
|
||||
inline void history_received();
|
||||
// Camera images full screen and on an alert (firmware 0.2.57+, the Guition binds them; see the end of this file).
|
||||
inline void camera_open(const std::string &entity, const std::string &name);
|
||||
@@ -153,7 +167,7 @@ inline void load_settings() {
|
||||
buttons_preference = esphome::global_preferences->make_preference<uint32_t>(0x50474231);
|
||||
numbers_preference = esphome::global_preferences->make_preference<uint32_t>(0x4E554D31);
|
||||
uint32_t numbers_saved=0;
|
||||
if(numbers_preference.load(&numbers_saved) && numbers_saved<=3)screen_text::number_style=(uint8_t)numbers_saved;
|
||||
if(numbers_preference.load(&numbers_saved))screen_text_numbers(numbers_saved);
|
||||
uint32_t swipe_saved=0;
|
||||
if(swipe_preference.load(&swipe_saved))swipe_pages=swipe_saved==1;
|
||||
uint32_t dark_saved=0;
|
||||
@@ -308,8 +322,6 @@ inline std::string receive(const std::string &payload) {
|
||||
root["rotation"].as<unsigned>()>270 || root["rotation"].as<unsigned>()%90!=0))return false;
|
||||
// The clock and the number format of Settings -> Language & region (app 0.2.90), the same for every screen.
|
||||
if(!root["clock_24h"].isNull() && !root["clock_24h"].is<bool>())return false;
|
||||
if(!root["numbers"].isNull() && (!root["numbers"].is<const char*>() ||
|
||||
screen_text::number_style_of(root["numbers"].as<std::string>())<0))return false;
|
||||
if(!root["keepalive"].isNull() && (!root["keepalive"].is<unsigned>() ||
|
||||
root["keepalive"].as<unsigned>()<5 || root["keepalive"].as<unsigned>()>3600))return false;
|
||||
// Explicit grid positions (0.2.26+), one absolute slot per entity; absent on older managers.
|
||||
@@ -345,11 +357,16 @@ inline std::string receive(const std::string &payload) {
|
||||
}
|
||||
bool rotation_changed=false;
|
||||
if(root["clock_24h"].is<bool>())settings.clock_24h=root["clock_24h"].as<bool>()?1:0;
|
||||
// How numbers are written; a value this firmware doesn't know is left out, never the layout (a newer app may
|
||||
// know more styles).
|
||||
bool format_changed=false;
|
||||
if(root["numbers"].is<const char*>()){
|
||||
int style=screen_text::number_style_of(root["numbers"].as<std::string>());
|
||||
if(style!=screen_text::number_style){screen_text::number_style=(uint8_t)style;uint32_t saved=(uint32_t)style;numbers_preference.save(&saved);format_changed=true;}
|
||||
}
|
||||
int style=root["numbers"].is<const char*>()?screen_text::number_style_of(root["numbers"].as<std::string>()):-1;
|
||||
uint32_t numbers=screen_text_numbers();
|
||||
if(style>=0)numbers=(numbers&~0xFu)|(uint32_t)style;
|
||||
if(root["group_min"].is<unsigned>() && root["group_min"].as<unsigned>()>=1 && root["group_min"].as<unsigned>()<=2)
|
||||
numbers=(numbers&~0x30u)|(root["group_min"].as<unsigned>()<<4);
|
||||
if(root["percent_space"].is<bool>())numbers=(numbers&~0xC0u)|((root["percent_space"].as<bool>()?2u:1u)<<6);
|
||||
if(numbers!=screen_text_numbers()){screen_text_numbers(numbers);numbers_preference.save(&numbers);format_changed=true;}
|
||||
if(rotation_supported && root["rotation"].is<unsigned>() && rotation!=root["rotation"].as<unsigned>()){
|
||||
rotation=root["rotation"].as<unsigned>();rotation_preference.save(&rotation);rotation_changed=true;
|
||||
}
|
||||
@@ -591,7 +608,7 @@ inline std::string receive(const std::string &payload) {
|
||||
if (extra["days"].is<JsonArray>()) for (JsonVariant day : extra["days"].as<JsonArray>()) {
|
||||
if (next.forecast.size() == 5) break;
|
||||
next.forecast.emplace_back(); auto &f = next.forecast.back();
|
||||
f.day = string(day["d"], 3); f.condition = string(day["c"], 20); f.high = number(day["h"]); f.low = number(day["l"]);
|
||||
f.day = string(day["d"], 8); f.condition = string(day["c"], 20); f.high = number(day["h"]); f.low = number(day["l"]);
|
||||
f.rain = number(day["p"]); f.mm = number(day["r"]);
|
||||
}
|
||||
if (extra["hours"].is<JsonArray>()) for (JsonVariant hour : extra["hours"].as<JsonArray>()) {
|
||||
@@ -1086,10 +1103,9 @@ inline lv_obj_t *detail_text(lv_obj_t *parent,const std::string &text,int x,int
|
||||
}
|
||||
// "30%", "30% · 1.7 mm" or "1.7 mm": whatever the provider reports; empty when dry.
|
||||
inline std::string rain_text(float chance,float mm,bool with_mm){
|
||||
char b[32];
|
||||
if(std::isfinite(chance) && chance>=0){
|
||||
if(with_mm && std::isfinite(mm) && mm>=0.05f){snprintf(b,sizeof(b),"%d%% · ",(int)std::lround(chance));return b+screen_text::decimal(mm,1)+" mm";}
|
||||
snprintf(b,sizeof(b),"%d%%",(int)std::lround(chance));return b;
|
||||
if(with_mm && std::isfinite(mm) && mm>=0.05f)return screen_text::percent((int)std::lround(chance))+" · "+screen_text::with_unit(screen_text::decimal(mm,1),"mm");
|
||||
return screen_text::percent((int)std::lround(chance));
|
||||
}
|
||||
if(std::isfinite(mm) && mm>=0.05f)return screen_text::decimal(mm,mm<10?1:0)+" mm";
|
||||
return "";
|
||||
@@ -1134,7 +1150,7 @@ inline void render_weather_detail(const Tile &t,bool large,int width,int height,
|
||||
detail_text(now,t.available()?weather_text(t.state):tr(txt::ha_unavailable),text_x,cy+(hero-lines_h)/2,text_w,detail_font,LV_TEXT_ALIGN_LEFT,ink);
|
||||
std::string details;
|
||||
if(std::isfinite(weather.feels))details=fill(txt::weather_feels_like,"n",(int)std::lround(weather.feels));
|
||||
if(std::isfinite(t.humidity)){snprintf(b,sizeof(b),"%d%%",(int)std::lround(t.humidity));details+=(details.empty()?"":" · ")+std::string(b);}
|
||||
if(std::isfinite(t.humidity))details+=(details.empty()?"":" · ")+screen_text::percent((int)std::lround(t.humidity));
|
||||
if(std::isfinite(weather.wind)){snprintf(b,sizeof(b),"%.0f %s",weather.wind,weather.wind_unit.empty()?"km/h":weather.wind_unit.c_str());details+=(details.empty()?"":" · ")+std::string(b);}
|
||||
detail_text(now,details,text_x,cy+(hero-lines_h)/2+text_h+(large?2:0),text_w,small,LV_TEXT_ALIGN_LEFT,muted);
|
||||
// Next hours inside the same card: time, icon, temperature, rain per column.
|
||||
@@ -1221,7 +1237,7 @@ inline void vacuum_battery(lv_obj_t *parent,int x,int y,int w,int h,float level)
|
||||
// Without a parent it only measures.
|
||||
inline int vacuum_power(lv_obj_t *parent,const Tile &t,int x,int y,const lv_font_t *font,bool large){
|
||||
const lv_font_t *bolt_font=large && watch_icon_font?watch_icon_font:mini_icon_font;
|
||||
std::string percent=std::to_string((int)std::lround(t.battery))+"%";
|
||||
std::string percent=screen_text::percent((int)std::lround(t.battery));
|
||||
int h=lv_font_get_line_height(font),meter_w=large?30:22,meter_h=large?15:11,gap=large?10:6,words=text_width(percent,font);
|
||||
int bolt_w=t.extra().charging && bolt_font?text_width("\U000F0241",bolt_font):0;
|
||||
int width=meter_w+3+gap+words+(bolt_w?gap/2+bolt_w:0);
|
||||
@@ -1382,7 +1398,7 @@ inline void cover_slider_event(lv_event_t *e){
|
||||
int raw=lv_slider_get_value(slider);
|
||||
if(raw<0||raw>1000){raw=std::clamp(raw,0,1000);lv_slider_set_value(slider,raw,LV_ANIM_OFF);}
|
||||
int percent=(int)std::lround(raw/10.0f);
|
||||
if(auto *value=cover_values[tilt?1:0])label(value,std::to_string(tilt?percent:100-percent)+"%");
|
||||
if(auto *value=cover_values[tilt?1:0])label(value,screen_text::percent(tilt?percent:100-percent));
|
||||
return;
|
||||
}
|
||||
if(code!=LV_EVENT_RELEASED||detail_index>=model.count)return;
|
||||
@@ -1462,7 +1478,7 @@ inline void cover_battery(const Tile &t,bool large,int width){
|
||||
int level=(int)std::lround(std::clamp(t.battery,0.0f,100.0f));
|
||||
int line=lv_font_get_line_height(font),block=meter_h+gap+line,cx=width-bar_x-bar/2,y=bar_y+(bar-block)/2;
|
||||
vacuum_battery(detail_root,cx-meter_w/2-1,y,meter_w,meter_h,t.battery);
|
||||
detail_text(detail_root,std::to_string(level)+"%",cx-bar/2-8,y+meter_h+gap,bar+16,font,LV_TEXT_ALIGN_CENTER,theme::MUTED);
|
||||
detail_text(detail_root,screen_text::percent(level),cx-bar/2-8,y+meter_h+gap,bar+16,font,LV_TEXT_ALIGN_CENTER,theme::MUTED);
|
||||
}
|
||||
inline void render_cover_detail(Tile &t,bool large,int width,int height,int pad){
|
||||
auto card=tile_controls::cover_card(t);
|
||||
@@ -1485,7 +1501,7 @@ inline void render_cover_detail(Tile &t,bool large,int width,int height,int pad)
|
||||
if(card.position)parts[n++]={false,t.position,tr(txt::cover_position)};
|
||||
if(card.tilt)parts[n++]={true,t.extra().tilt,tr(txt::cover_tilt)};
|
||||
int value_h=lv_font_get_line_height(big),caption_h=lv_font_get_line_height(text);
|
||||
auto percent=[](float value){return std::isfinite(value)?std::to_string((int)std::lround(std::clamp(value,0.0f,100.0f)))+"%":std::string("--");};
|
||||
auto percent=[](float value){return std::isfinite(value)?screen_text::percent((int)std::lround(std::clamp(value,0.0f,100.0f))):std::string("--");};
|
||||
if(large){
|
||||
// Columns: the slider with its value and name below.
|
||||
int edge=16,slider_h=box_h-2*edge-value_h-caption_h+4,slider_w=n==2?120:140,column_gap=48;
|
||||
@@ -1911,7 +1927,7 @@ inline bool media_off(const Tile &t){return t.state=="off" || t.state=="standby"
|
||||
inline std::string media_volume_text(const Tile &t){
|
||||
if(t.muted)return tr(txt::media_muted);
|
||||
if(!std::isfinite(t.volume))return "";
|
||||
return std::to_string((int)std::lround(std::clamp(t.volume,0.0f,1.0f)*100))+" %";
|
||||
return screen_text::percent((int)std::lround(std::clamp(t.volume,0.0f,1.0f)*100));
|
||||
}
|
||||
// A rounded box without a style of its own: the art's placeholder, the bar's track and its fill.
|
||||
inline lv_obj_t *media_box(lv_obj_t *parent,lv_obj_t *existing,const media_card::Rect &r,uint32_t color,int radius){
|
||||
@@ -2072,7 +2088,7 @@ inline void show_detail(unsigned index){
|
||||
std::string state=d=="cover"?cover_status_line(t):detail_state(t);
|
||||
// The vacuum and history cards draw their own state.
|
||||
const bool with_history=history_card(t);
|
||||
if(d!="vacuum"&&d!="media_player"&&!with_history){detail_status=detail_label(detail_root,state+(t.unit.empty()?"":" "+t.unit),pad,large?80:50,width-2*pad);lv_obj_set_style_text_align(detail_status,LV_TEXT_ALIGN_CENTER,0);lv_obj_set_style_text_color(detail_status,theme::color(theme::MUTED),0);}
|
||||
if(d!="vacuum"&&d!="media_player"&&!with_history){detail_status=detail_label(detail_root,screen_text::with_unit(state,t.unit),pad,large?80:50,width-2*pad);lv_obj_set_style_text_align(detail_status,LV_TEXT_ALIGN_CENTER,0);lv_obj_set_style_text_color(detail_status,theme::color(theme::MUTED),0);}
|
||||
if(with_history){
|
||||
render_history_detail(t,large,width,height,pad);
|
||||
}else if(d=="vacuum"){
|
||||
@@ -2178,7 +2194,7 @@ inline std::string last_run_text(uint32_t epoch) {
|
||||
auto when = esphome::ESPTime::from_epoch_local(epoch);
|
||||
auto now = now_time ? now_time() : esphome::ESPTime{};
|
||||
if (!when.is_valid()) return tr(txt::script_never_run);
|
||||
char clock[8]; snprintf(clock, sizeof(clock), "%02d:%02d", when.hour, when.minute);
|
||||
std::string clock = screen_text::clock_text(hhmm(when), screen_settings::current.clock_24h != 0);
|
||||
if (now.is_valid() && now.year == when.year && now.day_of_year == when.day_of_year) return fill(txt::script_last_time, "time", clock);
|
||||
if (now.is_valid() && now.year == when.year && now.day_of_year == when.day_of_year + 1) return fill(txt::script_yesterday_time, "time", clock);
|
||||
std::string date = fill(fill(txt::date_day_month, "day", std::to_string(when.day_of_month)), "month", month_short(when));
|
||||
@@ -2470,8 +2486,12 @@ inline lv_obj_t *part_line(Widgets &w,unsigned i,lv_point_precise_t *points,unsi
|
||||
if(!p){p=lv_line_create(w.extra);lv_obj_remove_flag(p,LV_OBJ_FLAG_CLICKABLE);lv_obj_set_style_line_rounded(p,true,0);}
|
||||
set_line_width(p,width);lv_line_set_points(p,points,count);lv_obj_set_pos(p,x,y);return p;
|
||||
}
|
||||
// The big digits of the clock card: "07:12" on 24 hours, "7:12" on 12 (the clock font has no letters for AM and PM).
|
||||
inline std::string time_text(esphome::ESPTime now) {
|
||||
return now.is_valid() ? now.strftime(screen_settings::current.clock_24h ? "%H:%M" : "%I:%M") : "--:--";
|
||||
if (!now.is_valid()) return "--:--";
|
||||
if (screen_settings::current.clock_24h) return hhmm(now);
|
||||
char b[8]; snprintf(b, sizeof(b), "%d:%02d", now.hour % 12 ? now.hour % 12 : 12, now.minute);
|
||||
return b;
|
||||
}
|
||||
// Digital: big time over the date. Analog: index strokes (numerals at 12/3/6/9 on
|
||||
// large cards) with hour and minute hands. A single card adds a calendar block
|
||||
@@ -2794,7 +2814,7 @@ inline int layout_panel(Widgets &w,const Tile &t,bool large,int content_w,int co
|
||||
panel_w=count?count*m.key_w+(count-1)*m.gap:0;
|
||||
}else if(mode=="setpoint"||mode=="stepper"){
|
||||
float shown=std::isfinite(t.edit_value)?t.edit_value:tile_controls::edit_target(t);
|
||||
std::string suffix=d=="climate"?"°":t.unit.empty()?"":" "+t.unit;
|
||||
std::string suffix=d=="climate"?"°":screen_text::unit_suffix(t.unit);
|
||||
label(w.pill_value,tile_controls::format_value(shown,tile_controls::edit_step(t),suffix.c_str()));
|
||||
panel_w=m.pill_w;panel_h=m.key_h+2;
|
||||
}else if(tile_controls::is_slider(mode)){
|
||||
@@ -2881,7 +2901,7 @@ inline void control_event(lv_event_t *e) {
|
||||
float current=std::isfinite(t.edit_value)?t.edit_value:tile_controls::edit_target(t);
|
||||
t.edit_value=tile_controls::step_value(current,tile_controls::edit_step(t),t.minimum,t.maximum,command==tile_controls::STEP_UP?1:-1);
|
||||
t.edit_since=now;t.edit_sent=false;
|
||||
if(w.pill_value){std::string suffix=t.domain()=="climate"?"°":t.unit.empty()?"":" "+t.unit;label(w.pill_value,tile_controls::format_value(t.edit_value,tile_controls::edit_step(t),suffix.c_str()));}
|
||||
if(w.pill_value){std::string suffix=t.domain()=="climate"?"°":screen_text::unit_suffix(t.unit);label(w.pill_value,tile_controls::format_value(t.edit_value,tile_controls::edit_step(t),suffix.c_str()));}
|
||||
return;
|
||||
}
|
||||
if(t.waiting(now))return;
|
||||
@@ -3047,7 +3067,7 @@ inline void render_full(Widgets &w,const Tile &t,bool custom,bool clock,bool sun
|
||||
lv_obj_set_height(w.title,name_h);
|
||||
lv_obj_set_pos(w.title,0,top+circle+gap);lv_obj_set_width(w.title,content_w);
|
||||
lv_obj_set_pos(w.value,0,top+circle+gap+name_h+2);lv_obj_set_width(w.value,content_w);
|
||||
if(!unit.empty())label(w.value,value+" "+unit);
|
||||
if(!unit.empty())label(w.value,screen_text::with_unit(value,unit));
|
||||
return;
|
||||
}
|
||||
// The head as on the double-width card: the circle at the left, name and state beside it.
|
||||
@@ -3092,7 +3112,7 @@ inline void render_full(Widgets &w,const Tile &t,bool custom,bool clock,bool sun
|
||||
std::string middle;const lv_font_t *mid_font=watch_value_font?watch_value_font:w.value_font;
|
||||
auto d=t.domain();
|
||||
if(d=="climate" && std::isfinite(t.current))middle=screen_text::decimal(t.current,1)+"°";
|
||||
else if(d=="cover" && std::isfinite(t.position)){middle=std::to_string(static_cast<int>(std::lround(t.position)))+" %";}
|
||||
else if(d=="cover" && std::isfinite(t.position)){middle=screen_text::percent(static_cast<int>(std::lround(t.position)));}
|
||||
else if(d=="media_player" && !t.extra().media_title.empty()){middle=t.extra().media_title;mid_font=room_label?lv_obj_get_style_text_font(room_label,LV_PART_MAIN):w.title_font;}
|
||||
int mid_font_h=lv_font_get_line_height(mid_font);
|
||||
if(middle.empty() || mid_h<mid_font_h)return;
|
||||
@@ -3120,7 +3140,7 @@ inline void render_slot(size_t slot) {
|
||||
else if (!fresh() || !t.available()) value = tr(txt::ha_unavailable);
|
||||
else if (t.refused_at && esphome::millis() - t.refused_at < 4000) value = tr(txt::tile_refused);
|
||||
else if (d == "light" && t.state == "on" && tile_controls::effect_running(t.extra().effect)) value = t.extra().effect;
|
||||
else if (d == "light" && t.state == "on" && std::isfinite(t.brightness)) value = std::to_string(static_cast<int>(std::lround(std::clamp(t.brightness, 0.0f, 255.0f) * 100 / 255))) + " %";
|
||||
else if (d == "light" && t.state == "on" && std::isfinite(t.brightness)) value = screen_text::percent(static_cast<int>(std::lround(std::clamp(t.brightness, 0.0f, 255.0f) * 100 / 255)));
|
||||
// An airco that is off says so, with the room's temperature when it knows it, as Home Assistant's tile does
|
||||
// (firmware 0.2.71+); while it runs, the tile shows the temperature it is set to.
|
||||
else if (d == "climate" && t.state == "off") { value = tile_controls::climate_mode_text(t.state); if (std::isfinite(t.current)) value += " · " + screen_text::decimal(t.current, 1) + "°"; }
|
||||
@@ -3145,8 +3165,8 @@ inline void render_slot(size_t slot) {
|
||||
else if (!t.unit.empty() && !watch) value = screen_text::localize(value) + " " + t.unit;
|
||||
else if (!t.unit.empty() || d == "number" || d == "input_number" || d == "counter") value = screen_text::localize(value);
|
||||
bool pending=t.loading(esphome::millis());
|
||||
if(d=="weather" && std::isfinite(t.current)) {value=screen_text::decimal(t.current,1);if(!watch)value+=" "+t.unit;}
|
||||
if(d=="vacuum" && std::isfinite(t.battery))value += " / "+std::to_string((int)t.battery)+"%";
|
||||
if(d=="weather" && std::isfinite(t.current)) {value=screen_text::decimal(t.current,1);if(!watch)value=screen_text::with_unit(value,t.unit);}
|
||||
if(d=="vacuum" && std::isfinite(t.battery))value += " / "+screen_text::percent((int)t.battery);
|
||||
// Direct controls: only a wide card in the standard layout has room for the panel.
|
||||
bool with_panel=w.wide && !t.controls.empty() && !t.builtin() && !watch && t.inline_control!="slider" && fresh() && t.available();
|
||||
if(with_panel){std::string status=tile_controls::status_text(t);if(!status.empty())value=status;}
|
||||
@@ -3488,7 +3508,7 @@ inline void draw_header(bool live) {
|
||||
p.item = i;
|
||||
if (item.kind == Kind::analog) { p.dial = true; p.width = dial; }
|
||||
else {
|
||||
p.text = item.kind == Kind::clock ? (now.is_valid() ? now.strftime(screen_settings::current.clock_24h ? "%H:%M" : "%I:%M") : std::string("--:--"))
|
||||
p.text = item.kind == Kind::clock ? (now.is_valid() ? screen_text::clock_text(hhmm(now), screen_settings::current.clock_24h != 0) : std::string("--:--"))
|
||||
: item.kind == Kind::date ? (now.is_valid() ? header_bar::date_text(now.day_of_week, now.day_of_month, now.month) : std::string("—"))
|
||||
: item.kind == Kind::ago ? header_bar::ago_text(item.epoch, now_epoch()) : item.text;
|
||||
lv_font_glyph_dsc_t g;
|
||||
@@ -3886,7 +3906,7 @@ inline void tick() {
|
||||
}
|
||||
for(unsigned i=0;i<detail_action_count;++i){if(waiting||!fresh()||!t.available())lv_obj_add_state(detail_actions[i],LV_STATE_DISABLED);else lv_obj_remove_state(detail_actions[i],LV_STATE_DISABLED);}
|
||||
std::string status=waiting?std::string(tr(t.confirmed?txt::tile_confirmed:txt::tile_command_sent)):t.domain()=="cover"?cover_status_line(t):detail_state(t);
|
||||
if(detail_status)label(detail_status,status+(!waiting && !t.unit.empty()?" "+t.unit:""));
|
||||
if(detail_status)label(detail_status,waiting?status:screen_text::with_unit(status,t.unit));
|
||||
// The vacuum card lays its state out with the room and battery beside it, so a new text draws the
|
||||
// card again; once Home Assistant answered, the new state says enough.
|
||||
if(detail_badge_status && t.domain()=="vacuum"){
|
||||
|
||||
@@ -47,9 +47,12 @@ inline std::string plural(uint16_t id, int n) {
|
||||
return fill(form, "n", std::to_string(n));
|
||||
}
|
||||
|
||||
// How numbers are written (app 0.2.90): ESP Screens sends the choice of Settings -> Language & region, the one place
|
||||
// for every screen. 0 follows the language (screen.number), 1 is "1,234.5", 2 "1.234,5" and 3 "1 234,5".
|
||||
inline uint8_t number_style = 0;
|
||||
// How numbers are written (app 0.2.90): ESP Screens sends the choice of Settings -> Language & region with every layout,
|
||||
// the one place for every screen, worked out for the language Home Assistant speaks. Until a screen has it (and with an
|
||||
// app that doesn't send it), the screen writes numbers the way its own language does (screen.number); 0 means that.
|
||||
inline uint8_t number_style = 0; // 1 "1,234.5", 2 "1.234,5", 3 "1 234,5"
|
||||
inline uint8_t number_group_min = 0; // CLDR's minimum grouping digits: 2 writes 1234 but 12.345 (Spanish, Polish)
|
||||
inline uint8_t number_percent = 0; // 1 "54%", 2 "54 %" (German, French: Home Assistant's own rule)
|
||||
inline char decimal_mark() {
|
||||
if (number_style == 1) return '.';
|
||||
if (number_style == 2 || number_style == 3) return ',';
|
||||
@@ -66,8 +69,7 @@ inline const char *group_mark() {
|
||||
// How many digits a whole number needs before it gets separators: 4 ("1,234") in most languages, 5 in Italian, Spanish
|
||||
// and Polish, which write 1234 but 12.345 (CLDR's minimum grouping digits, as Home Assistant's own numbers do).
|
||||
inline size_t group_from() {
|
||||
if (number_style) return 4;
|
||||
int minimum = atoi(tr(txt::number_group_min));
|
||||
int minimum = number_group_min ? number_group_min : atoi(tr(txt::number_group_min));
|
||||
return minimum >= 2 ? 5 : 4;
|
||||
}
|
||||
// A number the way this screen writes numbers: `whole` digits (no sign) and the digits after the point.
|
||||
@@ -93,6 +95,23 @@ inline std::string localize(const std::string &state) {
|
||||
std::string fraction = dot == std::string::npos ? std::string() : state.substr(dot + 1);
|
||||
return state.substr(0, start) + write_number(whole, fraction);
|
||||
}
|
||||
// What follows a number for a percentage: "%" or " %".
|
||||
inline const char *percent_sign() {
|
||||
if (number_percent == 1) return "%";
|
||||
if (number_percent == 2) return " %";
|
||||
return tr(txt::number_percent);
|
||||
}
|
||||
// What follows a number for its unit, spaced as Home Assistant spaces them: " °C", "%" (" %" in German and French),
|
||||
// "°" (Home Assistant's blankBeforeUnit).
|
||||
inline std::string unit_suffix(const std::string &unit) {
|
||||
if (unit.empty() || unit == "\u00B0") return unit;
|
||||
if (unit == "%") return percent_sign();
|
||||
return " " + unit;
|
||||
}
|
||||
// A value with its unit: "21.5 °C", "54%", "18°".
|
||||
inline std::string with_unit(const std::string &value, const std::string &unit) { return value + unit_suffix(unit); }
|
||||
// A whole percentage: "54%", "54 %" in German and French.
|
||||
inline std::string percent(int value) { return std::to_string(value) + percent_sign(); }
|
||||
// A time "HH:MM" as Home Assistant or ESP Screens sends it, written for the screen's clock: "07:12" on 24 hours,
|
||||
// "7:12 AM" on 12 (in the language's day periods); `compact` leaves out ":00" ("2 PM"), as under a graph. Anything
|
||||
// else stays as it is.
|
||||
|
||||
@@ -94,19 +94,29 @@ def cpp_string(text):
|
||||
return '"' + ''.join(out) + '"'
|
||||
|
||||
|
||||
def chain(language, folder=TRANSLATIONS):
|
||||
"""The files a language's texts come from, most specific first: pt-BR, then pt, then English."""
|
||||
found = [language]
|
||||
base = language.split('-')[0]
|
||||
if base != language and load(base, folder) is not None:
|
||||
found.append(base)
|
||||
return found + ([] if 'en' in found else ['en'])
|
||||
|
||||
|
||||
def table(code, folder=TRANSLATIONS):
|
||||
"""(language, keys, texts, plural rule) of a build: every English key, in its language where it has one."""
|
||||
"""(language, keys, texts, plural rule) of a build: every English key, in its language where it has one, else in its
|
||||
base language (a small pt-BR file takes the rest from pt), else in English."""
|
||||
english = load('en', folder)
|
||||
language = resolve(code, folder)
|
||||
own = dict(screen_pairs(load(language, folder))) if language != 'en' else {}
|
||||
files = [load(name, folder) or {} for name in chain(language, folder)]
|
||||
texts_of = [dict(screen_pairs(data)) for data in files]
|
||||
keys, texts = [], []
|
||||
for key, text in screen_pairs(english):
|
||||
keys.append(key)
|
||||
translated = own.get(key)
|
||||
# Only an empty text is missing: a separator of one space (French thousands) is a text.
|
||||
texts.append(translated if isinstance(translated, str) and translated != '' else text)
|
||||
meta = (load(language, folder) or {}).get('_meta', {})
|
||||
rule = meta.get('plural') if meta.get('plural') in PLURAL_RULES else 'one_other'
|
||||
texts.append(next((own[key] for own in texts_of if isinstance(own.get(key), str) and own[key] != ''), text))
|
||||
plural = next((data.get('_meta', {}).get('plural') for data in files if data.get('_meta', {}).get('plural')), None)
|
||||
rule = plural if plural in PLURAL_RULES else 'one_other'
|
||||
return language, keys, texts, rule
|
||||
|
||||
|
||||
|
||||
@@ -9,274 +9,275 @@ namespace txt {
|
||||
constexpr uint16_t number_decimal = 0;
|
||||
constexpr uint16_t number_group = 1;
|
||||
constexpr uint16_t number_group_min = 2;
|
||||
constexpr uint16_t time_just_now = 3;
|
||||
constexpr uint16_t time_minutes_ago = 4;
|
||||
constexpr uint16_t time_hours_ago = 5;
|
||||
constexpr uint16_t time_yesterday = 6;
|
||||
constexpr uint16_t time_days_ago = 7;
|
||||
constexpr uint16_t time_weeks_ago = 8;
|
||||
constexpr uint16_t time_months_ago = 9;
|
||||
constexpr uint16_t time_years_ago = 10;
|
||||
constexpr uint16_t time_in_minutes = 11;
|
||||
constexpr uint16_t time_in_hours = 12;
|
||||
constexpr uint16_t time_tomorrow = 13;
|
||||
constexpr uint16_t time_in_days = 14;
|
||||
constexpr uint16_t time_am = 15;
|
||||
constexpr uint16_t time_pm = 16;
|
||||
constexpr uint16_t date_top_bar = 62;
|
||||
constexpr uint16_t date_full = 63;
|
||||
constexpr uint16_t date_day_month = 64;
|
||||
constexpr uint16_t date_weekday_time = 65;
|
||||
constexpr uint16_t duration_seconds = 66;
|
||||
constexpr uint16_t duration_minutes = 67;
|
||||
constexpr uint16_t duration_hours = 68;
|
||||
constexpr uint16_t duration_hours_minutes = 69;
|
||||
constexpr uint16_t duration_days = 70;
|
||||
constexpr uint16_t duration_days_hours = 71;
|
||||
constexpr uint16_t status_connecting = 72;
|
||||
constexpr uint16_t status_waiting = 73;
|
||||
constexpr uint16_t status_loading_tiles = 74;
|
||||
constexpr uint16_t status_ha_not_connected = 75;
|
||||
constexpr uint16_t status_manager_not_active = 76;
|
||||
constexpr uint16_t status_choose_tiles = 77;
|
||||
constexpr uint16_t status_home = 78;
|
||||
constexpr uint16_t status_no_network = 79;
|
||||
constexpr uint16_t settings_title = 80;
|
||||
constexpr uint16_t settings_brightness = 81;
|
||||
constexpr uint16_t settings_night = 82;
|
||||
constexpr uint16_t settings_screen = 83;
|
||||
constexpr uint16_t settings_this_screen = 84;
|
||||
constexpr uint16_t settings_dark_mode = 85;
|
||||
constexpr uint16_t settings_auto_standby = 86;
|
||||
constexpr uint16_t settings_standby_after = 87;
|
||||
constexpr uint16_t settings_standby_brightness = 88;
|
||||
constexpr uint16_t settings_night_mode = 89;
|
||||
constexpr uint16_t settings_starts = 90;
|
||||
constexpr uint16_t settings_ends = 91;
|
||||
constexpr uint16_t settings_night_brightness = 92;
|
||||
constexpr uint16_t settings_back_to_page_1 = 93;
|
||||
constexpr uint16_t settings_after = 94;
|
||||
constexpr uint16_t settings_also_on_standby = 95;
|
||||
constexpr uint16_t settings_swipe_between_pages = 96;
|
||||
constexpr uint16_t settings_page_buttons = 97;
|
||||
constexpr uint16_t settings_rotation = 98;
|
||||
constexpr uint16_t settings_address = 99;
|
||||
constexpr uint16_t settings_firmware = 100;
|
||||
constexpr uint16_t settings_home_assistant = 101;
|
||||
constexpr uint16_t settings_restart = 102;
|
||||
constexpr uint16_t settings_connected = 103;
|
||||
constexpr uint16_t settings_not_connected = 104;
|
||||
constexpr uint16_t settings_tap_again_to_restart = 105;
|
||||
constexpr uint16_t settings_seconds = 106;
|
||||
constexpr uint16_t settings_minutes = 107;
|
||||
constexpr uint16_t settings_hours = 108;
|
||||
constexpr uint16_t settings_hours_minutes = 109;
|
||||
constexpr uint16_t tile_tap_to_open = 110;
|
||||
constexpr uint16_t tile_page = 111;
|
||||
constexpr uint16_t tile_refused = 112;
|
||||
constexpr uint16_t tile_command_sent = 113;
|
||||
constexpr uint16_t tile_confirmed = 114;
|
||||
constexpr uint16_t alert_notification = 115;
|
||||
constexpr uint16_t alert_ok = 116;
|
||||
constexpr uint16_t light_color = 117;
|
||||
constexpr uint16_t light_color_temperature = 118;
|
||||
constexpr uint16_t light_brightness = 119;
|
||||
constexpr uint16_t light_waiting = 120;
|
||||
constexpr uint16_t effects_effect = 121;
|
||||
constexpr uint16_t effects_nothing = 122;
|
||||
constexpr uint16_t effects_loading = 123;
|
||||
constexpr uint16_t effects_not_connected = 124;
|
||||
constexpr uint16_t climate_target = 125;
|
||||
constexpr uint16_t climate_mode = 126;
|
||||
constexpr uint16_t climate_fan = 127;
|
||||
constexpr uint16_t climate_swing = 128;
|
||||
constexpr uint16_t climate_now = 129;
|
||||
constexpr uint16_t vacuum_check_robot = 130;
|
||||
constexpr uint16_t vacuum_auto_suction = 131;
|
||||
constexpr uint16_t vacuum_robot_chooses = 132;
|
||||
constexpr uint16_t vacuum_per_room = 133;
|
||||
constexpr uint16_t vacuum_speed_quiet = 134;
|
||||
constexpr uint16_t vacuum_speed_balanced = 135;
|
||||
constexpr uint16_t vacuum_speed_turbo = 136;
|
||||
constexpr uint16_t vacuum_speed_max = 137;
|
||||
constexpr uint16_t vacuum_pause_cleaning = 138;
|
||||
constexpr uint16_t vacuum_pause = 139;
|
||||
constexpr uint16_t vacuum_resume = 140;
|
||||
constexpr uint16_t vacuum_start_cleaning = 141;
|
||||
constexpr uint16_t vacuum_clean = 142;
|
||||
constexpr uint16_t vacuum_dock = 143;
|
||||
constexpr uint16_t cover_position = 144;
|
||||
constexpr uint16_t cover_tilt = 145;
|
||||
constexpr uint16_t cover_tilt_value = 146;
|
||||
constexpr uint16_t weather_feels_like = 147;
|
||||
constexpr uint16_t weather_no_forecast = 148;
|
||||
constexpr uint16_t weather_coming_days = 149;
|
||||
constexpr uint16_t history_no_data = 150;
|
||||
constexpr uint16_t history_average = 151;
|
||||
constexpr uint16_t history_average_of = 152;
|
||||
constexpr uint16_t history_now = 153;
|
||||
constexpr uint16_t history_range_hour = 154;
|
||||
constexpr uint16_t history_range_day = 155;
|
||||
constexpr uint16_t history_range_week = 156;
|
||||
constexpr uint16_t history_high = 157;
|
||||
constexpr uint16_t history_low = 158;
|
||||
constexpr uint16_t history_times = 159;
|
||||
constexpr uint16_t history_loading = 160;
|
||||
constexpr uint16_t history_empty = 161;
|
||||
constexpr uint16_t history_unavailable = 162;
|
||||
constexpr uint16_t media_muted = 163;
|
||||
constexpr uint16_t media_not_playing = 164;
|
||||
constexpr uint16_t media_loading = 165;
|
||||
constexpr uint16_t timer_running = 166;
|
||||
constexpr uint16_t timer_paused = 167;
|
||||
constexpr uint16_t timer_stopped = 168;
|
||||
constexpr uint16_t timer_pause = 169;
|
||||
constexpr uint16_t timer_start = 170;
|
||||
constexpr uint16_t timer_cancel = 171;
|
||||
constexpr uint16_t timer_paused_left = 172;
|
||||
constexpr uint16_t sun_name = 173;
|
||||
constexpr uint16_t sun_sunrise = 174;
|
||||
constexpr uint16_t sun_sunset = 175;
|
||||
constexpr uint16_t sun_rise = 176;
|
||||
constexpr uint16_t sun_set = 177;
|
||||
constexpr uint16_t script_running = 178;
|
||||
constexpr uint16_t script_never_run = 179;
|
||||
constexpr uint16_t script_last_time = 180;
|
||||
constexpr uint16_t script_yesterday_time = 181;
|
||||
constexpr uint16_t script_last_date = 182;
|
||||
constexpr uint16_t camera_no_image = 183;
|
||||
constexpr uint16_t camera_loading = 184;
|
||||
constexpr uint16_t camera_live = 185;
|
||||
constexpr uint16_t camera_recording = 186;
|
||||
constexpr uint16_t camera_tap_to_view = 187;
|
||||
constexpr uint16_t camera_no_image_yet = 188;
|
||||
constexpr uint16_t calibration_title = 189;
|
||||
constexpr uint16_t calibration_touch_check = 190;
|
||||
constexpr uint16_t calibration_hold = 191;
|
||||
constexpr uint16_t calibration_crosshair = 192;
|
||||
constexpr uint16_t calibration_gently = 193;
|
||||
constexpr uint16_t calibration_same = 194;
|
||||
constexpr uint16_t calibration_rejected = 195;
|
||||
constexpr uint16_t calibration_save_failed = 196;
|
||||
constexpr uint16_t ha_on = 197;
|
||||
constexpr uint16_t ha_off = 198;
|
||||
constexpr uint16_t ha_unavailable = 199;
|
||||
constexpr uint16_t ha_climate_off = 200;
|
||||
constexpr uint16_t ha_climate_heat = 201;
|
||||
constexpr uint16_t ha_climate_cool = 202;
|
||||
constexpr uint16_t ha_climate_heat_cool = 203;
|
||||
constexpr uint16_t ha_climate_auto = 204;
|
||||
constexpr uint16_t ha_climate_dry = 205;
|
||||
constexpr uint16_t ha_climate_fan_only = 206;
|
||||
constexpr uint16_t ha_hvac_action_heating = 207;
|
||||
constexpr uint16_t ha_hvac_action_cooling = 208;
|
||||
constexpr uint16_t ha_hvac_action_idle = 209;
|
||||
constexpr uint16_t ha_hvac_action_off = 210;
|
||||
constexpr uint16_t ha_hvac_action_drying = 211;
|
||||
constexpr uint16_t ha_hvac_action_fan = 212;
|
||||
constexpr uint16_t ha_hvac_action_preheating = 213;
|
||||
constexpr uint16_t ha_hvac_action_defrosting = 214;
|
||||
constexpr uint16_t ha_cover_open = 215;
|
||||
constexpr uint16_t ha_cover_closed = 216;
|
||||
constexpr uint16_t ha_cover_opening = 217;
|
||||
constexpr uint16_t ha_cover_closing = 218;
|
||||
constexpr uint16_t ha_media_playing = 219;
|
||||
constexpr uint16_t ha_media_paused = 220;
|
||||
constexpr uint16_t ha_media_idle = 221;
|
||||
constexpr uint16_t ha_media_standby = 222;
|
||||
constexpr uint16_t ha_person_home = 223;
|
||||
constexpr uint16_t ha_person_not_home = 224;
|
||||
constexpr uint16_t ha_sun_above_horizon = 225;
|
||||
constexpr uint16_t ha_sun_below_horizon = 226;
|
||||
constexpr uint16_t ha_vacuum_docked = 227;
|
||||
constexpr uint16_t ha_vacuum_cleaning = 228;
|
||||
constexpr uint16_t ha_vacuum_paused = 229;
|
||||
constexpr uint16_t ha_vacuum_returning = 230;
|
||||
constexpr uint16_t ha_vacuum_idle = 231;
|
||||
constexpr uint16_t ha_weather_sunny = 232;
|
||||
constexpr uint16_t ha_weather_clear_night = 233;
|
||||
constexpr uint16_t ha_weather_cloudy = 234;
|
||||
constexpr uint16_t ha_weather_partlycloudy = 235;
|
||||
constexpr uint16_t ha_weather_rainy = 236;
|
||||
constexpr uint16_t ha_weather_pouring = 237;
|
||||
constexpr uint16_t ha_weather_snowy = 238;
|
||||
constexpr uint16_t ha_weather_snowy_rainy = 239;
|
||||
constexpr uint16_t ha_weather_fog = 240;
|
||||
constexpr uint16_t ha_weather_hail = 241;
|
||||
constexpr uint16_t ha_weather_lightning = 242;
|
||||
constexpr uint16_t ha_weather_lightning_rainy = 243;
|
||||
constexpr uint16_t ha_weather_windy = 244;
|
||||
constexpr uint16_t ha_weather_exceptional = 245;
|
||||
constexpr uint16_t ha_binary_battery_on = 246;
|
||||
constexpr uint16_t ha_binary_battery_off = 247;
|
||||
constexpr uint16_t ha_binary_battery_charging_on = 248;
|
||||
constexpr uint16_t ha_binary_battery_charging_off = 249;
|
||||
constexpr uint16_t ha_binary_carbon_monoxide_on = 250;
|
||||
constexpr uint16_t ha_binary_carbon_monoxide_off = 251;
|
||||
constexpr uint16_t ha_binary_cold_on = 252;
|
||||
constexpr uint16_t ha_binary_cold_off = 253;
|
||||
constexpr uint16_t ha_binary_connectivity_on = 254;
|
||||
constexpr uint16_t ha_binary_connectivity_off = 255;
|
||||
constexpr uint16_t ha_binary_door_on = 256;
|
||||
constexpr uint16_t ha_binary_door_off = 257;
|
||||
constexpr uint16_t ha_binary_garage_door_on = 258;
|
||||
constexpr uint16_t ha_binary_garage_door_off = 259;
|
||||
constexpr uint16_t ha_binary_gas_on = 260;
|
||||
constexpr uint16_t ha_binary_gas_off = 261;
|
||||
constexpr uint16_t ha_binary_heat_on = 262;
|
||||
constexpr uint16_t ha_binary_heat_off = 263;
|
||||
constexpr uint16_t ha_binary_light_on = 264;
|
||||
constexpr uint16_t ha_binary_light_off = 265;
|
||||
constexpr uint16_t ha_binary_lock_on = 266;
|
||||
constexpr uint16_t ha_binary_lock_off = 267;
|
||||
constexpr uint16_t ha_binary_moisture_on = 268;
|
||||
constexpr uint16_t ha_binary_moisture_off = 269;
|
||||
constexpr uint16_t ha_binary_motion_on = 270;
|
||||
constexpr uint16_t ha_binary_motion_off = 271;
|
||||
constexpr uint16_t ha_binary_moving_on = 272;
|
||||
constexpr uint16_t ha_binary_moving_off = 273;
|
||||
constexpr uint16_t ha_binary_occupancy_on = 274;
|
||||
constexpr uint16_t ha_binary_occupancy_off = 275;
|
||||
constexpr uint16_t ha_binary_opening_on = 276;
|
||||
constexpr uint16_t ha_binary_opening_off = 277;
|
||||
constexpr uint16_t ha_binary_plug_on = 278;
|
||||
constexpr uint16_t ha_binary_plug_off = 279;
|
||||
constexpr uint16_t ha_binary_power_on = 280;
|
||||
constexpr uint16_t ha_binary_power_off = 281;
|
||||
constexpr uint16_t ha_binary_presence_on = 282;
|
||||
constexpr uint16_t ha_binary_presence_off = 283;
|
||||
constexpr uint16_t ha_binary_problem_on = 284;
|
||||
constexpr uint16_t ha_binary_problem_off = 285;
|
||||
constexpr uint16_t ha_binary_running_on = 286;
|
||||
constexpr uint16_t ha_binary_running_off = 287;
|
||||
constexpr uint16_t ha_binary_safety_on = 288;
|
||||
constexpr uint16_t ha_binary_safety_off = 289;
|
||||
constexpr uint16_t ha_binary_smoke_on = 290;
|
||||
constexpr uint16_t ha_binary_smoke_off = 291;
|
||||
constexpr uint16_t ha_binary_sound_on = 292;
|
||||
constexpr uint16_t ha_binary_sound_off = 293;
|
||||
constexpr uint16_t ha_binary_tamper_on = 294;
|
||||
constexpr uint16_t ha_binary_tamper_off = 295;
|
||||
constexpr uint16_t ha_binary_update_on = 296;
|
||||
constexpr uint16_t ha_binary_update_off = 297;
|
||||
constexpr uint16_t ha_binary_vibration_on = 298;
|
||||
constexpr uint16_t ha_binary_vibration_off = 299;
|
||||
constexpr uint16_t ha_binary_window_on = 300;
|
||||
constexpr uint16_t ha_binary_window_off = 301;
|
||||
constexpr uint16_t ha_button_activate = 302;
|
||||
constexpr uint16_t ha_button_run = 303;
|
||||
constexpr uint16_t ha_button_press = 304;
|
||||
constexpr uint16_t date_weekdays = 17;
|
||||
constexpr uint16_t number_percent = 3;
|
||||
constexpr uint16_t time_just_now = 4;
|
||||
constexpr uint16_t time_minutes_ago = 5;
|
||||
constexpr uint16_t time_hours_ago = 6;
|
||||
constexpr uint16_t time_yesterday = 7;
|
||||
constexpr uint16_t time_days_ago = 8;
|
||||
constexpr uint16_t time_weeks_ago = 9;
|
||||
constexpr uint16_t time_months_ago = 10;
|
||||
constexpr uint16_t time_years_ago = 11;
|
||||
constexpr uint16_t time_in_minutes = 12;
|
||||
constexpr uint16_t time_in_hours = 13;
|
||||
constexpr uint16_t time_tomorrow = 14;
|
||||
constexpr uint16_t time_in_days = 15;
|
||||
constexpr uint16_t time_am = 16;
|
||||
constexpr uint16_t time_pm = 17;
|
||||
constexpr uint16_t date_top_bar = 63;
|
||||
constexpr uint16_t date_full = 64;
|
||||
constexpr uint16_t date_day_month = 65;
|
||||
constexpr uint16_t date_weekday_time = 66;
|
||||
constexpr uint16_t duration_seconds = 67;
|
||||
constexpr uint16_t duration_minutes = 68;
|
||||
constexpr uint16_t duration_hours = 69;
|
||||
constexpr uint16_t duration_hours_minutes = 70;
|
||||
constexpr uint16_t duration_days = 71;
|
||||
constexpr uint16_t duration_days_hours = 72;
|
||||
constexpr uint16_t status_connecting = 73;
|
||||
constexpr uint16_t status_waiting = 74;
|
||||
constexpr uint16_t status_loading_tiles = 75;
|
||||
constexpr uint16_t status_ha_not_connected = 76;
|
||||
constexpr uint16_t status_manager_not_active = 77;
|
||||
constexpr uint16_t status_choose_tiles = 78;
|
||||
constexpr uint16_t status_home = 79;
|
||||
constexpr uint16_t status_no_network = 80;
|
||||
constexpr uint16_t settings_title = 81;
|
||||
constexpr uint16_t settings_brightness = 82;
|
||||
constexpr uint16_t settings_night = 83;
|
||||
constexpr uint16_t settings_screen = 84;
|
||||
constexpr uint16_t settings_this_screen = 85;
|
||||
constexpr uint16_t settings_dark_mode = 86;
|
||||
constexpr uint16_t settings_auto_standby = 87;
|
||||
constexpr uint16_t settings_standby_after = 88;
|
||||
constexpr uint16_t settings_standby_brightness = 89;
|
||||
constexpr uint16_t settings_night_mode = 90;
|
||||
constexpr uint16_t settings_starts = 91;
|
||||
constexpr uint16_t settings_ends = 92;
|
||||
constexpr uint16_t settings_night_brightness = 93;
|
||||
constexpr uint16_t settings_back_to_page_1 = 94;
|
||||
constexpr uint16_t settings_after = 95;
|
||||
constexpr uint16_t settings_also_on_standby = 96;
|
||||
constexpr uint16_t settings_swipe_between_pages = 97;
|
||||
constexpr uint16_t settings_page_buttons = 98;
|
||||
constexpr uint16_t settings_rotation = 99;
|
||||
constexpr uint16_t settings_address = 100;
|
||||
constexpr uint16_t settings_firmware = 101;
|
||||
constexpr uint16_t settings_home_assistant = 102;
|
||||
constexpr uint16_t settings_restart = 103;
|
||||
constexpr uint16_t settings_connected = 104;
|
||||
constexpr uint16_t settings_not_connected = 105;
|
||||
constexpr uint16_t settings_tap_again_to_restart = 106;
|
||||
constexpr uint16_t settings_seconds = 107;
|
||||
constexpr uint16_t settings_minutes = 108;
|
||||
constexpr uint16_t settings_hours = 109;
|
||||
constexpr uint16_t settings_hours_minutes = 110;
|
||||
constexpr uint16_t tile_tap_to_open = 111;
|
||||
constexpr uint16_t tile_page = 112;
|
||||
constexpr uint16_t tile_refused = 113;
|
||||
constexpr uint16_t tile_command_sent = 114;
|
||||
constexpr uint16_t tile_confirmed = 115;
|
||||
constexpr uint16_t alert_notification = 116;
|
||||
constexpr uint16_t alert_ok = 117;
|
||||
constexpr uint16_t light_color = 118;
|
||||
constexpr uint16_t light_color_temperature = 119;
|
||||
constexpr uint16_t light_brightness = 120;
|
||||
constexpr uint16_t light_waiting = 121;
|
||||
constexpr uint16_t effects_effect = 122;
|
||||
constexpr uint16_t effects_nothing = 123;
|
||||
constexpr uint16_t effects_loading = 124;
|
||||
constexpr uint16_t effects_not_connected = 125;
|
||||
constexpr uint16_t climate_target = 126;
|
||||
constexpr uint16_t climate_mode = 127;
|
||||
constexpr uint16_t climate_fan = 128;
|
||||
constexpr uint16_t climate_swing = 129;
|
||||
constexpr uint16_t climate_now = 130;
|
||||
constexpr uint16_t vacuum_check_robot = 131;
|
||||
constexpr uint16_t vacuum_auto_suction = 132;
|
||||
constexpr uint16_t vacuum_robot_chooses = 133;
|
||||
constexpr uint16_t vacuum_per_room = 134;
|
||||
constexpr uint16_t vacuum_speed_quiet = 135;
|
||||
constexpr uint16_t vacuum_speed_balanced = 136;
|
||||
constexpr uint16_t vacuum_speed_turbo = 137;
|
||||
constexpr uint16_t vacuum_speed_max = 138;
|
||||
constexpr uint16_t vacuum_pause_cleaning = 139;
|
||||
constexpr uint16_t vacuum_pause = 140;
|
||||
constexpr uint16_t vacuum_resume = 141;
|
||||
constexpr uint16_t vacuum_start_cleaning = 142;
|
||||
constexpr uint16_t vacuum_clean = 143;
|
||||
constexpr uint16_t vacuum_dock = 144;
|
||||
constexpr uint16_t cover_position = 145;
|
||||
constexpr uint16_t cover_tilt = 146;
|
||||
constexpr uint16_t cover_tilt_value = 147;
|
||||
constexpr uint16_t weather_feels_like = 148;
|
||||
constexpr uint16_t weather_no_forecast = 149;
|
||||
constexpr uint16_t weather_coming_days = 150;
|
||||
constexpr uint16_t history_no_data = 151;
|
||||
constexpr uint16_t history_average = 152;
|
||||
constexpr uint16_t history_average_of = 153;
|
||||
constexpr uint16_t history_now = 154;
|
||||
constexpr uint16_t history_range_hour = 155;
|
||||
constexpr uint16_t history_range_day = 156;
|
||||
constexpr uint16_t history_range_week = 157;
|
||||
constexpr uint16_t history_high = 158;
|
||||
constexpr uint16_t history_low = 159;
|
||||
constexpr uint16_t history_times = 160;
|
||||
constexpr uint16_t history_loading = 161;
|
||||
constexpr uint16_t history_empty = 162;
|
||||
constexpr uint16_t history_unavailable = 163;
|
||||
constexpr uint16_t media_muted = 164;
|
||||
constexpr uint16_t media_not_playing = 165;
|
||||
constexpr uint16_t media_loading = 166;
|
||||
constexpr uint16_t timer_running = 167;
|
||||
constexpr uint16_t timer_paused = 168;
|
||||
constexpr uint16_t timer_stopped = 169;
|
||||
constexpr uint16_t timer_pause = 170;
|
||||
constexpr uint16_t timer_start = 171;
|
||||
constexpr uint16_t timer_cancel = 172;
|
||||
constexpr uint16_t timer_paused_left = 173;
|
||||
constexpr uint16_t sun_name = 174;
|
||||
constexpr uint16_t sun_sunrise = 175;
|
||||
constexpr uint16_t sun_sunset = 176;
|
||||
constexpr uint16_t sun_rise = 177;
|
||||
constexpr uint16_t sun_set = 178;
|
||||
constexpr uint16_t script_running = 179;
|
||||
constexpr uint16_t script_never_run = 180;
|
||||
constexpr uint16_t script_last_time = 181;
|
||||
constexpr uint16_t script_yesterday_time = 182;
|
||||
constexpr uint16_t script_last_date = 183;
|
||||
constexpr uint16_t camera_no_image = 184;
|
||||
constexpr uint16_t camera_loading = 185;
|
||||
constexpr uint16_t camera_live = 186;
|
||||
constexpr uint16_t camera_recording = 187;
|
||||
constexpr uint16_t camera_tap_to_view = 188;
|
||||
constexpr uint16_t camera_no_image_yet = 189;
|
||||
constexpr uint16_t calibration_title = 190;
|
||||
constexpr uint16_t calibration_touch_check = 191;
|
||||
constexpr uint16_t calibration_hold = 192;
|
||||
constexpr uint16_t calibration_crosshair = 193;
|
||||
constexpr uint16_t calibration_gently = 194;
|
||||
constexpr uint16_t calibration_same = 195;
|
||||
constexpr uint16_t calibration_rejected = 196;
|
||||
constexpr uint16_t calibration_save_failed = 197;
|
||||
constexpr uint16_t ha_on = 198;
|
||||
constexpr uint16_t ha_off = 199;
|
||||
constexpr uint16_t ha_unavailable = 200;
|
||||
constexpr uint16_t ha_climate_off = 201;
|
||||
constexpr uint16_t ha_climate_heat = 202;
|
||||
constexpr uint16_t ha_climate_cool = 203;
|
||||
constexpr uint16_t ha_climate_heat_cool = 204;
|
||||
constexpr uint16_t ha_climate_auto = 205;
|
||||
constexpr uint16_t ha_climate_dry = 206;
|
||||
constexpr uint16_t ha_climate_fan_only = 207;
|
||||
constexpr uint16_t ha_hvac_action_heating = 208;
|
||||
constexpr uint16_t ha_hvac_action_cooling = 209;
|
||||
constexpr uint16_t ha_hvac_action_idle = 210;
|
||||
constexpr uint16_t ha_hvac_action_off = 211;
|
||||
constexpr uint16_t ha_hvac_action_drying = 212;
|
||||
constexpr uint16_t ha_hvac_action_fan = 213;
|
||||
constexpr uint16_t ha_hvac_action_preheating = 214;
|
||||
constexpr uint16_t ha_hvac_action_defrosting = 215;
|
||||
constexpr uint16_t ha_cover_open = 216;
|
||||
constexpr uint16_t ha_cover_closed = 217;
|
||||
constexpr uint16_t ha_cover_opening = 218;
|
||||
constexpr uint16_t ha_cover_closing = 219;
|
||||
constexpr uint16_t ha_media_playing = 220;
|
||||
constexpr uint16_t ha_media_paused = 221;
|
||||
constexpr uint16_t ha_media_idle = 222;
|
||||
constexpr uint16_t ha_media_standby = 223;
|
||||
constexpr uint16_t ha_person_home = 224;
|
||||
constexpr uint16_t ha_person_not_home = 225;
|
||||
constexpr uint16_t ha_sun_above_horizon = 226;
|
||||
constexpr uint16_t ha_sun_below_horizon = 227;
|
||||
constexpr uint16_t ha_vacuum_docked = 228;
|
||||
constexpr uint16_t ha_vacuum_cleaning = 229;
|
||||
constexpr uint16_t ha_vacuum_paused = 230;
|
||||
constexpr uint16_t ha_vacuum_returning = 231;
|
||||
constexpr uint16_t ha_vacuum_idle = 232;
|
||||
constexpr uint16_t ha_weather_sunny = 233;
|
||||
constexpr uint16_t ha_weather_clear_night = 234;
|
||||
constexpr uint16_t ha_weather_cloudy = 235;
|
||||
constexpr uint16_t ha_weather_partlycloudy = 236;
|
||||
constexpr uint16_t ha_weather_rainy = 237;
|
||||
constexpr uint16_t ha_weather_pouring = 238;
|
||||
constexpr uint16_t ha_weather_snowy = 239;
|
||||
constexpr uint16_t ha_weather_snowy_rainy = 240;
|
||||
constexpr uint16_t ha_weather_fog = 241;
|
||||
constexpr uint16_t ha_weather_hail = 242;
|
||||
constexpr uint16_t ha_weather_lightning = 243;
|
||||
constexpr uint16_t ha_weather_lightning_rainy = 244;
|
||||
constexpr uint16_t ha_weather_windy = 245;
|
||||
constexpr uint16_t ha_weather_exceptional = 246;
|
||||
constexpr uint16_t ha_binary_battery_on = 247;
|
||||
constexpr uint16_t ha_binary_battery_off = 248;
|
||||
constexpr uint16_t ha_binary_battery_charging_on = 249;
|
||||
constexpr uint16_t ha_binary_battery_charging_off = 250;
|
||||
constexpr uint16_t ha_binary_carbon_monoxide_on = 251;
|
||||
constexpr uint16_t ha_binary_carbon_monoxide_off = 252;
|
||||
constexpr uint16_t ha_binary_cold_on = 253;
|
||||
constexpr uint16_t ha_binary_cold_off = 254;
|
||||
constexpr uint16_t ha_binary_connectivity_on = 255;
|
||||
constexpr uint16_t ha_binary_connectivity_off = 256;
|
||||
constexpr uint16_t ha_binary_door_on = 257;
|
||||
constexpr uint16_t ha_binary_door_off = 258;
|
||||
constexpr uint16_t ha_binary_garage_door_on = 259;
|
||||
constexpr uint16_t ha_binary_garage_door_off = 260;
|
||||
constexpr uint16_t ha_binary_gas_on = 261;
|
||||
constexpr uint16_t ha_binary_gas_off = 262;
|
||||
constexpr uint16_t ha_binary_heat_on = 263;
|
||||
constexpr uint16_t ha_binary_heat_off = 264;
|
||||
constexpr uint16_t ha_binary_light_on = 265;
|
||||
constexpr uint16_t ha_binary_light_off = 266;
|
||||
constexpr uint16_t ha_binary_lock_on = 267;
|
||||
constexpr uint16_t ha_binary_lock_off = 268;
|
||||
constexpr uint16_t ha_binary_moisture_on = 269;
|
||||
constexpr uint16_t ha_binary_moisture_off = 270;
|
||||
constexpr uint16_t ha_binary_motion_on = 271;
|
||||
constexpr uint16_t ha_binary_motion_off = 272;
|
||||
constexpr uint16_t ha_binary_moving_on = 273;
|
||||
constexpr uint16_t ha_binary_moving_off = 274;
|
||||
constexpr uint16_t ha_binary_occupancy_on = 275;
|
||||
constexpr uint16_t ha_binary_occupancy_off = 276;
|
||||
constexpr uint16_t ha_binary_opening_on = 277;
|
||||
constexpr uint16_t ha_binary_opening_off = 278;
|
||||
constexpr uint16_t ha_binary_plug_on = 279;
|
||||
constexpr uint16_t ha_binary_plug_off = 280;
|
||||
constexpr uint16_t ha_binary_power_on = 281;
|
||||
constexpr uint16_t ha_binary_power_off = 282;
|
||||
constexpr uint16_t ha_binary_presence_on = 283;
|
||||
constexpr uint16_t ha_binary_presence_off = 284;
|
||||
constexpr uint16_t ha_binary_problem_on = 285;
|
||||
constexpr uint16_t ha_binary_problem_off = 286;
|
||||
constexpr uint16_t ha_binary_running_on = 287;
|
||||
constexpr uint16_t ha_binary_running_off = 288;
|
||||
constexpr uint16_t ha_binary_safety_on = 289;
|
||||
constexpr uint16_t ha_binary_safety_off = 290;
|
||||
constexpr uint16_t ha_binary_smoke_on = 291;
|
||||
constexpr uint16_t ha_binary_smoke_off = 292;
|
||||
constexpr uint16_t ha_binary_sound_on = 293;
|
||||
constexpr uint16_t ha_binary_sound_off = 294;
|
||||
constexpr uint16_t ha_binary_tamper_on = 295;
|
||||
constexpr uint16_t ha_binary_tamper_off = 296;
|
||||
constexpr uint16_t ha_binary_update_on = 297;
|
||||
constexpr uint16_t ha_binary_update_off = 298;
|
||||
constexpr uint16_t ha_binary_vibration_on = 299;
|
||||
constexpr uint16_t ha_binary_vibration_off = 300;
|
||||
constexpr uint16_t ha_binary_window_on = 301;
|
||||
constexpr uint16_t ha_binary_window_off = 302;
|
||||
constexpr uint16_t ha_button_activate = 303;
|
||||
constexpr uint16_t ha_button_run = 304;
|
||||
constexpr uint16_t ha_button_press = 305;
|
||||
constexpr uint16_t date_weekdays = 18;
|
||||
constexpr uint16_t date_weekdays_count = 7;
|
||||
constexpr uint16_t date_weekdays_short = 24;
|
||||
constexpr uint16_t date_weekdays_short = 25;
|
||||
constexpr uint16_t date_weekdays_short_count = 7;
|
||||
constexpr uint16_t date_weekdays_min = 31;
|
||||
constexpr uint16_t date_weekdays_min = 32;
|
||||
constexpr uint16_t date_weekdays_min_count = 7;
|
||||
constexpr uint16_t date_months = 38;
|
||||
constexpr uint16_t date_months = 39;
|
||||
constexpr uint16_t date_months_count = 12;
|
||||
constexpr uint16_t date_months_short = 50;
|
||||
constexpr uint16_t date_months_short = 51;
|
||||
constexpr uint16_t date_months_short_count = 12;
|
||||
} // namespace txt
|
||||
constexpr uint16_t KEY_COUNT = 305;
|
||||
constexpr uint32_t KEYS_HASH = 0xC6B1A3EDu;
|
||||
constexpr uint16_t KEY_COUNT = 306;
|
||||
constexpr uint32_t KEYS_HASH = 0x5DBB4605u;
|
||||
} // namespace screen_text
|
||||
|
||||
@@ -190,21 +190,17 @@ inline std::string duration_text(int32_t seconds) {
|
||||
}
|
||||
// Minutes since midnight as the clock on this screen shows them.
|
||||
inline std::string moment_text(int32_t minutes, bool clock_24h) {
|
||||
char buffer[16];
|
||||
int hour = minutes / 60, minute = minutes % 60;
|
||||
if (clock_24h) snprintf(buffer, sizeof(buffer), "%02d:%02d", hour, minute);
|
||||
else snprintf(buffer, sizeof(buffer), "%d:%02d %s", hour % 12 ? hour % 12 : 12, minute,
|
||||
screen_text::tr(hour < 12 ? screen_text::txt::time_am : screen_text::txt::time_pm));
|
||||
return buffer;
|
||||
char buffer[8];
|
||||
snprintf(buffer, sizeof(buffer), "%02d:%02d", (int) (minutes / 60 % 24), (int) (minutes % 60));
|
||||
return screen_text::clock_text(buffer, clock_24h);
|
||||
}
|
||||
// What the right-hand side of a row says. A toggle draws a switch instead, its text is for the tests.
|
||||
inline std::string value_text(const Row &row) {
|
||||
switch (row.kind) {
|
||||
case Kind::toggle: return screen_text::tr(row.read && row.read() ? screen_text::txt::ha_on : screen_text::txt::ha_off);
|
||||
case Kind::number: {
|
||||
char buffer[24];
|
||||
snprintf(buffer, sizeof(buffer), "%d%s", row.read ? (int) row.read() : 0, row.unit);
|
||||
return buffer;
|
||||
int value = row.read ? (int) row.read() : 0;
|
||||
return strcmp(row.unit, "%") == 0 ? screen_text::percent(value) : std::to_string(value) + row.unit;
|
||||
}
|
||||
case Kind::duration: return duration_text(row.read ? row.read() : 0);
|
||||
case Kind::moment: return moment_text(row.read ? row.read() : 0, screen_settings::current.clock_24h != 0);
|
||||
|
||||
@@ -243,7 +243,7 @@ inline const char *binary_state_text(const std::string &device_class, bool on) {
|
||||
}
|
||||
// Status line beside a control panel: what Home Assistant shows under the name.
|
||||
inline std::string status_text(const Tile &t) {
|
||||
auto d = t.domain(); char b[48];
|
||||
auto d = t.domain();
|
||||
if (d == "climate") {
|
||||
std::string text = climate_action_text(t.extra().hvac_action);
|
||||
if (text.empty()) text = climate_mode_text(t.state);
|
||||
@@ -252,13 +252,13 @@ inline std::string status_text(const Tile &t) {
|
||||
}
|
||||
if (d == "cover") {
|
||||
std::string text = cover_state_text(t.state);
|
||||
if (std::isfinite(t.position)) { snprintf(b, sizeof(b), " · %d%%", (int) std::lround(t.position)); text += b; }
|
||||
if (std::isfinite(t.position)) text += " · " + screen_text::percent((int) std::lround(t.position));
|
||||
return text;
|
||||
}
|
||||
if (d == "media_player") {
|
||||
const std::string &title = t.extra().media_title;
|
||||
std::string text = (t.state == "playing" || t.state == "paused") && !title.empty() ? title : media_state_text(t.state);
|
||||
if (std::isfinite(t.volume) && (t.supported & feature::MEDIA_VOLUME_SET)) { snprintf(b, sizeof(b), " · %d%%", (int) std::lround(t.volume * 100)); text += b; }
|
||||
if (std::isfinite(t.volume) && (t.supported & feature::MEDIA_VOLUME_SET)) text += " · " + screen_text::percent((int) std::lround(t.volume * 100));
|
||||
return text;
|
||||
}
|
||||
return {};
|
||||
|
||||
Reference in New Issue
Block a user