mirror of
https://github.com/randybb/esphome-configs.git
synced 2026-10-14 08:35:12 +02:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8738b39e0 | ||
|
|
7ee975875a | ||
|
|
dd508aec99 | ||
|
|
e13be73596 | ||
|
|
b523963944 | ||
|
|
6ca813fb64 | ||
|
|
d80abdbb90 | ||
|
|
29b375df68 | ||
|
|
5db5987405 | ||
|
|
dec5e5de0a | ||
|
|
88e1214058 | ||
|
|
61032ad248 | ||
|
|
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/wifi_info.yaml
|
||||
|
||||
# time:
|
||||
#. - <<: !include time/homeassistant.yaml
|
||||
time:
|
||||
- <<: !include time/homeassistant.yaml
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
@@ -24,5 +24,8 @@ text_sensor:
|
||||
- <<: !include text_sensor/version.yaml
|
||||
- <<: !include text_sensor/wifi_info.yaml
|
||||
|
||||
time:
|
||||
- <<: !include time/homeassistant.yaml
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
@@ -23,5 +23,8 @@ text_sensor:
|
||||
- <<: !include text_sensor/version.yaml
|
||||
- <<: !include text_sensor/wifi_info.yaml
|
||||
|
||||
time:
|
||||
- <<: !include time/homeassistant.yaml
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
@@ -21,5 +21,8 @@ text_sensor:
|
||||
- <<: !include text_sensor/version.yaml
|
||||
- <<: !include text_sensor/ethernet_info.yaml
|
||||
|
||||
time:
|
||||
- <<: !include time/homeassistant.yaml
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
@@ -27,3 +27,6 @@ text_sensor:
|
||||
- <<: !include text_sensor/git_ref.yaml
|
||||
- <<: !include text_sensor/version.yaml
|
||||
- <<: !include text_sensor/wifi_info.yaml
|
||||
|
||||
time:
|
||||
- <<: !include time/homeassistant.yaml
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
# 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:
|
||||
# network enable_ipv4 (esphome/esphome#19750), drop once merged
|
||||
- source: github://pr#19750
|
||||
components: [const, network, wifi]
|
||||
# audio HTTP reader over IPv6 (dual-stack hosts resolve A first), drop once in #19750
|
||||
- source: github://randybb/esphome-components
|
||||
components: [audio]
|
||||
refresh: 1h
|
||||
|
||||
wifi:
|
||||
networks:
|
||||
- id: !extend wifi_iot
|
||||
ssid: !secret wifi_ssid_ipv6
|
||||
password: !secret wifi_password_ipv6
|
||||
|
||||
esp32:
|
||||
framework:
|
||||
components:
|
||||
# mdns 1.12.0 + fix for "A 0.0.0.0" without IPv4 (espressif/esp-protocols PR pending), drop once released
|
||||
- name: espressif/mdns
|
||||
source: https://github.com/randybb/esp-protocols.git
|
||||
ref: mdns-v1.12.0-zero-ipv4-test
|
||||
path: components/mdns
|
||||
# IDF v5.5.5 lwip + .local resolution over IPv6 (espressif/esp-lwip#94), drop once in IDF
|
||||
- name: lwip
|
||||
source: https://github.com/randybb/esp-lwip.git
|
||||
ref: idf-v5.5.5-lwip-component
|
||||
path: lwip
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
enable_ipv4: false
|
||||
min_ipv6_addr_count: 1
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
# Sonoff NSPanel with the native Lovelace UI (randybb/esphome-nspanel-lovelace-native).
|
||||
# The device file adds the cards, buttons and relays:
|
||||
# packages:
|
||||
# common: !include common/common.yaml
|
||||
# nspanel: !include
|
||||
# file: common/nspanel.yaml
|
||||
# vars:
|
||||
# temperature_entity: sensor.zigbee_bedroom_temperature
|
||||
# nspanel_lovelace:
|
||||
# cards: [...]
|
||||
defaults:
|
||||
temperature_name: Temperature
|
||||
ntc_reference_temperature: 25°C
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- switch.turn_off: screen_power
|
||||
- delay: 1s
|
||||
- switch.turn_on: screen_power
|
||||
- delay: 2s
|
||||
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
minimum_chip_revision: 3.0
|
||||
sram1_as_iram: true
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/randybb/esphome-nspanel-lovelace-native
|
||||
ref: devices
|
||||
refresh: 0s
|
||||
components: [nspanel_lovelace]
|
||||
|
||||
api:
|
||||
homeassistant_services: true
|
||||
homeassistant_states: true
|
||||
actions:
|
||||
- action: play_rtttl
|
||||
variables:
|
||||
song: string
|
||||
then:
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify
|
||||
variables:
|
||||
heading: string
|
||||
message: string
|
||||
song: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify_fullscreen
|
||||
variables:
|
||||
title: string
|
||||
description: string
|
||||
button1: string
|
||||
button2: string
|
||||
timeout: int
|
||||
icon: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
|
||||
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
|
||||
|
||||
uart:
|
||||
id: display_uart
|
||||
tx_pin: 16
|
||||
rx_pin: 17
|
||||
baud_rate: 115200
|
||||
|
||||
nspanel_lovelace:
|
||||
id: nspanel_id
|
||||
sleep_timeout: 10
|
||||
experimental_vertical_ui: true
|
||||
locale:
|
||||
language: sk
|
||||
temperature_unit: celcius
|
||||
screensaver:
|
||||
time_id: ha_time
|
||||
time_format: '%H:%M'
|
||||
date_format: '%A, %d. %b. %Y'
|
||||
weather:
|
||||
entity_id: sensor.weather_forecast_daily
|
||||
icon_color: [255, 255, 255]
|
||||
entities:
|
||||
- entity_id: ${temperature_entity}
|
||||
icon:
|
||||
value: home-thermometer-outline
|
||||
color: [255, 255, 255]
|
||||
precision: 1
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
id: buzzer_out
|
||||
pin:
|
||||
number: 21
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: Screen Power
|
||||
id: screen_power
|
||||
entity_category: config
|
||||
pin:
|
||||
number: 4
|
||||
inverted: true
|
||||
restore_mode: ALWAYS_ON
|
||||
|
||||
rtttl:
|
||||
id: buzzer
|
||||
output: buzzer_out
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: ntc_source
|
||||
pin: 38
|
||||
update_interval: 5min
|
||||
attenuation: 12db
|
||||
|
||||
- platform: resistance
|
||||
id: resistance_sensor
|
||||
sensor: ntc_source
|
||||
configuration: DOWNSTREAM
|
||||
resistor: 11.2kOhm
|
||||
|
||||
- platform: ntc
|
||||
name: ${temperature_name}
|
||||
id: temperature
|
||||
sensor: resistance_sensor
|
||||
calibration:
|
||||
b_constant: 3950
|
||||
reference_temperature: ${ntc_reference_temperature}
|
||||
reference_resistance: 10kOhm
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: name_day
|
||||
entity_id: sensor.nameday_sk
|
||||
on_value:
|
||||
- lambda: |-
|
||||
id(nspanel_id).set_time_format("%H:%M~" + x);
|
||||
id(nspanel_id).update_time();
|
||||
|
||||
# HMI protocol version reported by the TFT on startup (52/53 for this UI)
|
||||
- platform: template
|
||||
name: TFT Version
|
||||
entity_category: diagnostic
|
||||
update_interval: 60s
|
||||
lambda: |-
|
||||
using esphome::nspanel_lovelace::Configuration;
|
||||
const auto ver = Configuration::get_version();
|
||||
if (ver == 0) return {"unknown"};
|
||||
return {std::to_string(ver) + " (" + Configuration::get_model_str() + ")"};
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
# NSPanel screensaver dims to 0 at night (sun below -6°), unless the room lights are on.
|
||||
# packages:
|
||||
# sleep_dim: !include
|
||||
# file: common/nspanel_sleep_dim.yaml
|
||||
# vars:
|
||||
# sleep_override_entity: light.bedroom_lights
|
||||
script:
|
||||
- id: apply_sleep_dim
|
||||
then:
|
||||
- lambda: |-
|
||||
uint8_t dim;
|
||||
if (id(sleep_override).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);
|
||||
|
||||
time:
|
||||
- id: !extend ha_time
|
||||
on_time_sync:
|
||||
- script.execute: apply_sleep_dim
|
||||
|
||||
binary_sensor:
|
||||
- platform: homeassistant # sleepOverride
|
||||
id: sleep_override
|
||||
entity_id: ${sleep_override_entity}
|
||||
on_state:
|
||||
- script.execute: apply_sleep_dim
|
||||
|
||||
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
|
||||
+3
-1
@@ -2,7 +2,9 @@
|
||||
wifi:
|
||||
# reboot_timeout: 0s
|
||||
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
|
||||
enable_btm: true
|
||||
enable_rrm: true
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
// Upcoming calendar events for mcu-m5paper: parsed from the calendar.get_events
|
||||
// response and kept in flash (NVS), so they survive the power-off between RTC wake-ups.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/time.h"
|
||||
|
||||
namespace m5paper_calendar {
|
||||
|
||||
static constexpr uint8_t MAX_EVENTS = 32;
|
||||
static constexpr size_t SUMMARY_LEN = 44;
|
||||
static constexpr uint16_t ALL_DAY = 0xFFFF;
|
||||
static constexpr uint32_t PREF_HASH = 0x6D35C41A;
|
||||
|
||||
enum Kind : uint8_t { BIRTHDAY = 0, WASTE };
|
||||
|
||||
struct Event {
|
||||
uint16_t day; // local date, days since 1970-01-01
|
||||
uint16_t minute; // local start time in minutes after midnight, ALL_DAY if none
|
||||
uint8_t kind;
|
||||
char summary[SUMMARY_LEN + 1];
|
||||
};
|
||||
|
||||
struct Events {
|
||||
uint32_t fetched_at{0}; // UTC epoch of the download
|
||||
uint8_t count{0};
|
||||
Event items[MAX_EVENTS]{};
|
||||
|
||||
bool valid() const { return this->fetched_at != 0; }
|
||||
};
|
||||
|
||||
// the events in RAM, loaded from flash at boot
|
||||
inline Events &events() {
|
||||
static Events ev;
|
||||
return ev;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
inline int32_t local_day(const esphome::ESPTime &t) { return days_from_civil(t.year, t.month, t.day_of_month); }
|
||||
|
||||
// 0 = Monday
|
||||
inline int weekday(int32_t day) { return (day + 3) % 7; }
|
||||
|
||||
// "2026-10-01" (all-day) or "2026-10-01T10:00:00+02:00" -> local day and minute; false on error
|
||||
inline bool parse_start(const char *s, uint16_t &day, uint16_t &minute) {
|
||||
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 < 3)
|
||||
return false;
|
||||
if (n < 5) {
|
||||
day = days_from_civil(y, mo, d);
|
||||
minute = ALL_DAY;
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (ts <= 0)
|
||||
return false;
|
||||
const auto t = esphome::ESPTime::from_epoch_local(ts);
|
||||
day = local_day(t);
|
||||
minute = t.hour * 60 + t.minute;
|
||||
return true;
|
||||
}
|
||||
|
||||
// copy at most SUMMARY_LEN bytes without cutting a UTF-8 sequence
|
||||
inline void copy_summary(char *dst, const char *src) {
|
||||
size_t n = strnlen(src, SUMMARY_LEN + 1);
|
||||
if (n > SUMMARY_LEN) {
|
||||
n = SUMMARY_LEN;
|
||||
while (n > 0 && (uint8_t(src[n]) & 0xC0) == 0x80)
|
||||
n--;
|
||||
}
|
||||
memcpy(dst, src, n);
|
||||
dst[n] = '\0';
|
||||
}
|
||||
|
||||
// Parse the calendar.get_events response; Home Assistant wraps the action response as
|
||||
// {"response": {"calendar.x": {"events": [...]}, ...}}; every calendar other than the waste one holds birthdays
|
||||
inline bool parse(ArduinoJson::JsonObjectConst response, const char *waste, uint32_t now, Events &ev) {
|
||||
ArduinoJson::JsonObjectConst root = response["response"].is<ArduinoJson::JsonObjectConst>()
|
||||
? response["response"].as<ArduinoJson::JsonObjectConst>()
|
||||
: response;
|
||||
if (root.size() == 0)
|
||||
return false;
|
||||
|
||||
Events out;
|
||||
for (ArduinoJson::JsonPairConst cal : root) {
|
||||
const uint8_t kind = strcmp(cal.key().c_str(), waste) == 0 ? WASTE : BIRTHDAY;
|
||||
for (ArduinoJson::JsonObjectConst item : cal.value()["events"].as<ArduinoJson::JsonArrayConst>()) {
|
||||
Event e;
|
||||
if (!parse_start(item["start"] | "", e.day, e.minute))
|
||||
continue;
|
||||
e.kind = kind;
|
||||
copy_summary(e.summary, item["summary"] | "");
|
||||
// the same birthday in the contacts and the manual calendar
|
||||
if (std::any_of(out.items, out.items + out.count,
|
||||
[&e](const Event &o) { return o.day == e.day && strcmp(o.summary, e.summary) == 0; }))
|
||||
continue;
|
||||
if (out.count < MAX_EVENTS) {
|
||||
out.items[out.count++] = e;
|
||||
continue;
|
||||
}
|
||||
// full: keep the nearest events
|
||||
Event *last = std::max_element(out.items, out.items + out.count,
|
||||
[](const Event &a, const Event &b) { return a.day < b.day; });
|
||||
if (e.day < last->day)
|
||||
*last = e;
|
||||
}
|
||||
}
|
||||
std::sort(out.items, out.items + out.count, [](const Event &a, const Event &b) {
|
||||
// all-day (0xFFFF wraps to 0) first
|
||||
return a.day != b.day ? a.day < b.day : uint16_t(a.minute + 1) < uint16_t(b.minute + 1);
|
||||
});
|
||||
out.fetched_at = now;
|
||||
ev = out;
|
||||
return true;
|
||||
}
|
||||
|
||||
// battery cycle: refreshed once a day, after midnight
|
||||
inline bool stale(const esphome::ESPTime &now) {
|
||||
const auto &ev = events();
|
||||
return !ev.valid() || !now.is_valid() || now.timestamp - ev.fetched_at >= 20 * 3600;
|
||||
}
|
||||
|
||||
inline esphome::ESPPreferenceObject &pref() {
|
||||
static esphome::ESPPreferenceObject p = esphome::global_preferences->make_preference<Events>(PREF_HASH, true);
|
||||
return p;
|
||||
}
|
||||
|
||||
inline bool load(Events &ev) {
|
||||
if (!pref().load(&ev)) {
|
||||
ev = Events{};
|
||||
return false;
|
||||
}
|
||||
ev.count = std::min(ev.count, MAX_EVENTS);
|
||||
return true;
|
||||
}
|
||||
|
||||
// written to flash right away: the board may power off before the next preferences flush
|
||||
inline bool save(const Events &ev) { return pref().save(&ev) && esphome::global_preferences->sync(); }
|
||||
|
||||
} // namespace m5paper_calendar
|
||||
+1
-2
@@ -491,8 +491,7 @@ sensor:
|
||||
# name: Dallas Temp 4
|
||||
# one_wire_id: hub_1
|
||||
|
||||
time:
|
||||
- !include common/time/homeassistant.yaml
|
||||
# time:
|
||||
# - platform: sntp
|
||||
# id: sntp_time
|
||||
# timezone: Europe/Bratislava
|
||||
|
||||
+1
-2
@@ -139,8 +139,7 @@ switch:
|
||||
id: relay_2
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
timezone: Europe/Bratislava
|
||||
- id: !extend ha_time
|
||||
on_time:
|
||||
- seconds: 0
|
||||
minutes: /15
|
||||
|
||||
@@ -31,11 +31,6 @@ packages:
|
||||
|
||||
debug:
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
i2c:
|
||||
# - id: i2c0
|
||||
sda: 32
|
||||
|
||||
+1
-5
@@ -23,6 +23,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# GPIO21 UART TX
|
||||
# GPIO20 UART RX
|
||||
@@ -109,11 +110,6 @@ globals:
|
||||
restore_value: no
|
||||
initial_value: "false"
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
display:
|
||||
- platform: tm1637
|
||||
id: tm1637_display
|
||||
|
||||
@@ -51,11 +51,6 @@ globals:
|
||||
restore_value: no
|
||||
initial_value: "false"
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: Button
|
||||
|
||||
@@ -19,6 +19,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# Mi Desk Lamp
|
||||
# Pin Function (Name)
|
||||
|
||||
@@ -34,6 +34,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# fan:
|
||||
# - platform: speed
|
||||
|
||||
@@ -20,6 +20,12 @@ esp32:
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
|
||||
external_components:
|
||||
# speaker media player: skip a failed playlist item (esphome#19862), drop once merged
|
||||
- source: github://randybb/esphome-components
|
||||
components: [speaker]
|
||||
refresh: 1h
|
||||
|
||||
api:
|
||||
actions:
|
||||
# https://github.com/granadaxronos/120-SONG_NOKIA_RTTTL_RINGTONE_PLAYER_FOR_ARDUINO_UNO/blob/master/RTTTL_PLAYER/songs.h
|
||||
|
||||
+8
-3
@@ -21,6 +21,12 @@ esp32:
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
|
||||
external_components:
|
||||
# speaker media player: skip a failed playlist item (esphome#19862), drop once merged
|
||||
- source: github://randybb/esphome-components
|
||||
components: [speaker]
|
||||
refresh: 1h
|
||||
|
||||
api:
|
||||
actions:
|
||||
# https://github.com/granadaxronos/120-SONG_NOKIA_RTTTL_RINGTONE_PLAYER_FOR_ARDUINO_UNO/blob/master/RTTTL_PLAYER/songs.h
|
||||
@@ -127,16 +133,15 @@ media_player:
|
||||
- platform: speaker
|
||||
id: media_out
|
||||
name: Player
|
||||
codec_support_enabled: false
|
||||
buffer_size: 14000
|
||||
media_pipeline:
|
||||
speaker: media_spk_resampling_input
|
||||
# format: WAV
|
||||
format: WAV # only the WAV decoder is built (was codec_support_enabled: false)
|
||||
# num_channels: 1
|
||||
# sample_rate: 8000 # Lower sample rate
|
||||
announcement_pipeline:
|
||||
speaker: announcement_spk_resampling_input
|
||||
# format: WAV
|
||||
format: WAV
|
||||
# num_channels: 1
|
||||
# sample_rate: 8000 # Lower sample rate
|
||||
on_play:
|
||||
|
||||
@@ -242,11 +242,6 @@ binary_sensor:
|
||||
# on_clockwise:
|
||||
# - display_menu.down:
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
# display:
|
||||
# - platform: tm1637
|
||||
# id: tm1637_display
|
||||
|
||||
+1
-5
@@ -27,6 +27,7 @@ external_components:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
|
||||
# PINOUTS: M5ATOM+Atom Hub Switch
|
||||
@@ -44,11 +45,6 @@ packages:
|
||||
# Relay1 G22
|
||||
# Relay2 G19
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
i2c:
|
||||
# - id: i2c0
|
||||
# sda: 26
|
||||
|
||||
@@ -19,6 +19,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# PINOUTS: M5ATOM
|
||||
# IR G12
|
||||
|
||||
+2
-3
@@ -37,6 +37,7 @@ external_components:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# PINOUTS: M5ATOM
|
||||
# IR G12
|
||||
@@ -129,9 +130,7 @@ globals:
|
||||
initial_value: '20.5'
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
- id: !extend ha_time
|
||||
on_time:
|
||||
- seconds: 0
|
||||
minutes: /1
|
||||
|
||||
+1
-5
@@ -36,6 +36,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
api:
|
||||
reboot_timeout: 300s
|
||||
@@ -55,11 +56,6 @@ api:
|
||||
# Relay1 G22
|
||||
# Relay2 G19
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
i2c:
|
||||
# - id: i2c0
|
||||
# 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
|
||||
area: Basement
|
||||
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:
|
||||
name: mcu-${device}
|
||||
@@ -17,9 +24,9 @@ esp32:
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
i2c:
|
||||
- id: touchscreen_bus
|
||||
@@ -36,71 +43,17 @@ spi:
|
||||
clk_pin: 47
|
||||
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:
|
||||
- id: bgcolor
|
||||
type: Color
|
||||
initial_value: "Color::random_color()"
|
||||
|
||||
# power_supply:
|
||||
# id: main_backlight
|
||||
# pin: 1
|
||||
# enable_on_boot: true
|
||||
# stopwatch: accumulated time of finished runs + start of the current run
|
||||
- id: sw_running
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: sw_start
|
||||
type: uint32_t
|
||||
initial_value: '0'
|
||||
- id: sw_accum
|
||||
type: uint32_t
|
||||
initial_value: '0'
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
@@ -119,7 +72,6 @@ display:
|
||||
- platform: mipi_spi
|
||||
model: AXS15231
|
||||
data_rate: 40MHz #20mhz is default
|
||||
#spi_mode: MODE2
|
||||
id: main_display
|
||||
spi_id: display_qspi
|
||||
dimensions:
|
||||
@@ -127,11 +79,6 @@ display:
|
||||
width: 320
|
||||
cs_pin: 45
|
||||
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:
|
||||
- platform: axs15231
|
||||
@@ -142,27 +89,8 @@ touchscreen:
|
||||
# interrupt_pin:
|
||||
# number: 3
|
||||
# ignore_strapping_warning: true
|
||||
calibration:
|
||||
x_min: 0
|
||||
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
|
||||
# );
|
||||
# no calibration/transform: the driver defaults to the native 320x480 panel and
|
||||
# LVGL applies its own rotation to touch points
|
||||
on_release:
|
||||
- if:
|
||||
condition: lvgl.is_paused
|
||||
@@ -183,107 +111,640 @@ uart:
|
||||
|
||||
binary_sensor:
|
||||
- platform: homeassistant
|
||||
id: exercise_room_light
|
||||
entity_id: light.exercise_room
|
||||
id: basement_light
|
||||
entity_id: ${light_basement}
|
||||
trigger_on_initial_state: true
|
||||
on_state:
|
||||
then:
|
||||
lvgl.widget.update:
|
||||
id: exercise_room_light_btn
|
||||
- lvgl.widget.update:
|
||||
id: basement_light_btn
|
||||
state:
|
||||
checked: !lambda return x;
|
||||
- platform: homeassistant
|
||||
id: exercise_room_fan
|
||||
entity_id: light.mcu_dimmer_exercise_room_fan
|
||||
id: workbench_light
|
||||
entity_id: ${light_workbench}
|
||||
trigger_on_initial_state: true
|
||||
on_state:
|
||||
then:
|
||||
lvgl.widget.update:
|
||||
id: exercise_room_fan_btn
|
||||
- lvgl.widget.update:
|
||||
id: workbench_light_btn
|
||||
state:
|
||||
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:
|
||||
rotation: 90
|
||||
buffer_size: 100%
|
||||
log_level: WARN
|
||||
color_depth: 16
|
||||
default_font: montserrat_18
|
||||
# on_idle:
|
||||
# - timeout: !lambda "return (id(display_timeout).state * 1000);"
|
||||
# then:
|
||||
# - logger.log: "LVGL is idle"
|
||||
# - light.turn_off:
|
||||
# id: display_backlight
|
||||
# transition_length: 2500ms
|
||||
# - lvgl.pause:
|
||||
default_font: font_ui
|
||||
bg_color: 0x000000
|
||||
text_color: 0xFFFFFF
|
||||
theme:
|
||||
button:
|
||||
bg_color: 0x2B2F36
|
||||
radius: 12
|
||||
checked:
|
||||
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:
|
||||
widgets:
|
||||
- buttonmatrix:
|
||||
id: nav_bar
|
||||
align: bottom_mid
|
||||
# styles: header_footer
|
||||
pad_all: 0
|
||||
outline_width: 0
|
||||
id: top_layer
|
||||
# items:
|
||||
# styles: header_footer
|
||||
width: 100%
|
||||
height: 56
|
||||
pad_all: 4
|
||||
pad_column: 6
|
||||
items:
|
||||
text_font: montserrat_28
|
||||
rows:
|
||||
- buttons:
|
||||
- id: page_prev
|
||||
text: "\uF053"
|
||||
text: ""
|
||||
on_press:
|
||||
then:
|
||||
lvgl.page.previous:
|
||||
- id: page_home
|
||||
text: "\uF015"
|
||||
text: ""
|
||||
on_press:
|
||||
then:
|
||||
lvgl.page.show: main_page
|
||||
lvgl.page.show: clock_page
|
||||
- id: page_next
|
||||
text: "\uF054"
|
||||
text: ""
|
||||
on_press:
|
||||
then:
|
||||
lvgl.page.next:
|
||||
pages:
|
||||
- id: main_page
|
||||
# 1: clock (default after boot)
|
||||
- id: clock_page
|
||||
widgets:
|
||||
- label:
|
||||
text: "ESPHome LVGL Display"
|
||||
id: clock_label
|
||||
align: CENTER
|
||||
text_align: CENTER
|
||||
text_color: 0xFFFFFF
|
||||
- id: excercise_page
|
||||
y: -28
|
||||
text_font: font_digits
|
||||
text: "--:--:--"
|
||||
|
||||
# 2: basement lights
|
||||
- id: lights_page
|
||||
pad_all: 10
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: 'Hello World!'
|
||||
- button:
|
||||
id: exercise_room_light_btn
|
||||
id: basement_light_btn
|
||||
align: TOP_LEFT
|
||||
width: 100
|
||||
height: 70
|
||||
width: 148
|
||||
height: 80
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: 'Light'
|
||||
text: "Pivnica"
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: light.exercise_room
|
||||
entity_id: ${light_basement}
|
||||
- button:
|
||||
id: exercise_room_fan_btn
|
||||
align: TOP_RIGHT
|
||||
width: 100
|
||||
height: 70
|
||||
id: workbench_light_btn
|
||||
align: TOP_MID
|
||||
width: 148
|
||||
height: 80
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: 'Fan'
|
||||
text: "Pracovný stôl"
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
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}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
platformio_options:
|
||||
board_build.flash_mode: dio
|
||||
on_boot:
|
||||
- priority: -100
|
||||
then:
|
||||
- lvgl.page.show: status_page
|
||||
|
||||
esp32:
|
||||
variant: esp32s3
|
||||
@@ -19,9 +21,9 @@ esp32:
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
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:
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: y
|
||||
CONFIG_ESP32S3_DATA_CACHE_64KB: y
|
||||
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
|
||||
CONFIG_SPIRAM_RODATA: y
|
||||
@@ -32,6 +34,7 @@ psram:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# PINOUTS: ESP32 Cheap Yellow Display, 4.3", IPS, Capacitive
|
||||
## Display
|
||||
@@ -63,6 +66,7 @@ packages:
|
||||
# HSYNC 39
|
||||
# VSYNC 41
|
||||
# DE 40
|
||||
# BL 2
|
||||
## Capacitive Touch
|
||||
# TP_INT
|
||||
# TP_RST 38
|
||||
@@ -89,579 +93,248 @@ packages:
|
||||
# 3 19 TP_SDA
|
||||
# 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:
|
||||
- id: i2c_main
|
||||
sda: 19
|
||||
scl: 20
|
||||
scan: true
|
||||
|
||||
# same pins and timing as the 5" Sunton board, pclk lowered for the 4.3" panel
|
||||
display:
|
||||
- platform: rpi_dpi_rgb
|
||||
- platform: mipi_rgb
|
||||
model: ESP32-8048S050
|
||||
id: main_display
|
||||
color_order: RGB
|
||||
invert_colors: true
|
||||
pclk_frequency: 12.5MHz
|
||||
update_interval: never
|
||||
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:
|
||||
- platform: ledc
|
||||
id: gpio_backlight_pwm
|
||||
pin: 2
|
||||
frequency: 1220
|
||||
id: gpio_backlight_pwm
|
||||
|
||||
light:
|
||||
- platform: monochromatic
|
||||
output: gpio_backlight_pwm
|
||||
id: back_light
|
||||
name: Backlight
|
||||
output: gpio_backlight_pwm
|
||||
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:
|
||||
- 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:
|
||||
# - lvgl.indicator.line.update:
|
||||
# id: power_meter_input
|
||||
# value: !lambda return id(counting_number).state;
|
||||
- lvgl.label.update:
|
||||
id: battery_kw
|
||||
id: clock_label
|
||||
text: !lambda |-
|
||||
static char buf[8];
|
||||
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state);
|
||||
return buf;
|
||||
- lvgl.label.update:
|
||||
id: battery_status
|
||||
text_color: 0xFF0000
|
||||
#text: "charging"
|
||||
text: "discharging"
|
||||
- lvgl.label.update:
|
||||
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
|
||||
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:
|
||||
- file: mdi:sun-wireless-outline
|
||||
#"solar-power-invert.png"
|
||||
# mdi:solar-power
|
||||
# mdi:battery-charging-outline
|
||||
# mdi:battery-arrow-up-outline
|
||||
# mdi:battery-arrow-down-outline
|
||||
id: solar_power_icon
|
||||
resize: 50x50
|
||||
type: BINARY
|
||||
- platform: file
|
||||
file: assets/images/made-for-esphome-white-on-black.svg
|
||||
id: esphome_logo
|
||||
resize: 400x118
|
||||
type: RGB565
|
||||
transparency: alpha_channel
|
||||
|
||||
- file: mdi:battery-arrow-down-outline
|
||||
id: home_battery_icon
|
||||
resize: 30x30
|
||||
type: BINARY
|
||||
font:
|
||||
- file: "gfonts://Roboto@500"
|
||||
id: font_clock
|
||||
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:
|
||||
log_level: INFO
|
||||
log_level: WARN
|
||||
color_depth: 16
|
||||
#buffer_size: 25%
|
||||
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
|
||||
bg_color: 0x000000
|
||||
text_color: 0xFFFFFF
|
||||
#bg_opa: cover
|
||||
theme:
|
||||
buttonmatrix:
|
||||
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
|
||||
outline_width: 0
|
||||
shadow_width: 0
|
||||
pad_all: 4
|
||||
align: TOP_LEFT
|
||||
#scrollbar_mode: "off"
|
||||
items:
|
||||
bg_color: 0x2B2F36
|
||||
text_color: 0xFFFFFF
|
||||
radius: 12
|
||||
checked:
|
||||
bg_color: 0xE08A00
|
||||
top_layer:
|
||||
widgets:
|
||||
- meter: # Gradient color arc
|
||||
height: 100%
|
||||
# same navigation bar as on mcu-lvgl-trainer
|
||||
- buttonmatrix:
|
||||
id: nav_bar
|
||||
align: bottom_mid
|
||||
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_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
|
||||
height: 64
|
||||
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
|
||||
#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:
|
||||
- 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:
|
||||
styles: font_style
|
||||
text_font: MONTSERRAT_18 # override font size
|
||||
y: 8 #negative = higher
|
||||
x: 99
|
||||
text: "+10"
|
||||
id: status_wifi
|
||||
text_font: font_status
|
||||
text: ""
|
||||
- label:
|
||||
styles: font_style
|
||||
id: battery_status2
|
||||
y: -35 #negative = higher
|
||||
id: status_ips
|
||||
text_font: font_status
|
||||
text_color: 0x8C8C8C
|
||||
pad_left: 30
|
||||
text: ""
|
||||
- label:
|
||||
styles: font_style
|
||||
id: battery_kw2
|
||||
y: -60
|
||||
id: status_ha
|
||||
text_font: font_status
|
||||
text: ""
|
||||
- label:
|
||||
styles: font_style
|
||||
text_font: MONTSERRAT_40 # override font size
|
||||
id: battery_soc2
|
||||
y: 0
|
||||
- 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
|
||||
id: status_footer
|
||||
text_font: font_status
|
||||
text_color: 0x8C8C8C
|
||||
text: ""
|
||||
+579
-77
@@ -4,94 +4,380 @@ substitutions:
|
||||
name: M5Paper
|
||||
area: DEV
|
||||
comment: M5Paper
|
||||
waste_calendar: calendar.https_www_hviezdoslavov_sk_kalendar_icalfeed_category_0_ongoing_1
|
||||
# contacts + manual
|
||||
birthday_calendars: calendar.birthdays, calendar.narodeniny
|
||||
|
||||
# Power modes
|
||||
# - USB: always on, WiFi + API, events refreshed and redrawn after midnight
|
||||
# - battery: RTC wakes the board at 00:05, it refreshes the events, draws them and powers
|
||||
# itself off; the RTC timer lasts at most 255 min, so the wake-ups on the way to
|
||||
# midnight only re-arm it
|
||||
# The power button (and USB) also connects to WiFi: the events are refreshed on every
|
||||
# Home Assistant connection outside the battery cycle.
|
||||
# Screens: status (after power-on, until Home Assistant connects) and calendar
|
||||
# The mode is detected by trying to power off: on USB the board keeps running.
|
||||
|
||||
esphome:
|
||||
name: mcu-${device}
|
||||
friendly_name: ${name}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
includes:
|
||||
- include/m5paper_calendar.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_calendar::load(m5paper_calendar::events());
|
||||
ESP_LOGI("m5paper", "Wake-up: %s, events: %u", id(rtc_wake) ? "RTC" : "power-on", m5paper_calendar::events().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:
|
||||
variant: esp32
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
flash_chip: generic
|
||||
minimum_chip_revision: 3.0
|
||||
sram1_as_iram: true
|
||||
|
||||
psram:
|
||||
|
||||
external_components:
|
||||
- source: github://Passific/m5paper_esphome
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
on_time_sync:
|
||||
- bm8563.write_time
|
||||
- platform: bm8563
|
||||
id: rtc_time
|
||||
sleep_duration: 3600000ms
|
||||
globals:
|
||||
- id: show_status
|
||||
type: bool
|
||||
initial_value: 'true'
|
||||
- id: usb_mode
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: rtc_wake
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
# battery cycle running: calendar screen regardless of the connection state
|
||||
- id: calendar_page
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: fetch_done
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: sleep_s
|
||||
type: int
|
||||
initial_value: '900'
|
||||
|
||||
m5paper:
|
||||
battery_power_pin: GPIO5
|
||||
main_power_pin: GPIO2
|
||||
# update_interval: 10s
|
||||
# battery_voltage:
|
||||
# name: "${name} Battery"
|
||||
wifi:
|
||||
enable_on_boot: false
|
||||
on_connect:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(show_status) && !id(calendar_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(calendar_page);
|
||||
then:
|
||||
- component.update: m5paper_display
|
||||
# outside the battery cycle (power button, USB) WiFi is up anyway: always refresh
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- lambda: return !id(calendar_page);
|
||||
- api.connected:
|
||||
state_subscription_only: true
|
||||
then:
|
||||
- script.execute: fetch_events
|
||||
on_client_disconnected:
|
||||
- if:
|
||||
condition:
|
||||
not:
|
||||
api.connected:
|
||||
state_subscription_only: true
|
||||
then:
|
||||
- lambda: id(show_status) = true;
|
||||
- if:
|
||||
condition:
|
||||
lambda: return !id(calendar_page);
|
||||
then:
|
||||
- component.update: m5paper_display
|
||||
|
||||
# I2C devices (i2c_main)
|
||||
# 0x44 SHT30
|
||||
# 0x50
|
||||
# 0x51
|
||||
# 0x5D GT911
|
||||
# 0x50 FM24C02 EEPROM (unused)
|
||||
# 0x51 BM8563 RTC
|
||||
# 0x5D GT911 touch (docs say 0x14; the driver tries 0x5D, then 0x14)
|
||||
|
||||
i2c:
|
||||
- id: i2c_main
|
||||
sda: 21
|
||||
scl: 22
|
||||
scan: true #false
|
||||
scan: true
|
||||
# Port A
|
||||
# - id: i2c_a
|
||||
# sda: 25
|
||||
# scl: 32
|
||||
# scan: true #false
|
||||
# Port B
|
||||
# - id: i2c_b
|
||||
# sda: 26
|
||||
# scl: 33
|
||||
# scan: true #false
|
||||
# Port C
|
||||
# - id: i2c_c
|
||||
# sda: 18
|
||||
# scl: 19
|
||||
# scan: true #false
|
||||
|
||||
spi:
|
||||
clk_pin: 14
|
||||
miso_pin: 13
|
||||
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 events if they are old, draw them, power off
|
||||
- id: battery_cycle
|
||||
mode: single
|
||||
then:
|
||||
- lambda: id(calendar_page) = true;
|
||||
- wait_until:
|
||||
condition:
|
||||
lambda: return id(rtc_time).now().is_valid();
|
||||
timeout: 10s
|
||||
- if:
|
||||
# RTC wake-up on the way to midnight: nothing to draw
|
||||
condition:
|
||||
lambda: |-
|
||||
const auto now = id(rtc_time).now();
|
||||
return id(rtc_wake) && now.is_valid() && now.hour != 0;
|
||||
then:
|
||||
- script.execute: try_sleep
|
||||
else:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return m5paper_calendar::stale(id(rtc_time).now());
|
||||
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_events
|
||||
- wait_until:
|
||||
condition:
|
||||
lambda: return id(fetch_done);
|
||||
timeout: 15s
|
||||
- component.update: m5paper_display
|
||||
# let the e-paper refresh finish before cutting power
|
||||
- delay: 4s
|
||||
- script.execute: try_sleep
|
||||
|
||||
# today and the next 31 days (the month grid and the list)
|
||||
- id: fetch_events
|
||||
mode: single
|
||||
then:
|
||||
- lambda: id(fetch_done) = false;
|
||||
- homeassistant.action:
|
||||
action: calendar.get_events
|
||||
data:
|
||||
entity_id: ${waste_calendar}, ${birthday_calendars}
|
||||
start_date_time: !lambda return id(rtc_time).now().strftime("%Y-%m-%d 00:00:00");
|
||||
duration: "744:00:00"
|
||||
capture_response: true
|
||||
on_success:
|
||||
- lambda: |-
|
||||
auto &ev = m5paper_calendar::events();
|
||||
if (m5paper_calendar::parse(response, "${waste_calendar}", id(rtc_time).now().timestamp, ev)) {
|
||||
const bool saved = m5paper_calendar::save(ev);
|
||||
ESP_LOGI("m5paper", "Events: %u%s", ev.count, saved ? "" : " (flash write failed)");
|
||||
} else {
|
||||
ESP_LOGW("m5paper", "Events: unexpected response");
|
||||
}
|
||||
id(fetch_done) = true;
|
||||
# the battery cycle draws on its own
|
||||
- if:
|
||||
condition:
|
||||
lambda: return !id(calendar_page);
|
||||
then:
|
||||
- component.update: m5paper_display
|
||||
on_error:
|
||||
- lambda: |-
|
||||
ESP_LOGW("m5paper", "Events: %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 00:05; the timer lasts at most 255 min, so get there in steps
|
||||
const int MAX = 255 * 60;
|
||||
int duration = MAX;
|
||||
const auto now = id(rtc_time).now();
|
||||
if (now.is_valid()) {
|
||||
const int sod = now.hour * 3600 + now.minute * 60 + now.second;
|
||||
int left = (86400 + 300 - sod) % 86400;
|
||||
if (left < 600) // just drawn: the 1/60 Hz RTC timer may wake us up to a minute early
|
||||
left += 86400;
|
||||
// leave at least 20 min for the last step, so it cannot fall into the case above
|
||||
duration = left > MAX ? std::min(MAX, left - 1200) : left;
|
||||
}
|
||||
if (duration > 255) // longer timers count whole minutes
|
||||
duration = (duration + 59) / 60 * 60;
|
||||
id(sleep_s) = std::min(duration, MAX);
|
||||
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(rtc_wake) = false;
|
||||
id(calendar_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(calendar_page);
|
||||
then:
|
||||
- component.update: m5paper_display
|
||||
on_time:
|
||||
# new day
|
||||
- seconds: 0
|
||||
minutes: 0
|
||||
hours: 0
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return !id(show_status) && !id(calendar_page);
|
||||
then:
|
||||
- component.update: m5paper_display
|
||||
# USB mode: keep the stored events fresh for battery mode
|
||||
- seconds: 0
|
||||
minutes: 5
|
||||
hours: 0
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- lambda: return id(usb_mode);
|
||||
- api.connected:
|
||||
state_subscription_only: true
|
||||
then:
|
||||
- script.execute: fetch_events
|
||||
- platform: bm8563
|
||||
id: rtc_time
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: ${name} Button UP
|
||||
id: button_up
|
||||
pin:
|
||||
number: 37
|
||||
number: GPIO37
|
||||
inverted: true
|
||||
on_press:
|
||||
- component.update: m5paper_display
|
||||
on_release:
|
||||
- component.update: m5paper_display
|
||||
- platform: gpio
|
||||
name: ${name} Button Press
|
||||
id: button_press
|
||||
pin:
|
||||
number: 38
|
||||
number: GPIO38
|
||||
inverted: true
|
||||
- platform: gpio
|
||||
name: ${name} Button Down
|
||||
id: button_down
|
||||
pin:
|
||||
number: 39
|
||||
number: GPIO39
|
||||
inverted: true
|
||||
- platform: touchscreen
|
||||
name: ${name} Top Left Touch Button
|
||||
@@ -103,51 +389,19 @@ binary_sensor:
|
||||
y_min: 0
|
||||
y_max: 100
|
||||
on_press:
|
||||
# Example of applying the sleep duration and shutting down to the lowest power mode
|
||||
- bm8563.apply_sleep_duration
|
||||
- delay: 1s
|
||||
- m5paper.shutdown_main_power
|
||||
- script.execute: battery_cycle
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: font_default
|
||||
size: 20
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto_32
|
||||
size: 32
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto_24
|
||||
size: 24
|
||||
- file: "gfonts://Roboto"
|
||||
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!");
|
||||
button:
|
||||
- platform: template
|
||||
name: Refresh Display
|
||||
entity_category: config
|
||||
on_press:
|
||||
- component.update: m5paper_display
|
||||
- platform: template
|
||||
name: Refresh Calendar
|
||||
entity_category: config
|
||||
on_press:
|
||||
- script.execute: fetch_events
|
||||
|
||||
sensor:
|
||||
- platform: sht3xd
|
||||
@@ -156,9 +410,257 @@ sensor:
|
||||
id: current_temperature
|
||||
humidity:
|
||||
name: Humidity
|
||||
id: current_humidity
|
||||
address: 0x44
|
||||
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:
|
||||
# - platform: gt911
|
||||
# interrupt_pin: 36
|
||||
.image_defaults:
|
||||
icon: &icon {platform: file, type: BINARY, transparency: chroma_key}
|
||||
|
||||
image:
|
||||
- platform: file
|
||||
file: assets/images/made-for-esphome-white-on-black.svg
|
||||
id: esphome_logo
|
||||
resize: 460x136
|
||||
type: GRAYSCALE
|
||||
transparency: alpha_channel
|
||||
# same order as m5paper_calendar::Kind
|
||||
- {<<: *icon, file: "mdi:cake-variant", id: icon_birthday, resize: 32x32}
|
||||
- {<<: *icon, file: "mdi:trash-can", id: icon_waste, resize: 32x32}
|
||||
- {<<: *icon, file: "mdi:cake-variant", id: mark_birthday, resize: 18x18}
|
||||
- {<<: *icon, file: "mdi:trash-can", id: mark_waste, resize: 18x18}
|
||||
|
||||
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_footer
|
||||
size: 18
|
||||
glyphs: "0123456789abcdef:. "
|
||||
- file: "gfonts://Roboto@500"
|
||||
id: font_date
|
||||
size: 34
|
||||
glyphs: *sk_glyphs
|
||||
glyphsets: [GF_Latin_Kernel]
|
||||
|
||||
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;
|
||||
|
||||
// status screen after power-on, until Home Assistant connects
|
||||
if (id(show_status) && !id(calendar_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), "Calendar: %u events stored", m5paper_calendar::events().count);
|
||||
it.printf(40, 900, id(font_small), id(ink), "ESPHome %s", ESPHOME_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
// calendar screen
|
||||
using namespace m5paper_calendar;
|
||||
image::Image *icons[] = {id(icon_birthday), id(icon_waste)};
|
||||
image::Image *marks[] = {id(mark_birthday), id(mark_waste)};
|
||||
static const char *const DAYS[] = {"Pondelok", "Utorok", "Streda", "Štvrtok", "Piatok", "Sobota", "Nedeľa"};
|
||||
static const char *const DAYS_SHORT[] = {"Po", "Ut", "St", "Št", "Pi", "So", "Ne"};
|
||||
static const char *const MONTHS[] = {"Január", "Február", "Marec", "Apríl", "Máj", "Jún",
|
||||
"Júl", "August", "September", "Október", "November", "December"};
|
||||
|
||||
const auto &ev = m5paper_calendar::events();
|
||||
const auto now = id(rtc_time).now();
|
||||
if (!now.is_valid()) {
|
||||
it.print(cx, 400, id(font_status), id(ink), TextAlign::CENTER, "Čas nie je k dispozícii");
|
||||
return;
|
||||
}
|
||||
const int32_t today = local_day(now);
|
||||
|
||||
// header: date, battery
|
||||
it.printf(24, 16, id(font_date), id(ink), "%s %d. %d.", DAYS[weekday(today)], 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);
|
||||
|
||||
// month grid; days with events get the icons of their kinds
|
||||
const int32_t first = days_from_civil(now.year, now.month, 1);
|
||||
const int32_t next = now.month == 12 ? days_from_civil(now.year + 1, 1, 1) : days_from_civil(now.year, now.month + 1, 1);
|
||||
const int dim = next - first, lead = weekday(first);
|
||||
uint8_t kinds[32] = {};
|
||||
for (int i = 0; i < ev.count; i++) {
|
||||
if (ev.items[i].day >= first && ev.items[i].day < next)
|
||||
kinds[ev.items[i].day - first + 1] |= 1 << ev.items[i].kind;
|
||||
}
|
||||
const int col_w = 70, row_h = 54, gx = (w - 7 * col_w) / 2, gy = 150;
|
||||
it.printf(cx, 70, id(font_status), id(ink), TextAlign::TOP_CENTER, "%s %d", MONTHS[now.month - 1], now.year);
|
||||
for (int c = 0; c < 7; c++)
|
||||
it.print(gx + c * col_w + col_w / 2, 116, id(font_small), id(ink), TextAlign::TOP_CENTER, DAYS_SHORT[c]);
|
||||
for (int d = 1; d <= dim; d++) {
|
||||
const int cell = lead + d - 1;
|
||||
const int x = gx + (cell % 7) * col_w, y = gy + (cell / 7) * row_h;
|
||||
const bool is_today = first + d - 1 == today;
|
||||
if (is_today)
|
||||
it.filled_rectangle(x + 3, y + 1, col_w - 6, row_h - 2, id(ink));
|
||||
const Color fg = is_today ? id(paper) : id(ink);
|
||||
it.printf(x + col_w / 2, y + 4, id(font_small), fg, TextAlign::TOP_CENTER, "%d", d);
|
||||
const int n = __builtin_popcount(kinds[d]);
|
||||
int mx = x + (col_w - n * 20) / 2;
|
||||
for (int k = 0; k < 2; k++) {
|
||||
if (kinds[d] & (1 << k)) {
|
||||
it.image(mx, y + 32, marks[k], fg);
|
||||
mx += 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// next 14 days; waste collection today or tomorrow is inverted: put the bin out
|
||||
const int top = 490, list_h = 50, max_rows = 8;
|
||||
it.horizontal_line(24, top - 12, w - 48, id(ink));
|
||||
int rows = 0, more = 0;
|
||||
for (int i = 0; i < ev.count; i++) {
|
||||
const Event &e = ev.items[i];
|
||||
if (e.day < today || e.day >= today + 14)
|
||||
continue;
|
||||
if (rows == max_rows) {
|
||||
more++;
|
||||
continue;
|
||||
}
|
||||
const int y = top + rows++ * list_h;
|
||||
const bool urgent = e.kind == WASTE && e.day <= today + 1;
|
||||
if (urgent)
|
||||
it.filled_rectangle(16, y, w - 32, list_h - 4, id(ink));
|
||||
const Color fg = urgent ? id(paper) : id(ink);
|
||||
const int ty = y + (list_h - 4) / 2;
|
||||
if (e.day == today)
|
||||
it.print(28, ty, id(font_small), fg, TextAlign::CENTER_LEFT, "Dnes");
|
||||
else if (e.day == today + 1)
|
||||
it.print(28, ty, id(font_small), fg, TextAlign::CENTER_LEFT, "Zajtra");
|
||||
else {
|
||||
const auto t = ESPTime::from_epoch_local(uint32_t(e.day) * 86400 + 43200);
|
||||
it.printf(28, ty, id(font_small), fg, TextAlign::CENTER_LEFT, "%s %d. %d.", DAYS_SHORT[weekday(e.day)],
|
||||
t.day_of_month, t.month);
|
||||
}
|
||||
if (e.kind < 2)
|
||||
it.image(150, ty - 16, icons[e.kind], fg);
|
||||
it.start_clipping(194, y, w - 28, y + list_h);
|
||||
if (e.minute == ALL_DAY)
|
||||
it.print(194, ty, id(font_small), fg, TextAlign::CENTER_LEFT, e.summary);
|
||||
else
|
||||
it.printf(194, ty, id(font_small), fg, TextAlign::CENTER_LEFT, "%02d:%02d %s", e.minute / 60, e.minute % 60,
|
||||
e.summary);
|
||||
it.end_clipping();
|
||||
}
|
||||
if (!ev.valid())
|
||||
it.print(cx, top + 20, id(font_small), id(ink), TextAlign::TOP_CENTER, "Kalendár nie je k dispozícii");
|
||||
else if (rows == 0)
|
||||
it.print(cx, top + 20, id(font_small), id(ink), TextAlign::TOP_CENTER, "Žiadne udalosti v najbližších 14 dňoch");
|
||||
else if (more > 0)
|
||||
it.printf(w - 28, top + rows * list_h, id(font_small), id(ink), TextAlign::TOP_RIGHT, "+%d ďalšie", more);
|
||||
|
||||
// footer: IPv6 address (only while WiFi is up), last download
|
||||
it.horizontal_line(24, 910, w - 48, id(ink));
|
||||
char buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
|
||||
if (!ip.is_set() || !ip.is_ip6())
|
||||
continue;
|
||||
ip.str_to(buf);
|
||||
if (strncmp(buf, "fe80", 4) != 0) {
|
||||
it.print(24, 944, id(font_footer), id(ink), TextAlign::BOTTOM_LEFT, buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ev.valid()) {
|
||||
const auto f = ESPTime::from_epoch_local(ev.fetched_at);
|
||||
it.printf(w - 24, 944, id(font_footer), id(ink), TextAlign::BOTTOM_RIGHT, "%d. %d. %02d:%02d", f.day_of_month,
|
||||
f.month, f.hour, f.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:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# PINOUTS: M5ATOM
|
||||
# IR G12
|
||||
@@ -228,9 +229,7 @@ button:
|
||||
- pzemac.reset_energy: pzemac_l3
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
- id: !extend ha_time
|
||||
on_time:
|
||||
- cron: '0 0 0 * * *'
|
||||
then:
|
||||
|
||||
+8
-168
@@ -11,103 +11,19 @@ esphome:
|
||||
friendly_name: ${name}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
on_boot:
|
||||
then:
|
||||
- switch.turn_off: screen_power
|
||||
- delay: 1s
|
||||
- switch.turn_on: screen_power
|
||||
- delay: 2s
|
||||
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
minimum_chip_revision: 3.0
|
||||
sram1_as_iram: true
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/randybb/esphome-nspanel-lovelace-native
|
||||
ref: devices
|
||||
refresh: 0s
|
||||
components: [nspanel_lovelace]
|
||||
|
||||
packages:
|
||||
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:
|
||||
homeassistant_services: true
|
||||
homeassistant_states: true
|
||||
actions:
|
||||
- action: play_rtttl
|
||||
variables:
|
||||
song: string
|
||||
then:
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify
|
||||
variables:
|
||||
heading: string
|
||||
message: string
|
||||
song: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify_fullscreen
|
||||
variables:
|
||||
title: string
|
||||
description: string
|
||||
button1: string
|
||||
button2: string
|
||||
timeout: int
|
||||
icon: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
|
||||
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
|
||||
|
||||
uart:
|
||||
id: display_uart
|
||||
tx_pin: 16
|
||||
rx_pin: 17
|
||||
baud_rate: 115200
|
||||
nspanel: !include
|
||||
file: common/nspanel.yaml
|
||||
vars:
|
||||
temperature_entity: sensor.zigbee_bedroom_temperature
|
||||
sleep_dim: !include
|
||||
file: common/nspanel_sleep_dim.yaml
|
||||
vars:
|
||||
sleep_override_entity: light.bedroom_lights
|
||||
|
||||
nspanel_lovelace:
|
||||
id: nspanel_id
|
||||
sleep_timeout: 10
|
||||
experimental_vertical_ui: true
|
||||
locale:
|
||||
language: sk
|
||||
temperature_unit: celcius
|
||||
screensaver:
|
||||
time_id: ha_time
|
||||
time_format: '%H:%M'
|
||||
date_format: '%A, %d. %b. %Y'
|
||||
weather:
|
||||
entity_id: sensor.weather_forecast_daily
|
||||
icon_color: [255, 255, 255]
|
||||
entities:
|
||||
- entity_id: sensor.zigbee_bedroom_temperature
|
||||
icon:
|
||||
value: home-thermometer-outline
|
||||
color: [255, 255, 255]
|
||||
precision: 1
|
||||
cards:
|
||||
- type: cardGrid
|
||||
title: Spálňa
|
||||
@@ -135,13 +51,6 @@ nspanel_lovelace:
|
||||
title: Kúrenie
|
||||
thermo_entity_id: climate.bt_trv_bedroom
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
on_time_sync:
|
||||
- script.execute: apply_sleep_dim
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: Left Button
|
||||
@@ -166,12 +75,6 @@ binary_sensor:
|
||||
entity_id: light.bedroom_lamps
|
||||
- lambda: 'id(nspanel_id).render_default_page();'
|
||||
|
||||
- platform: homeassistant # sleepOverride
|
||||
id: bedroom_lights
|
||||
entity_id: light.bedroom_lights
|
||||
on_state:
|
||||
- script.execute: apply_sleep_dim
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
name: Light
|
||||
@@ -179,10 +82,6 @@ light:
|
||||
output: relay_1
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
id: buzzer_out
|
||||
pin:
|
||||
number: 21
|
||||
- id: relay_1
|
||||
platform: gpio
|
||||
pin:
|
||||
@@ -191,62 +90,3 @@ output:
|
||||
# platform: gpio
|
||||
# pin:
|
||||
# number: 19
|
||||
|
||||
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:
|
||||
- platform: gpio
|
||||
name: Screen Power
|
||||
id: screen_power
|
||||
entity_category: config
|
||||
pin:
|
||||
number: 4
|
||||
inverted: true
|
||||
restore_mode: ALWAYS_ON
|
||||
|
||||
rtttl:
|
||||
id: buzzer
|
||||
output: buzzer_out
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: ntc_source
|
||||
pin: 38
|
||||
update_interval: 5min
|
||||
attenuation: 12db
|
||||
|
||||
- platform: resistance
|
||||
id: resistance_sensor
|
||||
sensor: ntc_source
|
||||
configuration: DOWNSTREAM
|
||||
resistor: 11.2kOhm
|
||||
|
||||
- platform: ntc
|
||||
name: Temperature
|
||||
id: temperature
|
||||
sensor: resistance_sensor
|
||||
calibration:
|
||||
b_constant: 3950
|
||||
reference_temperature: 25°C
|
||||
reference_resistance: 10kOhm
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: name_day
|
||||
entity_id: sensor.nameday_sk
|
||||
on_value:
|
||||
- lambda: |-
|
||||
id(nspanel_id).set_time_format("%H:%M~" + x);
|
||||
id(nspanel_id).update_time();
|
||||
@@ -59,6 +59,7 @@ external_components:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
# common: !include common/common_dev.yaml
|
||||
|
||||
# web_server:
|
||||
@@ -540,11 +541,6 @@ sensor:
|
||||
- platform: template
|
||||
id: room_temperature_id
|
||||
|
||||
time:
|
||||
platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: name_day
|
||||
|
||||
+5
-136
@@ -10,93 +10,16 @@ esphome:
|
||||
friendly_name: ${name}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
on_boot:
|
||||
then:
|
||||
- switch.turn_off: screen_power
|
||||
- delay: 1s
|
||||
- switch.turn_on: screen_power
|
||||
- delay: 2s
|
||||
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
minimum_chip_revision: 3.0
|
||||
sram1_as_iram: true
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/randybb/esphome-nspanel-lovelace-native
|
||||
ref: devices
|
||||
refresh: 0s
|
||||
components: [nspanel_lovelace]
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
|
||||
api:
|
||||
homeassistant_services: true
|
||||
homeassistant_states: true
|
||||
actions:
|
||||
- action: play_rtttl
|
||||
variables:
|
||||
song: string
|
||||
then:
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify
|
||||
variables:
|
||||
heading: string
|
||||
message: string
|
||||
song: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify_fullscreen
|
||||
variables:
|
||||
title: string
|
||||
description: string
|
||||
button1: string
|
||||
button2: string
|
||||
timeout: int
|
||||
icon: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
|
||||
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
|
||||
|
||||
uart:
|
||||
id: display_uart
|
||||
tx_pin: 16
|
||||
rx_pin: 17
|
||||
baud_rate: 115200
|
||||
nspanel: !include
|
||||
file: common/nspanel.yaml
|
||||
vars:
|
||||
temperature_entity: sensor.zigbee_living_room_kitchen_temperature
|
||||
temperature_name: Living Room Temperature
|
||||
|
||||
nspanel_lovelace:
|
||||
id: nspanel_id
|
||||
sleep_timeout: 10
|
||||
experimental_vertical_ui: true
|
||||
locale:
|
||||
language: sk
|
||||
temperature_unit: celcius
|
||||
screensaver:
|
||||
time_id: ha_time
|
||||
time_format: '%H:%M'
|
||||
date_format: '%A, %d. %b. %Y'
|
||||
weather:
|
||||
entity_id: sensor.weather_forecast_daily
|
||||
icon_color: [255, 255, 255]
|
||||
entities:
|
||||
- entity_id: sensor.zigbee_living_room_kitchen_temperature
|
||||
icon:
|
||||
value: home-thermometer-outline
|
||||
color: [255, 255, 255]
|
||||
precision: 1
|
||||
cards:
|
||||
- type: cardGrid
|
||||
title: Kuchyňa / Obývačka
|
||||
@@ -185,11 +108,6 @@ nspanel_lovelace:
|
||||
- name: Auto-zatváranie
|
||||
entity_id: switch.gate_auto_close
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: Left Button
|
||||
@@ -240,11 +158,6 @@ binary_sensor:
|
||||
entity_id: light.mcu_kitchen_stove
|
||||
- lambda: 'id(nspanel_id).render_default_page();'
|
||||
- lambda: 'id(nspanel_id).render_next_page();'
|
||||
output:
|
||||
- platform: ledc
|
||||
id: buzzer_out
|
||||
pin:
|
||||
number: 21
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
@@ -259,47 +172,3 @@ switch:
|
||||
entity_category: config
|
||||
pin:
|
||||
number: 19
|
||||
|
||||
- platform: gpio
|
||||
name: Screen Power
|
||||
id: screen_power
|
||||
entity_category: config
|
||||
pin:
|
||||
number: 4
|
||||
inverted: true
|
||||
restore_mode: ALWAYS_ON
|
||||
|
||||
rtttl:
|
||||
id: buzzer
|
||||
output: buzzer_out
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: ntc_source
|
||||
pin: 38
|
||||
update_interval: 5min
|
||||
attenuation: 12db
|
||||
|
||||
- platform: resistance
|
||||
id: resistance_sensor
|
||||
sensor: ntc_source
|
||||
configuration: DOWNSTREAM
|
||||
resistor: 11.2kOhm
|
||||
|
||||
- platform: ntc
|
||||
name: "Living Room Temperature"
|
||||
id: temperature
|
||||
sensor: resistance_sensor
|
||||
calibration:
|
||||
b_constant: 3950
|
||||
reference_temperature: 25°C
|
||||
reference_resistance: 10kOhm
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: name_day
|
||||
entity_id: sensor.nameday_sk
|
||||
on_value:
|
||||
- lambda: |-
|
||||
id(nspanel_id).set_time_format("%H:%M~" + x);
|
||||
id(nspanel_id).update_time();
|
||||
+9
-137
@@ -10,93 +10,20 @@ esphome:
|
||||
friendly_name: ${name}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
on_boot:
|
||||
then:
|
||||
- switch.turn_off: screen_power
|
||||
- delay: 1s
|
||||
- switch.turn_on: screen_power
|
||||
- delay: 2s
|
||||
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
minimum_chip_revision: 3.0
|
||||
sram1_as_iram: true
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/randybb/esphome-nspanel-lovelace-native
|
||||
ref: devices
|
||||
refresh: 0s
|
||||
components: [nspanel_lovelace]
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
|
||||
api:
|
||||
homeassistant_services: true
|
||||
homeassistant_states: true
|
||||
actions:
|
||||
- action: play_rtttl
|
||||
variables:
|
||||
song: string
|
||||
then:
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify
|
||||
variables:
|
||||
heading: string
|
||||
message: string
|
||||
song: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify_fullscreen
|
||||
variables:
|
||||
title: string
|
||||
description: string
|
||||
button1: string
|
||||
button2: string
|
||||
timeout: int
|
||||
icon: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
|
||||
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
|
||||
|
||||
uart:
|
||||
id: display_uart
|
||||
tx_pin: 16
|
||||
rx_pin: 17
|
||||
baud_rate: 115200
|
||||
nspanel: !include
|
||||
file: common/nspanel.yaml
|
||||
vars:
|
||||
temperature_entity: sensor.zigbee_office_j_temperature
|
||||
ntc_reference_temperature: 22°C
|
||||
sleep_dim: !include
|
||||
file: common/nspanel_sleep_dim.yaml
|
||||
vars:
|
||||
sleep_override_entity: light.office_j_lights
|
||||
|
||||
nspanel_lovelace:
|
||||
id: nspanel_id
|
||||
sleep_timeout: 10
|
||||
experimental_vertical_ui: true
|
||||
locale:
|
||||
language: sk
|
||||
temperature_unit: celcius
|
||||
screensaver:
|
||||
time_id: ha_time
|
||||
time_format: '%H:%M'
|
||||
date_format: '%A, %d. %b. %Y'
|
||||
weather:
|
||||
entity_id: sensor.weather_forecast_daily
|
||||
icon_color: [255, 255, 255]
|
||||
entities:
|
||||
- entity_id: sensor.zigbee_office_j_temperature
|
||||
icon:
|
||||
value: home-thermometer-outline
|
||||
color: [255, 255, 255]
|
||||
precision: 1
|
||||
cards:
|
||||
- type: cardGrid
|
||||
title: Janko
|
||||
@@ -134,11 +61,6 @@ nspanel_lovelace:
|
||||
- name: Auto-zatváranie
|
||||
entity_id: switch.gate_auto_close
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: Left Button
|
||||
@@ -162,11 +84,6 @@ binary_sensor:
|
||||
- lambda: 'id(nspanel_id).render_default_page();'
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
id: buzzer_out
|
||||
pin:
|
||||
number: 21
|
||||
|
||||
- id: relay_1
|
||||
platform: gpio
|
||||
pin:
|
||||
@@ -185,48 +102,3 @@ light:
|
||||
# # - platform: binary
|
||||
# # name: Relay 2
|
||||
# # output: relay_2
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: Screen Power
|
||||
id: screen_power
|
||||
entity_category: config
|
||||
pin:
|
||||
number: 4
|
||||
inverted: true
|
||||
restore_mode: ALWAYS_ON
|
||||
|
||||
rtttl:
|
||||
id: buzzer
|
||||
output: buzzer_out
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: ntc_source
|
||||
pin: 38
|
||||
update_interval: 5min
|
||||
attenuation: 12db
|
||||
|
||||
- platform: resistance
|
||||
id: resistance_sensor
|
||||
sensor: ntc_source
|
||||
configuration: DOWNSTREAM
|
||||
resistor: 11.2kOhm
|
||||
|
||||
- platform: ntc
|
||||
name: Temperature
|
||||
id: temperature
|
||||
sensor: resistance_sensor
|
||||
calibration:
|
||||
b_constant: 3950
|
||||
reference_temperature: 22°C
|
||||
reference_resistance: 10kOhm
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: name_day
|
||||
entity_id: sensor.nameday_sk
|
||||
on_value:
|
||||
- lambda: |-
|
||||
id(nspanel_id).set_time_format("%H:%M~" + x);
|
||||
id(nspanel_id).update_time();
|
||||
+6
-134
@@ -10,33 +10,15 @@ esphome:
|
||||
friendly_name: ${name}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
on_boot:
|
||||
then:
|
||||
- switch.turn_off: screen_power
|
||||
- delay: 1s
|
||||
- switch.turn_on: screen_power
|
||||
- delay: 2s
|
||||
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
minimum_chip_revision: 3.0
|
||||
sram1_as_iram: true
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/randybb/esphome-nspanel-lovelace-native
|
||||
ref: devices
|
||||
refresh: 0s
|
||||
components: [nspanel_lovelace]
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
nspanel: !include
|
||||
file: common/nspanel.yaml
|
||||
vars:
|
||||
temperature_entity: sensor.zigbee_office_r_temperature
|
||||
ntc_reference_temperature: 22°C
|
||||
|
||||
script:
|
||||
- id: geiger_click
|
||||
@@ -55,65 +37,7 @@ script:
|
||||
return esphome::random_uint32() % 450 + 50;
|
||||
- script.execute: geiger_click
|
||||
|
||||
api:
|
||||
homeassistant_services: true
|
||||
homeassistant_states: true
|
||||
actions:
|
||||
- action: play_rtttl
|
||||
variables:
|
||||
song: string
|
||||
then:
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify
|
||||
variables:
|
||||
heading: string
|
||||
message: string
|
||||
song: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify_fullscreen
|
||||
variables:
|
||||
title: string
|
||||
description: string
|
||||
button1: string
|
||||
button2: string
|
||||
timeout: int
|
||||
icon: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
|
||||
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
|
||||
|
||||
uart:
|
||||
id: display_uart
|
||||
tx_pin: 16
|
||||
rx_pin: 17
|
||||
baud_rate: 115200
|
||||
|
||||
nspanel_lovelace:
|
||||
id: nspanel_id
|
||||
sleep_timeout: 10
|
||||
experimental_vertical_ui: true
|
||||
locale:
|
||||
language: sk
|
||||
temperature_unit: celcius
|
||||
screensaver:
|
||||
time_id: ha_time
|
||||
time_format: '%H:%M'
|
||||
date_format: '%A, %d. %b. %Y'
|
||||
weather:
|
||||
entity_id: sensor.weather_forecast_daily
|
||||
icon_color: [255, 255, 255]
|
||||
entities:
|
||||
- entity_id: sensor.zigbee_office_r_temperature
|
||||
icon:
|
||||
value: home-thermometer-outline
|
||||
color: [255, 255, 255]
|
||||
precision: 1
|
||||
cards:
|
||||
- type: cardGrid
|
||||
title: Romanko
|
||||
@@ -158,11 +82,6 @@ nspanel_lovelace:
|
||||
- name: Auto-zatváranie
|
||||
entity_id: switch.gate_auto_close
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: Left Button
|
||||
@@ -186,10 +105,6 @@ binary_sensor:
|
||||
- lambda: 'id(nspanel_id).render_default_page();'
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
id: buzzer_out
|
||||
pin:
|
||||
number: 21
|
||||
- id: relay_1
|
||||
platform: gpio
|
||||
pin:
|
||||
@@ -229,14 +144,6 @@ switch:
|
||||
id: relay_2
|
||||
pin:
|
||||
number: 19
|
||||
- platform: gpio
|
||||
name: Screen Power
|
||||
id: screen_power
|
||||
entity_category: config
|
||||
pin:
|
||||
number: 4
|
||||
inverted: true
|
||||
restore_mode: ALWAYS_ON
|
||||
- platform: template
|
||||
name: "Geiger Counter"
|
||||
optimistic: True
|
||||
@@ -244,38 +151,3 @@ switch:
|
||||
- script.execute: geiger_click
|
||||
turn_off_action:
|
||||
- script.stop: geiger_click
|
||||
|
||||
rtttl:
|
||||
id: buzzer
|
||||
output: buzzer_out
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: ntc_source
|
||||
pin: 38
|
||||
update_interval: 5min
|
||||
attenuation: 12db
|
||||
|
||||
- platform: resistance
|
||||
id: resistance_sensor
|
||||
sensor: ntc_source
|
||||
configuration: DOWNSTREAM
|
||||
resistor: 11.2kOhm
|
||||
|
||||
- platform: ntc
|
||||
name: Temperature
|
||||
id: temperature
|
||||
sensor: resistance_sensor
|
||||
calibration:
|
||||
b_constant: 3950
|
||||
reference_temperature: 22°C
|
||||
reference_resistance: 10kOhm
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: name_day
|
||||
entity_id: sensor.nameday_sk
|
||||
on_value:
|
||||
- lambda: |-
|
||||
id(nspanel_id).set_time_format("%H:%M~" + x);
|
||||
id(nspanel_id).update_time();
|
||||
+12
-137
@@ -10,93 +10,16 @@ esphome:
|
||||
friendly_name: ${name}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
on_boot:
|
||||
then:
|
||||
- switch.turn_off: screen_power
|
||||
- delay: 1s
|
||||
- switch.turn_on: screen_power
|
||||
- delay: 2s
|
||||
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
minimum_chip_revision: 3.0
|
||||
sram1_as_iram: true
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/randybb/esphome-nspanel-lovelace-native
|
||||
ref: devices
|
||||
refresh: 0s
|
||||
components: [nspanel_lovelace]
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
|
||||
api:
|
||||
homeassistant_services: true
|
||||
homeassistant_states: true
|
||||
actions:
|
||||
- action: play_rtttl
|
||||
variables:
|
||||
song: string
|
||||
then:
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify
|
||||
variables:
|
||||
heading: string
|
||||
message: string
|
||||
song: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
|
||||
- rtttl.play:
|
||||
rtttl: !lambda 'return song;'
|
||||
|
||||
- action: notify_fullscreen
|
||||
variables:
|
||||
title: string
|
||||
description: string
|
||||
button1: string
|
||||
button2: string
|
||||
timeout: int
|
||||
icon: string
|
||||
then:
|
||||
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
|
||||
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
|
||||
|
||||
uart:
|
||||
id: display_uart
|
||||
tx_pin: 16
|
||||
rx_pin: 17
|
||||
baud_rate: 115200
|
||||
nspanel: !include
|
||||
file: common/nspanel.yaml
|
||||
vars:
|
||||
temperature_entity: sensor.zigbee_living_room_kitchen_temperature
|
||||
temperature_name: Living Room Temperature
|
||||
|
||||
nspanel_lovelace:
|
||||
id: nspanel_id
|
||||
sleep_timeout: 10
|
||||
experimental_vertical_ui: true
|
||||
locale:
|
||||
language: sk
|
||||
temperature_unit: celcius
|
||||
screensaver:
|
||||
time_id: ha_time
|
||||
time_format: '%H:%M'
|
||||
date_format: '%A, %d. %b. %Y'
|
||||
weather:
|
||||
entity_id: sensor.weather_forecast_daily
|
||||
icon_color: [255, 255, 255]
|
||||
entities:
|
||||
- entity_id: sensor.zigbee_living_room_kitchen_temperature
|
||||
icon:
|
||||
value: home-thermometer-outline
|
||||
color: [255, 255, 255]
|
||||
precision: 1
|
||||
cards:
|
||||
- type: cardGrid
|
||||
title: Kuchyňa / Obývačka
|
||||
@@ -214,11 +137,6 @@ nspanel_lovelace:
|
||||
- name: Rast trávy
|
||||
entity_id: automation.irrigation_grow_grass
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: Left Button
|
||||
@@ -238,8 +156,11 @@ binary_sensor:
|
||||
inverted: true
|
||||
on_click:
|
||||
if:
|
||||
# mcu-patio online -> toggle its lights; otherwise (no API or relay_2 cut its power) the relay
|
||||
condition:
|
||||
api.connected:
|
||||
and:
|
||||
- api.connected:
|
||||
- binary_sensor.is_on: patio_status
|
||||
then:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
@@ -251,11 +172,9 @@ binary_sensor:
|
||||
else:
|
||||
- switch.toggle: relay_2
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
id: buzzer_out
|
||||
pin:
|
||||
number: 21
|
||||
- platform: homeassistant
|
||||
id: patio_status
|
||||
entity_id: binary_sensor.mcu_patio_status
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
@@ -271,47 +190,3 @@ switch:
|
||||
entity_category: config
|
||||
pin:
|
||||
number: 19
|
||||
|
||||
- platform: gpio
|
||||
name: Screen Power
|
||||
id: screen_power
|
||||
entity_category: config
|
||||
pin:
|
||||
number: 4
|
||||
inverted: true
|
||||
restore_mode: ALWAYS_ON
|
||||
|
||||
rtttl:
|
||||
id: buzzer
|
||||
output: buzzer_out
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: ntc_source
|
||||
pin: 38
|
||||
update_interval: 5min
|
||||
attenuation: 12db
|
||||
|
||||
- platform: resistance
|
||||
id: resistance_sensor
|
||||
sensor: ntc_source
|
||||
configuration: DOWNSTREAM
|
||||
resistor: 11.2kOhm
|
||||
|
||||
- platform: ntc
|
||||
name: Living Room Temperature
|
||||
id: temperature
|
||||
sensor: resistance_sensor
|
||||
calibration:
|
||||
b_constant: 3950
|
||||
reference_temperature: 25°C
|
||||
reference_resistance: 10kOhm
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: name_day
|
||||
entity_id: sensor.nameday_sk
|
||||
on_value:
|
||||
- lambda: |-
|
||||
id(nspanel_id).set_time_format("%H:%M~" + x);
|
||||
id(nspanel_id).update_time();
|
||||
@@ -22,6 +22,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# GPIO21 UART TX
|
||||
# GPIO20 UART RX
|
||||
|
||||
+1
-5
@@ -37,6 +37,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
|
||||
# PINOUTS: M5ATOM+Atom Hub Switch
|
||||
@@ -54,11 +55,6 @@ packages:
|
||||
# Relay1 G22
|
||||
# Relay2 G19
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: Europe/Bratislava
|
||||
|
||||
i2c:
|
||||
# - id: i2c0
|
||||
# sda: 26
|
||||
|
||||
@@ -23,6 +23,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
uart:
|
||||
- id: cse7766_uart
|
||||
@@ -187,5 +188,3 @@ switch:
|
||||
- switch.turn_off: relay_off # bi-stable relay so no need to keep on
|
||||
interlock: [relay_on]
|
||||
|
||||
time:
|
||||
- !include common/time/homeassistant.yaml
|
||||
+1
-4
@@ -2,7 +2,7 @@
|
||||
substitutions:
|
||||
device: pow-washer
|
||||
name: Washer POW
|
||||
comment:
|
||||
comment: "spare"
|
||||
area: Washroom
|
||||
update_interval: 10s
|
||||
|
||||
@@ -186,6 +186,3 @@ switch:
|
||||
- delay: 500ms
|
||||
- switch.turn_off: relay_off # bi-stable relay so no need to keep on
|
||||
interlock: [relay_on]
|
||||
|
||||
time:
|
||||
- !include common/time/homeassistant.yaml
|
||||
@@ -19,6 +19,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
spi:
|
||||
clk_pin: 23
|
||||
|
||||
@@ -40,6 +40,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
# logger:
|
||||
# hardware_uart: UART0
|
||||
|
||||
+1
-2
@@ -21,6 +21,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
i2c:
|
||||
sda: 25
|
||||
@@ -109,5 +110,3 @@ switch:
|
||||
number: 19
|
||||
mode: OUTPUT
|
||||
|
||||
time:
|
||||
- !include common/time/homeassistant.yaml
|
||||
+1
-2
@@ -121,8 +121,7 @@ switch:
|
||||
# number: GPIO00
|
||||
# inverted: True
|
||||
|
||||
time:
|
||||
- !include common/time/homeassistant.yaml
|
||||
# time:
|
||||
# - platform: homeassistant
|
||||
# timezone: Europe/Bratislava
|
||||
# on_time:
|
||||
|
||||
+1
-2
@@ -122,8 +122,7 @@ switch:
|
||||
on_turn_off:
|
||||
- light.turn_off: light_red
|
||||
|
||||
time:
|
||||
- !include common/time/homeassistant.yaml
|
||||
# time:
|
||||
# - platform: homeassistant
|
||||
# timezone: Europe/Bratislava
|
||||
# on_time:
|
||||
|
||||
+1
-2
@@ -144,8 +144,7 @@ switch:
|
||||
# number: GPIO00
|
||||
# inverted: True
|
||||
|
||||
time:
|
||||
- !include common/time/homeassistant.yaml
|
||||
# time:
|
||||
# - platform: homeassistant
|
||||
# timezone: Europe/Bratislava
|
||||
# on_time:
|
||||
|
||||
+25
-11
@@ -33,14 +33,15 @@ psram:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
external_components:
|
||||
- source: github://m5stack/esphome-yaml/components
|
||||
components: [axp2101, aw88298, aw9523b, si12t, m5ioe1, ftservo, scs9009]
|
||||
refresh: 0s
|
||||
- source: github://randybb/esphome-components
|
||||
components: [audio_spectrum]
|
||||
refresh: 1d
|
||||
components: [audio_spectrum, speaker] # speaker: esphome#19862, drop once merged
|
||||
refresh: 1h
|
||||
|
||||
globals:
|
||||
- id: in_love
|
||||
@@ -464,9 +465,9 @@ spi:
|
||||
|
||||
image:
|
||||
- platform: file
|
||||
file: assets/images/esphome-logo-text-on-dark.svg
|
||||
file: assets/images/made-for-esphome-white-on-black.svg
|
||||
id: esphome_logo
|
||||
resize: 280x56
|
||||
resize: 230x68
|
||||
type: RGB565
|
||||
transparency: alpha_channel
|
||||
- platform: file
|
||||
@@ -545,22 +546,36 @@ display:
|
||||
if (id(show_status)) {
|
||||
first_draw = true;
|
||||
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}");
|
||||
|
||||
if (wifi_ok) {
|
||||
it.printf(20, 135, id(font_status), id(status_ok), "WiFi: %s (%d dBm)",
|
||||
id(ip_address_sensor).state.c_str(), rssi);
|
||||
it.printf(20, 114, id(font_status), id(status_ok), "WiFi: connected (%d dBm)", 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 {
|
||||
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();
|
||||
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...");
|
||||
|
||||
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)" : "");
|
||||
}
|
||||
it.printf(20, 212, id(font_status), id(status_dim), "ESPHome %s", ESPHOME_VERSION);
|
||||
@@ -883,4 +898,3 @@ time:
|
||||
id: rtc_time
|
||||
i2c_id: i2c_bsp
|
||||
update_interval: never
|
||||
- !include common/time/homeassistant.yaml
|
||||
+231
-79
@@ -10,9 +10,10 @@ esphome:
|
||||
friendly_name: ${name}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
platformio_options:
|
||||
board_build.flash_mode: dio
|
||||
|
||||
on_boot:
|
||||
- priority: -100
|
||||
then:
|
||||
- lvgl.page.show: status_page
|
||||
|
||||
esp32:
|
||||
variant: esp32c3
|
||||
@@ -21,93 +22,244 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
logger:
|
||||
logs:
|
||||
component: ERROR
|
||||
|
||||
# GPIO1 UART TX
|
||||
# GPIO3 UART RX
|
||||
# GPIO4 I2C SDA
|
||||
# GPIO5 I2C SCL
|
||||
# GPIO12 DISPLAY DIO
|
||||
# GPIO12 DISPLAY CLOCK
|
||||
# GPIO14 BUZZER
|
||||
# GPIO16 RGB
|
||||
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
|
||||
|
||||
spi:
|
||||
clk_pin: 4
|
||||
mosi_pin: 5
|
||||
miso_pin: 6
|
||||
|
||||
color:
|
||||
- id: my_green
|
||||
hex: 00ff00
|
||||
- id: my_magenta
|
||||
hex: ff00ff
|
||||
- id: my_red
|
||||
hex: ff0000
|
||||
- id: my_white
|
||||
hex: ffffff
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: font_default
|
||||
size: 20
|
||||
- file: "gfonts://B612"
|
||||
id: b612_24
|
||||
size: 24
|
||||
- file: "gfonts://B612"
|
||||
id: b612_16
|
||||
size: 16
|
||||
- file: "gfonts://B612"
|
||||
id: b612_12
|
||||
size: 12
|
||||
- file: "gfonts://B612 Mono"
|
||||
id: b612_24_mono
|
||||
size: 24
|
||||
- file: "gfonts://B612 Mono"
|
||||
id: b612_16_mono
|
||||
size: 16
|
||||
- file: "gfonts://B612 Mono"
|
||||
id: b612_12_mono
|
||||
size: 12
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto_32
|
||||
size: 32
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto_24
|
||||
size: 24
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto_16
|
||||
size: 16
|
||||
- file: "gfonts://Roboto"
|
||||
id: roboto_12
|
||||
size: 12
|
||||
- file: "gfonts://Roboto Mono"
|
||||
id: roboto_32_mono
|
||||
size: 32
|
||||
- file: "gfonts://Roboto Mono"
|
||||
id: roboto_24_mono
|
||||
size: 24
|
||||
- file: "gfonts://Roboto Mono"
|
||||
id: roboto_16_mono
|
||||
size: 16
|
||||
- file: "gfonts://Roboto Mono"
|
||||
id: roboto_12_mono
|
||||
size: 12
|
||||
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
id: main_display
|
||||
model: ILI9341
|
||||
cs_pin: 7
|
||||
dc_pin: 8
|
||||
reset_pin: 10
|
||||
invert_colors: false
|
||||
lambda: |-
|
||||
it.fill(Color::BLACK);
|
||||
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, 28, id(b612_12), id(my_magenta), TextAlign::TOP_LEFT, "HELLO WORLD!");
|
||||
it.print(0, 42, id(b612_12), id(my_red), TextAlign::TOP_LEFT, "HELLO WORLD!");
|
||||
data_rate: 40MHz
|
||||
update_interval: never
|
||||
auto_clear_enabled: false
|
||||
|
||||
touchscreen:
|
||||
- platform: xpt2046
|
||||
display: main_display
|
||||
cs_pin: 0
|
||||
interrupt_pin: 1
|
||||
transform:
|
||||
mirror_x: true
|
||||
calibration:
|
||||
x_min: 330
|
||||
x_max: 3620
|
||||
y_min: 230
|
||||
y_max: 3680
|
||||
|
||||
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\nup %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: 220x65
|
||||
type: RGB565
|
||||
transparency: alpha_channel
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto@500"
|
||||
id: font_clock
|
||||
size: 80
|
||||
glyphs: "0123456789:-"
|
||||
- file: "gfonts://Roboto"
|
||||
id: font_title
|
||||
size: 24
|
||||
- file: "gfonts://Roboto"
|
||||
id: font_status
|
||||
size: 14
|
||||
|
||||
lvgl:
|
||||
log_level: WARN
|
||||
color_depth: 16
|
||||
bg_color: 0x000000
|
||||
text_color: 0xFFFFFF
|
||||
theme:
|
||||
button:
|
||||
bg_color: 0x2B2F36
|
||||
radius: 12
|
||||
checked:
|
||||
bg_color: 0xE08A00
|
||||
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 the other LVGL displays
|
||||
- buttonmatrix:
|
||||
id: nav_bar
|
||||
align: bottom_mid
|
||||
width: 100%
|
||||
height: 48
|
||||
pad_all: 4
|
||||
pad_column: 6
|
||||
items:
|
||||
text_font: montserrat_20
|
||||
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: -24
|
||||
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: 8
|
||||
- label:
|
||||
align: TOP_MID
|
||||
y: 78
|
||||
text_font: font_title
|
||||
text: "${name}"
|
||||
- obj:
|
||||
x: 8
|
||||
y: 116
|
||||
width: 224
|
||||
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
|
||||
text: ""
|
||||
- label:
|
||||
id: status_ha
|
||||
text_font: font_status
|
||||
text: ""
|
||||
- label:
|
||||
id: status_footer
|
||||
text_font: font_status
|
||||
text_color: 0x8C8C8C
|
||||
text: ""
|
||||
+1
-2
@@ -34,6 +34,7 @@ esp32:
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
ipv6_only: !include common/ipv6_only.yaml
|
||||
|
||||
api:
|
||||
actions:
|
||||
@@ -358,5 +359,3 @@ valve:
|
||||
close_action:
|
||||
- switch.turn_off: irrigation_relay_8
|
||||
|
||||
time:
|
||||
- !include common/time/homeassistant.yaml
|
||||
@@ -1,6 +1,9 @@
|
||||
wifi_ssid: "ssid"
|
||||
wifi_password: "password"
|
||||
|
||||
wifi_ssid_ipv6: "ssid"
|
||||
wifi_password_ipv6: "password"
|
||||
|
||||
api_password: "password"
|
||||
api_key: "oSsCFxRUqIJLpT98cQwSwNxYoC7zbUh0YBHczAWRyBI="
|
||||
|
||||
|
||||
Reference in new issue
Block a user