mirror of
https://github.com/randybb/esphome-configs.git
synced 2026-10-14 16:35:12 +02:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
148c51c4df | ||
|
|
cc2ab4c467 | ||
|
|
504aa06d10 | ||
|
|
4b6544adfa | ||
|
|
6a51099789 | ||
|
|
369578a63a | ||
|
|
bdf49db1c2 | ||
|
|
349f699572 | ||
|
|
329830e553 | ||
|
|
d813717d2c |
No files matched your search
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1194" height="240" fill="none" viewBox="0 0 1194 240"><g clip-path="url(#clip0_2_63)"><path fill="#18BCF2" d="M240 219C240 227.24 233.24 234 225 234H15C6.76 234 0 227.24 0 219V129C0 120.76 4.78 109.22 10.6 103.4L109.4 4.61999C115.24 -1.22001 124.78 -1.22001 130.62 4.61999L229.4 103.4C235.24 109.24 240 120.76 240 129V219Z"/><path fill="#F2F4F9" d="M160 78.66H80C76.68 78.66 74 81.34 74 84.66V234H86V90.66H154V102.66H104C100.68 102.66 98 105.34 98 108.66V132.66C98 135.98 100.68 138.66 104 138.66H154V150.66H104C100.68 150.66 98 153.34 98 156.66V180.66C98 183.98 100.68 186.66 104 186.66H154V198.66H104C100.68 198.66 98 201.34 98 204.66C98 207.98 100.68 210.66 104 210.66H160C163.32 210.66 166 207.98 166 204.66V180.66C166 177.34 163.32 174.66 160 174.66H110V162.66H160C163.32 162.66 166 159.98 166 156.66V132.66C166 129.34 163.32 126.66 160 126.66H110V114.66H160C163.32 114.66 166 111.98 166 108.66V84.66C166 81.34 163.32 78.66 160 78.66Z"/><path fill="#F2F4F9" d="M423 167.61V190.11H336V49.95H423V72.63H359.82V108.91H416.26V130.01H359.82V167.61H423Z"/><path fill="#F2F4F9" d="M491.08 47.41C503.9 47.41 514.54 50.53 523.04 56.79C531.54 63.03 536.76 71.39 538.7 81.83L516.3 87.93C515.04 81.93 512.12 77.31 507.54 74.05C502.94 70.81 497.28 69.17 490.52 69.17C483.26 69.17 477.52 70.91 473.28 74.37C469.02 77.83 466.9 82.45 466.9 88.19C466.9 97.25 472.52 103.13 483.78 105.81L506.66 111.53C518.48 114.59 527.32 119.45 533.2 126.11C539.08 132.77 542.02 140.97 542.02 150.71C542.02 163.21 537.48 173.31 528.42 181.03C519.36 188.75 507.24 192.61 492.04 192.61C478.36 192.61 466.78 189.39 457.36 182.95C448.04 176.27 442.54 167.45 440.86 156.51L463.26 150.69C464.08 156.87 467.16 161.73 472.5 165.23C477.84 168.73 484.64 170.47 492.9 170.47C501.16 170.47 507.08 168.79 511.6 165.45C516.14 162.11 518.4 157.57 518.4 151.81C518.4 142.81 512.78 136.87 501.52 133.99L478.64 128.45C466.88 125.77 458.06 120.99 452.16 114.15C446.26 107.31 443.3 98.95 443.3 89.07C443.3 76.69 447.7 66.65 456.48 58.93C465.26 51.21 476.8 47.35 491.12 47.35L491.08 47.41Z"/><path fill="#F2F4F9" d="M666.1 95.61C666.1 109.37 661.92 120.49 653.58 128.99C645.24 137.49 634 141.75 619.88 141.75H588.38V190.13H564.56V49.95H620.06C634.3 49.95 645.54 54.07 653.76 62.27C661.98 70.49 666.08 81.59 666.08 95.59L666.1 95.61ZM641.16 95.05C641.16 87.99 638.96 82.25 634.56 77.85C630.16 73.45 623.92 71.25 615.86 71.25H588.4V120.85H615.5C623.74 120.85 630.1 118.51 634.54 113.81C638.98 109.13 641.2 102.87 641.2 95.05H641.16Z"/><path fill="#F2F4F9" d="M687.38 49.95H711.2V108.55L772.98 108.73V49.95H796.98V190.11H772.98V129.83L711.2 129.65V190.11H687.38V49.95Z"/><path fill="#F2F4F9" d="M868.6 92.79C883.1 92.79 894.92 97.45 904.04 106.75C913.16 116.07 917.72 128.03 917.72 142.65C917.72 157.27 913.16 169.15 904.04 178.47C894.92 187.79 883.1 192.43 868.6 192.43C854.1 192.43 842.1 187.77 832.98 178.47C823.86 169.15 819.3 157.23 819.3 142.65C819.3 128.07 823.86 115.97 832.98 106.69C842.1 97.41 853.98 92.77 868.6 92.77V92.79ZM868.6 172.01C876.34 172.01 882.64 169.27 887.5 163.81C892.34 158.35 894.76 151.23 894.76 142.49C894.76 133.75 892.34 126.67 887.5 121.25C882.66 115.85 876.36 113.15 868.6 113.15C860.84 113.15 854.26 115.85 849.38 121.25C844.5 126.65 842.06 133.73 842.06 142.49C842.06 151.25 844.5 158.35 849.38 163.81C854.26 169.27 860.66 172.01 868.6 172.01Z"/><path fill="#F2F4F9" d="M1081.6 129.83V190.11H1059.56V133.29C1059.56 126.73 1057.88 121.61 1054.5 117.91C1051.12 114.23 1046.54 112.37 1040.72 112.37C1034.54 112.37 1029.6 114.39 1025.9 118.41C1022.22 122.45 1020.36 128.09 1020.36 135.33V190.09H998.04V133.27C998.04 126.71 996.4 121.59 993.12 117.89C989.84 114.21 985.3 112.35 979.48 112.35C973.3 112.35 968.32 114.37 964.58 118.39C960.84 122.41 958.96 128.07 958.96 135.31V190.07H936.28V95.39H957.38L958.14 104.49C963.64 96.43 972.6 92.39 985.04 92.39C992.28 92.39 998.58 93.89 1003.88 96.89C1009.2 99.89 1013.22 104.27 1015.98 110.01C1018.3 104.51 1022.12 100.19 1027.46 97.07C1032.8 93.95 1039.16 92.39 1046.54 92.39C1057.28 92.39 1065.82 95.71 1072.14 102.33C1078.46 108.95 1081.6 118.11 1081.6 129.79V129.83Z"/><path fill="#F2F4F9" d="M1193.54 151.11H1120.98C1121.74 158.23 1124.42 163.67 1129.04 167.43C1133.66 171.19 1139.54 173.05 1146.66 173.05C1158.22 173.05 1166.32 168.23 1170.94 158.61L1189.88 166.11C1186.32 174.43 1180.7 180.93 1173.06 185.61C1165.4 190.29 1156.6 192.65 1146.66 192.65C1132.66 192.65 1121.3 188.07 1112.58 178.91C1103.86 169.75 1099.5 157.73 1099.5 142.87C1099.5 128.01 1103.9 115.91 1112.68 106.59C1121.46 97.27 1132.94 92.63 1147.14 92.63C1161.34 92.63 1172.3 97.23 1180.8 106.41C1189.3 115.59 1193.56 127.69 1193.56 142.69V151.13L1193.54 151.11ZM1121.36 135.17H1170.48C1170.04 127.49 1167.72 121.63 1163.5 117.59C1159.28 113.55 1153.6 111.55 1146.48 111.55C1139.36 111.55 1133.66 113.59 1129.24 117.69C1124.8 121.79 1122.18 127.61 1121.36 135.17Z"/></g><defs><clipPath id="clip0_2_63"><rect width="1193.56" height="240" fill="#fff"/></cliLine truncated
|
|
||||||
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_2" data-name="Layer 2" viewBox="0 0 681.98 201.98"><defs><style>.cls-1{fill:none;stroke:#1d2126;stroke-miterlimit:10;stroke-width:1.01px}.cls-2{fill:#1d2126}.cls-2,.cls-3,.cls-4{stroke-width:0}.cls-3{fill:#f2f4f9}.cls-4{fill:#18bcf2}</style></defs><g id="Layer_1-2" data-name="Layer 1"><g><g><rect width="680.97" height="200.97" x=".51" y=".51" class="cls-3" rx="40.48" ry="40.48"/><rect width="680.97" height="200.97" x=".51" y=".51" class="cls-1" rx="40.48" ry="40.48"/></g><g><path d="M236.13,43.11v26.53h-5.29v-14.5l-5.68,14.5h-5.08l-5.61-14.4v14.4h-5.12v-26.53h5.12l8.16,20.27,8.21-20.27h5.29Z" class="cls-4"/><path d="M259.54,63.84h-9.04l-2.02,5.8h-5.59l9.51-26.53h5.33l9.47,26.53h-5.7l-1.97-5.8ZM258,59.34l-2.95-8.7-2.99,8.7h5.94Z" class="cls-4"/><path d="M296.93,56.42c.02,1.89-.3,3.66-.98,5.31s-1.61,3.06-2.8,4.22-2.61,2.07-4.26,2.73-3.4.98-5.26.96h-9.65v-26.53h9.65c2.43-.04,4.66.52,6.71,1.68s3.67,2.76,4.86,4.82,1.78,4.33,1.74,6.81ZM291.25,56.42c0-2.46-.71-4.46-2.12-5.99s-3.27-2.29-5.58-2.29h-4.22v16.52h4.22c2.34,0,4.21-.75,5.61-2.26s2.09-3.5,2.09-5.99Z" class="cls-4"/><path d="M321.38,64.59v5.04h-16.7v-26.53h16.7v5.1h-11.34v5.66h10.07v4.75h-10.07v5.98h11.34Z" class="cls-4"/><path d="M348.35,48.17v6.1h9.58l-.04,4.82h-9.54v10.55h-5.36v-26.53h16.73l-.04,5.06h-11.34Z" class="cls-4"/><path d="M378.62,42.64c1.9,0,3.66.34,5.27,1.03s2.99,1.63,4.11,2.84,2,2.66,2.63,4.35.94,3.52.94,5.49c.02,1.95-.28,3.77-.91,5.48s-1.52,3.17-2.65,4.39-2.52,2.17-4.15,2.86-3.39,1.03-5.27,1c-2.5.04-4.74-.54-6.72-1.72s-3.52-2.82-4.6-4.92-1.6-4.45-1.56-7.05c-.02-1.95.28-3.77.91-5.48s1.51-3.18,2.65-4.39,2.51-2.17,4.13-2.87,3.37-1.03,5.23-1ZM378.65,64.82c2.24,0,4.01-.76,5.32-2.28s1.96-3.58,1.96-6.2-.65-4.71-1.95-6.22-3.08-2.27-5.33-2.27-4.03.76-5.33,2.27-1.95,3.59-1.95,6.22.65,4.71,1.95,6.21,3.08,2.26,5.33,2.26Z" class="cls-4"/><path d="M419.5,69.64h-6.01l-4.08-9.58h-4.89v9.58h-5.36v-26.53h10.21c3.28.01,5.76.74,7.44,2.19s2.51,3.55,2.51,6.3c0,1.79-.39,3.34-1.17,4.65s-1.89,2.28-3.33,2.91l4.68,10.48ZM413.63,51.6c0-1.15-.36-2.05-1.07-2.72s-1.78-.99-3.18-.99h-4.85v7.49h4.85c1.41,0,2.47-.34,3.18-1.01s1.07-1.6,1.07-2.77Z" class="cls-4"/></g><g><path d="M251.92,146.83v11.25h-43.5v-70.08h43.5v11.34h-31.59v18.14h28.22v10.55h-28.22v18.8h31.59Z" class="cls-2"/><path d="M285,86.74c6.41,0,11.73,1.56,15.98,4.69,4.25,3.12,6.86,7.3,7.83,12.52l-11.2,3.05c-.62-3-2.09-5.31-4.38-6.94-2.3-1.62-5.13-2.44-8.51-2.44-3.62,0-6.5.87-8.62,2.6s-3.19,4.04-3.19,6.91c0,4.53,2.81,7.47,8.44,8.81l11.44,2.86c5.91,1.53,10.33,3.96,13.27,7.29s4.41,7.43,4.41,12.3c0,6.25-2.27,11.3-6.8,15.16-4.53,3.86-10.59,5.79-18.19,5.79-6.84,0-12.62-1.61-17.34-4.83-4.66-3.34-7.41-7.75-8.25-13.22l11.2-2.91c.41,3.09,1.95,5.52,4.62,7.27,2.67,1.75,6.07,2.62,10.2,2.62s7.09-.84,9.35-2.51c2.27-1.67,3.4-3.94,3.4-6.82,0-4.5-2.81-7.47-8.44-8.91l-11.44-2.77c-5.88-1.34-10.29-3.73-13.24-7.15s-4.43-7.6-4.43-12.54c0-6.19,2.2-11.21,6.59-15.07,4.39-3.86,10.16-5.79,17.32-5.79Z" class="cls-2"/><path d="M371.55,110.83c0,6.88-2.09,12.44-6.26,16.69-4.17,4.25-9.79,6.38-16.85,6.38h-15.75v24.19h-11.91v-70.08h27.75c7.12,0,12.74,2.06,16.85,6.16,4.11,4.11,6.16,9.66,6.16,16.66ZM359.08,110.55c0-3.53-1.1-6.4-3.3-8.6s-5.32-3.3-9.35-3.3h-13.73v24.8h13.55c4.12,0,7.3-1.17,9.52-3.52s3.33-5.47,3.33-9.38Z" class="cls-2"/><path d="M381.23,88h11.91v29.3l30.89.09v-29.39h12v70.08h-12v-30.14l-30.89-.09v30.23h-11.91v-70.08Z" class="cls-2"/><path d="M470.88,109.43c7.25,0,13.16,2.33,17.72,6.98,4.56,4.66,6.84,10.64,6.84,17.95s-2.28,13.25-6.84,17.91c-4.56,4.66-10.47,6.98-17.72,6.98s-13.25-2.33-17.81-6.98c-4.56-4.66-6.84-10.62-6.84-17.91s2.28-13.34,6.84-17.98,10.5-6.96,17.81-6.96ZM470.88,149.04c3.88,0,7.02-1.37,9.45-4.1,2.42-2.73,3.63-6.29,3.63-10.66s-1.21-7.91-3.63-10.62c-2.42-2.7-5.57-4.05-9.45-4.05s-7.17,1.35-9.61,4.05c-2.44,2.7-3.66,6.24-3.66,10.62s1.22,7.93,3.66,10.66c2.44,2.73,5.64,4.1,9.61,4.1Z" class="cls-2"/><path d="M576.42,127.94v30.14h-11.02v-28.41c0-3.28-.84-5.84-2.53-7.69-1.69-1.84-3.98-2.77-6.89-2.77-3.09,0-5.56,1.01-7.41,3.02s-2.77,4.84-2.77,8.46v27.38h-11.16v-28.41c0-3.28-.82-5.84-2.46-7.69-1.64-1.84-3.91-2.77-6.82-2.77-3.09,0-5.58,1.01-7.45,3.02s-2.81,4.84-2.81,8.46v27.38h-11.34v-47.34h10.55l.38,4.55c2.75-4.03,7.23-6.05,13.45-6.05,3.62,0,6.77.75,9.42,2.25s4.67,3.69,6.05,6.56c1.16-2.75,3.07-4.91,5.74-6.47,2.67-1.56,5.85-2.34,9.54-2.34,5.38,0,9.64,1.66,12.8,4.97,3.16,3.31,4.73,7.89,4.73,13.73Z" class="cls-2"/><path d="M631.43,138.58h-36.28c.38,3.56,1.72,6.28,4.03,8.16s5.25,2.81,8.81,2.81c5.78,0,9.83-2.41,12.14-7.22l9.47,3.75c-1.78,4.16-4.59,7.41-8.41,9.75s-8.23,3.52-13.2,3.52c-7,0-12.68-2.29-17.04-6.87-4.36-4.58-6.54-10.59-6.54-18.02s2.2-13.48,6.59-18.14c4.39-4.66,10.13-6.98,17.23-6.98s12.58,2.3,16.83,6.89,6.38,10.64,6.38,18.14v4.22ZM595.34,130.61h24.56c-.22-3.84-1.38-6.77-3.49-8.79s-4.95-3.02-8.51-3.02-6.41,1.02-8.62,3.07c-2.22,2.05-3.53,4.96-3.94,8.74Z" class="cls-2"/></g><g><path d="M170.55,153.49c0,4.12-3.38,7.5-7.5,7.5H58.05c-4.12,0-7.5-3.38-7.5-7.5v-45c0-4.12,2.39-9.89,5.3-12Line truncated
|
||||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_2" data-name="Layer 2" viewBox="0 0 681.98 201.98"><defs><style>.cls-1{fill:#1d2126}.cls-1,.cls-2,.cls-3{stroke-width:0}.cls-2{fill:#f2f4f9}.cls-3{fill:#18bcf2}.cls-4{fill:none;stroke:#f2f4f9;stroke-miterlimit:10;stroke-width:1.01px}</style></defs><g id="Layer_1-2" data-name="Layer 1"><g><g><rect width="680.97" height="200.97" x=".51" y=".51" class="cls-1" rx="40.48" ry="40.48"/><rect width="680.97" height="200.97" x=".51" y=".51" class="cls-4" rx="40.48" ry="40.48"/></g><g><path d="M236.13,43.11v26.53h-5.29v-14.5l-5.68,14.5h-5.08l-5.61-14.4v14.4h-5.12v-26.53h5.12l8.16,20.27,8.21-20.27h5.29Z" class="cls-3"/><path d="M259.54,63.84h-9.04l-2.02,5.8h-5.59l9.51-26.53h5.33l9.47,26.53h-5.7l-1.97-5.8ZM258,59.34l-2.95-8.7-2.99,8.7h5.94Z" class="cls-3"/><path d="M296.93,56.42c.02,1.89-.3,3.66-.98,5.31s-1.61,3.06-2.8,4.22-2.61,2.07-4.26,2.73-3.4.98-5.26.96h-9.65v-26.53h9.65c2.43-.04,4.66.52,6.71,1.68s3.67,2.76,4.86,4.82,1.78,4.33,1.74,6.81ZM291.25,56.42c0-2.46-.71-4.46-2.12-5.99s-3.27-2.29-5.58-2.29h-4.22v16.52h4.22c2.34,0,4.21-.75,5.61-2.26s2.09-3.5,2.09-5.99Z" class="cls-3"/><path d="M321.38,64.59v5.04h-16.7v-26.53h16.7v5.1h-11.34v5.66h10.07v4.75h-10.07v5.98h11.34Z" class="cls-3"/><path d="M348.35,48.17v6.1h9.58l-.04,4.82h-9.54v10.55h-5.36v-26.53h16.73l-.04,5.06h-11.34Z" class="cls-3"/><path d="M378.62,42.64c1.9,0,3.66.34,5.27,1.03s2.99,1.63,4.11,2.84,2,2.66,2.63,4.35.94,3.52.94,5.49c.02,1.95-.28,3.77-.91,5.48s-1.52,3.17-2.65,4.39-2.52,2.17-4.15,2.86-3.39,1.03-5.27,1c-2.5.04-4.74-.54-6.72-1.72s-3.52-2.82-4.6-4.92-1.6-4.45-1.56-7.05c-.02-1.95.28-3.77.91-5.48s1.51-3.18,2.65-4.39,2.51-2.17,4.13-2.87,3.37-1.03,5.23-1ZM378.65,64.82c2.24,0,4.01-.76,5.32-2.28s1.96-3.58,1.96-6.2-.65-4.71-1.95-6.22-3.08-2.27-5.33-2.27-4.03.76-5.33,2.27-1.95,3.59-1.95,6.22.65,4.71,1.95,6.21,3.08,2.26,5.33,2.26Z" class="cls-3"/><path d="M419.5,69.64h-6.01l-4.08-9.58h-4.89v9.58h-5.36v-26.53h10.21c3.28.01,5.76.74,7.44,2.19s2.51,3.55,2.51,6.3c0,1.79-.39,3.34-1.17,4.65s-1.89,2.28-3.33,2.91l4.68,10.48ZM413.63,51.6c0-1.15-.36-2.05-1.07-2.72s-1.78-.99-3.18-.99h-4.85v7.49h4.85c1.41,0,2.47-.34,3.18-1.01s1.07-1.6,1.07-2.77Z" class="cls-3"/></g><g><path d="M251.92,146.83v11.25h-43.5v-70.08h43.5v11.34h-31.59v18.14h28.22v10.55h-28.22v18.8h31.59Z" class="cls-2"/><path d="M285,86.74c6.41,0,11.73,1.56,15.98,4.69,4.25,3.12,6.86,7.3,7.83,12.52l-11.2,3.05c-.62-3-2.09-5.31-4.38-6.94-2.3-1.62-5.13-2.44-8.51-2.44-3.62,0-6.5.87-8.62,2.6s-3.19,4.04-3.19,6.91c0,4.53,2.81,7.47,8.44,8.81l11.44,2.86c5.91,1.53,10.33,3.96,13.27,7.29s4.41,7.43,4.41,12.3c0,6.25-2.27,11.3-6.8,15.16-4.53,3.86-10.59,5.79-18.19,5.79-6.84,0-12.62-1.61-17.34-4.83-4.66-3.34-7.41-7.75-8.25-13.22l11.2-2.91c.41,3.09,1.95,5.52,4.62,7.27,2.67,1.75,6.07,2.62,10.2,2.62s7.09-.84,9.35-2.51c2.27-1.67,3.4-3.94,3.4-6.82,0-4.5-2.81-7.47-8.44-8.91l-11.44-2.77c-5.88-1.34-10.29-3.73-13.24-7.15s-4.43-7.6-4.43-12.54c0-6.19,2.2-11.21,6.59-15.07,4.39-3.86,10.16-5.79,17.32-5.79Z" class="cls-2"/><path d="M371.55,110.83c0,6.88-2.09,12.44-6.26,16.69-4.17,4.25-9.79,6.38-16.85,6.38h-15.75v24.19h-11.91v-70.08h27.75c7.12,0,12.74,2.06,16.85,6.16,4.11,4.11,6.16,9.66,6.16,16.66ZM359.08,110.55c0-3.53-1.1-6.4-3.3-8.6s-5.32-3.3-9.35-3.3h-13.73v24.8h13.55c4.12,0,7.3-1.17,9.52-3.52s3.33-5.47,3.33-9.38Z" class="cls-2"/><path d="M381.23,88h11.91v29.3l30.89.09v-29.39h12v70.08h-12v-30.14l-30.89-.09v30.23h-11.91v-70.08Z" class="cls-2"/><path d="M470.88,109.43c7.25,0,13.16,2.33,17.72,6.98,4.56,4.66,6.84,10.64,6.84,17.95s-2.28,13.25-6.84,17.91c-4.56,4.66-10.47,6.98-17.72,6.98s-13.25-2.33-17.81-6.98c-4.56-4.66-6.84-10.62-6.84-17.91s2.28-13.34,6.84-17.98,10.5-6.96,17.81-6.96ZM470.88,149.04c3.88,0,7.02-1.37,9.45-4.1,2.42-2.73,3.63-6.29,3.63-10.66s-1.21-7.91-3.63-10.62c-2.42-2.7-5.57-4.05-9.45-4.05s-7.17,1.35-9.61,4.05c-2.44,2.7-3.66,6.24-3.66,10.62s1.22,7.93,3.66,10.66c2.44,2.73,5.64,4.1,9.61,4.1Z" class="cls-2"/><path d="M576.42,127.94v30.14h-11.02v-28.41c0-3.28-.84-5.84-2.53-7.69-1.69-1.84-3.98-2.77-6.89-2.77-3.09,0-5.56,1.01-7.41,3.02s-2.77,4.84-2.77,8.46v27.38h-11.16v-28.41c0-3.28-.82-5.84-2.46-7.69-1.64-1.84-3.91-2.77-6.82-2.77-3.09,0-5.58,1.01-7.45,3.02s-2.81,4.84-2.81,8.46v27.38h-11.34v-47.34h10.55l.38,4.55c2.75-4.03,7.23-6.05,13.45-6.05,3.62,0,6.77.75,9.42,2.25s4.67,3.69,6.05,6.56c1.16-2.75,3.07-4.91,5.74-6.47,2.67-1.56,5.85-2.34,9.54-2.34,5.38,0,9.64,1.66,12.8,4.97,3.16,3.31,4.73,7.89,4.73,13.73Z" class="cls-2"/><path d="M631.43,138.58h-36.28c.38,3.56,1.72,6.28,4.03,8.16s5.25,2.81,8.81,2.81c5.78,0,9.83-2.41,12.14-7.22l9.47,3.75c-1.78,4.16-4.59,7.41-8.41,9.75s-8.23,3.52-13.2,3.52c-7,0-12.68-2.29-17.04-6.87-4.36-4.58-6.54-10.59-6.54-18.02s2.2-13.48,6.59-18.14c4.39-4.66,10.13-6.98,17.23-6.98s12.58,2.3,16.83,6.89,6.38,10.64,6.38,18.14v4.22ZM595.34,130.61h24.56c-.22-3.84-1.38-6.77-3.49-8.79s-4.95-3.02-8.51-3.02-6.41,1.02-8.62,3.07c-2.22,2.05-3.53,4.96-3.94,8.74Z" class="cls-2"/></g><g><path d="M170.55,153.49c0,4.12-3.38,7.5-7.5,7.5H58.05c-4.12,0-7.5-3.38-7.5-7.5v-45c0-4.12,2.39-9.89,5.3-12Line truncated
|
||||||
|
After Width: | Height: | Size: 5.3 KiB |
+2
-2
@@ -24,8 +24,8 @@ text_sensor:
|
|||||||
# - <<: !include text_sensor/uptime.yaml
|
# - <<: !include text_sensor/uptime.yaml
|
||||||
- <<: !include text_sensor/wifi_info.yaml
|
- <<: !include text_sensor/wifi_info.yaml
|
||||||
|
|
||||||
# time:
|
time:
|
||||||
#. - <<: !include time/homeassistant.yaml
|
- <<: !include time/homeassistant.yaml
|
||||||
|
|
||||||
network:
|
network:
|
||||||
enable_ipv6: true
|
enable_ipv6: true
|
||||||
@@ -24,5 +24,8 @@ text_sensor:
|
|||||||
- <<: !include text_sensor/version.yaml
|
- <<: !include text_sensor/version.yaml
|
||||||
- <<: !include text_sensor/wifi_info.yaml
|
- <<: !include text_sensor/wifi_info.yaml
|
||||||
|
|
||||||
|
time:
|
||||||
|
- <<: !include time/homeassistant.yaml
|
||||||
|
|
||||||
network:
|
network:
|
||||||
enable_ipv6: true
|
enable_ipv6: true
|
||||||
@@ -23,5 +23,8 @@ text_sensor:
|
|||||||
- <<: !include text_sensor/version.yaml
|
- <<: !include text_sensor/version.yaml
|
||||||
- <<: !include text_sensor/wifi_info.yaml
|
- <<: !include text_sensor/wifi_info.yaml
|
||||||
|
|
||||||
|
time:
|
||||||
|
- <<: !include time/homeassistant.yaml
|
||||||
|
|
||||||
network:
|
network:
|
||||||
enable_ipv6: true
|
enable_ipv6: true
|
||||||
@@ -21,5 +21,8 @@ text_sensor:
|
|||||||
- <<: !include text_sensor/version.yaml
|
- <<: !include text_sensor/version.yaml
|
||||||
- <<: !include text_sensor/ethernet_info.yaml
|
- <<: !include text_sensor/ethernet_info.yaml
|
||||||
|
|
||||||
|
time:
|
||||||
|
- <<: !include time/homeassistant.yaml
|
||||||
|
|
||||||
network:
|
network:
|
||||||
enable_ipv6: true
|
enable_ipv6: true
|
||||||
@@ -27,3 +27,6 @@ text_sensor:
|
|||||||
- <<: !include text_sensor/git_ref.yaml
|
- <<: !include text_sensor/git_ref.yaml
|
||||||
- <<: !include text_sensor/version.yaml
|
- <<: !include text_sensor/version.yaml
|
||||||
- <<: !include text_sensor/wifi_info.yaml
|
- <<: !include text_sensor/wifi_info.yaml
|
||||||
|
|
||||||
|
time:
|
||||||
|
- <<: !include time/homeassistant.yaml
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
# IPv6-only IoT network (SLAAC, no DHCPv4); add next to a common package:
|
||||||
|
# packages:
|
||||||
|
# common: !include common/common.yaml
|
||||||
|
# ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
external_components:
|
||||||
|
# wifi ipv6_only (esphome/esphome#19750), drop once merged
|
||||||
|
- source: github://pr#19750
|
||||||
|
components: [const, network, wifi]
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ipv6_only: true
|
||||||
|
networks:
|
||||||
|
- id: !extend wifi_iot
|
||||||
|
ssid: !secret wifi_ssid_ipv6
|
||||||
|
password: !secret wifi_password_ipv6
|
||||||
|
|
||||||
|
network:
|
||||||
|
min_ipv6_addr_count: 1
|
||||||
+3
-1
@@ -2,7 +2,9 @@
|
|||||||
wifi:
|
wifi:
|
||||||
# reboot_timeout: 0s
|
# reboot_timeout: 0s
|
||||||
networks:
|
networks:
|
||||||
- ssid: !secret wifi_ssid
|
# id lets devices override the network with '!extend wifi_iot'
|
||||||
|
- id: wifi_iot
|
||||||
|
ssid: !secret wifi_ssid
|
||||||
password: !secret wifi_password
|
password: !secret wifi_password
|
||||||
enable_btm: true
|
enable_btm: true
|
||||||
enable_rrm: true
|
enable_rrm: true
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
#pragma once
|
||||||
|
// Hourly weather forecast for mcu-m5paper: parsed from the weather.get_forecasts
|
||||||
|
// response and kept in the on-board FM24C02 EEPROM (256 B, I2C 0x50), so it
|
||||||
|
// survives the power-off between RTC wake-ups.
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "esphome/components/i2c/i2c_bus.h"
|
||||||
|
#include "esphome/components/json/json_util.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace m5paper_weather {
|
||||||
|
|
||||||
|
static constexpr uint8_t EEPROM_ADDR = 0x50;
|
||||||
|
static constexpr uint8_t EEPROM_PAGE = 8;
|
||||||
|
static constexpr uint8_t MAGIC = 0xA5;
|
||||||
|
static constexpr uint8_t VERSION = 1;
|
||||||
|
static constexpr uint8_t MAX_HOURS = 48;
|
||||||
|
// magic, version, fetched_at (u32), first_ts (u32), count
|
||||||
|
static constexpr size_t HEADER = 11;
|
||||||
|
static constexpr size_t MAX_SIZE = HEADER + MAX_HOURS * 4;
|
||||||
|
static_assert(MAX_SIZE <= 256, "forecast does not fit into the EEPROM");
|
||||||
|
|
||||||
|
static constexpr uint8_t NO_VALUE = 0xFF;
|
||||||
|
static constexpr int8_t NO_TEMP = INT8_MIN;
|
||||||
|
|
||||||
|
struct Hour {
|
||||||
|
uint8_t cond; // index into CONDITIONS, NO_VALUE if unknown
|
||||||
|
int8_t temp2; // temperature in 0.5 °C, NO_TEMP if unknown
|
||||||
|
uint8_t wind; // km/h, NO_VALUE if unknown
|
||||||
|
uint8_t pop; // precipitation probability %, NO_VALUE if unknown
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Hour) == 4, "Hour must stay 4 bytes");
|
||||||
|
|
||||||
|
struct Forecast {
|
||||||
|
uint32_t fetched_at{0}; // UTC epoch of the download
|
||||||
|
uint32_t first_ts{0}; // UTC epoch of hours[0]; entries are 1 h apart
|
||||||
|
uint8_t count{0};
|
||||||
|
Hour hours[MAX_HOURS]{};
|
||||||
|
|
||||||
|
bool valid() const { return this->count > 0; }
|
||||||
|
// hour covering the given UTC epoch, nullptr if outside the forecast
|
||||||
|
const Hour *at(uint32_t ts) const {
|
||||||
|
if (!this->valid() || ts < this->first_ts)
|
||||||
|
return nullptr;
|
||||||
|
uint32_t idx = (ts - this->first_ts) / 3600;
|
||||||
|
return idx < this->count ? &this->hours[idx] : nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// the forecast in RAM, loaded from the EEPROM at boot
|
||||||
|
inline Forecast &forecast() {
|
||||||
|
static Forecast fc;
|
||||||
|
return fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Home Assistant weather conditions; the index is the stored condition code
|
||||||
|
static const char *const CONDITIONS[] = {
|
||||||
|
"clear-night", "cloudy", "exceptional", "fog", "hail", "lightning", "lightning-rainy", "partlycloudy",
|
||||||
|
"pouring", "rainy", "snowy", "snowy-rainy", "sunny", "windy", "windy-variant",
|
||||||
|
};
|
||||||
|
static const char *const CONDITION_NAMES[] = {
|
||||||
|
"Jasno", "Oblačno", "Extrémne počasie", "Hmla", "Krupobitie", "Búrka", "Búrka s dažďom", "Polooblačno",
|
||||||
|
"Lejak", "Dážď", "Sneženie", "Dážď so snehom", "Slnečno", "Veterno", "Veterno, oblačno",
|
||||||
|
};
|
||||||
|
static constexpr uint8_t NUM_CONDITIONS = sizeof(CONDITIONS) / sizeof(CONDITIONS[0]);
|
||||||
|
|
||||||
|
inline uint8_t condition_code(const char *name) {
|
||||||
|
for (uint8_t i = 0; i < NUM_CONDITIONS; i++) {
|
||||||
|
if (strcmp(name, CONDITIONS[i]) == 0)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return NO_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *condition_name(uint8_t code) { return code < NUM_CONDITIONS ? CONDITION_NAMES[code] : "?"; }
|
||||||
|
|
||||||
|
// days since 1970-01-01 for a proleptic Gregorian date (H. Hinnant's algorithm)
|
||||||
|
inline int32_t days_from_civil(int y, int m, int d) {
|
||||||
|
y -= m <= 2;
|
||||||
|
const int era = (y >= 0 ? y : y - 399) / 400;
|
||||||
|
const int yoe = y - era * 400;
|
||||||
|
const int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
||||||
|
const int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
||||||
|
return era * 146097 + doe - 719468;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "2026-09-27T16:00:00+00:00" -> UTC epoch, 0 on error
|
||||||
|
inline uint32_t parse_iso8601(const char *s) {
|
||||||
|
int y, mo, d, h, mi, sec = 0, oh = 0, om = 0;
|
||||||
|
char sign = '+';
|
||||||
|
int n = sscanf(s, "%d-%d-%dT%d:%d:%d%c%d:%d", &y, &mo, &d, &h, &mi, &sec, &sign, &oh, &om);
|
||||||
|
if (n < 5)
|
||||||
|
return 0;
|
||||||
|
int64_t ts = int64_t(days_from_civil(y, mo, d)) * 86400 + h * 3600 + mi * 60 + sec;
|
||||||
|
if (n >= 8 && (sign == '+' || sign == '-')) {
|
||||||
|
int64_t offset = oh * 3600 + om * 60;
|
||||||
|
ts += sign == '+' ? -offset : offset;
|
||||||
|
}
|
||||||
|
return ts > 0 ? uint32_t(ts) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the weather.get_forecasts response for the given entity;
|
||||||
|
// Home Assistant wraps the action response as {"response": {...}}
|
||||||
|
inline bool parse(ArduinoJson::JsonObjectConst response, const char *entity, uint32_t now, Forecast &fc) {
|
||||||
|
ArduinoJson::JsonObjectConst root = response["response"].is<ArduinoJson::JsonObjectConst>()
|
||||||
|
? response["response"].as<ArduinoJson::JsonObjectConst>()
|
||||||
|
: response;
|
||||||
|
ArduinoJson::JsonArrayConst items = root[entity]["forecast"];
|
||||||
|
if (items.isNull() || items.size() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Forecast out;
|
||||||
|
for (ArduinoJson::JsonObjectConst item : items) {
|
||||||
|
if (out.count >= MAX_HOURS)
|
||||||
|
break;
|
||||||
|
if (out.count == 0) {
|
||||||
|
out.first_ts = parse_iso8601(item["datetime"] | "");
|
||||||
|
if (out.first_ts == 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Hour &hour = out.hours[out.count++];
|
||||||
|
hour.cond = condition_code(item["condition"] | "");
|
||||||
|
float temp = item["temperature"] | NAN;
|
||||||
|
hour.temp2 = std::isnan(temp) ? NO_TEMP : int8_t(std::max(-127L, std::min(127L, lroundf(temp * 2))));
|
||||||
|
float wind = item["wind_speed"] | NAN;
|
||||||
|
hour.wind = std::isnan(wind) ? NO_VALUE : uint8_t(std::max(0L, std::min(254L, lroundf(wind))));
|
||||||
|
float pop = item["precipitation_probability"] | NAN;
|
||||||
|
hour.pop = std::isnan(pop) ? NO_VALUE : uint8_t(std::max(0L, std::min(100L, lroundf(pop))));
|
||||||
|
}
|
||||||
|
out.fetched_at = now;
|
||||||
|
fc = out;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool load(esphome::i2c::I2CBus *bus, Forecast &fc) {
|
||||||
|
uint8_t buf[MAX_SIZE];
|
||||||
|
const uint8_t start = 0;
|
||||||
|
fc.count = 0;
|
||||||
|
if (bus->write_readv(EEPROM_ADDR, &start, 1, buf, sizeof(buf)) != esphome::i2c::ERROR_OK)
|
||||||
|
return false;
|
||||||
|
if (buf[0] != MAGIC || buf[1] != VERSION)
|
||||||
|
return false;
|
||||||
|
memcpy(&fc.fetched_at, buf + 2, 4);
|
||||||
|
memcpy(&fc.first_ts, buf + 6, 4);
|
||||||
|
fc.count = std::min(buf[10], MAX_HOURS);
|
||||||
|
memcpy(fc.hours, buf + HEADER, fc.count * sizeof(Hour));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool save(esphome::i2c::I2CBus *bus, const Forecast &fc) {
|
||||||
|
uint8_t buf[MAX_SIZE];
|
||||||
|
buf[0] = MAGIC;
|
||||||
|
buf[1] = VERSION;
|
||||||
|
memcpy(buf + 2, &fc.fetched_at, 4);
|
||||||
|
memcpy(buf + 6, &fc.first_ts, 4);
|
||||||
|
buf[10] = fc.count;
|
||||||
|
memcpy(buf + HEADER, fc.hours, fc.count * sizeof(Hour));
|
||||||
|
const size_t len = HEADER + fc.count * sizeof(Hour);
|
||||||
|
|
||||||
|
// page writes must not cross an 8-byte page boundary; 5 ms write cycle per page
|
||||||
|
for (size_t off = 0; off < len; off += EEPROM_PAGE) {
|
||||||
|
uint8_t page[EEPROM_PAGE + 1];
|
||||||
|
const size_t n = std::min<size_t>(EEPROM_PAGE, len - off);
|
||||||
|
page[0] = uint8_t(off);
|
||||||
|
memcpy(page + 1, buf + off, n);
|
||||||
|
if (bus->write(EEPROM_ADDR, page, n + 1) != esphome::i2c::ERROR_OK)
|
||||||
|
return false;
|
||||||
|
esphome::delay(6);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace m5paper_weather
|
||||||
+1
-2
@@ -491,8 +491,7 @@ sensor:
|
|||||||
# name: Dallas Temp 4
|
# name: Dallas Temp 4
|
||||||
# one_wire_id: hub_1
|
# one_wire_id: hub_1
|
||||||
|
|
||||||
time:
|
# time:
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
# - platform: sntp
|
# - platform: sntp
|
||||||
# id: sntp_time
|
# id: sntp_time
|
||||||
# timezone: Europe/Bratislava
|
# timezone: Europe/Bratislava
|
||||||
|
|||||||
+1
-2
@@ -139,8 +139,7 @@ switch:
|
|||||||
id: relay_2
|
id: relay_2
|
||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: homeassistant
|
- id: !extend ha_time
|
||||||
timezone: Europe/Bratislava
|
|
||||||
on_time:
|
on_time:
|
||||||
- seconds: 0
|
- seconds: 0
|
||||||
minutes: /15
|
minutes: /15
|
||||||
|
|||||||
@@ -31,11 +31,6 @@ packages:
|
|||||||
|
|
||||||
debug:
|
debug:
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
# - id: i2c0
|
# - id: i2c0
|
||||||
sda: 32
|
sda: 32
|
||||||
|
|||||||
+1
-5
@@ -23,6 +23,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# GPIO21 UART TX
|
# GPIO21 UART TX
|
||||||
# GPIO20 UART RX
|
# GPIO20 UART RX
|
||||||
@@ -109,11 +110,6 @@ globals:
|
|||||||
restore_value: no
|
restore_value: no
|
||||||
initial_value: "false"
|
initial_value: "false"
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
display:
|
display:
|
||||||
- platform: tm1637
|
- platform: tm1637
|
||||||
id: tm1637_display
|
id: tm1637_display
|
||||||
|
|||||||
@@ -51,11 +51,6 @@ globals:
|
|||||||
restore_value: no
|
restore_value: no
|
||||||
initial_value: "false"
|
initial_value: "false"
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: Button
|
name: Button
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# Mi Desk Lamp
|
# Mi Desk Lamp
|
||||||
# Pin Function (Name)
|
# Pin Function (Name)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# fan:
|
# fan:
|
||||||
# - platform: speed
|
# - platform: speed
|
||||||
|
|||||||
@@ -242,11 +242,6 @@ binary_sensor:
|
|||||||
# on_clockwise:
|
# on_clockwise:
|
||||||
# - display_menu.down:
|
# - display_menu.down:
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
# display:
|
# display:
|
||||||
# - platform: tm1637
|
# - platform: tm1637
|
||||||
# id: tm1637_display
|
# id: tm1637_display
|
||||||
|
|||||||
+1
-5
@@ -27,6 +27,7 @@ external_components:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
|
|
||||||
# PINOUTS: M5ATOM+Atom Hub Switch
|
# PINOUTS: M5ATOM+Atom Hub Switch
|
||||||
@@ -44,11 +45,6 @@ packages:
|
|||||||
# Relay1 G22
|
# Relay1 G22
|
||||||
# Relay2 G19
|
# Relay2 G19
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
# - id: i2c0
|
# - id: i2c0
|
||||||
# sda: 26
|
# sda: 26
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# PINOUTS: M5ATOM
|
# PINOUTS: M5ATOM
|
||||||
# IR G12
|
# IR G12
|
||||||
|
|||||||
+2
-3
@@ -37,6 +37,7 @@ external_components:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# PINOUTS: M5ATOM
|
# PINOUTS: M5ATOM
|
||||||
# IR G12
|
# IR G12
|
||||||
@@ -129,9 +130,7 @@ globals:
|
|||||||
initial_value: '20.5'
|
initial_value: '20.5'
|
||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: homeassistant
|
- id: !extend ha_time
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
on_time:
|
on_time:
|
||||||
- seconds: 0
|
- seconds: 0
|
||||||
minutes: /1
|
minutes: /1
|
||||||
|
|||||||
+1
-5
@@ -36,6 +36,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
api:
|
api:
|
||||||
reboot_timeout: 300s
|
reboot_timeout: 300s
|
||||||
@@ -55,11 +56,6 @@ api:
|
|||||||
# Relay1 G22
|
# Relay1 G22
|
||||||
# Relay2 G19
|
# Relay2 G19
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
# - id: i2c0
|
# - id: i2c0
|
||||||
# sda: 26
|
# sda: 26
|
||||||
|
|||||||
@@ -0,0 +1,306 @@
|
|||||||
|
---
|
||||||
|
substitutions:
|
||||||
|
device: lvgl-4848s040
|
||||||
|
name: LVGL 4848S040
|
||||||
|
area: DEV
|
||||||
|
comment: "${area} | Guition ESP32-S3-4848S040"
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
name: mcu-${device}
|
||||||
|
friendly_name: ${name}
|
||||||
|
area: ${area}
|
||||||
|
comment: ${comment}
|
||||||
|
on_boot:
|
||||||
|
- priority: -100
|
||||||
|
then:
|
||||||
|
- lvgl.page.show: status_page
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
variant: esp32s3
|
||||||
|
flash_size: 16MB
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
# PSRAM bandwidth for the RGB panel (not set by ESPHome); without it the
|
||||||
|
# image shifts during flash/WiFi activity
|
||||||
|
sdkconfig_options:
|
||||||
|
CONFIG_ESP32S3_DATA_CACHE_64KB: y
|
||||||
|
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
|
||||||
|
CONFIG_SPIRAM_RODATA: y
|
||||||
|
|
||||||
|
psram:
|
||||||
|
mode: octal
|
||||||
|
speed: 80MHz
|
||||||
|
|
||||||
|
packages:
|
||||||
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
|
# PINOUTS: Guition ESP32-S3-4848S040, 4", 480x480, ST7701S, capacitive
|
||||||
|
## Display (RGB, pins in the mipi_rgb model)
|
||||||
|
# CS 39
|
||||||
|
# SCK 48 ST7701S init (3-wire SPI)
|
||||||
|
# SDA 47
|
||||||
|
# DE 18
|
||||||
|
# HSYNC 16
|
||||||
|
# VSYNC 17
|
||||||
|
# PCLK 21
|
||||||
|
# BL 38
|
||||||
|
## Capacitive Touch (GT911)
|
||||||
|
# TP_SDA 19
|
||||||
|
# TP_SCL 45
|
||||||
|
## Relays (86 box variant)
|
||||||
|
# RELAY1 40
|
||||||
|
# RELAY2 2
|
||||||
|
# RELAY3 1
|
||||||
|
|
||||||
|
api:
|
||||||
|
# status page until Home Assistant connects (log viewers don't subscribe to states)
|
||||||
|
on_client_connected:
|
||||||
|
- delay: 2s
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- lvgl.page.show: main_page
|
||||||
|
on_client_disconnected:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
not:
|
||||||
|
api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- lvgl.page.show: status_page
|
||||||
|
|
||||||
|
i2c:
|
||||||
|
- id: i2c_main
|
||||||
|
sda: 19
|
||||||
|
scl: 45
|
||||||
|
scan: true
|
||||||
|
|
||||||
|
spi:
|
||||||
|
- id: lcd_spi
|
||||||
|
clk_pin: 48
|
||||||
|
mosi_pin: 47
|
||||||
|
|
||||||
|
display:
|
||||||
|
- platform: mipi_rgb
|
||||||
|
model: GUITION-4848S040
|
||||||
|
id: main_display
|
||||||
|
spi_id: lcd_spi
|
||||||
|
update_interval: never
|
||||||
|
auto_clear_enabled: false
|
||||||
|
|
||||||
|
touchscreen:
|
||||||
|
- platform: gt911
|
||||||
|
id: main_touch
|
||||||
|
display: main_display
|
||||||
|
i2c_id: i2c_main
|
||||||
|
# the mipi_rgb model mirrors the image on both axes, touch has to follow
|
||||||
|
transform:
|
||||||
|
mirror_x: true
|
||||||
|
mirror_y: true
|
||||||
|
|
||||||
|
output:
|
||||||
|
- platform: ledc
|
||||||
|
id: gpio_backlight_pwm
|
||||||
|
pin: 38
|
||||||
|
frequency: 1220
|
||||||
|
|
||||||
|
light:
|
||||||
|
- platform: monochromatic
|
||||||
|
id: back_light
|
||||||
|
name: Backlight
|
||||||
|
output: gpio_backlight_pwm
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
|
|
||||||
|
script:
|
||||||
|
- id: update_status
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
// same colors as the other status screens: ok green, waiting orange, details gray
|
||||||
|
static const uint32_t OK = 0x4DD966, WAIT = 0xFFB300;
|
||||||
|
auto set = [](lv_obj_t *label, const std::string &text, uint32_t color) {
|
||||||
|
lv_label_set_text(label, text.c_str());
|
||||||
|
lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool wifi_ok = wifi::global_wifi_component->is_connected();
|
||||||
|
set(id(status_wifi),
|
||||||
|
wifi_ok ? str_sprintf("WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi())
|
||||||
|
: std::string("WiFi: connecting..."),
|
||||||
|
wifi_ok ? OK : WAIT);
|
||||||
|
|
||||||
|
// IPv6 first (skip link-local), then IPv4
|
||||||
|
std::string ips;
|
||||||
|
if (wifi_ok) {
|
||||||
|
char buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||||
|
for (bool v6 : {true, false}) {
|
||||||
|
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
|
||||||
|
if (!ip.is_set() || ip.is_ip6() != v6)
|
||||||
|
continue;
|
||||||
|
ip.str_to(buf);
|
||||||
|
if (v6 && strncmp(buf, "fe80", 4) == 0)
|
||||||
|
continue;
|
||||||
|
if (!ips.empty())
|
||||||
|
ips += "\n";
|
||||||
|
ips += buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lv_label_set_text(id(status_ips), ips.c_str());
|
||||||
|
if (ips.empty())
|
||||||
|
lv_obj_add_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
|
||||||
|
else
|
||||||
|
lv_obj_remove_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
|
||||||
|
|
||||||
|
const bool api_ok = api::global_api_server->is_connected();
|
||||||
|
set(id(status_ha), api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...",
|
||||||
|
api_ok ? OK : WAIT);
|
||||||
|
|
||||||
|
const uint32_t up = millis() / 1000;
|
||||||
|
lv_label_set_text(id(status_footer),
|
||||||
|
str_sprintf("ESPHome %s · up %ud %02u:%02u:%02u", ESPHOME_VERSION, up / 86400,
|
||||||
|
(up / 3600) % 24, (up / 60) % 60, up % 60).c_str());
|
||||||
|
|
||||||
|
interval:
|
||||||
|
- interval: 1s
|
||||||
|
then:
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: clock_label
|
||||||
|
text: !lambda |-
|
||||||
|
auto now = id(ha_time).now();
|
||||||
|
if (!now.is_valid())
|
||||||
|
return std::string("--:--");
|
||||||
|
return now.strftime("%H:%M");
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lvgl.page.is_showing: status_page
|
||||||
|
then:
|
||||||
|
- script.execute: update_status
|
||||||
|
|
||||||
|
image:
|
||||||
|
- platform: file
|
||||||
|
file: assets/images/made-for-esphome-white-on-black.svg
|
||||||
|
id: esphome_logo
|
||||||
|
resize: 320x95
|
||||||
|
type: RGB565
|
||||||
|
transparency: alpha_channel
|
||||||
|
|
||||||
|
font:
|
||||||
|
- file: "gfonts://Roboto@500"
|
||||||
|
id: font_clock
|
||||||
|
size: 150
|
||||||
|
glyphs: "0123456789:-"
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_title
|
||||||
|
size: 32
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_status
|
||||||
|
size: 20
|
||||||
|
glyphs: "·"
|
||||||
|
glyphsets: [GF_Latin_Kernel]
|
||||||
|
|
||||||
|
lvgl:
|
||||||
|
log_level: WARN
|
||||||
|
color_depth: 16
|
||||||
|
bg_color: 0x000000
|
||||||
|
text_color: 0xFFFFFF
|
||||||
|
theme:
|
||||||
|
buttonmatrix:
|
||||||
|
bg_opa: TRANSP
|
||||||
|
border_width: 0
|
||||||
|
items:
|
||||||
|
bg_color: 0x2B2F36
|
||||||
|
text_color: 0xFFFFFF
|
||||||
|
radius: 12
|
||||||
|
checked:
|
||||||
|
bg_color: 0xE08A00
|
||||||
|
top_layer:
|
||||||
|
widgets:
|
||||||
|
# same navigation bar as on mcu-lvgl-trainer
|
||||||
|
- buttonmatrix:
|
||||||
|
id: nav_bar
|
||||||
|
align: bottom_mid
|
||||||
|
width: 100%
|
||||||
|
height: 60
|
||||||
|
pad_all: 4
|
||||||
|
pad_column: 6
|
||||||
|
items:
|
||||||
|
text_font: montserrat_28
|
||||||
|
rows:
|
||||||
|
- buttons:
|
||||||
|
- id: page_prev
|
||||||
|
text: ""
|
||||||
|
on_press:
|
||||||
|
then:
|
||||||
|
lvgl.page.previous:
|
||||||
|
- id: page_home
|
||||||
|
text: ""
|
||||||
|
on_press:
|
||||||
|
then:
|
||||||
|
lvgl.page.show: main_page
|
||||||
|
- id: page_next
|
||||||
|
text: ""
|
||||||
|
on_press:
|
||||||
|
then:
|
||||||
|
lvgl.page.next:
|
||||||
|
pages:
|
||||||
|
# 1: clock
|
||||||
|
- id: main_page
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
id: clock_label
|
||||||
|
align: CENTER
|
||||||
|
y: -30
|
||||||
|
text_font: font_clock
|
||||||
|
text: "--:--"
|
||||||
|
|
||||||
|
# 2: status (shown until Home Assistant connects)
|
||||||
|
- id: status_page
|
||||||
|
on_load:
|
||||||
|
- script.execute: update_status
|
||||||
|
widgets:
|
||||||
|
- image:
|
||||||
|
src: esphome_logo
|
||||||
|
align: TOP_MID
|
||||||
|
y: 16
|
||||||
|
- label:
|
||||||
|
align: TOP_MID
|
||||||
|
y: 120
|
||||||
|
text_font: font_title
|
||||||
|
text: "${name}"
|
||||||
|
- obj:
|
||||||
|
id: status_lines
|
||||||
|
x: 24
|
||||||
|
y: 170
|
||||||
|
width: 440
|
||||||
|
height: SIZE_CONTENT
|
||||||
|
bg_opa: TRANSP
|
||||||
|
border_width: 0
|
||||||
|
pad_all: 0
|
||||||
|
scrollbar_mode: "OFF"
|
||||||
|
layout:
|
||||||
|
type: flex
|
||||||
|
flex_flow: column
|
||||||
|
pad_row: 6
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
id: status_wifi
|
||||||
|
text_font: font_status
|
||||||
|
text: ""
|
||||||
|
- label:
|
||||||
|
id: status_ips
|
||||||
|
text_font: font_status
|
||||||
|
text_color: 0x8C8C8C
|
||||||
|
pad_left: 20
|
||||||
|
text: ""
|
||||||
|
- label:
|
||||||
|
id: status_ha
|
||||||
|
text_font: font_status
|
||||||
|
text: ""
|
||||||
|
- label:
|
||||||
|
id: status_footer
|
||||||
|
text_font: font_status
|
||||||
|
text_color: 0x8C8C8C
|
||||||
|
text: ""
|
||||||
+601
-140
@@ -4,6 +4,13 @@ substitutions:
|
|||||||
name: Trainer Display
|
name: Trainer Display
|
||||||
area: Basement
|
area: Basement
|
||||||
comment: "${area} | Pain Cave"
|
comment: "${area} | Pain Cave"
|
||||||
|
light_basement: light.mcu_basement_2_light
|
||||||
|
light_workbench: light.mcu_basement_2_workbench_light
|
||||||
|
light_exercise: light.exercise_room
|
||||||
|
fan: light.mcu_dimmer_exercise_room_fan
|
||||||
|
|
||||||
|
# Pages (480x320, navigation bar at the bottom):
|
||||||
|
# clock, lights, training, fan, stopwatch, status
|
||||||
|
|
||||||
esphome:
|
esphome:
|
||||||
name: mcu-${device}
|
name: mcu-${device}
|
||||||
@@ -17,9 +24,9 @@ esp32:
|
|||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
|
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
- id: touchscreen_bus
|
- id: touchscreen_bus
|
||||||
@@ -36,71 +43,17 @@ spi:
|
|||||||
clk_pin: 47
|
clk_pin: 47
|
||||||
data_pins: [21, 48, 40, 39]
|
data_pins: [21, 48, 40, 39]
|
||||||
|
|
||||||
color:
|
|
||||||
- id: my_red
|
|
||||||
red: 100%
|
|
||||||
green: 0%
|
|
||||||
blue: 0%
|
|
||||||
- id: my_orange
|
|
||||||
red: 100%
|
|
||||||
green: 50%
|
|
||||||
blue: 0%
|
|
||||||
- id: my_yellow
|
|
||||||
red: 100%
|
|
||||||
green: 100%
|
|
||||||
blue: 0%
|
|
||||||
- id: my_green
|
|
||||||
red: 0%
|
|
||||||
green: 100%
|
|
||||||
blue: 0%
|
|
||||||
- id: my_blue
|
|
||||||
red: 0%
|
|
||||||
green: 0%
|
|
||||||
blue: 100%
|
|
||||||
- id: my_teal
|
|
||||||
red: 0%
|
|
||||||
green: 100%
|
|
||||||
blue: 100%
|
|
||||||
- id: my_gray
|
|
||||||
red: 70%
|
|
||||||
green: 70%
|
|
||||||
blue: 70%
|
|
||||||
- id: my_white
|
|
||||||
red: 100%
|
|
||||||
green: 100%
|
|
||||||
blue: 100%
|
|
||||||
|
|
||||||
font:
|
|
||||||
- file: "gfonts://Roboto"
|
|
||||||
id: Roboto_48
|
|
||||||
size: 48
|
|
||||||
- file: "gfonts://Roboto"
|
|
||||||
id: Roboto_36
|
|
||||||
size: 36
|
|
||||||
- file: "gfonts://Roboto"
|
|
||||||
id: Roboto_24
|
|
||||||
size: 24
|
|
||||||
- file: "gfonts://Roboto"
|
|
||||||
id: Roboto_12
|
|
||||||
size: 12
|
|
||||||
- file: "gfonts://Roboto"
|
|
||||||
id: font_std
|
|
||||||
size: 40
|
|
||||||
glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/\\[]|&@#'"
|
|
||||||
- file: "gfonts://Roboto@700"
|
|
||||||
id: font_title
|
|
||||||
size: 40
|
|
||||||
glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/\\[]|&@#'"
|
|
||||||
|
|
||||||
globals:
|
globals:
|
||||||
- id: bgcolor
|
# stopwatch: accumulated time of finished runs + start of the current run
|
||||||
type: Color
|
- id: sw_running
|
||||||
initial_value: "Color::random_color()"
|
type: bool
|
||||||
|
initial_value: 'false'
|
||||||
# power_supply:
|
- id: sw_start
|
||||||
# id: main_backlight
|
type: uint32_t
|
||||||
# pin: 1
|
initial_value: '0'
|
||||||
# enable_on_boot: true
|
- id: sw_accum
|
||||||
|
type: uint32_t
|
||||||
|
initial_value: '0'
|
||||||
|
|
||||||
output:
|
output:
|
||||||
- platform: ledc
|
- platform: ledc
|
||||||
@@ -119,7 +72,6 @@ display:
|
|||||||
- platform: mipi_spi
|
- platform: mipi_spi
|
||||||
model: AXS15231
|
model: AXS15231
|
||||||
data_rate: 40MHz #20mhz is default
|
data_rate: 40MHz #20mhz is default
|
||||||
#spi_mode: MODE2
|
|
||||||
id: main_display
|
id: main_display
|
||||||
spi_id: display_qspi
|
spi_id: display_qspi
|
||||||
dimensions:
|
dimensions:
|
||||||
@@ -127,11 +79,6 @@ display:
|
|||||||
width: 320
|
width: 320
|
||||||
cs_pin: 45
|
cs_pin: 45
|
||||||
auto_clear_enabled: false
|
auto_clear_enabled: false
|
||||||
# show_test_card: true
|
|
||||||
# lambda: |-
|
|
||||||
# it.fill(id(bgcolor));
|
|
||||||
# it.print(it.get_width()/2, it.get_height()/2-20, id(font_title), TextAlign::CENTER, "ESPHome");
|
|
||||||
# it.print(it.get_width()/2, it.get_height()/2+20, id(font_std), TextAlign::CENTER, "@UTBDK");
|
|
||||||
|
|
||||||
touchscreen:
|
touchscreen:
|
||||||
- platform: axs15231
|
- platform: axs15231
|
||||||
@@ -142,27 +89,8 @@ touchscreen:
|
|||||||
# interrupt_pin:
|
# interrupt_pin:
|
||||||
# number: 3
|
# number: 3
|
||||||
# ignore_strapping_warning: true
|
# ignore_strapping_warning: true
|
||||||
calibration:
|
# no calibration/transform: the driver defaults to the native 320x480 panel and
|
||||||
x_min: 0
|
# LVGL applies its own rotation to touch points
|
||||||
x_max: 479
|
|
||||||
y_min: 0
|
|
||||||
y_max: 319
|
|
||||||
transform:
|
|
||||||
swap_xy: true
|
|
||||||
mirror_x: false
|
|
||||||
mirror_y: true
|
|
||||||
# on_touch:
|
|
||||||
# - lambda: |-
|
|
||||||
# Color newColor;
|
|
||||||
# do { newColor = Color::random_color(); } while (newColor == id(bgcolor));
|
|
||||||
# id(bgcolor) = newColor;
|
|
||||||
|
|
||||||
# ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
|
|
||||||
# touch.x,
|
|
||||||
# touch.y,
|
|
||||||
# touch.x_raw,
|
|
||||||
# touch.y_raw
|
|
||||||
# );
|
|
||||||
on_release:
|
on_release:
|
||||||
- if:
|
- if:
|
||||||
condition: lvgl.is_paused
|
condition: lvgl.is_paused
|
||||||
@@ -183,107 +111,640 @@ uart:
|
|||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: exercise_room_light
|
id: basement_light
|
||||||
entity_id: light.exercise_room
|
entity_id: ${light_basement}
|
||||||
trigger_on_initial_state: true
|
trigger_on_initial_state: true
|
||||||
on_state:
|
on_state:
|
||||||
then:
|
- lvgl.widget.update:
|
||||||
lvgl.widget.update:
|
id: basement_light_btn
|
||||||
id: exercise_room_light_btn
|
|
||||||
state:
|
state:
|
||||||
checked: !lambda return x;
|
checked: !lambda return x;
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: exercise_room_fan
|
id: workbench_light
|
||||||
entity_id: light.mcu_dimmer_exercise_room_fan
|
entity_id: ${light_workbench}
|
||||||
trigger_on_initial_state: true
|
trigger_on_initial_state: true
|
||||||
on_state:
|
on_state:
|
||||||
then:
|
- lvgl.widget.update:
|
||||||
lvgl.widget.update:
|
id: workbench_light_btn
|
||||||
id: exercise_room_fan_btn
|
|
||||||
state:
|
state:
|
||||||
checked: !lambda return x;
|
checked: !lambda return x;
|
||||||
|
- platform: homeassistant
|
||||||
|
id: exercise_light
|
||||||
|
entity_id: ${light_exercise}
|
||||||
|
trigger_on_initial_state: true
|
||||||
|
on_state:
|
||||||
|
- lvgl.widget.update:
|
||||||
|
id: exercise_light_btn
|
||||||
|
state:
|
||||||
|
checked: !lambda return x;
|
||||||
|
# brightness and color temperature only make sense while the light is on
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return x;
|
||||||
|
then:
|
||||||
|
- lvgl.widget.show: exercise_controls
|
||||||
|
else:
|
||||||
|
- lvgl.widget.hide: exercise_controls
|
||||||
|
- platform: homeassistant
|
||||||
|
id: fan_on
|
||||||
|
entity_id: ${fan}
|
||||||
|
trigger_on_initial_state: true
|
||||||
|
on_state:
|
||||||
|
- script.execute: update_fan_buttons
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: homeassistant
|
||||||
|
id: fan_brightness
|
||||||
|
entity_id: ${fan}
|
||||||
|
attribute: brightness
|
||||||
|
on_value:
|
||||||
|
- script.execute: update_fan_buttons
|
||||||
|
|
||||||
|
text_sensor:
|
||||||
|
# light attributes read as text: they are 'None' while the light is off
|
||||||
|
- platform: homeassistant
|
||||||
|
id: exercise_brightness
|
||||||
|
entity_id: ${light_exercise}
|
||||||
|
attribute: brightness
|
||||||
|
on_value:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return parse_number<float>(x).has_value();
|
||||||
|
then:
|
||||||
|
- lvgl.slider.update:
|
||||||
|
id: exercise_brightness_slider
|
||||||
|
value: !lambda return roundf(*parse_number<float>(x) * 100.0f / 255.0f);
|
||||||
|
- platform: homeassistant
|
||||||
|
id: exercise_color_temp
|
||||||
|
entity_id: ${light_exercise}
|
||||||
|
attribute: color_temp_kelvin
|
||||||
|
on_value:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return parse_number<float>(x).has_value();
|
||||||
|
then:
|
||||||
|
- lvgl.slider.update:
|
||||||
|
id: exercise_color_temp_slider
|
||||||
|
value: !lambda return *parse_number<float>(x);
|
||||||
|
# a running automation (e.g. its delay) has current > 0
|
||||||
|
- platform: homeassistant
|
||||||
|
id: training_start_running
|
||||||
|
entity_id: automation.zwift_enable_fan
|
||||||
|
attribute: current
|
||||||
|
on_value:
|
||||||
|
- script.execute: update_training_status
|
||||||
|
- platform: homeassistant
|
||||||
|
id: training_stop_running
|
||||||
|
entity_id: automation.zwift_disable_fan
|
||||||
|
attribute: current
|
||||||
|
on_value:
|
||||||
|
- script.execute: update_training_status
|
||||||
|
|
||||||
|
script:
|
||||||
|
- id: update_fan_buttons
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
// fan level rounded to the 10 % steps of the buttons, 0 = off
|
||||||
|
int pct = 0;
|
||||||
|
if (id(fan_on).state && !std::isnan(id(fan_brightness).state))
|
||||||
|
pct = (int) roundf(id(fan_brightness).state * 10.0f / 255.0f) * 10;
|
||||||
|
const int levels[] = {0, 50, 60, 70, 80, 90, 100};
|
||||||
|
lv_obj_t *matrix = id(fan_matrix)->obj;
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
if (levels[i] == pct)
|
||||||
|
lv_buttonmatrix_set_button_ctrl(matrix, i, LV_BUTTONMATRIX_CTRL_CHECKED);
|
||||||
|
else
|
||||||
|
lv_buttonmatrix_clear_button_ctrl(matrix, i, LV_BUTTONMATRIX_CTRL_CHECKED);
|
||||||
|
}
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: fan_status_label
|
||||||
|
text: !lambda |-
|
||||||
|
if (!id(fan_on).state)
|
||||||
|
return std::string("Ventilátor vypnutý");
|
||||||
|
if (std::isnan(id(fan_brightness).state))
|
||||||
|
return std::string("Ventilátor zapnutý");
|
||||||
|
return str_sprintf("Ventilátor %.0f %%", id(fan_brightness).state * 100.0f / 255.0f);
|
||||||
|
|
||||||
|
- id: update_training_status
|
||||||
|
then:
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: training_status_label
|
||||||
|
text: !lambda |-
|
||||||
|
if (id(training_start_running).state != "0" && !id(training_start_running).state.empty())
|
||||||
|
return std::string("Tréning sa spúšťa, ventilátor sa zapne o 10 min");
|
||||||
|
if (id(training_stop_running).state != "0" && !id(training_stop_running).state.empty())
|
||||||
|
return std::string("Tréning sa ukončuje, ventilátor sa vypne o 2 min");
|
||||||
|
return std::string("");
|
||||||
|
|
||||||
|
- id: update_stopwatch
|
||||||
|
then:
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: stopwatch_label
|
||||||
|
text: !lambda |-
|
||||||
|
const unsigned ms = id(sw_accum) + (id(sw_running) ? millis() - id(sw_start) : 0);
|
||||||
|
const unsigned s = ms / 1000;
|
||||||
|
if (s >= 3600)
|
||||||
|
return str_sprintf("%u:%02u:%02u", s / 3600, (s / 60) % 60, s % 60);
|
||||||
|
return str_sprintf("%02u:%02u.%u", s / 60, s % 60, (ms / 100) % 10);
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: stopwatch_start_label
|
||||||
|
text: !lambda |-
|
||||||
|
return std::string(id(sw_running) ? "Stop" : "Štart");
|
||||||
|
|
||||||
|
- id: update_status
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
// same colors as the other status screens: ok green, waiting orange, details gray
|
||||||
|
static const uint32_t OK = 0x4DD966, WAIT = 0xFFB300;
|
||||||
|
auto set = [](lv_obj_t *label, const std::string &text, uint32_t color) {
|
||||||
|
lv_label_set_text(label, text.c_str());
|
||||||
|
lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool wifi_ok = wifi::global_wifi_component->is_connected();
|
||||||
|
set(id(status_wifi),
|
||||||
|
wifi_ok ? str_sprintf("WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi())
|
||||||
|
: std::string("WiFi: connecting..."),
|
||||||
|
wifi_ok ? OK : WAIT);
|
||||||
|
|
||||||
|
// IPv6 first (skip link-local), then IPv4
|
||||||
|
std::string ips;
|
||||||
|
if (wifi_ok) {
|
||||||
|
char buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||||
|
for (bool v6 : {true, false}) {
|
||||||
|
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
|
||||||
|
if (!ip.is_set() || ip.is_ip6() != v6)
|
||||||
|
continue;
|
||||||
|
ip.str_to(buf);
|
||||||
|
if (v6 && strncmp(buf, "fe80", 4) == 0)
|
||||||
|
continue;
|
||||||
|
if (!ips.empty())
|
||||||
|
ips += "\n";
|
||||||
|
ips += buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lv_label_set_text(id(status_ips), ips.c_str());
|
||||||
|
if (ips.empty())
|
||||||
|
lv_obj_add_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
|
||||||
|
else
|
||||||
|
lv_obj_remove_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
|
||||||
|
|
||||||
|
const bool api_ok = api::global_api_server->is_connected();
|
||||||
|
set(id(status_ha), api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...",
|
||||||
|
api_ok ? OK : WAIT);
|
||||||
|
|
||||||
|
const uint32_t up = millis() / 1000;
|
||||||
|
lv_label_set_text(id(status_footer),
|
||||||
|
str_sprintf("ESPHome %s · up %ud %02u:%02u:%02u", ESPHOME_VERSION, up / 86400,
|
||||||
|
(up / 3600) % 24, (up / 60) % 60, up % 60).c_str());
|
||||||
|
interval:
|
||||||
|
- interval: 1s
|
||||||
|
then:
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: clock_label
|
||||||
|
text: !lambda |-
|
||||||
|
auto now = id(ha_time).now();
|
||||||
|
if (!now.is_valid())
|
||||||
|
return std::string("--:--:--");
|
||||||
|
return now.strftime("%H:%M:%S");
|
||||||
|
- interval: 100ms
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
and:
|
||||||
|
- lambda: return id(sw_running);
|
||||||
|
- lvgl.page.is_showing: stopwatch_page
|
||||||
|
then:
|
||||||
|
- script.execute: update_stopwatch
|
||||||
|
- interval: 2s
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lvgl.page.is_showing: status_page
|
||||||
|
then:
|
||||||
|
- script.execute: update_status
|
||||||
|
|
||||||
|
image:
|
||||||
|
- platform: file
|
||||||
|
file: assets/images/made-for-esphome-white-on-black.svg
|
||||||
|
id: esphome_logo
|
||||||
|
resize: 280x83
|
||||||
|
type: RGB565
|
||||||
|
transparency: alpha_channel
|
||||||
|
|
||||||
|
font:
|
||||||
|
- file: "gfonts://Roboto@500"
|
||||||
|
id: font_digits
|
||||||
|
size: 110
|
||||||
|
glyphs: "0123456789:.-"
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_ui
|
||||||
|
size: 24
|
||||||
|
glyphs: &sk_glyphs "ÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽáäčďéíĺľňóôŕšťúýž°·"
|
||||||
|
glyphsets: [GF_Latin_Kernel]
|
||||||
|
- file: "gfonts://Roboto@500"
|
||||||
|
id: font_button
|
||||||
|
size: 34
|
||||||
|
glyphs: *sk_glyphs
|
||||||
|
glyphsets: [GF_Latin_Kernel]
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_small
|
||||||
|
size: 18
|
||||||
|
glyphs: *sk_glyphs
|
||||||
|
glyphsets: [GF_Latin_Kernel]
|
||||||
|
|
||||||
lvgl:
|
lvgl:
|
||||||
rotation: 90
|
rotation: 90
|
||||||
buffer_size: 100%
|
buffer_size: 100%
|
||||||
log_level: WARN
|
log_level: WARN
|
||||||
color_depth: 16
|
color_depth: 16
|
||||||
default_font: montserrat_18
|
default_font: font_ui
|
||||||
# on_idle:
|
bg_color: 0x000000
|
||||||
# - timeout: !lambda "return (id(display_timeout).state * 1000);"
|
text_color: 0xFFFFFF
|
||||||
# then:
|
theme:
|
||||||
# - logger.log: "LVGL is idle"
|
button:
|
||||||
# - light.turn_off:
|
bg_color: 0x2B2F36
|
||||||
# id: display_backlight
|
radius: 12
|
||||||
# transition_length: 2500ms
|
checked:
|
||||||
# - lvgl.pause:
|
bg_color: 0xE08A00
|
||||||
|
buttonmatrix:
|
||||||
|
bg_opa: TRANSP
|
||||||
|
border_width: 0
|
||||||
|
items:
|
||||||
|
bg_color: 0x2B2F36
|
||||||
|
text_color: 0xFFFFFF
|
||||||
|
radius: 12
|
||||||
|
checked:
|
||||||
|
bg_color: 0xE08A00
|
||||||
|
slider:
|
||||||
|
bg_color: 0x2B2F36
|
||||||
|
indicator:
|
||||||
|
bg_color: 0xE08A00
|
||||||
|
knob:
|
||||||
|
bg_color: 0xFFFFFF
|
||||||
top_layer:
|
top_layer:
|
||||||
widgets:
|
widgets:
|
||||||
- buttonmatrix:
|
- buttonmatrix:
|
||||||
|
id: nav_bar
|
||||||
align: bottom_mid
|
align: bottom_mid
|
||||||
# styles: header_footer
|
width: 100%
|
||||||
pad_all: 0
|
height: 56
|
||||||
outline_width: 0
|
pad_all: 4
|
||||||
id: top_layer
|
pad_column: 6
|
||||||
# items:
|
items:
|
||||||
# styles: header_footer
|
text_font: montserrat_28
|
||||||
rows:
|
rows:
|
||||||
- buttons:
|
- buttons:
|
||||||
- id: page_prev
|
- id: page_prev
|
||||||
text: "\uF053"
|
text: ""
|
||||||
on_press:
|
on_press:
|
||||||
then:
|
then:
|
||||||
lvgl.page.previous:
|
lvgl.page.previous:
|
||||||
- id: page_home
|
- id: page_home
|
||||||
text: "\uF015"
|
text: ""
|
||||||
on_press:
|
on_press:
|
||||||
then:
|
then:
|
||||||
lvgl.page.show: main_page
|
lvgl.page.show: clock_page
|
||||||
- id: page_next
|
- id: page_next
|
||||||
text: "\uF054"
|
text: ""
|
||||||
on_press:
|
on_press:
|
||||||
then:
|
then:
|
||||||
lvgl.page.next:
|
lvgl.page.next:
|
||||||
pages:
|
pages:
|
||||||
- id: main_page
|
# 1: clock (default after boot)
|
||||||
|
- id: clock_page
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
text: "ESPHome LVGL Display"
|
id: clock_label
|
||||||
align: CENTER
|
align: CENTER
|
||||||
text_align: CENTER
|
y: -28
|
||||||
text_color: 0xFFFFFF
|
text_font: font_digits
|
||||||
- id: excercise_page
|
text: "--:--:--"
|
||||||
|
|
||||||
|
# 2: basement lights
|
||||||
|
- id: lights_page
|
||||||
|
pad_all: 10
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
|
||||||
align: CENTER
|
|
||||||
text: 'Hello World!'
|
|
||||||
- button:
|
- button:
|
||||||
id: exercise_room_light_btn
|
id: basement_light_btn
|
||||||
align: TOP_LEFT
|
align: TOP_LEFT
|
||||||
width: 100
|
width: 148
|
||||||
height: 70
|
height: 80
|
||||||
checkable: true
|
checkable: true
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
align: CENTER
|
align: CENTER
|
||||||
text: 'Light'
|
text: "Pivnica"
|
||||||
on_click:
|
on_click:
|
||||||
- homeassistant.action:
|
- homeassistant.action:
|
||||||
action: light.toggle
|
action: light.toggle
|
||||||
data:
|
data:
|
||||||
entity_id: light.exercise_room
|
entity_id: ${light_basement}
|
||||||
- button:
|
- button:
|
||||||
id: exercise_room_fan_btn
|
id: workbench_light_btn
|
||||||
align: TOP_RIGHT
|
align: TOP_MID
|
||||||
width: 100
|
width: 148
|
||||||
height: 70
|
height: 80
|
||||||
checkable: true
|
checkable: true
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
align: CENTER
|
align: CENTER
|
||||||
text: 'Fan'
|
text: "Pracovný stôl"
|
||||||
on_click:
|
on_click:
|
||||||
- homeassistant.action:
|
- homeassistant.action:
|
||||||
action: light.toggle
|
action: light.toggle
|
||||||
data:
|
data:
|
||||||
entity_id: light.mcu_dimmer_exercise_room_fan
|
entity_id: ${light_workbench}
|
||||||
|
- button:
|
||||||
|
id: exercise_light_btn
|
||||||
|
align: TOP_RIGHT
|
||||||
|
width: 148
|
||||||
|
height: 80
|
||||||
|
checkable: true
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
align: CENTER
|
||||||
|
text: "Posilka"
|
||||||
|
on_click:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.toggle
|
||||||
|
data:
|
||||||
|
entity_id: ${light_exercise}
|
||||||
|
- obj:
|
||||||
|
id: exercise_controls
|
||||||
|
hidden: true
|
||||||
|
y: 110
|
||||||
|
width: 460
|
||||||
|
height: 120
|
||||||
|
pad_all: 0
|
||||||
|
bg_opa: TRANSP
|
||||||
|
border_width: 0
|
||||||
|
scrollbar_mode: "OFF"
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
x: 4
|
||||||
|
y: 0
|
||||||
|
text_font: font_small
|
||||||
|
text: "Posilka jas"
|
||||||
|
- slider:
|
||||||
|
id: exercise_brightness_slider
|
||||||
|
x: 14
|
||||||
|
y: 32
|
||||||
|
width: 430
|
||||||
|
height: 16
|
||||||
|
min_value: 1
|
||||||
|
max_value: 100
|
||||||
|
value: 100
|
||||||
|
on_release:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: ${light_exercise}
|
||||||
|
brightness_pct: !lambda return (int) x;
|
||||||
|
- label:
|
||||||
|
x: 4
|
||||||
|
y: 62
|
||||||
|
text_font: font_small
|
||||||
|
text: "Posilka teplota (2700 – 6500 K)"
|
||||||
|
- slider:
|
||||||
|
id: exercise_color_temp_slider
|
||||||
|
x: 14
|
||||||
|
y: 94
|
||||||
|
width: 430
|
||||||
|
height: 16
|
||||||
|
min_value: 2700
|
||||||
|
max_value: 6500
|
||||||
|
value: 2700
|
||||||
|
on_release:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: ${light_exercise}
|
||||||
|
color_temp_kelvin: !lambda return (int) x;
|
||||||
|
|
||||||
|
# 3: training start / stop
|
||||||
|
- id: training_page
|
||||||
|
pad_all: 10
|
||||||
|
widgets:
|
||||||
|
- button:
|
||||||
|
align: TOP_LEFT
|
||||||
|
y: 10
|
||||||
|
width: 222
|
||||||
|
height: 150
|
||||||
|
bg_color: 0x1E7D32
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
align: CENTER
|
||||||
|
text_font: font_button
|
||||||
|
text: "Štart"
|
||||||
|
on_click:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: automation.trigger
|
||||||
|
data:
|
||||||
|
entity_id: automation.zwift_enable_fan
|
||||||
|
- button:
|
||||||
|
align: TOP_RIGHT
|
||||||
|
y: 10
|
||||||
|
width: 222
|
||||||
|
height: 150
|
||||||
|
bg_color: 0xB71C1C
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
align: CENTER
|
||||||
|
text_font: font_button
|
||||||
|
text: "Stop"
|
||||||
|
on_click:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: automation.trigger
|
||||||
|
data:
|
||||||
|
entity_id: automation.zwift_disable_fan
|
||||||
|
- label:
|
||||||
|
id: training_status_label
|
||||||
|
align: TOP_MID
|
||||||
|
y: 185
|
||||||
|
width: 440
|
||||||
|
text_align: CENTER
|
||||||
|
text_font: font_small
|
||||||
|
text: ""
|
||||||
|
|
||||||
|
# 4: fan
|
||||||
|
- id: fan_page
|
||||||
|
pad_all: 10
|
||||||
|
widgets:
|
||||||
|
- buttonmatrix:
|
||||||
|
id: fan_matrix
|
||||||
|
align: TOP_MID
|
||||||
|
width: 460
|
||||||
|
height: 180
|
||||||
|
pad_row: 8
|
||||||
|
pad_column: 8
|
||||||
|
one_checked: true
|
||||||
|
items:
|
||||||
|
text_font: font_button
|
||||||
|
rows:
|
||||||
|
- buttons:
|
||||||
|
- text: "Vyp"
|
||||||
|
control:
|
||||||
|
checkable: true
|
||||||
|
on_press:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_off
|
||||||
|
data:
|
||||||
|
entity_id: ${fan}
|
||||||
|
- text: "50"
|
||||||
|
control:
|
||||||
|
checkable: true
|
||||||
|
on_press:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: ${fan}
|
||||||
|
brightness_pct: "50"
|
||||||
|
- text: "60"
|
||||||
|
control:
|
||||||
|
checkable: true
|
||||||
|
on_press:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: ${fan}
|
||||||
|
brightness_pct: "60"
|
||||||
|
- text: "70"
|
||||||
|
control:
|
||||||
|
checkable: true
|
||||||
|
on_press:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: ${fan}
|
||||||
|
brightness_pct: "70"
|
||||||
|
- buttons:
|
||||||
|
- text: "80"
|
||||||
|
control:
|
||||||
|
checkable: true
|
||||||
|
on_press:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: ${fan}
|
||||||
|
brightness_pct: "80"
|
||||||
|
- text: "90"
|
||||||
|
control:
|
||||||
|
checkable: true
|
||||||
|
on_press:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: ${fan}
|
||||||
|
brightness_pct: "90"
|
||||||
|
- text: "100"
|
||||||
|
control:
|
||||||
|
checkable: true
|
||||||
|
on_press:
|
||||||
|
- homeassistant.action:
|
||||||
|
action: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: ${fan}
|
||||||
|
brightness_pct: "100"
|
||||||
|
- label:
|
||||||
|
id: fan_status_label
|
||||||
|
align: TOP_MID
|
||||||
|
y: 200
|
||||||
|
text_font: font_small
|
||||||
|
text: ""
|
||||||
|
|
||||||
|
# 5: stopwatch
|
||||||
|
- id: stopwatch_page
|
||||||
|
pad_all: 10
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
id: stopwatch_label
|
||||||
|
align: TOP_MID
|
||||||
|
y: 10
|
||||||
|
text_font: font_digits
|
||||||
|
text: "00:00.0"
|
||||||
|
- button:
|
||||||
|
align: TOP_LEFT
|
||||||
|
y: 150
|
||||||
|
width: 222
|
||||||
|
height: 90
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
id: stopwatch_start_label
|
||||||
|
align: CENTER
|
||||||
|
text_font: font_button
|
||||||
|
text: "Štart"
|
||||||
|
on_click:
|
||||||
|
- lambda: |-
|
||||||
|
if (id(sw_running)) {
|
||||||
|
id(sw_accum) += millis() - id(sw_start);
|
||||||
|
id(sw_running) = false;
|
||||||
|
} else {
|
||||||
|
id(sw_start) = millis();
|
||||||
|
id(sw_running) = true;
|
||||||
|
}
|
||||||
|
- script.execute: update_stopwatch
|
||||||
|
- button:
|
||||||
|
align: TOP_RIGHT
|
||||||
|
y: 150
|
||||||
|
width: 222
|
||||||
|
height: 90
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
align: CENTER
|
||||||
|
text_font: font_button
|
||||||
|
text: "Reset"
|
||||||
|
on_click:
|
||||||
|
- lambda: |-
|
||||||
|
id(sw_accum) = 0;
|
||||||
|
id(sw_start) = millis();
|
||||||
|
- script.execute: update_stopwatch
|
||||||
|
|
||||||
|
# 6: status
|
||||||
|
- id: status_page
|
||||||
|
on_load:
|
||||||
|
- script.execute: update_status
|
||||||
|
widgets:
|
||||||
|
- image:
|
||||||
|
src: esphome_logo
|
||||||
|
align: TOP_MID
|
||||||
|
y: 8
|
||||||
|
- label:
|
||||||
|
align: TOP_MID
|
||||||
|
y: 96
|
||||||
|
text: "${name}"
|
||||||
|
- obj:
|
||||||
|
id: status_lines
|
||||||
|
x: 20
|
||||||
|
y: 126
|
||||||
|
width: 440
|
||||||
|
height: SIZE_CONTENT
|
||||||
|
bg_opa: TRANSP
|
||||||
|
border_width: 0
|
||||||
|
pad_all: 0
|
||||||
|
scrollbar_mode: "OFF"
|
||||||
|
layout:
|
||||||
|
type: flex
|
||||||
|
flex_flow: column
|
||||||
|
pad_row: 4
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
id: status_wifi
|
||||||
|
text_font: font_small
|
||||||
|
text: ""
|
||||||
|
- label:
|
||||||
|
id: status_ips
|
||||||
|
text_font: font_small
|
||||||
|
text_color: 0x8C8C8C
|
||||||
|
pad_left: 16
|
||||||
|
text: ""
|
||||||
|
- label:
|
||||||
|
id: status_ha
|
||||||
|
text_font: font_small
|
||||||
|
text: ""
|
||||||
|
- label:
|
||||||
|
id: status_footer
|
||||||
|
text_font: font_small
|
||||||
|
text_color: 0x8C8C8C
|
||||||
|
text: ""
|
||||||
+207
-534
@@ -10,8 +10,10 @@ esphome:
|
|||||||
friendly_name: ${name}
|
friendly_name: ${name}
|
||||||
area: ${area}
|
area: ${area}
|
||||||
comment: ${comment}
|
comment: ${comment}
|
||||||
platformio_options:
|
on_boot:
|
||||||
board_build.flash_mode: dio
|
- priority: -100
|
||||||
|
then:
|
||||||
|
- lvgl.page.show: status_page
|
||||||
|
|
||||||
esp32:
|
esp32:
|
||||||
variant: esp32s3
|
variant: esp32s3
|
||||||
@@ -19,9 +21,9 @@ esp32:
|
|||||||
toolchain: esp-idf
|
toolchain: esp-idf
|
||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
# Required to achieve sufficient PSRAM bandwidth
|
# PSRAM bandwidth for the RGB panel (not set by ESPHome); without it the
|
||||||
|
# image shifts during flash/WiFi activity
|
||||||
sdkconfig_options:
|
sdkconfig_options:
|
||||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: y
|
|
||||||
CONFIG_ESP32S3_DATA_CACHE_64KB: y
|
CONFIG_ESP32S3_DATA_CACHE_64KB: y
|
||||||
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
|
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
|
||||||
CONFIG_SPIRAM_RODATA: y
|
CONFIG_SPIRAM_RODATA: y
|
||||||
@@ -32,6 +34,7 @@ psram:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# PINOUTS: ESP32 Cheap Yellow Display, 4.3", IPS, Capacitive
|
# PINOUTS: ESP32 Cheap Yellow Display, 4.3", IPS, Capacitive
|
||||||
## Display
|
## Display
|
||||||
@@ -63,6 +66,7 @@ packages:
|
|||||||
# HSYNC 39
|
# HSYNC 39
|
||||||
# VSYNC 41
|
# VSYNC 41
|
||||||
# DE 40
|
# DE 40
|
||||||
|
# BL 2
|
||||||
## Capacitive Touch
|
## Capacitive Touch
|
||||||
# TP_INT
|
# TP_INT
|
||||||
# TP_RST 38
|
# TP_RST 38
|
||||||
@@ -89,579 +93,248 @@ packages:
|
|||||||
# 3 19 TP_SDA
|
# 3 19 TP_SDA
|
||||||
# 4 20 TP_SCL
|
# 4 20 TP_SCL
|
||||||
|
|
||||||
|
api:
|
||||||
|
# status page until Home Assistant connects (log viewers don't subscribe to states)
|
||||||
|
on_client_connected:
|
||||||
|
- delay: 2s
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- lvgl.page.show: main_page
|
||||||
|
on_client_disconnected:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
not:
|
||||||
|
api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- lvgl.page.show: status_page
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
- id: i2c_main
|
- id: i2c_main
|
||||||
sda: 19
|
sda: 19
|
||||||
scl: 20
|
scl: 20
|
||||||
scan: true
|
scan: true
|
||||||
|
|
||||||
|
# same pins and timing as the 5" Sunton board, pclk lowered for the 4.3" panel
|
||||||
display:
|
display:
|
||||||
- platform: rpi_dpi_rgb
|
- platform: mipi_rgb
|
||||||
|
model: ESP32-8048S050
|
||||||
id: main_display
|
id: main_display
|
||||||
color_order: RGB
|
color_order: RGB
|
||||||
invert_colors: true
|
invert_colors: true
|
||||||
|
pclk_frequency: 12.5MHz
|
||||||
update_interval: never
|
update_interval: never
|
||||||
auto_clear_enabled: false # takes 2.8 seconds to clear the display
|
auto_clear_enabled: false # takes 2.8 seconds to clear the display
|
||||||
dimensions:
|
|
||||||
width: 800
|
|
||||||
height: 480
|
|
||||||
de_pin: 40
|
|
||||||
hsync_pin: 39
|
|
||||||
vsync_pin: 41
|
|
||||||
pclk_pin: 42
|
|
||||||
pclk_frequency: 12.5MHz
|
|
||||||
hsync_pulse_width: 4
|
|
||||||
hsync_front_porch: 8
|
|
||||||
hsync_back_porch: 8
|
|
||||||
vsync_pulse_width: 4
|
|
||||||
vsync_front_porch: 8
|
|
||||||
vsync_back_porch: 8
|
|
||||||
data_pins:
|
|
||||||
red:
|
|
||||||
- 45 # R1
|
|
||||||
- 48 # R2
|
|
||||||
- 47 # R3
|
|
||||||
- 21 # R4
|
|
||||||
- 14 # R5
|
|
||||||
green:
|
|
||||||
- 5 # G0
|
|
||||||
- 6 # G1
|
|
||||||
- 7 # G2
|
|
||||||
- 15 # G3
|
|
||||||
- 16 # G4
|
|
||||||
- 4 # G5
|
|
||||||
blue:
|
|
||||||
- 8 # B1
|
|
||||||
- 3 # B2
|
|
||||||
- 46 # B3
|
|
||||||
- 9 # B4
|
|
||||||
- 1 # B5
|
|
||||||
|
|
||||||
|
touchscreen:
|
||||||
|
- platform: gt911
|
||||||
|
id: main_touch
|
||||||
|
display: main_display
|
||||||
|
i2c_id: i2c_main
|
||||||
|
reset_pin: 38
|
||||||
|
|
||||||
# Define a PWM output on the ESP32
|
|
||||||
output:
|
output:
|
||||||
- platform: ledc
|
- platform: ledc
|
||||||
|
id: gpio_backlight_pwm
|
||||||
pin: 2
|
pin: 2
|
||||||
frequency: 1220
|
frequency: 1220
|
||||||
id: gpio_backlight_pwm
|
|
||||||
|
|
||||||
light:
|
light:
|
||||||
- platform: monochromatic
|
- platform: monochromatic
|
||||||
output: gpio_backlight_pwm
|
|
||||||
id: back_light
|
id: back_light
|
||||||
|
name: Backlight
|
||||||
|
output: gpio_backlight_pwm
|
||||||
restore_mode: ALWAYS_ON
|
restore_mode: ALWAYS_ON
|
||||||
|
|
||||||
text_sensor:
|
|
||||||
- platform: template
|
|
||||||
# name: Uptime Human Readable
|
|
||||||
id: uptime_human
|
|
||||||
icon: mdi:clock-start
|
|
||||||
internal: True
|
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: time_comp
|
|
||||||
|
|
||||||
number:
|
|
||||||
- platform: template
|
|
||||||
initial_value: 0
|
|
||||||
id: counting_number
|
|
||||||
internal: True
|
|
||||||
min_value: -10
|
|
||||||
max_value: 10
|
|
||||||
step: 1
|
|
||||||
optimistic: True
|
|
||||||
|
|
||||||
# debug:
|
|
||||||
# update_interval: 5s
|
|
||||||
|
|
||||||
sensor:
|
|
||||||
- platform: uptime
|
|
||||||
id: uptime_counter
|
|
||||||
update_interval: 1s
|
|
||||||
accuracy_decimals: 0
|
|
||||||
on_raw_value:
|
|
||||||
then:
|
|
||||||
- light.turn_on:
|
|
||||||
id: back_light
|
|
||||||
brightness: 90%
|
|
||||||
- number.increment:
|
|
||||||
id: counting_number
|
|
||||||
cycle: True
|
|
||||||
- script.execute: update_display
|
|
||||||
# - platform: debug
|
|
||||||
# free:
|
|
||||||
# name: "${name} Heap Free"
|
|
||||||
# block:
|
|
||||||
# name: "${name} Heap Max Block"
|
|
||||||
# loop_time:
|
|
||||||
# name: "${name} Loop Time"
|
|
||||||
# psram:
|
|
||||||
# name: "${name} Free PSRAM"
|
|
||||||
- platform: uptime
|
|
||||||
# name: Uptime Sensor
|
|
||||||
internal: True
|
|
||||||
id: uptime_sensor
|
|
||||||
update_interval: 1s
|
|
||||||
on_raw_value:
|
|
||||||
then:
|
|
||||||
- text_sensor.template.publish:
|
|
||||||
id: uptime_human
|
|
||||||
state: !lambda |-
|
|
||||||
int seconds = round(id(uptime_sensor).raw_state);
|
|
||||||
int days = seconds / (24 * 3600);
|
|
||||||
seconds = seconds % (24 * 3600);
|
|
||||||
int hours = seconds / 3600;
|
|
||||||
seconds = seconds % 3600;
|
|
||||||
int minutes = seconds / 60;
|
|
||||||
seconds = seconds % 60;
|
|
||||||
return (
|
|
||||||
("Uptime ") +
|
|
||||||
(days ? to_string(days) + "d " : "") +
|
|
||||||
(hours ? to_string(hours) + "h " : "") +
|
|
||||||
(minutes ? to_string(minutes) + "m " : "") +
|
|
||||||
(to_string(seconds) + "s")
|
|
||||||
).c_str();
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- id: update_display
|
- id: update_status
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
// same colors as the other status screens: ok green, waiting orange, details gray
|
||||||
|
static const uint32_t OK = 0x4DD966, WAIT = 0xFFB300;
|
||||||
|
auto set = [](lv_obj_t *label, const std::string &text, uint32_t color) {
|
||||||
|
lv_label_set_text(label, text.c_str());
|
||||||
|
lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool wifi_ok = wifi::global_wifi_component->is_connected();
|
||||||
|
set(id(status_wifi),
|
||||||
|
wifi_ok ? str_sprintf("WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi())
|
||||||
|
: std::string("WiFi: connecting..."),
|
||||||
|
wifi_ok ? OK : WAIT);
|
||||||
|
|
||||||
|
// IPv6 first (skip link-local), then IPv4
|
||||||
|
std::string ips;
|
||||||
|
if (wifi_ok) {
|
||||||
|
char buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||||
|
for (bool v6 : {true, false}) {
|
||||||
|
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
|
||||||
|
if (!ip.is_set() || ip.is_ip6() != v6)
|
||||||
|
continue;
|
||||||
|
ip.str_to(buf);
|
||||||
|
if (v6 && strncmp(buf, "fe80", 4) == 0)
|
||||||
|
continue;
|
||||||
|
if (!ips.empty())
|
||||||
|
ips += "\n";
|
||||||
|
ips += buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lv_label_set_text(id(status_ips), ips.c_str());
|
||||||
|
if (ips.empty())
|
||||||
|
lv_obj_add_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
|
||||||
|
else
|
||||||
|
lv_obj_remove_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
|
||||||
|
|
||||||
|
const bool api_ok = api::global_api_server->is_connected();
|
||||||
|
set(id(status_ha), api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...",
|
||||||
|
api_ok ? OK : WAIT);
|
||||||
|
|
||||||
|
const uint32_t up = millis() / 1000;
|
||||||
|
lv_label_set_text(id(status_footer),
|
||||||
|
str_sprintf("ESPHome %s · up %ud %02u:%02u:%02u", ESPHOME_VERSION, up / 86400,
|
||||||
|
(up / 3600) % 24, (up / 60) % 60, up % 60).c_str());
|
||||||
|
interval:
|
||||||
|
- interval: 1s
|
||||||
then:
|
then:
|
||||||
# - lvgl.indicator.line.update:
|
|
||||||
# id: power_meter_input
|
|
||||||
# value: !lambda return id(counting_number).state;
|
|
||||||
- lvgl.label.update:
|
- lvgl.label.update:
|
||||||
id: battery_kw
|
id: clock_label
|
||||||
text: !lambda |-
|
text: !lambda |-
|
||||||
static char buf[8];
|
auto now = id(ha_time).now();
|
||||||
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state);
|
if (!now.is_valid())
|
||||||
return buf;
|
return std::string("--:--");
|
||||||
- lvgl.label.update:
|
return now.strftime("%H:%M");
|
||||||
id: battery_status
|
- if:
|
||||||
text_color: 0xFF0000
|
condition:
|
||||||
#text: "charging"
|
lvgl.page.is_showing: status_page
|
||||||
text: "discharging"
|
then:
|
||||||
- lvgl.label.update:
|
- script.execute: update_status
|
||||||
id: battery_soc
|
|
||||||
text: !lambda |-
|
|
||||||
static char buf[8];
|
|
||||||
snprintf(buf, sizeof(buf), "%.1f%%", id(counting_number).state);
|
|
||||||
return buf;
|
|
||||||
- lvgl.label.update:
|
|
||||||
id: solar_kw
|
|
||||||
text: !lambda |-
|
|
||||||
static char buf[8];
|
|
||||||
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state);
|
|
||||||
return buf;
|
|
||||||
# - lvgl.img.update:
|
|
||||||
# id: img_solar_power
|
|
||||||
# src: solar_power_icon
|
|
||||||
# img_recolor: 0xFFF000 #mixes this color with the base image
|
|
||||||
# #img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
|
|
||||||
# #bg_color: 0xFFFFFF
|
|
||||||
# # - lvgl.indicator.line.update:
|
|
||||||
# # id: power_meter_input2
|
|
||||||
# # value: !lambda return id(counting_number).state;
|
|
||||||
- lvgl.label.update:
|
|
||||||
id: battery_kw2
|
|
||||||
text: !lambda |-
|
|
||||||
static char buf[8];
|
|
||||||
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state);
|
|
||||||
return buf;
|
|
||||||
- lvgl.label.update:
|
|
||||||
id: battery_status2
|
|
||||||
text_color: 0xFF0000
|
|
||||||
#text: "charging"
|
|
||||||
text: "discharging"
|
|
||||||
- lvgl.label.update:
|
|
||||||
id: battery_soc2
|
|
||||||
text: !lambda |-
|
|
||||||
static char buf[8];
|
|
||||||
snprintf(buf, sizeof(buf), "%.1f%%", id(counting_number).state);
|
|
||||||
return buf;
|
|
||||||
- lvgl.label.update:
|
|
||||||
id: solar_kw2
|
|
||||||
text: !lambda |-
|
|
||||||
static char buf[8];
|
|
||||||
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state);
|
|
||||||
return buf;
|
|
||||||
# - lvgl.img.update:
|
|
||||||
# id: img_solar_power2
|
|
||||||
# src: solar_power_icon
|
|
||||||
# img_recolor: 0xFFF000 #mixes this color with the base image
|
|
||||||
# #img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
|
|
||||||
# #bg_color: 0xFFFFFF
|
|
||||||
|
|
||||||
image:
|
image:
|
||||||
- file: mdi:sun-wireless-outline
|
- platform: file
|
||||||
#"solar-power-invert.png"
|
file: assets/images/made-for-esphome-white-on-black.svg
|
||||||
# mdi:solar-power
|
id: esphome_logo
|
||||||
# mdi:battery-charging-outline
|
resize: 400x118
|
||||||
# mdi:battery-arrow-up-outline
|
type: RGB565
|
||||||
# mdi:battery-arrow-down-outline
|
transparency: alpha_channel
|
||||||
id: solar_power_icon
|
|
||||||
resize: 50x50
|
|
||||||
type: BINARY
|
|
||||||
|
|
||||||
- file: mdi:battery-arrow-down-outline
|
font:
|
||||||
id: home_battery_icon
|
- file: "gfonts://Roboto@500"
|
||||||
resize: 30x30
|
id: font_clock
|
||||||
type: BINARY
|
size: 200
|
||||||
|
glyphs: "0123456789:-"
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_title
|
||||||
|
size: 40
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_status
|
||||||
|
size: 26
|
||||||
|
glyphs: "·"
|
||||||
|
glyphsets: [GF_Latin_Kernel]
|
||||||
|
|
||||||
lvgl:
|
lvgl:
|
||||||
log_level: INFO
|
log_level: WARN
|
||||||
color_depth: 16
|
color_depth: 16
|
||||||
#buffer_size: 25%
|
bg_color: 0x000000
|
||||||
bg_color: 0
|
|
||||||
border_width: 0
|
|
||||||
outline_width: 0
|
|
||||||
shadow_width: 0
|
|
||||||
text_font: unscii_16
|
|
||||||
align: center
|
|
||||||
style_definitions:
|
|
||||||
- id: meter_style
|
|
||||||
text_font: unscii_8
|
|
||||||
#state_default: null
|
|
||||||
- id: font_style
|
|
||||||
text_font: MONTSERRAT_24
|
|
||||||
#text_font: unscii_16
|
|
||||||
align: center
|
|
||||||
text_color: 0xFFFFFF
|
text_color: 0xFFFFFF
|
||||||
#bg_opa: cover
|
theme:
|
||||||
|
buttonmatrix:
|
||||||
bg_opa: TRANSP
|
bg_opa: TRANSP
|
||||||
bg_color: 0
|
|
||||||
radius: 4
|
|
||||||
pad_all: 2
|
|
||||||
- id: details_style
|
|
||||||
text_font: MONTSERRAT_18
|
|
||||||
align: center
|
|
||||||
text_color: 0xFFFFFF
|
|
||||||
#bg_opa: cover
|
|
||||||
bg_opa: TRANSP
|
|
||||||
bg_color: 0
|
|
||||||
radius: 4
|
|
||||||
pad_all: 2
|
|
||||||
widgets:
|
|
||||||
- obj: # Meter
|
|
||||||
height: 240
|
|
||||||
width: 240
|
|
||||||
#bg_opa: TRANSP
|
|
||||||
bg_color: 0
|
|
||||||
border_width: 0
|
border_width: 0
|
||||||
outline_width: 0
|
items:
|
||||||
shadow_width: 0
|
bg_color: 0x2B2F36
|
||||||
pad_all: 4
|
text_color: 0xFFFFFF
|
||||||
align: TOP_LEFT
|
radius: 12
|
||||||
#scrollbar_mode: "off"
|
checked:
|
||||||
|
bg_color: 0xE08A00
|
||||||
|
top_layer:
|
||||||
widgets:
|
widgets:
|
||||||
- meter: # Gradient color arc
|
# same navigation bar as on mcu-lvgl-trainer
|
||||||
height: 100%
|
- buttonmatrix:
|
||||||
|
id: nav_bar
|
||||||
|
align: bottom_mid
|
||||||
width: 100%
|
width: 100%
|
||||||
border_width: 0
|
height: 64
|
||||||
outline_width: 0
|
|
||||||
align: center
|
|
||||||
bg_color: 0
|
|
||||||
styles: meter_style
|
|
||||||
#bg_opa: TRANSP
|
|
||||||
scales:
|
|
||||||
angle_range: 180 # sets the total angle to 180 = starts mid left and ends mid right
|
|
||||||
range_to: 10
|
|
||||||
range_from: -10
|
|
||||||
ticks:
|
|
||||||
count: 0
|
|
||||||
indicators:
|
|
||||||
- line:
|
|
||||||
id: power_meter_input
|
|
||||||
width: 8
|
|
||||||
color: 0xFFFFFF
|
|
||||||
r_mod: 12 #sets line length by this much difference from the scale default radius
|
|
||||||
value: 50
|
|
||||||
# - image:
|
|
||||||
# id: power_meter_input_img
|
|
||||||
- arc:
|
|
||||||
color: 0xFF3000
|
|
||||||
r_mod: 10 #radius difference from the scale default radius
|
|
||||||
width: 20
|
|
||||||
start_value: -10
|
|
||||||
end_value: 0
|
|
||||||
- arc:
|
|
||||||
color: 0x00FF00
|
|
||||||
r_mod: 10 #radius difference from the scale default radius
|
|
||||||
width: 20
|
|
||||||
start_value: 0
|
|
||||||
end_value: 10
|
|
||||||
#- canvas:
|
|
||||||
- arc: # black arc to erase middle part of meter indicator line
|
|
||||||
height: 160
|
|
||||||
width: 160
|
|
||||||
align: center
|
|
||||||
arc_color: 0x000000 # background color
|
|
||||||
arc_width: 150
|
|
||||||
start_angle: 0
|
|
||||||
end_angle: 360
|
|
||||||
indicator:
|
|
||||||
arc_width: 150
|
|
||||||
arc_color: 0x000000
|
|
||||||
- label: # gauge lower and higher range indicators
|
|
||||||
styles: font_style
|
|
||||||
text_font: MONTSERRAT_18 # override font size
|
|
||||||
y: 8 #negative = higher
|
|
||||||
x: -99
|
|
||||||
text: "-10"
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
text_font: MONTSERRAT_18 # override font size
|
|
||||||
y: 8 #negative = higher
|
|
||||||
x: 99
|
|
||||||
text: "+10"
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
id: battery_status
|
|
||||||
y: -35 #negative = higher
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
id: battery_kw
|
|
||||||
y: -60
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
text_font: MONTSERRAT_40 # override font size
|
|
||||||
id: battery_soc
|
|
||||||
y: 0
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
id: solar_kw
|
|
||||||
text_color: 0xFFFF00
|
|
||||||
y: 90
|
|
||||||
#- img:
|
|
||||||
# src: home_battery_icon
|
|
||||||
#id: img_solar_power
|
|
||||||
#align: center
|
|
||||||
#img_recolor: 0xFFFFFF
|
|
||||||
#img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
|
|
||||||
#bg_color: 0xFFFFFF
|
|
||||||
#y: -35
|
|
||||||
#x: -60
|
|
||||||
- image:
|
|
||||||
src: solar_power_icon
|
|
||||||
id: img_solar_power
|
|
||||||
align: center
|
|
||||||
image_recolor: 0xFFFF00
|
|
||||||
image_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
|
|
||||||
#bg_color: 0xFFFFFF
|
|
||||||
y: 50
|
|
||||||
#x: -60
|
|
||||||
- obj: # Meter
|
|
||||||
height: 240
|
|
||||||
width: 240
|
|
||||||
#bg_opa: TRANSP
|
|
||||||
bg_color: 0
|
|
||||||
border_width: 0
|
|
||||||
outline_width: 0
|
|
||||||
shadow_width: 0
|
|
||||||
pad_all: 4
|
pad_all: 4
|
||||||
|
pad_column: 6
|
||||||
|
items:
|
||||||
|
text_font: montserrat_28
|
||||||
|
rows:
|
||||||
|
- buttons:
|
||||||
|
- id: page_prev
|
||||||
|
text: "\uF053"
|
||||||
|
on_press:
|
||||||
|
then:
|
||||||
|
lvgl.page.previous:
|
||||||
|
- id: page_home
|
||||||
|
text: "\uF015"
|
||||||
|
on_press:
|
||||||
|
then:
|
||||||
|
lvgl.page.show: main_page
|
||||||
|
- id: page_next
|
||||||
|
text: "\uF054"
|
||||||
|
on_press:
|
||||||
|
then:
|
||||||
|
lvgl.page.next:
|
||||||
|
pages:
|
||||||
|
# 1: clock
|
||||||
|
- id: main_page
|
||||||
|
widgets:
|
||||||
|
- label:
|
||||||
|
id: clock_label
|
||||||
|
align: CENTER
|
||||||
|
y: -32
|
||||||
|
text_font: font_clock
|
||||||
|
text: "--:--"
|
||||||
|
|
||||||
|
# 2: status (shown until Home Assistant connects)
|
||||||
|
- id: status_page
|
||||||
|
on_load:
|
||||||
|
- script.execute: update_status
|
||||||
|
widgets:
|
||||||
|
- image:
|
||||||
|
src: esphome_logo
|
||||||
align: TOP_MID
|
align: TOP_MID
|
||||||
#scrollbar_mode: "off"
|
y: 16
|
||||||
|
- label:
|
||||||
|
align: TOP_MID
|
||||||
|
y: 140
|
||||||
|
text_font: font_title
|
||||||
|
text: "${name}"
|
||||||
|
- obj:
|
||||||
|
id: status_lines
|
||||||
|
x: 60
|
||||||
|
y: 196
|
||||||
|
width: 700
|
||||||
|
height: SIZE_CONTENT
|
||||||
|
bg_opa: TRANSP
|
||||||
|
border_width: 0
|
||||||
|
pad_all: 0
|
||||||
|
scrollbar_mode: "OFF"
|
||||||
|
layout:
|
||||||
|
type: flex
|
||||||
|
flex_flow: column
|
||||||
|
pad_row: 8
|
||||||
widgets:
|
widgets:
|
||||||
- meter: # Gradient color arc
|
|
||||||
height: 100%
|
|
||||||
width: 100%
|
|
||||||
border_width: 0
|
|
||||||
outline_width: 0
|
|
||||||
align: center
|
|
||||||
bg_color: 0
|
|
||||||
styles: meter_style
|
|
||||||
#bg_opa: TRANSP
|
|
||||||
scales:
|
|
||||||
angle_range: 180 # sets the total angle to 180 = starts mid left and ends mid right
|
|
||||||
range_to: 10
|
|
||||||
range_from: -10
|
|
||||||
ticks:
|
|
||||||
count: 0
|
|
||||||
indicators:
|
|
||||||
- line:
|
|
||||||
id: power_meter_input2
|
|
||||||
width: 8
|
|
||||||
color: 0xFFFFFF
|
|
||||||
r_mod: 12 #sets line length by this much difference from the scale default radius
|
|
||||||
value: 50
|
|
||||||
# - image:
|
|
||||||
# id: power_meter_input_img2
|
|
||||||
- arc:
|
|
||||||
color: 0xFF3000
|
|
||||||
r_mod: 10 #radius difference from the scale default radius
|
|
||||||
width: 20
|
|
||||||
start_value: -10
|
|
||||||
end_value: 0
|
|
||||||
- arc:
|
|
||||||
color: 0x00FF00
|
|
||||||
r_mod: 10 #radius difference from the scale default radius
|
|
||||||
width: 20
|
|
||||||
start_value: 0
|
|
||||||
end_value: 10
|
|
||||||
#- canvas:
|
|
||||||
- arc: # black arc to erase middle part of meter indicator line
|
|
||||||
height: 160
|
|
||||||
width: 160
|
|
||||||
align: center
|
|
||||||
arc_color: 0x000000 # background color
|
|
||||||
arc_width: 150
|
|
||||||
start_angle: 0
|
|
||||||
end_angle: 360
|
|
||||||
indicator:
|
|
||||||
arc_width: 150
|
|
||||||
arc_color: 0x000000
|
|
||||||
- label: # gauge lower and higher range indicators
|
|
||||||
styles: font_style
|
|
||||||
text_font: MONTSERRAT_18 # override font size
|
|
||||||
y: 8 #negative = higher
|
|
||||||
x: -99
|
|
||||||
text: "-10"
|
|
||||||
- label:
|
- label:
|
||||||
styles: font_style
|
id: status_wifi
|
||||||
text_font: MONTSERRAT_18 # override font size
|
text_font: font_status
|
||||||
y: 8 #negative = higher
|
text: ""
|
||||||
x: 99
|
|
||||||
text: "+10"
|
|
||||||
- label:
|
- label:
|
||||||
styles: font_style
|
id: status_ips
|
||||||
id: battery_status2
|
text_font: font_status
|
||||||
y: -35 #negative = higher
|
text_color: 0x8C8C8C
|
||||||
|
pad_left: 30
|
||||||
|
text: ""
|
||||||
- label:
|
- label:
|
||||||
styles: font_style
|
id: status_ha
|
||||||
id: battery_kw2
|
text_font: font_status
|
||||||
y: -60
|
text: ""
|
||||||
- label:
|
- label:
|
||||||
styles: font_style
|
id: status_footer
|
||||||
text_font: MONTSERRAT_40 # override font size
|
text_font: font_status
|
||||||
id: battery_soc2
|
text_color: 0x8C8C8C
|
||||||
y: 0
|
text: ""
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
id: solar_kw2
|
|
||||||
text_color: 0xFFFF00
|
|
||||||
y: 90
|
|
||||||
#- img:
|
|
||||||
# src: home_battery_icon
|
|
||||||
#id: img_solar_power
|
|
||||||
#align: center
|
|
||||||
#img_recolor: 0xFFFFFF
|
|
||||||
#img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
|
|
||||||
#bg_color: 0xFFFFFF
|
|
||||||
#y: -35
|
|
||||||
#x: -60
|
|
||||||
- image:
|
|
||||||
src: solar_power_icon
|
|
||||||
id: img_solar_power2
|
|
||||||
align: center
|
|
||||||
image_recolor: 0xFFFF00
|
|
||||||
image_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
|
|
||||||
#bg_color: 0xFFFFFF
|
|
||||||
y: 50
|
|
||||||
#x: -60
|
|
||||||
- obj: # Meter
|
|
||||||
height: 240
|
|
||||||
width: 240
|
|
||||||
#bg_opa: TRANSP
|
|
||||||
bg_color: 0
|
|
||||||
border_width: 0
|
|
||||||
outline_width: 0
|
|
||||||
shadow_width: 0
|
|
||||||
pad_all: 4
|
|
||||||
align: BOTTOM_LEFT
|
|
||||||
#scrollbar_mode: "off"
|
|
||||||
widgets:
|
|
||||||
- meter: # Gradient color arc
|
|
||||||
height: 100%
|
|
||||||
width: 100%
|
|
||||||
border_width: 0
|
|
||||||
outline_width: 0
|
|
||||||
align: center
|
|
||||||
bg_color: 0
|
|
||||||
styles: meter_style
|
|
||||||
#bg_opa: TRANSP
|
|
||||||
scales:
|
|
||||||
angle_range: 180 # sets the total angle to 180 = starts mid left and ends mid right
|
|
||||||
range_to: 10
|
|
||||||
range_from: -10
|
|
||||||
ticks:
|
|
||||||
count: 0
|
|
||||||
indicators:
|
|
||||||
- line:
|
|
||||||
id: power_meter_input3
|
|
||||||
width: 8
|
|
||||||
color: 0xFFFFFF
|
|
||||||
r_mod: 12 #sets line length by this much difference from the scale default radius
|
|
||||||
value: 50
|
|
||||||
# - image:
|
|
||||||
# id: power_meter_input_img3
|
|
||||||
- arc:
|
|
||||||
color: 0xFF3000
|
|
||||||
r_mod: 10 #radius difference from the scale default radius
|
|
||||||
width: 20
|
|
||||||
start_value: -10
|
|
||||||
end_value: 0
|
|
||||||
- arc:
|
|
||||||
color: 0x00FF00
|
|
||||||
r_mod: 10 #radius difference from the scale default radius
|
|
||||||
width: 20
|
|
||||||
start_value: 0
|
|
||||||
end_value: 10
|
|
||||||
#- canvas:
|
|
||||||
- arc: # black arc to erase middle part of meter indicator line
|
|
||||||
height: 160
|
|
||||||
width: 160
|
|
||||||
align: center
|
|
||||||
arc_color: 0x000000 # background color
|
|
||||||
arc_width: 150
|
|
||||||
start_angle: 0
|
|
||||||
end_angle: 360
|
|
||||||
indicator:
|
|
||||||
arc_width: 150
|
|
||||||
arc_color: 0x000000
|
|
||||||
- label: # gauge lower and higher range indicators
|
|
||||||
styles: font_style
|
|
||||||
text_font: MONTSERRAT_18 # override font size
|
|
||||||
y: 8 #negative = higher
|
|
||||||
x: -99
|
|
||||||
text: "-10"
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
text_font: MONTSERRAT_18 # override font size
|
|
||||||
y: 8 #negative = higher
|
|
||||||
x: 99
|
|
||||||
text: "+10"
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
id: battery_status3
|
|
||||||
y: -35 #negative = higher
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
id: battery_kw3
|
|
||||||
y: -60
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
text_font: MONTSERRAT_40 # override font size
|
|
||||||
id: battery_soc3
|
|
||||||
y: 0
|
|
||||||
- label:
|
|
||||||
styles: font_style
|
|
||||||
id: solar_kw3
|
|
||||||
text_color: 0xFFFF00
|
|
||||||
y: 90
|
|
||||||
#- img:
|
|
||||||
# src: home_battery_icon
|
|
||||||
#id: img_solar_power
|
|
||||||
#align: center
|
|
||||||
#img_recolor: 0xFFFFFF
|
|
||||||
#img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
|
|
||||||
#bg_color: 0xFFFFFF
|
|
||||||
#y: -35
|
|
||||||
#x: -60
|
|
||||||
- image:
|
|
||||||
src: solar_power_icon
|
|
||||||
id: img_solar_power3
|
|
||||||
align: center
|
|
||||||
image_recolor: 0xFFFF00
|
|
||||||
image_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
|
|
||||||
#bg_color: 0xFFFFFF
|
|
||||||
y: 50
|
|
||||||
#x: -60
|
|
||||||
+564
-77
@@ -4,94 +4,370 @@ substitutions:
|
|||||||
name: M5Paper
|
name: M5Paper
|
||||||
area: DEV
|
area: DEV
|
||||||
comment: M5Paper
|
comment: M5Paper
|
||||||
|
weather_entity: weather.openweathermap
|
||||||
|
|
||||||
|
# Power modes
|
||||||
|
# - USB: always on, WiFi + API, forecast refreshed every hour and redrawn every 15 min
|
||||||
|
# - battery: RTC wakes the board every 15 min (off 1:00-5:00), it draws the forecast
|
||||||
|
# from EEPROM and powers itself off; WiFi only to refresh the forecast every 6 h
|
||||||
|
# Screens: status (after power-on, until Home Assistant connects) and forecast
|
||||||
|
# The mode is detected by trying to power off: on USB the board keeps running.
|
||||||
|
|
||||||
esphome:
|
esphome:
|
||||||
name: mcu-${device}
|
name: mcu-${device}
|
||||||
friendly_name: ${name}
|
friendly_name: ${name}
|
||||||
area: ${area}
|
area: ${area}
|
||||||
comment: ${comment}
|
comment: ${comment}
|
||||||
|
includes:
|
||||||
|
- include/m5paper_weather.h
|
||||||
|
on_boot:
|
||||||
|
- priority: 600
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
// BM8563 CONTROL_STATUS_2: TF (bit 2) is set when the timer woke us up
|
||||||
|
const uint8_t reg = 0x01;
|
||||||
|
uint8_t status = 0;
|
||||||
|
if (id(i2c_main)->write_readv(0x51, ®, 1, &status, 1) == i2c::ERROR_OK) {
|
||||||
|
id(rtc_wake) = status & 0x04;
|
||||||
|
// clear TF and TIE so the RTC releases the power latch
|
||||||
|
const uint8_t clear[2] = {reg, uint8_t(status & ~0x05)};
|
||||||
|
id(i2c_main)->write(0x51, clear, 2);
|
||||||
|
}
|
||||||
|
m5paper_weather::load(id(i2c_main), m5paper_weather::forecast());
|
||||||
|
ESP_LOGI("m5paper", "Wake-up: %s, forecast: %u h", id(rtc_wake) ? "RTC" : "power-on", m5paper_weather::forecast().count);
|
||||||
|
- priority: -100
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return id(rtc_wake);
|
||||||
|
then:
|
||||||
|
- script.execute: battery_cycle
|
||||||
|
else:
|
||||||
|
# power button / USB: stay awake for a while (logs, OTA), then decide the mode
|
||||||
|
- wifi.enable:
|
||||||
|
# IT8951 init runs asynchronously after setup
|
||||||
|
- delay: 3s
|
||||||
|
- component.update: m5paper_display
|
||||||
|
- delay: 2min
|
||||||
|
- script.execute: battery_cycle
|
||||||
|
|
||||||
esp32:
|
esp32:
|
||||||
variant: esp32
|
variant: esp32
|
||||||
toolchain: esp-idf
|
toolchain: esp-idf
|
||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
|
advanced:
|
||||||
|
flash_chip: generic
|
||||||
|
minimum_chip_revision: 3.0
|
||||||
|
sram1_as_iram: true
|
||||||
|
|
||||||
psram:
|
psram:
|
||||||
|
|
||||||
external_components:
|
|
||||||
- source: github://Passific/m5paper_esphome
|
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
|
||||||
time:
|
globals:
|
||||||
- platform: homeassistant
|
- id: show_status
|
||||||
id: ha_time
|
type: bool
|
||||||
timezone: Europe/Bratislava
|
initial_value: 'true'
|
||||||
on_time_sync:
|
- id: usb_mode
|
||||||
- bm8563.write_time
|
type: bool
|
||||||
- platform: bm8563
|
initial_value: 'false'
|
||||||
id: rtc_time
|
- id: rtc_wake
|
||||||
sleep_duration: 3600000ms
|
type: bool
|
||||||
|
initial_value: 'false'
|
||||||
|
# battery cycle running: forecast screen regardless of the connection state
|
||||||
|
- id: weather_page
|
||||||
|
type: bool
|
||||||
|
initial_value: 'false'
|
||||||
|
- id: fetch_done
|
||||||
|
type: bool
|
||||||
|
initial_value: 'false'
|
||||||
|
- id: sleep_s
|
||||||
|
type: int
|
||||||
|
initial_value: '900'
|
||||||
|
|
||||||
m5paper:
|
wifi:
|
||||||
battery_power_pin: GPIO5
|
enable_on_boot: false
|
||||||
main_power_pin: GPIO2
|
on_connect:
|
||||||
# update_interval: 10s
|
- if:
|
||||||
# battery_voltage:
|
condition:
|
||||||
# name: "${name} Battery"
|
lambda: return id(show_status) && !id(weather_page);
|
||||||
|
then:
|
||||||
|
- component.update: m5paper_display
|
||||||
|
|
||||||
|
api:
|
||||||
|
# status screen until Home Assistant connects (log viewers don't subscribe to states)
|
||||||
|
on_client_connected:
|
||||||
|
- delay: 2s
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- lambda: id(show_status) = false;
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return !id(weather_page);
|
||||||
|
then:
|
||||||
|
- component.update: m5paper_display
|
||||||
|
# outside the battery cycle: refresh a forecast older than an hour
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
and:
|
||||||
|
- lambda: |-
|
||||||
|
const auto &fc = m5paper_weather::forecast();
|
||||||
|
const auto now = id(rtc_time).now();
|
||||||
|
return !id(weather_page) && (!fc.valid() || !now.is_valid() || now.timestamp - fc.fetched_at >= 3600);
|
||||||
|
- api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- script.execute: fetch_forecast
|
||||||
|
on_client_disconnected:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
not:
|
||||||
|
api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- lambda: id(show_status) = true;
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return !id(weather_page);
|
||||||
|
then:
|
||||||
|
- component.update: m5paper_display
|
||||||
|
|
||||||
|
# I2C devices (i2c_main)
|
||||||
# 0x44 SHT30
|
# 0x44 SHT30
|
||||||
# 0x50
|
# 0x50 FM24C02 EEPROM (forecast storage, see include/m5paper_weather.h)
|
||||||
# 0x51
|
# 0x51 BM8563 RTC
|
||||||
# 0x5D GT911
|
# 0x5D GT911 touch (docs say 0x14; the driver tries 0x5D, then 0x14)
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
- id: i2c_main
|
- id: i2c_main
|
||||||
sda: 21
|
sda: 21
|
||||||
scl: 22
|
scl: 22
|
||||||
scan: true #false
|
scan: true
|
||||||
|
# Port A
|
||||||
# - id: i2c_a
|
# - id: i2c_a
|
||||||
# sda: 25
|
# sda: 25
|
||||||
# scl: 32
|
# scl: 32
|
||||||
# scan: true #false
|
# Port B
|
||||||
# - id: i2c_b
|
# - id: i2c_b
|
||||||
# sda: 26
|
# sda: 26
|
||||||
# scl: 33
|
# scl: 33
|
||||||
# scan: true #false
|
# Port C
|
||||||
# - id: i2c_c
|
# - id: i2c_c
|
||||||
# sda: 18
|
# sda: 18
|
||||||
# scl: 19
|
# scl: 19
|
||||||
# scan: true #false
|
|
||||||
|
|
||||||
spi:
|
spi:
|
||||||
clk_pin: 14
|
clk_pin: 14
|
||||||
miso_pin: 13
|
miso_pin: 13
|
||||||
mosi_pin: 12
|
mosi_pin: 12
|
||||||
|
|
||||||
|
switch:
|
||||||
|
# Power control MOS (no PMU on M5Paper); on battery the device powers off when this goes low
|
||||||
|
- platform: gpio
|
||||||
|
id: main_power
|
||||||
|
pin:
|
||||||
|
number: GPIO2
|
||||||
|
ignore_strapping_warning: true
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
|
internal: true
|
||||||
|
# External power (Grove ports)
|
||||||
|
- platform: gpio
|
||||||
|
id: ext_power
|
||||||
|
pin:
|
||||||
|
number: GPIO5
|
||||||
|
ignore_strapping_warning: true
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
|
internal: true
|
||||||
|
|
||||||
|
script:
|
||||||
|
# One wake-up on battery: refresh the forecast if it is old, draw it, power off
|
||||||
|
- id: battery_cycle
|
||||||
|
mode: single
|
||||||
|
then:
|
||||||
|
- lambda: id(weather_page) = true;
|
||||||
|
- wait_until:
|
||||||
|
condition:
|
||||||
|
lambda: return id(rtc_time).now().is_valid();
|
||||||
|
timeout: 10s
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: |-
|
||||||
|
const auto &fc = m5paper_weather::forecast();
|
||||||
|
const auto now = id(rtc_time).now();
|
||||||
|
return !fc.valid() || !now.is_valid() || now.timestamp - fc.fetched_at >= 6 * 3600 - 600;
|
||||||
|
then:
|
||||||
|
- wifi.enable:
|
||||||
|
- wait_until:
|
||||||
|
condition:
|
||||||
|
api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
timeout: 45s
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- script.execute: fetch_forecast
|
||||||
|
- wait_until:
|
||||||
|
condition:
|
||||||
|
lambda: return id(fetch_done);
|
||||||
|
timeout: 15s
|
||||||
|
- wait_until:
|
||||||
|
condition:
|
||||||
|
lambda: return !std::isnan(id(current_temperature).state);
|
||||||
|
timeout: 5s
|
||||||
|
- component.update: m5paper_display
|
||||||
|
# let the e-paper refresh finish before cutting power
|
||||||
|
- delay: 4s
|
||||||
|
- script.execute: try_sleep
|
||||||
|
|
||||||
|
- id: fetch_forecast
|
||||||
|
mode: single
|
||||||
|
then:
|
||||||
|
- lambda: id(fetch_done) = false;
|
||||||
|
- homeassistant.action:
|
||||||
|
action: weather.get_forecasts
|
||||||
|
data:
|
||||||
|
entity_id: ${weather_entity}
|
||||||
|
type: hourly
|
||||||
|
capture_response: true
|
||||||
|
on_success:
|
||||||
|
- lambda: |-
|
||||||
|
auto &fc = m5paper_weather::forecast();
|
||||||
|
if (m5paper_weather::parse(response, "${weather_entity}", id(rtc_time).now().timestamp, fc)) {
|
||||||
|
const bool saved = m5paper_weather::save(id(i2c_main), fc);
|
||||||
|
ESP_LOGI("m5paper", "Forecast: %u h%s", fc.count, saved ? "" : " (EEPROM write failed)");
|
||||||
|
} else {
|
||||||
|
ESP_LOGW("m5paper", "Forecast: unexpected response");
|
||||||
|
}
|
||||||
|
id(fetch_done) = true;
|
||||||
|
# the battery cycle draws on its own
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return !id(weather_page);
|
||||||
|
then:
|
||||||
|
- component.update: m5paper_display
|
||||||
|
on_error:
|
||||||
|
- lambda: |-
|
||||||
|
ESP_LOGW("m5paper", "Forecast: %s", error.c_str());
|
||||||
|
id(fetch_done) = true;
|
||||||
|
|
||||||
|
# Arm the RTC and cut power; if we are still running afterwards, we are on USB
|
||||||
|
- id: try_sleep
|
||||||
|
mode: single
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
// next quarter hour, 1:00-5:00 in one go
|
||||||
|
int duration = 900;
|
||||||
|
const auto now = id(rtc_time).now();
|
||||||
|
if (now.is_valid()) {
|
||||||
|
const int sod = now.hour * 3600 + now.minute * 60 + now.second;
|
||||||
|
int next = (sod / 900 + 1) * 900;
|
||||||
|
if (next - sod < 90) // the 1/60 Hz RTC timer may wake us up to a minute early
|
||||||
|
next += 900;
|
||||||
|
const int next_hour = (next / 3600) % 24;
|
||||||
|
if (next_hour >= 1 && next_hour < 5)
|
||||||
|
next = (next / 86400) * 86400 + 5 * 3600;
|
||||||
|
duration = next - sod;
|
||||||
|
}
|
||||||
|
if (duration > 255) // longer timers count whole minutes
|
||||||
|
duration = (duration + 59) / 60 * 60;
|
||||||
|
id(sleep_s) = std::min(duration, 255 * 60);
|
||||||
|
ESP_LOGI("m5paper", "Powering off for %d s", id(sleep_s));
|
||||||
|
- bm8563.start_timer:
|
||||||
|
duration: !lambda return id(sleep_s);
|
||||||
|
# a battery wake-up is shorter than boot_is_good_after: confirm the OTA image
|
||||||
|
# before cutting power, otherwise the bootloader rolls back
|
||||||
|
- safe_mode.mark_successful:
|
||||||
|
- delay: 200ms
|
||||||
|
- switch.turn_off: main_power
|
||||||
|
- delay: 2s
|
||||||
|
# still alive -> USB power
|
||||||
|
- switch.turn_on: main_power
|
||||||
|
- lambda: |-
|
||||||
|
ESP_LOGI("m5paper", "Still running, USB power");
|
||||||
|
id(usb_mode) = true;
|
||||||
|
id(weather_page) = false;
|
||||||
|
id(show_status) = !api::global_api_server->is_connected_with_state_subscription();
|
||||||
|
- wifi.enable:
|
||||||
|
- component.update: m5paper_display
|
||||||
|
|
||||||
|
interval:
|
||||||
|
# USB mode: the battery voltage drops once USB is unplugged -> go to battery mode
|
||||||
|
- interval: 5min
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: |-
|
||||||
|
static float last = NAN;
|
||||||
|
const float v = id(battery_voltage).state;
|
||||||
|
const bool dropped = id(usb_mode) && !std::isnan(last) && last - v > 0.03f;
|
||||||
|
last = v;
|
||||||
|
return dropped;
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
ESP_LOGI("m5paper", "Battery voltage dropping, checking USB");
|
||||||
|
id(usb_mode) = false;
|
||||||
|
- script.execute: battery_cycle
|
||||||
|
|
||||||
|
time:
|
||||||
|
- id: !extend ha_time
|
||||||
|
on_time_sync:
|
||||||
|
- bm8563.write_time
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return !id(weather_page);
|
||||||
|
then:
|
||||||
|
- component.update: m5paper_display
|
||||||
|
on_time:
|
||||||
|
- seconds: 0
|
||||||
|
minutes: /15
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return !id(show_status) && !id(weather_page);
|
||||||
|
then:
|
||||||
|
- component.update: m5paper_display
|
||||||
|
# USB mode: keep the stored forecast fresh for battery mode
|
||||||
|
- seconds: 0
|
||||||
|
minutes: 5
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
and:
|
||||||
|
- lambda: return id(usb_mode);
|
||||||
|
- api.connected:
|
||||||
|
state_subscription_only: true
|
||||||
|
then:
|
||||||
|
- script.execute: fetch_forecast
|
||||||
|
- platform: bm8563
|
||||||
|
id: rtc_time
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: ${name} Button UP
|
name: ${name} Button UP
|
||||||
id: button_up
|
id: button_up
|
||||||
pin:
|
pin:
|
||||||
number: 37
|
number: GPIO37
|
||||||
inverted: true
|
inverted: true
|
||||||
on_press:
|
on_press:
|
||||||
- component.update: m5paper_display
|
- component.update: m5paper_display
|
||||||
on_release:
|
|
||||||
- component.update: m5paper_display
|
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: ${name} Button Press
|
name: ${name} Button Press
|
||||||
id: button_press
|
id: button_press
|
||||||
pin:
|
pin:
|
||||||
number: 38
|
number: GPIO38
|
||||||
inverted: true
|
inverted: true
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: ${name} Button Down
|
name: ${name} Button Down
|
||||||
id: button_down
|
id: button_down
|
||||||
pin:
|
pin:
|
||||||
number: 39
|
number: GPIO39
|
||||||
inverted: true
|
inverted: true
|
||||||
- platform: touchscreen
|
- platform: touchscreen
|
||||||
name: ${name} Top Left Touch Button
|
name: ${name} Top Left Touch Button
|
||||||
@@ -103,51 +379,19 @@ binary_sensor:
|
|||||||
y_min: 0
|
y_min: 0
|
||||||
y_max: 100
|
y_max: 100
|
||||||
on_press:
|
on_press:
|
||||||
# Example of applying the sleep duration and shutting down to the lowest power mode
|
- script.execute: battery_cycle
|
||||||
- bm8563.apply_sleep_duration
|
|
||||||
- delay: 1s
|
|
||||||
- m5paper.shutdown_main_power
|
|
||||||
|
|
||||||
font:
|
button:
|
||||||
- file: "gfonts://Roboto"
|
- platform: template
|
||||||
id: font_default
|
name: Refresh Display
|
||||||
size: 20
|
entity_category: config
|
||||||
- file: "gfonts://Roboto"
|
on_press:
|
||||||
id: roboto_32
|
- component.update: m5paper_display
|
||||||
size: 32
|
- platform: template
|
||||||
- file: "gfonts://Roboto"
|
name: Refresh Forecast
|
||||||
id: roboto_24
|
entity_category: config
|
||||||
size: 24
|
on_press:
|
||||||
- file: "gfonts://Roboto"
|
- script.execute: fetch_forecast
|
||||||
id: roboto_12
|
|
||||||
size: 12
|
|
||||||
|
|
||||||
display:
|
|
||||||
- platform: it8951e
|
|
||||||
id: m5paper_display
|
|
||||||
cs_pin: GPIO15
|
|
||||||
reset_pin: GPIO23
|
|
||||||
busy_pin: GPIO27
|
|
||||||
rotation: 90
|
|
||||||
reversed: False
|
|
||||||
update_interval: "never"
|
|
||||||
lambda: |-
|
|
||||||
it.printf(25, 25, id(roboto_32), "%.1f°", id(current_temperature).state);
|
|
||||||
it.strftime(350, 25, id(roboto_32), "%H:%M:%S", id(rtc_time).now());
|
|
||||||
|
|
||||||
touchscreen:
|
|
||||||
- platform: gt911
|
|
||||||
display: m5paper_display
|
|
||||||
id: gt911_touchscreen
|
|
||||||
interrupt_pin: GPIO36
|
|
||||||
|
|
||||||
# display:
|
|
||||||
# - platform: waveshare_epaper
|
|
||||||
# cs_pin: 15
|
|
||||||
# model: 2.90in
|
|
||||||
# full_update_every: 30
|
|
||||||
# lambda: |-
|
|
||||||
# it.print(0, 0, id(font1), "Hello World!");
|
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: sht3xd
|
- platform: sht3xd
|
||||||
@@ -156,9 +400,252 @@ sensor:
|
|||||||
id: current_temperature
|
id: current_temperature
|
||||||
humidity:
|
humidity:
|
||||||
name: Humidity
|
name: Humidity
|
||||||
|
id: current_humidity
|
||||||
address: 0x44
|
address: 0x44
|
||||||
update_interval: 60s
|
update_interval: 60s
|
||||||
|
- platform: adc
|
||||||
|
id: battery_voltage
|
||||||
|
name: Battery Voltage
|
||||||
|
pin: GPIO35
|
||||||
|
attenuation: 12db
|
||||||
|
samples: 16
|
||||||
|
update_interval: 60s
|
||||||
|
entity_category: diagnostic
|
||||||
|
filters:
|
||||||
|
- multiply: 2.0 # 1:2 voltage divider
|
||||||
|
- platform: copy
|
||||||
|
source_id: battery_voltage
|
||||||
|
id: battery_level
|
||||||
|
name: Battery
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
device_class: battery
|
||||||
|
accuracy_decimals: 0
|
||||||
|
filters:
|
||||||
|
- calibrate_linear:
|
||||||
|
- 3.3 -> 0.0
|
||||||
|
- 4.15 -> 100.0
|
||||||
|
- clamp:
|
||||||
|
min_value: 0
|
||||||
|
max_value: 100
|
||||||
|
|
||||||
# touchscreen:
|
.image_defaults:
|
||||||
# - platform: gt911
|
icon_large: &icon_large {platform: file, type: BINARY, transparency: chroma_key, resize: 150x150}
|
||||||
# interrupt_pin: 36
|
icon_small: &icon_small {platform: file, type: BINARY, transparency: chroma_key, resize: 44x44}
|
||||||
|
|
||||||
|
image:
|
||||||
|
- platform: file
|
||||||
|
file: assets/images/made-for-esphome-white-on-black.svg
|
||||||
|
id: esphome_logo
|
||||||
|
resize: 460x136
|
||||||
|
type: GRAYSCALE
|
||||||
|
transparency: alpha_channel
|
||||||
|
# weather icons, same order as m5paper_weather::CONDITIONS
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-night", id: wl_clear_night}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-cloudy", id: wl_cloudy}
|
||||||
|
- {<<: *icon_large, file: "mdi:alert-circle-outline", id: wl_exceptional}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-fog", id: wl_fog}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-hail", id: wl_hail}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-lightning", id: wl_lightning}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-lightning-rainy", id: wl_lightning_rainy}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-partly-cloudy", id: wl_partlycloudy}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-pouring", id: wl_pouring}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-rainy", id: wl_rainy}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-snowy", id: wl_snowy}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-snowy-rainy", id: wl_snowy_rainy}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-sunny", id: wl_sunny}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-windy", id: wl_windy}
|
||||||
|
- {<<: *icon_large, file: "mdi:weather-windy-variant", id: wl_windy_variant}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-night", id: ws_clear_night}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-cloudy", id: ws_cloudy}
|
||||||
|
- {<<: *icon_small, file: "mdi:alert-circle-outline", id: ws_exceptional}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-fog", id: ws_fog}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-hail", id: ws_hail}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-lightning", id: ws_lightning}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-lightning-rainy", id: ws_lightning_rainy}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-partly-cloudy", id: ws_partlycloudy}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-pouring", id: ws_pouring}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-rainy", id: ws_rainy}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-snowy", id: ws_snowy}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-snowy-rainy", id: ws_snowy_rainy}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-sunny", id: ws_sunny}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-windy", id: ws_windy}
|
||||||
|
- {<<: *icon_small, file: "mdi:weather-windy-variant", id: ws_windy_variant}
|
||||||
|
|
||||||
|
font:
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_title
|
||||||
|
size: 48
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_status
|
||||||
|
size: 28
|
||||||
|
glyphs: &sk_glyphs "ÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽáäčďéíĺľňóôŕšťúýž°·"
|
||||||
|
glyphsets: [GF_Latin_Kernel]
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_small
|
||||||
|
size: 24
|
||||||
|
glyphs: *sk_glyphs
|
||||||
|
glyphsets: [GF_Latin_Kernel]
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_tiny
|
||||||
|
size: 18
|
||||||
|
glyphs: "0123456789% "
|
||||||
|
- file: "gfonts://Roboto@500"
|
||||||
|
id: font_date
|
||||||
|
size: 34
|
||||||
|
glyphs: *sk_glyphs
|
||||||
|
glyphsets: [GF_Latin_Kernel]
|
||||||
|
- file: "gfonts://Roboto@500"
|
||||||
|
id: font_hour
|
||||||
|
size: 28
|
||||||
|
glyphs: "0123456789-°"
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: font_temp
|
||||||
|
size: 110
|
||||||
|
glyphs: "0123456789.,-°C "
|
||||||
|
|
||||||
|
color:
|
||||||
|
- id: ink
|
||||||
|
red: 0%
|
||||||
|
green: 0%
|
||||||
|
blue: 0%
|
||||||
|
- id: paper
|
||||||
|
red: 100%
|
||||||
|
green: 100%
|
||||||
|
blue: 100%
|
||||||
|
|
||||||
|
display:
|
||||||
|
- platform: it8951
|
||||||
|
id: m5paper_display
|
||||||
|
model: m5stack-m5paper
|
||||||
|
rotation: 270
|
||||||
|
update_interval: never
|
||||||
|
# 1bpp: updates use the fast DU waveform, full GC16 refresh every 30 updates
|
||||||
|
grayscale: false
|
||||||
|
dithering: false
|
||||||
|
lambda: |-
|
||||||
|
// 540x960 portrait
|
||||||
|
it.fill(id(paper));
|
||||||
|
const int w = it.get_width();
|
||||||
|
const int cx = w / 2;
|
||||||
|
const float batt = id(battery_level).state;
|
||||||
|
const float temp = id(current_temperature).state;
|
||||||
|
const float hum = id(current_humidity).state;
|
||||||
|
|
||||||
|
// status screen after power-on, until Home Assistant connects
|
||||||
|
if (id(show_status) && !id(weather_page)) {
|
||||||
|
it.image(cx, 200, id(esphome_logo), ImageAlign::CENTER);
|
||||||
|
it.print(cx, 320, id(font_title), id(ink), TextAlign::CENTER, "${name}");
|
||||||
|
|
||||||
|
const bool wifi_ok = wifi::global_wifi_component->is_connected();
|
||||||
|
if (wifi_ok) {
|
||||||
|
it.printf(40, 440, id(font_status), id(ink), "WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi());
|
||||||
|
// IPv6 first (skip link-local), then IPv4
|
||||||
|
int y = 484;
|
||||||
|
char buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||||
|
for (bool v6 : {true, false}) {
|
||||||
|
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
|
||||||
|
if (!ip.is_set() || ip.is_ip6() != v6 || y > 590)
|
||||||
|
continue;
|
||||||
|
ip.str_to(buf);
|
||||||
|
if (v6 && strncmp(buf, "fe80", 4) == 0)
|
||||||
|
continue;
|
||||||
|
it.print(60, y, id(font_small), id(ink), buf);
|
||||||
|
y += 34;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it.print(40, 440, id(font_status), id(ink), "WiFi: connecting...");
|
||||||
|
}
|
||||||
|
const bool api_ok = api::global_api_server->is_connected_with_state_subscription();
|
||||||
|
it.print(40, 620, id(font_status), id(ink),
|
||||||
|
api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...");
|
||||||
|
if (!std::isnan(batt))
|
||||||
|
it.printf(40, 670, id(font_status), id(ink), "Battery: %.0f%% (%.2f V)", batt, id(battery_voltage).state);
|
||||||
|
it.printf(40, 720, id(font_status), id(ink), "Forecast: %u h stored", m5paper_weather::forecast().count);
|
||||||
|
it.printf(40, 900, id(font_small), id(ink), "ESPHome %s", ESPHOME_VERSION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// forecast screen
|
||||||
|
using namespace m5paper_weather;
|
||||||
|
image::Image *large[] = {id(wl_clear_night), id(wl_cloudy), id(wl_exceptional), id(wl_fog),
|
||||||
|
id(wl_hail), id(wl_lightning), id(wl_lightning_rainy), id(wl_partlycloudy),
|
||||||
|
id(wl_pouring), id(wl_rainy), id(wl_snowy), id(wl_snowy_rainy),
|
||||||
|
id(wl_sunny), id(wl_windy), id(wl_windy_variant)};
|
||||||
|
image::Image *small[] = {id(ws_clear_night), id(ws_cloudy), id(ws_exceptional), id(ws_fog),
|
||||||
|
id(ws_hail), id(ws_lightning), id(ws_lightning_rainy), id(ws_partlycloudy),
|
||||||
|
id(ws_pouring), id(ws_rainy), id(ws_snowy), id(ws_snowy_rainy),
|
||||||
|
id(ws_sunny), id(ws_windy), id(ws_windy_variant)};
|
||||||
|
static const char *const DAYS[] = {"Nedeľa", "Pondelok", "Utorok", "Streda", "Štvrtok", "Piatok", "Sobota"};
|
||||||
|
|
||||||
|
const auto &fc = m5paper_weather::forecast();
|
||||||
|
const auto now = id(rtc_time).now();
|
||||||
|
|
||||||
|
// header: date, battery
|
||||||
|
if (now.is_valid())
|
||||||
|
it.printf(24, 16, id(font_date), id(ink), "%s %d. %d.", DAYS[(now.day_of_week + 6) % 7], now.day_of_month,
|
||||||
|
now.month);
|
||||||
|
if (!std::isnan(batt))
|
||||||
|
it.printf(w - 24, 22, id(font_small), id(ink), TextAlign::TOP_RIGHT, "%.0f %%", batt);
|
||||||
|
|
||||||
|
const Hour *cur = now.is_valid() ? fc.at(now.timestamp) : nullptr;
|
||||||
|
if (cur == nullptr) {
|
||||||
|
it.print(cx, 230, id(font_status), id(ink), TextAlign::CENTER, "Predpoveď nie je k dispozícii");
|
||||||
|
} else {
|
||||||
|
// current hour
|
||||||
|
if (cur->cond < NUM_CONDITIONS)
|
||||||
|
it.image(24, 80, large[cur->cond], id(ink));
|
||||||
|
if (cur->temp2 != NO_TEMP)
|
||||||
|
it.printf(200, 70, id(font_temp), id(ink), "%.0f°", cur->temp2 / 2.0f);
|
||||||
|
it.print(204, 200, id(font_status), id(ink), condition_name(cur->cond));
|
||||||
|
if (cur->wind != NO_VALUE)
|
||||||
|
it.printf(204, 245, id(font_small), id(ink), "vietor %u km/h", cur->wind);
|
||||||
|
if (cur->pop != NO_VALUE)
|
||||||
|
it.printf(204, 280, id(font_small), id(ink), "zrážky %u %%", cur->pop);
|
||||||
|
|
||||||
|
// next 24 hours: 3 columns x 8 rows
|
||||||
|
const uint32_t first = (now.timestamp / 3600 + 1) * 3600;
|
||||||
|
const int top = 440, row_h = 60, col_w = w / 3;
|
||||||
|
for (int i = 0; i < 24; i++) {
|
||||||
|
const Hour *h = fc.at(first + i * 3600);
|
||||||
|
if (h == nullptr)
|
||||||
|
break;
|
||||||
|
const int x = (i / 8) * col_w + 12;
|
||||||
|
const int y = top + (i % 8) * row_h;
|
||||||
|
const auto t = ESPTime::from_epoch_local(first + i * 3600);
|
||||||
|
it.printf(x, y + 14, id(font_hour), id(ink), "%02d", t.hour);
|
||||||
|
if (h->cond < NUM_CONDITIONS)
|
||||||
|
it.image(x + 42, y + 8, small[h->cond], id(ink));
|
||||||
|
if (h->temp2 != NO_TEMP)
|
||||||
|
it.printf(x + 94, y + 6, id(font_hour), id(ink), "%.0f°", h->temp2 / 2.0f);
|
||||||
|
if (h->pop != NO_VALUE && h->pop >= 10)
|
||||||
|
it.printf(x + 94, y + 38, id(font_tiny), id(ink), "%u %%", h->pop);
|
||||||
|
}
|
||||||
|
for (int c = 1; c < 3; c++)
|
||||||
|
it.vertical_line(c * col_w, top, 8 * row_h, id(ink));
|
||||||
|
}
|
||||||
|
|
||||||
|
// indoor
|
||||||
|
it.horizontal_line(24, 340, w - 48, id(ink));
|
||||||
|
it.printf(cx, 380, id(font_status), id(ink), TextAlign::CENTER, "Doma %.1f °C · %.0f %%", temp, hum);
|
||||||
|
it.horizontal_line(24, 420, w - 48, id(ink));
|
||||||
|
|
||||||
|
// footer
|
||||||
|
if (fc.valid()) {
|
||||||
|
const auto f = ESPTime::from_epoch_local(fc.fetched_at);
|
||||||
|
it.printf(24, 944, id(font_small), id(ink), TextAlign::BOTTOM_LEFT, "predpoveď %d. %d. %02d:%02d",
|
||||||
|
f.day_of_month, f.month, f.hour, f.minute);
|
||||||
|
}
|
||||||
|
if (now.is_valid())
|
||||||
|
it.printf(w - 24, 944, id(font_small), id(ink), TextAlign::BOTTOM_RIGHT, "%02d:%02d", now.hour, now.minute);
|
||||||
|
|
||||||
|
touchscreen:
|
||||||
|
- platform: gt911
|
||||||
|
id: gt911_touchscreen
|
||||||
|
display: m5paper_display
|
||||||
|
# GPIO36 is input-only; INT is only driven as output when reset_pin is set
|
||||||
|
interrupt_pin:
|
||||||
|
number: GPIO36
|
||||||
|
mode:
|
||||||
|
input: true
|
||||||
|
output: false
|
||||||
@@ -26,6 +26,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# PINOUTS: M5ATOM
|
# PINOUTS: M5ATOM
|
||||||
# IR G12
|
# IR G12
|
||||||
@@ -228,9 +229,7 @@ button:
|
|||||||
- pzemac.reset_energy: pzemac_l3
|
- pzemac.reset_energy: pzemac_l3
|
||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: homeassistant
|
- id: !extend ha_time
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
on_time:
|
on_time:
|
||||||
- cron: '0 0 0 * * *'
|
- cron: '0 0 0 * * *'
|
||||||
then:
|
then:
|
||||||
|
|||||||
@@ -136,9 +136,7 @@ nspanel_lovelace:
|
|||||||
thermo_entity_id: climate.bt_trv_bedroom
|
thermo_entity_id: climate.bt_trv_bedroom
|
||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: homeassistant
|
- id: !extend ha_time
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
on_time_sync:
|
on_time_sync:
|
||||||
- script.execute: apply_sleep_dim
|
- script.execute: apply_sleep_dim
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ external_components:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
# common: !include common/common_dev.yaml
|
# common: !include common/common_dev.yaml
|
||||||
|
|
||||||
# web_server:
|
# web_server:
|
||||||
@@ -540,11 +541,6 @@ sensor:
|
|||||||
- platform: template
|
- platform: template
|
||||||
id: room_temperature_id
|
id: room_temperature_id
|
||||||
|
|
||||||
time:
|
|
||||||
platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
text_sensor:
|
text_sensor:
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: name_day
|
id: name_day
|
||||||
|
|||||||
@@ -185,11 +185,6 @@ nspanel_lovelace:
|
|||||||
- name: Auto-zatváranie
|
- name: Auto-zatváranie
|
||||||
entity_id: switch.gate_auto_close
|
entity_id: switch.gate_auto_close
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: Left Button
|
name: Left Button
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ external_components:
|
|||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
|
||||||
|
script:
|
||||||
|
- id: apply_sleep_dim
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
uint8_t dim;
|
||||||
|
if (id(bedroom_lights).state) dim = 5; // sleepOverride
|
||||||
|
else if (id(sun_id).is_above_horizon(-6)) dim = 5; // day
|
||||||
|
else dim = 0; // night
|
||||||
|
id(nspanel_id).set_display_inactive_dim(dim);
|
||||||
|
|
||||||
api:
|
api:
|
||||||
homeassistant_services: true
|
homeassistant_services: true
|
||||||
homeassistant_states: true
|
homeassistant_states: true
|
||||||
@@ -135,9 +145,9 @@ nspanel_lovelace:
|
|||||||
entity_id: switch.gate_auto_close
|
entity_id: switch.gate_auto_close
|
||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: homeassistant
|
- id: !extend ha_time
|
||||||
id: ha_time
|
on_time_sync:
|
||||||
timezone: Europe/Bratislava
|
- script.execute: apply_sleep_dim
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
@@ -161,6 +171,12 @@ binary_sensor:
|
|||||||
entity_id: light.bedside_left
|
entity_id: light.bedside_left
|
||||||
- lambda: 'id(nspanel_id).render_default_page();'
|
- lambda: 'id(nspanel_id).render_default_page();'
|
||||||
|
|
||||||
|
- platform: homeassistant # sleepOverride
|
||||||
|
id: bedroom_lights
|
||||||
|
entity_id: light.office_j_lights
|
||||||
|
on_state:
|
||||||
|
- script.execute: apply_sleep_dim
|
||||||
|
|
||||||
output:
|
output:
|
||||||
- platform: ledc
|
- platform: ledc
|
||||||
id: buzzer_out
|
id: buzzer_out
|
||||||
@@ -186,6 +202,20 @@ light:
|
|||||||
# # name: Relay 2
|
# # name: Relay 2
|
||||||
# # output: relay_2
|
# # output: relay_2
|
||||||
|
|
||||||
|
sun:
|
||||||
|
id: sun_id
|
||||||
|
time_id: ha_time
|
||||||
|
latitude: 48.15°
|
||||||
|
longitude: 17.11°
|
||||||
|
on_sunrise:
|
||||||
|
- elevation: -6°
|
||||||
|
then:
|
||||||
|
- script.execute: apply_sleep_dim
|
||||||
|
on_sunset:
|
||||||
|
- elevation: -6°
|
||||||
|
then:
|
||||||
|
- script.execute: apply_sleep_dim
|
||||||
|
|
||||||
switch:
|
switch:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: Screen Power
|
name: Screen Power
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ external_components:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- id: geiger_click
|
- id: geiger_click
|
||||||
@@ -158,11 +159,6 @@ nspanel_lovelace:
|
|||||||
- name: Auto-zatváranie
|
- name: Auto-zatváranie
|
||||||
entity_id: switch.gate_auto_close
|
entity_id: switch.gate_auto_close
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: Left Button
|
name: Left Button
|
||||||
|
|||||||
@@ -214,11 +214,6 @@ nspanel_lovelace:
|
|||||||
- name: Rast trávy
|
- name: Rast trávy
|
||||||
entity_id: automation.irrigation_grow_grass
|
entity_id: automation.irrigation_grow_grass
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: Left Button
|
name: Left Button
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# GPIO21 UART TX
|
# GPIO21 UART TX
|
||||||
# GPIO20 UART RX
|
# GPIO20 UART RX
|
||||||
|
|||||||
+1
-5
@@ -37,6 +37,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
|
|
||||||
# PINOUTS: M5ATOM+Atom Hub Switch
|
# PINOUTS: M5ATOM+Atom Hub Switch
|
||||||
@@ -54,11 +55,6 @@ packages:
|
|||||||
# Relay1 G22
|
# Relay1 G22
|
||||||
# Relay2 G19
|
# Relay2 G19
|
||||||
|
|
||||||
time:
|
|
||||||
- platform: homeassistant
|
|
||||||
id: ha_time
|
|
||||||
timezone: Europe/Bratislava
|
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
# - id: i2c0
|
# - id: i2c0
|
||||||
# sda: 26
|
# sda: 26
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
uart:
|
uart:
|
||||||
- id: cse7766_uart
|
- id: cse7766_uart
|
||||||
@@ -187,5 +188,3 @@ switch:
|
|||||||
- switch.turn_off: relay_off # bi-stable relay so no need to keep on
|
- switch.turn_off: relay_off # bi-stable relay so no need to keep on
|
||||||
interlock: [relay_on]
|
interlock: [relay_on]
|
||||||
|
|
||||||
time:
|
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
+1
-4
@@ -2,7 +2,7 @@
|
|||||||
substitutions:
|
substitutions:
|
||||||
device: pow-washer
|
device: pow-washer
|
||||||
name: Washer POW
|
name: Washer POW
|
||||||
comment:
|
comment: "spare"
|
||||||
area: Washroom
|
area: Washroom
|
||||||
update_interval: 10s
|
update_interval: 10s
|
||||||
|
|
||||||
@@ -186,6 +186,3 @@ switch:
|
|||||||
- delay: 500ms
|
- delay: 500ms
|
||||||
- switch.turn_off: relay_off # bi-stable relay so no need to keep on
|
- switch.turn_off: relay_off # bi-stable relay so no need to keep on
|
||||||
interlock: [relay_on]
|
interlock: [relay_on]
|
||||||
|
|
||||||
time:
|
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
@@ -19,6 +19,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
spi:
|
spi:
|
||||||
clk_pin: 23
|
clk_pin: 23
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
# logger:
|
# logger:
|
||||||
# hardware_uart: UART0
|
# hardware_uart: UART0
|
||||||
|
|||||||
+1
-2
@@ -21,6 +21,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
sda: 25
|
sda: 25
|
||||||
@@ -109,5 +110,3 @@ switch:
|
|||||||
number: 19
|
number: 19
|
||||||
mode: OUTPUT
|
mode: OUTPUT
|
||||||
|
|
||||||
time:
|
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
+1
-2
@@ -121,8 +121,7 @@ switch:
|
|||||||
# number: GPIO00
|
# number: GPIO00
|
||||||
# inverted: True
|
# inverted: True
|
||||||
|
|
||||||
time:
|
# time:
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
# - platform: homeassistant
|
# - platform: homeassistant
|
||||||
# timezone: Europe/Bratislava
|
# timezone: Europe/Bratislava
|
||||||
# on_time:
|
# on_time:
|
||||||
|
|||||||
+1
-2
@@ -122,8 +122,7 @@ switch:
|
|||||||
on_turn_off:
|
on_turn_off:
|
||||||
- light.turn_off: light_red
|
- light.turn_off: light_red
|
||||||
|
|
||||||
time:
|
# time:
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
# - platform: homeassistant
|
# - platform: homeassistant
|
||||||
# timezone: Europe/Bratislava
|
# timezone: Europe/Bratislava
|
||||||
# on_time:
|
# on_time:
|
||||||
|
|||||||
+1
-2
@@ -144,8 +144,7 @@ switch:
|
|||||||
# number: GPIO00
|
# number: GPIO00
|
||||||
# inverted: True
|
# inverted: True
|
||||||
|
|
||||||
time:
|
# time:
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
# - platform: homeassistant
|
# - platform: homeassistant
|
||||||
# timezone: Europe/Bratislava
|
# timezone: Europe/Bratislava
|
||||||
# on_time:
|
# on_time:
|
||||||
|
|||||||
+23
-9
@@ -33,6 +33,7 @@ psram:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
external_components:
|
external_components:
|
||||||
- source: github://m5stack/esphome-yaml/components
|
- source: github://m5stack/esphome-yaml/components
|
||||||
@@ -464,9 +465,9 @@ spi:
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
- platform: file
|
- platform: file
|
||||||
file: assets/images/esphome-logo-text-on-dark.svg
|
file: assets/images/made-for-esphome-white-on-black.svg
|
||||||
id: esphome_logo
|
id: esphome_logo
|
||||||
resize: 280x56
|
resize: 230x68
|
||||||
type: RGB565
|
type: RGB565
|
||||||
transparency: alpha_channel
|
transparency: alpha_channel
|
||||||
- platform: file
|
- platform: file
|
||||||
@@ -545,22 +546,36 @@ display:
|
|||||||
if (id(show_status)) {
|
if (id(show_status)) {
|
||||||
first_draw = true;
|
first_draw = true;
|
||||||
it.fill(id(face_bg));
|
it.fill(id(face_bg));
|
||||||
it.image(160, 45, id(esphome_logo), ImageAlign::CENTER);
|
it.image(160, 46, id(esphome_logo), ImageAlign::CENTER);
|
||||||
it.print(160, 95, id(font_title), id(face_fg), TextAlign::CENTER, "${name}");
|
it.print(160, 95, id(font_title), id(face_fg), TextAlign::CENTER, "${name}");
|
||||||
|
|
||||||
if (wifi_ok) {
|
if (wifi_ok) {
|
||||||
it.printf(20, 135, id(font_status), id(status_ok), "WiFi: %s (%d dBm)",
|
it.printf(20, 114, id(font_status), id(status_ok), "WiFi: connected (%d dBm)", rssi);
|
||||||
id(ip_address_sensor).state.c_str(), rssi);
|
// one global IPv6 (skip link-local) and one IPv4 address
|
||||||
|
int y = 133;
|
||||||
|
char buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||||
|
for (bool v6 : {true, false}) {
|
||||||
|
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
|
||||||
|
if (!ip.is_set() || ip.is_ip6() != v6)
|
||||||
|
continue;
|
||||||
|
ip.str_to(buf);
|
||||||
|
if (v6 && strncmp(buf, "fe80", 4) == 0)
|
||||||
|
continue;
|
||||||
|
it.print(30, y, id(font_status), id(status_dim), buf);
|
||||||
|
y += 19;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
it.print(20, 135, id(font_status), id(status_wait), "WiFi: connecting...");
|
it.print(20, 114, id(font_status), id(status_wait), "WiFi: connecting...");
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool api_ok = api::global_api_server->is_connected();
|
const bool api_ok = api::global_api_server->is_connected();
|
||||||
it.print(20, 162, id(font_status), api_ok ? id(status_ok) : id(status_wait),
|
it.print(20, 172, id(font_status), api_ok ? id(status_ok) : id(status_wait),
|
||||||
api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...");
|
api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...");
|
||||||
|
|
||||||
if (batt >= 0) {
|
if (batt >= 0) {
|
||||||
it.printf(20, 189, id(font_status), batt <= 20 && !charging ? id(status_low) : id(status_dim),
|
it.printf(20, 191, id(font_status), batt <= 20 && !charging ? id(status_low) : id(status_dim),
|
||||||
"Battery: %d%%%s", batt, charging ? " (charging)" : "");
|
"Battery: %d%%%s", batt, charging ? " (charging)" : "");
|
||||||
}
|
}
|
||||||
it.printf(20, 212, id(font_status), id(status_dim), "ESPHome %s", ESPHOME_VERSION);
|
it.printf(20, 212, id(font_status), id(status_dim), "ESPHome %s", ESPHOME_VERSION);
|
||||||
@@ -883,4 +898,3 @@ time:
|
|||||||
id: rtc_time
|
id: rtc_time
|
||||||
i2c_id: i2c_bsp
|
i2c_id: i2c_bsp
|
||||||
update_interval: never
|
update_interval: never
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
@@ -21,6 +21,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
logger:
|
logger:
|
||||||
logs:
|
logs:
|
||||||
@@ -49,6 +50,20 @@ color:
|
|||||||
hex: ff0000
|
hex: ff0000
|
||||||
- id: my_white
|
- id: my_white
|
||||||
hex: ffffff
|
hex: ffffff
|
||||||
|
- id: status_ok
|
||||||
|
hex: 4dd966
|
||||||
|
- id: status_wait
|
||||||
|
hex: ffb300
|
||||||
|
- id: status_dim
|
||||||
|
hex: 8c8c8c
|
||||||
|
|
||||||
|
image:
|
||||||
|
- platform: file
|
||||||
|
file: assets/images/made-for-esphome-white-on-black.svg
|
||||||
|
id: esphome_logo
|
||||||
|
resize: 220x65
|
||||||
|
type: RGB565
|
||||||
|
transparency: alpha_channel
|
||||||
|
|
||||||
font:
|
font:
|
||||||
- file: "gfonts://Roboto"
|
- file: "gfonts://Roboto"
|
||||||
@@ -107,6 +122,46 @@ display:
|
|||||||
invert_colors: false
|
invert_colors: false
|
||||||
lambda: |-
|
lambda: |-
|
||||||
it.fill(Color::BLACK);
|
it.fill(Color::BLACK);
|
||||||
|
|
||||||
|
// status screen until Home Assistant connects (240x320)
|
||||||
|
if (!api::global_api_server->is_connected_with_state_subscription()) {
|
||||||
|
const int cx = it.get_width() / 2;
|
||||||
|
it.image(cx, 48, id(esphome_logo), ImageAlign::CENTER);
|
||||||
|
it.print(cx, 100, id(roboto_24), id(my_white), TextAlign::CENTER, "${name}");
|
||||||
|
|
||||||
|
int y = 130;
|
||||||
|
if (wifi::global_wifi_component->is_connected()) {
|
||||||
|
it.printf(8, y, id(roboto_16), id(status_ok), "WiFi: connected (%d dBm)",
|
||||||
|
wifi::global_wifi_component->wifi_rssi());
|
||||||
|
y += 22;
|
||||||
|
// IPv6 first (skip link-local), then IPv4
|
||||||
|
char buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||||
|
for (bool v6 : {true, false}) {
|
||||||
|
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
|
||||||
|
if (!ip.is_set() || ip.is_ip6() != v6 || y > 200)
|
||||||
|
continue;
|
||||||
|
ip.str_to(buf);
|
||||||
|
if (v6 && strncmp(buf, "fe80", 4) == 0)
|
||||||
|
continue;
|
||||||
|
it.print(8, y, id(roboto_12), id(status_dim), buf);
|
||||||
|
y += 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it.print(8, y, id(roboto_16), id(status_wait), "WiFi: connecting...");
|
||||||
|
y += 22;
|
||||||
|
}
|
||||||
|
y += 8;
|
||||||
|
it.print(8, y, id(roboto_16), api::global_api_server->is_connected() ? id(status_ok) : id(status_wait),
|
||||||
|
api::global_api_server->is_connected() ? "Home Assistant: connected" : "Home Assistant: waiting...");
|
||||||
|
|
||||||
|
it.printf(8, it.get_height() - 8, id(roboto_12), id(status_dim), TextAlign::BOTTOM_LEFT, "ESPHome %s",
|
||||||
|
ESPHOME_VERSION);
|
||||||
|
it.printf(it.get_width() - 8, it.get_height() - 8, id(roboto_12), id(status_dim), TextAlign::BOTTOM_RIGHT,
|
||||||
|
"up %us", (unsigned) (millis() / 1000));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
it.print(0, 0, id(b612_12), id(my_white), TextAlign::TOP_LEFT, "HELLO WORLD!");
|
it.print(0, 0, id(b612_12), id(my_white), TextAlign::TOP_LEFT, "HELLO WORLD!");
|
||||||
it.print(0, 14, id(b612_12), id(my_green), TextAlign::TOP_LEFT, "HELLO WORLD!");
|
it.print(0, 14, id(b612_12), id(my_green), TextAlign::TOP_LEFT, "HELLO WORLD!");
|
||||||
it.print(0, 28, id(b612_12), id(my_magenta), TextAlign::TOP_LEFT, "HELLO WORLD!");
|
it.print(0, 28, id(b612_12), id(my_magenta), TextAlign::TOP_LEFT, "HELLO WORLD!");
|
||||||
|
|||||||
+1
-2
@@ -34,6 +34,7 @@ esp32:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
common: !include common/common.yaml
|
common: !include common/common.yaml
|
||||||
|
ipv6_only: !include common/ipv6_only.yaml
|
||||||
|
|
||||||
api:
|
api:
|
||||||
actions:
|
actions:
|
||||||
@@ -358,5 +359,3 @@ valve:
|
|||||||
close_action:
|
close_action:
|
||||||
- switch.turn_off: irrigation_relay_8
|
- switch.turn_off: irrigation_relay_8
|
||||||
|
|
||||||
time:
|
|
||||||
- !include common/time/homeassistant.yaml
|
|
||||||
Reference in new issue
Block a user