Created
January 9, 2026 21:40
-
-
Save idcrook/51f27869a4ba4cd78d5cf2be8babe70e to your computer and use it in GitHub Desktop.
Home Assistant Weather Card and Dashboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| apexcharts_card_templates: | |
| tufte: | |
| apex_config: | |
| legend: | |
| show: false | |
| grid: | |
| show: false | |
| xaxis: | |
| axisBorder: | |
| show: false | |
| all_series_config: | |
| stroke_width: 1 | |
| temp_hum: | |
| config_templates: tufte | |
| header: | |
| show: true | |
| show_states: true | |
| colorize_states: true | |
| yaxis: | |
| - id: temp | |
| - id: hum | |
| opposite: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| type: custom:grid-layout | |
| path: weather | |
| title: Weather | |
| icon: mdi:weather-partly-cloudy | |
| layout: | |
| grid-template-columns: repeat(4, 1fr) | |
| grid-template-areas: |- | |
| "a b b b" | |
| "c b b b" | |
| "d e f g" | |
| place-content: stretch | |
| mediaquery: | |
| "(max-width: 1300px)": | |
| grid-template-columns: 1fr | |
| grid-template-areas: |- | |
| "a" | |
| "b" | |
| "c" | |
| "d" | |
| badges: [] | |
| cards: | |
| - show_current: true | |
| show_forecast: true | |
| type: weather-forecast | |
| entity: weather.pirateweather | |
| #forecast_type: twice_daily | |
| forecast_type: daily | |
| name: Forecast | |
| view_layout: | |
| grid-area: a | |
| - type: iframe | |
| url: >- | |
| https://embed.windy.com/embed.html?type=map&l... | |
| aspect_ratio: 50% | |
| view_layout: | |
| place-self: center stretch | |
| grid-area: b | |
| - type: custom:apexcharts-card | |
| config_templates: | |
| - tufte | |
| header: | |
| show: true | |
| title: Temps | |
| show_states: true | |
| colorize_states: true | |
| graph_span: 36h | |
| all_series_config: | |
| stroke_width: 1 | |
| series: | |
| - entity: sensor.official_weather_station_outdoor_temperature | |
| name: Temperature | |
| show: | |
| extremas: true | |
| - entity: sensor.official_weather_station_feel_likes | |
| name: Feels Like | |
| - entity: sensor.official_weather_station_dew_point | |
| name: Dewpoint | |
| view_layout: | |
| grid-area: c | |
| - type: custom:apexcharts-card | |
| config_templates: | |
| - tufte | |
| header: | |
| show: true | |
| title: Wind | |
| show_states: true | |
| colorize_states: true | |
| graph_span: 36h | |
| all_series_config: | |
| stroke_width: 1 | |
| yaxis: | |
| - id: speed | |
| - id: direction | |
| opposite: true | |
| min: 0 | |
| max: 360 | |
| decimals: 0 | |
| apex_config: | |
| tickAmount: 8 | |
| labels: | |
| formatter: > | |
| EVAL:function(val, index) { if (val == 0) { return "N"; } else | |
| if (val == 90) { return "E"; } else if (val == 180) { return | |
| "S"; } else if (val == 270) { return "W"; } else if (val == | |
| 360) {return "N"; } } | |
| series: | |
| - entity: sensor.official_weather_station_wind_speed | |
| name: Speed | |
| type: line | |
| yaxis_id: speed | |
| show: | |
| extremas: max | |
| group_by: | |
| func: avg | |
| - entity: sensor.official_weather_station_wind_gust | |
| name: Gust | |
| opacity: 0.2 | |
| type: area | |
| yaxis_id: speed | |
| show: | |
| extremas: max | |
| group_by: | |
| func: max | |
| - entity: sensor.official_weather_station_10min_avg_wind_direction | |
| name: Direction | |
| type: line | |
| yaxis_id: direction | |
| group_by: | |
| func: avg | |
| - type: custom:apexcharts-card | |
| config_templates: | |
| - tufte | |
| header: | |
| show: true | |
| title: Pressure and Humidity | |
| show_states: true | |
| colorize_states: true | |
| graph_span: 36h | |
| all_series_config: | |
| stroke_width: 1 | |
| show: | |
| extremas: true | |
| yaxis: | |
| - id: pressure | |
| decimals: 1 | |
| - id: humidity | |
| min: 0 | |
| max: 100 | |
| decimals: 0 | |
| opposite: true | |
| series: | |
| - entity: sensor.official_weather_station_relative_pressure | |
| name: Rel Pressure | |
| yaxis_id: pressure | |
| - entity: sensor.official_weather_station_outdoor_humidity | |
| name: Humidity | |
| yaxis_id: humidity | |
| - type: custom:apexcharts-card | |
| config_templates: | |
| - tufte | |
| header: | |
| show: true | |
| title: Solar and UVI | |
| show_states: true | |
| colorize_states: true | |
| graph_span: 1d | |
| all_series_config: | |
| stroke_width: 1 | |
| show: | |
| extremas: max | |
| yaxis: | |
| - id: radiation | |
| - id: lux | |
| - id: uvi | |
| opposite: true | |
| min: 0 | |
| max: 12 | |
| decimals: 0 | |
| series: | |
| - entity: sensor.official_weather_station_solar_irradiance | |
| name: Solar | |
| yaxis_id: radiation | |
| show: | |
| extremas: max | |
| - entity: sensor.official_weather_station_uv_index | |
| name: UVI | |
| yaxis_id: uvi | |
| show: | |
| extremas: max | |
| group_by: | |
| func: max | |
| #type: line | |
| opacity: 0.2 | |
| type: area | |
| - type: custom:apexcharts-card | |
| config_templates: | |
| - tufte | |
| header: | |
| show: true | |
| title: Rain | |
| show_states: true | |
| colorize_states: true | |
| graph_span: 1d | |
| all_series_config: | |
| stroke_width: 1 | |
| show: | |
| extremas: max | |
| yaxis: | |
| - id: rate | |
| min: 0 | |
| max: ~0.05 | |
| decimals: 2 | |
| - id: total | |
| min: 0 | |
| max: ~0.1 | |
| decimals: 2 | |
| opposite: true | |
| series: | |
| - entity: sensor.official_weather_station_piezo_rain_rate | |
| name: Rain Rate | |
| type: column | |
| stroke_width: 2 | |
| yaxis_id: rate | |
| color: rebeccapurple | |
| - entity: sensor.official_weather_station_daily_piezo_rainfall | |
| name: Daily Accum | |
| yaxis_id: total | |
| type: area | |
| opacity: 0.2 | |
| - type: custom:plotly-graph | |
| title: Windrose | |
| layout: | |
| legend: | |
| orientation: h | |
| margin: | |
| t: 25 | |
| polar: | |
| bgcolor: hsl(0% 0% 20%) | |
| barmode: stack | |
| bargap: 1em | |
| radialaxis: | |
| type: linear | |
| ticksuffix: '%' | |
| angle: 45 | |
| dtick: 4 | |
| color: hsl(0% 0% 80%) | |
| angularaxis: | |
| direction: clockwise | |
| color: hsl(0% 0% 50%) | |
| colorway: | |
| - '#1984c5' | |
| - '#22a7f0' | |
| - '#63bff0' | |
| - '#a7d5ed' | |
| - '#e2e2e2' | |
| - '#e1a692' | |
| - '#de6e56' | |
| - '#e14b31' | |
| - '#c23728' | |
| config: | |
| displaylogo: false | |
| hours_to_show: 24 | |
| raw_plotly_config: true | |
| an: |- | |
| $ex vars.theta = ( ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', | |
| 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] ) | |
| fn: |- | |
| $ex vars.windRose = (vars, minSpeed, maxSpeed) => { | |
| // Define the headings and degree ranges for the 16 cardinal headings | |
| const headings = [ | |
| { label: "N", min: 350.5, max: 13.0 }, | |
| { label: "NNE", min: 13.0, max: 35.5 }, | |
| { label: "NE", min: 35.5, max: 58.0 }, | |
| { label: "ENE", min: 58.0, max: 80.5 }, | |
| { label: "E", min: 80.5, max: 103.0 }, | |
| { label: "ESE", min: 103.0, max: 125.5 }, | |
| { label: "SE", min: 125.5, max: 148.0 }, | |
| { label: "SSE", min: 148.0, max: 170.5 }, | |
| { label: "S", min: 170.5, max: 193.0 }, | |
| { label: "SSW", min: 193.0, max: 215.5 }, | |
| { label: "SW", min: 215.5, max: 238.0 }, | |
| { label: "WSW", min: 238.0, max: 260.5 }, | |
| { label: "W", min: 260.5, max: 283.0 }, | |
| { label: "WNW", min: 283.0, max: 305.5 }, | |
| { label: "NW", min: 305.5, max: 328.0 }, | |
| { label: "NNW", min: 328.0, max: 350.5 } | |
| ]; | |
| // Initialize headingsCount for each heading | |
| let headingsCount = headings.map(heading => 0); | |
| // console.log("headingsCount Initial", headingsCount ); | |
| const observationCount = vars.windDirections.length; | |
| // Count wind readings for each heading | |
| // console.log("directions", vars.windDirections); | |
| for (let i = 0; i < observationCount; i++) { | |
| const direction = vars.windDirections[i]; | |
| const speed = vars.windSpeeds[i]; | |
| if ( (minSpeed != 0 || maxSpeed != 0) && (speed > minSpeed && speed <= maxSpeed) ) { | |
| // Find the corresponding heading | |
| const headingFound = headings.find(seg => { | |
| if (seg.min < seg.max) { | |
| return direction >= seg.min && direction <= seg.max; | |
| } else if ( seg.min > seg.max ) { | |
| return direction >= seg.min || direction <= seg.max; | |
| } else { | |
| // console.log("heading not found", i); | |
| return false; | |
| } | |
| }); | |
| // Increment counter for the heading | |
| headingsCount[headings.indexOf(headingFound)]++; | |
| } else if (minSpeed == 0 && maxSpeed == 0 && speed == 0) { | |
| headingsCount.forEach((_, j) => headingsCount[j]++); // increment each heading element to create a zeros "circle" at the center of the windrose plot | |
| } | |
| } | |
| // Calculate percentages for headings | |
| const percentages = headingsCount.map(count => (count / observationCount) * 100); | |
| // console.log( "windSpeeds", vars.windSpeeds ); | |
| // console.log( "HeadingsCount", headingsCount ); | |
| // console.log( "Percentages", percentages ); | |
| return ( percentages ); | |
| } | |
| defaults: | |
| entity: | |
| hovertemplate: '%{theta} %{r:.2f}%' | |
| entities: | |
| - entity: sensor.official_weather_station_wind_direction | |
| internal: true | |
| filters: | |
| - resample: 5m | |
| - map_y: parseFloat(y) | |
| dn: $fn ({ ys, vars }) => { vars.windDirections = ys } | |
| - entity: sensor.official_weather_station_wind_speed | |
| internal: true | |
| filters: | |
| - resample: 5m | |
| - map_y: parseFloat(y) | |
| sn: $fn ({ ys, vars }) => { vars.windSpeeds = ys } | |
| - entity: '' | |
| type: barpolar | |
| name: ≤5 MPH | |
| r: $ex vars.windRose( vars, 0, 5 ) | |
| theta: $ex vars.theta | |
| showlegend: $ex vars.windRose(vars, 0, 5).some((x) => x > 0) | |
| - entity: '' | |
| type: barpolar | |
| name: ≤10 MPH | |
| r: $ex vars.windRose( vars, 5, 10 ) | |
| theta: $ex vars.theta | |
| showlegend: $ex vars.windRose(vars, 5, 10).some((x) => x > 0) | |
| - entity: '' | |
| type: barpolar | |
| name: ≤20 MPH | |
| r: $ex vars.windRose( vars, 10, 20 ) | |
| theta: $ex vars.theta | |
| showlegend: $ex vars.windRose(vars, 10, 20).some((x) => x > 0) | |
| - entity: '' | |
| type: barpolar | |
| name: ≤30 MPH | |
| r: $ex vars.windRose( vars, 20, 30 ) | |
| theta: $ex vars.theta | |
| showlegend: $ex vars.windRose(vars, 20, 30).some((x) => x > 0) | |
| - entity: '' | |
| type: barpolar | |
| name: ≤40 MPH | |
| r: $ex vars.windRose( vars, 30, 40 ) | |
| theta: $ex vars.theta | |
| showlegend: $ex vars.windRose(vars, 30, 40).some((x) => x > 0) | |
| - entity: '' | |
| type: barpolar | |
| name: ≤50 MPH | |
| r: $ex vars.windRose( vars, 40, 50 ) | |
| theta: $ex vars.theta | |
| showlegend: $ex vars.windRose(vars, 40, 50).some((x) => x > 0) | |
| - entity: '' | |
| type: barpolar | |
| name: ≥50 MPH | |
| r: $ex vars.windRose( vars, 50, 1000 ) | |
| theta: $ex vars.theta | |
| showlegend: $ex vars.windRose(vars, 50, 1000).some((x) => x > 0) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| type: custom:apexcharts-card | |
| header: | |
| show: true | |
| title: Outdoor Temperature | |
| show_states: true | |
| colorize_states: true | |
| graph_span: 12h | |
| all_series_config: | |
| stroke_width: 2 | |
| group_by: | |
| func: avg | |
| apex_config: | |
| legend: | |
| show: false | |
| grid: | |
| show: true | |
| xaxis: | |
| axisBorder: | |
| show: false | |
| series: | |
| - entity: sensor.official_weather_station_outdoor_temperature | |
| name: Temperature | |
| show: | |
| extremas: true | |
| - entity: sensor.official_weather_station_feel_likes | |
| name: Feels Like | |
| show: | |
| extremas: true | |
| - entity: sensor.official_weather_station_dew_point | |
| name: Dewpoint |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment