diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 329c2c9c..53104d3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -120,7 +120,12 @@ jobs: working-directory: "D:\\a\\HASS.Agent\\HASS.Agent\\src\\HASS.Agent\\HASS.Agent.Satellite.Service" run: "dotnet publish -c Release -f net6.0-windows10.0.19041.0 -o bin\\Publish-x64\\Release\\ --no-self-contained -r win-x64 -p:Platform=x64" - - name: Compile InnoSetup Installer + - name: Compile InnoSetup Installer - Satellite Service + working-directory: "D:\\a\\HASS.Agent\\HASS.Agent\\src\\HASS.Agent.Installer" + run: | + & "${Env:ProgramFiles(x86)}\\Inno Setup 6\\iscc.exe" InstallerScript-Service.iss + + - name: Compile InnoSetup Installer - Client working-directory: "D:\\a\\HASS.Agent\\HASS.Agent\\src\\HASS.Agent.Installer" run: | & "${Env:ProgramFiles(x86)}\\Inno Setup 6\\iscc.exe" InstallerScript.iss diff --git a/src/HASS.Agent.Installer/AfterInstallNotice-Service.rtf b/src/HASS.Agent.Installer/AfterInstallNotice-Service.rtf new file mode 100644 index 00000000..e0517548 --- /dev/null +++ b/src/HASS.Agent.Installer/AfterInstallNotice-Service.rtf @@ -0,0 +1,222 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang2057\deflangfe2057\themelang1045\themelangfe0\themelangcs1025{\fonttbl{\f0\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\f34\fbidi \froman\fcharset238\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f43\fbidi \fswiss\fcharset238\fprq2{\*\panose 00000000000000000000}Tahoma;} +{\flomajor\f31500\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhimajor\f31502\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\flominor\f31504\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhiminor\f31506\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \fswiss\fcharset238\fprq2{\*\panose 020b0604020202020204}Arial;}{\f46\fbidi \froman\fcharset0\fprq2 Times New Roman;} +{\f45\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f47\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f48\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f49\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\f50\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f51\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f52\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f386\fbidi \froman\fcharset0\fprq2 Cambria Math;} +{\f385\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f387\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f388\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f391\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;} +{\f392\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f476\fbidi \fswiss\fcharset0\fprq2 Tahoma;}{\f475\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr;}{\f477\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek;} +{\f478\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur;}{\f479\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew);}{\f480\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic);}{\f481\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic;} +{\f482\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese);}{\f483\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai);}{\flomajor\f31510\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbmajor\f31520\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31530\fbidi \fswiss\fcharset0\fprq2 Calibri Light;} +{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;} +{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;} +{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31540\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\flominor\f31550\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31560\fbidi \froman\fcharset0\fprq2 Times New Roman;} +{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31570\fbidi \fswiss\fcharset0\fprq2 Calibri;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);} +{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);} +{\fbiminor\f31580\fbidi \fswiss\fcharset0\fprq2 Arial;}{\fbiminor\f31579\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;}{\fbiminor\f31581\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\fbiminor\f31582\fbidi \fswiss\fcharset162\fprq2 Arial Tur;} +{\fbiminor\f31583\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}{\fbiminor\f31584\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);}{\fbiminor\f31585\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;} +{\fbiminor\f31586\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0; +\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0; +\chyperlink\ctint255\cshade255\red5\green99\blue193;\red96\green94\blue92;\red225\green223\blue221;}{\*\defchp \f31506\fs22\lang2057\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\upr{\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 +\ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid11156637 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid11156637 Unresolved Mention;}{ +\s17\ql \li720\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\contextualspace \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 +\sbasedon0 \snext17 \sqformat \spriority34 \styrsid5989241 List Paragraph;}}{\*\ud\uc0{\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 +\f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid11156637 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid11156637 Unresolved Mention;}{ +\s17\ql \li720\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\contextualspace \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 +\sbasedon0 \snext17 \sqformat \spriority34 \styrsid5989241 List Paragraph;}}}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid8367\rsid4147124\rsid5989241\rsid7820185\rsid7821067\rsid8347995\rsid8915765\rsid9249300\rsid11156637\rsid11431173 +\rsid12408085\rsid15815836\rsid15995208\rsid16254539\rsid16263522\rsid16538847}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Amadeo}{\operator Amadeo} +{\creatim\yr2023\mo11\dy27\hr16\min48}{\revtim\yr2024\mo5\dy5\hr20\min11}{\version13}{\edmins110}{\nofpages1}{\nofwords69}{\nofchars394}{\nofcharsws462}{\vern81}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} +\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1417\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1 +\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1417\dgvorigin1417\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot16254539\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 +{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}} +{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9 +\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid16254539 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 +\f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid11431173 \line }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539\charrsid8347995 Thank you for choosing HASS.Agent! +\par We hope you'll have a seamless and enjoyable experience with our application. If you ever need assistance or have feedback, feel free to reach out to the HASS.Agent team on}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid5989241\charrsid8347995 } +{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid8367 HYPERLINK "https://discord.com/invite/JfZj98xqJr"}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid8367\charrsid8347995 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b64000000680074007400700073003a002f002f0064006900730063006f00720064002e0063006f006d002f0069006e0076006900740065002f004a0066005a006a0039003800780071004a0072000000795881f43b1d +7f48af2c825dc485276300000000a5ab000345}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs15\f43\fs16\ul\cf19\insrsid5989241\charrsid8347995 Discord}}}\sectd \ltrsect +\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid5989241\charrsid8347995 . +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539\charrsid8347995 Make sure to read through the documentation }{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid7820185 HYPERLINK "https://www.hass-agent.io/latest/"}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid7820185\charrsid8347995 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b5c000000680074007400700073003a002f002f007700770077002e0068006100730073002d006100670065006e0074002e0069006f002f006c00610074006500730074002f000000795881f43b1d7f48af2c825dc485 +276300000000a5ab0003000000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs15\f43\fs16\ul\cf19\insrsid16254539\charrsid8347995 here}}}\sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj { +\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539\charrsid8347995 to get started configuring HASS.Agent}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16538847 Satellite Service}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid8347995\charrsid8347995 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid16254539\charrsid8347995 Happy exploring! +\par The HASS.Agent Team}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid12408085\charrsid8347995 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210041daf6e5a6070000e6200000160000007468656d652f7468656d652f +7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f222b524cfda33f660c90e7bac914add65557789aed28cc56208de532e81c026e49085 +bded21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c9f3810326cc0886eed2efbdfad57bafde7baabaffd9ab983a1738e584253db77eafe63a3899b13949 +c29efb7c3aae745c870b94cc116509eeb91bccddcf1efcfa57f7d19188708c1d904ff811eab99110aba36a95cf6018f17b6c8513f86ec1d21809784dc3ea3c45 +97a037a6d546add6aac68824ae93a018d49e32b1b974566849b0583b4f170b32c3ee83ed24230a332582cb81194d27720a9c4b9e29195d64beac4b20dff080a6 +ce05a23d17a69db3cb297e255c87222ee08b9e5b537f6ef5c1fd2a3aca85a83820abc98dd55f2e970bcc970d35671a9e17937a9eefb5fa857e05a0621f376a8f +5aa356a14f01d06c060bceb8983adb8dc0cbb11a287bb4e81eb687cdba81d7f437f738f77df931f00a94e9f7f6f0e371005634f00a94e1fd3dbc3fe80e86a67e +05caf0ad3d7cbbd61f7a6d43bf02459424cb3d74cd6f3583ed6a0bc882d1632bbceb7be37623575ea2201a8a2093532c5822ae08b918bd64e91870124f912089 +23362bbc403388ed0051729e12e7848411c4df0a258cc370ad511bd79af05f7e3cf5a41c8b8e30d2a4253d20c4f786242d87cf52b2123df711687535c8bb9f7f +7efbe6c7b76f7e7afbd5576fdffc3d9f5ba932e48e5112ea72bf7cf787ff7cf35be7df3f7cfbcbd77fcca6dec5731dfffe6fbf7bff8f7f7e483dacb834c5bb3f +7dfffec7efdffdf9f7fffaebd716edfd149debf0298931779ee04be7198b618116fef83cbd99c434424497e82721470992b358f48f4464a09f6c104516dc009b +767c9142c6b1011fae5f1a842751ba16c4a2f171141bc053c6e880a5562b3c967369669eae93d03e79bad671cf10bab0cd1da0c4f0f268bd828c4b6c2a83081b +34cf284a040a71828523bf634b8c2dabfb8210c3aea7649632ce16c2f982380344ac26999273239a4aa16312835f363682e06fc336a72f9c01a3b6550ff18589 +84bd81a885fc1453c38c0fd15aa0d8a6728a62aa1bfc0489c84672b249673a6ec405783ac49439a339e6dc26f33485f56a4e7f0c69c6eef653ba894d642ac8d2 +a6f30431a623876c1944285ed9b01392443af673be841045ce191336f8293377887c073fa0e4a0bb5f402fa04f707536780e1956972803447eb34e2dbe7c8899 +11bf930d5d206c4b35fd3436526c3f25d6e818ac4323b44f30a6e812cd31769e7f6e6130602bc3e625e947116495636c0bac47c88c55f99e608e1dd5e3ece7c9 +13c28d909de0901de073bad9493c1b94c4283da4f909785db7f9084a5d6c0b80a774b6d4814f087486102f56a33ce5a0430bee835acf22641430f9ceedf1ba49 +0dff5d678fc1be7c69d0b8c6be04197c631948ecbacc076d3345d498a00c9829822ec3966e41c4707f29228bab125b5be516e6a62ddd004d92d1f4c424b9b203 +dae97dfcff5def031dc6bbbf7c63d96c1fa7dfb12b3692d50d3b9d43c9e478a7bf3984dbed6a0296cec9a7dfd40cd13a39c35047f633d65d4f73d7d3b8fff73d +cda1fd7cd7c91cea37ee3a19173a8cbb4e263f5cf9389d4cd9bc405f230f3cb2f31e75fa135f75f8b320944ec486e213aece7f38fcac998f61508aabe3505c9c +09ae227894d50ee63170618a948c9332f11b22a249845670485477a59290e7aa43eeac1887b323356cd52df1741d9fb27976f459afcb63ceacc07224caf19a5f +8cc37995c8d0ad76799c57a8576c4375faba2520656f42429bcc24d1b490686f07a591d4592f18cd4242adeca3b0e85a5874a4faadabf65800b5c22bf0bbdb81 +5feb3dd7f7400484e0580e7af4b9f453e6eaad7795333fa6a70f19d38800e8b3b711507aba2bb91e5c9e5c5d166ad7f0b441420b379384b28ceaf37804bf86f3 +e894a3d7a171535f774b971af4a429d47c105a258d76e7432c6eeb6b90dbcd0d34d133054d9ccb9edb6afa103233b4eab90b383b86c77805b1c3e54f2f4443b8 +96998934dbf0b7c92cab948b21e25166709574b26c101381538792b8e7cae5176ea089ca218a5bbd0109e19325d785b4f2a99103a79b4ec68b059e09ddedda88 +b474f60a193ecb15d66f95f8edc15292adc1dd93687ee99cd375fa0c4188f9edba34e09c70b842a867d69c13b81a2b1259197f3b85294fbbfadd948aa16c1cd1 +5584f28aa227f30cae52794147bd1536d0def23583413593e485f03c94055637aa514d8baa9171385875af169296d3926659338dac22aba63d8b19336ccbc08e +2d6f57e435565b13434ed32b7c96ba77536e779beb76fa84a24a80c10bfb59aaee350a8246ad9ccca02619efa76199b3f351b3766c177805b5eb14092debb7b6 +6a77ec56d408eb743078abca0f72bb510b438b6d7ba92cadaed4f5db6e76fe1292c7109add35155cb9122eb153040dd144f52459da802df24ae45b039e9c754a +7aee9735bfef050d3fa8d43afea8e235bd5aa5e3f79b95beef37eb23bf5e1b0e1aafa1b08828aefbd975fe18ee31e826bfd457e37b17fbf1f6aae6de8cc555a6 +aeeaab8ab8bad8af378c8bfdec2adf99ca1b7bd7219074be6c35c6dd6677d0aa749bfd71c51b0e3a956ed01a5486ada03d1c0f03bfd31dbf769d0b05f6facdc0 +6b8d3a95563d082a5eab26e977ba95b6d768f4bd76bf33f2faaff33606569ea58fdc16605ec5ebc17f010000ffff0300504b0304140006000800000021000dd1 +909fb60000001b010000270000007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277 +086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b +64b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996 +509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff +0000001c0200001300000000000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7 +c0000000360100000b00000000000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000 +001c00000000000000000000000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210041 +daf6e5a6070000e62000001600000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d0014000600080000 +0021000dd1909fb60000001b0100002700000000000000000000000000b00a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000ab0b00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid; +\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid; +\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1; +\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1; +\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; +\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2; +\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3; +\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; +\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e50000000000000000000000000098 +809f179fda01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/src/HASS.Agent.Installer/BeforeInstallNotice-Service.rtf b/src/HASS.Agent.Installer/BeforeInstallNotice-Service.rtf new file mode 100644 index 00000000..b9ab19df --- /dev/null +++ b/src/HASS.Agent.Installer/BeforeInstallNotice-Service.rtf @@ -0,0 +1,218 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang2057\deflangfe2057\themelang1045\themelangfe0\themelangcs1025{\fonttbl{\f0\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\f34\fbidi \froman\fcharset238\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f43\fbidi \fswiss\fcharset238\fprq2{\*\panose 020b0604030504040204}Tahoma;} +{\flomajor\f31500\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhimajor\f31502\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\flominor\f31504\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhiminor\f31506\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \fswiss\fcharset238\fprq2{\*\panose 020b0604020202020204}Arial;}{\f46\fbidi \froman\fcharset0\fprq2 Times New Roman;} +{\f45\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f47\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f48\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f49\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\f50\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f51\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f52\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f386\fbidi \froman\fcharset0\fprq2 Cambria Math;} +{\f385\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f387\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f388\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f391\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;} +{\f392\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f476\fbidi \fswiss\fcharset0\fprq2 Tahoma;}{\f475\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr;}{\f477\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek;} +{\f478\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur;}{\f479\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew);}{\f480\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic);}{\f481\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic;} +{\f482\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese);}{\f483\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai);}{\flomajor\f31510\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbmajor\f31520\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31530\fbidi \fswiss\fcharset0\fprq2 Calibri Light;} +{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;} +{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;} +{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31540\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\flominor\f31550\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31560\fbidi \froman\fcharset0\fprq2 Times New Roman;} +{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31570\fbidi \fswiss\fcharset0\fprq2 Calibri;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);} +{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);} +{\fbiminor\f31580\fbidi \fswiss\fcharset0\fprq2 Arial;}{\fbiminor\f31579\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;}{\fbiminor\f31581\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\fbiminor\f31582\fbidi \fswiss\fcharset162\fprq2 Arial Tur;} +{\fbiminor\f31583\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}{\fbiminor\f31584\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);}{\fbiminor\f31585\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;} +{\fbiminor\f31586\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0; +\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0; +\chyperlink\ctint255\cshade255\red5\green99\blue193;\red96\green94\blue92;\red225\green223\blue221;}{\*\defchp \f31506\fs22\lang2057\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\upr{\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 +\ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid490208 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid490208 Unresolved Mention;}} +{\*\ud\uc0{\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 +\snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid490208 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid490208 Unresolved Mention;}}}} +{\*\rsidtbl \rsid227266\rsid490208\rsid1248923\rsid2324250\rsid3874449\rsid4072052\rsid5209844\rsid6709929\rsid7356386\rsid7820567\rsid12408085\rsid16263522}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1 +\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Amadeo}{\operator Amadeo}{\creatim\yr2023\mo11\dy27\hr16\min53}{\revtim\yr2024\mo5\dy5\hr20\min10}{\version9}{\edmins14}{\nofpages1}{\nofwords76}{\nofchars435}{\nofcharsws510}{\vern81}} +{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1417\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1 +\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1417\dgvorigin1417\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot490208\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 +{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}} +{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9 +\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid490208 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 +\f31506\fs22\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid227266 \line }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 Welcome to HASS.Agent}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\f43\fs16\insrsid1248923 Satellite Service}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 Installer!}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 This version is maintained by the }{\field{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid2324250 HYPERLINK "https://github.com/hass-agent/" }{\rtlch\fcs1 \af43 +\ltrch\fcs0 \f43\fs16\insrsid2324250 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b56000000680074007400700073003a002f002f006700690074006800750062002e0063006f006d002f0068006100730073002d006100670065006e0074002f000000795881f43b1d7f48af2c825dc485276300000000 +a5ab000300}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs15\f43\fs16\ul\cf19\insrsid490208\charrsid2324250 HASS.Agent Team}}}\sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\rtlch\fcs1 +\af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 and is a fork of the }{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 HYPERLINK "https://github.com/LAB02-Research/HASS.Agent" }{\rtlch\fcs1 +\af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b72000000680074007400700073003a002f002f006700690074006800750062002e0063006f006d002f004c0041004200300032002d00520065007300650061007200630068002f0048004100530053002e0041006700 +65006e0074000000795881f43b1d7f48af2c825dc485276300000000a5ab00030064d96621af0000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs15\f43\fs16\ul\cf19\insrsid490208\charrsid4072052 original}}}\sectd \ltrsect +\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid16263522\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid490208\charrsid4072052 version by LAB02-Research. +\par The intention of this version is to provide bug-fix and feature updated until the development of original version is resumed. +\par We've tried our best to maintain compatibility with the original version but some breaking changes are unavoidable.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \f43\fs16\insrsid12408085\charrsid4072052 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210041daf6e5a6070000e6200000160000007468656d652f7468656d652f +7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f222b524cfda33f660c90e7bac914add65557789aed28cc56208de532e81c026e49085 +bded21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c9f3810326cc0886eed2efbdfad57bafde7baabaffd9ab983a1738e584253db77eafe63a3899b13949 +c29efb7c3aae745c870b94cc116509eeb91bccddcf1efcfa57f7d19188708c1d904ff811eab99110aba36a95cf6018f17b6c8513f86ec1d21809784dc3ea3c45 +97a037a6d546add6aac68824ae93a018d49e32b1b974566849b0583b4f170b32c3ee83ed24230a332582cb81194d27720a9c4b9e29195d64beac4b20dff080a6 +ce05a23d17a69db3cb297e255c87222ee08b9e5b537f6ef5c1fd2a3aca85a83820abc98dd55f2e970bcc970d35671a9e17937a9eefb5fa857e05a0621f376a8f +5aa356a14f01d06c060bceb8983adb8dc0cbb11a287bb4e81eb687cdba81d7f437f738f77df931f00a94e9f7f6f0e371005634f00a94e1fd3dbc3fe80e86a67e +05caf0ad3d7cbbd61f7a6d43bf02459424cb3d74cd6f3583ed6a0bc882d1632bbceb7be37623575ea2201a8a2093532c5822ae08b918bd64e91870124f912089 +23362bbc403388ed0051729e12e7848411c4df0a258cc370ad511bd79af05f7e3cf5a41c8b8e30d2a4253d20c4f786242d87cf52b2123df711687535c8bb9f7f +7efbe6c7b76f7e7afbd5576fdffc3d9f5ba932e48e5112ea72bf7cf787ff7cf35be7df3f7cfbcbd77fcca6dec5731dfffe6fbf7bff8f7f7e483dacb834c5bb3f +7dfffec7efdffdf9f7fffaebd716edfd149debf0298931779ee04be7198b618116fef83cbd99c434424497e82721470992b358f48f4464a09f6c104516dc009b +767c9142c6b1011fae5f1a842751ba16c4a2f171141bc053c6e880a5562b3c967369669eae93d03e79bad671cf10bab0cd1da0c4f0f268bd828c4b6c2a83081b +34cf284a040a71828523bf634b8c2dabfb8210c3aea7649632ce16c2f982380344ac26999273239a4aa16312835f363682e06fc336a72f9c01a3b6550ff18589 +84bd81a885fc1453c38c0fd15aa0d8a6728a62aa1bfc0489c84672b249673a6ec405783ac49439a339e6dc26f33485f56a4e7f0c69c6eef653ba894d642ac8d2 +a6f30431a623876c1944285ed9b01392443af673be841045ce191336f8293377887c073fa0e4a0bb5f402fa04f707536780e1956972803447eb34e2dbe7c8899 +11bf930d5d206c4b35fd3436526c3f25d6e818ac4323b44f30a6e812cd31769e7f6e6130602bc3e625e947116495636c0bac47c88c55f99e608e1dd5e3ece7c9 +13c28d909de0901de073bad9493c1b94c4283da4f909785db7f9084a5d6c0b80a774b6d4814f087486102f56a33ce5a0430bee835acf22641430f9ceedf1ba49 +0dff5d678fc1be7c69d0b8c6be04197c631948ecbacc076d3345d498a00c9829822ec3966e41c4707f29228bab125b5be516e6a62ddd004d92d1f4c424b9b203 +dae97dfcff5def031dc6bbbf7c63d96c1fa7dfb12b3692d50d3b9d43c9e478a7bf3984dbed6a0296cec9a7dfd40cd13a39c35047f633d65d4f73d7d3b8fff73d +cda1fd7cd7c91cea37ee3a19173a8cbb4e263f5cf9389d4cd9bc405f230f3cb2f31e75fa135f75f8b320944ec486e213aece7f38fcac998f61508aabe3505c9c +09ae227894d50ee63170618a948c9332f11b22a249845670485477a59290e7aa43eeac1887b323356cd52df1741d9fb27976f459afcb63ceacc07224caf19a5f +8cc37995c8d0ad76799c57a8576c4375faba2520656f42429bcc24d1b490686f07a591d4592f18cd4242adeca3b0e85a5874a4faadabf65800b5c22bf0bbdb81 +5feb3dd7f7400484e0580e7af4b9f453e6eaad7795333fa6a70f19d38800e8b3b711507aba2bb91e5c9e5c5d166ad7f0b441420b379384b28ceaf37804bf86f3 +e894a3d7a171535f774b971af4a429d47c105a258d76e7432c6eeb6b90dbcd0d34d133054d9ccb9edb6afa103233b4eab90b383b86c77805b1c3e54f2f4443b8 +96998934dbf0b7c92cab948b21e25166709574b26c101381538792b8e7cae5176ea089ca218a5bbd0109e19325d785b4f2a99103a79b4ec68b059e09ddedda88 +b474f60a193ecb15d66f95f8edc15292adc1dd93687ee99cd375fa0c4188f9edba34e09c70b842a867d69c13b81a2b1259197f3b85294fbbfadd948aa16c1cd1 +5584f28aa227f30cae52794147bd1536d0def23583413593e485f03c94055637aa514d8baa9171385875af169296d3926659338dac22aba63d8b19336ccbc08e +2d6f57e435565b13434ed32b7c96ba77536e779beb76fa84a24a80c10bfb59aaee350a8246ad9ccca02619efa76199b3f351b3766c177805b5eb14092debb7b6 +6a77ec56d408eb743078abca0f72bb510b438b6d7ba92cadaed4f5db6e76fe1292c7109add35155cb9122eb153040dd144f52459da802df24ae45b039e9c754a +7aee9735bfef050d3fa8d43afea8e235bd5aa5e3f79b95beef37eb23bf5e1b0e1aafa1b08828aefbd975fe18ee31e826bfd457e37b17fbf1f6aae6de8cc555a6 +aeeaab8ab8bad8af378c8bfdec2adf99ca1b7bd7219074be6c35c6dd6677d0aa749bfd71c51b0e3a956ed01a5486ada03d1c0f03bfd31dbf769d0b05f6facdc0 +6b8d3a95563d082a5eab26e977ba95b6d768f4bd76bf33f2faaff33606569ea58fdc16605ec5ebc17f010000ffff0300504b0304140006000800000021000dd1 +909fb60000001b010000270000007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277 +086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b +64b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996 +509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff +0000001c0200001300000000000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7 +c0000000360100000b00000000000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000 +001c00000000000000000000000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210041 +daf6e5a6070000e62000001600000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d0014000600080000 +0021000dd1909fb60000001b0100002700000000000000000000000000b00a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000ab0b00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid; +\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid; +\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1; +\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1; +\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; +\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2; +\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3; +\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; +\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000803c +d68e179fda01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/src/HASS.Agent.Installer/InstallerScript-Service.iss b/src/HASS.Agent.Installer/InstallerScript-Service.iss new file mode 100644 index 00000000..f630602d --- /dev/null +++ b/src/HASS.Agent.Installer/InstallerScript-Service.iss @@ -0,0 +1,114 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! +; Modified to suit HASS.Agent requirements :) + +; InnoDependencyInstaller +; Thanks to https://github.com/DomGries/InnoDependencyInstaller for the amazing work! +#define public Dependency_Path_NetCoreCheck "dependencies\" +#include "CodeDependencies.iss" + +; Standard installation constants +#define MyAppName "HASS.Agent Satellite Service" +#define MyAppVersion "2.1.0-beta3" +#define MyAppPublisher "HASS.Agent Team" +#define MyAppURL "https://hass-agent.io" +#define MyAppExeName "HASS.Agent.Satellite.Service.exe" +#define ServiceName "hass.agent.svc" +#define ServiceDisplayName "HASS.Agent - Satellite Service" +#define ServiceDescription "Satellite service for HASS.Agent: a Windows based Home Assistant client. This service processes commands and sensors without the requirement of a logged-in user." + +[Setup] +ArchitecturesInstallIn64BitMode=x64 +;SetupMutex=Global\HASS.Agent.Setup.Satellite.Mutex,HASS.Agent.Satellite.Setup.Mutex +AppMutex=Global\\HASS.Agent.Service.Mutex +AppId={{4004588E-F411-41C2-ABD8-A898B0A14B93} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={commonpf64}\{#MyAppName}\Service +DisableProgramGroupPage=yes +LicenseFile=..\..\LICENSE.md +InfoBeforeFile=.\BeforeInstallNotice-Service.rtf +InfoAfterFile=.\AfterInstallNotice-Service.rtf +PrivilegesRequired=admin +OutputDir=.\bin +OutputBaseFilename=HASS.Agent.Service.Installer +SetupIconFile=..\HASS.Agent\HASS.Agent.Shared\hassagent.ico +Compression=lzma +SolidCompression=yes +WizardStyle=modern +CloseApplications=force +CloseApplicationsFilter=*.* +UninstallDisplayIcon={app}\{#MyAppExeName} +UninstallDisplayName={#MyAppName} {#MyAppVersion} + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +; NOTE: Don't use "Flags: ignoreversion" on any shared system files +[Files] +; Service files +Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Run] +Filename: "{sys}\sc.exe"; Parameters: "start {#ServiceName}"; Description: "Start Satellite Service"; Flags: postinstall runhidden runascurrentuser + +[Registry] +Root: HKLM; Subkey: "SOFTWARE\HASSAgent\SatelliteService"; ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"; Flags: createvalueifdoesntexist uninsdeletevalue + +; Service registration and removal +[Run] +Filename: "{sys}\sc.exe"; Parameters: "create {#ServiceName} binpath= ""{app}\{#MyAppExeName}"""; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "failure {#ServiceName} reset= 86400 actions= restart/60000/restart/60000//1000"; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "description {#ServiceName} ""{#ServiceDescription}"""; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} DisplayName= ""{#ServiceDisplayName}"""; Flags: runhidden +Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} start= auto"; Flags: runhidden +[UninstallRun] +Filename: "{sys}\sc.exe"; Parameters: "stop {#ServiceName}"; RunOnceId: StopService; Flags: runhidden +Filename: "{sys}\timeout.exe"; Parameters: "5"; RunOnceId: Delay1; Flags:runhidden +Filename: "{sys}\sc.exe"; Parameters: "delete {#ServiceName}" ; RunOnceId: DeleteService; Flags: runhidden +Filename: "{sys}\timeout.exe"; Parameters: "5"; RunOnceId: Delay2; Flags:runhidden + +[Code] +function InitializeSetup: Boolean; +begin + Dependency_ForceX86 := False; + Dependency_AddDotNet60Desktop; + Result := True; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + ProgressPage: TOutputProgressWizardPage; + I, Step, Wait, ResultCode: Integer; +begin + if CurStep = ssInstall then + begin + Exec(ExpandConstant('{sys}') + '\sc.exe', 'stop ' + ExpandConstant('{#ServiceName}'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) + + //thanks to https://stackoverflow.com/a/39827761 + Wait := 5000; + Step := 100; + ProgressPage := + CreateOutputProgressPage( + WizardForm.PageNameLabel.Caption, + WizardForm.PageDescriptionLabel.Caption); + ProgressPage.SetText('Making sure the satellite service is stopped...', ''); + ProgressPage.SetProgress(0, Wait); + ProgressPage.Show; + try + for I := 0 to Wait div Step do + begin + ProgressPage.SetProgress(I * Step, Wait); + Sleep(Step); + end; + finally + ProgressPage.Hide; + ProgressPage.Free; + end; + end; +end; \ No newline at end of file diff --git a/src/HASS.Agent.Installer/InstallerScript.iss b/src/HASS.Agent.Installer/InstallerScript.iss index 6b2aa517..52f1b638 100644 --- a/src/HASS.Agent.Installer/InstallerScript.iss +++ b/src/HASS.Agent.Installer/InstallerScript.iss @@ -9,19 +9,15 @@ ; Standard installation constants #define MyAppName "HASS.Agent" -#define MyAppVersion "2.0.1" +#define MyAppVersion "2.1.0-beta3" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.exe" -#define MyAppServiceExeName "HASS.Agent.Satellite.Service.exe" -#define ServiceName "hass.agent.svc" -#define ServiceDisplayName "HASS.Agent - Satellite Service" -#define ServiceDescription "Satellite service for HASS.Agent: a Windows based Home Assistant client. This service processes commands and sensors without the requirement of a logged-in user." [Setup] ArchitecturesInstallIn64BitMode=x64 SetupMutex=Global\HASS.Agent.Setup.Mutex,HASS.Agent.Setup.Mutex -AppMutex=Global\HASS.Agent.App.Mutex,HASS.Agent.App.Mutex +AppMutex=HASS.Agent.App.Mutex AppId={{7BBED458-609B-4D13-AD9E-4FF219DF8644} AppName={#MyAppName} AppVersion={#MyAppVersion} @@ -36,7 +32,7 @@ LicenseFile=..\..\LICENSE.md InfoBeforeFile=.\BeforeInstallNotice.rtf InfoAfterFile=.\AfterInstallNotice.rtf ; Uncomment the following line to run in non administrative install mode (install for current user only.) -;PrivilegesRequired=lowest +PrivilegesRequired=lowest OutputDir=.\bin OutputBaseFilename=HASS.Agent.Installer SetupIconFile=..\HASS.Agent\HASS.Agent.Shared\hassagent.ico @@ -57,38 +53,19 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{ ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Files] ; Client files -;Source: "..\HASS.Agent\HASS.Agent\bin\Publish-x64\Release\*"; Excludes: "*.pdb"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -; Service files -;Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; Excludes: "*.pdb"; DestDir: "{commonpf64}\{#MyAppName}\Service"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\HASS.Agent\HASS.Agent\bin\Publish-x64\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -; Service files -Source: "..\HASS.Agent\HASS.Agent.Satellite.Service\bin\Publish-x64\Release\*"; DestDir: "{commonpf64}\{#MyAppName}\Service"; Flags: ignoreversion recursesubdirs createallsubdirs +; Service installer +Source: ".\bin\HASS.Agent.Service.Installer.exe"; DestDir: "{tmp}"; Flags: ignoreversion [Icons] Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon [Run] -Filename: "{app}\{#MyAppExeName}"; Parameters: "compat_migrate"; Description: "Try to migrate configuration"; Flags: postinstall skipifsilent runascurrentuser unchecked -Filename: "{sys}\sc.exe"; Parameters: "start {#ServiceName}"; Description: "Start Satellite Service"; Flags: postinstall runhidden runascurrentuser +Filename: "{app}\{#MyAppExeName}"; Parameters: "compat_migrate"; Description: "Try to migrate configuration - use only once (administrative permissions required)"; Flags: postinstall skipifsilent runascurrentuser unchecked +Filename: "{tmp}\HASS.Agent.Service.Installer.exe"; Description: "Install Satellite Service (administrative permissions required)"; Flags: postinstall runascurrentuser Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: postinstall skipifsilent nowait -[Registry] -Root: HKLM; Subkey: "SOFTWARE\HASSAgent\SatelliteService"; ValueType: string; ValueName: "InstallPath"; ValueData: "{commonpf64}\{#MyAppName}\Service"; Flags: createvalueifdoesntexist uninsdeletevalue - -; Service registration and removal -[Run] -Filename: "{sys}\sc.exe"; Parameters: "create {#ServiceName} binpath= ""{commonpf64}\{#MyAppName}\Service\{#MyAppServiceExeName}"""; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "failure {#ServiceName} reset= 86400 actions= restart/60000/restart/60000//1000"; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "description {#ServiceName} ""{#ServiceDescription}"""; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} DisplayName= ""{#ServiceDisplayName}"""; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "config {#ServiceName} start= auto"; Flags: runhidden -[UninstallRun] -Filename: "{sys}\sc.exe"; Parameters: "stop {#ServiceName}"; RunOnceId: StopService; Flags: runhidden -Filename: "{sys}\sc.exe"; Parameters: "delete {#ServiceName}" ; RunOnceId: DeleteService; Flags: runhidden -; Additional delay for the service to be uninstalled -Filename: "{sys}\timeout.exe"; Parameters: "5"; Flags:runhidden - [Code] function InitializeSetup: Boolean; begin @@ -118,3 +95,20 @@ begin Result := True; end end; + +procedure CurUninstallStepChanged (CurUninstallStep: TUninstallStep); +var + mres : integer; + serviceUninstallerPath : String; + ResultCode : integer; +begin + case CurUninstallStep of + usPostUninstall: + begin + mres := MsgBox('Do you want to uninstall the Satellite Service? (administrative permissions required)', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) + if mres = IDYES then + RegQueryStringValue(HKLM, 'SOFTWARE\HASSAgent\SatelliteService', 'InstallPath', serviceUninstallerPath); + Exec(serviceUninstallerPath + '\unins000.exe', '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode); + end; + end; +end; diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Commands/CommandsManager.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Commands/CommandsManager.cs index d65f8dba..872371fd 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Commands/CommandsManager.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Commands/CommandsManager.cs @@ -15,6 +15,8 @@ internal static class CommandsManager private static bool _active = true; private static bool _pause; + private static bool _discoveryPublished = false; + private static DateTime _lastAutoDiscoPublish = DateTime.MinValue; /// @@ -122,20 +124,22 @@ private static async void Process() // do we have commands? if (!CommandsPresent()) continue; - // publish availability & sensor autodisco's every 30 sec + // publish availability & autodiscovery every 30 sec if ((DateTime.Now - _lastAutoDiscoPublish).TotalSeconds > 30) { - // let hass know we're still here await Variables.MqttManager.AnnounceAvailabilityAsync(); - // publish command autodisco's - foreach (var command in Variables.Commands.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (!_discoveryPublished) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await command.PublishAutoDiscoveryConfigAsync(); + foreach (var command in Variables.Commands.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await command.PublishAutoDiscoveryConfigAsync(); + } + + _discoveryPublished = true; } - // are we subscribed? if (!_subscribed) { foreach (var command in Variables.Commands.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) @@ -146,7 +150,6 @@ private static async void Process() _subscribed = true; } - // log moment _lastAutoDiscoPublish = DateTime.Now; } diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs index 9275740a..a57b6c07 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Extensions/RpcExtensions.cs @@ -108,7 +108,11 @@ public static ConfiguredSensor ConvertToConfiguredSensor(this RpcConfiguredServe Counter = rpcConfiguredSensor.Counter, Instance = rpcConfiguredSensor.Instance, EntityName = rpcConfiguredSensor.EntityName, - Name = rpcConfiguredSensor.Name + Name = rpcConfiguredSensor.Name, + AdvancedSettings = rpcConfiguredSensor.AdvancedSettings, + IgnoreAvailability = rpcConfiguredSensor.IgnoreAvailability, + ApplyRounding = rpcConfiguredSensor.ApplyRounding, + Round = rpcConfiguredSensor.RoundValue, }; return configuredSensor; @@ -149,7 +153,11 @@ public static RpcConfiguredServerSensor ConvertToRpcConfiguredSensor(this Config Counter = configuredSensor.Counter ?? string.Empty, Instance = configuredSensor.Instance ?? string.Empty, Name = configuredSensor.Name ?? string.Empty, - EntityName = configuredSensor?.EntityName ?? string.Empty + EntityName = configuredSensor?.EntityName ?? string.Empty, + AdvancedSettings = configuredSensor?.AdvancedSettings ?? string.Empty, + IgnoreAvailability = configuredSensor?.IgnoreAvailability ?? false, + ApplyRounding = configuredSensor?.ApplyRounding ?? false, + RoundValue = configuredSensor?.Round ?? 0, }; return configuredRpcSensor; diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj index 5fbcd2b0..007702cc 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj @@ -8,7 +8,7 @@ dotnet-HASSAgentSatelliteService-6E4FA50A-3AC9-4E66-8671-9FAB92372154 x64 x64;x86 - 2.0.1 + 2.1.0-beta3 HASS.Agent Team HASS.Agent Satellite Service HASS.Agent.Satellite.Service @@ -17,9 +17,9 @@ https://github.com/hass-agent/HASS.Agent https://github.com/hass-agent/HASS.Agent hass.png - 2.0.1 + 2.1.0 hass.ico - 2.0.1 + 2.1.0 10.0.17763.0 false @@ -29,22 +29,22 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + - + diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto b/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto index 169b6263..a21c9157 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/RPC/Protos/hassagentsatellite.proto @@ -134,6 +134,10 @@ message RpcConfiguredServerSensor { string instance = 9; string name = 10; string entityName = 11; + string advancedSettings = 12; + bool applyRounding = 13; + int32 roundValue = 14; + bool ignoreAvailability = 15; } message RpcConfiguredServerCommand { diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Sensors/SensorsManager.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Sensors/SensorsManager.cs index 8a7e3623..10122601 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Sensors/SensorsManager.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Sensors/SensorsManager.cs @@ -14,6 +14,8 @@ internal static class SensorsManager private static bool _active = true; private static bool _pause; + private static bool _discoveryPublished = false; + private static DateTime _lastAutoDiscoPublish = DateTime.MinValue; /// @@ -116,32 +118,34 @@ private static async void Process() // optionally flag as the first real run if (!firstRunDone) firstRunDone = true; - // publish availability & sensor autodisco's every 30 sec + // publish availability & autodiscovery every 30 sec if ((DateTime.Now - _lastAutoDiscoPublish).TotalSeconds > 30) { - // let hass know we're still here await Variables.MqttManager.AnnounceAvailabilityAsync(); - // publish the autodisco's - if (SingleValueSensorsPresent()) + if (!_discoveryPublished) { - foreach (var sensor in Variables.SingleValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (SingleValueSensorsPresent()) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await sensor.PublishAutoDiscoveryConfigAsync(); + foreach (var sensor in Variables.SingleValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await sensor.PublishAutoDiscoveryConfigAsync(); + } } - } - if (MultiValueSensorsPresent()) - { - foreach (var sensor in Variables.MultiValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (MultiValueSensorsPresent()) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await sensor.PublishAutoDiscoveryConfigAsync(); + foreach (var sensor in Variables.MultiValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await sensor.PublishAutoDiscoveryConfigAsync(); + } } + + _discoveryPublished = true; } - // log moment _lastAutoDiscoPublish = DateTime.Now; } diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs index e2f2765d..6ff98745 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs @@ -90,88 +90,91 @@ await Task.Run(delegate switch (sensor.Type) { case SensorType.UserNotificationStateSensor: - abstractSensor = new UserNotificationStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new UserNotificationStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.DummySensor: - abstractSensor = new DummySensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new DummySensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.CurrentClockSpeedSensor: - abstractSensor = new CurrentClockSpeedSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CurrentClockSpeedSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.CpuLoadSensor: - abstractSensor = new CpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.MemoryUsageSensor: - abstractSensor = new MemoryUsageSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MemoryUsageSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.ActiveWindowSensor: - abstractSensor = new ActiveWindowSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ActiveWindowSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.NamedWindowSensor: - abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString()); + abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastActiveSensor: - abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastSystemStateChangeSensor: - abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastBootSensor: - abstractSensor = new LastBootSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastBootSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WebcamActiveSensor: - abstractSensor = new WebcamActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WebcamActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MicrophoneActiveSensor: - abstractSensor = new MicrophoneActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MicrophoneActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.SessionStateSensor: - abstractSensor = new SessionStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new SessionStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.CurrentVolumeSensor: - abstractSensor = new CurrentVolumeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CurrentVolumeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GpuLoadSensor: - abstractSensor = new GpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GpuTemperatureSensor: - abstractSensor = new GpuTemperatureSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GpuTemperatureSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WmiQuerySensor: - abstractSensor = new WmiQuerySensor(sensor.Query, sensor.Scope, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WmiQuerySensor(sensor.Query, sensor.Scope, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.PerformanceCounterSensor: - abstractSensor = new PerformanceCounterSensor(sensor.Category, sensor.Counter, sensor.Instance, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new PerformanceCounterSensor(sensor.Category, sensor.Counter, sensor.Instance, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ProcessActiveSensor: - abstractSensor = new ProcessActiveSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ProcessActiveSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ServiceStateSensor: - abstractSensor = new ServiceStateSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ServiceStateSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LoggedUsersSensor: - abstractSensor = new LoggedUsersSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LoggedUsersSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LoggedUserSensor: - abstractSensor = new LoggedUserSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LoggedUserSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.PowershellSensor: - abstractSensor = new PowershellSensor(sensor.Query, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new PowershellSensor(sensor.Query, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WindowStateSensor: - abstractSensor = new WindowStateSensor(sensor.Query, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString()); + abstractSensor = new WindowStateSensor(sensor.Query, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MicrophoneProcessSensor: - abstractSensor = new MicrophoneProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MicrophoneProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WebcamProcessSensor: - abstractSensor = new WebcamProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WebcamProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; default: Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured single-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); break; } + if (abstractSensor != null) + abstractSensor.IgnoreAvailability = sensor.IgnoreAvailability; + return abstractSensor; } @@ -231,10 +234,12 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = wmiSensor.Name, Type = type, UpdateInterval = wmiSensor.UpdateIntervalSeconds, + IgnoreAvailability = wmiSensor.IgnoreAvailability, Scope = wmiSensor.Scope, Query = wmiSensor.Query, ApplyRounding = wmiSensor.ApplyRounding, Round = wmiSensor.Round, + AdvancedSettings = wmiSensor.AdvancedSettings }; } @@ -248,7 +253,9 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = namedWindowSensor.EntityName, Type = type, UpdateInterval = namedWindowSensor.UpdateIntervalSeconds, - WindowName = namedWindowSensor.WindowName + IgnoreAvailability = namedWindowSensor.IgnoreAvailability, + WindowName = namedWindowSensor.WindowName, + AdvancedSettings = namedWindowSensor.AdvancedSettings }; } @@ -262,11 +269,13 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = performanceCounterSensor.EntityName, Type = type, UpdateInterval = performanceCounterSensor.UpdateIntervalSeconds, + IgnoreAvailability = performanceCounterSensor.IgnoreAvailability, Category = performanceCounterSensor.CategoryName, Counter = performanceCounterSensor.CounterName, Instance = performanceCounterSensor.InstanceName, ApplyRounding = performanceCounterSensor.ApplyRounding, Round = performanceCounterSensor.Round, + AdvancedSettings = performanceCounterSensor.AdvancedSettings }; } @@ -280,7 +289,9 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = processActiveSensor.EntityName, Type = type, UpdateInterval = processActiveSensor.UpdateIntervalSeconds, - Query = processActiveSensor.ProcessName + IgnoreAvailability = processActiveSensor.IgnoreAvailability, + Query = processActiveSensor.ProcessName, + AdvancedSettings = processActiveSensor.AdvancedSettings }; } @@ -294,7 +305,9 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = serviceStateSensor.EntityName, Type = type, UpdateInterval = serviceStateSensor.UpdateIntervalSeconds, - Query = serviceStateSensor.ServiceName + IgnoreAvailability = serviceStateSensor.IgnoreAvailability, + Query = serviceStateSensor.ServiceName, + AdvancedSettings = serviceStateSensor.AdvancedSettings }; } @@ -308,9 +321,11 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = powershellSensor.EntityName, Type = type, UpdateInterval = powershellSensor.UpdateIntervalSeconds, + IgnoreAvailability = powershellSensor.IgnoreAvailability, Query = powershellSensor.Command, ApplyRounding = powershellSensor.ApplyRounding, - Round = powershellSensor.Round + Round = powershellSensor.Round, + AdvancedSettings = powershellSensor.AdvancedSettings }; } @@ -324,7 +339,9 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = windowStateSensor.EntityName, Type = type, UpdateInterval = windowStateSensor.UpdateIntervalSeconds, - Query = windowStateSensor.ProcessName + IgnoreAvailability = windowStateSensor.IgnoreAvailability, + Query = windowStateSensor.ProcessName, + AdvancedSettings = windowStateSensor.AdvancedSettings }; } @@ -337,7 +354,9 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract EntityName = sensor.EntityName, Name = sensor.EntityName, Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability, + AdvancedSettings = sensor.AdvancedSettings }; } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs index d2b6e148..a25ca5b1 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/CommandType.cs @@ -101,6 +101,14 @@ public enum CommandType [EnumMember(Value = "SetApplicationVolumeCommand")] SetApplicationVolumeCommand, + [LocalizedDescription("CommandType_SetAudioOutputCommand", typeof(Languages))] + [EnumMember(Value = "SetAudioOutputCommand")] + SetAudioOutputCommand, + + [LocalizedDescription("CommandType_SetAudioInputCommand", typeof(Languages))] + [EnumMember(Value = "SetAudioInputCommand")] + SetAudioInputCommand, + [LocalizedDescription("CommandType_ShutdownCommand", typeof(Languages))] [EnumMember(Value = "ShutdownCommand")] ShutdownCommand, @@ -113,6 +121,10 @@ public enum CommandType [EnumMember(Value = "WebViewCommand")] WebViewCommand, + [LocalizedDescription("CommandType_TrayWebViewCommand", typeof(Languages))] + [EnumMember(Value = "TrayWebViewCommand")] + TrayWebViewCommand, + [LocalizedDescription("CommandType_RadioCommand", typeof(Languages))] [EnumMember(Value = "RadioCommand")] RadioCommand diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs index ad01d554..6bead640 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassAction.cs @@ -45,6 +45,16 @@ public enum HassAction [LocalizedDescription("HassAction_Toggle", typeof(Languages))] [Category("toggle")] [EnumMember(Value = "Toggle")] - Toggle + Toggle, + + [LocalizedDescription("HassAction_Trigger", typeof(Languages))] + [Category("trigger")] + [EnumMember(Value = "Trigger")] + Trigger, + + [LocalizedDescription("HassAction_Press", typeof(Languages))] + [Category("press")] + [EnumMember(Value = "Press")] + Press } } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassDomain.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassDomain.cs index 2d153255..4b453b34 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/HassDomain.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/HassDomain.cs @@ -57,6 +57,11 @@ public enum HassDomain [LocalizedDescription("HassDomain_Switch", typeof(Languages))] [Category("switch")] [EnumMember(Value = "Switch")] - Switch + Switch, + + [LocalizedDescription("HassDomain_Button", typeof(Languages))] + [Category("button")] + [EnumMember(Value = "Button")] + Button } } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs b/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs index bf3b31eb..4e2bbb53 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Enums/SensorType.cs @@ -58,6 +58,10 @@ public enum SensorType [LocalizedDescription("SensorType_GeoLocationSensor", typeof(Languages))] [EnumMember(Value = "GeoLocationSensor")] GeoLocationSensor, + + [LocalizedDescription("SensorType_DeviceTrackerSensor", typeof(Languages))] + [EnumMember(Value = "DeviceTrackerSensor")] + DeviceTrackerSensor, [LocalizedDescription("SensorType_GpuLoadSensor", typeof(Languages))] [EnumMember(Value = "GpuLoadSensor")] @@ -165,6 +169,10 @@ public enum SensorType [LocalizedDescription("SensorType_InternalDeviceSensor", typeof(Languages))] [EnumMember(Value = "InternalDeviceSensor")] - InternalDeviceSensor + InternalDeviceSensor, + + [LocalizedDescription("SensorType_ScreenshotSensor", typeof(Languages))] + [EnumMember(Value = "ScreenshotSensor")] + ScreenshotSensor } } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Extensions/CommandExtensions.cs b/src/HASS.Agent/HASS.Agent.Shared/Extensions/CommandExtensions.cs index 0038d6d3..c7983404 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Extensions/CommandExtensions.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Extensions/CommandExtensions.cs @@ -21,18 +21,19 @@ public static string GetCommandName(this CommandType commandType) //TODO: remove after tests - /// - /// Returns the name of the commandtype, based on the provided devicename - /// - /// - /// - /// - /* public static string GetCommandName(this CommandType commandType, string deviceName) - { - var (_, name) = commandType.GetLocalizedDescriptionAndKey(); - var commandName = name.ToLower(); + /* + /// + /// Returns the name of the commandtype, based on the provided devicename + /// + /// + /// + /// + public static string GetCommandName(this CommandType commandType, string deviceName) + { + var (_, name) = commandType.GetLocalizedDescriptionAndKey(); + var commandName = name.ToLower(); - return $"{SharedHelperFunctions.GetSafeValue(deviceName)}_{commandName}"; - }*/ + return $"{SharedHelperFunctions.GetSafeValue(deviceName)}_{commandName}"; + }*/ } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/Extensions/SensorExtensions.cs b/src/HASS.Agent/HASS.Agent.Shared/Extensions/SensorExtensions.cs index ef91a026..2dd5617c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Extensions/SensorExtensions.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Extensions/SensorExtensions.cs @@ -21,13 +21,14 @@ public static string GetSensorName(this SensorType sensorType) //TODO: remove after tests + /* /// /// Returns the name of the sensortype, based on the provided devicename /// /// /// /// -/* public static string GetSensorName(this SensorType sensorType, string deviceName) + public static string GetSensorName(this SensorType sensorType, string deviceName) { var (_, name) = sensorType.GetLocalizedDescriptionAndKey(); var sensorName = name.ToLower(); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index 9d3b507f..968e8ba9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -10,9 +10,9 @@ Shared functions and models for the HASS.Agent platform. https://github.com/hass-agent/HASS.Agent https://github.com/hass-agent/HASS.Agent - 2.0.1 - 2.0.1 - 2.0.1 + 2.1.0 + 2.1.0 + 2.1.0-beta3 logo_128.png True hassagent.ico @@ -30,19 +30,21 @@ - + - - - + + - + + + + diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs index f0646eb3..cdb39cdc 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetApplicationVolumeCommand.cs @@ -1,5 +1,7 @@ -using CoreAudio; -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; +using HidSharp; using Newtonsoft.Json; using Serilog; using System; @@ -13,7 +15,6 @@ namespace HASS.Agent.Shared.HomeAssistant.Commands.InternalCommands public class SetApplicationVolumeCommand : InternalCommand { private const string DefaultName = "setappvolume"; - private static readonly Dictionary ApplicationNames = new Dictionary(); public SetApplicationVolumeCommand(string entityName = DefaultName, string name = DefaultName, string commandConfig = "", CommandEntityType entityType = CommandEntityType.Button, string id = default) : base(entityName ?? DefaultName, name ?? null, commandConfig, entityType, id) { @@ -33,30 +34,6 @@ public override void TurnOn() TurnOnWithAction(CommandConfig); } - private MMDevice GetAudioDeviceOrDefault(string playbackDeviceName) - { - var devices = Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); - var playbackDevice = devices.Where(d => d.DeviceFriendlyName == playbackDeviceName).FirstOrDefault(); - - return playbackDevice ?? Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - } - - private string GetSessionDisplayName(AudioSessionControl2 session) - { - var procId = (int)session.ProcessID; - - if (procId <= 0) - return session.DisplayName; - - if (ApplicationNames.ContainsKey(procId)) - return ApplicationNames[procId]; - - using var p = Process.GetProcessById(procId); - ApplicationNames.Add(procId, p.ProcessName); - - return p.ProcessName; - } - public override void TurnOnWithAction(string action) { State = "ON"; @@ -72,29 +49,7 @@ public override void TurnOnWithAction(string action) return; } - using var audioDevice = GetAudioDeviceOrDefault(actionData.PlaybackDevice); - using var session = audioDevice.AudioSessionManager2?.Sessions?.Where(s => - s != null && - actionData.ApplicationName == GetSessionDisplayName(s) - ).FirstOrDefault(); - - if (session == null) - { - Log.Error("[SETAPPVOLUME] Error, no session of application {app} can be found", actionData.ApplicationName); - - return; - } - - session.SimpleAudioVolume.Mute = actionData.Mute; - if (actionData.Volume == -1) - { - Log.Debug("[SETAPPVOLUME] No volume value provided, only mute has been set for {app}", actionData.ApplicationName); - - return; - } - - var volume = Math.Clamp(actionData.Volume, 0, 100) / 100.0f; - session.SimpleAudioVolume.MasterVolume = volume; + AudioManager.SetApplicationProperties(actionData.PlaybackDevice, actionData.ApplicationName, actionData.SessionId, actionData.Volume, actionData.Mute); } catch (Exception ex) { @@ -112,6 +67,7 @@ private class ApplicationVolumeAction public bool Mute { get; set; } = false; public string ApplicationName { get; set; } = string.Empty; public string PlaybackDevice { get; set; } = string.Empty; + public string SessionId { get; set; } = string.Empty; } } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs new file mode 100644 index 00000000..1b6b1a35 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioInputCommand.cs @@ -0,0 +1,56 @@ +using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; +using Newtonsoft.Json; +using Serilog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace HASS.Agent.Shared.HomeAssistant.Commands.InternalCommands; + +public class SetAudioInputCommand : InternalCommand +{ + private const string DefaultName = "setaudioinput"; + + private string InputDevice { get => CommandConfig; } + + public SetAudioInputCommand(string entityName = DefaultName, string name = DefaultName, string audioDevice = "", CommandEntityType entityType = CommandEntityType.Button, string id = default) : base(entityName ?? DefaultName, name ?? null, audioDevice, entityType, id) + { + State = "OFF"; + } + + public override void TurnOn() + { + if (string.IsNullOrWhiteSpace(InputDevice)) + { + Log.Error("[SETAUDIOIN] Error, input device name cannot be null/blank"); + + return; + } + + TurnOnWithAction(InputDevice); + } + + public override void TurnOnWithAction(string action) + { + State = "ON"; + + try + { + AudioManager.ActivateDevice(action); + } + catch (Exception ex) + { + Log.Error("[SETAUDIOIN] Error while processing action '{action}': {err}", action, ex.Message); + } + finally + { + State = "OFF"; + } + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs index 4893eea6..51769946 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetAudioOutputCommand.cs @@ -1,5 +1,6 @@ -using CoreAudio; -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Newtonsoft.Json; using Serilog; using System; @@ -35,25 +36,13 @@ public override void TurnOn() TurnOnWithAction(OutputDevice); } - private MMDevice GetAudioDeviceOrDefault(string playbackDeviceName) - { - var devices = Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); - var playbackDevice = devices.Where(d => d.DeviceFriendlyName == playbackDeviceName).FirstOrDefault(); - - return playbackDevice ?? Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - } - public override void TurnOnWithAction(string action) { State = "ON"; try { - var outputDevice = GetAudioDeviceOrDefault(action); - if (outputDevice == Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)) - return; - - outputDevice.Selected = true; + AudioManager.ActivateDevice(action); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs index 746d70c7..dccacc8e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/InternalCommands/SetVolumeCommand.cs @@ -3,9 +3,11 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using CoreAudio; +using System.Linq; using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Functions; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Serilog; namespace HASS.Agent.Shared.HomeAssistant.Commands.InternalCommands @@ -18,7 +20,7 @@ namespace HASS.Agent.Shared.HomeAssistant.Commands.InternalCommands public class SetVolumeCommand : InternalCommand { private const string DefaultName = "setvolume"; - private readonly float _volume = -1f; + private readonly int _volume = -1; public SetVolumeCommand(string entityName = DefaultName, string name = DefaultName, string volume = "", CommandEntityType entityType = CommandEntityType.Button, string id = default) : base(entityName ?? DefaultName, name ?? null, volume, entityType, id) { @@ -28,10 +30,10 @@ public SetVolumeCommand(string entityName = DefaultName, string name = DefaultNa if (!parsed) { Log.Error("[SETVOLUME] [{name}] Unable to parse configured volume level, not an int: {val}", EntityName, volume); - _volume = -1f; + _volume = -1; } - _volume = volumeInt / 100.0f; + _volume = volumeInt; } State = "OFF"; @@ -46,20 +48,11 @@ public override void TurnOn() if (_volume == -1f) { Log.Warning("[SETVOLUME] [{name}] Unable to trigger command, it's configured as action-only", EntityName); - - return; - } - // get the current default endpoint - using var audioDevice = Variables.AudioDeviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - if (audioDevice?.AudioEndpointVolume == null) - { - Log.Warning("[SETVOLUME] [{name}] Unable to trigger command, no default audio endpoint found", EntityName); - return; } - audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar = _volume; + AudioManager.SetDefaultDeviceProperties(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console, _volume, null); } catch (Exception ex) { @@ -81,20 +74,11 @@ public override void TurnOnWithAction(string action) if (!parsed) { Log.Error("[SETVOLUME] [{name}] Unable to trigger command, the provided action value can't be parsed: {val}", EntityName, action); - - return; - } - // get the current default endpoint - using var audioDevice = Variables.AudioDeviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - if (audioDevice?.AudioEndpointVolume == null) - { - Log.Warning("[SETVOLUME] [{name}] Unable to trigger action for command, no default audio endpoint found", EntityName); - return; } - audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar = volumeInt / 100.0f; ; + AudioManager.SetDefaultDeviceProperties(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console, _volume, null); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/MultipleKeysCommand.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/MultipleKeysCommand.cs index 485c80ad..3d56a286 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/MultipleKeysCommand.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Commands/MultipleKeysCommand.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Models.HomeAssistant; +using HASS.Agent.Shared.Resources.Localization; using Serilog; namespace HASS.Agent.Shared.HomeAssistant.Commands @@ -50,7 +53,7 @@ public override void TurnOff() // } - public override async void TurnOn() + public async override void TurnOn() { try { @@ -73,9 +76,63 @@ public override async void TurnOn() } } - public override void TurnOnWithAction(string action) + public async override void TurnOnWithAction(string action) { - // + var keys = ParseMultipleKeys(action); + if (keys.Count == 0) + return; + + foreach (var key in keys) + { + SendKeys.SendWait(key); + SendKeys.Flush(); + await Task.Delay(50); + } + } + + private List ParseMultipleKeys(string keyString) + { + var keys = new List(); + + try + { + if (string.IsNullOrWhiteSpace(keyString)) + return keys; + + + if (!keyString.Contains('[') || !keyString.Contains(']')) + return keys; + + // replace all escaped brackets + // todo: ugly, let regex do that + keyString = keyString.Replace(@"\[", "left_bracket"); + keyString = keyString.Replace(@"\]", "right_bracket"); + + // lets see if the brackets corresponds + var leftBrackets = keyString.Count(x => x == '['); + var rightBrackets = keyString.Count(x => x == ']'); + + if (leftBrackets != rightBrackets) + return keys; + + // ok, try parsen + var pattern = @"\[(.*?)\]"; + var matches = Regex.Matches(keyString, pattern); + keys.AddRange(from Match m in matches select m.Groups[1].ToString()); + + // restore escaped brackets + for (var i = 0; i < keys.Count; i++) + { + if (keys[i] == "left_bracket") keys[i] = "["; + if (keys[i] == "right_bracket") keys[i] = "]"; + } + } + catch (Exception ex) + { + Log.Error("[MULTIPLEKEYS] [{name}] Error parsing multiple keys: {msg}", EntityName, ex.Message); + } + + return keys; } } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs index 4739e906..4c6ecb97 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/AudioSensors.cs @@ -3,9 +3,10 @@ using System.Diagnostics; using System.Globalization; using System.Linq; -using CoreAudio; using HASS.Agent.Shared.Functions; using HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue.DataTypes; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using HASS.Agent.Shared.Models.HomeAssistant; using HASS.Agent.Shared.Models.Internal; using HidSharp; @@ -20,7 +21,6 @@ namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue; public class AudioSensors : AbstractMultiValueSensor { private const string DefaultName = "audio"; - private static readonly Dictionary ApplicationNames = new(); private bool _errorPrinted = false; private readonly int _updateInterval; @@ -42,76 +42,58 @@ private void AddUpdateSensor(string sensorId, AbstractSingleValueSensor sensor) Sensors[sensorId] = sensor; } - private List GetAudioDevices(DataFlow type) + private void HandleAudioOutputSensors(IEnumerable outputDevices, string parentSensorSafeName) { - var audioDevices = new List(); - foreach (var device in Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(type, DeviceState.Active)) - { - audioDevices.Add(device.DeviceFriendlyName); - device.Dispose(); - } - - return audioDevices; - } - - private List GetAudioOutputDevices() => GetAudioDevices(DataFlow.Render); - private List GetAudioInputDevices() => GetAudioDevices(DataFlow.Capture); - - private void HandleAudioOutputSensors(string parentSensorSafeName, string deviceName) - { - using var audioDevice = Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + var outputDevice = outputDevices.Where(d => d.Default).FirstOrDefault(); + if (outputDevice == null) + return; var defaultDeviceEntityName = $"{parentSensorSafeName}_default_device"; var defaultDeviceId = $"{Id}_default_device"; var defaultDeviceSensor = new DataTypeStringSensor(_updateInterval, defaultDeviceEntityName, $"Default Device", defaultDeviceId, string.Empty, "mdi:speaker", string.Empty, EntityName); - defaultDeviceSensor.SetState(audioDevice.DeviceFriendlyName); + defaultDeviceSensor.SetState(outputDevice.FriendlyName); AddUpdateSensor(defaultDeviceId, defaultDeviceSensor); var defaultDeviceStateEntityName = $"{parentSensorSafeName}_default_device_state"; var defaultDeviceStateId = $"{Id}_default_device_state"; var defaultDeviceStateSensor = new DataTypeStringSensor(_updateInterval, defaultDeviceStateEntityName, $"Default Device State", defaultDeviceStateId, string.Empty, "mdi:speaker", string.Empty, EntityName); - defaultDeviceStateSensor.SetState(GetReadableState(audioDevice.State)); + defaultDeviceStateSensor.SetState(outputDevice.State); AddUpdateSensor(defaultDeviceStateId, defaultDeviceStateSensor); - var masterVolume = Convert.ToInt32(Math.Round(audioDevice.AudioEndpointVolume?.MasterVolumeLevelScalar * 100 ?? 0, 0)); var defaultDeviceVolumeEntityName = $"{parentSensorSafeName}_default_device_volume"; var defaultDeviceVolumeId = $"{Id}_default_device_volume"; - var defaultDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultDeviceVolumeEntityName, $"Default Device Volume", defaultDeviceVolumeId, string.Empty, "mdi:speaker", string.Empty, EntityName); - defaultDeviceVolumeSensor.SetState(masterVolume); + var defaultDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultDeviceVolumeEntityName, $"Default Device Volume", defaultDeviceVolumeId, string.Empty, "measurement", "mdi:speaker", string.Empty, EntityName); + defaultDeviceVolumeSensor.SetState(outputDevice.Volume); AddUpdateSensor(defaultDeviceVolumeId, defaultDeviceVolumeSensor); - var defaultDeviceIsMuted = audioDevice.AudioEndpointVolume?.Mute ?? false; var defaultDeviceIsMutedEntityName = $"{parentSensorSafeName}_default_device_muted"; var defaultDeviceIsMutedId = $"{Id}_default_device_muted"; var defaultDeviceIsMutedSensor = new DataTypeBoolSensor(_updateInterval, defaultDeviceIsMutedEntityName, $"Default Device Muted", defaultDeviceIsMutedId, string.Empty, "mdi:speaker", EntityName); - defaultDeviceIsMutedSensor.SetState(defaultDeviceIsMuted); + defaultDeviceIsMutedSensor.SetState(outputDevice.Muted); AddUpdateSensor(defaultDeviceIsMutedId, defaultDeviceIsMutedSensor); - // get session and volume info - var sessionInfos = GetSessions(out var peakVolume); - var peakVolumeEntityName = $"{parentSensorSafeName}_peak_volume"; var peakVolumeId = $"{Id}_peak_volume"; - var peakVolumeSensor = new DataTypeStringSensor(_updateInterval, peakVolumeEntityName, $"Peak Volume", peakVolumeId, string.Empty, "mdi:volume-high", string.Empty, EntityName); - peakVolumeSensor.SetState(peakVolume.ToString(CultureInfo.CurrentCulture)); + var peakVolumeSensor = new DataTypeDoubleSensor(_updateInterval, peakVolumeEntityName, $"Peak Volume", peakVolumeId, string.Empty, "measurement", "mdi:volume-high", string.Empty, EntityName); + peakVolumeSensor.SetState(outputDevice.PeakVolume); AddUpdateSensor(peakVolumeId, peakVolumeSensor); var sessionsEntityName = $"{parentSensorSafeName}_sessions"; var sessionsId = $"{Id}_sessions"; - var sessionsSensor = new DataTypeIntSensor(_updateInterval, sessionsEntityName, $"Audio Sessions", sessionsId, string.Empty, "mdi:music-box-multiple-outline", string.Empty, EntityName, true); - sessionsSensor.SetState(sessionInfos.Count); + var sessionsSensor = new DataTypeIntSensor(_updateInterval, sessionsEntityName, $"Audio Sessions", sessionsId, string.Empty, "measurement", "mdi:music-box-multiple-outline", string.Empty, EntityName, true); + sessionsSensor.SetState(outputDevice.Sessions.Count); sessionsSensor.SetAttributes( JsonConvert.SerializeObject(new { - AudioSessions = sessionInfos + AudioSessions = outputDevice.Sessions }, Formatting.Indented) ); AddUpdateSensor(sessionsId, sessionsSensor); - var audioOutputDevices = GetAudioOutputDevices(); + var audioOutputDevices = outputDevices.Select(d => d.FriendlyName).ToList(); var audioOutputDevicesEntityName = $"{parentSensorSafeName}_output_devices"; var audioOutputDevicesId = $"{Id}_output_devices"; - var audioOutputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioOutputDevicesEntityName, $"Audio Output Devices", audioOutputDevicesId, string.Empty, "mdi:music-box-multiple-outline", string.Empty, EntityName, true); + var audioOutputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioOutputDevicesEntityName, $"Audio Output Devices", audioOutputDevicesId, string.Empty, "measurement", "mdi:music-box-multiple-outline", string.Empty, EntityName, true); audioOutputDevicesSensor.SetState(audioOutputDevices.Count); audioOutputDevicesSensor.SetAttributes( JsonConvert.SerializeObject(new @@ -122,40 +104,40 @@ private void HandleAudioOutputSensors(string parentSensorSafeName, string device AddUpdateSensor(audioOutputDevicesId, audioOutputDevicesSensor); } - private void HandleAudioInputSensors(string parentSensorSafeName, string deviceName) + private void HandleAudioInputSensors(IEnumerable inputDevices, string parentSensorSafeName) { - using var inputDevice = Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications); + var inputDevice = inputDevices.Where(d => d.Default).FirstOrDefault(); + if (inputDevice == null) + return; var defaultInputDeviceEntityName = $"{parentSensorSafeName}_default_input_device"; var defaultInputDeviceId = $"{Id}_default_input_device"; var defaultInputDeviceSensor = new DataTypeStringSensor(_updateInterval, defaultInputDeviceEntityName, $"Default Input Device", defaultInputDeviceId, string.Empty, "mdi:microphone", string.Empty, EntityName); - defaultInputDeviceSensor.SetState(inputDevice.DeviceFriendlyName); + defaultInputDeviceSensor.SetState(inputDevice.FriendlyName); AddUpdateSensor(defaultInputDeviceId, defaultInputDeviceSensor); var defaultInputDeviceStateEntityName = $"{parentSensorSafeName}_default_input_device_state"; var defaultInputDeviceStateId = $"{Id}_default_input_device_state"; var defaultInputDeviceStateSensor = new DataTypeStringSensor(_updateInterval, defaultInputDeviceStateEntityName, $"Default Input Device State", defaultInputDeviceStateId, string.Empty, "mdi:microphone", string.Empty, EntityName); - defaultInputDeviceStateSensor.SetState(GetReadableState(inputDevice.State)); + defaultInputDeviceStateSensor.SetState(inputDevice.State); AddUpdateSensor(defaultInputDeviceStateId, defaultInputDeviceStateSensor); - var defaultInputDeviceIsMuted = inputDevice.AudioEndpointVolume?.Mute ?? false; var defaultInputDeviceIsMutedEntityName = $"{parentSensorSafeName}_default_input_device_muted"; var defaultInputDeviceIsMutedId = $"{Id}_default_input_device_muted"; var defaultInputDeviceIsMutedSensor = new DataTypeBoolSensor(_updateInterval, defaultInputDeviceIsMutedEntityName, $"Default Input Device Muted", defaultInputDeviceIsMutedId, string.Empty, "mdi:microphone", EntityName); - defaultInputDeviceIsMutedSensor.SetState(defaultInputDeviceIsMuted); + defaultInputDeviceIsMutedSensor.SetState(inputDevice.Muted); AddUpdateSensor(defaultInputDeviceIsMutedId, defaultInputDeviceIsMutedSensor); - var inputVolume = (int)GetDefaultInputDevicePeakVolume(inputDevice); var defaultInputDeviceVolumeEntityName = $"{parentSensorSafeName}_default_input_device_volume"; var defaultInputDeviceVolumeId = $"{Id}_default_input_device_volume"; - var defaultInputDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultInputDeviceVolumeEntityName, $"Default Input Device Volume", defaultInputDeviceVolumeId, string.Empty, "mdi:microphone", string.Empty, EntityName); - defaultInputDeviceVolumeSensor.SetState(inputVolume); + var defaultInputDeviceVolumeSensor = new DataTypeIntSensor(_updateInterval, defaultInputDeviceVolumeEntityName, $"Default Input Device Volume", defaultInputDeviceVolumeId, string.Empty, "measurement", "mdi:microphone", string.Empty, EntityName); + defaultInputDeviceVolumeSensor.SetState(Convert.ToInt32(inputDevice.PeakVolume)); AddUpdateSensor(defaultInputDeviceVolumeId, defaultInputDeviceVolumeSensor); - var audioInputDevices = GetAudioInputDevices(); + var audioInputDevices = inputDevices.Select(d => d.FriendlyName).ToList(); var audioInputDevicesEntityName = $"{parentSensorSafeName}_input_devices"; var audioInputDevicesId = $"{Id}_input_devices"; - var audioInputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioInputDevicesEntityName, $"Audio Input Devices", audioInputDevicesId, string.Empty, "mdi:microphone", string.Empty, EntityName, true); + var audioInputDevicesSensor = new DataTypeIntSensor(_updateInterval, audioInputDevicesEntityName, $"Audio Input Devices", audioInputDevicesId, string.Empty, "measurement", "mdi:microphone", string.Empty, EntityName, true); audioInputDevicesSensor.SetState(audioInputDevices.Count); audioInputDevicesSensor.SetAttributes( JsonConvert.SerializeObject(new @@ -170,11 +152,14 @@ public override sealed void UpdateSensorValues() { try { + var audioDevices = AudioManager.GetDevices(); + var outputDevices = audioDevices.Where(d => d.Type == DeviceType.Output); + var inputDevices = audioDevices.Where(d => d.Type == DeviceType.Input); + var parentSensorSafeName = SharedHelperFunctions.GetSafeValue(EntityName); - var deviceSafeName = SharedHelperFunctions.GetSafeDeviceName(); - HandleAudioOutputSensors(parentSensorSafeName, deviceSafeName); - HandleAudioInputSensors(parentSensorSafeName, deviceSafeName); + HandleAudioOutputSensors(outputDevices, parentSensorSafeName); + HandleAudioInputSensors(inputDevices, parentSensorSafeName); if (_errorPrinted) _errorPrinted = false; @@ -190,191 +175,5 @@ public override sealed void UpdateSensorValues() } } - private string GetSessionDisplayName(AudioSessionControl2 session) - { - var procId = (int)session.ProcessID; - - if (procId <= 0) - return session.DisplayName; - - if (ApplicationNames.ContainsKey(procId)) - return ApplicationNames[procId]; - - // we don't know this app yet, get process info - using var p = Process.GetProcessById(procId); - ApplicationNames.Add(procId, p.ProcessName); - - return p.ProcessName; - } - - private List GetSessions(out float peakVolume) - { - var sessionInfos = new List(); - peakVolume = 0f; - - try - { - var errors = false; - - foreach (var device in Variables.AudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)) - { - using (device) - { - foreach (var session in device.AudioSessionManager2?.Sessions.Where(x => x != null)) - { - if (session.ProcessID == 0) - continue; - - try - { - var displayName = GetSessionDisplayName(session); - - if (displayName == "audiodg") - continue; - - if (displayName.Length > 30) - displayName = $"{displayName[..30]}.."; - - var sessionInfo = new AudioSessionInfo - { - Application = displayName, - PlaybackDevice = device.DeviceFriendlyName, - Muted = session.SimpleAudioVolume?.Mute ?? false, - Active = session.State == AudioSessionState.AudioSessionStateActive, - MasterVolume = session.SimpleAudioVolume?.MasterVolume * 100 ?? 0f, - PeakVolume = session.AudioMeterInformation?.MasterPeakValue * 100 ?? 0f - }; - - // new max? - if (sessionInfo.PeakVolume > peakVolume) - peakVolume = sessionInfo.PeakVolume; - - sessionInfos.Add(sessionInfo); - } - catch (Exception ex) - { - if (!_errorPrinted) - Log.Fatal(ex, "[AUDIO] [{name}] [{app}] Exception while retrieving info: {err}", EntityName, session.DisplayName, ex.Message); - - errors = true; - } - finally - { - session?.Dispose(); - } - } - } - } - - // only print errors once - if (errors && !_errorPrinted) - { - _errorPrinted = true; - - return sessionInfos; - } - - // optionally reset error flag - if (_errorPrinted) - _errorPrinted = false; - } - catch (Exception ex) - { - // something went wrong, only print once - if (_errorPrinted) - return sessionInfos; - - _errorPrinted = true; - - Log.Fatal(ex, "[AUDIO] [{name}] Fatal exception while getting sessions: {err}", EntityName, ex.Message); - } - - return sessionInfos; - } - - private float GetDefaultInputDevicePeakVolume(MMDevice inputDevice) - { - if (inputDevice == null) - return 0f; - - var peakVolume = 0f; - - try - { - var errors = false; - - // process sessions (and get peak volume) - foreach (var session in inputDevice.AudioSessionManager2?.Sessions?.Where(x => x != null)!) - { - try - { - // filter inactive sessions - if (session.State != AudioSessionState.AudioSessionStateActive) - continue; - - // set peak volume - var sessionPeakVolume = session.AudioMeterInformation?.MasterPeakValue * 100 ?? 0f; - - // new max? - if (sessionPeakVolume > peakVolume) - peakVolume = sessionPeakVolume; - } - catch (Exception ex) - { - if (!_errorPrinted) - Log.Fatal(ex, "[AUDIO] [{name}] [{app}] Exception while retrieving input info: {err}", EntityName, session.DisplayName, ex.Message); - - errors = true; - } - finally - { - session?.Dispose(); - } - } - - // only print errors once - if (errors && !_errorPrinted) - { - _errorPrinted = true; - - return peakVolume; - } - - // optionally reset error flag - if (_errorPrinted) - _errorPrinted = false; - } - catch (Exception ex) - { - // something went wrong, only print once - if (_errorPrinted) - return peakVolume; - - _errorPrinted = true; - - Log.Fatal(ex, "[AUDIO] [{name}] Fatal exception while getting input info: {err}", EntityName, ex.Message); - } - - return peakVolume; - } - - /// - /// Converts the audio device's state to a better readable form - /// - /// - /// - private static string GetReadableState(DeviceState state) - { - return state switch - { - DeviceState.Active => "ACTIVE", - DeviceState.Disabled => "DISABLED", - DeviceState.NotPresent => "NOT PRESENT", - DeviceState.Unplugged => "UNPLUGGED", - DeviceState.MaskAll => "STATEMASK_ALL", - _ => "UNKNOWN" - }; - } - public override DiscoveryConfigModel GetAutoDiscoveryConfig() => null; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/BatterySensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/BatterySensors.cs index 23835adb..708e2dd2 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/BatterySensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/BatterySensors.cs @@ -52,14 +52,14 @@ public sealed override void UpdateSensorValues() var fullChargeLifetimeEntityName = $"{parentSensorSafeName}_full_charge_lifetime"; var fullChargeLifetimeId = $"{Id}_full_charge_lifetime"; - var fullChargeLifetimeSensor = new DataTypeIntSensor(_updateInterval, fullChargeLifetimeEntityName, "Full Charge Lifetime", fullChargeLifetimeId, string.Empty, "mdi:battery-high", string.Empty, EntityName); + var fullChargeLifetimeSensor = new DataTypeIntSensor(_updateInterval, fullChargeLifetimeEntityName, "Full Charge Lifetime", fullChargeLifetimeId, string.Empty, "measurement", "mdi:battery-high", string.Empty, EntityName); fullChargeLifetimeSensor.SetState(fullChargeLifetimeMinutes); AddUpdateSensor(fullChargeLifetimeId, fullChargeLifetimeSensor); var chargeRemainingPercentage = Convert.ToInt32(powerStatus.BatteryLifePercent * 100); var chargeRemainingPercentageEntityName = $"{parentSensorSafeName}_charge_remaining_percentage"; var chargeRemainingPercentageId = $"{Id}_charge_remaining_percentage"; - var chargeRemainingPercentageSensor = new DataTypeIntSensor(_updateInterval, chargeRemainingPercentageEntityName, "Charge Remaining Percentage", chargeRemainingPercentageId, string.Empty, "mdi:battery-high", "%", EntityName); + var chargeRemainingPercentageSensor = new DataTypeIntSensor(_updateInterval, chargeRemainingPercentageEntityName, "Charge Remaining Percentage", chargeRemainingPercentageId, string.Empty, "measurement", "mdi:battery-high", "%", EntityName); chargeRemainingPercentageSensor.SetState(chargeRemainingPercentage); AddUpdateSensor(chargeRemainingPercentageId, chargeRemainingPercentageSensor); @@ -69,7 +69,7 @@ public sealed override void UpdateSensorValues() var chargeRemainingMinutesEntityName = $"{parentSensorSafeName}_charge_remaining"; var chargeRemainingMinutesId = $"{Id}_charge_remaining"; - var chargeRemainingMinutesSensor = new DataTypeIntSensor(_updateInterval, chargeRemainingMinutesEntityName, "Charge Remaining", chargeRemainingMinutesId, string.Empty, "mdi:battery-high", string.Empty, EntityName); + var chargeRemainingMinutesSensor = new DataTypeIntSensor(_updateInterval, chargeRemainingMinutesEntityName, "Charge Remaining", chargeRemainingMinutesId, string.Empty, "measurement", "mdi:battery-high", string.Empty, EntityName); chargeRemainingMinutesSensor.SetState(chargeRemainingMinutes); AddUpdateSensor(chargeRemainingMinutesId, chargeRemainingMinutesSensor); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeDoubleSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeDoubleSensor.cs index fd80e103..b0fbc741 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeDoubleSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeDoubleSensor.cs @@ -10,17 +10,19 @@ namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue.Data public class DataTypeDoubleSensor : AbstractSingleValueSensor { private readonly string _deviceClass; + private readonly string _stateClass; private readonly string _unitOfMeasurement; private readonly string _icon; private double _value = 0d; private string _attributes = string.Empty; - public DataTypeDoubleSensor(int? updateInterval, string entityName, string name, string id, string deviceClass, string icon, string unitOfMeasurement, string multiValueSensorName, bool useAttributes = false) : base(entityName, name, updateInterval ?? 30, id, useAttributes) + public DataTypeDoubleSensor(int? updateInterval, string entityName, string name, string id, string deviceClass, string stateClass, string icon, string unitOfMeasurement, string multiValueSensorName, bool useAttributes = false) : base(entityName, name, updateInterval ?? 30, id, useAttributes) { TopicName = multiValueSensorName; _deviceClass = deviceClass; + _stateClass = stateClass; _unitOfMeasurement = unitOfMeasurement; _icon = icon; @@ -60,9 +62,14 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() if (UseAttributes) model.Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{TopicName}/{ObjectId}/attributes"; - if (!string.IsNullOrWhiteSpace(_deviceClass)) model.Device_class = _deviceClass; - if (!string.IsNullOrWhiteSpace(_unitOfMeasurement)) model.Unit_of_measurement = _unitOfMeasurement; - if (!string.IsNullOrWhiteSpace(_icon)) model.Icon = _icon; + if (!string.IsNullOrWhiteSpace(_deviceClass)) + model.Device_class = _deviceClass; + if (!string.IsNullOrWhiteSpace(_stateClass)) + model.State_class = _stateClass; + if (!string.IsNullOrWhiteSpace(_unitOfMeasurement)) + model.Unit_of_measurement = _unitOfMeasurement; + if (!string.IsNullOrWhiteSpace(_icon)) + model.Icon = _icon; return SetAutoDiscoveryConfigModel(model); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeIntSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeIntSensor.cs index 59d08581..0a9ec4e6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeIntSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DataTypes/DataTypeIntSensor.cs @@ -9,17 +9,19 @@ namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue.Data public class DataTypeIntSensor : AbstractSingleValueSensor { private readonly string _deviceClass; + private readonly string _stateClass; private readonly string _unitOfMeasurement; private readonly string _icon; private int _value = 0; private string _attributes = string.Empty; - public DataTypeIntSensor(int? updateInterval, string entityName, string name, string id, string deviceClass, string icon, string unitOfMeasurement, string multiValueSensorName, bool useAttributes = false) : base(entityName, name, updateInterval ?? 30, id, useAttributes) + public DataTypeIntSensor(int? updateInterval, string entityName, string name, string id, string deviceClass, string stateClass, string icon, string unitOfMeasurement, string multiValueSensorName, bool useAttributes = false) : base(entityName, name, updateInterval ?? 30, id, useAttributes) { TopicName = multiValueSensorName; _deviceClass = deviceClass; + _stateClass = stateClass; _unitOfMeasurement = unitOfMeasurement; _icon = icon; @@ -59,9 +61,14 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() if (UseAttributes) model.Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{TopicName}/{ObjectId}/attributes"; - if (!string.IsNullOrWhiteSpace(_deviceClass)) model.Device_class = _deviceClass; - if (!string.IsNullOrWhiteSpace(_unitOfMeasurement)) model.Unit_of_measurement = _unitOfMeasurement; - if (!string.IsNullOrWhiteSpace(_icon)) model.Icon = _icon; + if (!string.IsNullOrWhiteSpace(_deviceClass)) + model.Device_class = _deviceClass; + if (!string.IsNullOrWhiteSpace(_stateClass)) + model.State_class = _stateClass; + if (!string.IsNullOrWhiteSpace(_unitOfMeasurement)) + model.Unit_of_measurement = _unitOfMeasurement; + if (!string.IsNullOrWhiteSpace(_icon)) + model.Icon = _icon; return SetAutoDiscoveryConfigModel(model); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DisplaySensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DisplaySensors.cs index d60c31bd..fd454cb9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DisplaySensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/DisplaySensors.cs @@ -48,7 +48,7 @@ public sealed override void UpdateSensorValues() var displayCount = displays.Length; var displayCountEntityName = $"{parentSensorSafeName}_display_count"; var displayCountId = $"{Id}_display_count"; - var displayCountSensor = new DataTypeIntSensor(_updateInterval, displayCountEntityName, "Display Count", displayCountId, string.Empty, "mdi:monitor", string.Empty, EntityName); + var displayCountSensor = new DataTypeIntSensor(_updateInterval, displayCountEntityName, "Display Count", displayCountId, string.Empty, "measurement", "mdi:monitor", string.Empty, EntityName); displayCountSensor.SetState(displayCount); AddUpdateSensor(displayCountId, displayCountSensor); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/NetworkSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/NetworkSensors.cs index fdc091e1..e43755b6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/NetworkSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/NetworkSensors.cs @@ -139,7 +139,7 @@ public override sealed void UpdateSensorValues() var nicCountEntityName = $"{parentSensorSafeName}_total_network_card_count"; var nicCountId = $"{Id}_total_network_card_count"; - var nicCountSensor = new DataTypeIntSensor(_updateInterval, nicCountEntityName, "Network Card Count", nicCountId, string.Empty, "mdi:lan", string.Empty, EntityName); + var nicCountSensor = new DataTypeIntSensor(_updateInterval, nicCountEntityName, "Network Card Count", nicCountId, string.Empty, "measurement", "mdi:lan", string.Empty, EntityName); nicCountSensor.SetState(nicCount); AddUpdateSensor(nicCountId, nicCountSensor); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/StorageSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/StorageSensors.cs index 9d4c682d..dd65447c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/StorageSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/StorageSensors.cs @@ -117,7 +117,7 @@ public override sealed void UpdateSensorValues() var driveCountEntityName = $"{parentSensorSafeName}_total_disk_count"; var driveCountId = $"{Id}_total_disk_count"; - var driveCountSensor = new DataTypeIntSensor(_updateInterval, driveCountEntityName, "Total Disk Count", driveCountId, string.Empty, "mdi:harddisk", string.Empty, EntityName); + var driveCountSensor = new DataTypeIntSensor(_updateInterval, driveCountEntityName, "Total Disk Count", driveCountId, string.Empty, "measurement", "mdi:harddisk", string.Empty, EntityName); driveCountSensor.SetState(driveCount); AddUpdateSensor(driveCountId, driveCountSensor); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs index 3a3af5cb..949d7933 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/MultiValue/WindowsUpdatesSensors.cs @@ -16,7 +16,7 @@ public class WindowsUpdatesSensors : AbstractMultiValueSensor private const string DefaultName = "windowsupdates"; private readonly int _updateInterval; - public sealed override Dictionary Sensors { get; protected set; } = new Dictionary(); + public override sealed Dictionary Sensors { get; protected set; } = new Dictionary(); public WindowsUpdatesSensors(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 900, id) { @@ -33,37 +33,44 @@ private void AddUpdateSensor(string sensorId, AbstractSingleValueSensor sensor) Sensors[sensorId] = sensor; } - public sealed override void UpdateSensorValues() + public override sealed void UpdateSensorValues() { var parentSensorSafeName = SharedHelperFunctions.GetSafeValue(EntityName); var (driverUpdates, softwareUpdates) = WindowsUpdatesManager.GetAvailableUpdates(); - var driverUpdateCount = driverUpdates.Count; - var driverUpdateCountId = $"{parentSensorSafeName}_driver_updates_pending"; - var driverUpdateCountSensor = new DataTypeIntSensor(_updateInterval, driverUpdateCountId, "Driver Updates Pending", driverUpdateCountId, string.Empty, "mdi:microsoft-windows", string.Empty, EntityName); - driverUpdateCountSensor.SetState(driverUpdateCount); + var driverUpdateCountEntityName = $"{parentSensorSafeName}_driver_updates_pending"; + var driverUpdateCountId = $"{Id}_driver_updates_pending"; + var driverUpdateCountSensor = new DataTypeIntSensor(_updateInterval, driverUpdateCountEntityName, "Driver Updates Pending", driverUpdateCountId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName); + driverUpdateCountSensor.SetState(driverUpdates.Count); AddUpdateSensor(driverUpdateCountId, driverUpdateCountSensor); - var softwareUpdateCount = softwareUpdates.Count; - var softwareUpdateCountId = $"{parentSensorSafeName}_software_updates_pending"; - var softwareUpdateCountSensor = new DataTypeIntSensor(_updateInterval, softwareUpdateCountId, "Software Updates Pending", softwareUpdateCountId, string.Empty, "mdi:microsoft-windows", string.Empty, EntityName); - softwareUpdateCountSensor.SetState(softwareUpdateCount); + var softwareUpdateCountEntityName = $"{parentSensorSafeName}_software_updates_pending"; + var softwareUpdateCountId = $"{Id}_software_updates_pending"; + var softwareUpdateCountSensor = new DataTypeIntSensor(_updateInterval, softwareUpdateCountEntityName, "Software Updates Pending", softwareUpdateCountId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName); + softwareUpdateCountSensor.SetState(softwareUpdates.Count); AddUpdateSensor(softwareUpdateCountId, softwareUpdateCountSensor); - var driverUpdatesList = new WindowsUpdateInfoCollection(driverUpdates); - var driverUpdatesStr = JsonConvert.SerializeObject(driverUpdatesList, Formatting.Indented); - var driverUpdatesId = $"{parentSensorSafeName}_driver_updates"; - var driverUpdatesSensor = new DataTypeIntSensor(_updateInterval, driverUpdatesId, "Available Driver Updates", driverUpdatesId, string.Empty, "mdi:microsoft-windows", string.Empty, EntityName, true); + var driverUpdatesEntityName = $"{parentSensorSafeName}_driver_updates"; + var driverUpdatesId = $"{Id}_driver_updates"; + var driverUpdatesSensor = new DataTypeIntSensor(_updateInterval, driverUpdatesEntityName, "Available Driver Updates", driverUpdatesId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName, true); driverUpdatesSensor.SetState(driverUpdates.Count); - driverUpdatesSensor.SetAttributes(driverUpdatesStr); + driverUpdatesSensor.SetAttributes( + JsonConvert.SerializeObject( + new WindowsUpdateInfoCollection(driverUpdates) + , Formatting.Indented) + ); AddUpdateSensor(driverUpdatesId, driverUpdatesSensor); - var softwareUpdatesStr = JsonConvert.SerializeObject(new WindowsUpdateInfoCollection(softwareUpdates), Formatting.Indented); - var softwareUpdatesId = $"{parentSensorSafeName}_software_updates"; - var softwareUpdatesSensor = new DataTypeIntSensor(_updateInterval, softwareUpdatesId, "Available Software Updates", softwareUpdatesId, string.Empty, "mdi:microsoft-windows", string.Empty, EntityName, true); + var softwareUpdatesEntityName = $"{parentSensorSafeName}_software_updates"; + var softwareUpdatesId = $"{Id}_software_updates"; + var softwareUpdatesSensor = new DataTypeIntSensor(_updateInterval, softwareUpdatesEntityName, "Available Software Updates", softwareUpdatesId, string.Empty, "measurement", "mdi:microsoft-windows", string.Empty, EntityName, true); softwareUpdatesSensor.SetState(softwareUpdates.Count); - softwareUpdatesSensor.SetAttributes(softwareUpdatesStr); + softwareUpdatesSensor.SetAttributes( + JsonConvert.SerializeObject( + new WindowsUpdateInfoCollection(softwareUpdates) + , Formatting.Indented) + ); AddUpdateSensor(softwareUpdatesId, softwareUpdatesSensor); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs index c6711c3e..46b689e3 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveWindowSensor.cs @@ -1,7 +1,9 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using HASS.Agent.Shared.Models.HomeAssistant; +using Newtonsoft.Json; namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue { @@ -12,14 +14,18 @@ public class ActiveWindowSensor : AbstractSingleValueSensor { private const string DefaultName = "activewindow"; - public ActiveWindowSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id) { } + private string _processName = string.Empty; + + public ActiveWindowSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, useAttributes: true, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { - if (Variables.MqttManager == null) return null; + if (Variables.MqttManager == null) + return null; var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); - if (deviceConfig == null) return null; + if (deviceConfig == null) + return null; return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() { @@ -29,30 +35,49 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Icon = "mdi:window-maximize", - Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" + Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability", + Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" }); } public override string GetState() { - return GetActiveWindowTitle(); + var windowHandle = GetForegroundWindow(); + + var returnValue = GetWindowThreadProcessId(windowHandle, out var processId); + if (returnValue != 0 && processId != 0) + { + using var process = Process.GetProcessById(Convert.ToInt32(processId)); + _processName = process.ProcessName ?? string.Empty; + } + + return GetWindowTitle(windowHandle); } - public override string GetAttributes() => string.Empty; + public override string GetAttributes() => JsonConvert.SerializeObject(new + { + processName = _processName + }); [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); - [DllImport("user32.dll")] - private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count); + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder builder, int count); + + [DllImport("user32.dll", SetLastError = true)] + static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); - private static string GetActiveWindowTitle() + private static string GetWindowTitle(IntPtr windowHandle) { - const int nChars = 256; - var buff = new StringBuilder(nChars); - var handle = GetForegroundWindow(); + var titleLength = GetWindowTextLength(windowHandle) + 1; + var builder = new StringBuilder(titleLength); + var windowTitle = GetWindowText(windowHandle, builder, titleLength) > 0 ? builder.ToString() : string.Empty; - return GetWindowText(handle, buff, nChars) > 0 ? buff.ToString() : string.Empty; + return windowTitle.Length > 255 ? windowTitle[..255] : windowTitle; //Note(Amadeo): to make sure we don't exceed HA limitation of 255 payload length } } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs index 7481c3e9..659db848 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/CurrentVolumeSensor.cs @@ -1,6 +1,8 @@ using System; using System.Globalization; -using CoreAudio; +using System.Linq; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using HASS.Agent.Shared.Models.HomeAssistant; namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue @@ -12,7 +14,7 @@ public class CurrentVolumeSensor : AbstractSingleValueSensor { private const string DefaultName = "currentvolume"; - public CurrentVolumeSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id) { } + public CurrentVolumeSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { @@ -30,19 +32,17 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Icon = "mdi:volume-medium", Unit_of_measurement = "%", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } public override string GetState() { - using var audioDevice = Variables.AudioDeviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - // check for null & mute - if (audioDevice?.AudioEndpointVolume == null) return "0"; - if (audioDevice.AudioEndpointVolume.Mute) return "0"; + var volume = AudioManager.GetDefaultDeviceVolume(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console); // return as percentage - return Math.Round(audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0).ToString(CultureInfo.InvariantCulture); + return volume.ToString(CultureInfo.InvariantCulture); } public override string GetAttributes() => string.Empty; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs index 767f17ca..45afb306 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/DummySensor.cs @@ -9,7 +9,7 @@ public class DummySensor : AbstractSingleValueSensor { private const string DefaultName = "dummy"; - public DummySensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 5, id) { } + public DummySensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 5, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { @@ -25,6 +25,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs index 1e075b67..e4b4e92a 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuLoadSensor.cs @@ -14,7 +14,7 @@ public class GpuLoadSensor : AbstractSingleValueSensor private const string DefaultName = "gpuload"; private readonly IHardware _gpu; - public GpuLoadSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public GpuLoadSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { _gpu = HardwareManager.Hardware.FirstOrDefault( h => h.HardwareType == HardwareType.GpuAmd || @@ -40,7 +40,8 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Unit_of_measurement = "%", - Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" + State_class = "measurement", + Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs index 574f1429..8204c3d9 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/GpuTemperatureSensor.cs @@ -14,7 +14,7 @@ public class GpuTemperatureSensor : AbstractSingleValueSensor private const string DefaultName = "gputemperature"; private readonly IHardware _gpu; - public GpuTemperatureSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public GpuTemperatureSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { _gpu = HardwareManager.Hardware.FirstOrDefault( h => h.HardwareType == HardwareType.GpuAmd || @@ -39,6 +39,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", Device_class = "temperature", Unit_of_measurement = "°C", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs index 80473337..bd67b9d6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs @@ -23,7 +23,7 @@ public class LastActiveSensor : AbstractSingleValueSensor public bool ApplyRounding { get; private set; } public int Round { get; private set; } - public LastActiveSensor(bool updateOnResume, int? updateOnResumeTimeWindow, int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public LastActiveSensor(bool updateOnResume, int? updateOnResumeTimeWindow, int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { ApplyRounding = updateOnResume; Round = updateOnResumeTimeWindow ?? 30; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastBootSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastBootSensor.cs index 2f24b0a2..c8e6429c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastBootSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastBootSensor.cs @@ -13,7 +13,7 @@ public class LastBootSensor : AbstractSingleValueSensor { private const string DefaultName = "lastboot"; - public LastBootSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public LastBootSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastSystemStateChangeSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastSystemStateChangeSensor.cs index 1e24dbf9..482c0fad 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastSystemStateChangeSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastSystemStateChangeSensor.cs @@ -10,7 +10,7 @@ public class LastSystemStateChangeSensor : AbstractSingleValueSensor { private const string DefaultName = "lastsystemstatechange"; - public LastSystemStateChangeSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public LastSystemStateChangeSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUserSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUserSensor.cs index 487377d8..ba14ba5b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUserSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUserSensor.cs @@ -10,7 +10,7 @@ public class LoggedUserSensor : AbstractSingleValueSensor { private const string DefaultName = "loggeduser"; - public LoggedUserSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public LoggedUserSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUsersSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUsersSensor.cs index d0940f9e..e10c958e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUsersSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LoggedUsersSensor.cs @@ -18,7 +18,7 @@ public class LoggedUsersSensor : AbstractSingleValueSensor { private const string DefaultName = "loggedusers"; - public LoggedUsersSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) { } + public LoggedUsersSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneActiveSensor.cs index 4198a9fe..5d630ef6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneActiveSensor.cs @@ -11,7 +11,7 @@ public class MicrophoneActiveSensor : AbstractSingleValueSensor { private const string DefaultName = "microphoneactive"; - public MicrophoneActiveSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public MicrophoneActiveSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { Domain = "binary_sensor"; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs index e2cbf944..c7b89615 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/MicrophoneProcessSensor.cs @@ -13,9 +13,11 @@ namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue; public class MicrophoneProcessSensor : AbstractSingleValueSensor { private const string DefaultName = "microphoneprocess"; + + private const string _lastUsedTimeStop = "LastUsedTimeStop"; + private const string _regKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"; - private const string LastUsedTimeStop = "LastUsedTimeStop"; - public MicrophoneProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool useAttributes = true) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, useAttributes) + public MicrophoneProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, true, advancedSettings: advancedSettings) { // } @@ -24,10 +26,6 @@ public MicrophoneProcessSensor(int? updateInterval = null, string entityName = D private string _attributes = string.Empty; - public override string GetState() => MicrophoneProcess(); - public void SetAttributes(string value) => _attributes = string.IsNullOrWhiteSpace(value) ? "{}" : value; - public override string GetAttributes() => _attributes; - public override DiscoveryConfigModel GetAutoDiscoveryConfig() { if (Variables.MqttManager == null) @@ -48,41 +46,33 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability", - Icon = "mdi:microphone" + Icon = "mdi:microphone", + Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" }; - if (UseAttributes) - { - model.Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes"; - } - return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(model); } private string MicrophoneProcess() { - const string regKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"; - _processes.Clear(); // first local machine - using (var key = Registry.LocalMachine.OpenSubKey(regKey)) + using (var key = Registry.LocalMachine.OpenSubKey(_regKey)) { CheckRegForMicrophoneInUse(key); } // then current user - using (var key = Registry.CurrentUser.OpenSubKey(regKey)) + using (var key = Registry.CurrentUser.OpenSubKey(_regKey)) { CheckRegForMicrophoneInUse(key); } // add processes as attributes - if (_processes.Count > 0) - { - _attributes = JsonConvert.SerializeObject(_processes, Formatting.Indented); - } + _attributes = _processes.Count > 0 ? JsonConvert.SerializeObject(_processes, Formatting.Indented) : "{}"; // return the count return _processes.Count.ToString(); @@ -109,13 +99,13 @@ private void CheckRegForMicrophoneInUse(RegistryKey key) foreach (var nonpackagedSubKeyName in nonpackagedkey.GetSubKeyNames()) { using var subKey = nonpackagedkey.OpenSubKey(nonpackagedSubKeyName); - if (subKey == null || !subKey.GetValueNames().Contains(LastUsedTimeStop)) + if (subKey == null || !subKey.GetValueNames().Contains(_lastUsedTimeStop)) { continue; } - var endTime = subKey.GetValue(LastUsedTimeStop) is long - ? (long)(subKey.GetValue(LastUsedTimeStop) ?? -1) + var endTime = subKey.GetValue(_lastUsedTimeStop) is long + ? (long)(subKey.GetValue(_lastUsedTimeStop) ?? -1) : -1; if (endTime <= 0) @@ -127,12 +117,12 @@ private void CheckRegForMicrophoneInUse(RegistryKey key) else { using var subKey = key.OpenSubKey(subKeyName); - if (subKey == null || !subKey.GetValueNames().Contains(LastUsedTimeStop)) + if (subKey == null || !subKey.GetValueNames().Contains(_lastUsedTimeStop)) { continue; } - var endTime = subKey.GetValue(LastUsedTimeStop) is long ? (long)(subKey.GetValue(LastUsedTimeStop) ?? -1) : -1; + var endTime = subKey.GetValue(_lastUsedTimeStop) is long ? (long)(subKey.GetValue(_lastUsedTimeStop) ?? -1) : -1; if (endTime <= 0) { _processes[SharedHelperFunctions.ParseRegWebcamMicApplicationName(subKey.Name)] = "on"; @@ -140,4 +130,7 @@ private void CheckRegForMicrophoneInUse(RegistryKey key) } } } + + public override string GetState() => MicrophoneProcess(); + public override string GetAttributes() => _attributes; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/NamedWindowSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/NamedWindowSensor.cs index 2d18cb25..b3f494c7 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/NamedWindowSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/NamedWindowSensor.cs @@ -12,7 +12,7 @@ public class NamedWindowSensor : AbstractSingleValueSensor private const string DefaultName = "namedwindow"; public string WindowName { get; protected set; } - public NamedWindowSensor(string windowName, string entityName = DefaultName, string name = DefaultName, int? updateInterval = 10, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public NamedWindowSensor(string windowName, string entityName = DefaultName, string name = DefaultName, int? updateInterval = 10, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { Domain = "binary_sensor"; WindowName = windowName; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs index 731f05a4..09d51781 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ProcessActiveSensor.cs @@ -13,7 +13,7 @@ public class ProcessActiveSensor : AbstractSingleValueSensor private const string DefaultName = "processactive"; public string ProcessName { get; protected set; } - public ProcessActiveSensor(string processName, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public ProcessActiveSensor(string processName, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { ProcessName = processName; @@ -36,6 +36,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:file-eye-outline", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability" }); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs new file mode 100644 index 00000000..fa2dd172 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ScreenshotSensor.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.DirectoryServices.ActiveDirectory; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using HASS.Agent.Shared.Models.HomeAssistant; +using Serilog; +using System.Windows.Forms; +using System.Xml.Linq; +using System.Runtime.InteropServices; +using System.Drawing.Imaging; +using System.Text.RegularExpressions; +using Windows.Graphics.Display; + +namespace HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue; +public class ScreenshotSensor : AbstractSingleValueSensor +{ + private const string DefaultName = "screenshot"; + + private DEVMODE _devMode = new() + { + dmSize = (short)Marshal.SizeOf(typeof(DEVMODE)) + }; + + public int ScreenIndex; + + public ScreenshotSensor(string screenIndex = "0", int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) + { + ScreenIndex = int.TryParse(screenIndex, out var parsedScreenIndex) ? parsedScreenIndex : 0; + Domain = "camera"; + } + + public override DiscoveryConfigModel GetAutoDiscoveryConfig() + { + if (Variables.MqttManager == null) + return null; + + var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); + if (deviceConfig == null) + return null; + + return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new CameraSensorDiscoveryConfigModel() + { + EntityName = EntityName, + Name = Name, + Unique_id = Id, + Device = deviceConfig, + Image_encoding = "b64", + Icon = "mdi:camera", + State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + Topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability" + }); + } + + public override string GetState() + { + if (ScreenIndex >= Screen.AllScreens.Length || ScreenIndex < 0) + { + Log.Warning("[SCREENSHOT] Wrong index '{index}' - returning image for screen 0", ScreenIndex); + ScreenIndex = 0; + } + + var screenImage = CaptureScreen(ScreenIndex); + + return Convert.ToBase64String(screenImage); + } + + private byte[] CaptureScreen(int screenIndex) + { + try + { + return CapturePngFile(Screen.AllScreens[screenIndex]); + } + catch (Exception ex) + { + Log.Error(ex, "[SCREENSHOT] Internal Error capturing screen {index}, {ex}", ex.Message); + + return Array.Empty(); + } + } + + private byte[] CapturePngFile(Screen screen) + { + EnumDisplaySettings(screen.DeviceName, -1, ref _devMode); + + var scalingFactor = Math.Round(decimal.Divide(_devMode.dmPelsWidth, screen.Bounds.Width), 2); + + var captureRectangle = screen.Bounds; + var captureSize = new Size( + Convert.ToInt32(screen.Bounds.Width * scalingFactor), + Convert.ToInt32(screen.Bounds.Height * scalingFactor) + ); + using var captureBitmap = new Bitmap(captureSize.Width, captureSize.Height, PixelFormat.Format32bppArgb); + using var captureGraphics = Graphics.FromImage(captureBitmap); + captureGraphics.CopyFromScreen(captureRectangle.Left, captureRectangle.Top, 0, 0, captureSize); + + using var memoryStream = new MemoryStream(); + captureBitmap.Save(memoryStream, ImageFormat.Png); + + return memoryStream.ToArray(); + } + + public override string GetAttributes() => string.Empty; + + [StructLayout(LayoutKind.Sequential)] + private struct DEVMODE + { + private const int CCHDEVICENAME = 0x20; + private const int CCHFORMNAME = 0x20; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string dmDeviceName; + public short dmSpecVersion; + public short dmDriverVersion; + public short dmSize; + public short dmDriverExtra; + public int dmFields; + public int dmPositionX; + public int dmPositionY; + public ScreenOrientation dmDisplayOrientation; + public int dmDisplayFixedOutput; + public short dmColor; + public short dmDuplex; + public short dmYResolution; + public short dmTTOption; + public short dmCollate; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string dmFormName; + public short dmLogPixels; + public int dmBitsPerPel; + public int dmPelsWidth; + public int dmPelsHeight; + public int dmDisplayFlags; + public int dmDisplayFrequency; + public int dmICMMethod; + public int dmICMIntent; + public int dmMediaType; + public int dmDitherType; + public int dmReserved1; + public int dmReserved2; + public int dmPanningWidth; + public int dmPanningHeight; + } + + [DllImport("user32.dll")] + private static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode); +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ServiceStateSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ServiceStateSensor.cs index 0b11de55..f3655606 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ServiceStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/ServiceStateSensor.cs @@ -12,7 +12,7 @@ public class ServiceStateSensor : AbstractSingleValueSensor private const string DefaultName = "servicestate"; public string ServiceName { get; protected set; } - public ServiceStateSensor(string serviceName, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public ServiceStateSensor(string serviceName, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { ServiceName = serviceName; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/SessionStateSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/SessionStateSensor.cs index 71671e8d..9f698e0e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/SessionStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/SessionStateSensor.cs @@ -10,7 +10,7 @@ public class SessionStateSensor : AbstractSingleValueSensor { private const string DefaultName = "sessionstate"; - public SessionStateSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public SessionStateSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/UserNotificationStateSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/UserNotificationStateSensor.cs index 6567af7c..ee66f883 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/UserNotificationStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/UserNotificationStateSensor.cs @@ -11,7 +11,7 @@ public class UserNotificationStateSensor : AbstractSingleValueSensor { private const string DefaultName = "notificationstate"; - public UserNotificationStateSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) { } + public UserNotificationStateSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamActiveSensor.cs index f1729cf9..372f8a1c 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamActiveSensor.cs @@ -11,7 +11,7 @@ public class WebcamActiveSensor : AbstractSingleValueSensor { private const string DefaultName = "webcamactive"; - public WebcamActiveSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public WebcamActiveSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { Domain = "binary_sensor"; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs index 6f98ca4e..8dbfbccd 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WebcamProcessSensor.cs @@ -14,7 +14,7 @@ public class WebcamProcessSensor : AbstractSingleValueSensor { private const string DefaultName = "webcamprocess"; - public WebcamProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool useAttributes = true) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, useAttributes) + public WebcamProcessSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, true, advancedSettings: advancedSettings) { // } @@ -41,6 +41,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/sensor/{deviceConfig.Name}/availability", Icon = "mdi:webcam" }; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WindowStateSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WindowStateSensor.cs index 17ba6c70..b07eccc6 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WindowStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/WindowStateSensor.cs @@ -15,7 +15,7 @@ public class WindowStateSensor : AbstractSingleValueSensor private const string DefaultName = "windowstate"; public string ProcessName { get; protected set; } - public WindowStateSensor(string processName, string entityName = DefaultName, string name = DefaultName, int? updateInterval = 10, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public WindowStateSensor(string processName, string entityName = DefaultName, string name = DefaultName, int? updateInterval = 10, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { ProcessName = processName; } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs index f7ab6dbd..245c143e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerfCounterSensors/SingleValue/CpuLoadSensor.cs @@ -9,7 +9,7 @@ public class CpuLoadSensor : PerformanceCounterSensor { private const string DefaultName = "cpuload"; - public CpuLoadSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null) : base("Processor", "% Processor Time", "_Total", applyRounding, round, updateInterval ?? 30, entityName ?? DefaultName, name ?? null, id) { } + public CpuLoadSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null, string advancedSettings = default) : base("Processor", "% Processor Time", "_Total", applyRounding, round, updateInterval ?? 30, entityName ?? DefaultName, name ?? null, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { @@ -27,6 +27,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:chart-areaspline", Unit_of_measurement = "%", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs index 3c429b8d..cde16828 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PerformanceCounterSensor.cs @@ -22,7 +22,7 @@ public class PerformanceCounterSensor : AbstractSingleValueSensor public bool ApplyRounding { get; private set; } public int? Round { get; private set; } - public PerformanceCounterSensor(string categoryName, string counterName, string instanceName, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public PerformanceCounterSensor(string categoryName, string counterName, string instanceName, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { CategoryName = categoryName; CounterName = counterName; @@ -54,6 +54,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{EntityName}/state", + State_class = "measurement", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs index a84e57ea..832f7e66 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/PowershellSensor.cs @@ -5,59 +5,63 @@ using HASS.Agent.Shared.Models.HomeAssistant; using Serilog; -namespace HASS.Agent.Shared.HomeAssistant.Sensors +namespace HASS.Agent.Shared.HomeAssistant.Sensors; + +/// +/// Sensor containing the result of the provided Powershell command or script +/// +public class PowershellSensor : AbstractSingleValueSensor { - /// - /// Sensor containing the result of the provided Powershell command or script - /// - public class PowershellSensor : AbstractSingleValueSensor + private const string DefaultName = "powershellsensor"; + + public string Command { get; private set; } + public bool ApplyRounding { get; private set; } + public int? Round { get; private set; } + + public PowershellSensor(string command, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, advancedSettings: advancedSettings) { - private const string DefaultName = "powershellsensor"; + Command = command; + ApplyRounding = applyRounding; + Round = round; + } - public string Command { get; private set; } - public bool ApplyRounding { get; private set; } - public int? Round { get; private set; } + public override DiscoveryConfigModel GetAutoDiscoveryConfig() + { + if (Variables.MqttManager == null) + return null; - public PowershellSensor(string command, bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) - { - Command = command; - ApplyRounding = applyRounding; - Round = round; - } + var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); + if (deviceConfig == null) + return null; - public override DiscoveryConfigModel GetAutoDiscoveryConfig() - { - if (Variables.MqttManager == null) return null; - - var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); - if (deviceConfig == null) return null; - - return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() - { - EntityName = EntityName, - Name = Name, - Unique_id = Id, - Device = deviceConfig, - State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{EntityName}/state", - Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" - }); - } - - public override string GetState() + return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() { - var executed = PowershellManager.ExecuteWithOutput(Command, TimeSpan.FromMinutes(5), out var output, out var errors); - if (!executed) return "error_during_execution"; + EntityName = EntityName, + Name = Name, + Unique_id = Id, + Device = deviceConfig, + State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{EntityName}/state", + Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" + }); + } - if (string.IsNullOrWhiteSpace(output) && string.IsNullOrWhiteSpace(errors)) return string.Empty; - if (string.IsNullOrWhiteSpace(output)) return errors.Trim(); + public override string GetState() + { + var executed = PowershellManager.ExecuteWithOutput(Command, TimeSpan.FromMinutes(5), out var output, out var errors); + if (!executed) + return "error_during_execution"; - // optionally apply rounding - if (ApplyRounding && Round != null && double.TryParse(output, out var dblValue)) { output = Math.Round(dblValue, (int)Round).ToString(CultureInfo.CurrentCulture); } + if (string.IsNullOrWhiteSpace(output) && string.IsNullOrWhiteSpace(errors)) + return string.Empty; + if (string.IsNullOrWhiteSpace(output)) + return errors.Trim(); - // done - return string.IsNullOrWhiteSpace(errors) ? output : $"{output} | {errors}"; - } + // optionally apply rounding + if (ApplyRounding && Round != null && double.TryParse(output, out var dblValue)) { output = Math.Round(dblValue, (int)Round).ToString(CultureInfo.CurrentCulture); } - public override string GetAttributes() => string.Empty; + // done + return string.IsNullOrWhiteSpace(errors) ? output : $"{output} | {errors}"; } + + public override string GetAttributes() => string.Empty; } \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs index 62858511..1b0b5b5b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiQuerySensor.cs @@ -2,6 +2,8 @@ using System.Globalization; using System.Management; using HASS.Agent.Shared.Models.HomeAssistant; +using HASS.Agent.Shared.Models.Internal; +using Newtonsoft.Json; namespace HASS.Agent.Shared.HomeAssistant.Sensors { @@ -20,7 +22,7 @@ public class WmiQuerySensor : AbstractSingleValueSensor protected readonly ObjectQuery ObjectQuery; protected readonly ManagementObjectSearcher Searcher; - public WmiQuerySensor(string query, string scope = "", bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id) + public WmiQuerySensor(string query, string scope = "", bool applyRounding = false, int? round = null, int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 10, id, false, advancedSettings) { Query = query; Scope = scope; @@ -31,22 +33,24 @@ public WmiQuerySensor(string query, string scope = "", bool applyRounding = fals ObjectQuery = new ObjectQuery(Query); // use either default or provided scope - var managementscope = !string.IsNullOrWhiteSpace(scope) - ? new ManagementScope(scope) + var managementscope = !string.IsNullOrWhiteSpace(scope) + ? new ManagementScope(scope) : new ManagementScope(@"\\localhost\"); // prepare searcher Searcher = new ManagementObjectSearcher(managementscope, ObjectQuery); } - + public void Dispose() => Searcher?.Dispose(); public override DiscoveryConfigModel GetAutoDiscoveryConfig() { - if (Variables.MqttManager == null) return null; + if (Variables.MqttManager == null) + return null; var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); - if (deviceConfig == null) return null; + if (deviceConfig == null) + return null; return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() { @@ -58,7 +62,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" }); } - + public override string GetState() { using var collection = Searcher.Get(); @@ -68,12 +72,13 @@ public override string GetState() { try { - if (!string.IsNullOrEmpty(retValue)) continue; + if (!string.IsNullOrEmpty(retValue)) + continue; using var managementObject = (ManagementObject)managementBaseObject; foreach (var property in managementObject.Properties) { - retValue = property.Value.ToString(); + retValue = property?.Value?.ToString() ?? string.Empty; break; } } @@ -84,7 +89,10 @@ public override string GetState() } // optionally apply rounding - if (ApplyRounding && Round != null && double.TryParse(retValue, out var dblValue)) { retValue = Math.Round(dblValue, (int)Round).ToString(CultureInfo.CurrentCulture); } + if (ApplyRounding && Round != null && double.TryParse(retValue, out var dblValue)) + { + retValue = Math.Round(dblValue, (int)Round).ToString(CultureInfo.CurrentCulture); + } // done return retValue; diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs index b431dc14..23770359 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/CurrentClockSpeedSensor.cs @@ -17,7 +17,7 @@ public class CurrentClockSpeedSensor : WmiQuerySensor private protected DateTime LastFetched = DateTime.MinValue; private protected string LastValue = string.Empty; - public CurrentClockSpeedSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null) : base(string.Empty, string.Empty, applyRounding, round, updateInterval ?? 300, entityName ?? DefaultName, name ?? null, id) + public CurrentClockSpeedSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null, string advancedSettings = default) : base(string.Empty, string.Empty, applyRounding, round, updateInterval ?? 300, entityName ?? DefaultName, name ?? null, id, advancedSettings: advancedSettings) => _managementObject = new ManagementObject("Win32_Processor.DeviceID='CPU0'"); public override DiscoveryConfigModel GetAutoDiscoveryConfig() @@ -34,6 +34,8 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", + Device_class = "frequency", Icon = "mdi:speedometer", Unit_of_measurement = "MHz", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs index fa87542b..eaf79098 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/WmiSensors/SingleValue/MemoryUsageSensor.cs @@ -11,7 +11,7 @@ public class MemoryUsageSensor : WmiQuerySensor { private const string DefaultName = "memoryusage"; - public MemoryUsageSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null) : base("SELECT FreePhysicalMemory,TotalVisibleMemorySize FROM Win32_OperatingSystem", string.Empty, applyRounding, round, updateInterval ?? 30, entityName ?? DefaultName, name ?? null, id) { } + public MemoryUsageSensor(int? updateInterval = null, string entityName = DefaultName, string name = DefaultName, string id = default, bool applyRounding = false, int? round = null, string advancedSettings = default) : base("SELECT FreePhysicalMemory,TotalVisibleMemorySize FROM Win32_OperatingSystem", string.Empty, applyRounding, round, updateInterval ?? 30, entityName ?? DefaultName, name ?? null, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { @@ -27,6 +27,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:memory", Unit_of_measurement = "%", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability" diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioDevice.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioDevice.cs new file mode 100644 index 00000000..7db9b95c --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioDevice.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio; +public class AudioDevice +{ + public string State { get; set; } = string.Empty; + public DeviceType Type { get; set; } + public string Id { get; set; } = string.Empty; + public string FriendlyName { get; set; } = string.Empty; + public int Volume { get; set; } + public double PeakVolume { get; set; } + public bool Muted { get; set; } + public List Sessions { get; set; } = new(); + public bool Default { get; set; } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs new file mode 100644 index 00000000..7f1be72b --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs @@ -0,0 +1,476 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi; +using HASS.Agent.Shared.Managers.Audio.Exceptions; +using HASS.Agent.Shared.Managers.Audio.Internal; +using Serilog; +using NAudio.CoreAudioApi.Interfaces; +using HidSharp; +using Microsoft.VisualBasic.ApplicationServices; + +namespace HASS.Agent.Shared.Managers.Audio; +public static class AudioManager +{ + private static bool _initialized = false; + + private static MMDeviceEnumerator _enumerator = null; + private static MMNotificationClient _notificationClient = null; + + private static readonly ConcurrentDictionary _devices = new(); + + private static readonly Dictionary _applicationNameCache = new(); + + private static void InitializeDevices() + { + _enumerator = new MMDeviceEnumerator(); + + foreach (var device in _enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active)) + _devices[device.ID] = device.FriendlyName; + + _notificationClient = new MMNotificationClient(); + _notificationClient.DeviceAdded += DeviceAdded; + _notificationClient.DeviceRemoved += DeviceRemoved; + _notificationClient.DeviceStateChanged += DeviceStateChanged; + _enumerator.RegisterEndpointNotificationCallback(_notificationClient); + + _initialized = true; + } + + private static void AddDevice(string deviceId) + { + if (_devices.ContainsKey(deviceId)) + return; + + using var device = _enumerator.GetDevice(deviceId); + _devices[deviceId] = device.FriendlyName; + Log.Debug($"[AUDIOMGR] added device: {_devices[deviceId]}"); + } + + private static void RemoveDevice(string deviceId) + { + _devices.Remove(deviceId, out var removedDeviceName); + if (!string.IsNullOrWhiteSpace(removedDeviceName)) + Log.Debug($"[AUDIOMGR] removed device: {removedDeviceName}"); + } + + private static void DeviceRemoved(object sender, DeviceNotificationEventArgs e) + { + try + { + RemoveDevice(e.DeviceId); + } + catch + { + Log.Error($"[AUDIOMGR] failed to remove device: {e.DeviceId}"); + } + } + private static void DeviceAdded(object sender, DeviceNotificationEventArgs e) + { + try + { + AddDevice(e.DeviceId); + } + catch + { + Log.Error($"[AUDIOMGR] failed to add device: {e.DeviceId}"); + } + } + + private static void DeviceStateChanged(object sender, DeviceStateChangedEventArgs e) + { + switch (e.DeviceState) + { + case DeviceState.Active: + AddDevice(e.DeviceId); + break; + + case DeviceState.NotPresent: + case DeviceState.Unplugged: + case DeviceState.Disabled: + RemoveDevice(e.DeviceId); + break; + + default: + break; + } + } + + private static bool CheckInitialization() + { + if (!_initialized) + { + Log.Warning("[AUDIOMGR] not yet initialized!"); + return false; + } + + return true; + } + + private static string GetSessionDisplayName(InternalAudioSession session) + { + var procId = session.ProcessId; + + if (procId <= 0) + return session.DisplayName; + + if (_applicationNameCache.TryGetValue(procId, out var cachedName)) + return cachedName; + + using var process = Process.GetProcessById(procId); + _applicationNameCache[procId] = process.ProcessName; + + return process.ProcessName; + } + + private static List GetDeviceSessions(MMDevice mmDevice) + { + using var internalAudioSessionManager = new InternalAudioSessionManager(mmDevice.AudioSessionManager); + return GetDeviceSessions(_devices[mmDevice.ID], internalAudioSessionManager); + } + + private static List GetDeviceSessions(string deviceName, InternalAudioSessionManager internalAudioSessionManager) + { + var audioSessions = new List(); + + foreach (var (sessionId, session) in internalAudioSessionManager.Sessions) + { + try + { + var displayName = string.IsNullOrWhiteSpace(session.DisplayName) ? GetSessionDisplayName(session) : session.DisplayName; + if (displayName == "audiodg") + continue; + + if (displayName.Length > 30) + displayName = $"{displayName[..30]}.."; + + var audioSession = new AudioSession + { + Id = sessionId, + Application = displayName, + PlaybackDevice = deviceName, + Muted = session.Volume.Mute, + Active = session.Control.State == AudioSessionState.AudioSessionStateActive, + MasterVolume = Convert.ToInt32(session.Volume.Volume * 100), + PeakVolume = Convert.ToDouble(session.MeterInformation.MasterPeakValue * 100) + }; + + audioSessions.Add(audioSession); + } + catch (Exception ex) + { + throw new AudioSessionException($"error retrieving session information for {deviceName}", ex); + } + } + + return audioSessions; + } + + private static string GetReadableState(DeviceState state) + { + return state switch + { + DeviceState.Active => "ACTIVE", + DeviceState.Disabled => "DISABLED", + DeviceState.NotPresent => "NOT PRESENT", + DeviceState.Unplugged => "UNPLUGGED", + DeviceState.All => "STATEMASK_ALL", + _ => "UNKNOWN" + }; + } + + public static void Initialize() + { + Log.Debug("[AUDIOMGR] initializing"); + + InitializeDevices(); + + Log.Information("[AUDIOMGR] initialized"); + } + + public static void CleanupDevices() + { + Log.Debug("[AUDIOMGR] starting cleanup"); + _initialized = false; + + _notificationClient.DeviceAdded -= DeviceAdded; + _notificationClient.DeviceRemoved -= DeviceRemoved; + _notificationClient.DeviceStateChanged -= DeviceStateChanged; + _enumerator.UnregisterEndpointNotificationCallback(_notificationClient); + + _enumerator.Dispose(); + + _devices.Clear(); + Log.Debug("[AUDIOMGR] cleanup completed"); + } + + public static List GetDevices() + { + var audioDevices = new List(); + + if (!CheckInitialization()) + return audioDevices; + + try + { + using var defaultInputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + using var defaultOutputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + + var defaultInputDeviceId = defaultInputDevice.ID; + var defaultOutputDeviceId = defaultOutputDevice.ID; + + foreach (var (deviceId, deviceName) in _devices) + { + using var device = _enumerator.GetDevice(deviceId); + + var audioSessions = GetDeviceSessions(device); + var loudestSession = audioSessions.MaxBy(s => s.PeakVolume); + + var audioDevice = new AudioDevice + { + State = GetReadableState(device.State), + Type = device.DataFlow == DataFlow.Capture ? DeviceType.Input : DeviceType.Output, + Id = device.ID, + FriendlyName = deviceName, + Volume = Convert.ToInt32(Math.Round(device.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0)), + Muted = device.AudioEndpointVolume.Mute, + PeakVolume = loudestSession == null ? 0 : loudestSession.PeakVolume, + Sessions = audioSessions, + Default = device.ID == defaultInputDeviceId || device.ID == defaultOutputDeviceId + }; + + audioDevices.Add(audioDevice); + } + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to retrieve devices: {msg}", ex.Message); + } + + return audioDevices; + } + + public static string GetDefaultDeviceId(DeviceType deviceType, DeviceRole deviceRole) + { + if (!CheckInitialization()) + return string.Empty; + + var deviceId = string.Empty; + + try + { + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; + var role = (Role)deviceRole; + + using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); + + deviceId = defaultDevice.ID; + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to retrieve default device id: {msg}", ex.Message); + } + + return deviceId; + } + + public static int GetDefaultDeviceVolume(DeviceType deviceType, DeviceRole deviceRole) + { + if (!CheckInitialization()) + return 0; + + var volume = 0; + + try + { + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; + var role = (Role)deviceRole; + + using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); + + volume = Convert.ToInt32(Math.Round(defaultDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0)); + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to retrieve default device volume: {msg}", ex.Message); + } + + return volume; + } + + public static bool GetDefaultDeviceMute(DeviceType deviceType, DeviceRole deviceRole) + { + if (!CheckInitialization()) + return false; + + var mute = false; + + try + { + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; + var role = (Role)deviceRole; + + using var defaultDevice = _enumerator.GetDefaultAudioEndpoint(dataFlow, role); + + mute = defaultDevice.AudioEndpointVolume.Mute; + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to retrieve default device mute: {msg}", ex.Message); + } + + return mute; + } + + public static void ActivateDevice(string deviceName) + { + if (!CheckInitialization()) + return; + + try + { + var deviceId = _devices.FirstOrDefault(v => v.Value == deviceName).Key; + if (string.IsNullOrWhiteSpace(deviceId)) + return; + + using var configClient = new CPolicyConfigVistaClient(); + configClient.SetDefaultDevice(deviceId); + + Log.Debug($"[AUDIOMGR] device '{deviceName}' activated"); + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to activate device '{deviceName}': {msg}", deviceName, ex.Message); + } + } + + public static void SetDeviceProperties(string deviceName, int volume, bool? mute) + { + if (!CheckInitialization()) + return; + + try + { + var deviceId = _devices.FirstOrDefault(v => v.Value == deviceName).Key; + if (string.IsNullOrWhiteSpace(deviceId)) + return; + + using var device = _enumerator.GetDevice(deviceId); + SetDeviceProperties(device, volume, mute); + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to set device properties '{deviceName}': {msg}", deviceName, ex.Message); + } + } + + private static void SetDeviceProperties(MMDevice device, int volume, bool? mute) + { + if (mute != null) + { + device.AudioEndpointVolume.Mute = (bool)mute; + Log.Debug($"[AUDIOMGR] mute for '{_devices[device.ID]}' set to '{mute}'"); + } + + if (volume != -1) + { + var volumeScalar = volume / 100f; + device.AudioEndpointVolume.MasterVolumeLevelScalar = volumeScalar; + Log.Debug($"[AUDIOMGR] volume for '{_devices[device.ID]}' set to '{volume}'"); + } + } + + public static void SetDefaultDeviceProperties(DeviceType type, DeviceRole deviceRole, int volume, bool? mute) + { + try + { + var flow = type == DeviceType.Output ? DataFlow.Render : DataFlow.Capture; + var role = (Role)deviceRole; + using var device = _enumerator.GetDefaultAudioEndpoint(flow, role); + SetDeviceProperties(device, volume, mute); + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to set default device properties: {msg}", ex.Message); + } + } + + public static void SetApplicationProperties(string deviceName, string applicationName, string sessionId, int volume, bool mute) + { + if (!CheckInitialization()) + return; + + try + { + var deviceId = _devices.FirstOrDefault(v => v.Value == deviceName).Key; + if (string.IsNullOrWhiteSpace(deviceId)) + return; + + using var device = _enumerator.GetDevice(deviceId); + using var sessionManager = new InternalAudioSessionManager(device.AudioSessionManager); + var sessions = GetDeviceSessions(deviceName, sessionManager); + + var applicationAudioSessions = sessions.Where(s => + s.Application == applicationName + ); + + if (string.IsNullOrWhiteSpace(sessionId)) + { + foreach (var session in applicationAudioSessions) + { + if (!sessionManager.Sessions.TryGetValue(session.Id, out var internalSession)) + return; + + SetSessionProperties(internalSession, volume, mute); + } + } + else + { + if (!sessionManager.Sessions.TryGetValue(sessionId, out var internalSession)) + { + Log.Debug("[AUDIOMGR] no session '{sessionId}' found for device '{device}'", sessionId, deviceName); + return; + } + + SetSessionProperties(internalSession, volume, mute); + } + } + catch (Exception ex) + { + Log.Error(ex, "[AUDIOMGR] failed to set application properties '{appName}': {msg}", applicationName, ex.Message); + } + } + + private static void SetSessionProperties(InternalAudioSession internalAudioSession, int volume, bool mute) + { + var displayName = string.IsNullOrWhiteSpace(internalAudioSession.DisplayName) ? GetSessionDisplayName(internalAudioSession) : internalAudioSession.DisplayName; + + internalAudioSession.Volume.Mute = mute; + Log.Debug("[AUDIOMGR] mute for '{sessionName}' ({sessionId}) set to '{mute}'", displayName, internalAudioSession.Control.GetSessionInstanceIdentifier, mute); + + if (volume == -1) + return; + + var volumeScalar = volume / 100f; + internalAudioSession.Volume.Volume = volumeScalar; + Log.Debug("[AUDIOMGR] volume for '{sessionName}' ({sessionId}) set to '{vol}'", displayName, internalAudioSession.Control.GetSessionInstanceIdentifier, volume); + } + + public static void Shutdown() + { + Log.Debug("[AUDIOMGR] shutting down"); + try + { + CleanupDevices(); + } + catch (Exception ex) + { + Log.Fatal(ex, "[AUDIOMGR] shutdown fatal error: {ex}", ex.Message); + } + Log.Debug("[AUDIOMGR] shutdown completed"); + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioSession.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioSession.cs new file mode 100644 index 00000000..26849437 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioSession.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio; +public class AudioSession +{ + public string Id { get; set; } + public string Application { get; set; } = string.Empty; + public string PlaybackDevice { get; set; } = string.Empty; + public bool Muted { get; set; } + public bool Active { get; set; } + public int MasterVolume { get; set; } + public double PeakVolume { get; set; } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceRole.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceRole.cs new file mode 100644 index 00000000..1c0a34c2 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceRole.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio; +public enum DeviceRole +{ + Console, + Multimedia, + Communications +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceType.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceType.cs new file mode 100644 index 00000000..3de925f6 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/DeviceType.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio; +public enum DeviceType +{ + Input, + Output +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Exceptions/AudioSessionException.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Exceptions/AudioSessionException.cs new file mode 100644 index 00000000..246e53ea --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Exceptions/AudioSessionException.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio.Exceptions; +public class AudioSessionException : Exception +{ + public AudioSessionException() + { + } + + public AudioSessionException(string message) : base(message) + { + } + + public AudioSessionException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs new file mode 100644 index 00000000..9eed7840 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/CPolicyConfigVistaClient.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +// https://github.com/morphx666/CoreAudio +// https://github.com/File-New-Project/EarTrumpet + +internal class CPolicyConfigVistaClient : IDisposable +{ + private IPolicyConfigVista policyConfigVistaClient; + + public CPolicyConfigVistaClient() + { + policyConfigVistaClient = (IPolicyConfigVista)new _CPolicyConfigVistaClient(); + } + + public void SetDefaultDevice(string deviceID) + { + + policyConfigVistaClient.SetDefaultEndpoint(deviceID, Role.Console); + policyConfigVistaClient.SetDefaultEndpoint(deviceID, Role.Multimedia); + policyConfigVistaClient.SetDefaultEndpoint(deviceID, Role.Communications); + } + + public void Dispose() + { + if (policyConfigVistaClient != null && Marshal.IsComObject(policyConfigVistaClient)) + Marshal.FinalReleaseComObject(policyConfigVistaClient); + + GC.SuppressFinalize(this); + } + + ~CPolicyConfigVistaClient() + { + Dispose(); + } +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/DeviceChangeEventArgs.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/DeviceChangeEventArgs.cs new file mode 100644 index 00000000..177cf4e3 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/DeviceChangeEventArgs.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +internal class DeviceNotificationEventArgs : EventArgs +{ + public string DeviceId { get; private set; } + + public DeviceNotificationEventArgs(string deviceId) + { + DeviceId = deviceId; + } +} + +internal class DefaultDeviceChangedEventArgs : DeviceNotificationEventArgs +{ + public DataFlow DataFlow { get; private set; } + public Role Role { get; private set; } + + public DefaultDeviceChangedEventArgs(string deviceId, DataFlow dataFlow, Role role) : base(deviceId) + { + DataFlow = dataFlow; + Role = role; + } +} + +internal class DeviceStateChangedEventArgs : DeviceNotificationEventArgs +{ + public DeviceState DeviceState { get; private set; } + + public DeviceStateChangedEventArgs(string deviceId, DeviceState deviceState) : base(deviceId) + { + DeviceState = deviceState; + } +} + +internal class DevicePropertyChangedEventArgs : DeviceNotificationEventArgs +{ + public PropertyKey PropertyKey { get; private set; } + + public DevicePropertyChangedEventArgs(string deviceId, PropertyKey propertyKey) : base(deviceId) + { + PropertyKey = propertyKey; + } +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs new file mode 100644 index 00000000..b6d91b92 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/IPolicyConfigVista.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +// https://github.com/morphx666/CoreAudio +// https://github.com/File-New-Project/EarTrumpet + +[Guid("568b9108-44bf-40b4-9006-86afe5b5a620")] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface IPolicyConfigVista +{ + [PreserveSig] + int GetMixFormat(); + + [PreserveSig] + int GetDeviceFormat(); + + [PreserveSig] + int SetDeviceFormat(); + + [PreserveSig] + int GetProcessingPeriod(); + + [PreserveSig] + int SetProcessingPeriod(); + + [PreserveSig] + int GetShareMode(); + + [PreserveSig] + int SetShareMode(); + + [PreserveSig] + int GetPropertyValue(); + + [PreserveSig] + int SetPropertyValue(); + + [PreserveSig] + int SetDefaultEndpoint([MarshalAs(UnmanagedType.LPWStr)] string wszDeviceId, Role eRole); + + [PreserveSig] + int SetEndpointVisibility(); +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs new file mode 100644 index 00000000..9e9c210d --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSession.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi; +using NAudio.CoreAudioApi.Interfaces; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; +internal class InternalAudioSession : IDisposable, IAudioSessionEventsHandler +{ + public AudioSessionControl Control { get; private set; } + public SimpleAudioVolume Volume { get; private set; } + public AudioMeterInformation MeterInformation { get; private set; } + + public string DisplayName { get; private set; } + public int ProcessId { get; private set; } + + public bool Expired { get; private set; } = false; + + public InternalAudioSession(AudioSessionControl audioSessionControl) + { + Control = audioSessionControl; + Volume = audioSessionControl.SimpleAudioVolume; + MeterInformation = audioSessionControl.AudioMeterInformation; + + DisplayName = Control.DisplayName; + ProcessId = Convert.ToInt32(Control.GetProcessID); + + Control.RegisterEventClient(this); + } + + public void Dispose() + { + Control.UnRegisterEventClient(this); + + Volume?.Dispose(); + Control?.Dispose(); + + GC.SuppressFinalize(this); + } + + public void OnVolumeChanged(float volume, bool isMuted) + { + + } + + public void OnDisplayNameChanged(string displayName) + { + + } + + public void OnIconPathChanged(string iconPath) + { + + } + + public void OnChannelVolumeChanged(uint channelCount, nint newVolumes, uint channelIndex) + { + + } + + public void OnGroupingParamChanged(ref Guid groupingId) + { + + } + + public void OnStateChanged(AudioSessionState state) + { + if (state == AudioSessionState.AudioSessionStateExpired) + Expired = true; + } + + public void OnSessionDisconnected(AudioSessionDisconnectReason disconnectReason) + { + + } + + ~InternalAudioSession() + { + Dispose(); + } + +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs new file mode 100644 index 00000000..c201b427 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/InternalAudioSessionManager.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; +internal class InternalAudioSessionManager : IDisposable +{ + public AudioSessionManager Manager { get; private set; } + public ConcurrentDictionary Sessions { get; private set; } = new(); + public InternalAudioSessionManager(AudioSessionManager sessionManager2) + { + Manager = sessionManager2; + + var sessions = Manager.Sessions; + for (var i = 0; i < sessions.Count; i++) + { + var session = sessions[i]; + var internalSession = new InternalAudioSession(session); + Sessions[internalSession.Control.GetSessionInstanceIdentifier] = internalSession; + } + + Manager.OnSessionCreated += Manager_OnSessionCreated; + } + + private void Manager_OnSessionCreated(object sender, NAudio.CoreAudioApi.Interfaces.IAudioSessionControl newSession) + { + if (newSession != null) + { + var internalSession = new InternalAudioSession(new AudioSessionControl(newSession)); + Sessions[internalSession.Control.GetSessionInstanceIdentifier] = internalSession; + } + } + + public void RemoveDisconnectedSessions() + { + var expiredSessionsId = Sessions.Values.Where(s => s.Expired).Select(s => s.Control.GetSessionInstanceIdentifier); + if (!expiredSessionsId.Any()) + return; + + foreach (var expiredSessionId in expiredSessionsId) + { + Sessions.Remove(expiredSessionId, out var expiredSession); + expiredSession?.Dispose(); + } + } + + public void Dispose() + { + if (Manager != null) + Manager.OnSessionCreated -= Manager_OnSessionCreated; + + foreach (var session in Sessions.Values) + session?.Dispose(); + + Manager?.Dispose(); + + GC.SuppressFinalize(this); + } + + ~InternalAudioSessionManager() + { + Dispose(); + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/MMNotificationClient.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/MMNotificationClient.cs new file mode 100644 index 00000000..acc3c66c --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/MMNotificationClient.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NAudio.CoreAudioApi.Interfaces; +using NAudio.CoreAudioApi; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +internal class MMNotificationClient : IMMNotificationClient +{ + public event EventHandler DeviceStateChanged; + public event EventHandler DeviceAdded; + public event EventHandler DeviceRemoved; + public event EventHandler DefaultDeviceChanged; + public event EventHandler DevicePropertyChanged; + + void IMMNotificationClient.OnDeviceStateChanged(string deviceId, DeviceState deviceState) + { + DeviceStateChanged?.Invoke(this, new DeviceStateChangedEventArgs(deviceId, deviceState)); + } + + void IMMNotificationClient.OnDeviceAdded(string deviceId) + { + DeviceAdded?.Invoke(this, new DeviceNotificationEventArgs(deviceId)); + } + + void IMMNotificationClient.OnDeviceRemoved(string deviceId) + { + DeviceRemoved?.Invoke(this, new DeviceNotificationEventArgs(deviceId)); + } + + void IMMNotificationClient.OnDefaultDeviceChanged(DataFlow dataFlow, Role role, string deviceId) + { + DefaultDeviceChanged?.Invoke(this, new DefaultDeviceChangedEventArgs(deviceId, dataFlow, role)); + } + + void IMMNotificationClient.OnPropertyValueChanged(string deviceId, PropertyKey key) + { + DevicePropertyChanged?.Invoke(this, new DevicePropertyChangedEventArgs(deviceId, key)); + } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/_CPolicyConfigVistaClient.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/_CPolicyConfigVistaClient.cs new file mode 100644 index 00000000..eea62999 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/Internal/_CPolicyConfigVistaClient.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Managers.Audio.Internal; + +// https://github.com/morphx666/CoreAudio +// https://github.com/File-New-Project/EarTrumpet + +[ComImport] +[Guid("294935CE-F637-4E7C-A41B-AB255460B862")] +internal class _CPolicyConfigVistaClient +{ +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs index 576823dc..37e13b4d 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/PowershellManager.cs @@ -3,321 +3,321 @@ using System.Globalization; using System.IO; using System.Text; -using CliWrap; + using Newtonsoft.Json; using Serilog; -namespace HASS.Agent.Shared.Managers +namespace HASS.Agent.Shared.Managers; + +/// +/// Performs Powershell-related actions +/// +public static class PowershellManager { - /// - /// Performs Powershell-related actions - /// - public static class PowershellManager - { - /// - /// Execute a Powershell command without waiting for or checking results - /// - /// - /// - public static bool ExecuteCommandHeadless(string command) => ExecuteHeadless(command, string.Empty, false); - - /// - /// Executes a Powershell script without waiting for or checking results - /// - /// - /// - /// - public static bool ExecuteScriptHeadless(string script, string parameters) => ExecuteHeadless(script, parameters, true); - - private static string GetProcessArguments(string command, string parameters, bool isScript) - { - if (isScript) - { - return string.IsNullOrWhiteSpace(parameters) - ? $"-File \"{command}\"" - : $"-File \"{command}\" \"{parameters}\""; - } - else - { - return $@"& {{{command}}}"; //NOTE: place to fix any potential future issues with "command part of the command" - } - } - - private static bool ExecuteHeadless(string command, string parameters, bool isScript) - { - var descriptor = isScript ? "script" : "command"; - - try - { - var workingDir = string.Empty; - if (isScript) - { - // try to get the script's startup path - var scriptDir = Path.GetDirectoryName(command); - workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; - } - - var psExec = GetPsExecutable(); - if (string.IsNullOrEmpty(psExec)) - return false; - - var processInfo = new ProcessStartInfo - { - WindowStyle = ProcessWindowStyle.Hidden, - CreateNoWindow = true, - FileName = psExec, - WorkingDirectory = workingDir, - Arguments = GetProcessArguments(command, parameters, isScript) - }; - - using var process = new Process(); - process.StartInfo = processInfo; - var start = process.Start(); - - if (!start) - { - Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {command}", descriptor, command); - - return false; - } - - return true; - } - catch (Exception ex) - { - Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command); - - return false; - } - } - - /// - /// Execute a Powershell command, logs the output if it fails - /// - /// - /// - /// - public static bool ExecuteCommand(string command, TimeSpan timeout) => Execute(command, string.Empty, false, timeout); - - /// - /// Executes a Powershell script, logs the output if it fails - /// - /// - /// - /// - public static bool ExecuteScript(string script, string parameters, TimeSpan timeout) => Execute(script, parameters, true, timeout); - - private static bool Execute(string command, string parameters, bool isScript, TimeSpan timeout) - { - var descriptor = isScript ? "script" : "command"; - - try - { - var workingDir = string.Empty; - if (isScript) - { - // try to get the script's startup path - var scriptDir = Path.GetDirectoryName(command); - workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; - } - - var psExec = GetPsExecutable(); - if (string.IsNullOrEmpty(psExec)) return false; - - var processInfo = new ProcessStartInfo - { - FileName = psExec, - RedirectStandardError = true, - RedirectStandardOutput = true, - UseShellExecute = false, - WorkingDirectory = workingDir, - Arguments = GetProcessArguments(command, parameters, isScript) - }; - - using var process = new Process(); - process.StartInfo = processInfo; - var start = process.Start(); - - if (!start) - { - Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {script}", descriptor, command); - - return false; - } - - process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds)); - - if (process.ExitCode == 0) - return true; - - // non-zero exitcode, process as failed - Log.Error("[POWERSHELL] The {descriptor} returned non-zero exitcode: {code}", descriptor, process.ExitCode); - - var errors = process.StandardError.ReadToEnd().Trim(); - if (!string.IsNullOrEmpty(errors)) - { - Log.Error("[POWERSHELL] Error output:\r\n{output}", errors); - } - else - { - var console = process.StandardOutput.ReadToEnd().Trim(); - if (!string.IsNullOrEmpty(console)) - Log.Error("[POWERSHELL] No error output, console output:\r\n{output}", errors); - else - Log.Error("[POWERSHELL] No error and no console output"); - } - - return false; - } - catch (Exception ex) - { - Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command); - - return false; - } - } - - private static Encoding TryParseCodePage(int codePage) - { - Encoding encoding = null; - try - { - encoding = Encoding.GetEncoding(codePage); - } - catch - { - // best effort - } - - return encoding; - } - - private static Encoding GetEncoding() - { - var encoding = TryParseCodePage(CultureInfo.InstalledUICulture.TextInfo.OEMCodePage); - if (encoding != null) - return encoding; - - encoding = TryParseCodePage(CultureInfo.CurrentCulture.TextInfo.OEMCodePage); - if (encoding != null) - return encoding; - - encoding = TryParseCodePage(CultureInfo.CurrentUICulture.TextInfo.OEMCodePage); - if (encoding != null) - return encoding; - - encoding = TryParseCodePage(CultureInfo.InvariantCulture.TextInfo.OEMCodePage); - if (encoding != null) - return encoding; - - Log.Warning("[POWERSHELL] Cannot parse system text culture to encoding, returning UTF-8 as a fallback, please report this as a GitHub issue"); - - Log.Debug("[POWERSHELL] currentInstalledUICulture {c}", JsonConvert.SerializeObject(CultureInfo.InstalledUICulture.TextInfo)); - Log.Debug("[POWERSHELL] currentCulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentCulture.TextInfo)); - Log.Debug("[POWERSHELL] currentUICulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentUICulture.TextInfo)); - Log.Debug("[POWERSHELL] invariantCulture {c}", JsonConvert.SerializeObject(CultureInfo.InvariantCulture.TextInfo)); - - return Encoding.UTF8; - } - - /// - /// Executes the command or script, and returns the standard and error output - /// - /// - /// - /// - /// - /// - internal static bool ExecuteWithOutput(string command, TimeSpan timeout, out string output, out string errors) - { - output = string.Empty; - errors = string.Empty; - - try - { - var isScript = command.ToLower().EndsWith(".ps1"); - - var workingDir = string.Empty; - if (isScript) - { - // try to get the script's startup path - var scriptDir = Path.GetDirectoryName(command); - workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; - } - - var psExec = GetPsExecutable(); - if (string.IsNullOrEmpty(psExec)) - return false; - - var encoding = GetEncoding(); - - var processInfo = new ProcessStartInfo - { - FileName = psExec, - RedirectStandardError = true, - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = workingDir, - StandardOutputEncoding = encoding, - StandardErrorEncoding = encoding, - // set the right type of arguments - Arguments = isScript - ? $@"& '{command}'" - : $@"& {{{command}}}" - }; - - using var process = new Process(); - process.StartInfo = processInfo; - - var start = process.Start(); - if (!start) - { - Log.Error("[POWERSHELL] Unable to begin executing the {type}: {cmd}", isScript ? "script" : "command", command); - - return false; - } - - var completed = process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds)); - if (!completed) - Log.Error("[POWERSHELL] Timeout executing the {type}: {cmd}", isScript ? "script" : "command", command); - - output = process.StandardOutput.ReadToEnd().Trim(); - errors = process.StandardError.ReadToEnd().Trim(); - - process.StandardOutput.Dispose(); - process.StandardError.Dispose(); - - process.Kill(); - - return completed; - } - catch (Exception ex) - { - Log.Fatal(ex, ex.Message); - - return false; - } - } - - /// - /// Attempt to locate powershell.exe - /// - /// - public static string GetPsExecutable() - { - // try regular location - var psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "WindowsPowerShell\\v1.0\\powershell.exe"); - if (File.Exists(psExec)) - return psExec; - - // try specific - psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "WindowsPowerShell\\v1.0\\powershell.exe"); - if (File.Exists(psExec)) - return psExec; - - Log.Error("[POWERSHELL] PS executable not found, make sure you have powershell installed on your system"); - return string.Empty; - } - } + /// + /// Execute a Powershell command without waiting for or checking results + /// + /// + /// + public static bool ExecuteCommandHeadless(string command) => ExecuteHeadless(command, string.Empty, false); + + /// + /// Executes a Powershell script without waiting for or checking results + /// + /// + /// + /// + public static bool ExecuteScriptHeadless(string script, string parameters) => ExecuteHeadless(script, parameters, true); + + private static string GetProcessArguments(string command, string parameters, bool isScript) + { + if (isScript) + { + return string.IsNullOrWhiteSpace(parameters) + ? $"-File \"{command}\"" + : $"-File \"{command}\" {parameters}"; + } + else + { + return $@"& {{{command}}}"; //NOTE: place to fix any potential future issues with "command part of the command" + } + } + + private static bool ExecuteHeadless(string command, string parameters, bool isScript) + { + var descriptor = isScript ? "script" : "command"; + + try + { + var workingDir = string.Empty; + if (isScript) + { + // try to get the script's startup path + var scriptDir = Path.GetDirectoryName(command); + workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; + } + + var psExec = GetPsExecutable(); + if (string.IsNullOrEmpty(psExec)) + return false; + + var processInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + FileName = psExec, + WorkingDirectory = workingDir, + Arguments = GetProcessArguments(command, parameters, isScript) + }; + + using var process = new Process(); + process.StartInfo = processInfo; + var start = process.Start(); + + if (!start) + { + Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {command}", descriptor, command); + + return false; + } + + return true; + } + catch (Exception ex) + { + Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command); + + return false; + } + } + + /// + /// Execute a Powershell command, logs the output if it fails + /// + /// + /// + /// + public static bool ExecuteCommand(string command, TimeSpan timeout) => Execute(command, string.Empty, false, timeout); + + /// + /// Executes a Powershell script, logs the output if it fails + /// + /// + /// + /// + /// + public static bool ExecuteScript(string script, string parameters, TimeSpan timeout) => Execute(script, parameters, true, timeout); + + private static bool Execute(string command, string parameters, bool isScript, TimeSpan timeout) + { + var descriptor = isScript ? "script" : "command"; + + try + { + var workingDir = string.Empty; + if (isScript) + { + // try to get the script's startup path + var scriptDir = Path.GetDirectoryName(command); + workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; + } + + var psExec = GetPsExecutable(); + if (string.IsNullOrEmpty(psExec)) return false; + + var processInfo = new ProcessStartInfo + { + FileName = psExec, + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + WorkingDirectory = workingDir, + Arguments = GetProcessArguments(command, parameters, isScript) + }; + + using var process = new Process(); + process.StartInfo = processInfo; + var start = process.Start(); + + if (!start) + { + Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {script}", descriptor, command); + + return false; + } + + process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds)); + + if (process.ExitCode == 0) + return true; + + // non-zero exitcode, process as failed + Log.Error("[POWERSHELL] The {descriptor} returned non-zero exitcode: {code}", descriptor, process.ExitCode); + + var errors = process.StandardError.ReadToEnd().Trim(); + if (!string.IsNullOrEmpty(errors)) + { + Log.Error("[POWERSHELL] Error output:\r\n{output}", errors); + } + else + { + var console = process.StandardOutput.ReadToEnd().Trim(); + if (!string.IsNullOrEmpty(console)) + Log.Error("[POWERSHELL] No error output, console output:\r\n{output}", errors); + else + Log.Error("[POWERSHELL] No error and no console output"); + } + + return false; + } + catch (Exception ex) + { + Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command); + + return false; + } + } + + private static Encoding TryParseCodePage(int codePage) + { + Encoding encoding = null; + try + { + encoding = Encoding.GetEncoding(codePage); + } + catch + { + // best effort + } + + return encoding; + } + + private static Encoding GetEncoding() + { + var encoding = TryParseCodePage(CultureInfo.InstalledUICulture.TextInfo.OEMCodePage); + if (encoding != null) + return encoding; + + encoding = TryParseCodePage(CultureInfo.CurrentCulture.TextInfo.OEMCodePage); + if (encoding != null) + return encoding; + + encoding = TryParseCodePage(CultureInfo.CurrentUICulture.TextInfo.OEMCodePage); + if (encoding != null) + return encoding; + + encoding = TryParseCodePage(CultureInfo.InvariantCulture.TextInfo.OEMCodePage); + if (encoding != null) + return encoding; + + Log.Warning("[POWERSHELL] Cannot parse system text culture to encoding, returning UTF-8 as a fallback, please report this as a GitHub issue"); + + Log.Debug("[POWERSHELL] currentInstalledUICulture {c}", JsonConvert.SerializeObject(CultureInfo.InstalledUICulture.TextInfo)); + Log.Debug("[POWERSHELL] currentCulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentCulture.TextInfo)); + Log.Debug("[POWERSHELL] currentUICulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentUICulture.TextInfo)); + Log.Debug("[POWERSHELL] invariantCulture {c}", JsonConvert.SerializeObject(CultureInfo.InvariantCulture.TextInfo)); + + return Encoding.UTF8; + } + + /// + /// Executes the command or script, and returns the standard and error output + /// + /// + /// + /// + /// + /// + internal static bool ExecuteWithOutput(string command, TimeSpan timeout, out string output, out string errors) + { + output = string.Empty; + errors = string.Empty; + + try + { + var isScript = command.ToLower().EndsWith(".ps1"); + + var workingDir = string.Empty; + if (isScript) + { + // try to get the script's startup path + var scriptDir = Path.GetDirectoryName(command); + workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty; + } + + var psExec = GetPsExecutable(); + if (string.IsNullOrEmpty(psExec)) + return false; + + var encoding = GetEncoding(); + + var processInfo = new ProcessStartInfo + { + FileName = psExec, + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = workingDir, + StandardOutputEncoding = encoding, + StandardErrorEncoding = encoding, + // set the right type of arguments + Arguments = isScript + ? $@"& '{command}'" + : $@"& {{{command}}}" + }; + + using var process = new Process(); + process.StartInfo = processInfo; + + var start = process.Start(); + if (!start) + { + Log.Error("[POWERSHELL] Unable to begin executing the {type}: {cmd}", isScript ? "script" : "command", command); + + return false; + } + + var completed = process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds)); + if (!completed) + Log.Error("[POWERSHELL] Timeout executing the {type}: {cmd}", isScript ? "script" : "command", command); + + output = process.StandardOutput.ReadToEnd().Trim(); + errors = process.StandardError.ReadToEnd().Trim(); + + process.StandardOutput.Dispose(); + process.StandardError.Dispose(); + + process.Kill(); + + return completed; + } + catch (Exception ex) + { + Log.Fatal(ex, ex.Message); + + return false; + } + } + + /// + /// Attempt to locate powershell.exe + /// + /// + public static string GetPsExecutable() + { + // try regular location + var psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "WindowsPowerShell\\v1.0\\powershell.exe"); + if (File.Exists(psExec)) + return psExec; + + // try specific + psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "WindowsPowerShell\\v1.0\\powershell.exe"); + if (File.Exists(psExec)) + return psExec; + + Log.Error("[POWERSHELL] PS executable not found, make sure you have powershell installed on your system"); + return string.Empty; + } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/Config/ConfiguredSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/Config/ConfiguredSensor.cs index 1ee66b4e..13bc204b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/Config/ConfiguredSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/Config/ConfiguredSensor.cs @@ -26,6 +26,7 @@ public class ConfiguredSensor public bool IgnoreAvailability { get; set; } = false; public bool ApplyRounding { get; set; } = false; public int? Round { get; set; } + public string AdvancedSettings { get; set; } = string.Empty; public static ConfiguredSensor FromLAB02(ConfiguredSensorLAB02 oldConfig) { diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs index 63261023..e3d3f267 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/AbstractSingleValueSensor.cs @@ -1,136 +1,170 @@ using System; using System.Threading.Tasks; +using HASS.Agent.Shared.Models.Internal; using MQTTnet; +using Newtonsoft.Json; using Serilog; -namespace HASS.Agent.Shared.Models.HomeAssistant +namespace HASS.Agent.Shared.Models.HomeAssistant; + +/// +/// Abstract singlevalue-sensor from which all singlevalue-sensors are derived +/// +public abstract class AbstractSingleValueSensor : AbstractDiscoverable { - /// - /// Abstract singlevalue-sensor from which all singlevalue-sensors are derived - /// - public abstract class AbstractSingleValueSensor : AbstractDiscoverable - { - public int UpdateIntervalSeconds { get; protected set; } - public DateTime? LastUpdated { get; protected set; } + private SensorAdvancedSettings _advancedInfo; + + public int UpdateIntervalSeconds { get; protected set; } + public DateTime? LastUpdated { get; protected set; } - public string PreviousPublishedState { get; protected set; } = string.Empty; - public string PreviousPublishedAttributes { get; protected set; } = string.Empty; + public string PreviousPublishedState { get; protected set; } = string.Empty; + public string PreviousPublishedAttributes { get; protected set; } = string.Empty; - protected AbstractSingleValueSensor(string entityName, string name, int updateIntervalSeconds = 10, string id = default, bool useAttributes = false) + public string AdvancedSettings { get; private set; } + + protected AbstractSingleValueSensor(string entityName, string name, int updateIntervalSeconds = 10, string id = default, bool useAttributes = false, string advancedSettings = default) + { + Id = id == null || id == Guid.Empty.ToString() ? Guid.NewGuid().ToString() : id; + EntityName = entityName; + Name = name; + UpdateIntervalSeconds = updateIntervalSeconds; + Domain = "sensor"; + UseAttributes = useAttributes; + + if (!string.IsNullOrWhiteSpace(advancedSettings)) { - Id = id == null || id == Guid.Empty.ToString() ? Guid.NewGuid().ToString() : id; - EntityName = entityName; - Name = name; - UpdateIntervalSeconds = updateIntervalSeconds; - Domain = "sensor"; - UseAttributes = useAttributes; + AdvancedSettings = advancedSettings; + _advancedInfo = JsonConvert.DeserializeObject(advancedSettings); } + } - protected SensorDiscoveryConfigModel AutoDiscoveryConfigModel; - protected SensorDiscoveryConfigModel SetAutoDiscoveryConfigModel(SensorDiscoveryConfigModel config) + protected SensorDiscoveryConfigModel AutoDiscoveryConfigModel; + protected SensorDiscoveryConfigModel SetAutoDiscoveryConfigModel(SensorDiscoveryConfigModel config) + { + AutoDiscoveryConfigModel = config; + + // overwrite with advanced settings + if (_advancedInfo != null) { - AutoDiscoveryConfigModel = config; - return config; + if (!string.IsNullOrWhiteSpace(_advancedInfo.DeviceClass)) + AutoDiscoveryConfigModel.Device_class = _advancedInfo.DeviceClass; + if (!string.IsNullOrWhiteSpace(_advancedInfo.UnitOfMeasurement)) + AutoDiscoveryConfigModel.Unit_of_measurement = _advancedInfo.UnitOfMeasurement; + if (!string.IsNullOrWhiteSpace(_advancedInfo.StateClass)) + AutoDiscoveryConfigModel.State_class = _advancedInfo.StateClass; } - public override void ClearAutoDiscoveryConfig() => AutoDiscoveryConfigModel = null; + return AutoDiscoveryConfigModel; + } - // nullable in preparation for possible future "nullable enablement" - public abstract string? GetState(); + public override void ClearAutoDiscoveryConfig() => AutoDiscoveryConfigModel = null; - // nullable in preparation for possible future "nullable enablement" - public abstract string? GetAttributes(); + // nullable in preparation for possible future "nullable enablement" + public abstract string? GetState(); - public void ResetChecks() - { - LastUpdated = DateTime.MinValue; + // nullable in preparation for possible future "nullable enablement" + public abstract string? GetAttributes(); - PreviousPublishedState = string.Empty; - PreviousPublishedAttributes = string.Empty; - } + public void ResetChecks() + { + LastUpdated = DateTime.MinValue; - public async Task PublishStateAsync(bool respectChecks = true) - { - try - { - if (Variables.MqttManager == null) return; + PreviousPublishedState = string.Empty; + PreviousPublishedAttributes = string.Empty; + } - // are we asked to check elapsed time? - if (respectChecks) - { - if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(UpdateIntervalSeconds) > DateTime.Now) return; - } + public async Task PublishStateAsync(bool respectChecks = true) + { + try + { + if (Variables.MqttManager == null) + return; - // get the current state/attributes - var state = GetState(); - if (state == null) + // are we asked to check elapsed time? + if (respectChecks) + { + if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(UpdateIntervalSeconds) > DateTime.Now) return; + } - var attributes = GetAttributes(); + // get the current state/attributes + var state = GetState(); + if (state == null) + return; - // are we asked to check state changes? - if (respectChecks) + var attributes = GetAttributes(); + + // are we asked to check state changes? + if (respectChecks) + { + if (PreviousPublishedState == state && PreviousPublishedAttributes == attributes) { - if (PreviousPublishedState == state && PreviousPublishedAttributes == attributes) return; + LastUpdated = DateTime.Now; + return; } + } - // fetch the autodiscovery config - var autoDiscoConfig = (SensorDiscoveryConfigModel)GetAutoDiscoveryConfig(); - if (autoDiscoConfig == null) return; - - // prepare the state message - var message = new MqttApplicationMessageBuilder() - .WithTopic(autoDiscoConfig.State_topic) - .WithPayload(state) + // fetch the autodiscovery config + var autoDiscoConfig = (SensorDiscoveryConfigModel)GetAutoDiscoveryConfig(); + if (autoDiscoConfig == null) + return; + + // prepare the state message + var message = new MqttApplicationMessageBuilder() + .WithTopic(autoDiscoConfig.State_topic) + .WithPayload(state) + .WithRetainFlag(Variables.MqttManager.UseRetainFlag()) + .Build(); + + // send it + var published = await Variables.MqttManager.PublishAsync(message); + if (!published) + return; + + // optionally prepare and send attributes + if (UseAttributes && attributes != null) + { + message = new MqttApplicationMessageBuilder() + .WithTopic(autoDiscoConfig.Json_attributes_topic) + .WithPayload(attributes) .WithRetainFlag(Variables.MqttManager.UseRetainFlag()) .Build(); - // send it - var published = await Variables.MqttManager.PublishAsync(message); + published = await Variables.MqttManager.PublishAsync(message); if (!published) - return; // failed, don't store the state - - // optionally prepare and send attributes - if (UseAttributes && attributes != null) - { - message = new MqttApplicationMessageBuilder() - .WithTopic(autoDiscoConfig.Json_attributes_topic) - .WithPayload(attributes) - .WithRetainFlag(Variables.MqttManager.UseRetainFlag()) - .Build(); - - published = await Variables.MqttManager.PublishAsync(message); - if (!published) - return; // failed, don't store the state - } - - // only store the values if the checks are respected - // otherwise, we might stay in 'unknown' state until the value changes - if (!respectChecks) return; + } - PreviousPublishedState = state; - if (attributes != null) - PreviousPublishedAttributes = attributes; + // only store the values if the checks are respected + // otherwise, we might stay in 'unknown' state until the value changes + if (!respectChecks) + return; - LastUpdated = DateTime.Now; - } - catch (Exception ex) - { - Log.Fatal("[SENSOR] [{name}] Error publishing state: {err}", EntityName, ex.Message); - } - } + PreviousPublishedState = state; + if (attributes != null) + PreviousPublishedAttributes = attributes; - public async Task PublishAutoDiscoveryConfigAsync() - { - if (Variables.MqttManager == null) return; - await Variables.MqttManager.AnnounceAutoDiscoveryConfigAsync(this, Domain); + LastUpdated = DateTime.Now; } - - public async Task UnPublishAutoDiscoveryConfigAsync() + catch (Exception ex) { - if (Variables.MqttManager == null) return; - await Variables.MqttManager.AnnounceAutoDiscoveryConfigAsync(this, Domain, true); + Log.Fatal("[SENSOR] [{name}] Error publishing state: {err}", EntityName, ex.Message); } } + + public async Task PublishAutoDiscoveryConfigAsync() + { + if (Variables.MqttManager == null) + return; + + await Variables.MqttManager.AnnounceAutoDiscoveryConfigAsync(this, Domain); + } + + public async Task UnPublishAutoDiscoveryConfigAsync() + { + if (Variables.MqttManager == null) + return; + + await Variables.MqttManager.AnnounceAutoDiscoveryConfigAsync(this, Domain, true); + } } diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs index 68c4d4a6..8d2cd9f5 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/HomeAssistant/DiscoveryConfigModel.cs @@ -52,6 +52,12 @@ public class SensorDiscoveryConfigModel : DiscoveryConfigModel /// public string Device_class { get; set; } + /// + /// (Optional) The state class of the sensor. See https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes for options. + /// + /// + public string State_class { get; set; } + /// /// (Optional) Defines the number of seconds after the sensor’s state expires, if it’s not updated. After expiry, the sensor’s state becomes unavailable. Defaults to 0 in hass. /// @@ -143,6 +149,20 @@ public string Object_id public string Value_template { get; set; } } + [SuppressMessage("ReSharper", "InconsistentNaming")] + public class CameraSensorDiscoveryConfigModel : SensorDiscoveryConfigModel + { + /// + /// Messages published to this topic need to contain full contents of an image + /// + public string Topic { get; set; } + + /// + /// (Optional) The encoding of the image payloads received. + /// + public string Image_encoding { get; set; } + } + [SuppressMessage("ReSharper", "InconsistentNaming")] public class CommandDiscoveryConfigModel : DiscoveryConfigModel { diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/AudioSessionInfo.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/AudioSessionInfo.cs deleted file mode 100644 index c884e3ed..00000000 --- a/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/AudioSessionInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; - -namespace HASS.Agent.Shared.Models.Internal -{ - public class AudioSessionInfo - { - public AudioSessionInfo() - { - // - } - - public string Application { get; set; } = string.Empty; - public string PlaybackDevice { get; set; } = string.Empty; - public bool Muted { get; set; } - public bool Active { get; set; } - public float MasterVolume { get; set; } = 0f; - public float PeakVolume { get; set; } = 0f; - } -} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/SensorAdvancedSettings.cs b/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/SensorAdvancedSettings.cs new file mode 100644 index 00000000..77aed989 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent.Shared/Models/Internal/SensorAdvancedSettings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HASS.Agent.Shared.Models.Internal; +public class SensorAdvancedSettings +{ + public string DeviceClass { get; set; } + public string UnitOfMeasurement { get; set; } + public string StateClass { get; set; } +} diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs index fac76b2c..0dd73ba8 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.Designer.cs @@ -1284,6 +1284,24 @@ internal static string CommandType_SetApplicationVolumeCommand { } } + /// + /// Looks up a localized string similar to SetAudioInputCommand. + /// + internal static string CommandType_SetAudioInputCommand { + get { + return ResourceManager.GetString("CommandType_SetAudioInputCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SetAudioOutputCommand. + /// + internal static string CommandType_SetAudioOutputCommand { + get { + return ResourceManager.GetString("CommandType_SetAudioOutputCommand", resourceCulture); + } + } + /// /// Looks up a localized string similar to SetVolume. /// @@ -1320,6 +1338,15 @@ internal static string CommandType_SwitchDesktopCommand { } } + /// + /// Looks up a localized string similar to TrayWebView. + /// + internal static string CommandType_TrayWebViewCommand { + get { + return ResourceManager.GetString("CommandType_TrayWebViewCommand", resourceCulture); + } + } + /// /// Looks up a localized string similar to WebView. /// @@ -3335,6 +3362,15 @@ internal static string HassAction_Play { } } + /// + /// Looks up a localized string similar to Press. + /// + internal static string HassAction_Press { + get { + return ResourceManager.GetString("HassAction_Press", resourceCulture); + } + } + /// /// Looks up a localized string similar to Stop. /// @@ -3353,6 +3389,15 @@ internal static string HassAction_Toggle { } } + /// + /// Looks up a localized string similar to Trigger. + /// + internal static string HassAction_Trigger { + get { + return ResourceManager.GetString("HassAction_Trigger", resourceCulture); + } + } + /// /// Looks up a localized string similar to Client certificate file not found.. /// @@ -3443,6 +3488,15 @@ internal static string HassDomain_Automation { } } + /// + /// Looks up a localized string similar to Button. + /// + internal static string HassDomain_Button { + get { + return ResourceManager.GetString("HassDomain_Button", resourceCulture); + } + } + /// /// Looks up a localized string similar to Climate. /// @@ -6537,6 +6591,15 @@ internal static string SensorType_CurrentVolumeSensor { } } + /// + /// Looks up a localized string similar to DeviceLocationTracker. + /// + internal static string SensorType_DeviceTrackerSensor { + get { + return ResourceManager.GetString("SensorType_DeviceTrackerSensor", resourceCulture); + } + } + /// /// Looks up a localized string similar to Display. /// @@ -6726,6 +6789,15 @@ internal static string SensorType_ProcessActiveSensor { } } + /// + /// Looks up a localized string similar to Screenshot. + /// + internal static string SensorType_ScreenshotSensor { + get { + return ResourceManager.GetString("SensorType_ScreenshotSensor", resourceCulture); + } + } + /// /// Looks up a localized string similar to ServiceState. /// diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx index fcd970f4..e1fa5ece 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.de.resx @@ -3352,4 +3352,31 @@ Willst Du den Runtime Installer herunterladen? AktiverDesktop + + Legen Sie die Anwendungslautstärke fest + + + Legen Sie den Audioausgabebefehl fest + + + Bildschirmfoto + + + Legen Sie den Audioeingabebefehl fest + + + TrayWebView + + + Auslösen + + + Knopf + + + Presse + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx index 4a7ea851..ef59f0ef 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.en.resx @@ -3225,10 +3225,34 @@ Do you want to download the runtime installer? InternalDeviceSensor - - SwitchDesktop - ActiveDesktop + + SetApplicationVolume + + + SetAudioOutputCommand + + + Screenshot + + + SetAudioInputCommand + + + TrayWebView + + + Trigger + + + Press + + + Button + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx index 1bac329d..30e9d133 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.es.resx @@ -3228,4 +3228,31 @@ Oculta, Maximizada, Minimizada, Normal y Desconocida. EscritorioActivo + + Establecer volumen de aplicación + + + Establecer comando de salida de audio + + + Captura de pantalla + + + Establecer comando de entrada de audio + + + Vista web de la bandeja + + + Desencadenar + + + Botón + + + Prensa + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx index f78ca843..b1fdcaaa 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.fr.resx @@ -3261,4 +3261,31 @@ Do you want to download the runtime installer? ActiveDesktop + + Définir le volume d'application + + + Définir la commande de sortie audio + + + Capture d'écran + + + Définir la commande d'entrée audio + + + Vue Web de la barre d'état + + + Déclencher + + + Knop + + + Presse + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx index 8765dac7..c43f0a20 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.nl.resx @@ -3250,4 +3250,31 @@ Wil je de runtime installatie downloaden? ActiveDesktop + + Stel het toepassingsvolume in + + + Stel het audio-uitvoercommando in + + + Schermafbeelding + + + Stel het audio-invoercommando in + + + TrayWebView + + + Activering + + + Knop + + + Pers + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx index 23625103..64ffb845 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pl.resx @@ -3338,4 +3338,31 @@ Czy chcesz pobrać plik instalacyjny? AktywnyPulpit + + Ustaw głośność aplikacji + + + Ustaw wyjście audio + + + Zrzut ekranu + + + Ustaw wejście audio + + + TrayWebView + + + Wyzwól + + + Przycisk + + + Naciśnij + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx index 32ffbfff..e6e688a5 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.pt-br.resx @@ -3275,4 +3275,31 @@ Deseja baixar o Microsoft WebView2 runtime? ActiveDesktop + + Definir Volume de Aplicativo + + + Definir comando de saída de áudio + + + Captura de tela + + + Definir comando de entrada de áudio + + + BandejaWebView + + + Acionar + + + Botão + + + Imprensa + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx index f033ea56..cdf119f3 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.resx @@ -1,76 +1,96 @@  + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, ... + System.Resources.ResXResourceWriter, System.Windows.Forms, ... + this is my long stringthis is a comment + Blue + + [base64 mime encoded serialized .NET Framework object] + + + [base64 mime encoded string representing a byte array form of the .NET Framework object] + This is a comment + + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + + + + + + + + + + + + + + + + + + - + + @@ -89,296 +109,296 @@ text/microsoft-resx - 1.3 + 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - This page allows you to configure bindings with external tools. - + This page allows you to configure bindings with external tools. + - Clear Image Cache - + Clear Image Cache + - Open Folder - + Open Folder + - Image Cache Location - + Image Cache Location + - days - + days + - Port - + Port + - Show Test Notification - + Show Test Notification + - Execute Port Reservation - + Execute Port Reservation + - Note: 5115 is the default port, only change it if you changed it in Home Assistant. - + Note: 5115 is the default port, only change it if you changed it in Home Assistant. + - Yes, accept notifications on port - + Yes, accept notifications on port + - HASS.Agent can receive notifications from Home Assistant, using text and/or images. + HASS.Agent can receive notifications from Home Assistant, using text and/or images. Do you want to enable this function? - + - HASS.Agent-Notifier GitHub Page - + HASS.Agent-Notifier GitHub Page + - Make sure you follow these steps: + Make sure you follow these steps: - Install HASS.Agent-Notifier integration - Restart Home Assistant - Configure a notifier entity - Restart Home Assistant - + - To use notifications, you need to install and configure the HASS.Agent-notifier integration in + To use notifications, you need to install and configure the HASS.Agent-notifier integration in Home Assistant. This is very easy using HACS but may also be installed manually, visit the link below for more information. - + - Name - + Name + - Type - + Type + - Apply - + Apply + - Stored! - + Stored! + - Name - + Name + - Type - + Type + - HASS.Agent Port Reservation - + HASS.Agent Port Reservation + - HASS.Agent Post Update - + HASS.Agent Post Update + - HASS.Agent Restarter - + HASS.Agent Restarter + - Name - + Name + - Type - + Type + - Type - + Type + - Domain - + Domain + - Entity - + Entity + - Action - + Action + - Hotkey - + Hotkey + - Description - + Description + - Domain - + Domain + - enable hotkey - + enable hotkey + - Name - + Name + - Type - + Type + - Sensors Configuration - + Sensors Configuration + - setting 1 - + setting 1 + - Setting 2 - + Setting 2 + - Setting 3 - + Setting 3 + - Type - + Type + - General - + General + - MQTT - + MQTT + - Commands - + Commands + - Sensors - + Sensors + - General - + General + - External Tools - + External Tools + - Home Assistant API - + Home Assistant API + - Hotkey - + Hotkey + - Local Storage - + Local Storage + - Logging - + Logging + - MQTT - + MQTT + - Notifications - + Notifications + - Satellite Service - + Satellite Service + - Startup - + Startup + - Updates - + Updates + - Browse HASS.Agent documentation and usage examples. - + Browse HASS.Agent documentation and usage examples. + - Show HASS.Agent - + Show HASS.Agent + - Show Quick Actions - + Show Quick Actions + - Configuration - + Configuration + - Manage Quick Actions - + Manage Quick Actions + - Manage Local Sensors - + Manage Local Sensors + - Manage Commands - + Manage Commands + - Check for Updates - + Check for Updates + - Donate - + Donate + - Help && Contact - + Help && Contact + - About - + About + - Exit HASS.Agent - + Exit HASS.Agent + - Loading.. - + Loading.. + - Loading.. - + Loading.. + - notification api: - + notification api: + - HASS.Agent Onboarding - + HASS.Agent Onboarding + - Fetching info, please wait.. - + Fetching info, please wait.. + - Execute a custom command. + Execute a custom command. These commands run without special elevation. To run elevated, create a Scheduled Task, and use 'schtasks /Run /TN "TaskName"' as the command to execute your task. Or enable 'run as low integrity' for even stricter execution. - + - Executes the command through the configured custom executor (in Configuration -> External Tools). + Executes the command through the configured custom executor (in Configuration -> External Tools). Your command is provided as an argument 'as is', so you have to supply your own quotes etc. if necessary. - + - Sets the machine in hibernation. - + Sets the machine in hibernation. + - Simulates a single keypress. + Simulates a single keypress. Click on the 'keycode' textbox and press the key you want simulated. The corresponding keycode will be entered for you. If you need more keys and/or modifiers like CTRL, use the MultipleKeys command. - + - Launches the provided URL, by default in your default browser. + Launches the provided URL, by default in your default browser. To use 'incognito', provide a specific browser in Configuration -> External Tools. If you just want a window with a specific URL (not an entire browser), use a 'WebView' command. - + - Locks the current session. - + Locks the current session. + - Logs off the current session. - + Logs off the current session. + - Simulates 'Mute' key. - + Simulates 'Mute' key. + - Simulates 'Media Next' key. - + Simulates 'Media Next' key. + - Simulates 'Media Pause/Play' key. - + Simulates 'Media Pause/Play' key. + - Simulates 'Media Previous' key. - + Simulates 'Media Previous' key. + - Simulates 'Volume Down' key. - + Simulates 'Volume Down' key. + - Simulates 'Volume Up' key. - + Simulates 'Volume Up' key. + - Simulates pressing mulitple keys. + Simulates pressing mulitple keys. You need to put [ ] between every key, otherwise HASS.Agent can't tell them apart. So say you want to press X TAB Y SHIFT-Z, it'd be [X] [{TAB}] [Y] [+Z]. @@ -393,803 +413,803 @@ There are a few tricks you can use: - For multiple presses, use {z 15}, which means Z will get pressed 15 times. More info: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.sendkeys - + - Execute a Powershell command or script. + Execute a Powershell command or script. You can either provide the location of a script (*.ps1), or a single-line command. This will run without special elevation. - + - Resets all sensor checks, forcing all sensors to process and send their value. + Resets all sensor checks, forcing all sensors to process and send their value. Useful for example if you want to force HASS.Agent to update all your sensors after a HA reboot. - + - Restarts the machine after one minute. + Restarts the machine after one minute. Tip: Accidentally triggered? Run 'shutdown /a' to abort shutdown. - + - Shuts down the machine after one minute. + Shuts down the machine after one minute. Tip: Accidentally triggered? Run 'shutdown /a' to abort shutdown. - + - Puts the machine to sleep. + Puts the machine to sleep. Note: due to a limitation in Windows, this only works if hibernation is disabled, otherwise it will just hibernate. You can use something like NirCmd (http://www.nirsoft.net/utils/nircmd.html) to circumvent this. - + - Please enter the location of your browser's binary! (.exe file) - + Please enter the location of your browser's binary! (.exe file) + - The browser binary provided could not be found, please ensure the path is correct and try again. - + The browser binary provided could not be found, please ensure the path is correct and try again. + - No incognito arguments were provided so the browser will likely launch normally. + No incognito arguments were provided so the browser will likely launch normally. Do you want to continue? - + - Something went wrong while launching your browser in incognito mode! + Something went wrong while launching your browser in incognito mode! Please check the logs for more information. - + - Please enter a valid API key! - + Please enter a valid API key! + - Please enter a value for your Home Assistant's URI. - + Please enter a value for your Home Assistant's URI. + - Unable to connect, the following error was returned: + Unable to connect, the following error was returned: {0} - + - Connection OK! + Connection OK! Home Assistant version: {0} - + - Image cache has been cleared! - + Image cache has been cleared! + - Cleaning.. - + Cleaning.. + - Notifications are currently disabled, please enable them and restart the HASS.Agent, then try again. - + Notifications are currently disabled, please enable them and restart the HASS.Agent, then try again. + - The test notification should have appeared, if you did not receive it please check the logs or consult the documentation for troubleshooting tips. + The test notification should have appeared, if you did not receive it please check the logs or consult the documentation for troubleshooting tips. Note: This only tests locally whether notifications can be shown! - + - This is a test notification! - + This is a test notification! + - Executing, please wait.. - + Executing, please wait.. + - Something went wrong whilst reserving the port! + Something went wrong whilst reserving the port! Manual execution is required and a command has been copied to your clipboard, please open an elevated terminal and paste the command. Additionally, remember to change your Firewall Rules port! - + - Not Installed - + Not Installed + - Disabled - + Disabled + - Running - + Running + - Stopped - + Stopped + - Failed - + Failed + - Something went wrong whilst stopping the service, did you allow the UAC prompt? + Something went wrong whilst stopping the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - The service is set to 'disabled', so it cannot be started. + The service is set to 'disabled', so it cannot be started. Please enable the service first and try again. - + - Something went wrong whilst starting the service, did you allow the UAC prompt? + Something went wrong whilst starting the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - Something went wrong whilst disabling the service, did you allow the UAC prompt? + Something went wrong whilst disabling the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - Something went wrong whilst enabling the service, did you allow the UAC prompt? + Something went wrong whilst enabling the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - Something went wrong whilst reinstalling the service, did you allow the UAC prompt? + Something went wrong whilst reinstalling the service, did you allow the UAC prompt? Check the HASS.Agent (not the service) logs for more information. - + - Something went wrong whilst disabling Start-on-Login, please check the logs for more information. - + Something went wrong whilst disabling Start-on-Login, please check the logs for more information. + - Something went wrong whilst disabling Start-on-Login, please check the logs for more information. - + Something went wrong whilst disabling Start-on-Login, please check the logs for more information. + - Enabled - + Enabled + - Disable Start-on-Login - + Disable Start-on-Login + - Disabled - + Disabled + - Enable Start-on-Login - + Enable Start-on-Login + - Start-on-Login has been activated! - + Start-on-Login has been activated! + - Do you want to enable Start-on-Login now? - + Do you want to enable Start-on-Login now? + - Start-on-Login is already activated, all set! - + Start-on-Login is already activated, all set! + - Activating Start-on-Login.. - + Activating Start-on-Login.. + - Something went wrong. You can try again, or skip to the next page and retry after HASS.Agent's restart. - + Something went wrong. You can try again, or skip to the next page and retry after HASS.Agent's restart. + - Enable Start-on-Login - + Enable Start-on-Login + - Please provide a valid API key. - + Please provide a valid API key. + - Please enter your Home Assistant's URI. - + Please enter your Home Assistant's URI. + - Unable to connect, the following error was returned: + Unable to connect, the following error was returned: {0} - + - Connection OK! + Connection OK! Home Assistant version: {0} - + - Testing.. - + Testing.. + - An error occurred whilst saving your commands, please check the logs for more information. - + An error occurred whilst saving your commands, please check the logs for more information. + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - Connecting with satellite service, please wait.. - + Connecting with satellite service, please wait.. + - Connecting to the service has failed! - + Connecting to the service has failed! + - The service hasn't been found! You can install and manage it from the configuration panel. + The service hasn't been found! You can install and manage it from the configuration panel. When it's up and running, come back here to configure the commands and sensors. - + - Communicating with the service has failed! - + Communicating with the service has failed! + - Unable to communicate with the service. Check the logs for more info. + Unable to communicate with the service. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Unauthorized - + Unauthorized + - You are not authorized to contact the service. + You are not authorized to contact the service. If you have the correct auth ID, you can set it now and try again. - + - Fetching settings failed! - + Fetching settings failed! + - The service returned an error while requesting its settings. Check the logs for more info. + The service returned an error while requesting its settings. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Fetching MQTT settings failed! - + Fetching MQTT settings failed! + - The service returned an error while requesting its MQTT settings. Check the logs for more info. + The service returned an error while requesting its MQTT settings. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Fetching configured commands failed! - + Fetching configured commands failed! + - The service returned an error while requesting its configured commands. Check the logs for more info. + The service returned an error while requesting its configured commands. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Fetching configured sensors failed! - + Fetching configured sensors failed! + - The service returned an error while requesting its configured sensors. Check the logs for more info. + The service returned an error while requesting its configured sensors. Check the logs for more info. You can open the logs and manage the service from the configuration panel. - + - Storing an empty auth ID will allow all HASS.Agent to access the service. + Storing an empty auth ID will allow all HASS.Agent to access the service. Are you sure you want this? - + - An error occurred whilst saving, check the logs for more information. - + An error occurred whilst saving, check the logs for more information. + - Please provide a device name! - + Please provide a device name! + - Please select an executor first. (Tip: Double click to Browse) - + Please select an executor first. (Tip: Double click to Browse) + - The selected executor could not be found, please ensure the path provided is correct and try again. - + The selected executor could not be found, please ensure the path provided is correct and try again. + - Set an auth ID if you don't want every instance of HASS.Agent on this PC to connect with the satellite service. + Set an auth ID if you don't want every instance of HASS.Agent on this PC to connect with the satellite service. Only the instances that have the correct ID, can connect. Leave empty to allow all to connect. - + - This is the name with which the satellite service registers itself on Home Assistant. + This is the name with which the satellite service registers itself on Home Assistant. By default, it's your PC's name plus '-satellite'. - + - The amount of time the satellite service will wait before reporting a lost connection to the MQTT broker. - + The amount of time the satellite service will wait before reporting a lost connection to the MQTT broker. + - Error fetching status, please check logs for information. - + Error fetching status, please check logs for information. + - An error occurred whilst saving the configuration, please check the logs for more information. - + An error occurred whilst saving the configuration, please check the logs for more information. + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - An error occurred whilst saving the sensors, please check the logs for more information. - + An error occurred whilst saving the sensors, please check the logs for more information. + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - Not all steps completed succesfully. Please consult the logs for more information. - + Not all steps completed succesfully. Please consult the logs for more information. + - Not all steps completed succesfully. Please consult the logs for more information. - + Not all steps completed succesfully. Please consult the logs for more information. + - HASS.Agent is still active after {0} seconds. Please close all instances and restart manually. + HASS.Agent is still active after {0} seconds. Please close all instances and restart manually. Check the logs for more info, and optionally inform the developers. - + - Not all steps completed successfully, please check the logs for more information. - + Not all steps completed successfully, please check the logs for more information. + - Enable Satellite Service - + Enable Satellite Service + - Disable Satellite Service - + Disable Satellite Service + - Start Satellite Service - + Start Satellite Service + - Stop Satellite Service - + Stop Satellite Service + - Something went wrong while processing the desired service state. + Something went wrong while processing the desired service state. Please consult the logs for more information. - + - Topic copied to clipboard! - + Topic copied to clipboard! + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - An error occurred whilst saving commands, please check the logs for more information. - + An error occurred whilst saving commands, please check the logs for more information. + - New Command - + New Command + - Mod Command - + Mod Command + - Please select a command type! - + Please select a command type! + - Please select a valid command type! - + Please select a valid command type! + - Select a valid entity type first. - + Select a valid entity type first. + - Please provide a name! - + Please provide a name! + - A command with that name already exists, are you sure you want to continue? - + A command with that name already exists, are you sure you want to continue? + - If a command is not provided, you may only use this entity with an 'action' value via Home Assistant, running it as-is will have no action. + If a command is not provided, you may only use this entity with an 'action' value via Home Assistant, running it as-is will have no action. Are you sure you want to proceed? - + - If you don't enter a command or script, you can only use this entity with an 'action' value through Home Assistant. Running it as-is won't do anything. + If you don't enter a command or script, you can only use this entity with an 'action' value through Home Assistant. Running it as-is won't do anything. Are you sure you want this? - + - Please enter a key code! - + Please enter a key code! + - Checking keys failed: {0} - + Checking keys failed: {0} + - If a URL is not provided, you may only use this entity with an 'action' value via Home Assistant, running it as-is will have no action. + If a URL is not provided, you may only use this entity with an 'action' value via Home Assistant, running it as-is will have no action. Are you sure you want to proceed? - + - Command - + Command + - Command or Script - + Command or Script + - Keycode - + Keycode + - Keycodes - + Keycodes + - Launch in Incognito Mode - + Launch in Incognito Mode + - Browser: Default + Browser: Default Please configure a custom browser to enable incognito mode. - + - URL - + URL + - Browser: {0} - + Browser: {0} + - Executor: None + Executor: None Please configure an executor or your command will not run. - + - Executor: {0} - + Executor: {0} + - Low integrity means your command will be executed with restricted privileges. - + Low integrity means your command will be executed with restricted privileges. + - This means it will only be able to save and modify files in certain locations, - + This means it will only be able to save and modify files in certain locations, + - such as the '%USERPROFILE%\AppData\LocalLow' folder or - + such as the '%USERPROFILE%\AppData\LocalLow' folder or + - the 'HKEY_CURRENT_USER\Software\AppDataLow' registry key. - + the 'HKEY_CURRENT_USER\Software\AppDataLow' registry key. + - You should test your command to make sure it's not influenced by this! - + You should test your command to make sure it's not influenced by this! + - {0} only! - + {0} only! + - The MQTT manager hasn't been configured properly, or hasn't yet completed its startup. - + The MQTT manager hasn't been configured properly, or hasn't yet completed its startup. + - Unable to fetch your entities because of missing config, please enter the required values in the config screen. - + Unable to fetch your entities because of missing config, please enter the required values in the config screen. + - There was an error trying to fetch your entities! - + There was an error trying to fetch your entities! + - New Quick Action - + New Quick Action + - Mod Quick Action - + Mod Quick Action + - Unable to fetch your entities because of missing config, please enter the required values in the config screen. - + Unable to fetch your entities because of missing config, please enter the required values in the config screen. + - There was an error trying to fetch your entities. - + There was an error trying to fetch your entities. + - Please select an entity! - + Please select an entity! + - Unknown action, please select a valid one. - + Unknown action, please select a valid one. + - Storing and registering, please wait.. - + Storing and registering, please wait.. + - An error occurred whilst saving the sensors, please check the logs for more information. - + An error occurred whilst saving the sensors, please check the logs for more information. + - New Sensor - + New Sensor + - Mod Sensor - + Mod Sensor + - Window Name - + Window Name + - WMI Query - + WMI Query + - WMI Scope (optional) - + WMI Scope (optional) + - Category - + Category + - Counter - + Counter + - Instance (optional) - + Instance (optional) + - Process - + Process + - Service - + Service + - Please select a sensor type! - + Please select a sensor type! + - Please select a valid sensor type! - + Please select a valid sensor type! + - Please provide a name! - + Please provide a name! + - A single-value sensor already exists with that name, are you sure you want to proceed? - + A single-value sensor already exists with that name, are you sure you want to proceed? + - A multi-value sensor already exists with that name, are you sure you want to proceed? - + A multi-value sensor already exists with that name, are you sure you want to proceed? + - Please provide an interval between 1 and 43200 (12 hours)! - + Please provide an interval between 1 and 43200 (12 hours)! + - Please enter a window name! - + Please enter a window name! + - Please enter a query! - + Please enter a query! + - Please enter a category and instance! - + Please enter a category and instance! + - Please enter the name of a process! - + Please enter the name of a process! + - Please enter the name of a service! - + Please enter the name of a service! + - {0} only! - + {0} only! + - You've changed your device's name. + You've changed your device's name. All your sensors and commands will now be unpublished, and HASS.Agent will restart afterwards to republish them. Don't worry, they'll keep their current names, so your automations or scripts will keep working. Note: the name will get 'sanitized', which means everything except letters, digits and whitespace get replaced by an underscore. This is required by HA. - + - You've changed the local API's port. This new port needs to be reserved. + You've changed the local API's port. This new port needs to be reserved. You'll get an UAC request to do so, please approve. - + - Something went wrong! + Something went wrong! Please manually execute the required command. It has been copied onto your clipboard, you just need to paste it into an elevated command prompt. Remember to change your firewall rule's port as well. - + - The port has succesfully been reserved! + The port has succesfully been reserved! HASS.Agent will now restart to activate the new configuration. - + - Something went wrong while preparing to restart. + Something went wrong while preparing to restart. Please restart manually. - + - Your configuration has been saved. Most changes require HASS.Agent to restart before they take effect. + Your configuration has been saved. Most changes require HASS.Agent to restart before they take effect. Do you want to restart now? - + - Something went wrong while loading your settings. + Something went wrong while loading your settings. Check appsettings.json in the 'config' subfolder, or just delete it to start fresh. - + - There was an error launching HASS.Agent. + There was an error launching HASS.Agent. Please check the logs and make a bug report on GitHub. - + - &Sensors - + &Sensors + - &Commands - + &Commands + - Checking.. - + Checking.. + - You're running the latest version: {0}{1} - + You're running the latest version: {0}{1} + - There's a new BETA release available: - + There's a new BETA release available: + - HASS.Agent BETA Update - + HASS.Agent BETA Update + - Do you want to &download and launch the installer? - + Do you want to &download and launch the installer? + - Do you want to &navigate to the release page? - + Do you want to &navigate to the release page? + - Install Update - + Install Update + - Install Beta Release - + Install Beta Release + - Open Release Page - + Open Release Page + - Open Beta Release Page - + Open Beta Release Page + - Processing request, please wait.. - + Processing request, please wait.. + - Processing.. - + Processing.. + - HASS.Agent Onboarding: Start [{0}/{1}] - + HASS.Agent Onboarding: Start [{0}/{1}] + - HASS.Agent Onboarding: Startup [{0}/{1}] - + HASS.Agent Onboarding: Startup [{0}/{1}] + - HASS.Agent Onboarding: Notifications [{0}/{1}] - + HASS.Agent Onboarding: Notifications [{0}/{1}] + - HASS.Agent Onboarding: Integration [{0}/{1}] - + HASS.Agent Onboarding: Integration [{0}/{1}] + - HASS.Agent Onboarding: API [{0}/{1}] - + HASS.Agent Onboarding: API [{0}/{1}] + - HASS.Agent Onboarding: MQTT [{0}/{1}] - + HASS.Agent Onboarding: MQTT [{0}/{1}] + - HASS.Agent Onboarding: HotKey [{0}/{1}] - + HASS.Agent Onboarding: HotKey [{0}/{1}] + - HASS.Agent Onboarding: Updates [{0}/{1}] - + HASS.Agent Onboarding: Updates [{0}/{1}] + - HASS.Agent Onboarding: Completed [{0}/{1}] - + HASS.Agent Onboarding: Completed [{0}/{1}] + - Are you sure you want to abort the onboarding process? + Are you sure you want to abort the onboarding process? Your progress will not be saved, and it will not be shown again on next launch. - + - Error fetching info, please check logs for more information. - + Error fetching info, please check logs for more information. + - Unable to prepare downloading the update, check the logs for more info. + Unable to prepare downloading the update, check the logs for more info. The release page will now open instead. - + - Unable to download the update, check the logs for more info. + Unable to download the update, check the logs for more info. The release page will now open instead. - + - The downloaded file FAILED the certificate check. + The downloaded file FAILED the certificate check. This could be a technical error, but also a tampered file! Please check the logs, and post a ticket with the findings. - + - Unable to launch the installer (did you approve the UAC prompt?), check the logs for more info. + Unable to launch the installer (did you approve the UAC prompt?), check the logs for more info. The release page will now open instead. - + - HASS API: Connection setup failed. - + HASS API: Connection setup failed. + - HASS API: Initial connection failed. - + HASS API: Initial connection failed. + - HASS API: Connection failed. - + HASS API: Connection failed. + - Client certificate file not found. - + Client certificate file not found. + - Unable to connect, check URI. - + Unable to connect, check URI. + - Unable to fetch configuration, please check API key. - + Unable to fetch configuration, please check API key. + - Unable to connect, please check URI and configuration. - + Unable to connect, please check URI and configuration. + - quick action: action failed, check the logs for info - + quick action: action failed, check the logs for info + - quick action: action failed, entity not found - + quick action: action failed, entity not found + - MQTT: Error while connecting - + MQTT: Error while connecting + - MQTT: Failed to connect - + MQTT: Failed to connect + - MQTT: Disconnected - + MQTT: Disconnected + - Error trying to bind the API to port {0}. + Error trying to bind the API to port {0}. Make sure no other instance of HASS.Agent is running and the port is available and registered. - + - Provides the title of the current active window. - + Provides the title of the current active window. + - Provides information various aspects of your device's audio: + Provides information various aspects of your device's audio: Current peak volume level (can be used as a simple 'is something playing' value). Default audio device: name, state and volume. Summary of your audio sessions: application name, muted state, volume and current peak volume. - + - Provides a sensor with the current charging status, estimated amount of minutes on a full charge, remaining charge as a percentage, remaining charge in minutes and the powerline status. - + Provides a sensor with the current charging status, estimated amount of minutes on a full charge, remaining charge as a percentage, remaining charge in minutes and the powerline status. + - Provides the current load of the first CPU as a percentage. - + Provides the current load of the first CPU as a percentage. + - Provides the current clockspeed of the first CPU. - + Provides the current clockspeed of the first CPU. + - Provides the current volume level as a percentage. + Provides the current volume level as a percentage. Currently takes the volume of your default device. - + - Provides a sensor with the amount of displays, name of the primary display, and per display its name, resolution and bits per pixel. - + Provides a sensor with the amount of displays, name of the primary display, and per display its name, resolution and bits per pixel. + - Dummy sensor for testing purposes, sends a random integer value between 0 and 100. - + Dummy sensor for testing purposes, sends a random integer value between 0 and 100. + - Provides the current load of the first GPU as a percentage. - + Provides the current load of the first GPU as a percentage. + - Provides the current temperature of the first GPU. - + Provides the current temperature of the first GPU. + - Provides a datetime value containing the last moment the user provided any input. - + Provides a datetime value containing the last moment the user provided any input. + - Provides a datetime value containing the last moment the system (re)booted. + Provides a datetime value containing the last moment the system (re)booted. Important: Windows' FastBoot option can throw this value off, because that's a form of hibernation. You can disable it through Power Options -> 'Choose what the power buttons do' -> uncheck 'Turn on fast start-up'. It doesn't make much difference for modern machines with SSDs, but disabling makes sure you get a clean state after rebooting. - + - Provides the last system state change: + Provides the last system state change: ApplicationStarted, Logoff, SystemShutdown, Resume, Suspend, ConsoleConnect, ConsoleDisconnect, RemoteConnect, RemoteDisconnect, SessionLock, SessionLogoff, SessionLogon, SessionRemoteControl and SessionUnlock. - + - Returns the name of the currently logged user. + Returns the name of the currently logged user. This will only show active users, and falls back to 'Empty' if there are none. If there are multiple, the first will be used. - + - Returns a json-formatted list of currently logged users. + Returns a json-formatted list of currently logged users. This will also contain users that aren't active. If you only want the current active user, use the LoggedUser sensor instead. - + - Provides the amount of used memory as a percentage. - + Provides the amount of used memory as a percentage. + - Provides a bool value based on whether the microphone is currently being used. + Provides a bool value based on whether the microphone is currently being used. Note: if used in the satellite service, it won't detect userspace applications. - + - Provides an ON/OFF value based on whether the window is currently open (doesn't have to be active). - + Provides an ON/OFF value based on whether the window is currently open (doesn't have to be active). + - Provides card info, configuration, transfer- & package statistics and addresses (ip, mac, dhcp, dns) of the selected network card(s). + Provides card info, configuration, transfer- & package statistics and addresses (ip, mac, dhcp, dns) of the selected network card(s). This is a multi-value sensor. - + - Provides the values of a performance counter. + Provides the values of a performance counter. For example, the built-in CPU load sensor uses these values: @@ -1198,415 +1218,415 @@ Counter: % Processor Time Instance: _Total You can explore the counters through Windows' 'perfmon.exe' tool. - + - Provides the number of active instances of the process. + Provides the number of active instances of the process. Note: don't add the extension (eg. notepad.exe becomes notepad). - + - Returns the state of the provided service: NotFound, Stopped, StartPending, StopPending, Running, ContinuePending, PausePending or Paused. + Returns the state of the provided service: NotFound, Stopped, StartPending, StopPending, Running, ContinuePending, PausePending or Paused. Make sure to provide the 'Service name', not the 'Display name'. - + - Provides the current session state: + Provides the current session state: Locked, Unlocked or Unknown. Use a LastSystemStateChangeSensor to monitor session state changes. - + - Provides the labels, total size (MB), available space (MB), used space (MB) and file system of all present non-removable disks. - + Provides the labels, total size (MB), available space (MB), used space (MB) and file system of all present non-removable disks. + - Provides the current user state: + Provides the current user state: NotPresent, Busy, RunningDirect3dFullScreen, PresentationMode, AcceptsNotifications, QuietTime or RunningWindowsStoreApp. Can for instance be used to determine whether to send notifications or TTS messages. - + - Provides a bool value based on whether the webcam is currently being used. + Provides a bool value based on whether the webcam is currently being used. Note: if used in the satellite service, it won't detect userspace applications. - + - Provides a sensor with the amount of pending driver updates, a sensor with the amount of pending software updates, a sensor containing all pending driver updates information (title, kb article id's, hidden, type and categories) and a sensor containing the same for pending software updates. + Provides a sensor with the amount of pending driver updates, a sensor with the amount of pending software updates, a sensor containing all pending driver updates information (title, kb article id's, hidden, type and categories) and a sensor containing the same for pending software updates. This is a costly request, so the recommended interval is 15 minutes (900 seconds). But it's capped at 10 minutes, if you provide a lower value, you'll get the last known list. - + - Provides the result of the WMI query. - + Provides the result of the WMI query. + - Error loading settings: + Error loading settings: {0} - + - Error storing initial settings: + Error storing initial settings: {0} - + - Error storing settings: + Error storing settings: {0} - + - Error loading commands: + Error loading commands: {0} - + - Error storing commands: + Error storing commands: {0} - + - Error loading quick actions: + Error loading quick actions: {0} - + - Error storing quick actions: + Error storing quick actions: {0} - + - Error loading sensors: + Error loading sensors: {0} - + - Error storing sensors: + Error storing sensors: {0} - + - Wiki - + Wiki + - Busy, please wait.. - + Busy, please wait.. + - Finish - + Finish + - Connected - + Connected + - Connecting.. - + Connecting.. + - Configuration missing - + Configuration missing + - Disconnected - + Disconnected + - Error - + Error + - Custom - + Custom + - CustomExecutor - + CustomExecutor + - Hibernate - + Hibernate + - Key - + Key + - LaunchUrl - + LaunchUrl + - Lock - + Lock + - LogOff - + LogOff + - MediaMute - + MediaMute + - MediaNext - + MediaNext + - MediaPlayPause - + MediaPlayPause + - MediaPrevious - + MediaPrevious + - MediaVolumeDown - + MediaVolumeDown + - MediaVolumeUp - + MediaVolumeUp + - MultipleKeys - + MultipleKeys + - Powershell - + Powershell + - PublishAllSensors - + PublishAllSensors + - Restart - + Restart + - Shutdown - + Shutdown + - Sleep - + Sleep + - Button - + Button + - Light - + Light + - Lock - + Lock + - Siren - + Siren + - Switch - + Switch + - Locked - + Locked + - Unknown - + Unknown + - Unlocked - + Unlocked + - ActiveWindow - + ActiveWindow + - Audio - + Audio + - Battery - + Battery + - CpuLoad - + CpuLoad + - CurrentClockSpeed - + CurrentClockSpeed + - CurrentVolume - + CurrentVolume + - Display - + Display + - Dummy - + Dummy + - GpuLoad - + GpuLoad + - GpuTemperature - + GpuTemperature + - LastActive - + LastActive + - LastBoot - + LastBoot + - LastSystemStateChange - + LastSystemStateChange + - LoggedUser - + LoggedUser + - LoggedUsers - + LoggedUsers + - MemoryUsage - + MemoryUsage + - MicrophoneActive - + MicrophoneActive + - NamedWindow - + NamedWindow + - Network - + Network + - PerformanceCounter - + PerformanceCounter + - ProcessActive - + ProcessActive + - ServiceState - + ServiceState + - SessionState - + SessionState + - Storage - + Storage + - UserNotification - + UserNotification + - WebcamActive - + WebcamActive + - WindowsUpdates - + WindowsUpdates + - WmiQuery - + WmiQuery + - ApplicationStarted - + ApplicationStarted + - Logoff - + Logoff + - SystemShutdown - + SystemShutdown + - Resume - + Resume + - Suspend - + Suspend + - ConsoleConnect - + ConsoleConnect + - ConsoleDisconnect - + ConsoleDisconnect + - RemoteConnect - + RemoteConnect + - RemoteDisconnect - + RemoteDisconnect + - SessionLock - + SessionLock + - SessionLogoff - + SessionLogoff + - SessionLogon - + SessionLogon + - SessionRemoteControl - + SessionRemoteControl + - SessionUnlock - + SessionUnlock + - NotPresent - + NotPresent + - Busy - + Busy + - RunningDirect3dFullScreen - + RunningDirect3dFullScreen + - PresentationMode - + PresentationMode + - AcceptsNotifications - + AcceptsNotifications + - QuietTime - + QuietTime + - RunningWindowsStoreApp - + RunningWindowsStoreApp + - On - + On + - Off - + Off + - Open - + Open + - Close - + Close + - Play - + Play + - Pause - + Pause + - Stop - + Stop + - Toggle - + Toggle + - Automation - + Automation + - Climate - + Climate + - Cover - + Cover + - InputBoolean - + InputBoolean + - Light - + Light + - MediaPlayer - + MediaPlayer + - Scene - + Scene + - Script - + Script + - Switch - + Switch + - Connecting.. - + Connecting.. + - Failed - + Failed + - Loading.. - + Loading.. + - Running - + Running + - Stopped - + Stopped + - Disabled - + Disabled + - It looks like your scope is malformed, it should probably start like this: + It looks like your scope is malformed, it should probably start like this: \\.\ROOT\ @@ -1617,1588 +1637,1588 @@ The scope you entered: Tip: make sure you haven't switched the scope and query fields around. Do you still want to use the current values? - + - Enter a WMI query first. - + Enter a WMI query first. + - Query succesfully executed, result value: + Query succesfully executed, result value: {0} - + - The query failed to execute: + The query failed to execute: {0} Do you want to open the logs folder? - + - Enter a category and counter first. - + Enter a category and counter first. + - Test succesfully executed, result value: + Test succesfully executed, result value: {0} - + - The test failed to execute: + The test failed to execute: {0} Do you want to open the logs folder? - + - Test WMI Query - + Test WMI Query + - Test Performance Counter - + Test Performance Counter + - GeoLocation - + GeoLocation + - All - + All + - Network Card - + Network Card + - Last Known Value - + Last Known Value + - SendWindowToFront - + SendWindowToFront + - Cleaning.. - + Cleaning.. + - The audio cache has been cleared! - + The audio cache has been cleared! + - Cleaning.. - + Cleaning.. + - The WebView cache has been cleared! - + The WebView cache has been cleared! + - WebView - + WebView + - Looks for the specified process, and tries to send its main window to the front. + Looks for the specified process, and tries to send its main window to the front. If the application is minimized, it'll get restored. Example: if you want to send VLC to the foreground, use 'vlc'. - + - Shows a window with the provided URL. + Shows a window with the provided URL. This differs from the 'LaunchUrl' command in that it doesn't load a full-fledged browser, just the provided URL in its own window. You can use this to for instance quickly show Home Assistant's dashboard. By default, it stores cookies indefinitely so you only have to log in once. - + - Unable to load the stored command settings, resetting to default. - + Unable to load the stored command settings, resetting to default. + - If you do not configure the command, you may only use this entity with an 'action' value via Home Assistant and it will appear using the default settings, running it as-is will have no action. + If you do not configure the command, you may only use this entity with an 'action' value via Home Assistant and it will appear using the default settings, running it as-is will have no action. Are you sure you want to do this? - + - Please select an domain! - + Please select an domain! + - HASS.Agent Commands - + HASS.Agent Commands + - It looks like you're using an alternative scaling. This may result in some parts of HASS.Agent not looking as intended. + It looks like you're using an alternative scaling. This may result in some parts of HASS.Agent not looking as intended. Please report any unusable aspects on GitHub. Thanks! Note: this message only shows once. - + - Your device name contained some characters that are not allowed by HA (only letters, digits and whitespace). + Your device name contained some characters that are not allowed by HA (only letters, digits and whitespace). The final name is: {0} Do you want to use that version? - + - No keys found - + No keys found + - brackets missing, start and close all keys with [ ] - + brackets missing, start and close all keys with [ ] + - Error while parsing keys, please check the logs for more information. - + Error while parsing keys, please check the logs for more information. + - Your input language '{0}' is known to collide with the default CTRL-ALT-Q hotkey. Please set your own. - + Your input language '{0}' is known to collide with the default CTRL-ALT-Q hotkey. Please set your own. + - Your input language '{0}' is unknown, and might collide with the default CTRL-ALT-Q hotkey. Please check to be sure. If it does, consider opening a ticket on GitHub so it can be added to the list. - + Your input language '{0}' is unknown, and might collide with the default CTRL-ALT-Q hotkey. Please check to be sure. If it does, consider opening a ticket on GitHub so it can be added to the list. + - The number of open brackets ('[') does not correspond to the number of closed brackets. (']')! ({0} to {1}) - + The number of open brackets ('[') does not correspond to the number of closed brackets. (']')! ({0} to {1}) + - &Hide - + &Hide + - Browser Name - + Browser Name + - Browser Binary - + Browser Binary + - Additional Launch Arguments - + Additional Launch Arguments + - Custom Executor Binary - + Custom Executor Binary + - Tip: Double-click to Browse - + Tip: Double-click to Browse + - &Test - + &Test + - Seconds - + Seconds + - Disconnected Grace &Period - + Disconnected Grace &Period + - This page contains general configuration settings, for more settings you can browse the tabs on the left. - + This page contains general configuration settings, for more settings you can browse the tabs on the left. + - Device &Name - + Device &Name + - Interface &Language - + Interface &Language + - Tip: Double-click this field to browse - + Tip: Double-click this field to browse + - Client &Certificate - + Client &Certificate + - Use &automatic client certificate selection - + Use &automatic client certificate selection + - &Test Connection - + &Test Connection + - &API Token - + &API Token + - Server &URI - + Server &URI + - &Clear - + &Clear + - An easy way to pull up your quick actions is to use a global hotkey. + An easy way to pull up your quick actions is to use a global hotkey. This way, whatever you're doing on your machine, you can always interact with Home Assistant. - + - &Enable Quick Actions Hotkey - + &Enable Quick Actions Hotkey + - &Hotkey Combination - + &Hotkey Combination + - &Port - + &Port + - Execute Port &Reservation - + Execute Port &Reservation + - &Enable Local API - + &Enable Local API + - To be able to listen to the requests, HASS.Agent needs to have its port reserved and opened in your firewall. You can use this button to have it done for you. - + To be able to listen to the requests, HASS.Agent needs to have its port reserved and opened in your firewall. You can use this button to have it done for you. + - Image Cache Location - + Image Cache Location + - days - + days + - Some items like images shown in notifications have to be temporarily stored locally. You can + Some items like images shown in notifications have to be temporarily stored locally. You can configure the amount of days they should be kept before HASS.Agent deletes them. Enter '0' to keep them permanently. - + - Keep images for - + Keep images for + - Keep audio for - + Keep audio for + - Clear Audio Cache - + Clear Audio Cache + - Audio Cache Location - + Audio Cache Location + - Clear WebView Cache - + Clear WebView Cache + - WebView Cache Location - + WebView Cache Location + - Clear cache every - + Clear cache every + - Extended logging provides more verbose and in-depth logging, in case the default logging isn't + Extended logging provides more verbose and in-depth logging, in case the default logging isn't sufficient. Please note that enabling this can cause the logfiles to grow large, and should only be used when you suspect something's wrong with HASS.Agent itself or when asked by the developers. - + - &Enable Extended Logging - + &Enable Extended Logging + - &Open Logs Folder - + &Open Logs Folder + - Media Player &Documentation - + Media Player &Documentation + - &Enable Media Player Functionality - + &Enable Media Player Functionality + - Tip: Double-click these fields to browse - + Tip: Double-click these fields to browse + - Client Certificate - + Client Certificate + - Root Certificate - + Root Certificate + - Use &Retain Flag - + Use &Retain Flag + - &Allow Untrusted Certificates - + &Allow Untrusted Certificates + - &Clear Configuration - + &Clear Configuration + - (leave default if unsure) - + (leave default if unsure) + - Discovery Prefix - + Discovery Prefix + - &TLS - + &TLS + - Password - + Password + - Username - + Username + - Port - + Port + - Broker IP Address or Hostname - + Broker IP Address or Hostname + - (leave empty to auto generate) - + (leave empty to auto generate) + - Client ID - + Client ID + - Notifications &Documentation - + Notifications &Documentation + - &Accept Notifications - + &Accept Notifications + - &Ignore certificate errors for images - + &Ignore certificate errors for images + - The local API is disabled however the media player requires it in order to function. - + The local API is disabled however the media player requires it in order to function. + - Service Status: - + Service Status: + - S&tart Service - + S&tart Service + - &Disable Service - + &Disable Service + - &Stop Service - + &Stop Service + - &Enable Service - + &Enable Service + - &Reinstall Service - + &Reinstall Service + - If you do not configure the service, it won't do anything. However, you can still decide to disable it as well. + If you do not configure the service, it won't do anything. However, you can still decide to disable it as well. The installer will leave the disabled service alone(if you remove the service, the installer will reinstall it). - + - You can try reinstalling the service if it's not working correctly. + You can try reinstalling the service if it's not working correctly. Your configuration and entities won't be removed. - + - Open Service &Logs Folder - + Open Service &Logs Folder + - If the service still fails after reinstalling, please open a ticket and send the content of the latest log. - + If the service still fails after reinstalling, please open a ticket and send the content of the latest log. + - HASS.Agent can start when you login by creating an entry in your user profile's registry. + HASS.Agent can start when you login by creating an entry in your user profile's registry. Since HASS.Agent is user based, if you want to launch for another user, just install and config HASS.Agent there. - + - &Enable Start-on-Login - + &Enable Start-on-Login + - Start-on-Login Status: - + Start-on-Login Status: + - Control the behaviour of the tray icon when it is right-clicked. - + Control the behaviour of the tray icon when it is right-clicked. + - Show &Default Menu - + Show &Default Menu + - Show &WebView - + Show &WebView + - &WebView URL (For instance, your Home Assistant Dashboard URL) - + &WebView URL (For instance, your Home Assistant Dashboard URL) + - Size (px) - + Size (px) + - Show &Preview - + Show &Preview + - &Keep page loaded in the background - + &Keep page loaded in the background + - (This uses extra resources, but reduces loading time.) - + (This uses extra resources, but reduces loading time.) + - Notify me of &beta releases - + Notify me of &beta releases + - When a new update is available, HASS.Agent can download the installer and launch it for you. + When a new update is available, HASS.Agent can download the installer and launch it for you. The certificate of the downloaded file will get checked before running,you will still get to review the release notes and manually approve the update. - + - Automatically &download future updates - + Automatically &download future updates + - HASS.Agent checks for updates in the background if enabled. + HASS.Agent checks for updates in the background if enabled. You will be sent a push notification if a new update is discovered, letting you know a new version is ready to be installed. - + - Notify me when a new &release is available - + Notify me when a new &release is available + - Welcome to the HASS.Agent! It looks like this is the first time you are launching the agent. + Welcome to the HASS.Agent! It looks like this is the first time you are launching the agent. To assist you with a first time setup, proceed with the configuration steps below or alternatively, click 'Close'. - + - The device name is used to identify your machine on Home Assistant, it is also used as a suggested prefix for your commands and sensors. - + The device name is used to identify your machine on Home Assistant, it is also used as a suggested prefix for your commands and sensors. + - Device &Name - + Device &Name + - Interface &Language - + Interface &Language + - Yes, &start HASS.Agent on System Login - + Yes, &start HASS.Agent on System Login + - HASS.Agent can start with your system, this allows for any sensors and data transmission between your device and Home Assistant to begin as soon as you login. + HASS.Agent can start with your system, this allows for any sensors and data transmission between your device and Home Assistant to begin as soon as you login. This setting can be changed any time later in the HASS.Agent configuration window. - + - Fetching current state, please wait.. - + Fetching current state, please wait.. + - Note: 5115 is the default port, only change it if you changed it in Home Assistant. - + Note: 5115 is the default port, only change it if you changed it in Home Assistant. + - Yes, &enable the local API on port - + Yes, &enable the local API on port + - HASS.Agent has its own internal API, so Home Assistant can send requests (like notifications or text-to-speech). + HASS.Agent has its own internal API, so Home Assistant can send requests (like notifications or text-to-speech). Do you want to enable it? - + - Enable &Notifications - + Enable &Notifications + - Enable &Media Player and text-to-speech (TTS) - + Enable &Media Player and text-to-speech (TTS) + - You can choose what modules you want to enable. They require HA integrations, but don't worry, the next page will give you more info on how to set them up. - + You can choose what modules you want to enable. They require HA integrations, but don't worry, the next page will give you more info on how to set them up. + - To use notifications, you need to install and configure the HASS.Agent-notifier integration in + To use notifications, you need to install and configure the HASS.Agent-notifier integration in Home Assistant. This is very easy using HACS, but you can also install manually. Visit the link below for more information. - + - HASS.Agent-MediaPlayer GitHub Page - + HASS.Agent-MediaPlayer GitHub Page + - The same goes for the media player, this integration allows you to control your device as a media_player entity, see what's playing and send text-to-speech. - + The same goes for the media player, this integration allows you to control your device as a media_player entity, see what's playing and send text-to-speech. + - API &Token - + API &Token + - Server &URI (should be ok like this) - + Server &URI (should be ok like this) + - Test &Connection - + Test &Connection + - Tip: Specialized settings can be found in the Configuration Window. - + Tip: Specialized settings can be found in the Configuration Window. + - &TLS - + &TLS + - Discovery Prefix - + Discovery Prefix + - (leave default if not sure) - + (leave default if not sure) + - Tip: Specialized settings can be found in the Configuration Window. - + Tip: Specialized settings can be found in the Configuration Window. + - &Hotkey Combination - + &Hotkey Combination + - An easy way to pull up your quick actions is to use a global hotkey. + An easy way to pull up your quick actions is to use a global hotkey. This way, whatever you're doing on your machine, you can always interact with Home Assistant. - + - &Clear - + &Clear + - Yes, notify me on new &updates - + Yes, notify me on new &updates + - Yes, &download and launch the installer for me - + Yes, &download and launch the installer for me + - HASS.Agent GitHub page - + HASS.Agent GitHub page + - Low Integrity - + Low Integrity + - &Remove - + &Remove + - &Modify - + &Modify + - &Add New - + &Add New + - &Send && Activate Commands - + &Send && Activate Commands + - commands stored! - + commands stored! + - &Apply - + &Apply + - Auth &ID - + Auth &ID + - Authenticate - + Authenticate + - Connect with service - + Connect with service + - Connecting satellite service, please wait.. - + Connecting satellite service, please wait.. + - Fetch Configuration - + Fetch Configuration + - (leave empty to auto generate) - + (leave empty to auto generate) + - Client ID - + Client ID + - Tip: Double-click these fields to browse - + Tip: Double-click these fields to browse + - Client Certificate - + Client Certificate + - Root Certificate - + Root Certificate + - Use &Retain Flag - + Use &Retain Flag + - &Allow Untrusted Certificates - + &Allow Untrusted Certificates + - &Clear Configuration - + &Clear Configuration + - (leave default if not sure) - + (leave default if not sure) + - Commands and sensors are sent through MQTT. Please provide credentials for your server. If you're using the HA addon, + Commands and sensors are sent through MQTT. Please provide credentials for your server. If you're using the HA addon, you can probably use the preset address. - + - Discovery Prefix - + Discovery Prefix + - &TLS - + &TLS + - Password - + Password + - Username - + Username + - Port - + Port + - Broker IP Address or Hostname - + Broker IP Address or Hostname + - &Send && Activate Configuration - + &Send && Activate Configuration + - Copy from &HASS.Agent - + Copy from &HASS.Agent + - Configuration stored! - + Configuration stored! + - Status - + Status + - Querying.. - + Querying.. + - Refresh - + Refresh + - &Remove - + &Remove + - &Add New - + &Add New + - &Modify - + &Modify + - &Send && Activate Sensors - + &Send && Activate Sensors + - Sensors stored! - + Sensors stored! + - Please wait a bit while the task is performed .. - + Please wait a bit while the task is performed .. + - Create API Port Binding - + Create API Port Binding + - Set Firewall Rule - + Set Firewall Rule + - Please wait a bit while some post-update tasks are performed .. - + Please wait a bit while some post-update tasks are performed .. + - Configuring Satellite Service - + Configuring Satellite Service + - Create API Port Binding - + Create API Port Binding + - Please wait while HASS.Agent restarts.. - + Please wait while HASS.Agent restarts.. + - Waiting for previous instance to close.. - + Waiting for previous instance to close.. + - Relaunch HASS.Agent - + Relaunch HASS.Agent + - Please wait while the satellite service is re-installed.. - + Please wait while the satellite service is re-installed.. + - Remove Satellite Service - + Remove Satellite Service + - Install Satellite Service - + Install Satellite Service + - HASS.Agent Reinstall Satellite Service - + HASS.Agent Reinstall Satellite Service + - Please wait while the satellite service is configured.. - + Please wait while the satellite service is configured.. + - Enable Satellite Service - + Enable Satellite Service + - HASS.Agent Configure Satellite Service - + HASS.Agent Configure Satellite Service + - &Close - + &Close + - This is the MQTT topic on which you can publish action commands: - + This is the MQTT topic on which you can publish action commands: + - Copy &to Clipboard - + Copy &to Clipboard + - help and examples - + help and examples + - MQTT Action Topic - + MQTT Action Topic + - &Save - + &Save + - Drag and resize this window to set the size and location of your webview command. - + Drag and resize this window to set the size and location of your webview command. + - &URL - + &URL + - Size - + Size + - Location - + Location + - Show the window's &title bar - + Show the window's &title bar + - &Always show centered in screen - + &Always show centered in screen + - Set window as 'Always on &Top' - + Set window as 'Always on &Top' + - Tip: Press ESCAPE to close a WebView. - + Tip: Press ESCAPE to close a WebView. + - WebView Configuration - + WebView Configuration + - &Remove - + &Remove + - &Modify - + &Modify + - &Add New - + &Add New + - &Store and Activate Commands - + &Store and Activate Commands + - Low Integrity - + Low Integrity + - Action - + Action + - Commands Config - + Commands Config + - &Store Command - + &Store Command + - &Configuration - + &Configuration + - &Name - + &Name + - Description - + Description + - &Run as 'Low Integrity' - + &Run as 'Low Integrity' + - What's this? - + What's this? + - Service - + Service + - agent - + agent + - Configure Command &Parameters - + Configure Command &Parameters + - Command - + Command + - Retrieving entities, please wait.. - + Retrieving entities, please wait.. + - Quick Actions - + Quick Actions + - &Store Quick Actions - + &Store Quick Actions + - &Add New - + &Add New + - &Modify - + &Modify + - &Remove - + &Remove + - &Preview - + &Preview + - Hotkey Enabled - + Hotkey Enabled + - Quick Actions Configuration - + Quick Actions Configuration + - &Store Quick Action - + &Store Quick Action + - &Entity - + &Entity + - Desired &Action - + Desired &Action + - &Description - + &Description + - Retrieving entities, please wait.. - + Retrieving entities, please wait.. + - &hotkey combination - + &hotkey combination + - (optional, will be used instead of entity name) - + (optional, will be used instead of entity name) + - Quick Action - + Quick Action + - &Remove - + &Remove + - &Modify - + &Modify + - &Add New - + &Add New + - &Store && Activate Sensors - + &Store && Activate Sensors + - Refresh - + Refresh + - &Store Sensor - + &Store Sensor + - Selected Type - + Selected Type + - &Name - + &Name + - &Update every - + &Update every + - seconds - + seconds + - Description - + Description + - Agent - + Agent + - Service - + Service + - HASS.Agent only! - + HASS.Agent only! + - &Test - + &Test + - Sensor - + Sensor + - Satellite Service Configuration - + Satellite Service Configuration + - &Close - + &Close + - A Windows-based client for the Home Assistant platform. - + A Windows-based client for the Home Assistant platform. + - This application is open source and completely free, please check the project pages of + This application is open source and completely free, please check the project pages of the used components for their individual licenses: - + - A big 'thank you' to the developers of these projects, who were kind enough to share + A big 'thank you' to the developers of these projects, who were kind enough to share their hard work with the rest of us mere mortals. - + - And of course; thanks to Paulus Shoutsen and the entire team of developers that + And of course; thanks to Paulus Shoutsen and the entire team of developers that created and maintain Home Assistant :-) - + - Created with love by - + Created with love by + - or - + or + - About - + About + - Local API - + Local API + - Media Player - + Media Player + - Tray Icon - + Tray Icon + - &About - + &About + - &Help && Contact - + &Help && Contact + - &Save Configuration - + &Save Configuration + - Close &Without Saving - + Close &Without Saving + - Configuration - + Configuration + - What would you like to do? - + What would you like to do? + - &Restart - + &Restart + - &Hide - + &Hide + - &Exit - + &Exit + - Exit Dialog - + Exit Dialog + - &Close - + &Close + - If you are having trouble with HASS.Agent and require support + If you are having trouble with HASS.Agent and require support with any sensors, commands, or for general support and feedback, there are few ways you can reach us: - + - About - + About + - Home Assistant Forum - + Home Assistant Forum + - GitHub Issues - + GitHub Issues + - Bit of everything, with the addition that other + Bit of everything, with the addition that other HA users can help you out too! - + - Report bugs, post feature requests, see latest changes, etc. - + Report bugs, post feature requests, see latest changes, etc. + - Get help with setting up and using HASS.Agent, + Get help with setting up and using HASS.Agent, report bugs or get involved in general chit-chat! - + - Documentation and Usage Examples - + Documentation and Usage Examples + - Documentation - + Documentation + - Help - + Help + - Manage Satellite Service - + Manage Satellite Service + - Controls - + Controls + - S&atellite Service - + S&atellite Service + - C&onfiguration - + C&onfiguration + - &Quick Actions - + &Quick Actions + - System Status - + System Status + - Satellite Service: - + Satellite Service: + - MQTT: - + MQTT: + - Commands: - + Commands: + - Sensors: - + Sensors: + - Quick Actions: - + Quick Actions: + - Home Assistant API: - + Home Assistant API: + - Local API: - + Local API: + - Check for &Updates - + Check for &Updates + - &Next - + &Next + - &Close - + &Close + - &Previous - + &Previous + - HASS.Agent Onboarding - + HASS.Agent Onboarding + - There's a new release available: - + There's a new release available: + - Release notes - + Release notes + - &Ignore Update - + &Ignore Update + - Release Page - + Release Page + - HASS.Agent Update - + HASS.Agent Update + - WebView - + WebView + - By default HASS.Agent will launch URLs using your default browser. You can also configure + By default HASS.Agent will launch URLs using your default browser. You can also configure a specific browser to be used instead along with launch arguments to run in private mode. - + - Make sure you follow these steps: + Make sure you follow these steps: - Install the HASS.Agent-Notifier and / or HASS.Agent-MediaPlayer integration - Restart Home Assistant -Configure a notifier and / or media_player entity -Restart Home Assistant - + - To learn which entities you have configured and to send quick actions, HASS.Agent uses + To learn which entities you have configured and to send quick actions, HASS.Agent uses Home Assistant's API. Please provide a long-lived access token and the address of your Home Assistant instance. You can get a token in Home Assistant by clicking your profile picture at the bottom-left and navigating to the bottom of the page until you see the 'CREATE TOKEN' button. - + - When a new update is available, HASS.Agent can download the installer and launch it for you. + When a new update is available, HASS.Agent can download the installer and launch it for you. The certificate of the downloaded file will get checked before running,you will still get to review the release notes and manually approve the update. - + - HASS.Agent checks for updates in the background if enabled. + HASS.Agent checks for updates in the background if enabled. You will be sent a push notification if a new update is discovered, letting you know a new version is ready to be installed. Do you want to enable this automatic update checks? - + - You can configure the HASS.Agent to use a specific interpreter such as Perl or Python. + You can configure the HASS.Agent to use a specific interpreter such as Perl or Python. Use the 'custom executor' command to launch this executor. - + - Custom Executor Name - + Custom Executor Name + - HASS.Agent will wait a grace period before notifying you of disconnects from MQTT or HA's API. + HASS.Agent will wait a grace period before notifying you of disconnects from MQTT or HA's API. You can set the amount of seconds to wait in this grace period below. - + - IMPORTANT: if you change this value, HASS.Agent will unpublish all your sensors, commands and force a restart of itself so they can be republished under the new device name. + IMPORTANT: if you change this value, HASS.Agent will unpublish all your sensors, commands and force a restart of itself so they can be republished under the new device name. Your automations and scripts will keep working. - + - The device name is used to identify your machine on Home Assistant. + The device name is used to identify your machine on Home Assistant. It is also used as a prefix for your command/sensor names (this can be changed per entity). - + - The local API is disabled however the media player requires it in order to function. - + The local API is disabled however the media player requires it in order to function. + - Password - + Password + - Username - + Username + - Port - + Port + - IP Address or Hostname - + IP Address or Hostname + - This page contains general configuration settings, for MQTT settings, commands, and sensors, browse the different tabs above. - + This page contains general configuration settings, for MQTT settings, commands, and sensors, browse the different tabs above. + - Auth &ID - + Auth &ID + - Device &Name - + Device &Name + - Tip: Double-click these fields to browse - + Tip: Double-click these fields to browse + - Custom Executor &Binary - + Custom Executor &Binary + - Custom &Executor Name - + Custom &Executor Name + - seconds - + seconds + - Disconnected Grace &Period - + Disconnected Grace &Period + - Version - + Version + - Tip: Double-click to generate random - + Tip: Double-click to generate random + - stored! - + stored! + - You can use the satellite service to run sensors and commands without having to be logged in. Not all types are available, for instance the 'LaunchUrl' command can only be added as a regular command. - + You can use the satellite service to run sensors and commands without having to be logged in. Not all types are available, for instance the 'LaunchUrl' command can only be added as a regular command. + - The keycode you have provided is not a valid number! + The keycode you have provided is not a valid number! Please ensure the keycode field is in focus and press the key you want simulated, the keycode should then be generated for you. - + - HASS.Agent will sanitize your device name to make sure HA will accept it, you can overrule this rule below if you're sure that your name will be accepted as-is. - + HASS.Agent will sanitize your device name to make sure HA will accept it, you can overrule this rule below if you're sure that your name will be accepted as-is. + - Enable Device Name &Sanitation - + Enable Device Name &Sanitation + - You've changed your device's name. + You've changed your device's name. All your sensors and commands will now be unpublished and published again after the HASS.Agent restarts. Don't worry! they'll keep their current names so your automations and scripts will continue to work. Note: You disabled sanitation, so make sure your device name is accepted by Home Assistant. - + - Enable State Notifications - + Enable State Notifications + - HASS.Agent sends notifications when the state of a module changes, you can adjust whether or not you want to receive these notifications below. - + HASS.Agent sends notifications when the state of a module changes, you can adjust whether or not you want to receive these notifications below. + - Error trying to bind the API to port {0}. + Error trying to bind the API to port {0}. Make sure no other instance of HASS.Agent is running and the port is available and registered. - + - Printers - + Printers + - No URL has been set! Please configure the webview through Configuration -> Tray Icon. - + No URL has been set! Please configure the webview through Configuration -> Tray Icon. + - MonitorSleep - + MonitorSleep + - MonitorWake - + MonitorWake + - PowerOn - + PowerOn + - PowerOff - + PowerOff + - Dimmed - + Dimmed + - Unknown - + Unknown + - MonitorPowerState - + MonitorPowerState + - PowershellSensor - + PowershellSensor + - powershell command or script - + powershell command or script + - Test Command/Script - + Test Command/Script + - Test succesfully executed, result value: + Test succesfully executed, result value: {0} - + - Please enter a command or script! - + Please enter a command or script! + - The test failed to execute: + The test failed to execute: {0} Do you want to open the logs folder? - + - SetVolume - + SetVolume + - Please enter a value between 0-100 as the desired volume level! - + Please enter a value between 0-100 as the desired volume level! + - If you don't enter a volume value, you can only use this entity with an 'action' value through Home Assistant. Running it as-is won't do anything. + If you don't enter a volume value, you can only use this entity with an 'action' value through Home Assistant. Running it as-is won't do anything. Are you sure you want this? - + - Maximized - + Maximized + - Minimized - + Minimized + - Normal - + Normal + - Unknown - + Unknown + - Hidden - + Hidden + - WindowState - + WindowState + - Puts all monitors in sleep (low power) mode. - + Puts all monitors in sleep (low power) mode. + - Tries to wake up all monitors by simulating a 'arrow up' keypress. - + Tries to wake up all monitors by simulating a 'arrow up' keypress. + - Sets the volume of the current default audiodevice to the specified level. - + Sets the volume of the current default audiodevice to the specified level. + - Returns your current latitude, longitude and altitude as a comma-seperated value. + Returns your current latitude, longitude and altitude as a comma-seperated value. Make sure Windows' location services are enabled! Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. - + - Provides the last monitor power state change: + Provides the last monitor power state change: Dimmed, PowerOff, PowerOn and Unkown. - + - Returns the result of the provided Powershell command or script. + Returns the result of the provided Powershell command or script. Converts the outcome to text. - + - Provides information about all installed printers and their queues. - + Provides information about all installed printers and their queues. + - Provides the current state of the process' window: + Provides the current state of the process' window: Hidden, Maximized, Minimized, Normal and Unknown. - + - Testing.. - + Testing.. + - WebcamProcess - + WebcamProcess + - MicrophoneProcess - + MicrophoneProcess + - Provides the name of the process that's currently using the webcam. + Provides the name of the process that's currently using the webcam. Note: if used in the satellite service, it won't detect userspace applications. - + - Provides the name of the process that's currently using the microphone. + Provides the name of the process that's currently using the microphone. Note: if used in the satellite service, it won't detect userspace applications. - + - BluetoothDevices - + BluetoothDevices + - BluetoothLeDevices - + BluetoothLeDevices + - Provides a sensor with the amount of bluetooth devices found. + Provides a sensor with the amount of bluetooth devices found. The devices and their connected state are added as attributes. - + - Provides a sensors with the amount of bluetooth LE devices found. + Provides a sensors with the amount of bluetooth LE devices found. The devices and their connected state are added as attributes. Only shows devices that were seen since the last report, ie. when the sensor publishes, the list clears. - + - The service is currently stopped and cannot be configured. + The service is currently stopped and cannot be configured. Please start the service first in order to configure it. - + - The name you provided contains unsupported characters and won't work. The suggested version is: + The name you provided contains unsupported characters and won't work. The suggested version is: {0} Do you want to use this version? - + - The name you provided contains unsupported characters and won't work. The suggested version is: + The name you provided contains unsupported characters and won't work. The suggested version is: {0} Do you want to use this version? - + - To learn which entities you have configured and to send quick actions, HASS.Agent uses + To learn which entities you have configured and to send quick actions, HASS.Agent uses Home Assistant's API. Please provide a long-lived access token and the address of your Home Assistant instance. You can get a token in Home Assistant by clicking your profile picture at the bottom-left and navigating to the bottom of the page until you see the 'CREATE TOKEN' button. - + - HASS.Agent has its own local API, so Home Assistant can send requests (for instance to send a notification). You can configure it globally here, and afterwards you can configure the dependent sections (currently notifications and mediaplayer). + HASS.Agent has its own local API, so Home Assistant can send requests (for instance to send a notification). You can configure it globally here, and afterwards you can configure the dependent sections (currently notifications and mediaplayer). Note: this is not required for the new integration to function. Only enable and use it if you don't use MQTT. - + - If something is not working, make sure you try the following steps: + If something is not working, make sure you try the following steps: - Install the HASS.Agent integration - Restart Home Assistant - Make sure HASS.Agent is active with MQTT enabled! - Your device should get detected and added as an entity automatically - Optionally: manually add it using the local API - + - HASS.Agent can act as a media player for Home Assistant, so you'll be able to see and control any media that's playing, and send text-to-speech. If you have MQTT enabled, your device will automatically get added. Otherwise, manually configure the integration to use the local API. - + HASS.Agent can act as a media player for Home Assistant, so you'll be able to see and control any media that's playing, and send text-to-speech. If you have MQTT enabled, your device will automatically get added. Otherwise, manually configure the integration to use the local API. + - both the local API and MQTT are disabled, but the integration needs at least one for it to work - + both the local API and MQTT are disabled, but the integration needs at least one for it to work + - Commands and sensors use MQTT, as well as notifications and media player functions when using the new integration. + Commands and sensors use MQTT, as well as notifications and media player functions when using the new integration. Please provide credentials for your broker, if you're using the HA Mosquitto addon, you can probably use the preset address. Note: these settings (excluding the Client ID) will also be applied to the satellite service. - + - Enable MQTT - + Enable MQTT + - If MQTT is not enabled, commands and sensors will not work! - + If MQTT is not enabled, commands and sensors will not work! + - If something is not working, make sure you try the following steps: + If something is not working, make sure you try the following steps: - Install the HASS.Agent integration - Restart Home Assistant - Make sure HASS.Agent is active with MQTT enabled! - Your device should get detected and added as an entity automatically - Optionally: manually add it using the local API - + - HASS.Agent can receive notifications from Home Assistant, using text, images and actions. If you have MQTT enabled, your device will automatically get added. Otherwise, manually configure the integration to use the local API. - + HASS.Agent can receive notifications from Home Assistant, using text, images and actions. If you have MQTT enabled, your device will automatically get added. Otherwise, manually configure the integration to use the local API. + - both the local API and MQTT are disabled, but the integration needs at least one for it to work - + both the local API and MQTT are disabled, but the integration needs at least one for it to work + - The satellite service allows you to run sensors and commands even when no user's logged in. + The satellite service allows you to run sensors and commands even when no user's logged in. Use the 'satellite service' button on the main window to manage it. - + - If you want to manage the service (add commands and sensor, change settings) you can do so here, or by using the 'satellite service' button on the main window. - + If you want to manage the service (add commands and sensor, change settings) you can do so here, or by using the 'satellite service' button on the main window. + - &Manage Service - + &Manage Service + - Show default menu on mouse left-click - + Show default menu on mouse left-click + - Commands and sensors are sent through MQTT. The notifications- and media player integration also make use of them. + Commands and sensors are sent through MQTT. The notifications- and media player integration also make use of them. Tip: if you're using the HA addon, you can probably use the preset address - just provide credentials. - + - Enable MQTT - + Enable MQTT + - HASS.Agent-Integration GitHub Page - + HASS.Agent-Integration GitHub Page + - Enable &Media Player (including text-to-speech) - + Enable &Media Player (including text-to-speech) + - Enable &Notifications - + Enable &Notifications + - Developing and maintaining this tool (and everything that surrounds it) takes up a lot of time. Like most developers, I run on caffeïne - so if you can spare it, a cup of coffee is always very much appreciated! - + Developing and maintaining this tool (and everything that surrounds it) takes up a lot of time. Like most developers, I run on caffeïne - so if you can spare it, a cup of coffee is always very much appreciated! + - There's a lot more to tinker with, so make sure you take a look at the Configuration Wwindow! + There's a lot more to tinker with, so make sure you take a look at the Configuration Wwindow! Thank you for using HASS.Agent, hopefully it'll be useful for you :-) - + - HASS.Agent will now restart to apply your configuration changes. - + HASS.Agent will now restart to apply your configuration changes. + - Yay, done! - + Yay, done! + - Tip: Other donation methods are available on the About Window. - + Tip: Other donation methods are available on the About Window. + - HASS.Agent Post Update - + HASS.Agent Post Update + - Command - + Command + - Like this tool? Support us (read: keep us awake) by buying a cup of coffee: - + Like this tool? Support us (read: keep us awake) by buying a cup of coffee: + - HASS.Agent is completely free, and will always stay that way without restrictions! + HASS.Agent is completely free, and will always stay that way without restrictions! However, developing and maintaining this tool (and everything that surrounds it, like support and the docs) takes up a lot of time. Like most developers, I run on caffeïne - so if you can spare it, a cup of coffee is always very much appreciated! - + - &Close - + &Close + - I already donated, hide the button on the main window. - + I already donated, hide the button on the main window. + - Donate - + Donate + - Check for Updates - + Check for Updates + - The API token you have provided doesn't appear to be valid, please ensure you selected the entire token (Don't use CTRL + A or double-click). A valid API key contains three sections, separated by two dots. + The API token you have provided doesn't appear to be valid, please ensure you selected the entire token (Don't use CTRL + A or double-click). A valid API key contains three sections, separated by two dots. Are you sure you want to use this key anyway? - + - The URI you have provided does not appear to be valid, a valid URI may look like either of the following: + The URI you have provided does not appear to be valid, a valid URI may look like either of the following: - http://homeassistant.local:8123 - http://192.168.0.1:8123 Are you sure you want to use this URI anyway? - + - The API token you have provided doesn't appear to be valid, please ensure you selected the entire token (Don't use CTRL + A or double-click). A valid API key contains three sections, separated by two dots. + The API token you have provided doesn't appear to be valid, please ensure you selected the entire token (Don't use CTRL + A or double-click). A valid API key contains three sections, separated by two dots. Are you sure you want to use this key anyway? - + - The URI you have provided does not appear to be valid, a valid URI may look like either of the following: + The URI you have provided does not appear to be valid, a valid URI may look like either of the following: - http://homeassistant.local:8123 - http://192.168.0.1:8123 Are you sure you want to use this URI anyway? - + - Your Home Assistant API token doesn't look right. Make sure you selected the entire token (don't use CTRL+A or doubleclick). + Your Home Assistant API token doesn't look right. Make sure you selected the entire token (don't use CTRL+A or doubleclick). It should contain three sections (seperated by two dots). Are you sure you want to use it like this? - + - Your Home Assistant URI doesn't look right. It should look something like 'http://homeassistant.local:8123' or 'https://192.168.0.1:8123'. + Your Home Assistant URI doesn't look right. It should look something like 'http://homeassistant.local:8123' or 'https://192.168.0.1:8123'. Are you sure you want to use it like this? - + - Your MQTT broker URI doesn't look right. It should look something like 'homeassistant.local' or '192.168.0.1'. + Your MQTT broker URI doesn't look right. It should look something like 'homeassistant.local' or '192.168.0.1'. Are you sure you want to use it like this? - + - Microsoft's WebView2 runtime isn't found on your machine. Usually this is handled by the installer, but you can install it manually. + Microsoft's WebView2 runtime isn't found on your machine. Usually this is handled by the installer, but you can install it manually. Do you want to download the runtime installer? - + - Something went wrong while initializing the WebView! Please check your logs and open a GitHub issue for further assistance. - + Something went wrong while initializing the WebView! Please check your logs and open a GitHub issue for further assistance. + - unable to open Service Manager - + unable to open Service Manager + - unable to open service - + unable to open service + - Error configuring startup mode, please check the logs for more information. - + Error configuring startup mode, please check the logs for more information. + - Error setting startup mode, please check the logs for more information. - + Error setting startup mode, please check the logs for more information. + - Timeout expired - + Timeout expired + - Fatal error, please check logs for information! - + Fatal error, please check logs for information! + - unknown reason - + unknown reason + - HassAgentSatelliteServiceStarted - + HassAgentSatelliteServiceStarted + - HassAgentStarted - + HassAgentStarted + - Selected Type - + Selected Type + - HASS.Agent only! - + HASS.Agent only! + - &Entity Type - + &Entity Type + - Show MQTT Action Topic - + Show MQTT Action Topic + - Action - + Action + - Multivalue - + Multivalue + - domain - + domain + InternalDeviceSensor @@ -3214,4 +3234,28 @@ Do you want to download the runtime installer? SwitchDesktop + + SetAudioOutputCommand + + + Screenshot + + + SetAudioInputCommand + + + TrayWebView + + + Trigger + + + Press + + + Button + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx index e448d6b5..9dadee0b 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.ru.resx @@ -3296,4 +3296,31 @@ Home Assistant. Активдесктоп + + Установить громкость приложения + + + Установить команду вывода звука + + + Скриншот + + + Установить команду аудиовхода + + + TrayWebView + + + Вызывать + + + Button + + + Нажимать + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx index d0acb0a5..3e4b4bd0 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.sl.resx @@ -3377,4 +3377,31 @@ Ali želite prenesti runtime installer? ActiveDesktop + + Nastavite glasnost aplikacije + + + Nastavite ukaz za avdio izhod + + + Posnetek zaslona + + + Nastavite ukaz za zvočni vhod + + + TrayWebView + + + Sprožilec + + + Gumb + + + Pritisnite + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx index 3a6a5395..8d211787 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent.Shared/Resources/Localization/Languages.tr.resx @@ -2835,4 +2835,31 @@ Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini ku AktifMasaüstü + + Uygulama Sesini Ayarla + + + Ses Çıkışı Komutunu Ayarla + + + Ekran görüntüsü + + + Ses Giriş Komutunu Ayarla + + + TepsiWebGörünümü + + + Tetiklemek + + + Buton + + + Basmak + + + DeviceLocationTracker + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent.Shared/Variables.cs b/src/HASS.Agent/HASS.Agent.Shared/Variables.cs index 1c73153d..bcd3ff7a 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Variables.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Variables.cs @@ -1,5 +1,4 @@ using System; -using CoreAudio; using HASS.Agent.Shared.Mqtt; namespace HASS.Agent.Shared @@ -14,7 +13,6 @@ internal class Variables /// /// public references /// - internal static MMDeviceEnumerator AudioDeviceEnumerator { get; } = new MMDeviceEnumerator(Guid.NewGuid()); internal static Random Rnd { get; } = new Random(); /// diff --git a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs index 99057184..811b574d 100644 --- a/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Commands/CommandsManager.cs @@ -17,6 +17,8 @@ internal static class CommandsManager private static bool _active = true; private static bool _pause; + private static bool _discoveryPublished = false; + private static DateTime _lastAutoDiscoPublish = DateTime.MinValue; /// @@ -66,6 +68,8 @@ internal static async Task UnpublishAllCommands() await command.UnPublishAutoDiscoveryConfigAsync(); await Variables.MqttManager.UnsubscribeAsync(command); } + + _discoveryPublished = false; } /// @@ -103,18 +107,24 @@ private static async void Process() firstRun = false; + // publish availability & autodiscovery every 30 sec if ((DateTime.Now - _lastAutoDiscoPublish).TotalSeconds > 30) { await Variables.MqttManager.AnnounceAvailabilityAsync(); - foreach (var command in Variables.Commands - .TakeWhile(_ => !_pause) - .TakeWhile(_ => _active)) + if (!_discoveryPublished) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) - continue; + foreach (var command in Variables.Commands + .TakeWhile(_ => !_pause) + .TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) + continue; + + await command.PublishAutoDiscoveryConfigAsync(); + } - await command.PublishAutoDiscoveryConfigAsync(); + _discoveryPublished = true; } if (!subscribed) @@ -413,7 +423,7 @@ internal static void LoadCommandInfo() commandInfoCard = new CommandInfoCard(CommandType.MultipleKeysCommand, Languages.CommandsManager_MultipleKeysCommandDescription, - true, false, false); + true, false, true); CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); @@ -455,10 +465,10 @@ internal static void LoadCommandInfo() Languages.CommandsManager_SwitchDesktopCommandDescription, true, false, true); - // ================================= - CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + // ================================= + commandInfoCard = new CommandInfoCard(CommandType.SetVolumeCommand, Languages.CommandsManager_SetVolumeCommandDescription, true, true, true); @@ -467,6 +477,30 @@ internal static void LoadCommandInfo() // ================================= + commandInfoCard = new CommandInfoCard(CommandType.SetApplicationVolumeCommand, + Languages.CommandsManager_SetApplicationVolumeCommandDescription, + true, false, true); + + CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + + // ================================= + + commandInfoCard = new CommandInfoCard(CommandType.SetAudioOutputCommand, + Languages.CommandsManager_SetAudioOutputCommandDescription, + true, false, true); + + CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + + // ================================= + + commandInfoCard = new CommandInfoCard(CommandType.SetAudioInputCommand, + Languages.CommandsManager_SetAudioInputCommandDescription, + true, false, true); + + CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + + // ================================= + commandInfoCard = new CommandInfoCard(CommandType.ShutdownCommand, Languages.CommandsManager_ShutdownCommandDescription, true, true, false); @@ -491,6 +525,14 @@ internal static void LoadCommandInfo() // ================================= + commandInfoCard = new CommandInfoCard(CommandType.TrayWebViewCommand, + Languages.CommandsManager_TrayWebViewCommandDescription, + true, false, false); + + CommandInfoCards.Add(commandInfoCard.CommandType, commandInfoCard); + + // ================================= + commandInfoCard = new CommandInfoCard(CommandType.RadioCommand, Languages.CommandsManager_RadioCommandDescription, true, false, true); diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.de.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.de.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.de.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.en.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.en.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.en.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.es.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.es.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.es.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.fr.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.fr.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.fr.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.nl.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.nl.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.nl.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pl.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pl.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pl.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pt-br.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pt-br.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.pt-br.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.ru.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.ru.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.ru.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.sl.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.sl.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.sl.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.tr.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.tr.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigGeneral - Copy.tr.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.Designer.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.Designer.cs index 48c9bfbb..670ddf22 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.Designer.cs @@ -56,6 +56,7 @@ private void InitializeComponent() this.LblClientId = new System.Windows.Forms.Label(); this.NumMqttPort = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); this.PbShow = new System.Windows.Forms.PictureBox(); + this.CbIgnoreGracePeriod = new System.Windows.Forms.CheckBox(); this.CbEnableMqtt = new System.Windows.Forms.CheckBox(); this.LblMqttDisabledWarning = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.NumMqttPort)).BeginInit(); @@ -460,51 +461,68 @@ private void InitializeComponent() this.LblMqttDisabledWarning.Text = Languages.ConfigMqtt_LblMqttDisabledWarning; this.LblMqttDisabledWarning.Visible = false; // + // CbIgnoreGracePeriod + // + this.CbIgnoreGracePeriod.AccessibleDescription = "Ignore grace period after waking up from hibernation."; + this.CbIgnoreGracePeriod.AccessibleName = "Ignore grace period"; + this.CbIgnoreGracePeriod.AccessibleRole = AccessibleRole.CheckButton; + this.CbIgnoreGracePeriod.AutoSize = true; + this.CbIgnoreGracePeriod.Checked = false; + this.CbIgnoreGracePeriod.CheckState = CheckState.Unchecked; + this.CbIgnoreGracePeriod.Font = new Font("Segoe UI", 10F); + this.CbIgnoreGracePeriod.Location = new Point(59, 612); + this.CbIgnoreGracePeriod.Name = "CbIgnoreGracePeriod"; + this.CbIgnoreGracePeriod.Size = new Size(354, 23); + this.CbIgnoreGracePeriod.TabIndex = 103; + this.CbIgnoreGracePeriod.Text = Languages.ConfigMqtt_CbIgnoreGracePeriod; + this.CbIgnoreGracePeriod.UseVisualStyleBackColor = true; + this.CbIgnoreGracePeriod.CheckedChanged += new System.EventHandler(this.CbIgnoreGracePeriod_CheckedChanged); + // // ConfigMqtt // this.AccessibleDescription = "Panel containing the MQTT client configuration."; this.AccessibleName = "MQTT"; - this.AccessibleRole = System.Windows.Forms.AccessibleRole.Pane; - this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48))))); - this.Controls.Add(this.LblMqttDisabledWarning); - this.Controls.Add(this.CbEnableMqtt); - this.Controls.Add(this.PbShow); - this.Controls.Add(this.NumMqttPort); - this.Controls.Add(this.LblTip2); - this.Controls.Add(this.TbMqttClientId); - this.Controls.Add(this.LblClientId); - this.Controls.Add(this.LblTip3); - this.Controls.Add(this.TbMqttClientCertificate); - this.Controls.Add(this.LblClientCert); - this.Controls.Add(this.TbMqttRootCertificate); - this.Controls.Add(this.LblRootCert); - this.Controls.Add(this.CbUseRetainFlag); - this.Controls.Add(this.CbAllowUntrustedCertificates); - this.Controls.Add(this.BtnMqttClearConfig); - this.Controls.Add(this.LblTip1); - this.Controls.Add(this.LblInfo1); - this.Controls.Add(this.TbMqttDiscoveryPrefix); - this.Controls.Add(this.LblDiscoPrefix); - this.Controls.Add(this.TbMqttPassword); - this.Controls.Add(this.TbMqttUsername); - this.Controls.Add(this.TbMqttAddress); - this.Controls.Add(this.CbMqttTls); - this.Controls.Add(this.LblBrokerPassword); - this.Controls.Add(this.LblBrokerUsername); - this.Controls.Add(this.LblBrokerPort); - this.Controls.Add(this.LblBrokerIp); - this.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.Margin = new System.Windows.Forms.Padding(4); + this.AccessibleRole = AccessibleRole.Pane; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = AutoScaleMode.Dpi; + this.BackColor = Color.FromArgb(45, 45, 48); + this.Controls.Add(CbIgnoreGracePeriod); + this.Controls.Add(LblMqttDisabledWarning); + this.Controls.Add(CbEnableMqtt); + this.Controls.Add(PbShow); + this.Controls.Add(NumMqttPort); + this.Controls.Add(LblTip2); + this.Controls.Add(TbMqttClientId); + this.Controls.Add(LblClientId); + this.Controls.Add(LblTip3); + this.Controls.Add(TbMqttClientCertificate); + this.Controls.Add(LblClientCert); + this.Controls.Add(TbMqttRootCertificate); + this.Controls.Add(LblRootCert); + this.Controls.Add(CbUseRetainFlag); + this.Controls.Add(CbAllowUntrustedCertificates); + this.Controls.Add(BtnMqttClearConfig); + this.Controls.Add(LblTip1); + this.Controls.Add(LblInfo1); + this.Controls.Add(TbMqttDiscoveryPrefix); + this.Controls.Add(LblDiscoPrefix); + this.Controls.Add(TbMqttPassword); + this.Controls.Add(TbMqttUsername); + this.Controls.Add(TbMqttAddress); + this.Controls.Add(CbMqttTls); + this.Controls.Add(LblBrokerPassword); + this.Controls.Add(LblBrokerUsername); + this.Controls.Add(LblBrokerPort); + this.Controls.Add(LblBrokerIp); + this.ForeColor = Color.FromArgb(241, 241, 241); + this.Margin = new Padding(4); this.Name = "ConfigMqtt"; - this.Size = new System.Drawing.Size(700, 614); - this.Load += new System.EventHandler(this.ConfigMqtt_Load); - ((System.ComponentModel.ISupportInitialize)(this.NumMqttPort)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.PbShow)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - + this.Size = new Size(700, 658); + this.Load += ConfigMqtt_Load; + ((System.ComponentModel.ISupportInitialize)NumMqttPort).EndInit(); + ((System.ComponentModel.ISupportInitialize)PbShow).EndInit(); + ResumeLayout(false); + PerformLayout(); } #endregion @@ -536,5 +554,6 @@ private void InitializeComponent() private PictureBox PbShow; internal CheckBox CbEnableMqtt; private Label LblMqttDisabledWarning; + internal CheckBox CbIgnoreGracePeriod; } } diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.cs index c39635d7..83354e26 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.cs +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.cs @@ -60,5 +60,10 @@ private void CbEnableMqtt_CheckedChanged(object sender, EventArgs e) { LblMqttDisabledWarning.Visible = CbEnableMqtt.CheckState != CheckState.Checked; } + + private void CbIgnoreGracePeriod_CheckedChanged(object sender, EventArgs e) + { + + } } } diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.resx index c037b6da..1435c3aa 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.resx +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigMqtt.resx @@ -1,17 +1,17 @@  - @@ -117,11 +117,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Commands and sensors use MQTT, as well as notifications and media player functions when using the new integration. + +Please provide credentials for your broker, if you're using the HA Mosquitto addon, you can probably use the preset address. + +Note: these settings (excluding the Client ID) will also be applied to the satellite service. + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wAAADsABataJCQAAAZZJREFUSEvtk61TAlEUxTcQDAajwWAgGgwG/gCCgUAwGA0GgsFIZMZANBgMBgOR + vgAADr4B6kKxwAAAAZZJREFUSEvtk61TAlEUxTcQDAajwWAgGgwG/gCCgUAwGA0GgsFIZMZANBgMBgOR YDQYNliAhdkgMwQCwUAgGAgGgv7Oeh/zdmdRscqZObPv3Y9z37v3bbDBBgkGg0Gx1+uV+/3+mdjtdo/Z H5j7bwjDcAuxOozhRx6jKBrzvYrjeMfSfgdOd0rixBf7jhSawRqHKphEPnRtEp6yAmswgiWTSwNHlVPM XTDrB/js9qtIzAjeebYF+5rJfoGTX3oBUw1RdtahZ88lYmOLLWnt7DyMa9mz4nGn09lLHEBBnm8V2xYe diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.Designer.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.Designer.cs new file mode 100644 index 00000000..c353bf48 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.Designer.cs @@ -0,0 +1,144 @@ +using HASS.Agent.Resources.Localization; + +namespace HASS.Agent.Controls.Configuration +{ + partial class ConfigNFC + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + var resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigNFC)); + LblInfo1 = new Label(); + LblSelectedScanner = new Label(); + CbNfcScanner = new ComboBox(); + PbLine1 = new PictureBox(); + CbEnableNfc = new CheckBox(); + ((System.ComponentModel.ISupportInitialize)PbLine1).BeginInit(); + SuspendLayout(); + // + // LblInfo1 + // + LblInfo1.AccessibleDescription = "NFC scanning ability description"; + LblInfo1.AccessibleName = "NFC information"; + LblInfo1.AccessibleRole = AccessibleRole.StaticText; + LblInfo1.AutoEllipsis = true; + LblInfo1.Font = new Font("Segoe UI", 10F); + LblInfo1.Location = new Point(70, 36); + LblInfo1.Name = "LblInfo1"; + LblInfo1.Size = new Size(583, 74); + LblInfo1.TabIndex = 57; + LblInfo1.Text = resources.GetString("LblInfo1.Text"); + // + // LblSelectedScanner + // + LblSelectedScanner.AccessibleDescription = "NFC Scanner"; + LblSelectedScanner.AccessibleName = "NFC Scanner"; + LblSelectedScanner.AccessibleRole = AccessibleRole.StaticText; + LblSelectedScanner.AutoSize = true; + LblSelectedScanner.Font = new Font("Segoe UI", 10F); + LblSelectedScanner.Location = new Point(70, 188); + LblSelectedScanner.Name = "LblSelectedScanner"; + LblSelectedScanner.Size = new Size(87, 19); + LblSelectedScanner.TabIndex = 65; + LblSelectedScanner.Text = "NFC &Scanner"; + // + // CbNfcScanner + // + CbNfcScanner.AccessibleDescription = "Selected NFC scanner"; + CbNfcScanner.AccessibleName = "Selected NFC scanner"; + CbNfcScanner.AccessibleRole = AccessibleRole.DropList; + CbNfcScanner.BackColor = Color.FromArgb(63, 63, 70); + CbNfcScanner.DrawMode = DrawMode.OwnerDrawFixed; + CbNfcScanner.DropDownHeight = 300; + CbNfcScanner.DropDownStyle = ComboBoxStyle.DropDownList; + CbNfcScanner.Font = new Font("Segoe UI", 9.75F); + CbNfcScanner.ForeColor = Color.FromArgb(241, 241, 241); + CbNfcScanner.FormattingEnabled = true; + CbNfcScanner.IntegralHeight = false; + CbNfcScanner.Location = new Point(73, 210); + CbNfcScanner.Name = "CbNfcScanner"; + CbNfcScanner.Size = new Size(358, 26); + CbNfcScanner.Sorted = true; + CbNfcScanner.TabIndex = 0; + // + // PbLine1 + // + PbLine1.AccessibleDescription = "Seperator line."; + PbLine1.AccessibleName = "Seperator"; + PbLine1.AccessibleRole = AccessibleRole.Graphic; + PbLine1.Image = Properties.Resources.line; + PbLine1.Location = new Point(73, 130); + PbLine1.Name = "PbLine1"; + PbLine1.Size = new Size(576, 1); + PbLine1.SizeMode = PictureBoxSizeMode.AutoSize; + PbLine1.TabIndex = 67; + PbLine1.TabStop = false; + // + // CbEnableNfc + // + CbEnableNfc.AccessibleDescription = "Enable NFC tag scanning"; + CbEnableNfc.AccessibleName = "Enable NFC"; + CbEnableNfc.AccessibleRole = AccessibleRole.CheckButton; + CbEnableNfc.AutoSize = true; + CbEnableNfc.Font = new Font("Segoe UI", 10F); + CbEnableNfc.Location = new Point(73, 150); + CbEnableNfc.Name = "CbEnableNfc"; + CbEnableNfc.Size = new Size(180, 23); + CbEnableNfc.TabIndex = 72; + CbEnableNfc.Text = "Enable &NFC tag scanning"; + CbEnableNfc.UseVisualStyleBackColor = true; + // + // ConfigNFC + // + AccessibleDescription = "Panel containing general configuration options."; + AccessibleName = "General configuration"; + AccessibleRole = AccessibleRole.Pane; + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + BackColor = Color.FromArgb(45, 45, 48); + Controls.Add(CbEnableNfc); + Controls.Add(PbLine1); + Controls.Add(CbNfcScanner); + Controls.Add(LblSelectedScanner); + Controls.Add(LblInfo1); + ForeColor = Color.FromArgb(241, 241, 241); + Margin = new Padding(4); + Name = "ConfigNFC"; + Size = new Size(700, 275); + Load += ConfigNFC_Load; + ((System.ComponentModel.ISupportInitialize)PbLine1).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + private System.Windows.Forms.Label LblInfo1; + private Label LblSelectedScanner; + internal ComboBox CbNfcScanner; + private PictureBox PbLine1; + internal CheckBox CbEnableNfc; + } +} diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.cs new file mode 100644 index 00000000..f3ffd70d --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.cs @@ -0,0 +1,29 @@ +using System.Globalization; +using HASS.Agent.Functions; +using HASS.Agent.Managers; +using HASS.Agent.Resources.Localization; +using Syncfusion.Windows.Forms; + +namespace HASS.Agent.Controls.Configuration; + +public partial class ConfigNFC : UserControl +{ + public ConfigNFC() + { + InitializeComponent(); + + BindComboBoxTheme(); + } + + private void BindComboBoxTheme() => CbNfcScanner.DrawItem += ComboBoxTheme.DrawItem; + + private void ConfigNFC_Load(object sender, EventArgs e) + { + CbNfcScanner.Items.Add(Languages.SensorsMod_None); + + foreach (var nfcReaderName in RadioManager.AvailableNFCReaderNames) + CbNfcScanner.Items.Add(nfcReaderName); + + CbNfcScanner.SelectedItem = string.IsNullOrWhiteSpace(Variables.AppSettings.NfcSelectedScanner) ? Languages.SensorsMod_None : Variables.AppSettings.NfcSelectedScanner; + } +} diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.resx new file mode 100644 index 00000000..6b83a3b0 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigNFC.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + If your device is equipped with NFC scanner, you can enable the scanning functionality and select one of the available scanners. +This functionality works like the Home Assistant Compaion App NFC tag scanning. + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.Designer.cs b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.Designer.cs index 53fdff2f..8fec92e9 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.Designer.cs @@ -30,320 +30,318 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.LblInfo1 = new System.Windows.Forms.Label(); - this.CbDefaultMenu = new System.Windows.Forms.CheckBox(); - this.CbShowWebView = new System.Windows.Forms.CheckBox(); - this.TbWebViewUrl = new System.Windows.Forms.TextBox(); - this.LblWebViewUrl = new System.Windows.Forms.Label(); - this.LblX = new System.Windows.Forms.Label(); - this.LblWebViewSize = new System.Windows.Forms.Label(); - this.BtnShowWebViewPreview = new Syncfusion.WinForms.Controls.SfButton(); - this.NumWebViewWidth = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); - this.NumWebViewHeight = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); - this.BtnWebViewReset = new Syncfusion.WinForms.Controls.SfButton(); - this.CbWebViewKeepLoaded = new System.Windows.Forms.CheckBox(); - this.LblInfo2 = new System.Windows.Forms.Label(); - this.CbWebViewShowMenuOnLeftClick = new System.Windows.Forms.CheckBox(); - ((System.ComponentModel.ISupportInitialize)(this.NumWebViewWidth)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.NumWebViewHeight)).BeginInit(); - this.SuspendLayout(); + LblInfo1 = new Label(); + CbUseModernIcon = new CheckBox(); + CbDefaultMenu = new CheckBox(); + CbShowWebView = new CheckBox(); + TbWebViewUrl = new TextBox(); + LblWebViewUrl = new Label(); + LblX = new Label(); + LblWebViewSize = new Label(); + BtnShowWebViewPreview = new Syncfusion.WinForms.Controls.SfButton(); + NumWebViewWidth = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); + NumWebViewHeight = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); + BtnWebViewReset = new Syncfusion.WinForms.Controls.SfButton(); + CbWebViewKeepLoaded = new CheckBox(); + LblInfo2 = new Label(); + CbWebViewShowMenuOnLeftClick = new CheckBox(); + ((System.ComponentModel.ISupportInitialize)NumWebViewWidth).BeginInit(); + ((System.ComponentModel.ISupportInitialize)NumWebViewHeight).BeginInit(); + SuspendLayout(); // // LblInfo1 // - this.LblInfo1.AccessibleDescription = "Tray icon information."; - this.LblInfo1.AccessibleName = "Information"; - this.LblInfo1.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblInfo1.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblInfo1.Location = new System.Drawing.Point(70, 36); - this.LblInfo1.Name = "LblInfo1"; - this.LblInfo1.Size = new System.Drawing.Size(541, 78); - this.LblInfo1.TabIndex = 31; - this.LblInfo1.Text = Languages.ConfigTrayIcon_LblInfo1; + LblInfo1.AccessibleDescription = "Tray icon information."; + LblInfo1.AccessibleName = "Information"; + LblInfo1.AccessibleRole = AccessibleRole.StaticText; + LblInfo1.Font = new Font("Segoe UI", 10F); + LblInfo1.Location = new Point(70, 102); + LblInfo1.Name = "LblInfo1"; + LblInfo1.Size = new Size(541, 36); + LblInfo1.TabIndex = 31; + LblInfo1.Text = "Control the behaviour of the tray icon when it is right-clicked."; + // + // CbUseModernIcon + // + CbUseModernIcon.AccessibleDescription = "If enabled, modern white and transparent icon will be used"; + CbUseModernIcon.AccessibleName = "Use modern icon"; + CbUseModernIcon.AccessibleRole = AccessibleRole.CheckButton; + CbUseModernIcon.AutoSize = true; + CbUseModernIcon.Font = new Font("Segoe UI", 10F); + CbUseModernIcon.Location = new Point(70, 42); + CbUseModernIcon.Name = "CbUseModernIcon"; + CbUseModernIcon.Size = new Size(160, 23); + CbUseModernIcon.TabIndex = 50; + CbUseModernIcon.Text = Languages.ConfigTrayIcon_CbUseModernIcon; + CbUseModernIcon.UseVisualStyleBackColor = true; // // CbDefaultMenu // - this.CbDefaultMenu.AccessibleDescription = "If enabled, right clicking the system tray icon will show the default menu."; - this.CbDefaultMenu.AccessibleName = "Show default menu"; - this.CbDefaultMenu.AccessibleRole = System.Windows.Forms.AccessibleRole.CheckButton; - this.CbDefaultMenu.AutoSize = true; - this.CbDefaultMenu.Checked = true; - this.CbDefaultMenu.CheckState = System.Windows.Forms.CheckState.Checked; - this.CbDefaultMenu.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.CbDefaultMenu.Location = new System.Drawing.Point(70, 117); - this.CbDefaultMenu.Name = "CbDefaultMenu"; - this.CbDefaultMenu.Size = new System.Drawing.Size(145, 23); - this.CbDefaultMenu.TabIndex = 0; - this.CbDefaultMenu.Text = global::HASS.Agent.Resources.Localization.Languages.ConfigTrayIcon_CbDefaultMenu; - this.CbDefaultMenu.UseVisualStyleBackColor = true; - this.CbDefaultMenu.CheckedChanged += new System.EventHandler(this.CbDefaultMenu_CheckedChanged); + CbDefaultMenu.AccessibleDescription = "If enabled, right clicking the system tray icon will show the default menu."; + CbDefaultMenu.AccessibleName = "Show default menu"; + CbDefaultMenu.AccessibleRole = AccessibleRole.CheckButton; + CbDefaultMenu.AutoSize = true; + CbDefaultMenu.Checked = true; + CbDefaultMenu.CheckState = CheckState.Checked; + CbDefaultMenu.Font = new Font("Segoe UI", 10F); + CbDefaultMenu.Location = new Point(70, 141); + CbDefaultMenu.Name = "CbDefaultMenu"; + CbDefaultMenu.Size = new Size(149, 23); + CbDefaultMenu.TabIndex = 0; + CbDefaultMenu.Text = Languages.ConfigTrayIcon_CbDefaultMenu; + CbDefaultMenu.UseVisualStyleBackColor = true; + CbDefaultMenu.CheckedChanged += CbDefaultMenu_CheckedChanged; // // CbShowWebView // - this.CbShowWebView.AccessibleDescription = "If enabled, right clicking the system tray icon will show a webview with the url " + - "you provide."; - this.CbShowWebView.AccessibleName = "Show webview"; - this.CbShowWebView.AccessibleRole = System.Windows.Forms.AccessibleRole.CheckButton; - this.CbShowWebView.AutoSize = true; - this.CbShowWebView.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.CbShowWebView.Location = new System.Drawing.Point(70, 209); - this.CbShowWebView.Name = "CbShowWebView"; - this.CbShowWebView.Size = new System.Drawing.Size(116, 23); - this.CbShowWebView.TabIndex = 1; - this.CbShowWebView.Text = global::HASS.Agent.Resources.Localization.Languages.ConfigTrayIcon_CbShowWebView; - this.CbShowWebView.UseVisualStyleBackColor = true; - this.CbShowWebView.CheckedChanged += new System.EventHandler(this.CbShowWebView_CheckedChanged); + CbShowWebView.AccessibleDescription = "If enabled, right clicking the system tray icon will show a webview with the url you provide."; + CbShowWebView.AccessibleName = "Show webview"; + CbShowWebView.AccessibleRole = AccessibleRole.CheckButton; + CbShowWebView.AutoSize = true; + CbShowWebView.Font = new Font("Segoe UI", 10F); + CbShowWebView.Location = new Point(70, 209); + CbShowWebView.Name = "CbShowWebView"; + CbShowWebView.Size = new Size(121, 23); + CbShowWebView.TabIndex = 1; + CbShowWebView.Text = Languages.ConfigTrayIcon_CbShowWebView; + CbShowWebView.UseVisualStyleBackColor = true; + CbShowWebView.CheckedChanged += CbShowWebView_CheckedChanged; // // TbWebViewUrl // - this.TbWebViewUrl.AccessibleDescription = "The URL to show. Defaults to the Home Assistant API\'s URL."; - this.TbWebViewUrl.AccessibleName = "URL"; - this.TbWebViewUrl.AccessibleRole = System.Windows.Forms.AccessibleRole.Text; - this.TbWebViewUrl.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.TbWebViewUrl.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.TbWebViewUrl.Enabled = false; - this.TbWebViewUrl.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.TbWebViewUrl.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.TbWebViewUrl.Location = new System.Drawing.Point(90, 280); - this.TbWebViewUrl.Name = "TbWebViewUrl"; - this.TbWebViewUrl.Size = new System.Drawing.Size(521, 25); - this.TbWebViewUrl.TabIndex = 2; + TbWebViewUrl.AccessibleDescription = "The URL to show. Defaults to the Home Assistant API's URL."; + TbWebViewUrl.AccessibleName = "URL"; + TbWebViewUrl.AccessibleRole = AccessibleRole.Text; + TbWebViewUrl.BackColor = Color.FromArgb(63, 63, 70); + TbWebViewUrl.BorderStyle = BorderStyle.FixedSingle; + TbWebViewUrl.Enabled = false; + TbWebViewUrl.Font = new Font("Segoe UI", 10F); + TbWebViewUrl.ForeColor = Color.FromArgb(241, 241, 241); + TbWebViewUrl.Location = new Point(90, 280); + TbWebViewUrl.Name = "TbWebViewUrl"; + TbWebViewUrl.Size = new Size(521, 25); + TbWebViewUrl.TabIndex = 2; // // LblWebViewUrl // - this.LblWebViewUrl.AccessibleDescription = "URL textbox description"; - this.LblWebViewUrl.AccessibleName = "URL info"; - this.LblWebViewUrl.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblWebViewUrl.AutoSize = true; - this.LblWebViewUrl.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblWebViewUrl.Location = new System.Drawing.Point(87, 258); - this.LblWebViewUrl.Name = "LblWebViewUrl"; - this.LblWebViewUrl.Size = new System.Drawing.Size(349, 19); - this.LblWebViewUrl.TabIndex = 49; - this.LblWebViewUrl.Text = Languages.ConfigTrayIcon_LblWebViewUrl; + LblWebViewUrl.AccessibleDescription = "URL textbox description"; + LblWebViewUrl.AccessibleName = "URL info"; + LblWebViewUrl.AccessibleRole = AccessibleRole.StaticText; + LblWebViewUrl.AutoSize = true; + LblWebViewUrl.Font = new Font("Segoe UI", 10F); + LblWebViewUrl.Location = new Point(87, 258); + LblWebViewUrl.Name = "LblWebViewUrl"; + LblWebViewUrl.Size = new Size(415, 19); + LblWebViewUrl.TabIndex = 49; + LblWebViewUrl.Text = "&WebView URL (For instance, your Home Assistant Dashboard URL)"; // // LblX // - this.LblX.AccessibleDescription = "Shows X, meaning \'by\' in this context."; - this.LblX.AccessibleName = "X info"; - this.LblX.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblX.AutoSize = true; - this.LblX.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblX.Location = new System.Drawing.Point(184, 350); - this.LblX.Name = "LblX"; - this.LblX.Size = new System.Drawing.Size(17, 19); - this.LblX.TabIndex = 53; - this.LblX.Text = "X"; + LblX.AccessibleDescription = "Shows X, meaning 'by' in this context."; + LblX.AccessibleName = "X info"; + LblX.AccessibleRole = AccessibleRole.StaticText; + LblX.AutoSize = true; + LblX.Font = new Font("Segoe UI", 10F); + LblX.Location = new Point(184, 350); + LblX.Name = "LblX"; + LblX.Size = new Size(17, 19); + LblX.TabIndex = 53; + LblX.Text = "X"; // // LblWebViewSize // - this.LblWebViewSize.AccessibleDescription = "Size description."; - this.LblWebViewSize.AccessibleName = "Size info"; - this.LblWebViewSize.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblWebViewSize.AutoSize = true; - this.LblWebViewSize.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblWebViewSize.Location = new System.Drawing.Point(85, 326); - this.LblWebViewSize.Name = "LblWebViewSize"; - this.LblWebViewSize.Size = new System.Drawing.Size(31, 19); - this.LblWebViewSize.TabIndex = 51; - this.LblWebViewSize.Text = Languages.ConfigTrayIcon_LblWebViewSize; + LblWebViewSize.AccessibleDescription = "Size description."; + LblWebViewSize.AccessibleName = "Size info"; + LblWebViewSize.AccessibleRole = AccessibleRole.StaticText; + LblWebViewSize.AutoSize = true; + LblWebViewSize.Font = new Font("Segoe UI", 10F); + LblWebViewSize.Location = new Point(85, 326); + LblWebViewSize.Name = "LblWebViewSize"; + LblWebViewSize.Size = new Size(58, 19); + LblWebViewSize.TabIndex = 51; + LblWebViewSize.Text = "Size (px)"; // // BtnShowWebViewPreview // - this.BtnShowWebViewPreview.AccessibleDescription = "Shows the webview, using the currently configured values."; - this.BtnShowWebViewPreview.AccessibleName = "Webview preview"; - this.BtnShowWebViewPreview.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton; - this.BtnShowWebViewPreview.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnShowWebViewPreview.Enabled = false; - this.BtnShowWebViewPreview.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.BtnShowWebViewPreview.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnShowWebViewPreview.Location = new System.Drawing.Point(405, 348); - this.BtnShowWebViewPreview.Name = "BtnShowWebViewPreview"; - this.BtnShowWebViewPreview.Size = new System.Drawing.Size(206, 26); - this.BtnShowWebViewPreview.Style.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnShowWebViewPreview.Style.FocusedBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnShowWebViewPreview.Style.FocusedForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnShowWebViewPreview.Style.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnShowWebViewPreview.Style.HoverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnShowWebViewPreview.Style.HoverForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnShowWebViewPreview.Style.PressedForeColor = System.Drawing.Color.Black; - this.BtnShowWebViewPreview.TabIndex = 6; - this.BtnShowWebViewPreview.Text = global::HASS.Agent.Resources.Localization.Languages.ConfigTrayIcon_BtnShowWebViewPreview; - this.BtnShowWebViewPreview.UseVisualStyleBackColor = false; - this.BtnShowWebViewPreview.Click += new System.EventHandler(this.BtnShowWebViewPreview_Click); + BtnShowWebViewPreview.AccessibleDescription = "Shows the webview, using the currently configured values."; + BtnShowWebViewPreview.AccessibleName = "Webview preview"; + BtnShowWebViewPreview.AccessibleRole = AccessibleRole.PushButton; + BtnShowWebViewPreview.BackColor = Color.FromArgb(63, 63, 70); + BtnShowWebViewPreview.Enabled = false; + BtnShowWebViewPreview.Font = new Font("Segoe UI", 10F); + BtnShowWebViewPreview.ForeColor = Color.FromArgb(241, 241, 241); + BtnShowWebViewPreview.Location = new Point(405, 348); + BtnShowWebViewPreview.Name = "BtnShowWebViewPreview"; + BtnShowWebViewPreview.Size = new Size(206, 26); + BtnShowWebViewPreview.Style.BackColor = Color.FromArgb(63, 63, 70); + BtnShowWebViewPreview.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); + BtnShowWebViewPreview.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); + BtnShowWebViewPreview.Style.ForeColor = Color.FromArgb(241, 241, 241); + BtnShowWebViewPreview.Style.HoverBackColor = Color.FromArgb(63, 63, 70); + BtnShowWebViewPreview.Style.HoverForeColor = Color.FromArgb(241, 241, 241); + BtnShowWebViewPreview.Style.PressedForeColor = Color.Black; + BtnShowWebViewPreview.TabIndex = 6; + BtnShowWebViewPreview.Text = Languages.ConfigTrayIcon_BtnShowWebViewPreview; + BtnShowWebViewPreview.UseVisualStyleBackColor = false; + BtnShowWebViewPreview.Click += BtnShowWebViewPreview_Click; // // NumWebViewWidth // - this.NumWebViewWidth.AccessibleDescription = "The width of the webview. Only accepts numeric values."; - this.NumWebViewWidth.AccessibleName = "Width"; - this.NumWebViewWidth.AccessibleRole = System.Windows.Forms.AccessibleRole.Text; - this.NumWebViewWidth.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.NumWebViewWidth.BeforeTouchSize = new System.Drawing.Size(83, 25); - this.NumWebViewWidth.Border3DStyle = System.Windows.Forms.Border3DStyle.Flat; - this.NumWebViewWidth.BorderColor = System.Drawing.SystemColors.WindowFrame; - this.NumWebViewWidth.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.NumWebViewWidth.Enabled = false; - this.NumWebViewWidth.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.NumWebViewWidth.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.NumWebViewWidth.Location = new System.Drawing.Point(87, 348); - this.NumWebViewWidth.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.NumWebViewWidth.MaxLength = 10; - this.NumWebViewWidth.MetroColor = System.Drawing.SystemColors.WindowFrame; - this.NumWebViewWidth.Name = "NumWebViewWidth"; - this.NumWebViewWidth.Size = new System.Drawing.Size(83, 25); - this.NumWebViewWidth.TabIndex = 3; - this.NumWebViewWidth.ThemeName = "Metro"; - this.NumWebViewWidth.Value = new decimal(new int[] { - 700, - 0, - 0, - 0}); - this.NumWebViewWidth.VisualStyle = Syncfusion.Windows.Forms.VisualStyle.Metro; + NumWebViewWidth.AccessibleDescription = "The width of the webview. Only accepts numeric values."; + NumWebViewWidth.AccessibleName = "Width"; + NumWebViewWidth.AccessibleRole = AccessibleRole.Text; + NumWebViewWidth.BackColor = Color.FromArgb(63, 63, 70); + NumWebViewWidth.BeforeTouchSize = new Size(83, 25); + NumWebViewWidth.Border3DStyle = Border3DStyle.Flat; + NumWebViewWidth.BorderColor = SystemColors.WindowFrame; + NumWebViewWidth.BorderStyle = BorderStyle.FixedSingle; + NumWebViewWidth.Enabled = false; + NumWebViewWidth.Font = new Font("Segoe UI", 10F); + NumWebViewWidth.ForeColor = Color.FromArgb(241, 241, 241); + NumWebViewWidth.Location = new Point(87, 348); + NumWebViewWidth.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + NumWebViewWidth.MaxLength = 10; + NumWebViewWidth.MetroColor = SystemColors.WindowFrame; + NumWebViewWidth.Name = "NumWebViewWidth"; + NumWebViewWidth.Size = new Size(83, 25); + NumWebViewWidth.TabIndex = 3; + NumWebViewWidth.ThemeName = "Metro"; + NumWebViewWidth.Value = new decimal(new int[] { 700, 0, 0, 0 }); + NumWebViewWidth.VisualStyle = Syncfusion.Windows.Forms.VisualStyle.Metro; // // NumWebViewHeight // - this.NumWebViewHeight.AccessibleDescription = "The height of the webview. Only accepts numeric values."; - this.NumWebViewHeight.AccessibleName = "Height"; - this.NumWebViewHeight.AccessibleRole = System.Windows.Forms.AccessibleRole.Text; - this.NumWebViewHeight.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.NumWebViewHeight.BeforeTouchSize = new System.Drawing.Size(83, 25); - this.NumWebViewHeight.Border3DStyle = System.Windows.Forms.Border3DStyle.Flat; - this.NumWebViewHeight.BorderColor = System.Drawing.SystemColors.WindowFrame; - this.NumWebViewHeight.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.NumWebViewHeight.Enabled = false; - this.NumWebViewHeight.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.NumWebViewHeight.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.NumWebViewHeight.Location = new System.Drawing.Point(218, 348); - this.NumWebViewHeight.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.NumWebViewHeight.MaxLength = 10; - this.NumWebViewHeight.MetroColor = System.Drawing.SystemColors.WindowFrame; - this.NumWebViewHeight.Name = "NumWebViewHeight"; - this.NumWebViewHeight.Size = new System.Drawing.Size(83, 25); - this.NumWebViewHeight.TabIndex = 4; - this.NumWebViewHeight.ThemeName = "Metro"; - this.NumWebViewHeight.Value = new decimal(new int[] { - 560, - 0, - 0, - 0}); - this.NumWebViewHeight.VisualStyle = Syncfusion.Windows.Forms.VisualStyle.Metro; + NumWebViewHeight.AccessibleDescription = "The height of the webview. Only accepts numeric values."; + NumWebViewHeight.AccessibleName = "Height"; + NumWebViewHeight.AccessibleRole = AccessibleRole.Text; + NumWebViewHeight.BackColor = Color.FromArgb(63, 63, 70); + NumWebViewHeight.BeforeTouchSize = new Size(83, 25); + NumWebViewHeight.Border3DStyle = Border3DStyle.Flat; + NumWebViewHeight.BorderColor = SystemColors.WindowFrame; + NumWebViewHeight.BorderStyle = BorderStyle.FixedSingle; + NumWebViewHeight.Enabled = false; + NumWebViewHeight.Font = new Font("Segoe UI", 10F); + NumWebViewHeight.ForeColor = Color.FromArgb(241, 241, 241); + NumWebViewHeight.Location = new Point(218, 348); + NumWebViewHeight.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + NumWebViewHeight.MaxLength = 10; + NumWebViewHeight.MetroColor = SystemColors.WindowFrame; + NumWebViewHeight.Name = "NumWebViewHeight"; + NumWebViewHeight.Size = new Size(83, 25); + NumWebViewHeight.TabIndex = 4; + NumWebViewHeight.ThemeName = "Metro"; + NumWebViewHeight.Value = new decimal(new int[] { 560, 0, 0, 0 }); + NumWebViewHeight.VisualStyle = Syncfusion.Windows.Forms.VisualStyle.Metro; // // BtnWebViewReset // - this.BtnWebViewReset.AccessibleDescription = "Resets the width and height values to their defaults."; - this.BtnWebViewReset.AccessibleName = "Reset webview"; - this.BtnWebViewReset.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton; - this.BtnWebViewReset.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnWebViewReset.Enabled = false; - this.BtnWebViewReset.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.BtnWebViewReset.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnWebViewReset.ImageSize = new System.Drawing.Size(24, 24); - this.BtnWebViewReset.Location = new System.Drawing.Point(317, 348); - this.BtnWebViewReset.Name = "BtnWebViewReset"; - this.BtnWebViewReset.Size = new System.Drawing.Size(51, 26); - this.BtnWebViewReset.Style.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnWebViewReset.Style.FocusedBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnWebViewReset.Style.FocusedForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnWebViewReset.Style.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnWebViewReset.Style.HoverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70))))); - this.BtnWebViewReset.Style.HoverForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.BtnWebViewReset.Style.Image = global::HASS.Agent.Properties.Resources.reset_24; - this.BtnWebViewReset.Style.PressedForeColor = System.Drawing.Color.Black; - this.BtnWebViewReset.TabIndex = 5; - this.BtnWebViewReset.TextImageRelation = System.Windows.Forms.TextImageRelation.Overlay; - this.BtnWebViewReset.UseVisualStyleBackColor = false; - this.BtnWebViewReset.Click += new System.EventHandler(this.BtnWebViewReset_Click); + BtnWebViewReset.AccessibleDescription = "Resets the width and height values to their defaults."; + BtnWebViewReset.AccessibleName = "Reset webview"; + BtnWebViewReset.AccessibleRole = AccessibleRole.PushButton; + BtnWebViewReset.BackColor = Color.FromArgb(63, 63, 70); + BtnWebViewReset.Enabled = false; + BtnWebViewReset.Font = new Font("Segoe UI", 10F); + BtnWebViewReset.ForeColor = Color.FromArgb(241, 241, 241); + BtnWebViewReset.ImageSize = new Size(24, 24); + BtnWebViewReset.Location = new Point(317, 348); + BtnWebViewReset.Name = "BtnWebViewReset"; + BtnWebViewReset.Size = new Size(51, 26); + BtnWebViewReset.Style.BackColor = Color.FromArgb(63, 63, 70); + BtnWebViewReset.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); + BtnWebViewReset.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); + BtnWebViewReset.Style.ForeColor = Color.FromArgb(241, 241, 241); + BtnWebViewReset.Style.HoverBackColor = Color.FromArgb(63, 63, 70); + BtnWebViewReset.Style.HoverForeColor = Color.FromArgb(241, 241, 241); + BtnWebViewReset.Style.Image = Properties.Resources.reset_24; + BtnWebViewReset.Style.PressedForeColor = Color.Black; + BtnWebViewReset.TabIndex = 5; + BtnWebViewReset.TextImageRelation = TextImageRelation.Overlay; + BtnWebViewReset.UseVisualStyleBackColor = false; + BtnWebViewReset.Click += BtnWebViewReset_Click; // // CbWebViewKeepLoaded // - this.CbWebViewKeepLoaded.AccessibleDescription = "Keeps the webview loaded in the background, resulting in faster loading when invo" + - "ked."; - this.CbWebViewKeepLoaded.AccessibleName = "Background loading"; - this.CbWebViewKeepLoaded.AccessibleRole = System.Windows.Forms.AccessibleRole.CheckButton; - this.CbWebViewKeepLoaded.AutoSize = true; - this.CbWebViewKeepLoaded.Checked = true; - this.CbWebViewKeepLoaded.CheckState = System.Windows.Forms.CheckState.Checked; - this.CbWebViewKeepLoaded.Enabled = false; - this.CbWebViewKeepLoaded.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.CbWebViewKeepLoaded.Location = new System.Drawing.Point(90, 405); - this.CbWebViewKeepLoaded.Name = "CbWebViewKeepLoaded"; - this.CbWebViewKeepLoaded.Size = new System.Drawing.Size(252, 23); - this.CbWebViewKeepLoaded.TabIndex = 7; - this.CbWebViewKeepLoaded.Text = global::HASS.Agent.Resources.Localization.Languages.ConfigTrayIcon_CbWebViewKeepLoaded; - this.CbWebViewKeepLoaded.UseVisualStyleBackColor = true; + CbWebViewKeepLoaded.AccessibleDescription = "Keeps the webview loaded in the background, resulting in faster loading when invoked."; + CbWebViewKeepLoaded.AccessibleName = "Background loading"; + CbWebViewKeepLoaded.AccessibleRole = AccessibleRole.CheckButton; + CbWebViewKeepLoaded.AutoSize = true; + CbWebViewKeepLoaded.Checked = true; + CbWebViewKeepLoaded.CheckState = CheckState.Checked; + CbWebViewKeepLoaded.Enabled = false; + CbWebViewKeepLoaded.Font = new Font("Segoe UI", 10F); + CbWebViewKeepLoaded.Location = new Point(90, 405); + CbWebViewKeepLoaded.Name = "CbWebViewKeepLoaded"; + CbWebViewKeepLoaded.Size = new Size(253, 23); + CbWebViewKeepLoaded.TabIndex = 7; + CbWebViewKeepLoaded.Text = Languages.ConfigTrayIcon_CbWebViewKeepLoaded; + CbWebViewKeepLoaded.UseVisualStyleBackColor = true; // // LblInfo2 // - this.LblInfo2.AccessibleDescription = "Background loading information."; - this.LblInfo2.AccessibleName = "Background loading info"; - this.LblInfo2.AccessibleRole = System.Windows.Forms.AccessibleRole.StaticText; - this.LblInfo2.AutoSize = true; - this.LblInfo2.Enabled = false; - this.LblInfo2.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.LblInfo2.Location = new System.Drawing.Point(107, 435); - this.LblInfo2.Name = "LblInfo2"; - this.LblInfo2.Size = new System.Drawing.Size(320, 19); - this.LblInfo2.TabIndex = 76; - this.LblInfo2.Text = Languages.ConfigTrayIcon_LblInfo2; + LblInfo2.AccessibleDescription = "Background loading information."; + LblInfo2.AccessibleName = "Background loading info"; + LblInfo2.AccessibleRole = AccessibleRole.StaticText; + LblInfo2.AutoSize = true; + LblInfo2.Enabled = false; + LblInfo2.Font = new Font("Segoe UI", 10F); + LblInfo2.Location = new Point(107, 435); + LblInfo2.Name = "LblInfo2"; + LblInfo2.Size = new Size(330, 19); + LblInfo2.TabIndex = 76; + LblInfo2.Text = "(This uses extra resources, but reduces loading time.)"; // // CbWebViewShowMenuOnLeftClick // - this.CbWebViewShowMenuOnLeftClick.AccessibleDescription = "If enabled, left clicking the system tray icon will show the default menu."; - this.CbWebViewShowMenuOnLeftClick.AccessibleName = "Show default menu on left click"; - this.CbWebViewShowMenuOnLeftClick.AccessibleRole = System.Windows.Forms.AccessibleRole.CheckButton; - this.CbWebViewShowMenuOnLeftClick.AutoSize = true; - this.CbWebViewShowMenuOnLeftClick.Enabled = false; - this.CbWebViewShowMenuOnLeftClick.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.CbWebViewShowMenuOnLeftClick.Location = new System.Drawing.Point(90, 487); - this.CbWebViewShowMenuOnLeftClick.Name = "CbWebViewShowMenuOnLeftClick"; - this.CbWebViewShowMenuOnLeftClick.Size = new System.Drawing.Size(304, 23); - this.CbWebViewShowMenuOnLeftClick.TabIndex = 77; - this.CbWebViewShowMenuOnLeftClick.Text = Languages.ConfigTrayIcon_CbWebViewShowMenuOnLeftClick; - this.CbWebViewShowMenuOnLeftClick.UseVisualStyleBackColor = true; + CbWebViewShowMenuOnLeftClick.AccessibleDescription = "If enabled, left clicking the system tray icon will show the default menu."; + CbWebViewShowMenuOnLeftClick.AccessibleName = "Show default menu on left click"; + CbWebViewShowMenuOnLeftClick.AccessibleRole = AccessibleRole.CheckButton; + CbWebViewShowMenuOnLeftClick.AutoSize = true; + CbWebViewShowMenuOnLeftClick.Enabled = false; + CbWebViewShowMenuOnLeftClick.Font = new Font("Segoe UI", 10F); + CbWebViewShowMenuOnLeftClick.Location = new Point(90, 487); + CbWebViewShowMenuOnLeftClick.Name = "CbWebViewShowMenuOnLeftClick"; + CbWebViewShowMenuOnLeftClick.Size = new Size(265, 23); + CbWebViewShowMenuOnLeftClick.TabIndex = 77; + CbWebViewShowMenuOnLeftClick.Text = Languages.ConfigTrayIcon_CbWebViewShowMenuOnLeftClick; + CbWebViewShowMenuOnLeftClick.UseVisualStyleBackColor = true; // // ConfigTrayIcon // - this.AccessibleDescription = "Panel containing the tray icon configuration."; - this.AccessibleName = "Tray icon"; - this.AccessibleRole = System.Windows.Forms.AccessibleRole.Pane; - this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48))))); - this.Controls.Add(this.CbWebViewShowMenuOnLeftClick); - this.Controls.Add(this.LblInfo2); - this.Controls.Add(this.CbWebViewKeepLoaded); - this.Controls.Add(this.BtnWebViewReset); - this.Controls.Add(this.NumWebViewHeight); - this.Controls.Add(this.NumWebViewWidth); - this.Controls.Add(this.BtnShowWebViewPreview); - this.Controls.Add(this.LblX); - this.Controls.Add(this.LblWebViewSize); - this.Controls.Add(this.TbWebViewUrl); - this.Controls.Add(this.LblWebViewUrl); - this.Controls.Add(this.CbShowWebView); - this.Controls.Add(this.LblInfo1); - this.Controls.Add(this.CbDefaultMenu); - this.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); - this.Margin = new System.Windows.Forms.Padding(4); - this.Name = "ConfigTrayIcon"; - this.Size = new System.Drawing.Size(700, 544); - this.Load += new System.EventHandler(this.ConfigTrayIcon_Load); - ((System.ComponentModel.ISupportInitialize)(this.NumWebViewWidth)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.NumWebViewHeight)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - + AccessibleDescription = "Panel containing the tray icon configuration."; + AccessibleName = "Tray icon"; + AccessibleRole = AccessibleRole.Pane; + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + BackColor = Color.FromArgb(45, 45, 48); + Controls.Add(CbWebViewShowMenuOnLeftClick); + Controls.Add(LblInfo2); + Controls.Add(CbWebViewKeepLoaded); + Controls.Add(BtnWebViewReset); + Controls.Add(NumWebViewHeight); + Controls.Add(NumWebViewWidth); + Controls.Add(BtnShowWebViewPreview); + Controls.Add(LblX); + Controls.Add(LblWebViewSize); + Controls.Add(TbWebViewUrl); + Controls.Add(LblWebViewUrl); + Controls.Add(CbShowWebView); + Controls.Add(LblInfo1); + Controls.Add(CbDefaultMenu); + Controls.Add(CbUseModernIcon); + ForeColor = Color.FromArgb(241, 241, 241); + Margin = new Padding(4); + Name = "ConfigTrayIcon"; + Size = new Size(700, 544); + Load += ConfigTrayIcon_Load; + ((System.ComponentModel.ISupportInitialize)NumWebViewWidth).EndInit(); + ((System.ComponentModel.ISupportInitialize)NumWebViewHeight).EndInit(); + ResumeLayout(false); + PerformLayout(); } #endregion private System.Windows.Forms.Label LblInfo1; + internal System.Windows.Forms.CheckBox CbUseModernIcon; internal System.Windows.Forms.CheckBox CbDefaultMenu; internal CheckBox CbShowWebView; internal TextBox TbWebViewUrl; diff --git a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.resx b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.resx index f298a7be..af32865e 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.resx +++ b/src/HASS.Agent/HASS.Agent/Controls/Configuration/ConfigTrayIcon.resx @@ -1,4 +1,64 @@ - + + + diff --git a/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs b/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs index 0ca8550c..d36c4879 100644 --- a/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs +++ b/src/HASS.Agent/HASS.Agent/Extensions/RpcExtensions.cs @@ -109,7 +109,11 @@ public static ConfiguredSensor ConvertToConfiguredSensor(this RpcConfiguredServe Counter = rpcConfiguredSensor.Counter, Instance = rpcConfiguredSensor.Instance, EntityName = rpcConfiguredSensor.EntityName, - Name = rpcConfiguredSensor.Name + Name = rpcConfiguredSensor.Name, + AdvancedSettings = rpcConfiguredSensor.AdvancedSettings, + IgnoreAvailability = rpcConfiguredSensor.IgnoreAvailability, + ApplyRounding = rpcConfiguredSensor.ApplyRounding, + Round = rpcConfiguredSensor.RoundValue, }; return configuredSensor; @@ -150,7 +154,11 @@ public static RpcConfiguredServerSensor ConvertToRpcConfiguredSensor(this Config Counter = configuredSensor.Counter ?? string.Empty, Instance = configuredSensor.Instance ?? string.Empty, Name = configuredSensor.Name ?? string.Empty, - EntityName = configuredSensor.EntityName ?? string.Empty + EntityName = configuredSensor.EntityName ?? string.Empty, + AdvancedSettings = configuredSensor?.AdvancedSettings ?? string.Empty, + IgnoreAvailability = configuredSensor?.IgnoreAvailability ?? false, + ApplyRounding = configuredSensor?.ApplyRounding ?? false, + RoundValue = configuredSensor?.Round ?? 0, }; return configuredRpcSensor; diff --git a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs index d279bef5..f494dbb9 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs @@ -205,6 +205,8 @@ private void LoadCommand() case CommandType.SetVolumeCommand: case CommandType.SetApplicationVolumeCommand: + case CommandType.SetAudioOutputCommand: + case CommandType.SetAudioInputCommand: TbSetting.Text = Command.Command; break; } @@ -263,7 +265,7 @@ private void BtnStore_Click(object sender, EventArgs e) MessageBoxAdv.Show(this, Languages.CommandsMod_BtnStore_DeviceNameInSensorName, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); } - var friendlyName = string.IsNullOrEmpty(TbName.Text.Trim()) ? name : TbName.Text.Trim(); + var friendlyName = string.IsNullOrEmpty(TbFriendlyName.Text.Trim()) ? name : TbFriendlyName.Text.Trim(); var sanitized = SharedHelperFunctions.GetSafeValue(name); if (sanitized != name) @@ -351,7 +353,19 @@ private void BtnStore_Click(object sender, EventArgs e) return; } - Command.Keys = keys; + + if (keys.Count == 0) + { + var q = MessageBoxAdv.Show(this, Languages.CommandsMod_BtnStore_MessageBox4, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (q != DialogResult.Yes) + { + ActiveControl = TbSetting; + + return; + } + } + + Command.Keys = keys; break; case CommandType.LaunchUrlCommand: @@ -455,6 +469,22 @@ private void BtnStore_Click(object sender, EventArgs e) } break; + case CommandType.SetAudioOutputCommand: + case CommandType.SetAudioInputCommand: + var audioDeviceName = TbSetting.Text.Trim(); + if (string.IsNullOrEmpty(audioDeviceName)) + { + var q = MessageBoxAdv.Show(this, Languages.CommandsMod_BtnStore_MessageBox8, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (q != DialogResult.Yes) + { + ActiveControl = BtnConfigureCommand; + return; + } + } + Command.Command = audioDeviceName; + break; + + case CommandType.WebViewCommand: var webview = TbSetting.Text.Trim(); if (string.IsNullOrEmpty(webview)) @@ -613,6 +643,11 @@ private bool SetType(bool setDefaultValues = true) SetApplicationVolumeUi(); break; + case CommandType.SetAudioOutputCommand: + case CommandType.SetAudioInputCommand: + SetAudioOutputUi(); + break; + case CommandType.RadioCommand: CbConfigDropdown.DataSource = new BindingSource(_radioDevices, null); SetRadioUi(); @@ -795,10 +830,10 @@ private void SetVolumeUi() { SetEmptyGui(); - LblSetting.Text = "volume (between 0 and 100)"; - LblSetting.Visible = true; + LblSetting.Text = Languages.CommandsMod_LblSetting_VolumeRange; + LblSetting.Visible = true; - TbSetting.Text = string.Empty; + TbSetting.Text = string.Empty; TbSetting.Visible = true; })); } @@ -812,7 +847,24 @@ private void SetApplicationVolumeUi() { SetEmptyGui(); - LblSetting.Text = "JSON Command Payload"; + LblSetting.Text = Languages.CommandsMod_LblSetting_JsonPayload; + LblSetting.Visible = true; + + TbSetting.Text = string.Empty; + TbSetting.Visible = true; + })); + } + + /// + /// Change the UI to a 'setaudiooutput' type + /// + private void SetAudioOutputUi() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting.Text = Languages.CommandsMod_LblSetting_AudioDeviceName; LblSetting.Visible = true; TbSetting.Text = string.Empty; diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.Designer.cs b/src/HASS.Agent/HASS.Agent/Forms/Configuration.Designer.cs index 0b47fbcc..150e1c3a 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.Designer.cs @@ -46,6 +46,7 @@ private void InitializeComponent() this.TabStartup = new Syncfusion.Windows.Forms.Tools.TabPageAdv(); this.TabTrayIcon = new Syncfusion.Windows.Forms.Tools.TabPageAdv(); this.TabUpdates = new Syncfusion.Windows.Forms.Tools.TabPageAdv(); + this.TabNFC = new Syncfusion.Windows.Forms.Tools.TabPageAdv(); this.BtnAbout = new Syncfusion.WinForms.Controls.SfButton(); this.BtnHelp = new Syncfusion.WinForms.Controls.SfButton(); this.BtnStore = new Syncfusion.WinForms.Controls.SfButton(); @@ -80,6 +81,7 @@ private void InitializeComponent() this.ConfigTabs.Controls.Add(this.TabStartup); this.ConfigTabs.Controls.Add(this.TabTrayIcon); this.ConfigTabs.Controls.Add(this.TabUpdates); + this.ConfigTabs.Controls.Add(this.TabNFC); this.ConfigTabs.Dock = System.Windows.Forms.DockStyle.Fill; this.ConfigTabs.FixedSingleBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); this.ConfigTabs.FocusOnTabClick = false; @@ -355,6 +357,23 @@ private void InitializeComponent() this.TabUpdates.Text = global::HASS.Agent.Resources.Localization.Languages.Configuration_TabUpdates; this.TabUpdates.ThemesEnabled = false; // + // TabNFC + // + this.TabNFC.AccessibleDescription = "Contains the \'NFC\' controls."; + this.TabNFC.AccessibleName = "NFC"; + this.TabNFC.AccessibleRole = System.Windows.Forms.AccessibleRole.PageTab; + this.TabNFC.Image = null; + this.TabNFC.ImageSize = new System.Drawing.Size(16, 16); + this.TabNFC.Location = new System.Drawing.Point(142, 2); + this.TabNFC.Name = "TabNFC"; + this.TabNFC.ShowCloseButton = true; + this.TabNFC.Size = new System.Drawing.Size(740, 544); + this.TabNFC.TabBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48))))); + this.TabNFC.TabForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); + this.TabNFC.TabIndex = 15; + this.TabNFC.Text = Languages.Configuration_NFC; + this.TabNFC.ThemesEnabled = false; + // // BtnAbout // this.BtnAbout.AccessibleDescription = "Opens the about window."; @@ -523,6 +542,7 @@ private void InitializeComponent() private Syncfusion.Windows.Forms.Tools.TabPageAdv TablLocalApi; private Syncfusion.Windows.Forms.Tools.TabPageAdv TabMediaPlayer; private Syncfusion.Windows.Forms.Tools.TabPageAdv TabTrayIcon; + private Syncfusion.Windows.Forms.Tools.TabPageAdv TabNFC; } } diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs index dedf1cf2..2f83e22a 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs @@ -36,6 +36,7 @@ public partial class Configuration : MetroForm private readonly ConfigLocalApi _localApi = new(); private readonly ConfigMediaPlayer _mediaPlayer = new(); private readonly ConfigTrayIcon _trayIcon = new(); + private readonly ConfigNFC _nfc = new(); private bool _initializing = true; @@ -67,6 +68,8 @@ private void Configuration_Load(object sender, EventArgs e) TablLocalApi.Controls.Add(_localApi); TabMediaPlayer.Controls.Add(_mediaPlayer); TabTrayIcon.Controls.Add(_trayIcon); + TabNFC.Controls.Add(_nfc); + // bind events BindEvents(); @@ -103,6 +106,7 @@ private void Configuration_FormClosing(object sender, FormClosingEventArgs e) _localApi.Dispose(); _mediaPlayer.Dispose(); _trayIcon.Dispose(); + _nfc.Dispose(); } private void BindEvents() @@ -323,6 +327,7 @@ private void LoadSettings() _mqtt.TbMqttClientCertificate.Text = Variables.AppSettings.MqttClientCertificate; _mqtt.CbAllowUntrustedCertificates.CheckState = Variables.AppSettings.MqttAllowUntrustedCertificates ? CheckState.Checked : CheckState.Unchecked; _mqtt.CbUseRetainFlag.CheckState = Variables.AppSettings.MqttUseRetainFlag ? CheckState.Checked : CheckState.Unchecked; + _mqtt.CbIgnoreGracePeriod.CheckState = Variables.AppSettings.MqttIgnoreGracePeriod ? CheckState.Checked : CheckState.Unchecked; // updates _updates.CbUpdates.CheckState = Variables.AppSettings.CheckForUpdates ? CheckState.Checked : CheckState.Unchecked; @@ -351,6 +356,7 @@ private void LoadSettings() _mediaPlayer.CbEnableMediaPlayer.CheckState = Variables.AppSettings.MediaPlayerEnabled ? CheckState.Checked : CheckState.Unchecked; // tray icon + _trayIcon.CbUseModernIcon.CheckState = Variables.AppSettings.TrayIconUseModern ? CheckState.Checked : CheckState.Unchecked; _trayIcon.CbDefaultMenu.CheckState = Variables.AppSettings.TrayIconShowDefaultMenu ? CheckState.Checked : CheckState.Unchecked; _trayIcon.CbShowWebView.CheckState = Variables.AppSettings.TrayIconShowWebView ? CheckState.Checked : CheckState.Unchecked; _trayIcon.NumWebViewWidth.Value = Variables.AppSettings.TrayIconWebViewWidth; @@ -359,6 +365,8 @@ private void LoadSettings() _trayIcon.CbWebViewKeepLoaded.CheckState = Variables.AppSettings.TrayIconWebViewBackgroundLoading ? CheckState.Checked : CheckState.Unchecked; _trayIcon.CbWebViewShowMenuOnLeftClick.CheckState = Variables.AppSettings.TrayIconWebViewShowMenuOnLeftClick ? CheckState.Checked : CheckState.Unchecked; + _nfc.CbEnableNfc.CheckState = Variables.AppSettings.NfcScanningEnabled ? CheckState.Checked : CheckState.Unchecked; + // done _initializing = false; } @@ -425,6 +433,7 @@ private async Task StoreSettingsAsync() Variables.AppSettings.MqttClientCertificate = _mqtt.TbMqttClientCertificate.Text; Variables.AppSettings.MqttAllowUntrustedCertificates = _mqtt.CbAllowUntrustedCertificates.CheckState == CheckState.Checked; Variables.AppSettings.MqttUseRetainFlag = _mqtt.CbUseRetainFlag.CheckState == CheckState.Checked; + Variables.AppSettings.MqttIgnoreGracePeriod = _mqtt.CbIgnoreGracePeriod.CheckState == CheckState.Checked; // mqtt -> service await SettingsManager.SendMqttSettingsToServiceAsync(); @@ -456,6 +465,7 @@ private async Task StoreSettingsAsync() Variables.AppSettings.MediaPlayerEnabled = _mediaPlayer.CbEnableMediaPlayer.CheckState == CheckState.Checked; // tray icon + Variables.AppSettings.TrayIconUseModern = _trayIcon.CbUseModernIcon.CheckState == CheckState.Checked; Variables.AppSettings.TrayIconShowDefaultMenu = _trayIcon.CbDefaultMenu.CheckState == CheckState.Checked; Variables.AppSettings.TrayIconShowWebView = _trayIcon.CbShowWebView.CheckState == CheckState.Checked; Variables.AppSettings.TrayIconWebViewWidth = (int)_trayIcon.NumWebViewWidth.Value; @@ -464,6 +474,10 @@ private async Task StoreSettingsAsync() Variables.AppSettings.TrayIconWebViewBackgroundLoading = _trayIcon.CbWebViewKeepLoaded.CheckState == CheckState.Checked; Variables.AppSettings.TrayIconWebViewShowMenuOnLeftClick = _trayIcon.CbWebViewShowMenuOnLeftClick.CheckState == CheckState.Checked; + // nfc + Variables.AppSettings.NfcScanningEnabled = _nfc.CbEnableNfc.CheckState == CheckState.Checked; + Variables.AppSettings.NfcSelectedScanner = _nfc.CbNfcScanner.SelectedItem == null ? string.Empty : _nfc.CbNfcScanner.SelectedItem.ToString(); + // save to file SettingsManager.StoreAppSettings(); } diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.resx b/src/HASS.Agent/HASS.Agent/Forms/Configuration.resx index d1f11844..9cfd6d5f 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.resx +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.resx @@ -1,4 +1,64 @@ - + + + diff --git a/src/HASS.Agent/HASS.Agent/Forms/Main.cs b/src/HASS.Agent/HASS.Agent/Forms/Main.cs index 3eb10a0f..0f8467ff 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Main.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Main.cs @@ -20,6 +20,7 @@ using HASS.Agent.Shared.Extensions; using HASS.Agent.Shared.Functions; using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Serilog; using Syncfusion.Windows.Forms; using WindowsDesktop; @@ -89,11 +90,12 @@ private async void Main_Load(object sender, EventArgs e) // core components initialization - required for loading the entities await RadioManager.Initialize(); await InternalDeviceSensorsManager.Initialize(); - InitializeHardwareManager(); - InitializeVirtualDesktopManager(); + InitializeHardwareManager(); + InitializeVirtualDesktopManager(); + await Task.Run(InitializeAudioManager); - // load entities - var loaded = await SettingsManager.LoadEntitiesAsync(); + // load entities + var loaded = await SettingsManager.LoadEntitiesAsync(); if (!loaded) { MessageBoxAdv.Show(this, Languages.Main_Load_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); @@ -163,6 +165,7 @@ await Task.WhenAll(hassApiTask, sensorTask, commandsTask, serviceTask, private void OnProcessExit(object sender, EventArgs e) { + AudioManager.Shutdown(); HardwareManager.Shutdown(); NotificationManager.Exit(); } @@ -245,8 +248,15 @@ private void CheckDpiScalingFactor() MessageBoxAdv.Show(this, Languages.Main_CheckDpiScalingFactor_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } - private static void ProcessTrayIcon() + private void ProcessTrayIcon() { + if (Variables.AppSettings.TrayIconUseModern) + { + var icon = (Icon)new System.Resources.ResourceManager(typeof(Main)).GetObject("ModernNotifyIcon"); + if (icon != null) + NotifyIcon.Icon = icon; + } + // are we set to show the webview and keep it loaded? if (!Variables.AppSettings.TrayIconShowWebView) return; @@ -327,6 +337,14 @@ private void InitializeHardwareManager() HardwareManager.Initialize(); } + /// + /// Initialized the Audio Manager + /// + private void InitializeAudioManager() + { + AudioManager.Initialize(); + } + /// /// Hide if not shutting down, close otherwise /// @@ -337,7 +355,19 @@ private void Main_FormClosing(object sender, FormClosingEventArgs e) if (_isClosing) return; - Invoke(new MethodInvoker(Hide)); + Invoke(() => + { + HelperFunctions.GetForm("QuickActions")?.Close(); + HelperFunctions.GetForm("Configuration")?.Close(); + HelperFunctions.GetForm("QuickActionsConfig")?.Close(); + HelperFunctions.GetForm("SensorsConfig")?.Close(); + HelperFunctions.GetForm("ServiceConfig")?.Close(); + HelperFunctions.GetForm("CommandsConfig")?.Close(); + HelperFunctions.GetForm("Help")?.Close(); + HelperFunctions.GetForm("Donate")?.Close(); + + new MethodInvoker(Hide).Invoke(); + }); if (!Variables.ShuttingDown) { @@ -931,16 +961,7 @@ private void NotifyIcon_MouseClick(object sender, MouseEventArgs e) return; } - // prepare the webview - var webView = new WebViewInfo - { - Url = Variables.AppSettings.TrayIconWebViewUrl, - Height = Variables.AppSettings.TrayIconWebViewHeight, - Width = Variables.AppSettings.TrayIconWebViewWidth, - }; - - // show it - HelperFunctions.LaunchTrayIconWebView(webView); + HelperFunctions.LaunchTrayIconWebView(); } private async void PbDonate_Click(object sender, EventArgs e) diff --git a/src/HASS.Agent/HASS.Agent/Forms/Main.resx b/src/HASS.Agent/HASS.Agent/Forms/Main.resx index 3676f237..df700854 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Main.resx +++ b/src/HASS.Agent/HASS.Agent/Forms/Main.resx @@ -1,4 +1,64 @@ - + + + @@ -57,17 +117,17 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + 17, 17 - - + + 125, 17 - - + iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAnRJREFUWEftlt+HVVEUx/vXIiIi5ikiIiKGISKip+glIiIiehoieomYmhpNv9TU + wAAADsABataJCQAAAnRJREFUWEftlt+HVVEUx/vXIiIi5ikiIiKGISKip+glIiIiehoieomYmhpNv9TU ZDI1PzI1mn7o53TPvefcc85qPvtY2nvffebuY+41ZBZf99599t7rs9daZ6+7a/dES7ZTOwD/D8C+yZbc +tiVuV+FjD5rB+eENBAAnM/+KCQvRdbapSS5RENsGcB2fuZVR0amEllpFQbi2JP+EFsC2HvXda7jQKx3 S5n6kjvzQ2oMwIn33Km+n51LBTv3OnXmXH/fNeMX3rjjITUCYMO0EPmWlibHhx+2zemJAmDMUed8+utD @@ -84,7 +144,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAiNJREFUWEftlc1L3FAUxfuvCV0VBKFQKLgtFISuCkKhK/eCIBQEQSh0JbgpCIJi + wAAADsABataJCQAAAiNJREFUWEftlc1L3FAUxfuvCV0VBKFQKLgtFISuCkKhK/eCIBQEQSh0JbgpCIJi nSqoFT+oYLVa60et0qlfnSSTSebW35tcTKZp5mWy0MUcOMy8l5d3z7vv3JsHXdMVuUt2BHQE3H8BL1c9 2bwMZfY0SH1elJkCni26EtRFJn/UpH/Nk545J3VdEWYKGNvzZfV3IH3Lrpy4dRn56ieeP5xJrm+HLTPw YsWVxV+BPF9yE88elxwjjuzE5/PSyoQI4ffpvCPv9n15te7JmXdzNxGGt6v/vGNLKwHKNzt+FDIJfNKc @@ -99,7 +159,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAnlJREFUWEftlu1LFUEUxv3XAiEIAkEQhCAIAiEQBEEQgsBPgV+CIAiCIAgEQYhA + wAAADsABataJCQAAAnlJREFUWEftlu1LFUEUxv3XAiEIAkEQhCAIAiEQBEEQgsBPgV+CIAiCIAgEQYhA CDQrM+3N0lDKtCxT8jWQfd97T/1m73inZXbv3BvRh+4DD3d3Z2bnmXOec/Z2nJry5F+yLaAtwFnA1ZVI wJZftY63ylIBV5ZDub0Rq2t+wU6QCTj/LJAHO4mcmbGvdWWhgL7ngaRVtacs7KdynGQ3PJvbS8VP1a3M fE+t611ZKKBz2pOXB7VdCoCY/leBdb0rG6ZAY2wzlp5ZX0behSoNYOmoYl3XDK0Cbn6Mldl0mPMbaUOC @@ -116,7 +176,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAiNJREFUWEftlc1L3FAUxfuvCV0VBKFQKLgtFISuCkKhK/eCIBQEQSh0JbgpCIJi + wAAADsABataJCQAAAiNJREFUWEftlc1L3FAUxfuvCV0VBKFQKLgtFISuCkKhK/eCIBQEQSh0JbgpCIJi nSqoFT+oYLVa60et0qlfnSSTSebW35tcTKZp5mWy0MUcOMy8l5d3z7vv3JsHXdMVuUt2BHQE3H8BL1c9 2bwMZfY0SH1elJkCni26EtRFJn/UpH/Nk545J3VdEWYKGNvzZfV3IH3Lrpy4dRn56ieeP5xJrm+HLTPw YsWVxV+BPF9yE88elxwjjuzE5/PSyoQI4ffpvCPv9n15te7JmXdzNxGGt6v/vGNLKwHKNzt+FDIJfNKc @@ -131,7 +191,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAe9JREFUWEftlttHRFEUxvvXIiIiIiIiIiIiIiIiIhE9RfTUU0QiopruN92bmm6j + wAAADsABataJCQAAAe9JREFUWEftlttHRFEUxvvXIiIiIiIiIiIiIiIiIhE9RfTUU0QiopruN92bmm6j y9Qo0mU6tzkzs/PtzjTn7LP2zJ4xGTEf38vZa9b57b3WrH0qKhc0VkqXAcoA/w+gY8/gFp/Xreisddv/ PJeVAZA89Jlk0KuV4i90r0/exvlaRE+x3mPTs5bNSgB9JyZLpHj+Xx2/JVn7rsEa13U2dh1nesJZcDR+ EydziVYCGAhaTlp1TYSLCFC1qDHr5/SVVbvsLZHMygDvcaEGOYTSULlEKwGgqUTNPdm8NOiPqXvbd0LD @@ -145,7 +205,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAwJJREFUWEftlttrE1EQxv3XBEEQBEHwqVAoCIIgCAXBJ0EoCH0RBEEQBF8FwRdB + wAAADsABataJCQAAAwJJREFUWEftlttrE1EQxv3XBEEQBEHwqVAoCIIgCAXBJ0EoCH0RBEEQBF8FwRdB UKsttbZqvdW7FVQsrdRbm2Q3u8m0v92OTs6Z3cQHKUIHPpKdnD3zzTcz52TX7psN2U7sEPi/COybaMjY Qio3ljJ5+r0jS61ugYWfHZlYzmX8ZSoH7jbdd6swEAECX1psS9qRHvvR7spq2t16Ki3ffLz6KRuYSF8C R2ZbspKUQSBw5WMmxx62onWH77fk8oe2rGXlWj5PPE6idSFqCZx+nvzOGpmH78WBQxyaasr017x4BzXO @@ -164,7 +224,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAkVJREFUWEftlt9LFUEUx/vXhCDwKQiCQOgpCIJAEIKeBJ+CEAKfAkEIAkEQfAkC + wAAADsABataJCQAAAkVJREFUWEftlt9LFUEUx/vXhCDwKQiCQOgpCIJAEIKeBJ+CEAKfAkEIAkEQfAkC S/wVZv7IpMwfpSVZlhbK7t79cXe6n7NMzO6Ou7qtSeCBL5d77pk7n5k5c85caBt11FnqHOD/Bbg66aon X0K15TTVykFT9a366uIze2yRKgEw+a8gVtjMj0ggsKc7oTW+SJUAWDl2Y8b743u8GYivc76Rii1TJYBt N1bT36OU78qEKwAP14OUv0yVANYOm7Ltpu/mS08AyAXTX6ZKAKwSe/QxUJfHXZn81V6k/FYqXJtyrWOO @@ -180,7 +240,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAbxJREFUWEftlu9HBEEYx/vXIiIiehUR/QHR24heRfQyIiJ6Fb2MiKur01X0Q5eK + wAAADsABataJCQAAAbxJREFUWEftlu9HBEEYx/vXIiIiehUR/QHR24heRfQyIiJ6Fb2MiKur01X0Q5eK ri6lurq6iuv25+w+9Z01uRuzu7PuUrTLhzMzZj7P7LPPcx2dmTr9JqlAKpAKJBbo3TRotuhQ5pFRseZR 2fRp74XR0o1DQztm09qu9ToNbBtNYzLaAths+tymqu1T4c2juUuHxgsWjR5afHyt7BLzicv054JDcTge ea9GtAVWH1we7dixpZwHODhXYVSxfH4bbRNYvHbozvC/I4tj+TaQHd41WxcYOTDp3QkiUs2HcfTKKP/M @@ -221,7 +281,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAXxJREFUWEftlUFLw0AQhf1rgidPglevnvwHXr2K4NWrIAiC4MWTUiwUigdRClUQ + wAAADsABataJCQAAAXxJREFUWEftlUFLw0AQhf1rgidPglevnvwHXr2K4NWrIAiC4MWTUiwUigdRClUQ tVoUq6igSdMkzdiXbMgmHc10Gy2FPHhQkunMt5Od3ZnZI4sm6RKgBBgZYLnepb17j2ovPrXtgFpWP/yN Z3jH/ec3iwEWqzZVOz7l6fDRC2O5HJxFAHPHFl2891WJfNVffTYPZxHAzp2rUif69AI6aHth6/EZstq8 6rG5shYBnL2lW//UDWjhJGnzfMWim680BDqm5/jJIoAPN1BpI21du0MxG5c99TaSPWDOxnAWb8I8r547 @@ -233,7 +293,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAJtJREFUWEdjcNz79f9A4lEHjDpg6Dog5ti3/yff/Pl//t2f/6knv2NVQwwm2wFr + wAAADsABataJCQAAAJtJREFUWEdjcNz79f9A4lEHjDpg6Dog5ti3/yff/Pl//t2f/6knv2NVQwwm2wFr Hv3+DwN7X/zBqoYYTLYDJt78CbX+//9lD35hVUMMJtsB7vu//m++8uN/57WfYDY2NcTgoZsIqYXJdgAo 4T3//g+MByQRgrIfDIDY2NQQg0cdMOqAUQeMOmDUAaMOINsB1MKjDhh1wAA74Ot/AF92BBjcG70TAAAA AElFTkSuQmCC @@ -242,7 +302,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAg5JREFUWEftls8rBVEUx/1ryspKKStlpaxsla2yslRWSikrZakUCT1RChGR3+Vn + wAAADsABataJCQAAAg5JREFUWEftls8rBVEUx/1ryspKKStlpaxsla2yslRWSikrZakUCT1RChGR3+Vn Epr33ng/Dh+6GveeMzPPj6R867uZuXfO55577rnT1DwbyW/6H+BvAbTORzK4HcvMRUU272pyUaq/evu+ JvNXVRnaiaVtoajOtZwLgMBjh08S1yRT1brI1FklN0gmQM9qSa7LL19tUI+VuvStl9VvJp0KMLBVzrVq S2RjeC9Wv+1sArDyrwRPqn/DzoQKwJ5/Ju2WilWRjiW9JlQACs4SFX8Sham5jetSuHmJZGj6vBLEwQEA @@ -283,7 +343,7 @@ iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAmFJREFUSEutlStwU1EQhiMQCAQCgUAgEBHIioqKCiQCUYmojEAgEBERnYlAIBCR + wAAADsABataJCQAAAmFJREFUSEutlStwU1EQhiMQCAQCgUAgEBHIioqKCiQCUYmojEAgEBERnYlAIBCR yMqKiIoKRERF3hNBZioqIiIqKioiKiLa7zvsCW24hEC6M/+ce/bx7949r9K6MhgMdvr9/jHjiPHzeDx+ EqbNBdJtMAcXoAVuTBbmzQWyb2CWqyZB3SSdTudFcthUIDsC01ar9cg5ySom6PV6r5PDpgLhnoSMTXAA Lpn/CPPDCIRViGeRqM1YDtP6QmANXEmyhDn64263uzUajZ6G+78JJPtBdgiZbVgAXZ3xHPgHOxGyvrgb @@ -299,7 +359,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAA9xJREFUaEPVmi1Q3FAUhREIBAKBqKhAICoQCAQCgahAIBAVCAQCgahAICoQzCAq + wAAADsABataJCQAAA9xJREFUaEPVmi1Q3FAUhREIBAKBqKhAICoQCAQCgahAIBAVCAQCgahAICoQzCAq KhAIRAUCgahAIBAIBKKiAoGoQCAQCAQCgWBm6Xf2HXZnySZ5+WGy/WbuJPvevSd33ybvLztUF61WaxZb wbawn9gZdo3dYH+xc+wI23x5efmMjTh0MCCxe5KKBv9FhzYP+YyHtOLhC2w4vHlIZtZ5RUPMvsObh2TW nFc0xJw7vHlI5ofzioaYO4c3B3mMkMgm9hDSKgZxB9ik5eoF/TnETzguYcMu7kDdMnatRKqAxjN2wOkH @@ -322,7 +382,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAABTdJREFUaEPVmS1QHTEQxxEIBKICUVGBqKhAIBAVFQgEEoFAIBAViIoKZAUzFQgE + wAAADsABataJCQAABTdJREFUaEPVmS1QHTEQxxEIBKICUVGBqKhAIBAVFQgEEoFAIBAViIoKZAUzFQgE ogKBQCAQCERFBQKBqKioQCAQiIqKioqKis7Q/v65vZBccveO93Hv9T+zk2R3k93cy8du3tRjcX9/f/k3 AO0/Jvo/gMO35nuIGRNPDnD0DY7NWtNDX7zwOcK8iT3q+ncCjB/IK8rPFE+NPaW6+FWgt2Qq0pmmfWb8 K4puJ1E6X4L2DfSc6gy065gVIN+neALNUv/omAba3U0CY/rZE8D/Af20ZhbIf0O5/SHZmZkYLbD1FGPf @@ -350,7 +410,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAABH1JREFUaEPNmS9Q3UAQhxEIBAKBQFQgEAgEAoFAIBAIBAKJQCAqKxAIBDMIBAJR + wAAADsABataJCQAABH1JREFUaEPNmS9Q3UAQhxEIBAKBQFQgEAgEAoFAIBAIBAKJQCAqKxAIBDMIBAJR gUBUICoQCAQCgahAVCAqKioQiIoKBKKCGej3u+w7Eu4uSSEvL9/MTpLdzd6+vNv7kwyleH5+Hn16etpC LpBfyB9k3czdhuSnlTTHAujmzaW7kOcEid5mKQeMm1t3IfkzS1ZPXN1mT0+ey0lz6S4kOpWlniXPYdpM riaQDWQXWTV1tyDpHZJzcL5launXkN9mkm3NTN2CxI4sR7EgHcdV9I9ScLxBviAf3A0VcMuonbaDkrNE @@ -375,7 +435,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAAuVJREFUaEPVma9zE0EUxyMqEMiKigpERUUlojKiAlFRgUAgIpEV/QOYQVZUVCAQ + wAAADsABataJCQAAAuVJREFUaEPVma9zE0EUxyMqEMiKigpERUUlojKiAlFRgUAgIpEV/QOYQVZUVCAQ CGRFRAWiAhGBQCIQFREREUgkMwmf9/aba0M2vST3YzefmTd3+31vd793c7m92XSaYjqddieTySVxRwyJ AXGFfqaSPMHgc4x+4rgU8n0Ou+qSD5gy87/cZQnUjYl9dc2D/+887c/EKacviBPOr4m/ngTOv6prevDT DbYCmHur1Byk7LdRXAT0lEoLpi5lyO+85CjkP6jUam8kpwUjd/Jkpk4lRyF/pFKrvZecFoyM5MlMlf44 @@ -393,7 +453,7 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAABpVJREFUaEPFmi9QHTsUxhEIBKICUYGoQCAqEE9UVCAQCASiAlGBqKh4oqICUcEM + wAAADsABataJCQAABpVJREFUaEPFmi9QHTsUxhEIBKICUYGoQCAqEE9UVCAQCASiAlGBqKh4oqICUcEM ouIJBAKBQCAqKhAIBAKBqEAgKioQFYgnKioQCGagvy97NpvsbrJ7917e+2bObHLOl7PJ5t9J7p2aFB4e Hl49Pj5u8dxFviIXyE+Tb8gxsod8greMTFvR/wdUYIbKrCIHyA35kUCZX8gR8obsrLn9b8ALN4dUOgV8 /UY+kHzaXuEFy7zoyr01Azh3yDVybvIDuTVzElbmjb1ucsD3LI6/FK9pAtsNsktyBXlmxRrANou8hruD @@ -427,7 +487,7 @@ iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wQAADsEBuJFr7QAAARtJREFUSEvdlK8SAVEUxjcIgiCKgigIHkUQBEEQPMQ2QfAIoiCIHkAQBA8gegRB + wAAADsABataJCQAAARtJREFUSEvdlK8SAVEUxjcIgiCKgigIHkUQBEEQPMQ2QfAIoiCIHkAQBA8gegRB MLPrd/d8u2OMsbhrBr+ZM+d+97vn8++u4P+J47gWRdGEWlF7lVtPqYaOvQfhISEn+l2cR411/DUYHCkn F84ONfYczJQYOtu4gV5TA5Z9+sZ2DfSRVtZ4Pgw0bdRA72kl2Qnsbc010G1Z+XC4wUyYFrorK4O9iQtO QfdkFQOBS2UnoDuy/CGvQmB2u1i736su2x8C5xZtoGey/CFsqtwEtLsAVdnvQ4i7urfvfEf5Pc0pd8JX @@ -437,28 +497,28 @@ - iVBORw0KGgoAAAANSUhEUgAAACIAAAAiCAIAAAC1JZyVAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAASpSURBVEhLvZZ/TJVVGMe/cEEQLlyueH/B5d7Lr4v8CgPM - CqqJWoRrkIpglBrKMMdyLU3+sc1kkFbKlmlruYhlscw23FyL6cC2XI7Vao1loFCKFiQ/TQZFnJ7z9hrv - fe4F2xidfcbe7+U833Pec57nnBfiyv8B1/ME1zPSDfETxM8QVxXoucerz8xw7UGn4jsBIRTGIW5BDEOM - QIxBTN75vU/pyWI94XqaLohe6dt+Jqiu2rgu35adHpPgdDqiXS67M83tWJ0b/fyzpk/eDpPdRiEueTlo - 4HqaaxA38fR6C5ACuDMyHEUFti0bzJVlpm2l5tIiyyM50ZGmOCA12e3q69CJIS8HDVxPc0MO4451AEuB - xI0bLAdfNjbWh508qm86oj9aZygvtVRsMp9+KwRIO/CiUa4ec9DAtQqtGEGRkzj1jv6ZddY4u8vfL4HG - ozdTSAxekLitxEx9bGbXE6ussjOlCUUxKwWuVS7LXfn60wUt74fKvKKdH4D4FeK6kma0Gb9B/C4nMT7m - 9/B90QE6d+dXATJHKJBZKXAtoa5/ovXkQiBZ2ZglNP0IY1yK2/Fgtn1lbtTKnKgHsuxLEh36MNob+WZK - n6Qvm4MpkLspcC2hgphCVmqM3Rbb2RH48TH9nu2L1xdYc7PtlGAJLidBDySL11irn4tsPh7a+X2gKTJu - +T12uXS+6olrCa1MP2iOBStsMuy/kbvMDiRQoAxnhr6HoQqfwI5NJkohXUBCkttRkBdFcv+uRW/uM7x7 - IJw48oqhZrexaoupcLWtcI0tOd4JpFc+ZZK1TOHMcMa9+UXW9vG68N2Vi9c+bo0IpT2gDUjyhHYlQR9O - /5Kbd6zOIF+FAn1lAdcqPyp1I9DeHHT2RAht7ORlv6Fv/a9fCOg5H9Ddht7zGLyISTpjJpAcb8lMi5VL - R+k3w1nA9TT07gLxzljAJZeCipxcKK1vKs9jqeKvnUK8IcRes2Vp/kMGyhpZN8zkDlxPQzEC5cWLgAxx - LUb0BYtuvbiaLvqrxFib0DT4F+woVRKBOWjg2gOBmp2UcvePDI2IqX4xOaQaa9rAwDDN49Vdcxum8TUa - xnWpa1B19WodHV2A88PDcxlmEq0ndEBEa1u76urVzp67CIR/8ZFOXj8sXAPXHtzGDy00jK7xg89UV6/2 - XsMZwL/znE4ecSxcA9ceDGL4Gz9atZraRtXVq+3b30AdRr/zk0nIwjVw7QHdvj1kgortlLi+W3nF6zQR - mf10frNwDVx7QBfoAEx6rMqvVl29Wt6jeyzh8r1lZxaugWsPaI7juNeNpLStqqtXS0wpz0pSvkZ8HWX/ - wrUHVKFTKFqBUONa1dWrhUQUPZknu81yBBBccwSqymh3clRXrwbkVZbcpWgIrjkCDbVBgKGp6bRqrGmf - t1wALKfqdXMeph/jN3RZGQYgNnP5C8UbD5ZtPrR5a31J2cH0zCo6/wsfWyhu+/u8yrRwzaEb4RamegNr - XwpalgKXGbYImMJgNcjUOLRXJ0Z1Ms2oGwv0hGsfkAVdAbQs9DlBnzh0C1Al0t8/lB/pWrrbGATXs0Hf - EvRJTfXxD7NmMIPreYLreeEK/gbPMONd+jRQGwAAAABJRU5ErkJggg== + iVBORw0KGgoAAAANSUhEUgAAACIAAAAiCAIAAAC1JZyVAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL + EQAACxEBf2RfkQAABKlJREFUSEu9ln9MlVUYx79wQRAuXK54f8Hl3suvi/wKA8wKqolahGuQimCUGsow + x3ItTf6xzWSQVsqWaWu5iGWxzDbcXIvpwLZcjtVqjWWgUIoWJD9NBkWcnvP2Gu997gXbGJ19xt7v5Tzf + c95znuecF+LK/wHX8wTXM9IN8RPEzxBXFei5x6vPzHDtQafiOwEhFMYhbkEMQ4xAjEFM3vm9T+nJYj3h + epouiF7p234mqK7auC7flp0ek+B0OqJdLrszze1YnRv9/LOmT94Ok91GIS55OWjgepprEDfx9HoLkAK4 + MzIcRQW2LRvMlWWmbaXm0iLLIznRkaY4IDXZ7err0IkhLwcNXE9zQw7jjnUAS4HEjRssB182NtaHnTyq + bzqiP1pnKC+1VGwyn34rBEg78KJRrh5z0MC1Cq0YQZGTOPWO/pl11ji7y98vgcajN1NIDF6QuK3ETH1s + ZtcTq6yyM6UJRTErBa5VLstd+frTBS3vh8q8op0fgPgV4rqSZrQZv0H8LicxPub38H3RATp351cBMkco + kFkpcC2hrn+i9eRCIFnZmCU0/QhjXIrb8WC2fWVu1MqcqAey7EsSHfow2hv5ZkqfpC+bgymQuylwLaGC + mEJWaozdFtvZEfjxMf2e7YvXF1hzs+2UYAkuJ0EPJIvXWKufi2w+Htr5faApMm75PXa5dL7qiWsJrUw/ + aI4FK2wy7L+Ru8wOJFCgDGeGvoehCp/Ajk0mSiFdQEKS21GQF0Vy/65Fb+4zvHsgnDjyiqFmt7Fqi6lw + ta1wjS053gmkVz5lkrVM4cxwxr35Rdb28brw3ZWL1z5ujQilPaANSPKEdiVBH07/kpt3rM4gX4UCfWUB + 1yo/KnUj0N4cdPZECG3s5GW/oW/9r18I6Dkf0N2G3vMYvIhJOmMmkBxvyUyLlUtH6TfDWcD1NPTuAvHO + WMAll4KKnFworW8qz2Op4q+dQrwhxF6zZWn+QwbKGlk3zOQOXE9DMQLlxYuADHEtRvQFi269uJou+qvE + WJvQNPgX7ChVEoE5aODaA4GanZRy948MjYipfjE5pBpr2sDAMM3j1V1zG6bxNRrGdalrUHX1ah0dXYDz + w8NzGWYSrSd0QERrW7vq6tXOnrsIhH/xkU5ePyxcA9ce3MYPLTSMrvGDz1RXr/ZewxnAv/OcTh5xLFwD + 1x4MYvgbP1q1mtpG1dWr7dvfQB1Gv/OTScjCNXDtAd2+PWSCiu2UuL5becXrNBGZ/XR+s3ANXHtAF+gA + THqsyq9WXb1a3qN7LOHyvWVnFq6Baw9ojuO4142ktK2qq1dLTCnPSlK+RnwdZf/CtQdUoVMoWoFQ41rV + 1auFRBQ9mSe7zXIEEFxzBKrKaHdyVFevBuRVltylaAiuOQINtUGAoanptGqsaZ+3XAAsp+p1cx6mH+M3 + dFkZBiA2c/kLxRsPlm0+tHlrfUnZwfTMKjr/Cx9bKG77+7zKtHDNoRvhFqZ6A2tfClqWApcZtgiYwmA1 + yNQ4tFcnRnUyzagbC/SEax+QBV0BtCz0OUGfOHQLUCXS3z+UH+lautsYBNezQd8S9ElN9fEPs2Ywg+t5 + gut54Qr+Bs8w4136NFAbAAAAAElFTkSuQmCC @@ -487,4 +547,8 @@ NLGJMOvFeKEqY0ecbYgf8dDTDERBTzMQBX0DvaZvoNdMcQMi/wKHssFyJvHDuwAAAABJRU5ErkJggg== + + + ..\Resources\modern-tray-icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs index 827c5c06..dcb776fb 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs @@ -388,6 +388,14 @@ private void LoadEntityList() CbEntity.Items.Add(item.EntityName); } break; + + case HassDomain.Button: + foreach (var item in HassApiManager.ButtonList) + { + CbEntity.AutoCompleteCustomSource.Add(item); + CbEntity.Items.Add(item); + } + break; } } diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.Designer.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.Designer.cs new file mode 100644 index 00000000..0f922ff9 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.Designer.cs @@ -0,0 +1,209 @@ + +using HASS.Agent.Resources.Localization; + +namespace HASS.Agent.Forms.Commands.CommandConfig +{ + partial class AdvancedSensorSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + var resources = new System.ComponentModel.ComponentResourceManager(typeof(AdvancedSensorSettings)); + BtnSave = new Syncfusion.WinForms.Controls.SfButton(); + LblInfo1 = new Label(); + LblDeviceClass = new Label(); + TbDeviceClass = new TextBox(); + LblUnitOfMeasurement = new Label(); + TbUnitOfMeasurement = new TextBox(); + LblStateClass = new Label(); + TbStateClass = new TextBox(); + SuspendLayout(); + // + // BtnSave + // + BtnSave.AccessibleDescription = "Saves and closes the window."; + BtnSave.AccessibleName = "Save"; + BtnSave.AccessibleRole = AccessibleRole.PushButton; + BtnSave.BackColor = Color.FromArgb(63, 63, 70); + BtnSave.Dock = DockStyle.Bottom; + BtnSave.Font = new Font("Segoe UI", 10F); + BtnSave.ForeColor = Color.FromArgb(241, 241, 241); + BtnSave.Location = new Point(0, 299); + BtnSave.Name = "BtnSave"; + BtnSave.Size = new Size(349, 37); + BtnSave.Style.BackColor = Color.FromArgb(63, 63, 70); + BtnSave.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); + BtnSave.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); + BtnSave.Style.ForeColor = Color.FromArgb(241, 241, 241); + BtnSave.Style.HoverBackColor = Color.FromArgb(63, 63, 70); + BtnSave.Style.HoverForeColor = Color.FromArgb(241, 241, 241); + BtnSave.Style.PressedForeColor = Color.Black; + BtnSave.TabIndex = 8; + BtnSave.Text = Languages.WebViewCommandConfig_BtnSave; + BtnSave.UseVisualStyleBackColor = false; + BtnSave.Click += BtnSave_Click; + // + // LblInfo1 + // + LblInfo1.AccessibleDescription = "Warning regarding advanced settings"; + LblInfo1.AccessibleName = "Advanced settings warning"; + LblInfo1.AccessibleRole = AccessibleRole.StaticText; + LblInfo1.Font = new Font("Segoe UI", 10F); + LblInfo1.Location = new Point(12, 13); + LblInfo1.Name = "LblInfo1"; + LblInfo1.Size = new Size(325, 45); + LblInfo1.TabIndex = 3; + LblInfo1.Text = Languages.AdvancedSensorConfig_Warning; + LblInfo1.TextAlign = ContentAlignment.MiddleCenter; + // + // LblDeviceClass + // + LblDeviceClass.AccessibleDescription = "Device class textbox"; + LblDeviceClass.AccessibleName = "Device class"; + LblDeviceClass.AccessibleRole = AccessibleRole.StaticText; + LblDeviceClass.AutoSize = true; + LblDeviceClass.Font = new Font("Segoe UI", 10F); + LblDeviceClass.Location = new Point(34, 81); + LblDeviceClass.Name = "LblDeviceClass"; + LblDeviceClass.Size = new Size(81, 19); + LblDeviceClass.TabIndex = 5; + LblDeviceClass.Text = Languages.AdvancedSensorConfig_LblDeviceClass; + // + // TbDeviceClass + // + TbDeviceClass.AccessibleDescription = "Device class of the sensor"; + TbDeviceClass.AccessibleName = "URL"; + TbDeviceClass.AccessibleRole = AccessibleRole.Text; + TbDeviceClass.BackColor = Color.FromArgb(63, 63, 70); + TbDeviceClass.BorderStyle = BorderStyle.FixedSingle; + TbDeviceClass.Font = new Font("Segoe UI", 10F); + TbDeviceClass.ForeColor = Color.FromArgb(241, 241, 241); + TbDeviceClass.Location = new Point(34, 103); + TbDeviceClass.Name = "TbDeviceClass"; + TbDeviceClass.Size = new Size(282, 25); + TbDeviceClass.TabIndex = 0; + // + // LblUnitOfMeasurement + // + LblUnitOfMeasurement.AccessibleDescription = "Unit of measurement textbox"; + LblUnitOfMeasurement.AccessibleName = "Unit of measurement"; + LblUnitOfMeasurement.AccessibleRole = AccessibleRole.StaticText; + LblUnitOfMeasurement.AutoSize = true; + LblUnitOfMeasurement.Font = new Font("Segoe UI", 10F); + LblUnitOfMeasurement.Location = new Point(34, 143); + LblUnitOfMeasurement.Name = "LblUnitOfMeasurement"; + LblUnitOfMeasurement.Size = new Size(139, 19); + LblUnitOfMeasurement.TabIndex = 10; + LblUnitOfMeasurement.Text = Languages.AdvancedSensorConfig_LblUnitOfMeasurement; + // + // TbUnitOfMeasurement + // + TbUnitOfMeasurement.AccessibleDescription = "Device class of the sensor"; + TbUnitOfMeasurement.AccessibleName = "URL"; + TbUnitOfMeasurement.AccessibleRole = AccessibleRole.Text; + TbUnitOfMeasurement.BackColor = Color.FromArgb(63, 63, 70); + TbUnitOfMeasurement.BorderStyle = BorderStyle.FixedSingle; + TbUnitOfMeasurement.Font = new Font("Segoe UI", 10F); + TbUnitOfMeasurement.ForeColor = Color.FromArgb(241, 241, 241); + TbUnitOfMeasurement.Location = new Point(34, 165); + TbUnitOfMeasurement.Name = "TbUnitOfMeasurement"; + TbUnitOfMeasurement.Size = new Size(282, 25); + TbUnitOfMeasurement.TabIndex = 9; + // + // LblStateClass + // + LblStateClass.AccessibleDescription = "State class textbox"; + LblStateClass.AccessibleName = "State class"; + LblStateClass.AccessibleRole = AccessibleRole.StaticText; + LblStateClass.AutoSize = true; + LblStateClass.Font = new Font("Segoe UI", 10F); + LblStateClass.Location = new Point(34, 206); + LblStateClass.Name = "LblStateClass"; + LblStateClass.Size = new Size(72, 19); + LblStateClass.TabIndex = 12; + LblStateClass.Text = Languages.AdvancedSensorConfig_LblStateClass; + // + // TbStateClass + // + TbStateClass.AccessibleDescription = "State class of the sensor"; + TbStateClass.AccessibleName = "URL"; + TbStateClass.AccessibleRole = AccessibleRole.Text; + TbStateClass.BackColor = Color.FromArgb(63, 63, 70); + TbStateClass.BorderStyle = BorderStyle.FixedSingle; + TbStateClass.Font = new Font("Segoe UI", 10F); + TbStateClass.ForeColor = Color.FromArgb(241, 241, 241); + TbStateClass.Location = new Point(34, 228); + TbStateClass.Name = "TbStateClass"; + TbStateClass.Size = new Size(282, 25); + TbStateClass.TabIndex = 11; + // + // AdvancedSensorConfig + // + AccessibleDescription = "Configuration for the webview command, like position and url."; + AccessibleName = "Webview configuration"; + AccessibleRole = AccessibleRole.Window; + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + BackColor = Color.FromArgb(45, 45, 48); + CaptionBarColor = Color.FromArgb(63, 63, 70); + CaptionFont = new Font("Segoe UI", 10F); + CaptionForeColor = Color.FromArgb(241, 241, 241); + ClientSize = new Size(349, 336); + Controls.Add(LblStateClass); + Controls.Add(TbStateClass); + Controls.Add(LblUnitOfMeasurement); + Controls.Add(TbUnitOfMeasurement); + Controls.Add(BtnSave); + Controls.Add(LblDeviceClass); + Controls.Add(TbDeviceClass); + Controls.Add(LblInfo1); + DoubleBuffered = true; + ForeColor = Color.FromArgb(241, 241, 241); + Icon = (Icon)resources.GetObject("$this.Icon"); + MaximizeBox = false; + MetroColor = Color.FromArgb(63, 63, 70); + Name = "AdvancedSensorConfig"; + ShowMaximizeBox = false; + ShowMinimizeBox = false; + StartPosition = FormStartPosition.CenterScreen; + Text = Languages.AdvancedSensorConfig_Title; + Load += AdvancedSensorConfig_Load; + ResumeLayout(false); + PerformLayout(); + } + + #endregion + private Syncfusion.WinForms.Controls.SfButton BtnSave; + private System.Windows.Forms.Label LblInfo1; + private Label LblDeviceClass; + private TextBox TbDeviceClass; + private Label LblUnitOfMeasurement; + private TextBox TbUnitOfMeasurement; + private Label LblStateClass; + private TextBox TbStateClass; + } +} + diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.cs new file mode 100644 index 00000000..abff8f7c --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.cs @@ -0,0 +1,58 @@ +using HASS.Agent.Functions; +using HASS.Agent.Models.Internal; +using HASS.Agent.Resources.Localization; +using HASS.Agent.Shared.Models.Internal; +using Newtonsoft.Json; +using Serilog; +using Syncfusion.Windows.Forms; +using Syncfusion.WinForms.Controls.Styles; + +namespace HASS.Agent.Forms.Commands.CommandConfig; + +public partial class AdvancedSensorSettings : MetroForm +{ + + public SensorAdvancedSettings AdvancedSettings { get; private set; } = null; + + public AdvancedSensorSettings(string wmiAdvancedInfo = "") + { + InitializeComponent(); + + if (string.IsNullOrEmpty(wmiAdvancedInfo)) + return; + + var advancedInfo = JsonConvert.DeserializeObject(wmiAdvancedInfo); + if (advancedInfo == null) + return; + + AdvancedSettings = advancedInfo; + } + + private void AdvancedSensorConfig_Load(object sender, EventArgs e) + { + CaptionBarHeight = 26; + + if (AdvancedSettings != null) + SetStoredVariables(); + + Opacity = 100; + } + + private void SetStoredVariables() + { + TbDeviceClass.Text = AdvancedSettings.DeviceClass; + TbUnitOfMeasurement.Text = AdvancedSettings.UnitOfMeasurement; + TbStateClass.Text = AdvancedSettings.StateClass; + } + + private void BtnSave_Click(object sender, EventArgs e) + { + AdvancedSettings ??= new SensorAdvancedSettings(); + + AdvancedSettings.DeviceClass = TbDeviceClass.Text; + AdvancedSettings.UnitOfMeasurement = TbUnitOfMeasurement.Text; + AdvancedSettings.StateClass = TbStateClass.Text; + + DialogResult = DialogResult.OK; + } +} diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.de.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.de.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.de.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.es.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.es.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.es.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.fr.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.fr.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.fr.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.nl.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.nl.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.nl.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.resx new file mode 100644 index 00000000..9cfd6d5f --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAMDAAAAEAIAAQBQAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAwAAAAMAgGAAAAVwL5hwAAAAFz + UkdCAK7OHOkAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAEpUlEQVRoQ+2Ya4hU + ZRjHn7nujFnGZmksbVlecLMkpTXCiCQoiD5VHyzoQgRlEdEHMQoiKBI/2AW6YHQlCKIoqCCICEnKUCIr + VyzLsix101xT5z7T+3vPvOPb2TMz55w5zrQwPxjPOHP2nP//ub3vnNhVnx6ryRQmXj9OWfoGek3fQK/p + GwjKlWcl5JklGXl00YAMT+v89l1dB244Jyn3zkvX/ydyqFiTB78uyJ7j1fonwelaBmzxm8YrWvRgOibr + l3SWia4YsMVvPFCRx74vNCLfqYmTbsAt/vHtBamoorXLBxOr5qX0OUE5qQZGTot7ijdg4rP9Ff1++JT/ + QQZmpGL1dw7XnJ3Ux+8OVyeJB7Jz2xwn8m/sLuljUCIxQP2+cGlG3rsiKy8vy8hFpzuX3XbYie7IjLgs + PzOh3xvs0nrux6J8/GdZvw9KxwYQTxMuODWuIzxHlcLaxY4JyoZXQiXmkQsH9BoAbvHv/hZOPHRkwIin + CWnG2zfndLlklU5MEHlKxzaxemE6MvEQ2oBbPBNlb64ma7blW5q4tt4XUYiH0CvxhtGMzJ0eb4hnohiM + eMoop9oAU2MTVS3+8pkJ+fCPsnz5l9MfnRLKANF/7bKMrnnKhsi78TJBZqImVAkRdURTEqsXDmixbmzR + tpmoCX3FdTsKWiSiENcrE6GvhijEhTHBqI2KwFfi5ozBFbMSujHDmLjj/HD7Hi8CGWAhYvqwEDHTefk1 + YTM0LaYnWBT4nkKIRzCNu+totSHAbNKY90a8XV5gSsdd/3z/9p6SvPlLadI+yS++wmCLR/A9W/J6IbK/ + a5YJL/GsGZzHd2zm1l/inMv1pyfVPwFoa8At3uwqWUVtEytmJT0b2xa/L1/T5m/clJOHv528O127eEDe + WZ6V2Rn/JloaGD3DW7wBE/w8hGXqXHCbsCP/vDK885+q/uyJi53rmvPT8ZgsHUyoY/1kn7Q8/VaV3mbi + gWY22+TPx0/saxB139a8fLC3rBc9GC/UtFmTGUrGNrt08ESWePmlpYGJ+v6GDZs7MvaWmExg0mb3sao8 + tbMoT253yozabiYebhp2RuuWg/+9TjtaGuBXkl3P3Bjc4k0veIER4G+biV95bkr/niiqU9/6taQb+6ER + 5/rtaGmAerXrGQHczK94QNT7vzvl5SWeYNx1gRN9RiqZwgBDwc9EamkA7BtiwtzMj3jDSz8VdS/Y16K3 + 7GBQgq+rjNN38Mm+shwtt+8F3wsZTxhWzU/rEYd4Uh0E+iinpgCDgOjecl5KhrJOhBG7bkdRDwQeOXIO + 2/RELKbe1zy364auPVok4verAFw9O9noJSJM1AmIvd4wvfiMBwVw3cbj+uhF2xKKCjJw/ZAjnpH6ys8l + ufmLvGxWv8xoWCJvRvazPxTlgQUpfe7YRL1ZmtC1DAA7WcYxwwFoVvNcCD5SPzWfVqP37rlp3dyU0p1f + 5RtriRddywAwUo14mK9GJ3zzd0UvfK+qrFBGiAeGRCvx0NUMuGFMmqcaNPSG0awuGyL/4i5/Ty26mgE3 + NLGJMOvFeKEqY0ecbYgf8dDTDERBTzMQBX0DvaZvoNdMcQMi/wKHssFyJvHDuwAAAABJRU5ErkJggg== + + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.ru.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.ru.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.ru.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.tr.resx b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.tr.resx new file mode 100644 index 00000000..deb06fac --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorConfig/AdvancedSensorSettings.tr.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.Designer.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.Designer.cs index ee6b3fed..e2070687 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.Designer.cs @@ -66,7 +66,8 @@ private void InitializeComponent() PbService = new PictureBox(); LblSpecificClient = new Label(); BtnTest = new Syncfusion.WinForms.Controls.SfButton(); - CbNetworkCard = new ComboBox(); + BtnAdvSettings = new Syncfusion.WinForms.Controls.SfButton(); + CbNetworkCard = new ComboBox(); NumRound = new Syncfusion.Windows.Forms.Tools.NumericUpDownExt(); LblDigits = new Label(); CbApplyRounding = new CheckBox(); @@ -521,10 +522,34 @@ private void InitializeComponent() BtnTest.UseVisualStyleBackColor = false; BtnTest.Visible = false; BtnTest.Click += BtnTest_Click; - // - // CbNetworkCard - // - CbNetworkCard.AccessibleDescription = "List of available network cards."; + // + // BtnAdvSettings + // + BtnAdvSettings.AccessibleDescription = "Shows popup with advanced sensor settings."; + BtnAdvSettings.AccessibleName = "Advanced settings"; + BtnAdvSettings.AccessibleRole = AccessibleRole.PushButton; + BtnAdvSettings.BackColor = Color.FromArgb(63, 63, 70); + BtnAdvSettings.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); + BtnAdvSettings.ForeColor = Color.FromArgb(241, 241, 241); + BtnAdvSettings.Location = new Point(955, 446); + BtnAdvSettings.Name = "BtnTest"; + BtnAdvSettings.Size = new Size(354, 25); + BtnAdvSettings.Style.BackColor = Color.FromArgb(63, 63, 70); + BtnAdvSettings.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); + BtnAdvSettings.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); + BtnAdvSettings.Style.ForeColor = Color.FromArgb(241, 241, 241); + BtnAdvSettings.Style.HoverBackColor = Color.FromArgb(63, 63, 70); + BtnAdvSettings.Style.HoverForeColor = Color.FromArgb(241, 241, 241); + BtnAdvSettings.Style.PressedForeColor = Color.Black; + BtnAdvSettings.TabIndex = 48; + BtnAdvSettings.Text = Languages.SensorsMod_BtnAdvancedSettings; + BtnAdvSettings.UseVisualStyleBackColor = false; + BtnAdvSettings.Visible = false; + BtnAdvSettings.Click += BtnAdvSettings_Click; + // + // CbNetworkCard + // + CbNetworkCard.AccessibleDescription = "List of available network cards."; CbNetworkCard.AccessibleName = "Network cards"; CbNetworkCard.AccessibleRole = AccessibleRole.DropList; CbNetworkCard.BackColor = Color.FromArgb(63, 63, 70); @@ -688,6 +713,7 @@ private void InitializeComponent() Controls.Add(LblDescription); Controls.Add(LblSetting1); Controls.Add(BtnStore); + Controls.Add(BtnAdvSettings); Controls.Add(TbSetting1); Controls.Add(LblType); Controls.Add(LblSeconds); @@ -755,7 +781,8 @@ private void InitializeComponent() private Label LblSpecificClient; private ColumnHeader ClmId; private Syncfusion.WinForms.Controls.SfButton BtnTest; - private ComboBox CbNetworkCard; + private Syncfusion.WinForms.Controls.SfButton BtnAdvSettings; + private ComboBox CbNetworkCard; private Syncfusion.Windows.Forms.Tools.NumericUpDownExt NumRound; private Label LblDigits; internal CheckBox CbApplyRounding; diff --git a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs index ba5c019d..e852dbbf 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Sensors/SensorsMod.cs @@ -13,783 +13,808 @@ using HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.SingleValue; using HASS.Agent.Managers.DeviceSensors; using HASS.Agent.Managers; +using HASS.Agent.Forms.Commands.CommandConfig; +using Newtonsoft.Json; namespace HASS.Agent.Forms.Sensors { - public partial class SensorsMod : MetroForm - { - internal readonly ConfiguredSensor Sensor; - - private readonly bool _serviceMode; - private readonly string _serviceDeviceName; + public partial class SensorsMod : MetroForm + { + internal readonly ConfiguredSensor Sensor; - private bool _interfaceLockedWrongType; - private bool _loading = true; + private readonly bool _serviceMode; + private readonly string _serviceDeviceName; - private readonly Dictionary _networkCards = new(); - private readonly Dictionary _internalSensors = new(); - - private SensorType _selectedSensorType = SensorType.ActiveWindowSensor; + private bool _interfaceLockedWrongType; + private bool _loading = true; - public SensorsMod(ConfiguredSensor sensor, bool serviceMode = false, string serviceDeviceName = "") - { - Sensor = sensor; + private readonly Dictionary _networkCards = new(); + private readonly Dictionary _internalSensors = new(); - _serviceMode = serviceMode; - _serviceDeviceName = serviceDeviceName; - - InitializeComponent(); - - BindListViewTheme(); - - BindComboBoxTheme(); - } - - public SensorsMod(bool serviceMode = false, string serviceDeviceName = "") - { - Sensor = new ConfiguredSensor(); - - _serviceMode = serviceMode; - _serviceDeviceName = serviceDeviceName; - - InitializeComponent(); - - BindListViewTheme(); - - BindComboBoxTheme(); - } - - private void CbIgnoreAvailability_CheckedChanged(object sender, EventArgs e) - { - if ((sender as CheckBox).Checked) - MessageBoxAdv.Show(this, Languages.SensorsMod_IgnoreAvailability_Info, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); - } - - private void SetCbIgnoreAvailability(bool check) - { - CbIgnoreAvailability.CheckedChanged -= CbIgnoreAvailability_CheckedChanged; - CbIgnoreAvailability.Checked = check; - CbIgnoreAvailability.CheckedChanged += CbIgnoreAvailability_CheckedChanged; - } - - private void BindListViewTheme() - { - LvSensors.DrawItem += ListViewTheme.DrawItem; - LvSensors.DrawSubItem += ListViewTheme.DrawSubItem; - LvSensors.DrawColumnHeader += ListViewTheme.DrawColumnHeader; - } - - private void BindComboBoxTheme() => CbNetworkCard.DrawItem += ComboBoxTheme.DrawDictionaryStringStringItem; - - private void SensorMod_Load(object sender, EventArgs e) - { - // catch all key presses - KeyPreview = true; - - // load sensors - LvSensors.BeginUpdate(); - foreach (var sensor in SensorsManager.SensorInfoCards.Select(x => x.Value)) - { - var lvSensor = new ListViewItem(sensor.Key.ToString()); - lvSensor.SubItems.Add(sensor.Name); - lvSensor.SubItems.Add(sensor.MultiValue ? "√" : string.Empty); - lvSensor.SubItems.Add(sensor.AgentCompatible ? "√" : string.Empty); - lvSensor.SubItems.Add(sensor.SatelliteCompatible ? "√" : string.Empty); - LvSensors.Items.Add(lvSensor); - } - LvSensors.EndUpdate(); - - _networkCards.Add("*", Languages.SensorsMod_All); - foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) - _networkCards.Add(nic.Id, nic.Name); - - _internalSensors.Add("none", Languages.SensorsMod_None); - foreach (var internalSensor in InternalDeviceSensorsManager.AvailableSensors) - { - var internalSensorType = internalSensor.Type.ToString(); - _internalSensors.Add(internalSensorType, internalSensorType); - } - - CbIgnoreAvailability.CheckedChanged += CbIgnoreAvailability_CheckedChanged; - - // load in gui - CbNetworkCard.DataSource = new BindingSource(_networkCards, null); - - // load or set sensor - if (Sensor.Id == Guid.Empty) - { - Sensor.Id = Guid.NewGuid(); - Text = Languages.SensorsMod_Title_New; - - // done - _loading = false; - return; - } - - // we're modding, load it - LoadSensor(); - Text = Languages.SensorsMod_Title_Mod; - - // done - _loading = false; - } - - /// - /// Loads the to-be-modded sensor - /// - private void LoadSensor() - { - // load the card - var sensorCard = SensorsManager.SensorInfoCards[Sensor.Type]; - - // set type - _selectedSensorType = sensorCard.SensorType; - - // load the type - TbSelectedType.Text = _selectedSensorType.ToString(); - - // select it as well - foreach (ListViewItem lvi in LvSensors.Items) - { - if (lvi.Text != sensorCard.Key.ToString()) continue; - lvi.Selected = true; - LvSensors.SelectedItems[0].EnsureVisible(); - break; - } - - // set gui - var guiOk = SetType(false); - if (!guiOk) return; - - // set the name - TbName.Text = Sensor.EntityName; - if (!string.IsNullOrWhiteSpace(TbName.Text)) TbName.SelectionStart = TbName.Text.Length; - - // set the friendly name - TbFriendlyName.Text = Sensor.Name; - - // set interval - NumInterval.Text = Sensor.UpdateInterval?.ToString() ?? "10"; - - - SetCbIgnoreAvailability(Sensor.IgnoreAvailability); - - // set optional setting - switch (_selectedSensorType) - { - case SensorType.NamedWindowSensor: - TbSetting1.Text = Sensor.WindowName; - break; - - case SensorType.WmiQuerySensor: - TbSetting1.Text = Sensor.Query; - TbSetting2.Text = Sensor.Scope; - CbApplyRounding.Checked = Sensor.ApplyRounding; - NumRound.Text = Sensor.Round?.ToString() ?? "2"; - break; - - case SensorType.PerformanceCounterSensor: - TbSetting1.Text = Sensor.Category; - TbSetting2.Text = Sensor.Counter; - TbSetting3.Text = Sensor.Instance; - CbApplyRounding.Checked = Sensor.ApplyRounding; - NumRound.Text = Sensor.Round?.ToString() ?? "2"; - break; - - case SensorType.ProcessActiveSensor: - TbSetting1.Text = Sensor.Query; - break; - - case SensorType.ServiceStateSensor: - TbSetting1.Text = Sensor.Query; - break; - - case SensorType.PowershellSensor: - TbSetting1.Text = Sensor.Query; - CbApplyRounding.Checked = Sensor.ApplyRounding; - NumRound.Text = Sensor.Round?.ToString() ?? "2"; - break; - - case SensorType.NetworkSensors: - if (_networkCards.ContainsKey(Sensor.Query)) - CbNetworkCard.SelectedItem = new KeyValuePair(Sensor.Query, _networkCards[Sensor.Query]); - break; - - case SensorType.InternalDeviceSensor: - if (_internalSensors.ContainsKey(Sensor.Query)) - CbNetworkCard.SelectedItem = new KeyValuePair(Sensor.Query, _internalSensors[Sensor.Query]); - break; - - case SensorType.WindowStateSensor: - TbSetting1.Text = Sensor.Query; - break; - - case SensorType.LastActiveSensor: - CbApplyRounding.Checked = Sensor.ApplyRounding; - NumRound.Text = Sensor.Round?.ToString() ?? LastActiveSensor.DefaultTimeWindow.ToString(); ; - break; - } - } - - /// - /// Change the UI depending on the selected type - /// - /// - private bool SetType(bool setDefaultValues = true) - { - if (LvSensors.SelectedItems.Count == 0) - { - // was the interface locked? - if (_interfaceLockedWrongType) UnlockWrongClient(); - return false; - } - - // find the sensor card - var sensorId = int.Parse(LvSensors.SelectedItems[0].Text); - var sensorCard = SensorsManager.SensorInfoCards.Where(card => card.Value.Key == sensorId) - .Select(card => card.Value).FirstOrDefault(); - if (sensorCard == null) return false; - - // can the current client load this type? - if (_serviceMode && !sensorCard.SatelliteCompatible) - { - LockWrongClient(); - return false; - } - - if (!_serviceMode && !sensorCard.AgentCompatible) - { - LockWrongClient(); - return false; - } - - // was the interface locked? - if (_interfaceLockedWrongType) UnlockWrongClient(); - - // set default values - if (setDefaultValues) - { - TbName.Text = sensorCard.SensorType.GetSensorName(); - NumInterval.Text = sensorCard.RefreshTimer.ToString(); - _selectedSensorType = sensorCard.SensorType; - } - - SetCbIgnoreAvailability(false); - - TbSelectedType.Text = sensorCard.SensorType.ToString(); - TbDescription.Text = sensorCard.Description; - CbApplyRounding.Visible = false; - NumRound.Visible = false; - LblDigits.Visible = false; - - // process the interface - switch (sensorCard.SensorType) - { - case SensorType.NamedWindowSensor: - SetWindowGui(); - break; - - case SensorType.WmiQuerySensor: - SetWmiGui(); - break; - - case SensorType.PerformanceCounterSensor: - SetPerformanceCounterGui(); - break; - - case SensorType.ProcessActiveSensor: - SetProcessGui(); - break; - - case SensorType.ServiceStateSensor: - SetServiceStateGui(); - break; - - case SensorType.NetworkSensors: - CbNetworkCard.DataSource = new BindingSource(_networkCards, null); - SetNetworkGui(); - break; - - case SensorType.InternalDeviceSensor: - CbNetworkCard.DataSource = new BindingSource(_internalSensors, null); - SetInternalSensorGui(); - break; - - case SensorType.PowershellSensor: - SetPowershellGui(); - break; - - case SensorType.WindowStateSensor: - SetWindowGui(); - break; - - case SensorType.LastActiveSensor: - SetLastActiveGui(); - break; - - default: - SetEmptyGui(); - break; - } - - return true; - } - - /// - /// Change the UI to a 'named window' type - /// - private void SetWindowGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_WindowName; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - - BtnTest.Visible = false; - })); - } - - /// - /// Change the UI to a 'wmi query' type - /// - [SuppressMessage("ReSharper", "InvertIf")] - private void SetWmiGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Wmi; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - - LblSetting2.Text = Languages.SensorsMod_LblSetting2_Wmi; - LblSetting2.Visible = true; - TbSetting2.Visible = true; - - BtnTest.Text = Languages.SensorsMod_BtnTest_Wmi; - BtnTest.Visible = true; - - CbApplyRounding.Visible = true; - if (CbApplyRounding.Checked) - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - })); - } - - /// - /// Change the UI to a 'powershell command' type - /// - [SuppressMessage("ReSharper", "InvertIf")] - private void SetPowershellGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Powershell; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - - BtnTest.Text = Languages.SensorsMod_SensorsMod_BtnTest_Powershell; - BtnTest.Visible = true; - - CbApplyRounding.Visible = true; - if (CbApplyRounding.Checked) - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - })); - } - - /// - /// Change the UI to a 'performance counter' type - /// - [SuppressMessage("ReSharper", "InvertIf")] - private void SetPerformanceCounterGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Category; - LblSetting1.Visible = true; - TbSetting1.Text = string.Empty; - TbSetting1.Visible = true; - - LblSetting2.Text = Languages.SensorsMod_LblSetting2_Counter; - LblSetting2.Visible = true; - TbSetting2.Text = string.Empty; - TbSetting2.Visible = true; - - LblSetting3.Text = Languages.SensorsMod_LblSetting3_Instance; - LblSetting3.Visible = true; - TbSetting3.Text = string.Empty; - TbSetting3.Visible = true; - - BtnTest.Text = Languages.SensorsMod_BtnTest_PerformanceCounter; - BtnTest.Visible = true; - - CbApplyRounding.Visible = true; - if (CbApplyRounding.Checked) - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - })); - } - - /// - /// Change the UI to a 'process active' type - /// - private void SetProcessGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Process; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - })); - } - - /// - /// Change the UI to a 'service state' type - /// - private void SetServiceStateGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Service; - LblSetting1.Visible = true; - TbSetting1.Visible = true; - })); - } - - /// - /// Change the UI to a 'network' type - /// - private void SetNetworkGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_Network; - LblSetting1.Visible = true; - - CbNetworkCard.Visible = true; - })); - } - - /// - /// Change the UI to a 'internal sensor' type - /// - private void SetInternalSensorGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - LblSetting1.Text = Languages.SensorsMod_LblSetting1_InternalSensor; - LblSetting1.Visible = true; - - CbNetworkCard.Visible = true; - })); - } - - /// - /// Change the UI to a 'lastactive' type - /// - private void SetLastActiveGui() - { - Invoke(new MethodInvoker(delegate - { - SetEmptyGui(); - - CbApplyRounding.Text = Languages.SensorsMod_CbApplyRounding_LastActive; - LblDigits.Text = Languages.SensorsMod_LblSeconds; - NumRound.Text = LastActiveSensor.DefaultTimeWindow.ToString(); - CbApplyRounding.Visible = true; - if (CbApplyRounding.Checked) - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - })); - } - - /// - /// Change the UI to a general type - /// - private void SetEmptyGui() - { - Invoke(new MethodInvoker(delegate - { - LblSetting1.Visible = false; - - CbNetworkCard.Visible = false; - - TbSetting1.Text = string.Empty; - TbSetting1.Visible = false; - - LblSetting2.Visible = false; - TbSetting2.Text = string.Empty; - TbSetting2.Visible = false; - - LblSetting3.Visible = false; - TbSetting3.Text = string.Empty; - TbSetting3.Visible = false; - - CbApplyRounding.Text = Languages.SensorsMod_CbApplyRounding; - CbApplyRounding.Checked = false; - CbApplyRounding.Visible = false; - NumRound.Visible = false; - LblDigits.Text = Languages.SensorsMod_LblDigits; - LblDigits.Visible = false; - - BtnTest.Visible = false; - })); - } - - private void LvSensors_SelectedIndexChanged(object sender, EventArgs e) - { - if (_loading) return; - - // set the ui to the selected type - SetType(); - - // set focus to the name field - ActiveControl = TbName; - if (!string.IsNullOrWhiteSpace(TbName.Text)) TbName.SelectionStart = TbName.Text.Length; - } - - /// - /// Prepare the sensor for processing - /// - /// - /// - private void BtnStore_Click(object sender, EventArgs e) - { - if (LvSensors.SelectedItems.Count == 0) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // get and check type - var sensorId = int.Parse(LvSensors.SelectedItems[0].Text); - var sensorCard = SensorsManager.SensorInfoCards.Where(card => card.Value.Key == sensorId) - .Select(card => card.Value).FirstOrDefault(); - - if (sensorCard == null) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox2, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // get and check name - var name = TbName.Text.Trim(); - if (string.IsNullOrEmpty(name)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox3, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbName; - return; - } - - if (CompatHelper.HassVersionEqualOrOver("2023.8") && name.Contains(SharedHelperFunctions.GetSafeConfiguredDeviceName())) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_DeviceNameInSensorName, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); - } - - // get friendly name - var friendlyName = string.IsNullOrEmpty(TbFriendlyName.Text.Trim()) ? name : TbFriendlyName.Text.Trim(); - - // name contains illegal chars? - var sanitized = SharedHelperFunctions.GetSafeValue(name); - if (sanitized != name) - { - var confirmSanitize = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_MessageBox_Sanitize, sanitized), Variables.MessageBoxTitle, MessageBoxButtons.OKCancel, MessageBoxIcon.Question); - if (confirmSanitize != DialogResult.OK) - { - ActiveControl = TbName; - return; - } - - TbName.Text = sanitized; - name = sanitized; - } - - // name already used? - if (!_serviceMode && Variables.SingleValueSensors.Any(x => string.Equals(x.EntityName, name, StringComparison.InvariantCultureIgnoreCase) && x.Id != Sensor.Id.ToString())) - { - var confirm = MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox4, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); - if (confirm != DialogResult.Yes) - { - ActiveControl = TbName; - return; - } - } - - if (!_serviceMode && Variables.MultiValueSensors.Any(x => string.Equals(x.EntityName, name, StringComparison.InvariantCultureIgnoreCase) && x.Id != Sensor.Id.ToString())) - { - var confirm = MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox5, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); - if (confirm != DialogResult.Yes) - { - ActiveControl = TbName; - return; - } - } - - // get and check update interval - var interval = (int)NumInterval.Value; - if (interval is < 1 or > 43200) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox6, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = NumInterval; - return; - } - - // get and check round value - var applyRounding = CbApplyRounding.Checked; - int? round = null; - if (applyRounding) - { - round = (int)NumRound.Value; - if (round is < 0 or > 20) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox12, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = NumRound; - return; - } - } - - // check and set optional settings - switch (sensorCard.SensorType) - { - case SensorType.NamedWindowSensor: - var window = TbSetting1.Text.Trim(); - if (string.IsNullOrEmpty(window)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox7, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.WindowName = window; - break; - - case SensorType.WmiQuerySensor: - var query = TbSetting1.Text.Trim(); - var scope = TbSetting2.Text.Trim(); - - // test the query - if (string.IsNullOrEmpty(query)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox8, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - - // test the scope - if (!string.IsNullOrEmpty(scope)) - { - if (!HelperFunctions.CheckWmiScope(scope)) - { - var scopeQ = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_WmiTestFailed, scope), - Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); - - if (scopeQ != DialogResult.Yes) return; - } - } - - Sensor.Query = query; - Sensor.Scope = scope; - break; - - case SensorType.PerformanceCounterSensor: - var category = TbSetting1.Text.Trim(); - var counter = TbSetting2.Text.Trim(); - var instance = TbSetting3.Text.Trim(); - if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counter)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox9, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.Category = category; - Sensor.Counter = counter; - Sensor.Instance = instance; - break; - - case SensorType.ProcessActiveSensor: - var process = TbSetting1.Text.Trim(); - if (string.IsNullOrEmpty(process)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.Query = process; - break; - - case SensorType.ServiceStateSensor: - var service = TbSetting1.Text.Trim(); - if (string.IsNullOrEmpty(service)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox11, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.Query = service; - break; - - case SensorType.NetworkSensors: - Sensor.Query = "*"; - if (CbNetworkCard.SelectedItem != null) - { - var item = (KeyValuePair)CbNetworkCard.SelectedItem; - Sensor.Query = item.Key; - } - break; - - case SensorType.InternalDeviceSensor: - if (CbNetworkCard.SelectedItem != null) - { - var item = (KeyValuePair)CbNetworkCard.SelectedItem; - if (item.Value == Languages.SensorsMod_None) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = CbNetworkCard; - return; - } - - Sensor.Query = item.Key; - } - break; - - case SensorType.PowershellSensor: - Sensor.Query = TbSetting1.Text.Trim(); - break; - - case SensorType.WindowStateSensor: - var windowprocess = TbSetting1.Text.Trim(); - if (string.IsNullOrEmpty(windowprocess)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - ActiveControl = TbSetting1; - return; - } - Sensor.Query = windowprocess; - break; + private SensorType _selectedSensorType = SensorType.ActiveWindowSensor; + + public SensorsMod(ConfiguredSensor sensor, bool serviceMode = false, string serviceDeviceName = "") + { + Sensor = sensor; + + _serviceMode = serviceMode; + _serviceDeviceName = serviceDeviceName; + + InitializeComponent(); + + BindListViewTheme(); + + BindComboBoxTheme(); + } + + public SensorsMod(bool serviceMode = false, string serviceDeviceName = "") + { + Sensor = new ConfiguredSensor(); + + _serviceMode = serviceMode; + _serviceDeviceName = serviceDeviceName; + + InitializeComponent(); + + BindListViewTheme(); + + BindComboBoxTheme(); + } + + private void CbIgnoreAvailability_CheckedChanged(object sender, EventArgs e) + { + if ((sender as CheckBox).Checked) + MessageBoxAdv.Show(this, Languages.SensorsMod_IgnoreAvailability_Info, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + private void SetCbIgnoreAvailability(bool check) + { + CbIgnoreAvailability.CheckedChanged -= CbIgnoreAvailability_CheckedChanged; + CbIgnoreAvailability.Checked = check; + CbIgnoreAvailability.CheckedChanged += CbIgnoreAvailability_CheckedChanged; + } + + private void BindListViewTheme() + { + LvSensors.DrawItem += ListViewTheme.DrawItem; + LvSensors.DrawSubItem += ListViewTheme.DrawSubItem; + LvSensors.DrawColumnHeader += ListViewTheme.DrawColumnHeader; + } + + private void BindComboBoxTheme() => CbNetworkCard.DrawItem += ComboBoxTheme.DrawDictionaryStringStringItem; + + private void SensorMod_Load(object sender, EventArgs e) + { + // catch all key presses + KeyPreview = true; + + // load sensors + LvSensors.BeginUpdate(); + foreach (var sensor in SensorsManager.SensorInfoCards.Select(x => x.Value)) + { + var lvSensor = new ListViewItem(sensor.Key.ToString()); + lvSensor.SubItems.Add(sensor.Name); + lvSensor.SubItems.Add(sensor.MultiValue ? "√" : string.Empty); + lvSensor.SubItems.Add(sensor.AgentCompatible ? "√" : string.Empty); + lvSensor.SubItems.Add(sensor.SatelliteCompatible ? "√" : string.Empty); + LvSensors.Items.Add(lvSensor); + } + LvSensors.EndUpdate(); + + _networkCards.Add("*", Languages.SensorsMod_All); + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + _networkCards.Add(nic.Id, nic.Name); + + _internalSensors.Add("none", Languages.SensorsMod_None); + foreach (var internalSensor in InternalDeviceSensorsManager.AvailableSensors) + { + var internalSensorType = internalSensor.Type.ToString(); + _internalSensors.Add(internalSensorType, internalSensorType); + } + + CbIgnoreAvailability.CheckedChanged += CbIgnoreAvailability_CheckedChanged; + + // load in gui + CbNetworkCard.DataSource = new BindingSource(_networkCards, null); + + // load or set sensor + if (Sensor.Id == Guid.Empty) + { + Sensor.Id = Guid.NewGuid(); + Text = Languages.SensorsMod_Title_New; + + // done + _loading = false; + return; + } + + // we're modding, load it + LoadSensor(); + Text = Languages.SensorsMod_Title_Mod; + + // done + _loading = false; + } + + /// + /// Loads the to-be-modded sensor + /// + private void LoadSensor() + { + // load the card + var sensorCard = SensorsManager.SensorInfoCards[Sensor.Type]; + + // set type + _selectedSensorType = sensorCard.SensorType; + + // load the type + TbSelectedType.Text = _selectedSensorType.ToString(); + + // select it as well + foreach (ListViewItem lvi in LvSensors.Items) + { + if (lvi.Text != sensorCard.Key.ToString()) continue; + lvi.Selected = true; + LvSensors.SelectedItems[0].EnsureVisible(); + break; + } + + // set gui + var guiOk = SetType(false); + if (!guiOk) return; + + // set the name + TbName.Text = Sensor.EntityName; + if (!string.IsNullOrWhiteSpace(TbName.Text)) TbName.SelectionStart = TbName.Text.Length; + + // set the friendly name + TbFriendlyName.Text = Sensor.Name; + + // set interval + NumInterval.Text = Sensor.UpdateInterval?.ToString() ?? "10"; + + + SetCbIgnoreAvailability(Sensor.IgnoreAvailability); + + // set optional setting + switch (_selectedSensorType) + { + case SensorType.NamedWindowSensor: + TbSetting1.Text = Sensor.WindowName; + break; + + case SensorType.WmiQuerySensor: + TbSetting1.Text = Sensor.Query; + TbSetting2.Text = Sensor.Scope; + CbApplyRounding.Checked = Sensor.ApplyRounding; + NumRound.Text = Sensor.Round?.ToString() ?? "2"; + break; + + case SensorType.PerformanceCounterSensor: + TbSetting1.Text = Sensor.Category; + TbSetting2.Text = Sensor.Counter; + TbSetting3.Text = Sensor.Instance; + CbApplyRounding.Checked = Sensor.ApplyRounding; + NumRound.Text = Sensor.Round?.ToString() ?? "2"; + break; + + case SensorType.ProcessActiveSensor: + TbSetting1.Text = Sensor.Query; + break; + + case SensorType.ServiceStateSensor: + TbSetting1.Text = Sensor.Query; + break; + + case SensorType.PowershellSensor: + TbSetting1.Text = Sensor.Query; + CbApplyRounding.Checked = Sensor.ApplyRounding; + NumRound.Text = Sensor.Round?.ToString() ?? "2"; + break; + + case SensorType.NetworkSensors: + if (_networkCards.ContainsKey(Sensor.Query)) + CbNetworkCard.SelectedItem = new KeyValuePair(Sensor.Query, _networkCards[Sensor.Query]); + break; + + case SensorType.InternalDeviceSensor: + if (_internalSensors.ContainsKey(Sensor.Query)) + CbNetworkCard.SelectedItem = new KeyValuePair(Sensor.Query, _internalSensors[Sensor.Query]); + break; + + case SensorType.WindowStateSensor: + TbSetting1.Text = Sensor.Query; + break; + + case SensorType.LastActiveSensor: + CbApplyRounding.Checked = Sensor.ApplyRounding; + NumRound.Text = Sensor.Round?.ToString() ?? LastActiveSensor.DefaultTimeWindow.ToString(); ; + break; + + case SensorType.ScreenshotSensor: + TbSetting1.Text = Sensor.Query; + break; + } + } + + /// + /// Change the UI depending on the selected type + /// + /// + private bool SetType(bool setDefaultValues = true) + { + if (LvSensors.SelectedItems.Count == 0) + { + // was the interface locked? + if (_interfaceLockedWrongType) UnlockWrongClient(); + return false; + } + + // find the sensor card + var sensorId = int.Parse(LvSensors.SelectedItems[0].Text); + var sensorCard = SensorsManager.SensorInfoCards.Where(card => card.Value.Key == sensorId) + .Select(card => card.Value).FirstOrDefault(); + if (sensorCard == null) return false; + + // can the current client load this type? + if (_serviceMode && !sensorCard.SatelliteCompatible) + { + LockWrongClient(); + return false; + } + + if (!_serviceMode && !sensorCard.AgentCompatible) + { + LockWrongClient(); + return false; + } + + // was the interface locked? + if (_interfaceLockedWrongType) UnlockWrongClient(); + + // set default values + if (setDefaultValues) + { + TbName.Text = sensorCard.SensorType.GetSensorName(); + NumInterval.Text = sensorCard.RefreshTimer.ToString(); + _selectedSensorType = sensorCard.SensorType; + } + + SetCbIgnoreAvailability(false); + + TbSelectedType.Text = sensorCard.SensorType.ToString(); + TbDescription.Text = sensorCard.Description; + CbApplyRounding.Visible = false; + NumRound.Visible = false; + LblDigits.Visible = false; + + // process the interface + switch (sensorCard.SensorType) + { + case SensorType.NamedWindowSensor: + SetWindowGui(); + break; + + case SensorType.WmiQuerySensor: + SetWmiGui(); + break; + + case SensorType.PerformanceCounterSensor: + SetPerformanceCounterGui(); + break; + + case SensorType.ProcessActiveSensor: + SetProcessGui(); + break; + + case SensorType.ServiceStateSensor: + SetServiceStateGui(); + break; + + case SensorType.NetworkSensors: + CbNetworkCard.DataSource = new BindingSource(_networkCards, null); + SetNetworkGui(); + break; + + case SensorType.InternalDeviceSensor: + CbNetworkCard.DataSource = new BindingSource(_internalSensors, null); + SetInternalSensorGui(); + break; + + case SensorType.PowershellSensor: + SetPowershellGui(); + break; + + case SensorType.WindowStateSensor: + SetWindowGui(); + break; + + case SensorType.LastActiveSensor: + SetLastActiveGui(); + break; + + case SensorType.ScreenshotSensor: + SetScreenshotGui(); + break; + + default: + SetEmptyGui(); + break; + } + + return true; + } + + /// + /// Change the UI to a 'named window' type + /// + private void SetWindowGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_WindowName; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + + BtnTest.Visible = false; + })); + } + + /// + /// Change the UI to a 'wmi query' type + /// + [SuppressMessage("ReSharper", "InvertIf")] + private void SetWmiGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Wmi; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + + LblSetting2.Text = Languages.SensorsMod_LblSetting2_Wmi; + LblSetting2.Visible = true; + TbSetting2.Visible = true; + + BtnTest.Text = Languages.SensorsMod_BtnTest_Wmi; + BtnTest.Visible = true; + + CbApplyRounding.Visible = true; + if (CbApplyRounding.Checked) + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + })); + } + + /// + /// Change the UI to a 'powershell command' type + /// + [SuppressMessage("ReSharper", "InvertIf")] + private void SetPowershellGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Powershell; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + + BtnTest.Text = Languages.SensorsMod_SensorsMod_BtnTest_Powershell; + BtnTest.Visible = true; + + CbApplyRounding.Visible = true; + if (CbApplyRounding.Checked) + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + })); + } + + /// + /// Change the UI to a 'performance counter' type + /// + [SuppressMessage("ReSharper", "InvertIf")] + private void SetPerformanceCounterGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Category; + LblSetting1.Visible = true; + TbSetting1.Text = string.Empty; + TbSetting1.Visible = true; + + LblSetting2.Text = Languages.SensorsMod_LblSetting2_Counter; + LblSetting2.Visible = true; + TbSetting2.Text = string.Empty; + TbSetting2.Visible = true; + + LblSetting3.Text = Languages.SensorsMod_LblSetting3_Instance; + LblSetting3.Visible = true; + TbSetting3.Text = string.Empty; + TbSetting3.Visible = true; + + BtnTest.Text = Languages.SensorsMod_BtnTest_PerformanceCounter; + BtnTest.Visible = true; + + CbApplyRounding.Visible = true; + if (CbApplyRounding.Checked) + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + })); + } + + /// + /// Change the UI to a 'process active' type + /// + private void SetProcessGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Process; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + })); + } + + /// + /// Change the UI to a 'service state' type + /// + private void SetServiceStateGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Service; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + })); + } + + /// + /// Change the UI to a 'network' type + /// + private void SetNetworkGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_Network; + LblSetting1.Visible = true; + + CbNetworkCard.Visible = true; + })); + } + + /// + /// Change the UI to a 'internal sensor' type + /// + private void SetInternalSensorGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_InternalSensor; + LblSetting1.Visible = true; + + CbNetworkCard.Visible = true; + })); + } + + /// + /// Change the UI to a 'lastactive' type + /// + private void SetLastActiveGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + CbApplyRounding.Text = Languages.SensorsMod_CbApplyRounding_LastActive; + LblDigits.Text = Languages.SensorsMod_LblSeconds; + NumRound.Text = LastActiveSensor.DefaultTimeWindow.ToString(); + CbApplyRounding.Visible = true; + if (CbApplyRounding.Checked) + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + })); + } + + private void SetScreenshotGui() + { + Invoke(new MethodInvoker(delegate + { + SetEmptyGui(); + + LblSetting1.Text = Languages.SensorsMod_LblSetting1_ScreenNumber; + LblSetting1.Visible = true; + TbSetting1.Visible = true; + + BtnTest.Visible = false; + })); + } + + /// + /// Change the UI to a general type + /// + private void SetEmptyGui() + { + Invoke(new MethodInvoker(delegate + { + LblSetting1.Visible = false; + + CbNetworkCard.Visible = false; + + TbSetting1.Text = string.Empty; + TbSetting1.Visible = false; + + LblSetting2.Visible = false; + TbSetting2.Text = string.Empty; + TbSetting2.Visible = false; + + LblSetting3.Visible = false; + TbSetting3.Text = string.Empty; + TbSetting3.Visible = false; + + CbApplyRounding.Text = Languages.SensorsMod_CbApplyRounding; + CbApplyRounding.Checked = false; + CbApplyRounding.Visible = false; + NumRound.Visible = false; + LblDigits.Text = Languages.SensorsMod_LblDigits; + LblDigits.Visible = false; + + BtnTest.Visible = false; + BtnAdvSettings.Visible = true; + })); + } + + private void LvSensors_SelectedIndexChanged(object sender, EventArgs e) + { + if (_loading) return; + + // set the ui to the selected type + SetType(); + + // set focus to the name field + ActiveControl = TbName; + if (!string.IsNullOrWhiteSpace(TbName.Text)) TbName.SelectionStart = TbName.Text.Length; + } + + /// + /// Prepare the sensor for processing + /// + /// + /// + private void BtnStore_Click(object sender, EventArgs e) + { + if (LvSensors.SelectedItems.Count == 0) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + // get and check type + var sensorId = int.Parse(LvSensors.SelectedItems[0].Text); + var sensorCard = SensorsManager.SensorInfoCards.Where(card => card.Value.Key == sensorId) + .Select(card => card.Value).FirstOrDefault(); + + if (sensorCard == null) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox2, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + // get and check name + var name = TbName.Text.Trim(); + if (string.IsNullOrEmpty(name)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox3, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbName; + return; + } + + if (CompatHelper.HassVersionEqualOrOver("2023.8") && name.Contains(SharedHelperFunctions.GetSafeConfiguredDeviceName())) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_DeviceNameInSensorName, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + + // get friendly name + var friendlyName = string.IsNullOrEmpty(TbFriendlyName.Text.Trim()) ? name : TbFriendlyName.Text.Trim(); + + // name contains illegal chars? + var sanitized = SharedHelperFunctions.GetSafeValue(name); + if (sanitized != name) + { + var confirmSanitize = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_MessageBox_Sanitize, sanitized), Variables.MessageBoxTitle, MessageBoxButtons.OKCancel, MessageBoxIcon.Question); + if (confirmSanitize != DialogResult.OK) + { + ActiveControl = TbName; + return; + } + + TbName.Text = sanitized; + name = sanitized; + } + + // name already used? + if (!_serviceMode && Variables.SingleValueSensors.Any(x => string.Equals(x.EntityName, name, StringComparison.InvariantCultureIgnoreCase) && x.Id != Sensor.Id.ToString())) + { + var confirm = MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox4, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (confirm != DialogResult.Yes) + { + ActiveControl = TbName; + return; + } + } + + if (!_serviceMode && Variables.MultiValueSensors.Any(x => string.Equals(x.EntityName, name, StringComparison.InvariantCultureIgnoreCase) && x.Id != Sensor.Id.ToString())) + { + var confirm = MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox5, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (confirm != DialogResult.Yes) + { + ActiveControl = TbName; + return; + } + } + + // get and check update interval + var interval = (int)NumInterval.Value; + if (interval is < 1 or > 43200) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox6, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = NumInterval; + return; + } + + // get and check round value + var applyRounding = CbApplyRounding.Checked; + int? round = null; + if (applyRounding) + { + round = (int)NumRound.Value; + if (round is < 0 or > 20) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox12, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = NumRound; + return; + } + } + + // check and set optional settings + switch (sensorCard.SensorType) + { + case SensorType.NamedWindowSensor: + var window = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(window)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox7, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.WindowName = window; + break; + + case SensorType.WmiQuerySensor: + var query = TbSetting1.Text.Trim(); + var scope = TbSetting2.Text.Trim(); + + // test the query + if (string.IsNullOrEmpty(query)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox8, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + + // test the scope + if (!string.IsNullOrEmpty(scope)) + { + if (!HelperFunctions.CheckWmiScope(scope)) + { + var scopeQ = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_WmiTestFailed, scope), + Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); + + if (scopeQ != DialogResult.Yes) return; + } + } + + Sensor.Query = query; + Sensor.Scope = scope; + break; + + case SensorType.PerformanceCounterSensor: + var category = TbSetting1.Text.Trim(); + var counter = TbSetting2.Text.Trim(); + var instance = TbSetting3.Text.Trim(); + if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counter)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox9, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Category = category; + Sensor.Counter = counter; + Sensor.Instance = instance; + break; + + case SensorType.ProcessActiveSensor: + var process = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(process)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Query = process; + break; + + case SensorType.ServiceStateSensor: + var service = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(service)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox11, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Query = service; + break; + + case SensorType.NetworkSensors: + Sensor.Query = "*"; + if (CbNetworkCard.SelectedItem != null) + { + var item = (KeyValuePair)CbNetworkCard.SelectedItem; + Sensor.Query = item.Key; + } + break; + + case SensorType.InternalDeviceSensor: + if (CbNetworkCard.SelectedItem != null) + { + var item = (KeyValuePair)CbNetworkCard.SelectedItem; + if (item.Value == Languages.SensorsMod_None) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = CbNetworkCard; + return; + } + + Sensor.Query = item.Key; + } + break; + + case SensorType.PowershellSensor: + Sensor.Query = TbSetting1.Text.Trim(); + break; + + case SensorType.WindowStateSensor: + var windowprocess = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(windowprocess)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Query = windowprocess; + break; case SensorType.ActiveDesktopSensor: if (!VirtualDesktopManager.Initialized) @@ -799,257 +824,285 @@ private void BtnStore_Click(object sender, EventArgs e) return; } break; + + case SensorType.ScreenshotSensor: + var screenIndex = TbSetting1.Text.Trim(); + if (string.IsNullOrEmpty(screenIndex)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_BtnStore_MessageBox10, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + ActiveControl = TbSetting1; + return; + } + Sensor.Query = screenIndex; + break; + } + + // set values + Sensor.Type = sensorCard.SensorType; + Sensor.EntityName = name; + Sensor.Name = friendlyName; + Sensor.UpdateInterval = interval; + Sensor.IgnoreAvailability = CbIgnoreAvailability.Checked; + Sensor.ApplyRounding = applyRounding; + Sensor.Round = round; + + // done + DialogResult = DialogResult.OK; + } + + /// + /// Show advanced settings for sensor + /// + /// + /// + private void BtnAdvSettings_Click(object sender, EventArgs e) + { + using var advancedSensorSettingsDialog = new AdvancedSensorSettings(Sensor.AdvancedSettings); + advancedSensorSettingsDialog.Opacity = 0; + + var ret = advancedSensorSettingsDialog.ShowDialog(); + if (ret != DialogResult.OK) + return; + + Sensor.AdvancedSettings = JsonConvert.SerializeObject(advancedSensorSettingsDialog.AdvancedSettings); + } + + private void TbDescription_LinkClicked(object sender, LinkClickedEventArgs e) + { + if (string.IsNullOrWhiteSpace(e.LinkText)) return; + if (!e.LinkText.ToLower().StartsWith("http")) return; + + HelperFunctions.LaunchUrl(e.LinkText); + } + + private void SensorsMod_ResizeEnd(object sender, EventArgs e) + { + if (Variables.ShuttingDown) return; + if (!IsHandleCreated) return; + if (IsDisposed) return; + + try + { + // hide the pesky horizontal scrollbar + ListViewTheme.ShowScrollBar(LvSensors.Handle, ListViewTheme.SB_HORZ, false); + + Refresh(); + } + catch + { + // best effort + } + } + + private void SensorsMod_KeyUp(object sender, KeyEventArgs e) + { + if (e.KeyCode != Keys.Escape) return; + Close(); + } + + private void SensorsMod_Layout(object sender, LayoutEventArgs e) + { + // hide the pesky horizontal scrollbar + ListViewTheme.ShowScrollBar(LvSensors.Handle, ListViewTheme.SB_HORZ, false); + } + + /// + /// Locks the interface if the selected entity can't be added to the current client + /// + private void LockWrongClient() + { + if (InvokeRequired) + { + Invoke(new MethodInvoker(LockWrongClient)); + return; + } + + _interfaceLockedWrongType = true; + + var requiredClient = _serviceMode ? "hass.agent" : "service"; + LblSpecificClient.Text = string.Format(Languages.SensorsMod_SpecificClient, requiredClient); + + LblSpecificClient.Visible = true; + + TbName.Enabled = false; + TbName.Text = string.Empty; + + TbFriendlyName.Enabled = false; + TbFriendlyName.Text = string.Empty; + + SetEmptyGui(); + + BtnStore.Enabled = false; + } + + /// + /// Unlocks the interface if the selected entity can be added to the current client + /// + private void UnlockWrongClient() + { + if (InvokeRequired) + { + Invoke(new MethodInvoker(UnlockWrongClient)); + return; + } + + _interfaceLockedWrongType = false; + + LblSpecificClient.Visible = false; + + TbName.Enabled = true; + TbFriendlyName.Enabled = true; + BtnStore.Enabled = true; + } + + private void BtnTest_Click(object sender, EventArgs e) + { + switch (_selectedSensorType) + { + case SensorType.WmiQuerySensor: + TestWmi(); + break; + + case SensorType.PerformanceCounterSensor: + TestPerformanceCounter(); + break; + + case SensorType.PowershellSensor: + TestPowershell(); + break; } + } + + private async void TestWmi() + { + // prepare values + var query = TbSetting1.Text.Trim(); + var scope = TbSetting2.Text.Trim(); + var applyRounding = CbApplyRounding.Checked; + var round = (int)NumRound.Value; + + if (string.IsNullOrEmpty(query)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_TestWmi_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + ActiveControl = TbSetting1; + return; + } + + // test the scope + if (!string.IsNullOrEmpty(scope)) + { + if (!HelperFunctions.CheckWmiScope(scope)) + { + var scopeQ = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_WmiTestFailed, scope), + Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); + + if (scopeQ != DialogResult.Yes) return; + } + } + + BtnTest.Enabled = false; + + // execute the test + var result = await Task.Run(() => SensorTester.TestWmiQuery(query, scope, applyRounding, round)); + + BtnTest.Enabled = true; + + if (result.Succesful) + { + MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestWmi_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + // failed + var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestWmi_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); + if (q != DialogResult.Yes) return; + + // open logs + HelperFunctions.OpenLocalFolder(Variables.LogPath); + } + + private async void TestPerformanceCounter() + { + // prepare values + var category = TbSetting1.Text.Trim(); + var counter = TbSetting2.Text.Trim(); + var instance = TbSetting3.Text.Trim(); + var applyRounding = CbApplyRounding.Checked; + var round = (int)NumRound.Value; + + if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counter)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_TestPerformanceCounter_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + } + + BtnTest.Enabled = false; + + // execute the test + var result = await Task.Run((() => SensorTester.TestPerformanceCounter(category, counter, instance, applyRounding, round))); + + BtnTest.Enabled = true; - // set values - Sensor.Type = sensorCard.SensorType; - Sensor.EntityName = name; - Sensor.Name = friendlyName; - Sensor.UpdateInterval = interval; - Sensor.IgnoreAvailability = CbIgnoreAvailability.Checked; - Sensor.ApplyRounding = applyRounding; - Sensor.Round = round; - - // done - DialogResult = DialogResult.OK; - } - - private void TbDescription_LinkClicked(object sender, LinkClickedEventArgs e) - { - if (string.IsNullOrWhiteSpace(e.LinkText)) return; - if (!e.LinkText.ToLower().StartsWith("http")) return; - - HelperFunctions.LaunchUrl(e.LinkText); - } - - private void SensorsMod_ResizeEnd(object sender, EventArgs e) - { - if (Variables.ShuttingDown) return; - if (!IsHandleCreated) return; - if (IsDisposed) return; - - try - { - // hide the pesky horizontal scrollbar - ListViewTheme.ShowScrollBar(LvSensors.Handle, ListViewTheme.SB_HORZ, false); - - Refresh(); - } - catch - { - // best effort - } - } - - private void SensorsMod_KeyUp(object sender, KeyEventArgs e) - { - if (e.KeyCode != Keys.Escape) return; - Close(); - } - - private void SensorsMod_Layout(object sender, LayoutEventArgs e) - { - // hide the pesky horizontal scrollbar - ListViewTheme.ShowScrollBar(LvSensors.Handle, ListViewTheme.SB_HORZ, false); - } - - /// - /// Locks the interface if the selected entity can't be added to the current client - /// - private void LockWrongClient() - { - if (InvokeRequired) - { - Invoke(new MethodInvoker(LockWrongClient)); - return; - } - - _interfaceLockedWrongType = true; - - var requiredClient = _serviceMode ? "hass.agent" : "service"; - LblSpecificClient.Text = string.Format(Languages.SensorsMod_SpecificClient, requiredClient); - - LblSpecificClient.Visible = true; - - TbName.Enabled = false; - TbName.Text = string.Empty; - - TbFriendlyName.Enabled = false; - TbFriendlyName.Text = string.Empty; - - SetEmptyGui(); - - BtnStore.Enabled = false; - } - - /// - /// Unlocks the interface if the selected entity can be added to the current client - /// - private void UnlockWrongClient() - { - if (InvokeRequired) - { - Invoke(new MethodInvoker(UnlockWrongClient)); - return; - } - - _interfaceLockedWrongType = false; - - LblSpecificClient.Visible = false; - - TbName.Enabled = true; - TbFriendlyName.Enabled = true; - BtnStore.Enabled = true; - } - - private void BtnTest_Click(object sender, EventArgs e) - { - switch (_selectedSensorType) - { - case SensorType.WmiQuerySensor: - TestWmi(); - break; - - case SensorType.PerformanceCounterSensor: - TestPerformanceCounter(); - break; - - case SensorType.PowershellSensor: - TestPowershell(); - break; - } - } - - private async void TestWmi() - { - // prepare values - var query = TbSetting1.Text.Trim(); - var scope = TbSetting2.Text.Trim(); - var applyRounding = CbApplyRounding.Checked; - var round = (int)NumRound.Value; - - if (string.IsNullOrEmpty(query)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_TestWmi_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); - ActiveControl = TbSetting1; - return; - } - - // test the scope - if (!string.IsNullOrEmpty(scope)) - { - if (!HelperFunctions.CheckWmiScope(scope)) - { - var scopeQ = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_WmiTestFailed, scope), - Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); - - if (scopeQ != DialogResult.Yes) return; - } - } - - BtnTest.Enabled = false; - - // execute the test - var result = await Task.Run(() => SensorTester.TestWmiQuery(query, scope, applyRounding, round)); - - BtnTest.Enabled = true; - - if (result.Succesful) - { - MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestWmi_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - // failed - var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestWmi_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); - if (q != DialogResult.Yes) return; - - // open logs - HelperFunctions.OpenLocalFolder(Variables.LogPath); - } - - private async void TestPerformanceCounter() - { - // prepare values - var category = TbSetting1.Text.Trim(); - var counter = TbSetting2.Text.Trim(); - var instance = TbSetting3.Text.Trim(); - var applyRounding = CbApplyRounding.Checked; - var round = (int)NumRound.Value; - - if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counter)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_TestPerformanceCounter_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); - return; - } - - BtnTest.Enabled = false; - - // execute the test - var result = await Task.Run((() => SensorTester.TestPerformanceCounter(category, counter, instance, applyRounding, round))); - - BtnTest.Enabled = true; - - if (result.Succesful) - { - MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPerformanceCounter_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - // failed - var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPerformanceCounter_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); - if (q != DialogResult.Yes) return; - - // open logs - HelperFunctions.OpenLocalFolder(Variables.LogPath); - } - - private async void TestPowershell() - { - // prepare values - var command = TbSetting1.Text.Trim(); - var applyRounding = CbApplyRounding.Checked; - var round = (int)NumRound.Value; - - if (string.IsNullOrEmpty(command)) - { - MessageBoxAdv.Show(this, Languages.SensorsMod_TestPowershell_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); - return; - } - - BtnTest.Enabled = false; - - // execute the test - var result = await Task.Run((() => SensorTester.TestPowershell(command, applyRounding, round))); - - BtnTest.Enabled = true; - - if (result.Succesful) - { - MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPowershell_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - // failed - var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPowershell_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); - if (q != DialogResult.Yes) return; - - // open logs - HelperFunctions.OpenLocalFolder(Variables.LogPath); - } - - private void CbRdValue_CheckedChanged(object sender, EventArgs e) - { - if (NumRound.Visible == true) - { - NumRound.Visible = false; - LblDigits.Visible = false; - } - else - { - NumRound.Visible = true; - LblDigits.Visible = true; - } - } - } + if (result.Succesful) + { + MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPerformanceCounter_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + // failed + var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPerformanceCounter_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); + if (q != DialogResult.Yes) return; + + // open logs + HelperFunctions.OpenLocalFolder(Variables.LogPath); + } + + private async void TestPowershell() + { + // prepare values + var command = TbSetting1.Text.Trim(); + var applyRounding = CbApplyRounding.Checked; + var round = (int)NumRound.Value; + + if (string.IsNullOrEmpty(command)) + { + MessageBoxAdv.Show(this, Languages.SensorsMod_TestPowershell_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + } + + BtnTest.Enabled = false; + + // execute the test + var result = await Task.Run((() => SensorTester.TestPowershell(command, applyRounding, round))); + + BtnTest.Enabled = true; + + if (result.Succesful) + { + MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPowershell_MessageBox2, result.ReturnValue), Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + // failed + var q = MessageBoxAdv.Show(this, string.Format(Languages.SensorsMod_TestPowershell_MessageBox3, result.ErrorReason), Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error); + if (q != DialogResult.Yes) return; + + // open logs + HelperFunctions.OpenLocalFolder(Variables.LogPath); + } + + private void CbRdValue_CheckedChanged(object sender, EventArgs e) + { + if (NumRound.Visible == true) + { + NumRound.Visible = false; + LblDigits.Visible = false; + } + else + { + NumRound.Visible = true; + LblDigits.Visible = true; + } + } + } } diff --git a/src/HASS.Agent/HASS.Agent/Forms/WebView.cs b/src/HASS.Agent/HASS.Agent/Forms/WebView.cs index b5983909..90444df0 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/WebView.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/WebView.cs @@ -274,10 +274,11 @@ private void WebView_FormClosing(object sender, FormClosingEventArgs e) return; } + Opacity = 0; + // do we need to stay open? if (_isTrayIcon) { - Opacity = 0; e.Cancel = true; return; } diff --git a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs index b1f7bc85..ab6d7cb9 100644 --- a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs +++ b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs @@ -236,10 +236,7 @@ internal static bool ParseMultipleKeys(string keyString, out List keys, { // any value? if (string.IsNullOrWhiteSpace(keyString)) - { - errorMsg = Languages.HelperFunctions_ParseMultipleKeys_ErrorMsg1; - return false; - } + return true; // any brackets? if (!keyString.Contains('[') || !keyString.Contains(']')) @@ -450,7 +447,7 @@ internal static void PrepareTrayIconWebView() Variables.MainForm.Invoke(new MethodInvoker(delegate { // optionally close an existing one - if (Variables.TrayIconWebView != null) Variables.TrayIconWebView.ForceClose(); + Variables.TrayIconWebView?.ForceClose(); // bind the new one Variables.TrayIconWebView = new WebView(webViewInfo); @@ -459,6 +456,22 @@ internal static void PrepareTrayIconWebView() })); } + /// + /// Shows the user-configured webview form near the tray icon + /// + /// + internal static void LaunchTrayIconWebView() + { + var webView = new WebViewInfo + { + Url = Variables.AppSettings.TrayIconWebViewUrl, + Height = Variables.AppSettings.TrayIconWebViewHeight, + Width = Variables.AppSettings.TrayIconWebViewWidth, + }; + + LaunchTrayIconWebView(webView); + } + /// /// Shows a new webview form near the tray icon /// @@ -494,7 +507,8 @@ private static void LaunchTrayIconBackgroundLoadedWebView() Variables.MainForm.Invoke(new MethodInvoker(delegate { // make sure it's ready - if (Variables.TrayIconWebView == null || Variables.TrayIconWebView.IsDisposed) PrepareTrayIconWebView(); + if (Variables.TrayIconWebView == null || Variables.TrayIconWebView.IsDisposed) + PrepareTrayIconWebView(); // show it Variables.TrayIconWebView?.MakeVisible(); @@ -517,6 +531,8 @@ private static void LaunchTrayIconCustomWebView(WebViewInfo webViewInfo) var webView = new WebView(webViewInfo); webView.Opacity = 0; + + Variables.TrayIconWebView = webView; webView.Show(); })); } @@ -546,13 +562,24 @@ internal static bool InputLanguageCheckDiffers(out bool knownToCollide, out stri var inputLanguage = InputLanguage.CurrentInputLanguage.Handle; // check for known OK languages - if (KnownOkInputLanguage.ContainsKey(inputLanguage)) return false; + if (KnownOkInputLanguage.ContainsKey(inputLanguage)) + return false; // check for known NOT OK languages - if (KnownNotOkInputLanguage.ContainsKey(inputLanguage)) + var germanLayoutDetected = false; + foreach (InputLanguage language in InputLanguage.InstalledInputLanguages) + { + if (language.Culture.TwoLetterISOLanguageName == "de") + { + germanLayoutDetected = true; + break; + } + } + + if (KnownNotOkInputLanguage.ContainsKey(inputLanguage) || germanLayoutDetected) { // get human-readable name - var langName = KnownNotOkInputLanguage[inputLanguage]; + var langName = germanLayoutDetected ? "German" : KnownNotOkInputLanguage[inputLanguage]; message = string.Format(Languages.HelperFunctions_InputLanguageCheckDiffers_ErrorMsg1, langName); return true; diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index ec6feb51..7c1b50ef 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -1,18 +1,18 @@ - - + + WinExe net6.0-windows10.0.19041.0 disable true - true + true enable HASS.Agent hassagent.ico x64 x64;x86 full - 2.0.1 + 2.1.0-beta3 HASS.Agent Team HASS.Agent Team Windows-based client for Home Assistant. Provides notifications, quick actions, commands, sensors and more. @@ -22,66 +22,71 @@ https://github.com/hass-agent/HASS.Agent MIT app.manifest - 2.0.1 - 2.0.1 + 2.1.0 + 2.1.0 HASS.Agent None win10-x64;win10-x86 true - true - false - false - false + true + false + false + false - + + 4.0 + en-US + true + true + + + + + - - - - + + - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + WV2 - - - + + + + - + - + - + + - - - Libraries\HADotNet.Core.dll @@ -90,8 +95,10 @@ Libraries\HotkeyListener.dll - + + UserControl + UserControl @@ -125,6 +132,9 @@ + + Form + @@ -138,7 +148,6 @@ Languages.resx - ConfigExternalTools.resx @@ -170,6 +179,36 @@ ConfigExternalTools.resx + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + + + ConfigGeneral.resx + ConfigGeneral.resx @@ -1580,6 +1619,24 @@ QuickActionsMod.resx + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + + + AdvancedSensorSettings.resx + SensorsConfig.resx @@ -1790,7 +1847,6 @@ Languages.resx - PreserveNewest @@ -1800,9 +1856,7 @@ \ - - \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/TrayWebViewCommand.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/TrayWebViewCommand.cs new file mode 100644 index 00000000..46dbd99d --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/TrayWebViewCommand.cs @@ -0,0 +1,58 @@ +using HASS.Agent.Functions; +using HASS.Agent.Models.Internal; +using HASS.Agent.Resources.Localization; +using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.HomeAssistant.Commands; +using Newtonsoft.Json; +using Serilog; +using Syncfusion.Windows.Forms; + +namespace HASS.Agent.HomeAssistant.Commands.InternalCommands +{ + internal class TrayWebViewCommand : InternalCommand + { + private const string DefaultName = "traywebview"; + + internal TrayWebViewCommand(string entityName = DefaultName, string name = DefaultName, CommandEntityType entityType = CommandEntityType.Switch, string id = default) : base(entityName ?? DefaultName, name ?? null, string.Empty, entityType, id) + { + State = "OFF"; + } + + public override void TurnOn() + { + if (string.IsNullOrEmpty(Variables.AppSettings.TrayIconWebViewUrl)) + { + Log.Warning("[TRAYWEBVIEW] [{name}] No webview URL is configured in 'Configuration -> Tray Icon'", EntityName); + return; + } + + HelperFunctions.LaunchTrayIconWebView(); + } + + public override void TurnOff() + { + Variables.MainForm.Invoke(() => + { + if(Variables.TrayIconWebView == null) + return; + + Variables.TrayIconWebView.Opacity = 0; + + if (!Variables.AppSettings.TrayIconWebViewBackgroundLoading) { + Variables.TrayIconWebView.ForceClose(); + Variables.TrayIconWebView.Dispose(); + } + }); + } + + public override string GetState() + { + return Variables.TrayIconWebView != null && Variables.TrayIconWebView.Opacity != 0 ? "ON" : "OFF"; + } + + public override void TurnOnWithAction(string action) + { + Log.Warning("[TRAYWEBVIEW] [{name}] Launching with action is not supported", EntityName); + } + } +} diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/HassApiManager.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/HassApiManager.cs index 524e6a4d..36a49af8 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/HassApiManager.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/HassApiManager.cs @@ -43,6 +43,7 @@ internal static class HassApiManager internal static List CoverList = new(); internal static List ClimateList = new(); internal static List MediaPlayerList = new(); + internal static List ButtonList = new(); private static readonly string[] OnStates = { "on", "playing", "open", "opening" }; private static readonly string[] OffStates = { "off", "idle", "paused", "stopped", "closed", "closing" }; @@ -389,6 +390,7 @@ private static async Task LoadEntitiesAsync(bool clearCurrent = false) CoverList.Clear(); ClimateList.Clear(); MediaPlayerList.Clear(); + ButtonList.Clear(); } try @@ -402,6 +404,7 @@ private static async Task LoadEntitiesAsync(bool clearCurrent = false) await LoadDomain("cover", CoverList); await LoadDomain("climate", ClimateList); await LoadDomain("media_player", MediaPlayerList); + await LoadDomain("button", ButtonList); if (ManagerStatus != HassManagerStatus.Failed) return; diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs index 7e60bc70..a06e73e3 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/MultiValue/PrintersSensors.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Globalization; using System.Printing; -using CoreAudio; using HASS.Agent.Models.Internal; using HASS.Agent.Shared.Functions; using HASS.Agent.Shared.HomeAssistant.Sensors.GeneralSensors.MultiValue.DataTypes; @@ -51,7 +50,7 @@ public sealed override void UpdateSensorValues() var printersCountEntityName = $"{parentSensorSafeName}_printers_count"; var printersCountId = $"{Id}_printers_count"; - var printersCountSensor = new DataTypeIntSensor(_updateInterval, printersCountEntityName, "Printers Count", printersCountId, string.Empty, "mdi:printer", string.Empty, EntityName); + var printersCountSensor = new DataTypeIntSensor(_updateInterval, printersCountEntityName, "Printers Count", printersCountId, string.Empty, "measurement", "mdi:printer", string.Empty, EntityName); printersCountSensor.SetState(printerInfo.PrintQueues.Count); AddUpdateSensor(printersCountId, printersCountSensor); @@ -63,7 +62,7 @@ public sealed override void UpdateSensorValues() var defaultQueueJobsEntityName = $"{parentSensorSafeName}_default_queue_jobs"; var defaultQueueJobsId = $"{Id}_default_queue_jobs"; - var defaultQueueJobsSensor = new DataTypeIntSensor(_updateInterval, defaultQueueJobsEntityName, "Default Queue Jobs", defaultQueueJobsId, string.Empty, "mdi:printer", string.Empty, EntityName); + var defaultQueueJobsSensor = new DataTypeIntSensor(_updateInterval, defaultQueueJobsEntityName, "Default Queue Jobs", defaultQueueJobsId, string.Empty, "measurement", "mdi:printer", string.Empty, EntityName); defaultQueueJobsSensor.SetState(printerInfo.DefaultQueueJobs); AddUpdateSensor(defaultQueueJobsId, defaultQueueJobsSensor); @@ -72,7 +71,7 @@ public sealed override void UpdateSensorValues() var printerQueueInfo = JsonConvert.SerializeObject(printer, Formatting.Indented); var printerEntityName = $"{parentSensorSafeName}_{SharedHelperFunctions.GetSafeValue(printer.Name)}"; var printerId = $"{Id}_{SharedHelperFunctions.GetSafeValue(printer.Name)}"; - var printerSensor = new DataTypeIntSensor(_updateInterval, printerEntityName, $"{printer.Name}", printerId, string.Empty, "mdi:printer", string.Empty, EntityName, true); + var printerSensor = new DataTypeIntSensor(_updateInterval, printerEntityName, $"{printer.Name}", printerId, string.Empty, "measurement", "mdi:printer", string.Empty, EntityName, true); printerSensor.SetState(printer.Jobs); printerSensor.SetAttributes(printerQueueInfo); diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveDesktopSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveDesktopSensor.cs index a5b2c14a..8bc87fae 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveDesktopSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/ActiveDesktopSensor.cs @@ -26,7 +26,7 @@ public class ActiveDesktopSensor : AbstractSingleValueSensor private string _desktopName = string.Empty; private string _attributes = string.Empty; - public ActiveDesktopSensor(int? updateInterval = null, string entityName = _defaultName, string name = _defaultName, string id = default) : base(entityName ?? _defaultName, name ?? null, updateInterval ?? 15, id) + public ActiveDesktopSensor(int? updateInterval = null, string entityName = _defaultName, string name = _defaultName, string id = default, string advancedSettings = default) : base(entityName ?? _defaultName, name ?? null, updateInterval ?? 15, id, advancedSettings: advancedSettings) { UseAttributes = true; } diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs index 99ecb2f1..62d75631 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothDevicesSensor.cs @@ -14,7 +14,7 @@ public class BluetoothDevicesSensor : AbstractSingleValueSensor private const string DefaultName = "bluetoothdevices"; private string _attributes = "{}"; - public BluetoothDevicesSensor(int? updateInterval = 30, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public BluetoothDevicesSensor(int? updateInterval = 30, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { UseAttributes = true; } @@ -33,6 +33,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:bluetooth", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability", Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs index 04ecf4d5..c61a57b8 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/BluetoothLeDevicesSensor.cs @@ -14,7 +14,7 @@ public class BluetoothLeDevicesSensor : AbstractSingleValueSensor private const string DefaultName = "bluetoothledevices"; private string _attributes = "{}"; - public BluetoothLeDevicesSensor(int? updateInterval = 30, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public BluetoothLeDevicesSensor(int? updateInterval = 30, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { UseAttributes = true; @@ -36,6 +36,7 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() Unique_id = Id, Device = deviceConfig, State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + State_class = "measurement", Icon = "mdi:bluetooth", Availability_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/availability", Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes" diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/DeviceTrackerSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/DeviceTrackerSensor.cs new file mode 100644 index 00000000..9a6029d8 --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/DeviceTrackerSensor.cs @@ -0,0 +1,104 @@ +#nullable enable +using System.Runtime.Caching; +using HASS.Agent.Shared.Extensions; +using HASS.Agent.Shared.Models.HomeAssistant; +using Windows.Devices.Geolocation; +using HASS.Agent.Models.Internal; +using Newtonsoft.Json; + +namespace HASS.Agent.HomeAssistant.Sensors.GeneralSensors.SingleValue +{ + /// + /// Sensor containing the coördinates of the device + /// XXXX Track location change event, and make non breaking + /// + public class DeviceTrackerSensor : AbstractSingleValueSensor + { + private const string DefaultName = "devicetracksensor"; + + public DeviceTrackerSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, + string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, + updateInterval ?? 30, id, advancedSettings: advancedSettings) + { + UseAttributes = true; + } + + public override DiscoveryConfigModel GetAutoDiscoveryConfig() + { + if (Variables.MqttManager == null) return null; + + var deviceConfig = Variables.MqttManager.GetDeviceConfigModel(); + if (deviceConfig == null) return null; + + return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() + { + EntityName = EntityName, + Name = Name, + Unique_id = Id, + Device = deviceConfig, + State_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/state", + Icon = "mdi:earth", + Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes", + }); + } + + public override string GetState() + { + var state = "ON"; + return state; + } + + public override string? GetAttributes() + { + ObjectCache cache = System.Runtime.Caching.MemoryCache.Default; + GeolocationInfo? fileContents = cache["location_" + this.Id] as GeolocationInfo; + string jsonPayload = ""; + + if (fileContents != null) + { + jsonPayload = JsonConvert.SerializeObject(fileContents, Formatting.Indented); + return jsonPayload; + } + + GeolocationInfo gli = new GeolocationInfo(); + + var accessStatus = Geolocator.RequestAccessAsync().GetAwaiter().GetResult(); + switch (accessStatus) + { + case GeolocationAccessStatus.Allowed: + // https://community.home-assistant.io/t/attributes-latitude-and-longitude-in-a-lovelace-map/318760 + var geolocator = new Geolocator(); + + var position = geolocator.GetGeopositionAsync().GetAwaiter().GetResult(); + var lat = position.Coordinate.Latitude.ConvertToStringDotDecimalSeperator(); + var lon = position.Coordinate.Longitude.ConvertToStringDotDecimalSeperator(); + var alt = position.Coordinate.Altitude?.ConvertToStringDotDecimalSeperator(); + var accuracy = position.Coordinate.Accuracy.ConvertToStringDotDecimalSeperator(); + var sourceType = position.Coordinate.PositionSource; + + gli = new GeolocationInfo(lon, lat, sourceType) + { + altitude = alt, + gps_accuracy = accuracy, + not_permitted = "false" + }; + + cache.Set("location_" + Id, gli, DateTimeOffset.Now.AddSeconds(UpdateIntervalSeconds - 1)); + break; + + case GeolocationAccessStatus.Denied: + // notify user: Access to location is denied + gli.not_permitted = "true - enable location access"; + break; + + case GeolocationAccessStatus.Unspecified: + // notify user: Unspecified error + gli.not_permitted = "true"; + break; + } + + jsonPayload = JsonConvert.SerializeObject(gli, Formatting.Indented); + return jsonPayload; + } + } +} diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/GeoLocationSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/GeoLocationSensor.cs index 96138fd4..78bc6d83 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/GeoLocationSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/GeoLocationSensor.cs @@ -9,7 +9,7 @@ namespace HASS.Agent.HomeAssistant.Sensors.GeneralSensors.SingleValue public class GeoLocationSensor : AbstractSingleValueSensor { private const string DefaultName = "geolocation"; - public GeoLocationSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) { } + public GeoLocationSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs index 2c261ca7..34fb6fd0 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/InternalDeviceSensor.cs @@ -17,7 +17,7 @@ public class InternalDeviceSensor : AbstractSingleValueSensor private readonly IInternalDeviceSensor _internalDeviceSensor; - public InternalDeviceSensor(string sensorType, int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) + public InternalDeviceSensor(string sensorType, int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { SensorType = Enum.Parse(sensorType); _internalDeviceSensor = InternalDeviceSensorsManager.AvailableSensors.First(s => s.Type == SensorType); @@ -48,6 +48,13 @@ public override DiscoveryConfigModel GetAutoDiscoveryConfig() if (UseAttributes) sensorDiscoveryConfigModel.Json_attributes_topic = $"{Variables.MqttManager.MqttDiscoveryPrefix()}/{Domain}/{deviceConfig.Name}/{ObjectId}/attributes"; + if (!string.IsNullOrWhiteSpace(_internalDeviceSensor.MeasurementType)) + sensorDiscoveryConfigModel.Device_class = _internalDeviceSensor.MeasurementType; + if (!string.IsNullOrWhiteSpace(_internalDeviceSensor.UnitOfMeasurement)) + sensorDiscoveryConfigModel.Unit_of_measurement = _internalDeviceSensor.UnitOfMeasurement; + if (_internalDeviceSensor.IsNumeric) + sensorDiscoveryConfigModel.State_class = "measurement"; + return AutoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(sensorDiscoveryConfigModel); } diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/MonitorPowerStateSensor.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/MonitorPowerStateSensor.cs index d4564bb1..ae984599 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/MonitorPowerStateSensor.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Sensors/GeneralSensors/SingleValue/MonitorPowerStateSensor.cs @@ -10,7 +10,7 @@ namespace HASS.Agent.HomeAssistant.Sensors.GeneralSensors.SingleValue public class MonitorPowerStateSensor : AbstractSingleValueSensor { private const string DefaultName = "monitorpowerstate"; - public MonitorPowerStateSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id) { } + public MonitorPowerStateSensor(int? updateInterval = 10, string entityName = DefaultName, string name = DefaultName, string id = default, string advancedSettings = default) : base(entityName ?? DefaultName, name ?? null, updateInterval ?? 30, id, advancedSettings: advancedSettings) { } public override DiscoveryConfigModel GetAutoDiscoveryConfig() { diff --git a/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs b/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs index ff56e52f..8507183f 100644 --- a/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs +++ b/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs @@ -14,6 +14,7 @@ using HASS.Agent.Resources.Localization; using HASS.Agent.Settings; using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers; using HASS.Agent.Shared.Models.HomeAssistant; using HASS.Agent.Shared.Mqtt; using MQTTnet; @@ -130,10 +131,14 @@ private async Task OnMqttDisconnected(MqttClientDisconnectedEventArgs arg) Variables.MainForm?.SetMqttStatus(ComponentStatus.Connecting); + var gracePeriod = Variables.AppSettings.DisconnectedGracePeriodSeconds; + // give the connection the grace period to recover var runningTimer = Stopwatch.StartNew(); - while (runningTimer.Elapsed.TotalSeconds < Variables.AppSettings.DisconnectedGracePeriodSeconds) + while (runningTimer.Elapsed.TotalSeconds < gracePeriod) { + await Task.Delay(TimeSpan.FromSeconds(5)); + if (IsConnected()) { _isReady = true; @@ -143,12 +148,20 @@ private async Task OnMqttDisconnected(MqttClientDisconnectedEventArgs arg) _status = MqttStatus.Connected; Variables.MainForm?.SetMqttStatus(ComponentStatus.Ok); - Log.Information("[MQTT] Connected"); + Log.Information("[MQTT] Reconnected from disconnection"); return; } - await Task.Delay(TimeSpan.FromSeconds(5)); + if (Variables.AppSettings.MqttIgnoreGracePeriod) + { + var lastResumed = SystemStateManager.LastEventOccurrence.TryGetValue(SystemStateEvent.Resume, out var lastResumeEventDate); + if (lastResumed && DateTime.Now < lastResumeEventDate.AddSeconds(gracePeriod)) + { + Log.Information("[MQTT] System resumed less than {gracePeriod} seconds ago, ignoring grace period on disconnection"); + break; + } + } } // nope, call it @@ -172,10 +185,14 @@ private async Task OnMqttConnectionFailed(ConnectingFailedEventArgs arg) { Variables.MainForm?.SetMqttStatus(ComponentStatus.Connecting); + var gracePeriod = Variables.AppSettings.DisconnectedGracePeriodSeconds; + // give the connection the grace period to recover var runningTimer = Stopwatch.StartNew(); - while (runningTimer.Elapsed.TotalSeconds < Variables.AppSettings.DisconnectedGracePeriodSeconds) + while (runningTimer.Elapsed.TotalSeconds < gracePeriod) { + await Task.Delay(TimeSpan.FromSeconds(5)); + if (IsConnected()) { // recovered @@ -184,12 +201,20 @@ private async Task OnMqttConnectionFailed(ConnectingFailedEventArgs arg) _status = MqttStatus.Connected; Variables.MainForm?.SetMqttStatus(ComponentStatus.Ok); - Log.Information("[MQTT] Connected"); + Log.Information("[MQTT] Reconnected from failed connection"); return; } - await Task.Delay(TimeSpan.FromSeconds(5)); + if (Variables.AppSettings.MqttIgnoreGracePeriod) + { + var lastResumed = SystemStateManager.LastEventOccurrence.TryGetValue(SystemStateEvent.Resume, out var lastResumeEventDate); + if (lastResumed && DateTime.Now < lastResumeEventDate.AddSeconds(gracePeriod)) + { + Log.Information("[MQTT] System resumed more than {gracePeriod} seconds ago, ignoring grace period on connection failed"); + break; + } + } } // nope, call it @@ -537,11 +562,38 @@ public async Task ClearDeviceConfigAsync() { if (string.IsNullOrEmpty(Variables.AppSettings.MqttDiscoveryPrefix)) Variables.AppSettings.MqttDiscoveryPrefix = "homeassistant"; - var messageBuilder = new MqttApplicationMessageBuilder() .WithTopic($"{Variables.AppSettings.MqttDiscoveryPrefix}/sensor/{Variables.DeviceConfig.Name}/availability") .WithPayload(Array.Empty()) - .WithRetainFlag(Variables.AppSettings.MqttUseRetainFlag); + .WithRetainFlag(false); + + await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); + + messageBuilder = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/devices/{Variables.DeviceConfig.Name}") + .WithPayload(Array.Empty()) + .WithRetainFlag(false); + + await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); + + messageBuilder = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/media_player/{Variables.DeviceConfig.Name}") + .WithPayload(Array.Empty()) + .WithRetainFlag(false); + + await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); + + messageBuilder = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/media_player/{Variables.DeviceConfig.Name}/thumbnail") + .WithPayload(Array.Empty()) + .WithRetainFlag(false); + + await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); + + messageBuilder = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/media_player/{Variables.DeviceConfig.Name}/state") + .WithPayload(Array.Empty()) + .WithRetainFlag(false); await _mqttClient.InternalClient.PublishAsync(messageBuilder.Build()); } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs index 9faf68b8..a8e3013b 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AccelerometerSensor.cs @@ -14,6 +14,9 @@ internal class AccelerometerSensor : IInternalDeviceSensor public const string AttributeAccelerationZ = "AccelerationZ"; public const string AttributeLastShaken = "LastShaken"; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + private readonly Accelerometer _accelerometer; public bool Available => _accelerometer != null; @@ -26,6 +29,9 @@ public string Measurement return null; var sensorReading = _accelerometer.GetCurrentReading(); + if(sensorReading == null) + return null; + var accX = Math.Round((decimal)sensorReading.AccelerationX, 2); var accY = Math.Round((decimal)sensorReading.AccelerationX, 2); var accZ = Math.Round((decimal)sensorReading.AccelerationX, 2); @@ -42,6 +48,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs index 12145bdd..c02d0a7b 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ActivitySensor.cs @@ -12,6 +12,9 @@ internal class ActivitySensor : IInternalDeviceSensor public const string AttributeConfidence = "Confidence"; public const string AttributeTimestamp = "Timestamp"; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + private readonly Windows.Devices.Sensors.ActivitySensor _activitySensor; public bool Available => _activitySensor != null; @@ -24,6 +27,9 @@ public string Measurement return null; var sensorReading = _activitySensor.GetCurrentReadingAsync().AsTask().Result; + if (sensorReading == null) + return null; + _attributes[AttributeConfidence] = sensorReading.Confidence.ToString(); _attributes[AttributeTimestamp] = sensorReading.Timestamp.ToLocalTime().ToString(); @@ -31,6 +37,8 @@ public string Measurement } } + public bool IsNumeric { get; } = false; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs index 3e1afd59..2662e6ae 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/AltimeterSensor.cs @@ -11,6 +11,9 @@ internal class AltimeterSensor : IInternalDeviceSensor { private readonly Altimeter _altimeter; + public string MeasurementType { get; } = "distance"; + public string UnitOfMeasurement { get; } = "m"; + public bool Available => _altimeter != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Altimeter; public string Measurement @@ -20,10 +23,16 @@ public string Measurement if (!Available) return null; - return _altimeter.GetCurrentReading().AltitudeChangeInMeters.ToString(); + var sensorReading = _altimeter.GetCurrentReading(); + if (sensorReading == null) + return null; + + return sensorReading.AltitudeChangeInMeters.ToString(); } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public AltimeterSensor(Altimeter altimeter) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs index 61a42c68..189883fc 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/BarometerSensor.cs @@ -11,6 +11,9 @@ internal class BarometerSensor : IInternalDeviceSensor { private readonly Barometer _barometer; + public string MeasurementType { get; } = "pressure"; + public string UnitOfMeasurement { get; } = "hPa"; + public bool Available => _barometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Barometer; public string Measurement @@ -20,10 +23,16 @@ public string Measurement if (!Available) return null; - return _barometer.GetCurrentReading().StationPressureInHectopascals.ToString(); + var sensorReading = _barometer.GetCurrentReading(); + if (sensorReading == null) + return null; + + return sensorReading.StationPressureInHectopascals.ToString(); } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public BarometerSensor(Barometer barometer) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs index 89433a79..2f94d5b6 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/CompassSensor.cs @@ -13,6 +13,9 @@ internal class CompassSensor : IInternalDeviceSensor private readonly Compass _compass; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _compass != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Compass; public string Measurement @@ -23,11 +26,16 @@ public string Measurement return null; var sensorReading = _compass.GetCurrentReading(); + if (sensorReading == null) + return null; + _attributes[AttributeMagneticNorth] = Math.Round((decimal)sensorReading.HeadingMagneticNorth, 2).ToString(); return Math.Round((decimal)sensorReading.HeadingTrueNorth, 2).ToString(); } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs index ae2024ce..39b4144b 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/GyrometerSensor.cs @@ -15,6 +15,9 @@ internal class GyrometerSensor : IInternalDeviceSensor private readonly Gyrometer _gyrometer; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _gyrometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Gyrometer; public string Measurement @@ -25,6 +28,9 @@ public string Measurement return null; var sensorReading = _gyrometer.GetCurrentReading(); + if (sensorReading == null) + return null; + var angVelX = Math.Round((decimal)sensorReading.AngularVelocityX, 2); var angVelY = Math.Round((decimal)sensorReading.AngularVelocityY, 2); var angVelZ = Math.Round((decimal)sensorReading.AngularVelocityZ, 2); @@ -41,6 +47,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs index b28382af..4844c0a9 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/HingeAngleSensor.cs @@ -11,6 +11,9 @@ internal class HingeAngleSensor : IInternalDeviceSensor { private readonly Windows.Devices.Sensors.HingeAngleSensor _hingeAngelSensor; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _hingeAngelSensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.HingeAngleSensor; public string Measurement @@ -20,11 +23,16 @@ public string Measurement if (!Available) return null; - var sensorReading = _hingeAngelSensor.GetCurrentReadingAsync().AsTask().Result.AngleInDegrees; - return Math.Round((decimal)sensorReading, 2).ToString(); + var sensorReading = _hingeAngelSensor.GetCurrentReadingAsync().AsTask().Result; + if (sensorReading == null) + return null; + + return Math.Round((decimal)sensorReading.AngleInDegrees, 2).ToString(); } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public HingeAngleSensor(Windows.Devices.Sensors.HingeAngleSensor hingeAngleSensor) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/IInternalDeviceSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/IInternalDeviceSensor.cs index 485b9a99..5159a8f8 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/IInternalDeviceSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/IInternalDeviceSensor.cs @@ -36,6 +36,9 @@ internal interface IInternalDeviceSensor public bool Available { get; } public InternalDeviceSensorType Type { get; } public string Measurement { get; } + public string MeasurementType { get; } + public string UnitOfMeasurement { get; } + public bool IsNumeric { get; } public Dictionary Attributes { get; } } } diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs index 6a0cf171..a3d1e497 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/InclinometerSensor.cs @@ -17,6 +17,9 @@ internal class InclinometerSensor : IInternalDeviceSensor private readonly Inclinometer _inclinometer; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _inclinometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Inclinometer; public string Measurement @@ -27,6 +30,9 @@ public string Measurement return null; var sensorReading = _inclinometer.GetCurrentReading(); + if (sensorReading == null) + return null; + _attributes[AttributePitchDegrees] = Math.Round((decimal)sensorReading.PitchDegrees, 2).ToString(); _attributes[AttributeYawDegrees] = Math.Round((decimal)sensorReading.YawDegrees, 2).ToString(); _attributes[AttributeRollDegrees] = Math.Round((decimal)sensorReading.RollDegrees, 2).ToString(); @@ -37,6 +43,8 @@ public string Measurement } } + public bool IsNumeric { get; } = false; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs index a3ea817a..74733911 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/LightSensor.cs @@ -11,6 +11,9 @@ internal class LightSensor : IInternalDeviceSensor { private readonly Windows.Devices.Sensors.LightSensor _lightSensor; + public string MeasurementType { get; } = "illuminance"; + public string UnitOfMeasurement { get; } = "lx"; + public bool Available => _lightSensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.LightSensor; public string Measurement @@ -19,11 +22,17 @@ public string Measurement { if (!Available) return null; + + var sensorReading = _lightSensor.GetCurrentReading(); + if (sensorReading == null) + return null; - return Math.Round((decimal)_lightSensor.GetCurrentReading().IlluminanceInLux, 2).ToString(); + return Math.Round((decimal)sensorReading.IlluminanceInLux, 2).ToString(); } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public LightSensor(Windows.Devices.Sensors.LightSensor lightSensor) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs index b7d5802c..d85a5bfe 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/MagnetometerSensor.cs @@ -15,6 +15,9 @@ internal class MagnetometerSensor : IInternalDeviceSensor private readonly Magnetometer _magnetometer; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _magnetometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Magnetometer; public string Measurement @@ -25,6 +28,9 @@ public string Measurement return null; var sensorReading = _magnetometer.GetCurrentReading(); + if (sensorReading == null) + return null; + var magFieldX = Math.Round((decimal)sensorReading.MagneticFieldX,2); var magFieldY = Math.Round((decimal)sensorReading.MagneticFieldY, 2); var magFieldZ = Math.Round((decimal)sensorReading.MagneticFieldZ, 2); @@ -41,6 +47,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs index cc9c974b..f8b46f17 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/OrientationSensor.cs @@ -15,6 +15,9 @@ internal class OrientationSensor : IInternalDeviceSensor private readonly Windows.Devices.Sensors.OrientationSensor _orientationSensor; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _orientationSensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.OrientationSensor; public string Measurement @@ -25,6 +28,9 @@ public string Measurement return null; var sensorReading = _orientationSensor.GetCurrentReading(); + if (sensorReading == null) + return null; + _attributes[AttributeRotationMatrix] = JsonConvert.SerializeObject(sensorReading.RotationMatrix); _attributes[AttributeYawAccuracy] = sensorReading.YawAccuracy.ToString(); @@ -32,6 +38,8 @@ public string Measurement } } + public bool IsNumeric { get; } = false; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs index d2d5084e..f4011458 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/PedometerSensor.cs @@ -11,6 +11,9 @@ internal class PedometerSensor : IInternalDeviceSensor { private readonly Pedometer _pedometer; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _pedometer != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.Pedometer; public string Measurement @@ -23,7 +26,10 @@ public string Measurement var totalStepCount = 0; var sensorReadings = _pedometer.GetCurrentReadings(); - foreach(var sensorReading in sensorReadings) + if (sensorReadings == null) + return null; + + foreach (var sensorReading in sensorReadings) { var attributeCumulativeSteps = $"{sensorReading.Key}CumulativeSteps"; _attributes[attributeCumulativeSteps] = sensorReading.Value.CumulativeSteps.ToString(); @@ -37,6 +43,8 @@ public string Measurement } } + public bool IsNumeric { get; } = true; + private readonly Dictionary _attributes = new(); public Dictionary Attributes => _attributes; diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs index 4ef5e466..3d46f893 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/ProximitySensor.cs @@ -11,6 +11,9 @@ internal class ProximitySensor : IInternalDeviceSensor { private Windows.Devices.Sensors.ProximitySensor _proximitySensor; + public string MeasurementType { get; } = "distance"; + public string UnitOfMeasurement { get; } = "mm"; + public bool Available => _proximitySensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.ProximitySensor; public string Measurement @@ -20,10 +23,16 @@ public string Measurement if (!Available) return null; - return _proximitySensor.GetCurrentReading().DistanceInMillimeters.ToString(); + var sensorReading = _proximitySensor.GetCurrentReading(); + if (sensorReading == null) + return null; + + return sensorReading.DistanceInMillimeters.ToString(); } } + public bool IsNumeric { get; } = true; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public ProximitySensor(Windows.Devices.Sensors.ProximitySensor proximitySensor) diff --git a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/SimpleOrientationSensor.cs b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/SimpleOrientationSensor.cs index f5b5a80e..b4babdfb 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/SimpleOrientationSensor.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/DeviceSensors/SimpleOrientationSensor.cs @@ -11,6 +11,9 @@ internal class SimpleOrientationSensor : IInternalDeviceSensor { private readonly Windows.Devices.Sensors.SimpleOrientationSensor _simpleOrientationSensor; + public string MeasurementType { get; } = string.Empty; + public string UnitOfMeasurement { get; } = string.Empty; + public bool Available => _simpleOrientationSensor != null; public InternalDeviceSensorType Type => InternalDeviceSensorType.SimpleOrientationSensor; public string Measurement @@ -24,6 +27,8 @@ public string Measurement } } + public bool IsNumeric { get; } = false; + public Dictionary Attributes => InternalDeviceSensor.NoAttributes; public SimpleOrientationSensor(Windows.Devices.Sensors.SimpleOrientationSensor simpleOrientationSensor) diff --git a/src/HASS.Agent/HASS.Agent/Managers/RadioManager.cs b/src/HASS.Agent/HASS.Agent/Managers/RadioManager.cs index 035aa8c9..c4792838 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/RadioManager.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/RadioManager.cs @@ -1,20 +1,52 @@ -using Serilog; +using MQTTnet; +using NdefLibrary.Ndef; +using Newtonsoft.Json; +using Serilog; using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; using System.Text; using System.Threading.Tasks; +using Windows.Devices.Enumeration; using Windows.Devices.Radios; +using Windows.Devices.SmartCards; +using Windows.Networking.Proximity; namespace HASS.Agent.Managers { internal static class RadioManager { public static List AvailableRadio { get; private set; } = new(); - public static List AvailableRadioNames { get => AvailableRadio.Select(r => r.Name).ToList(); } + public static List AvailableRadioNames => AvailableRadio.Select(r => r.Name).ToList(); + + public static Dictionary AvailableNFCReader { get; private set; } = new(); + public static List AvailableNFCReaderNames => AvailableNFCReader.Keys.ToList(); + + private static long s_subscriptionId = -1; + private static ProximityDevice s_selectedNFCReader = null; + public static ProximityDevice SelectedNFCReader + { + get => s_selectedNFCReader; + set + { + if (s_selectedNFCReader == value) + return; + + if (s_selectedNFCReader != null && s_subscriptionId != -1) + { + s_selectedNFCReader.StopSubscribingForMessage(s_subscriptionId); + } + + s_selectedNFCReader = value; + s_subscriptionId = s_selectedNFCReader.SubscribeForMessage("NDEF", MessageReceivedHandler); + } + } public static async Task Initialize() { + Log.Debug("[RADIOMGR] Initialization started"); + var accessStatus = await Radio.RequestAccessAsync(); if (accessStatus == RadioAccessStatus.Allowed) { @@ -23,12 +55,88 @@ public static async Task Initialize() AvailableRadio.Add(radio); } - Log.Information("[RADIOMGR] Ready"); + Log.Information("[RADIOMGR] Radio management permission granted"); } else { - Log.Fatal("[RADIOMGR] No permission granted for Bluetooth radio management"); + Log.Fatal("[RADIOMGR] No permission granted for radio management - privacy settings may be restricting the access"); + } + + Log.Debug("[RADIOMGR] Enumerating proximity/NFC devices"); + try + { + var proximityDevices = await DeviceInformation.FindAllAsync(ProximityDevice.GetDeviceSelector()); + foreach (var device in proximityDevices) + { + var proximityReader = ProximityDevice.FromId(device.Id); + AvailableNFCReader.Add(device.Name, proximityReader); + } + } + catch + { + Log.Fatal("[RADIOMGR] Error initializing proximity/NFC devices"); + } + + if (!string.IsNullOrEmpty(Variables.AppSettings.NfcSelectedScanner)) + { + if (AvailableNFCReader.TryGetValue(Variables.AppSettings.NfcSelectedScanner, out var selectedScanner)) + { + Log.Debug("[RADIOMGR] Selected NFC reader: '{nfcScanner}'", Variables.AppSettings.NfcSelectedScanner); + SelectedNFCReader = selectedScanner; + } + else + { + Log.Warning("[RADIOMGR] Selected NFC reader: '{nfcScanner}' not available", Variables.AppSettings.NfcSelectedScanner); + } } + + Log.Information("[RADIOMGR] Ready"); + } + + private static void MessageReceivedHandler(ProximityDevice sender, ProximityMessage message) + { + if(!Variables.AppSettings.NfcScanningEnabled) + return; + + try + { + var rawMsg = message.Data.ToArray(); + var ndefMessage = NdefMessage.FromByteArray(rawMsg); + + foreach (var record in ndefMessage) + { + Log.Debug("Record type: " + Encoding.UTF8.GetString(record.Type, 0, record.Type.Length)); + if (record.CheckSpecializedType(false) == typeof(NdefUriRecord)) + { + var uriRecord = new NdefUriRecord(record); + Log.Debug($"URI: {uriRecord.Uri}"); + + if (uriRecord.Uri.StartsWith("https://www.home-assistant.io/tag/")) + { + var tagId = uriRecord.Uri.Split('/').LastOrDefault(); + if(string.IsNullOrWhiteSpace(tagId)) + return; + + var tagScannedMessage = new MqttApplicationMessageBuilder() + .WithTopic($"hass.agent/devices/{Variables.DeviceConfig.Name}/tag_scanned") + .WithPayload(JsonConvert.SerializeObject(new + { + Time = DateTime.UtcNow.ToString("s"), + UID = tagId + })) + .Build(); + + Variables.MqttManager.PublishAsync(tagScannedMessage); + } + } + } + + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } } } diff --git a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs index ca4c4ded..3354bda2 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using HASS.Agent.Functions; using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers.Audio; using Microsoft.Win32; using Serilog; @@ -12,7 +13,7 @@ internal static class SystemStateManager /// The pointer to unregister the monitor power notifications /// internal static IntPtr UnRegPowerNotify { get; set; } = IntPtr.Zero; - + /// /// Notes the last time something happened to the system's state, eg. user logged on, session locked, etc /// @@ -21,7 +22,21 @@ internal static class SystemStateManager /// /// Contains the last event that happened to the system, eg. user logged on, session locked, etc /// - internal static SystemStateEvent LastSystemStateEvent { get; private set; } = SystemStateEvent.ApplicationStarted; + private static SystemStateEvent s_lastSystemStateEvent = SystemStateEvent.ApplicationStarted; + internal static SystemStateEvent LastSystemStateEvent + { + get => s_lastSystemStateEvent; + private set + { + s_lastSystemStateEvent = value; + LastEventOccurrence[s_lastSystemStateEvent] = DateTime.Now; + } + } + + /// + /// Contains the key value pair with SystemStateEvent and the last time it occurred + /// + public static Dictionary LastEventOccurrence = new(); /// /// Contains the last event that happened to the monitors, eg. power on @@ -33,6 +48,7 @@ internal static class SystemStateManager /// internal static async void Initialize() { + LastSystemStateEvent = SystemStateEvent.ApplicationStarted; await Task.Run(Monitor); } @@ -67,7 +83,7 @@ internal static void ProcessSessionEnd() Log.Information("[SYSTEMSTATE] Session ending: system shutting down"); Task.Run(() => HelperFunctions.ShutdownAsync(TimeSpan.Zero)); LastSystemStateEvent = SystemStateEvent.SystemShutdown; - + } else { @@ -185,6 +201,7 @@ private static void SystemEventsOnPowerModeChanged(object sender, PowerModeChang Log.Information("[SYSTEMSTATE] Session resuming"); Task.Run(() => Variables.MqttManager.AnnounceAvailabilityAsync()); LastSystemStateEvent = SystemStateEvent.Resume; + break; case PowerModes.Suspend: @@ -194,6 +211,7 @@ private static void SystemEventsOnPowerModeChanged(object sender, PowerModeChang Log.Information("[SYSTEMSTATE] Session halting: system suspending"); Task.Run(() => Variables.MqttManager.AnnounceAvailabilityAsync(true)); LastSystemStateEvent = SystemStateEvent.Suspend; + break; } } diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs index 76ed14f6..9dae42ec 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManager.cs @@ -2,7 +2,6 @@ using System.Text.Json; using Windows.Media.Control; using Windows.Media.Playback; -using CoreAudio; using HASS.Agent.Enums; using HASS.Agent.Extensions; using HASS.Agent.Managers; @@ -13,6 +12,8 @@ using Serilog; using MediaPlayerState = HASS.Agent.Enums.MediaPlayerState; using Octokit; +using Windows.Media.Core; +using HASS.Agent.Shared.Managers.Audio; namespace HASS.Agent.Media { @@ -38,7 +39,7 @@ internal static async Task InitializeAsync() Log.Information("[MEDIA] Disabled"); return; } - + if (!Variables.AppSettings.LocalApiEnabled && !Variables.AppSettings.MqttEnabled) { Log.Warning("[MEDIA] Both local API and MQTT are disabled, unable to receive media requests"); @@ -49,8 +50,7 @@ internal static async Task InitializeAsync() // todo: optional, but add an OS check - not all OSs support this try { - // create the objects - Variables.AudioDeviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); + // create the object Variables.MediaPlayer = new MediaPlayer(); _sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); @@ -71,7 +71,7 @@ internal static async Task InitializeAsync() } catch (Exception ex) { - Log.Fatal(ex, "[MEDIA] Unable to initialize: {err}" , ex.Message); + Log.Fatal(ex, "[MEDIA] Unable to initialize: {err}", ex.Message); Variables.AppSettings.MediaPlayerEnabled = false; Log.Warning("[MEDIA] Failed, disabled"); @@ -80,7 +80,7 @@ internal static async Task InitializeAsync() // start monitoring playing media _ = Task.Run(MediaMonitor); - + if (!Variables.AppSettings.MqttEnabled) Log.Warning("[MEDIA] MQTT is disabled, only basic media functionality will work"); else { @@ -192,7 +192,8 @@ private static async void MediaMonitor() message.AlbumArtist = mediaProperties.AlbumArtist; message.AlbumTitle = mediaProperties.AlbumTitle; message.Volume = MediaManagerRequests.GetVolume(); - + message.Muted = AudioManager.GetDevices().FirstOrDefault(d => d.Type == DeviceType.Output && d.Default)?.Muted == true; + // get timeline info var timeline = session.GetTimelineProperties(); if (timeline != null) @@ -244,9 +245,9 @@ private static GlobalSystemMediaTransportControlsSession GetCurrentMediaSession( // if none are playing: pick the first if (sessions.Count == 1) return sessions[0]; - - return sessions.Any(x => x.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) - ? sessions.First(x => x.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) + + return sessions.Any(x => x.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) + ? sessions.First(x => x.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) : sessions[0]; } @@ -332,7 +333,8 @@ internal static void ProcessCommand(MediaPlayerCommand command) case MediaPlayerCommand.Play: if (Variables.ExtendedLogging) Log.Information("[MEDIA] Command received: Play"); - if (State == MediaPlayerState.Playing) { + if (State == MediaPlayerState.Playing) + { if (Variables.ExtendedLogging) Log.Warning("[MEDIA] Media already playing"); break; } @@ -341,7 +343,8 @@ internal static void ProcessCommand(MediaPlayerCommand command) case MediaPlayerCommand.Pause: if (Variables.ExtendedLogging) Log.Information("[MEDIA] Command received: Pause"); - if (State == MediaPlayerState.Paused) { + if (State == MediaPlayerState.Paused) + { if (Variables.ExtendedLogging) Log.Warning("[MEDIA] Media already paused"); break; } @@ -408,7 +411,7 @@ internal static async void ProcessMedia(string mediaUri) if (Variables.MediaPlayer.CurrentState == Windows.Media.Playback.MediaPlayerState.Playing) Variables.MediaPlayer.Pause(); // set the uri source - Variables.MediaPlayer.SetUriSource(new Uri(localFile)); + Variables.MediaPlayer.Source = MediaSource.CreateFromUri(new Uri(localFile)); if (Variables.ExtendedLogging) Log.Information("[MEDIA] Playing: {file}", Path.GetFileName(localFile)); diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs index 1d4b2646..11b6665e 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManagerCommands.cs @@ -1,5 +1,4 @@ -using CoreAudio; -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -12,6 +11,8 @@ using static HASS.Agent.Shared.Functions.Inputs; using static System.Runtime.CompilerServices.RuntimeHelpers; using HASS.Agent.Shared.Functions; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; namespace HASS.Agent.Media { @@ -70,18 +71,7 @@ internal static void SetVolume(int volume) if (volume < 0) volume = 0; if (volume > 100) volume = 100; - var fVolume = volume / 100.0f; - - // get the current default endpoint - using var audioDevice = Variables.AudioDeviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - if (audioDevice?.AudioEndpointVolume == null) - { - Log.Warning("[MEDIA] Unable to set volume, no default audio endpoint found"); - return; - } - - // all good, set the volume - audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar = fVolume; + AudioManager.SetDefaultDeviceProperties(DeviceType.Output, DeviceRole.Multimedia | DeviceRole.Console, volume, null); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs b/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs index 9317ad4b..ceff8721 100644 --- a/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs +++ b/src/HASS.Agent/HASS.Agent/Media/MediaManagerRequests.cs @@ -3,7 +3,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using CoreAudio; +using HASS.Agent.Shared.Managers; +using HASS.Agent.Shared.Managers.Audio; using Serilog; namespace HASS.Agent.Media @@ -18,16 +19,7 @@ internal static int GetVolume() { try { - // get the default audio device - using var audioDevice = Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - - // get default device volume - var volume = Convert.ToInt32(Math.Round(audioDevice.AudioEndpointVolume?.MasterVolumeLevelScalar * 100 ?? 0, 0)); - - // Log.Debug("[MEDIA] Current volume: {vol}", volume); - - // return it - return volume; + return AudioManager.GetDefaultDeviceVolume(DeviceType.Output, DeviceRole.Multimedia); } catch (Exception ex) { @@ -44,16 +36,7 @@ internal static bool GetMuteState() { try { - // get the default audio device - using var audioDevice = Variables.AudioDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - - // get mute state - var muted = audioDevice.AudioEndpointVolume?.Mute ?? false; - - // Log.Debug("[MEDIA] Muted: {mute}", muted); - - // return it - return muted; + return AudioManager.GetDefaultDeviceMute(DeviceType.Output, DeviceRole.Multimedia); } catch (Exception ex) { diff --git a/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs b/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs index caccd39e..a7f705c7 100644 --- a/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs +++ b/src/HASS.Agent/HASS.Agent/Models/Config/AppSettings.cs @@ -37,6 +37,7 @@ public AppSettings() public string TrayIconWebViewUrl { get; set; } = string.Empty; public bool TrayIconWebViewBackgroundLoading { get; set; } = false; public bool TrayIconWebViewShowMenuOnLeftClick { get; set; } = false; + public bool TrayIconUseModern { get; set; } = false; public string ServiceAuthId { get; set; } = string.Empty; @@ -78,5 +79,9 @@ public AppSettings() public bool MqttUseRetainFlag { get; set; } = true; public string MqttRootCertificate { get; set; } = string.Empty; public string MqttClientCertificate { get; set; } = string.Empty; + public bool MqttIgnoreGracePeriod { get; set; } = false; + + public bool NfcScanningEnabled { get; set; } = false; + public string NfcSelectedScanner { get; set; } = string.Empty; } } diff --git a/src/HASS.Agent/HASS.Agent/Models/Internal/GeolocationInfo.cs b/src/HASS.Agent/HASS.Agent/Models/Internal/GeolocationInfo.cs new file mode 100644 index 00000000..045500cc --- /dev/null +++ b/src/HASS.Agent/HASS.Agent/Models/Internal/GeolocationInfo.cs @@ -0,0 +1,37 @@ +using Windows.Devices.Geolocation; + +namespace HASS.Agent.Models.Internal +{ + public class GeolocationInfo + { + public string latitude { get; set; } + public string longitude { get; set; } + public string altitude { get; set; } + public string source_type { get; set; } + public string gps_accuracy { get; set; } + public string not_permitted { get; set; } + + public GeolocationInfo() + { + + } + + public GeolocationInfo(string lon, string lat, PositionSource source) + { + longitude = lon; + latitude = lat; + source_type = ""; + gps_accuracy = "1.2"; + + switch (source) + { + case PositionSource.Satellite: + source_type = "gps"; + break; + case PositionSource.WiFi: + source_type = "router"; + break; + } + } + } +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs index e0f8d42d..14e09776 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.Designer.cs @@ -163,6 +163,51 @@ internal static string About_Title { } } + /// + /// Looks up a localized string similar to Device class. + /// + internal static string AdvancedSensorConfig_LblDeviceClass { + get { + return ResourceManager.GetString("AdvancedSensorConfig_LblDeviceClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to State class. + /// + internal static string AdvancedSensorConfig_LblStateClass { + get { + return ResourceManager.GetString("AdvancedSensorConfig_LblStateClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unit of measurement. + /// + internal static string AdvancedSensorConfig_LblUnitOfMeasurement { + get { + return ResourceManager.GetString("AdvancedSensorConfig_LblUnitOfMeasurement", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Advanced Settings. + /// + internal static string AdvancedSensorConfig_Title { + get { + return ResourceManager.GetString("AdvancedSensorConfig_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning, adding following settings (overrides) may break the sensor, use with caution!. + /// + internal static string AdvancedSensorConfig_Warning { + get { + return ResourceManager.GetString("AdvancedSensorConfig_Warning", resourceCulture); + } + } + /// /// Looks up a localized string similar to Button. /// @@ -608,6 +653,26 @@ internal static string CommandsManager_SetApplicationVolumeCommandDescription { } } + /// + /// Looks up a localized string similar to Sets the default audio input for the system (including default communication device). + ///Requires audio device name as a payload.. + /// + internal static string CommandsManager_SetAudioInputCommandDescription { + get { + return ResourceManager.GetString("CommandsManager_SetAudioInputCommandDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the default audio output for the system. + ///Requires audio device name as a payload.. + /// + internal static string CommandsManager_SetAudioOutputCommandDescription { + get { + return ResourceManager.GetString("CommandsManager_SetAudioOutputCommandDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sets the volume of the current default audiodevice to the specified level.. /// @@ -650,6 +715,16 @@ internal static string CommandsManager_SwitchDesktopCommandDescription { } } + /// + /// Looks up a localized string similar to Shows / hides the Tray Icon Web View. + ///Requires it to be configured in "Configuration -> Tray Icon". + /// + internal static string CommandsManager_TrayWebViewCommandDescription { + get { + return ResourceManager.GetString("CommandsManager_TrayWebViewCommandDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Shows a window with the provided URL. /// @@ -1038,6 +1113,15 @@ internal static string CommandsMod_LblSetting { } } + /// + /// Looks up a localized string similar to Audio Device Name. + /// + internal static string CommandsMod_LblSetting_AudioDeviceName { + get { + return ResourceManager.GetString("CommandsMod_LblSetting_AudioDeviceName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Command. /// @@ -1056,6 +1140,15 @@ internal static string CommandsMod_LblSetting_CommandScript { } } + /// + /// Looks up a localized string similar to JSON Command Payload. + /// + internal static string CommandsMod_LblSetting_JsonPayload { + get { + return ResourceManager.GetString("CommandsMod_LblSetting_JsonPayload", resourceCulture); + } + } + /// /// Looks up a localized string similar to Keycode. /// @@ -1092,6 +1185,15 @@ internal static string CommandsMod_LblSetting_Url { } } + /// + /// Looks up a localized string similar to Volume (between 0 and 100). + /// + internal static string CommandsMod_LblSetting_VolumeRange { + get { + return ResourceManager.GetString("CommandsMod_LblSetting_VolumeRange", resourceCulture); + } + } + /// /// Looks up a localized string similar to HASS.Agent only!. /// @@ -2315,6 +2417,15 @@ internal static string ConfigMqtt_CbEnableMqtt { } } + /// + /// Looks up a localized string similar to Ignore grace period after waking up from hibernation. + /// + internal static string ConfigMqtt_CbIgnoreGracePeriod { + get { + return ResourceManager.GetString("ConfigMqtt_CbIgnoreGracePeriod", resourceCulture); + } + } + /// /// Looks up a localized string similar to &TLS. /// @@ -2999,6 +3110,15 @@ internal static string ConfigTrayIcon_CbShowWebView { } } + /// + /// Looks up a localized string similar to Use modern tray icon. + /// + internal static string ConfigTrayIcon_CbUseModernIcon { + get { + return ResourceManager.GetString("ConfigTrayIcon_CbUseModernIcon", resourceCulture); + } + } + /// /// Looks up a localized string similar to &Keep page loaded in the background. /// @@ -3192,6 +3312,15 @@ internal static string Configuration_MessageBox_RestartManually { } } + /// + /// Looks up a localized string similar to NFC. + /// + internal static string Configuration_NFC { + get { + return ResourceManager.GetString("Configuration_NFC", resourceCulture); + } + } + /// /// Looks up a localized string similar to You've changed your device's name. /// @@ -3542,6 +3671,15 @@ internal static string HassAction_Play { } } + /// + /// Looks up a localized string similar to Press. + /// + internal static string HassAction_Press { + get { + return ResourceManager.GetString("HassAction_Press", resourceCulture); + } + } + /// /// Looks up a localized string similar to Stop. /// @@ -3560,6 +3698,15 @@ internal static string HassAction_Toggle { } } + /// + /// Looks up a localized string similar to Trigger. + /// + internal static string HassAction_Trigger { + get { + return ResourceManager.GetString("HassAction_Trigger", resourceCulture); + } + } + /// /// Looks up a localized string similar to Client certificate file not found.. /// @@ -3650,6 +3797,15 @@ internal static string HassDomain_Automation { } } + /// + /// Looks up a localized string similar to Button. + /// + internal static string HassDomain_Button { + get { + return ResourceManager.GetString("HassDomain_Button", resourceCulture); + } + } + /// /// Looks up a localized string similar to Climate. /// @@ -5834,6 +5990,19 @@ internal static string SensorsManager_CurrentVolumeSensorDescription { } } + /// + /// Looks up a localized string similar to Returns your current latitude, longitude and altitude as as location/device tracker, to directly display and track on a map. + /// + ///Make sure Windows' location services are enabled! + /// + ///Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'.. + /// + internal static string SensorsManager_DeviceTrackerSensorDescription { + get { + return ResourceManager.GetString("SensorsManager_DeviceTrackerSensorDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Provides a sensor with the amount of displays, name of the primary display, and per display its name, resolution and bits per pixel.. /// @@ -5969,7 +6138,7 @@ internal static string SensorsManager_MicrophoneActiveSensorDescription { } /// - /// Looks up a localized string similar to Provides the name of the process that's currently using the microphone. + /// Looks up a localized string similar to Provides the number of the processes that currently use the microphone - additionally provides name of the processes in the sensor's attributes. /// ///Note: if used in the satellite service, it won't detect userspace applications.. /// @@ -6029,6 +6198,7 @@ internal static string SensorsManager_PerformanceCounterSensorDescription { /// /// Looks up a localized string similar to Returns the result of the provided Powershell command or script. + ///Note: please keep in mind that Home Assistant accepts payload up to 255 characters. /// ///Converts the outcome to text.. /// @@ -6058,6 +6228,16 @@ internal static string SensorsManager_ProcessActiveSensorDescription { } } + /// + /// Looks up a localized string similar to Provides a screenshot sensor in form of a camera entity. + ///Screen number depends on system configuration - starts at 0.. + /// + internal static string SensorsManager_ScreenshotSensorDescription { + get { + return ResourceManager.GetString("SensorsManager_ScreenshotSensorDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Returns the state of the provided service: NotFound, Stopped, StartPending, StopPending, Running, ContinuePending, PausePending or Paused. /// @@ -6166,6 +6346,15 @@ internal static string SensorsMod_All { } } + /// + /// Looks up a localized string similar to Ad&vanced Settings. + /// + internal static string SensorsMod_BtnAdvancedSettings { + get { + return ResourceManager.GetString("SensorsMod_BtnAdvancedSettings", resourceCulture); + } + } + /// /// Looks up a localized string similar to &Store Sensor. /// @@ -6483,6 +6672,15 @@ internal static string SensorsMod_LblSetting1_Process { } } + /// + /// Looks up a localized string similar to Screen number. + /// + internal static string SensorsMod_LblSetting1_ScreenNumber { + get { + return ResourceManager.GetString("SensorsMod_LblSetting1_ScreenNumber", resourceCulture); + } + } + /// /// Looks up a localized string similar to Service. /// diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx index be5cc84d..2664b871 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.de.resx @@ -3252,11 +3252,6 @@ Zeigt nur Geräte an, die seit dem letzten Bericht gesehen wurden, d. h. Wenn de Stelle sicher, dass die Ortungsdienste von Windows aktiviert sind! Je nach Windows-Version finden Sie diese in der neuen Systemsteuerung -> „Datenschutz und Sicherheit“ -> „Standort“. - - - Stellt den Namen des Prozesses bereit, der das Mikrofon derzeit verwendet. - -Hinweis: Bei Verwendung im Satellitenservice werden keine Userspace-Anwendungen erkannt. Zeigt die letzte Änderung des Monitor-Energiezustands: @@ -3265,6 +3260,7 @@ Gedimmt, PowerOff, PowerOn und Unbekannt. Gibt das Ergebnis des bereitgestellten Powershell-Befehls oder -Skripts zurück. +Hinweis: Bitte beachten Sie, dass Home Assistant Nutzdaten von bis zu 255 Zeichen akzeptiert. Konvertiert das Ergebnis in Text. @@ -3420,7 +3416,15 @@ Befehl/Nutzlast muss im JSON-Format vorliegen. Beispiel aller möglichen Optione Wenn kein „playbackDevice“ angegeben ist, verwendet HASS.Agent das Standardgerät. Wenn keine „volume“ angegeben ist, stellt HASS.Agent nur den Stummschaltungsstatus ein. -Wenn keine „mute“ bereitgestellt wird, hebt HASS.Agent die Stummschaltung der bereitgestellten Anwendung auf. +Wenn keine „mute“ bereitgestellt wird, hebt HASS.Agent die Stummschaltung der bereitgestellten Anwendung auf. + +Erweiterte Option: Zusätzliche „sessionId“ kann bereitgestellt werden, um die Lautstärke nur für eine bestimmte Sitzung der Anwendung festzulegen. Die Sitzungs-ID kann vom Sensor „Audio Sessions“ abgerufen werden: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Bitte geben Sie einen gültigen JSON-String ein! @@ -3464,4 +3468,70 @@ aus der Originalversion Fehler beim Starten des Satellitendienstes! + + Legt die Standard-Audioausgabe für das System fest. +Erfordert den Namen des Audiogeräts als Nutzlast. + + + Name des Audiogeräts + + + JSON-Befehlsnutzlast + + + Lautstärke (zwischen 0 und 100) + + + Sie stellen einen Screenshot-Sensor in Form einer Kameraeinheit bereit. +Die Bildschirmnummer hängt von der Systemkonfiguration ab – beginnt bei 0. + + + Bildschirmnummer + + + Legt den Standard-Audioeingang für das System fest (einschließlich des Standard-Kommunikationsgeräts). +Als Payload benötigen Sie den Namen des Audiogeräts. + + + Ignorieren Sie die Schonfrist nach dem Aufwachen aus dem Ruhezustand + + + Verwenden Sie ein modernes Taskleistensymbol + + + Gibt die Anzahl der Prozesse an, die derzeit das Mikrofon verwenden. Zusätzlich werden die Namen der Prozesse in den Attributen des Sensors angegeben. + +Hinweis: Bei Verwendung im Satellitendienst werden keine Userspace-Anwendungen erkannt. + + + NFC + + + Erweiterte Einstellungen + + + Erwe&iterte Einstellungen + + + Achtung: Das Hinzufügen der folgenden Einstellungen (Überschreibungen) kann zur Beschädigung des Sensors führen. Gehen Sie daher vorsichtig vor! + + + Zeigt/verbirgt das Tray-Icon-Web-View. +Muss unter „Konfiguration -> Tray-Icon“ konfiguriert werden. + + + Auslösen + + + Knopf + + + Presse + + + Gibt den aktuellen Breitengrad, Längengrad und die Höhe als Location zurück, um sie direkt auf einer Karte anzuzeigen und zu verfolgen. + +Stelle sicher, dass die Ortungsdienste von Windows aktiviert sind! +Abhängig von Ihrer Windows-Version finden Sie dies in der neuen Systemsteuerung > "Datenschutz und Sicherheit" > "Standort". + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx index 0baa62cb..33bdce83 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.en.resx @@ -3130,11 +3130,6 @@ Only shows devices that were seen since the last report, ie. when the sensor pub Make sure Windows' location services are enabled! Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. - - - Provides the name of the process that's currently using the microphone. - -Note: if used in the satellite service, it won't detect userspace applications. Provides the last monitor power state change: @@ -3143,6 +3138,7 @@ Dimmed, PowerOff, PowerOn and Unkown. Returns the result of the provided Powershell command or script. +Note: please keep in mind that Home Assistant accepts payload up to 255 characters. Converts the outcome to text. @@ -3316,7 +3312,34 @@ Command / payload needs to be in JSON format. Example of all possible options: If no "playbackDevice" is provided, HASS.Agent will use the default one. If no "volume" is provided, HASS.Agent will set only mute status. -If no "mute" is provided, HASS.Agent will unmute the provided application. +If no "mute" is provided, HASS.Agent will unmute the provided application. + +Advanced option: additional "sessionId" can be provided to set volume only for specific session of the application, session id can be obtained from "Audio Sessions" sensor: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +}Sets the volume and mute status of the provided application on provided audio device to the specified level. +Command / payload needs to be in JSON format. Example of all possible options: +{ + "playbackDevice": "Speakers (THX Spatial Audio)", + "applicationName": "Discord", + "volume": 90, + "mute": true +} + +If no "playbackDevice" is provided, HASS.Agent will use the default one. +If no "volume" is provided, HASS.Agent will set only mute status. +If no "mute" is provided, HASS.Agent will unmute the provided application. + +Advanced option: additional "sessionId" can be provided to set volume only for specific session of the application, session id can be obtained from "Audio Sessions" sensor: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Please enter a valid JSON string! @@ -3341,4 +3364,80 @@ from the original version Error communicating with the satellite service! + + Sets the default audio output for the system. +Requires audio device name as a payload. + + + Audio Device Name + + + JSON Command Payload + + + Volume (between 0 and 100) + + + Screen number + + + Provides a screenshot sensor in form of a camera entity. +Screen number depends on system configuration - starts at 0. + + + Sets the default audio input for the system (including default communication device). +Requires audio device name as a payload. + + + Ignore grace period after waking up from hibernation + + + Use modern tray icon + + + Provides the number of the processes that currently use the microphone - additionally provides name of the processes in the sensor's attributes. + +Note: if used in the satellite service, it won't detect userspace applications. + + + NFC + + + Ad&vanced Settings + + + Warning, adding following settings (overrides) may break the sensor, use with caution! + + + Device class + + + Unit of measurement + + + State class + + + Advanced Settings + + + Shows / hides the Tray Icon Web View. +Requires it to be configured in "Configuration -> Tray Icon" + + + Trigger + + + Button + + + Press + + + Returns your current latitude, longitude and altitude as as location/device tracker, to directly display and track on a map. + +Make sure Windows' location services are enabled! + +Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx index 1dce77ca..aea7b6f8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.es.resx @@ -3129,11 +3129,6 @@ Sólo muestra los dispositivos que fueron vistos desde el último informe, es de Asegúrese de que los servicios de localización de Windows están activados. Dependiendo de su versión de Windows, esto se puede encontrar en el nuevo panel de control -> 'privacidad y seguridad' -> 'ubicación'. - - - Proporciona el nombre del proceso que está usando actualmente el micrófono. - -Nota: si se usa en el servicio de satélite, no detectará las aplicaciones del espacio de usuario. Proporciona el último cambio de estado de energía del monitor: @@ -3142,6 +3137,7 @@ Atenuado, Apagado, Encendido y Desconocido. Devuelve el resultado del comando o script de Powershell proporcionado. +Nota: tenga en cuenta que Home Assistant acepta una carga útil de hasta 255 caracteres. Convierte el resultado en texto. @@ -3296,7 +3292,15 @@ El comando/carga útil debe estar en formato JSON. Ejemplo de todas las opciones Si no se proporciona ningún "playbackDevice", HASS.Agent utilizará el predeterminado. Si no se proporciona ningún "volume", HASS.Agent establecerá sólo el estado de silencio. -Si no se proporciona ningún "mute", HASS.Agent reactivará el silencio de la aplicación proporcionada. +Si no se proporciona ningún "mute", HASS.Agent reactivará el silencio de la aplicación proporcionada. + +Opción avanzada: se puede proporcionar un "sessionId" adicional para configurar el volumen solo para una sesión específica de la aplicación; el id de sesión se puede obtener del sensor "Sesiones de audio": +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} ¡Ingrese una cadena JSON válida! @@ -3340,4 +3344,71 @@ HASS.Agent de la versión original ¡Error al iniciar el servicio satelital! + + Establece la salida de audio predeterminada para el sistema. +Requiere el nombre del dispositivo de audio como carga útil. + + + Volumen (entre 0 y 100) + + + Carga útil del comando JSON + + + Nombre del dispositivo de audio + + + Proporciona un sensor de captura de pantalla en forma de entidad de cámara. +El número de pantalla depende de la configuración del sistema: comienza en 0. + + + Número de pantalla + + + Establece la entrada de audio predeterminada para el sistema (incluido el dispositivo de comunicación predeterminado). +Necesita el nombre del dispositivo de audio como carga útil. + + + Ignorar el período de gracia después de despertar de la hibernación + + + Utilice el icono de bandeja moderno + + + Proporciona el número de procesos que utilizan actualmente el micrófono; además, proporciona el nombre de los procesos en los atributos del sensor. + +Nota: si se usa en el servicio satelital, no detectará aplicaciones del espacio de usuario. + + + NFC + + + Ajustes avanzados + + + A&justes avanzados + + + Advertencia, agregar las siguientes configuraciones (anulaciones) puede dañar el sensor, ¡úselo con precaución! + + + Muestra u oculta la vista web del icono de la bandeja. +Requiere que se configure en "Configuración -> Icono de la bandeja" + + + Desencadenar + + + Botón + + + Prensa + + + Devuelve su latitud, longitud y altitud actuales como rastreador de ubicación / dispositivo, para mostrar y rastrear directamente en un mapa. + +¡Asegúrate de que los servicios de ubicación de Windows estén habilitados! + +Dependiendo de su versión de Windows, esto se puede encontrar en el nuevo panel de control > 'privacidad y seguridad' > 'ubicación'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx index 03e69a61..b4ba691b 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.fr.resx @@ -3162,11 +3162,6 @@ Affiche uniquement les appareils qui ont été vus depuis le dernier rapport, c' Assurez-vous que les services de localisation de Windows sont activés ! Selon votre version de Windows, cela peut être trouvé dans le nouveau panneau de configuration -> 'confidentialité et sécurité' -> 'emplacement'. - - - Provides the name of the process that's currently using the microphone. - -Note: if used in the satellite service, it won't detect userspace applications. Provides the last monitor power state change: @@ -3174,9 +3169,10 @@ Note: if used in the satellite service, it won't detect userspace applications.< Dimmed, PowerOff, PowerOn and Unkown. - Returns the result of the provided Powershell command or script. + Renvoie le résultat de la commande ou du script Powershell fourni. +Remarque : veuillez garder à l'esprit que Home Assistant accepte des charges utiles allant jusqu'à 255 caractères. -Converts the outcome to text. +Convertit le résultat en texte. Fournit des informations sur toutes les imprimantes installées et leurs files d'attente. @@ -3329,7 +3325,15 @@ La commande/charge utile doit être au format JSON. Exemple de toutes les option Si aucun "playbackDevice" n'est fourni, HASS.Agent utilisera celui par défaut. Si aucun "volume" n'est fourni, HASS.Agent définira uniquement le statut muet. -Si aucun "mute" n'est fourni, HASS.Agent réactivera le son de l'application fournie. +Si aucun "mute" n'est fourni, HASS.Agent réactivera le son de l'application fournie. + +Option avancée : un "sessionId" supplémentaire peut être fourni pour définir le volume uniquement pour une session spécifique de l'application, l'identifiant de session peut être obtenu à partir du capteur "Sessions audio" : +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Veuillez saisir une chaîne JSON valide ! @@ -3373,4 +3377,71 @@ HASS.Agent de la version originale Erreur lors du démarrage du service satellite ! + + Définit la sortie audio par défaut du système. +Nécessite le nom du périphérique audio comme charge utile. + + + Volume (entre 0 et 100) + + + Charge utile de la commande JSON + + + Nom du périphérique audio + + + Vous fournissez un capteur de capture d’écran sous la forme d’une entité caméra. +Le numéro d'écran dépend de la configuration du système - commence à 0. + + + Numéro d'écran + + + Définit l'entrée audio par défaut du système (y compris le périphérique de communication par défaut). +Vous avez besoin du nom du périphérique audio comme charge utile. + + + Ignorer la période de grâce après la sortie de l'hibernation + + + Utiliser l'icône de la barre d'état moderne + + + Fournit le nombre de processus qui utilisent actuellement le microphone - fournit en outre le nom des processus dans les attributs du capteur. + +Remarque : s'il est utilisé dans le service satellite, il ne détectera pas les applications de l'espace utilisateur. + + + NFC + + + Réglages avancés + + + Réglages a&vancés + + + Attention, l'ajout des paramètres suivants (remplacements) peut casser le capteur, à utiliser avec prudence ! + + + Affiche/masque l'affichage Web de l'icône de la barre d'état système. +Nécessite sa configuration dans « Configuration -> Icône de la barre d'état système » + + + Déclencher + + + Knop + + + Presse + + + Renvoie votre latitude, longitude et altitude actuelles sous forme de traqueur de localisation/appareil, pour les afficher et les suivre directement sur une carte. + +Assurez-vous que les services de localisation de Windows sont activés ! + +Selon votre version de Windows, vous pouvez le trouver dans le nouveau panneau de configuration > 'confidentialité et sécurité' > 'localisation'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx index 4523dc56..dd4d12fe 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.nl.resx @@ -3151,11 +3151,6 @@ Toont alleen apparaten die zijn gezien sinds het laatste rapport, oftewel, zodra Verzeker dat Windows' localisatieservices ingeschakeld zijn! Afhankelijk van je Windows versie, kan dit gevonden worden in het nieuwe configuratiescherm -> 'privacy en beveiliging' -> 'locatie'. - - - Geeft de naam van het proces dat momenteel de microfoon gebruikt. - -Notitie: als hij in de satellietservice gebruikt wordt, zal hij geen gebruikerapplicaties detecteren. Geeft de laatste beeldscherm energiemodus status verandering: @@ -3163,9 +3158,10 @@ Notitie: als hij in de satellietservice gebruikt wordt, zal hij geen gebruikerap Gedimmed, Uitgeschakeld, Ingeschakeld en Onbekend - Geeft het resultaat van het opgegeven Powershell commando of script. + Retourneert het resultaat van de opgegeven Powershell-opdracht of -script. +Opmerking: houd er rekening mee dat Home Assistant een payload van maximaal 255 tekens accepteert. -Converteert de uitkomst naar text. +Converteert de uitkomst naar tekst. Geeft informatie over alle geïnstalleerde printers en hun wachtrijen. @@ -3317,7 +3313,15 @@ Commando/payload moet in JSON-indeling zijn. Voorbeeld van alle mogelijke opties } Als er geen "playbackDevice" is opgegeven, zal HASS.Agent het standaardapparaat gebruiken. Als er geen "volume" is opgegeven, zal HASS.Agent alleen de mute-status instellen. -Als er geen "mute" is opgegeven, zal HASS.Agent het dempen van de opgegeven applicatie opheffen. +Als er geen "mute" is opgegeven, zal HASS.Agent het dempen van de opgegeven applicatie opheffen. + +Geavanceerde optie: er kan een extra "sessionId" worden opgegeven om het volume alleen in te stellen voor een specifieke sessie van de applicatie. De sessie-ID kan worden verkregen via de sensor "Audio Sessions": +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Voer een geldige JSON-string in! @@ -3361,4 +3365,71 @@ van de originele versie Fout bij starten satellietservice! + + Stelt de standaard audio-uitvoer voor het systeem in. +Vereist de naam van het audioapparaat als payload. + + + Naam audioapparaat + + + JSON-opdrachtpayload + + + Volume (tussen 0 en 100) + + + U levert een screenshot-sensor in de vorm van een camera-entiteit. +Schermnummer is afhankelijk van de systeemconfiguratie - begint bij 0. + + + Schermnummer + + + Stelt de standaardaudio-invoer in voor het systeem (inclusief het standaardcommunicatieapparaat). +U hebt de naam van het audioapparaat nodig als payload. + + + Negeer de respijtperiode na het ontwaken uit de winterslaap + + + Gebruik het moderne ladepictogram + + + Geeft het aantal processen weer die momenteel de microfoon gebruiken - geeft bovendien de naam van de processen in de attributen van de sensor. + +Opmerking: indien gebruikt in de satellietdienst, zal het geen gebruikersruimtetoepassingen detecteren. + + + NFC + + + Geavanceerde instellingen + + + Gea&vanceerde instellingen + + + Waarschuwing: het toevoegen van de volgende instellingen (overschrijvingen) kan de sensor kapot maken, wees voorzichtig! + + + Toont/verbergt het Tray Icon Web View. +Vereist dat het geconfigureerd is in "Configuratie -> Tray Icon" + + + Activering + + + Knop + + + Pers + + + Retourneert uw huidige breedtegraad, lengtegraad en hoogte als locatie-/apparaattracker, om direct weer te geven en te volgen op een kaart. + +Zorg ervoor dat de locatieservices van Windows zijn ingeschakeld! + +Afhankelijk van je Windows-versie is dit terug te vinden in het nieuwe configuratiescherm > 'privacy en beveiliging' > 'locatie'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx index 0296c3a4..9ed5d707 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pl.resx @@ -3093,7 +3093,7 @@ Uwaga: wyłączyłeś czyszczenie nazw, więc upewnij się, że nazwa Twojego ur Stara się wybudzić wszystkie monitory poprzez symulowanie wciśnięcia przycisku "do góry". - Ustawia poziom głośności domyślnego urządzenia na podaną wartość. + Ustawia poziom głośności domyślnego urządzenia na podaną wartość. Wprowadź wartość pomiędzy 0-100 jako nowy poziom głośności! @@ -3239,11 +3239,6 @@ Pokazuje tylko te urządzenia które zgłaszały się w okresie od ostatniego ra Upewnij się że lokalizacja jest włączona w systemie Windows! W zalezności od Twojej wersji Windows'a opcje te możesz znaleźć w Ustawienia -> Prywatność i Zabezpieczenia -> Lokalizacja - - - Zwraca nazwę procesu który obecnie używa mikrofonu. - -Wskazówka: jeżeli korzystasz z usługi Satellite, nie będziesz miał informacji o aplikacji w przestrzeni użytkownika. Zwraca ostatnią zmianę stanu ekranu: @@ -3251,9 +3246,10 @@ Wskazówka: jeżeli korzystasz z usługi Satellite, nie będziesz miał informac Przyciemnienie, Wyłączenie, Włączenie, Nieznany - Zwraca wynik poprzedniej komendy Powershell lub skryptu. + Zwraca wynik podanego polecenia lub skryptu programu PowerShell. +Uwaga: pamiętaj, że Home Assistant akceptuje ładunek o długości do 255 znaków. -Konwertuje wynik do tekstu. +Konwertuje wynik na tekst. Zwraca informacje na temat wszystkich zainstalowanych drukarek i ich kolejek. @@ -3406,7 +3402,15 @@ Polecenie/ładunek musi być w formacie JSON. Przykład wszystkich możliwych op Jeśli nie podano „playbackDevice”, HASS.Agent użyje urządzenia domyślnego. Jeśli nie zostanie podany „volume”, HASS.Agent ustawi jedynie stan wyciszenia. -Jeśli nie podano opcji „mute”, HASS.Agent wyłączy wyciszenie dostarczonej aplikacji. +Jeśli nie podano opcji „mute”, HASS.Agent wyłączy wyciszenie dostarczonej aplikacji. + +Opcja zaawansowana: można udostępnić dodatkowy parametr „sessionId”, aby ustawić głośność tylko dla konkretnej sesji aplikacji, identyfikator sesji można uzyskać z czujnika „Audio Sessions”: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Proszę wprowadzić prawidłowy ciąg JSON! @@ -3450,4 +3454,71 @@ z oryginalnej wersji Błąd podczas uruchamiania usługi satelity! + + Ustawia domyślne wyjście audio dla systemu. +Wymaga nazwy urządzenia audio jako ładunku. + + + Głośność (od 0 do 100) + + + Ładunek polecenia JSON + + + Nazwa urządzenia audio + + + Czujnik zrzutu ekranu w postaci kamery. +Numer ekranu zależy od konfiguracji systemu – zaczyna się od 0. + + + Numer ekranu + + + Ustawia domyślne wejście audio dla systemu (w tym domyślne urządzenie komunikacyjne). +Wymaga nazwy urządzenia audio jako ładunku. + + + Ignoruj okres karencji po przebudzeniu ze stanu hibernacji + + + Użyj nowoczesnej ikony w zasobniku + + + Podaje liczbę procesów aktualnie korzystających z mikrofonu - dodatkowo podaje nazwę procesów w atrybutach czujnika. + +Uwaga: jeśli jest używany w usłudze satelitarnej, nie wykryje aplikacji przestrzeni użytkownika. + + + NFC + + + Ustawienia zaawansowane + + + Ustaw&ienia zaawansowane + + + Uwaga, dodanie następujących ustawień (nadpisań) może spowodować uszkodzenie czujnika, zachowaj ostrożność! + + + Pokazuje/ukrywa Tray Icon Web View. +Wymaga konfiguracji w „Configuration -> Tray Icon” + + + Wyzwól + + + Przycisk + + + Naciśnij + + + Zwraca aktualną szerokość, długość i wysokość jako lokalizację/urządzenie śledzące, aby bezpośrednio wyświetlić i śledzić na mapie. + +Upewnij się, że usługi lokalizacyjne systemu Windows są włączone! + +W zależności od wersji systemu Windows można to znaleźć w nowym panelu sterowania > "prywatność i bezpieczeństwo" > "lokalizacja". + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx index 483830cc..d46cecb5 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.pt-br.resx @@ -2250,11 +2250,6 @@ HassAgentStarted, Logoff, SystemShutdown, Resume, Suspend, ConsoleConnect, Conso Fornece um valor bool com base em se o microfone está sendo usado no momento. Fuzzy - - Fornece o nome do processo que está usando o microfone no momento. - -Observação: se usado no serviço satélite, ele não detectará aplicativos de espaço de usuário. - Fornece a última alteração do estado de energia do monitor: @@ -2281,6 +2276,7 @@ Você pode explorar os contadores através da ferramenta 'perfmon.exe' do Window Retorna o resultado do comando ou script do Powershell fornecido. +Observação: lembre-se de que o Home Assistant aceita carga útil de até 255 caracteres. Converte o resultado em texto. @@ -3342,7 +3338,15 @@ O comando/carga precisa estar no formato JSON. Exemplo de todas as opções poss Se nenhum "playbackDevice" for fornecido, o HASS.Agent usará o padrão. Se nenhum "volume" for fornecido, o HASS.Agent definirá apenas o status mudo. -Se nenhum "mute" for fornecido, o HASS.Agent ativará o som do aplicativo fornecido. +Se nenhum "mute" for fornecido, o HASS.Agent ativará o som do aplicativo fornecido. + +Opção avançada: "sessionId" adicional pode ser fornecido para definir o volume apenas para uma sessão específica do aplicativo, o ID da sessão pode ser obtido no sensor "Sessões de Áudio": +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Insira uma string JSON válida! @@ -3386,4 +3390,71 @@ HASS.Agent da versão original Erro ao iniciar o serviço de satélite! + + Define a saída de áudio padrão para o sistema. +Requer o nome do dispositivo de áudio como carga útil. + + + Nome do dispositivo de áudio + + + Carga útil do comando JSON + + + Volume (entre 0 e 100) + + + Você fornece um sensor de captura de tela na forma de uma entidade de câmera. +O número da tela depende da configuração do sistema – começa em 0. + + + Número da tela + + + Define a entrada de áudio padrão para o sistema (incluindo o dispositivo de comunicação padrão). +Você precisa do nome do dispositivo de áudio como carga útil. + + + Ignorar o período de carência após sair da hibernação + + + Use o ícone moderno da bandeja + + + Fornece o número de processos que utilizam atualmente o microfone - fornece adicionalmente o nome dos processos nos atributos do sensor. + +Nota: se usado no serviço de satélite, não detectará aplicações no espaço do usuário. + + + NFC + + + Configurações avançadas + + + Configurações a&vançadas + + + Atenção, adicionar as seguintes configurações (substituições) pode quebrar o sensor, use com cuidado! + + + Mostra/oculta o Tray Icon Web View. +Requer que seja configurado em "Configuration -> Tray Icon" + + + Acionar + + + Botão + + + Imprensa + + + Retorna sua latitude, longitude e altitude atuais como rastreador de localização/dispositivo, para exibir e rastrear diretamente em um mapa. + +Certifique-se de que os serviços de localização do Windows estejam ativados! + +Dependendo da sua versão do Windows, isso pode ser encontrado no novo painel de controle > 'privacidade e segurança' > 'localização'. + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx index 81c2b7ab..df9e9686 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.resx @@ -2916,6 +2916,13 @@ Are you sure you want this? Make sure Windows' location services are enabled! +Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. + + + Returns your current latitude, longitude and altitude as as location/device tracker, to directly display and track on a map. + +Make sure Windows' location services are enabled! + Depending on your Windows version, this can be found in the new control panel -> 'privacy and security' -> 'location'. @@ -2925,6 +2932,7 @@ Dimmed, PowerOff, PowerOn and Unkown. Returns the result of the provided Powershell command or script. +Note: please keep in mind that Home Assistant accepts payload up to 255 characters. Converts the outcome to text. @@ -2951,7 +2959,7 @@ Hidden, Maximized, Minimized, Normal and Unknown. Note: if used in the satellite service, it won't detect userspace applications. - Provides the name of the process that's currently using the microphone. + Provides the number of the processes that currently use the microphone - additionally provides name of the processes in the sensor's attributes. Note: if used in the satellite service, it won't detect userspace applications. @@ -3266,7 +3274,15 @@ Command / payload needs to be in JSON format. Example of all possible options: If no "playbackDevice" is provided, HASS.Agent will use the default one. If no "volume" is provided, HASS.Agent will set only mute status. -If no "mute" is provided, HASS.Agent will unmute the provided application. +If no "mute" is provided, HASS.Agent will unmute the provided application. + +Advanced option: additional "sessionId" can be provided to set volume only for specific session of the application, session id can be obtained from "Audio Sessions" sensor: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Please enter a valid JSON string! @@ -3353,4 +3369,68 @@ from the original version Error communicating with the satellite service! + + Sets the default audio output for the system. +Requires audio device name as a payload. + + + Audio Device Name + + + JSON Command Payload + + + Volume (between 0 and 100) + + + Screen number + + + Provides a screenshot sensor in form of a camera entity. +Screen number depends on system configuration - starts at 0. + + + Sets the default audio input for the system (including default communication device). +Requires audio device name as a payload. + + + Ignore grace period after waking up from hibernation + + + Use modern tray icon + + + NFC + + + Ad&vanced Settings + + + Warning, adding following settings (overrides) may break the sensor, use with caution! + + + Device class + + + Unit of measurement + + + State class + + + Advanced Settings + + + Shows / hides the Tray Icon Web View. +Requires it to be configured in "Configuration -> Tray Icon" + + + Trigger + + + Press + + + Button + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx index d9f1ac5d..bb90c870 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.ru.resx @@ -3197,11 +3197,6 @@ Home Assistant. Убедитесь, что службы определения местоположения Windows включены! В зависимости от вашей версии Windows, это можно найти в новой панели управления -> 'конфиденциальность и безопасность' -> 'местоположение'. - - - Указывает имя процесса, который в данный момент использует микрофон. - -Примечание: если он используется в спутниковой службе, он не будет обнаруживать приложения пользовательского пространства. Обеспечивает последнее изменение состояния питания монитора: @@ -3209,7 +3204,8 @@ Home Assistant. Затемненный, Выключенный, Включенный и Неизвестный. - Возвращает результат предоставленной команды Powershell или сценария. + Возвращает результат предоставленной команды или сценария Powershell. +Примечание. Имейте в виду, что Home Assistant принимает полезную нагрузку длиной до 255 символов. Преобразует результат в текст. @@ -3365,7 +3361,15 @@ Home Assistant. Если «playbackDevice» не указано, HASS.Agent будет использовать устройство по умолчанию. Если «громкость» не указана, HASS.Agent установит только статус отключения звука. -Если «отключение звука» не указано, HASS.Agent включит звук предоставленного приложения. +Если «отключение звука» не указано, HASS.Agent включит звук предоставленного приложения. + +Расширенный вариант: можно указать дополнительный «sessionId» для установки громкости только для определенного сеанса приложения, идентификатор сеанса можно получить из датчика «Аудио сеансы»: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Пожалуйста, введите действительную строку JSON! @@ -3409,4 +3413,71 @@ Home Assistant. Ошибка при запуске спутниковой службы! + + Устанавливает аудиовыход по умолчанию для системы. +В качестве полезной нагрузки требуется имя аудиоустройства. + + + Громкость (от 0 до 100) + + + Полезная нагрузка команды JSON + + + Имя аудиоустройства + + + Вы предоставляете датчик скриншота в виде объекта камеры. +Номер экрана зависит от конфигурации системы — начинается с 0. + + + Номер экрана + + + Устанавливает аудиовход по умолчанию для системы (включая устройство связи по умолчанию). +В качестве полезной нагрузки требуется имя аудиоустройства. + + + Игнорировать льготный период после выхода из спящего режима + + + Использовать современный значок в трее + + + Предоставляет количество процессов, которые в данный момент используют микрофон. Дополнительно указывается имя процесса в атрибутах датчика. + +Примечание. При использовании в спутниковой службе он не обнаружит приложения пользовательского пространства. + + + NFC + + + Расширенные настройки + + + Рас&ширенные настройки + + + Внимание! Добавление следующих настроек (переопределений) может привести к поломке датчика, используйте с осторожностью! + + + Показывает/скрывает веб-представление значка в трее. +Требует настройки в разделе «Конфигурация -> Значок в трее» + + + Вызывать + + + Button + + + Нажимать + + + Возвращает текущую широту, долготу и высоту в виде отслеживания местоположения/устройства для непосредственного отображения и отслеживания на карте. + +Убедитесь, что службы определения местоположения Windows включены! + +В зависимости от вашей версии Windows, это можно найти в новой панели управления > разделах «Конфиденциальность и безопасность» > «Местоположение». + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx index c01e258d..884780a1 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.sl.resx @@ -2328,11 +2328,6 @@ HassAgentStarted, Odjava, SystemShutdown, Resume, Suspend, ConsoleConnect, Conso Zagotavlja bool vrednost glede na to, ali se mikrofon trenutno uporablja. Fuzzy - - Vrne ime procesa, ki trenutno uporablja mikrofon. - -Opomba: če se uporablja v satelitski storitvi - Vrne zadnjo spremembo načina monitorja: @@ -2358,9 +2353,10 @@ Primer: _Skupaj Številke lahko raziščete z orodjem Windows 'perfmon.exe'. - Vrne rezultat Powershell ukaza ali skripta. + Vrne rezultat podanega ukaza ali skripta Powershell. +Opomba: ne pozabite, da Home Assistant sprejme vsebino do 255 znakov. -Pretvori rezultat v tekst. +Pretvori rezultat v besedilo. Vrne informacijo o nameščenih tiskalnikih in čakalni vrsti za njih. @@ -3445,7 +3441,15 @@ Ukaz/tovor mora biti v formatu JSON. Primer vseh možnih možnosti: Če ni na voljo "playbackDevice", bo HASS.Agent uporabil privzeto. Če ni na voljo nobena "volume", bo HASS.Agent nastavil samo stanje utišanja. -Če ni na voljo možnost "mute", bo HASS.Agent znova vključil zvok navedene aplikacije. +Če ni na voljo možnost "mute", bo HASS.Agent znova vključil zvok navedene aplikacije. + +Napredna možnost: dodatni "sessionId" je mogoče zagotoviti za nastavitev glasnosti samo za določeno sejo aplikacije, ID seje je mogoče pridobiti iz senzorja "Audio Sessions": +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} Vnesite veljaven niz JSON! @@ -3489,4 +3493,71 @@ iz izvirne različice Napaka pri zagonu satelitske storitve! + + Nastavi privzeti zvočni izhod za sistem. +Zahteva ime zvočne naprave kot tovor. + + + Ime zvočne naprave + + + Tovor ukazov JSON + + + Glasnost (med 0 in 100) + + + Zagotovite senzor posnetka zaslona v obliki entitete kamere. +Številka zaslona je odvisna od konfiguracije sistema - začne se pri 0. + + + Številka zaslona + + + Nastavi privzeti zvočni vhod za sistem (vključno s privzeto komunikacijsko napravo). +Ime zvočne naprave potrebujete kot obremenitev. + + + Po prebujanju iz mirovanja prezrite obdobje odloga + + + Uporabi sodobno ikono pladnja + + + Zagotavlja število procesov, ki trenutno uporabljajo mikrofon - dodatno podaja ime procesov v atributih senzorja. + +Opomba: če se uporablja v satelitski storitvi, ne bo zaznal aplikacij uporabniškega prostora. + + + NFC + + + Napredne nastavitve + + + Napredne nasta&vitve + + + Opozorilo, dodajanje naslednjih nastavitev (preglasitev) lahko pokvari senzor, uporabljajte previdno! + + + Prikaže/skrije spletni pogled ikone pladnja. +Zahteva, da je konfiguriran v "Konfiguracija -> Ikona pladnja" + + + Sprožilec + + + Gumb + + + Pritisnite + + + Vráti vašu aktuálnu zemepisnú šírku, dĺžku a nadmorskú výšku ako sledovanie polohy/zariadenia na priame zobrazenie a sledovanie na mape. + +Uistite sa, že sú povolené lokalizačné služby systému Windows! + +V závislosti od verzie systému Windows ho nájdete v novom ovládacom paneli > "súkromie a zabezpečenie" > "umiestnenie". + \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx index f8e566f5..9d30ffb8 100644 --- a/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx +++ b/src/HASS.Agent/HASS.Agent/Resources/Localization/Languages.tr.resx @@ -1,6 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Bu sayfa, dış araçlarla bağlantı kurmanızı sağlar. - + Tarayıcı adı - + Varsayılan olarak HASS.Agent, varsayılan tarayıcınızı kullanarak URL'leri başlatır. Ayrıca, özel modda çalışacak başlatma argümanlarıyla birlikte kullanılacak belirli bir tarayıcıyı da yapılandırabilirsiniz. Fuzzy - + Tarayıcı İkili - + Ek Başlatma Argümanları - + Özel Yürütücü İkili Dosyası - + HASS.Agent'ı Perl veya Python gibi belirli bir yorumlayıcı kullanacak şekilde yapılandırabilirsiniz. Bu yürütücüyü başlatmak için 'özel yürütücü' komutunu kullanın. - + Özel Yürütücü Adı - + İpucu: Göz atmak için çift tıklayın - + &Ölçek - + HASS.Agent, MQTT veya HA'nın API'si ile olan bağlantı kesintilerini size bildirmeden önce bir ek süre bekleyecektir. Aşağıda bu ek süre içinde beklenecek saniye miktarını ayarlayabilirsiniz. - + saniye - + Bağlantı Kesildi İzin ve Süresi - + ÖNEMLİ: Bu değeri değiştirirseniz, HASS.Agent tüm sensörlerinizi, komutlarınızı yayından kaldıracak ve yeni cihaz adı altında yeniden yayınlanabilmeleri için kendini yeniden başlatmaya zorlayacaktır. Otomasyonlarınız ve komut dosyalarınız çalışmaya devam edecek. - + Cihaz adı, Home Assistant'ta makinenizi tanımlamak için kullanılır. Ayrıca komut/sensör adlarınız için bir önek olarak kullanılır (bu, varlık başına değiştirilebilir). Fuzzy - + Bu sayfa genel yapılandırma ayarlarını içerir, daha fazla ayar için soldaki sekmelere göz atabilirsiniz. - + Cihaz adı - + İpucu: Göz atmak için bu alana çift tıklayın - + Müşteri ve Sertifika - + &Otomatik istemci sertifikası seçimini kullan - + &Test bağlantısı - + Hangi varlıkları yapılandırdığınızı öğrenmek ve hızlı eylemler göndermek için HASS.Agent, Home Assistant'ın API'sini kullanır. @@ -197,991 +197,991 @@ ve 'TOKEN OLUŞTUR' düğmesini görene kadar sayfanın en altına giderek bir t İşlem yapılabilir bildirim işlevi için yönetici hesabı belirteci sağlamanız gerektiğini lütfen unutmayın. - + &API Simgesi - + Sunucu &URI - + &Temizlemek - + Hızlı işlemlerinizi almanın kolay bir yolu, genel bir global hotkey kullanmaktır. Bu şekilde, makinenizde ne yapıyorsanız yapın, Home Assistant ile her zaman etkileşimde bulunabilirsiniz. - + &Hızlı İşlemler Kısayol Tuşunu Etkinleştir - + &Kısayol Kombinasyonu - + Görüntü Önbelleğini Temizle - + Açık dosya - + Görüntü Önbelleği Konumu - + günler - + Bildirimlerde gösterilen resimler gibi bazı öğelerin geçici olarak yerel olarak depolanması gerekir. HASS.Agent bunları silmeden önce tutulması gereken gün miktarını yapılandırabilirsiniz. Bunları kalıcı olarak tutmak için '0' girin. - + Genişletilmiş günlük kaydı, varsayılan günlük kaydının yeterli olmaması durumunda daha ayrıntılı ve derinlemesine günlük kaydı sağlar. Lütfen bunun etkinleştirilmesinin günlük dosyalarının büyümesine neden olabileceğini ve yalnızca HASS.Agent'ın kendisinde bir sorun olduğundan şüphelendiğinizde veya geliştiriciler tarafından istendiğinde kullanılması gerektiğini unutmayın. - + &Genişletilmiş Günlüğe Kaydetmeyi Etkinleştir - + &Günlükler Klasörünü Aç - + İpucu: Göz atmak için bu alanları çift tıklayın - + Müşteri Sertifikası - + Kök Sertifikası - + Bayrağı &Kaldır Kullan - + &Güvenilmeyen Sertifikalara İzin Ver - + &Yapılandırmayı Temizle - + (emin değilseniz varsayılanı bırakın) - + Komutlar ve sensörler, yeni entegrasyonu kullanırken bildirimler ve medya oynatıcı işlevlerinin yanı sıra MQTT'yi kullanır. Lütfen aracınız için credentialları sağlayın, HA Mosquitto eklentisini kullanıyorsanız, muhtemelen önceden ayarlanmış adresi kullanabilirsiniz. Not: Bu ayarlar (Client ID hariç) uydu hizmetine de uygulanacaktır. - + Keşif Öneki - + Şifre - + Kullanıcı adı - + Liman - + Aracı IP Adresi veya Ana Bilgisayar Adı - + (otomatik oluşturmak için boş bırakın) - + Müşteri Kimliği - + Bir şey çalışmıyorsa, aşağıdaki adımları denediğinizden emin olun: - HASS.Agent entegrasyonunu kurun - Home Assistant'ı yeniden başlatın - MQTT etkinken HASS.Agent'ın etkin olduğundan emin olun! - Cihazınız otomatik olarak bir varlık olarak algılanmalı ve eklenmelidir - İsteğe bağlı olarak: yerel API'yi kullanarak manuel olarak ekleyin - + HASS.Agent metin, resim ve eylemleri kullanarak Home Assistant'tan bildirimler alabilir. MQTT'yi etkinleştirdiyseniz, cihazınız otomatik olarak eklenir. Aksi takdirde, yerel API'yi kullanmak için entegrasyonu manuel olarak yapılandırın. - + Bildirimler ve Belgeler - + Liman - + &Bildirimleri Kabul Et - + Test Bildirimini Göster - + Liman Rezervasyonunu Yürüt - + &Görüntüler için sertifika hatalarını yoksay - + Uydu hizmeti, hiçbir kullanıcı oturum açmadığında bile sensörleri ve komutları çalıştırmanıza izin verir. Yönetmek için ana penceredeki 'uydu hizmeti' düğmesini kullanın. - + Servis durumu: - + Hizmeti&başlat - + &Hizmeti Devre Dışı Bırak - + &Hizmeti durdur - + &Hizmeti Etkinleştir - + &Hizmeti Yeniden Yükle - + Hizmeti yapılandırmazsanız, hiçbir şey yapmaz. Ancak, yine de devre dışı bırakmaya karar verebilirsiniz. Yükleyici, devre dışı bırakılan hizmeti kendi başına bırakacaktır (hizmeti kaldırırsanız, yükleyici yeniden yükleyecektir). - + Düzgün çalışmıyorsa hizmeti yeniden yüklemeyi deneyebilirsiniz. Yapılandırmanız ve varlıklarınız kaldırılmayacak. - + Hizmet ve Günlükler Klasörünü Aç - + Yeniden yüklemeden sonra hizmet hala başarısız olursa, lütfen bir bilet açın ve en son günlüğün içeriğini gönderin. - + HASS.Agent, kullanıcı profilinizin kayıt defterinde bir giriş oluşturarak oturum açtığınızda başlayabilir. HASS.Agent kullanıcı tabanlı olduğundan, başka bir kullanıcı için başlatmak istiyorsanız, HASS.Agent'ı orada kurun ve yapılandırın. - + &Oturum Açıldığında Başlatmayı Etkinleştir - + Girişte Başlama Durumu: - + Beni &beta sürümlerinden haberdar et - + Yeni bir güncelleme mevcut olduğunda, HASS.Agent yükleyiciyi indirebilir ve sizin için başlatabilir. İndirilen dosyanın sertifikası çalıştırmadan önce kontrol edilecek, yine de sürüm notlarını gözden geçirecek ve güncellemeyi manuel olarak onaylayacaksınız. - + Gelecekteki güncellemeleri otomatik olarak &indir - + HASS.Agent, etkinleştirilirse arka planda güncellemeleri kontrol eder. Yeni bir güncelleme keşfedilirse, yeni bir sürümün yüklenmeye hazır olduğunu bildiren bir anında iletme bildirimi gönderilecektir. - + Yeni bir &sürüm çıktığında bana haber ver - + HASS.Agent'a hoş geldiniz! Aracıyı ilk kez başlatıyorsunuz gibi görünüyor. İlk kurulumda size yardımcı olmak için aşağıdaki yapılandırma adımlarını uygulayın veya alternatif olarak 'Kapat'ı tıklayın. - + Cihaz adı, Home Assistant'ta makinenizi tanımlamak için kullanılır, ayrıca komutlarınız ve sensörleriniz için önerilen bir önek olarak kullanılır. - + Cihaz adı - + Evet, Sistem Girişinde HASS.Agent'ı &başlatın - + HASS.Agent, sisteminizle başlayabilir, bu, oturum açar açmaz cihazınız ve Home Assistant arasındaki tüm sensörlerin ve veri aktarımının başlamasına olanak tanır. Bu ayar, daha sonra HASS.Agent yapılandırma penceresinde herhangi bir zamanda değiştirilebilir. - + Mevcut durum getiriliyor, lütfen bekleyin.. - + Not: 5115 varsayılan bağlantı noktasıdır, yalnızca Home Assistant'ta değiştirdiyseniz değiştirin. - + Evet, bağlantı noktasındaki bildirimleri kabul et - + HASS.Agent, metin ve/veya resimler kullanarak Home Assistant'tan bildirimler alabilir. Bu işlevi etkinleştirmek istiyor musunuz? - + HASS.Agent-Notifier GitHub Sayfası - + Şu adımları uyguladığınızdan emin olun: - HASS.Agent-Notifier entegrasyonunu kurun - Home Assistant'ı yeniden başlatın - Bir bildirim varlığı yapılandırın - Home Assistant'ı yeniden başlatın - + Bildirimleri kullanmak için Home Assistant'ta HASS.Agent-notifier entegrasyonunu kurmanız ve yapılandırmanız gerekir. Bu, HACS'yi kullanmak çok kolaydır, ancak manuel olarak da kurulabilir, daha fazla bilgi için aşağıdaki bağlantıyı ziyaret edin. - + API & Jeton - + Sunucu &URI (böyle olması gerekir) - + Hangi varlıkları yapılandırdığınızı öğrenmek ve hızlı eylemler göndermek için HASS.Agent, Home Assistant'ın API'sini kullanır. Lütfen uzun ömürlü bir erişim belirteci ve Home Assistant örneğinizin adresini sağlayın. Home Assistant'ta sol alttaki profil resminize tıklayarak ve 'TOKEN OLUŞTUR' düğmesini görene kadar sayfanın en altına giderek bir jeton alabilirsiniz. - + Test bağlantısı - + İpucu: Özel ayarlar, Yapılandırma Penceresinde bulunabilir. - + Şifre - + Kullanıcı adı - + Liman - + IP Adresi veya Ana Bilgisayar Adı - + Komutlar ve sensörler MQTT aracılığıyla gönderilir. Bildirimler ve medya oynatıcı entegrasyonu da bunlardan yararlanır. İpucu: HA eklentisini kullanıyorsanız, muhtemelen önceden ayarlanmış adresi kullanabilirsiniz - sadece kimlik bilgilerini sağlayın. - + Keşif Öneki - + (emin değilseniz varsayılanı bırakın) - + İpucu: Özel ayarlar, Yapılandırma Penceresinde bulunabilir. - + &Kısayol Kombinasyonu - + Hızlı işlemlerinizi almanın kolay bir yolu, genel bir kısayol tuşu kullanmaktır. Bu şekilde, makinenizde ne yapıyorsanız yapın, Home Assistant ile her zaman etkileşimde bulunabilirsiniz. - + &Temizlemek - + HASS.Agent, etkinleştirilirse arka planda güncellemeleri kontrol eder. Yeni bir güncelleme keşfedilirse, yeni bir sürümün yüklenmeye hazır olduğunu bildiren bir anında iletme bildirimi gönderilecektir. Bu otomatik güncelleme kontrollerini etkinleştirmek istiyor musunuz? - + Evet, beni yeni &güncellemelerden haberdar et - + Evet, benim için yükleyiciyi indirip başlatın - + Yeni bir güncelleme mevcut olduğunda, HASS.Agent yükleyiciyi indirebilir ve sizin için başlatabilir. İndirilen dosyanın sertifikası çalıştırmadan önce kontrol edilecek, yine de sürüm notlarını gözden geçirecek ve güncellemeyi manuel olarak onaylayacaksınız. - + HASS.Agent GitHub sayfası - + Kurcalanacak daha çok şey var, bu yüzden Yapılandırma Penceresine bir göz attığınızdan emin olun! HASS.Agent'ı kullandığınız için teşekkür ederiz, umarım işinize yarar :-) - + HASS.Agent şimdi yapılandırma değişikliklerinizi uygulamak için yeniden başlatılacak. - + Tamam, bitti! - + Düşük Bütünlük - + İsim - + Tip - + &Kaldırmak - + &Değiştir - + &Yeni ekle - + &Gönder && Komutları Etkinleştir - + saklanan komutlar! - + &Uygulamak - + Yetki ve Kimlik - + kimlik doğrulama - + Hizmetle bağlantı kurun - + Uydu servisi bağlanıyor, lütfen bekleyin.. - + Yapılandırmayı Getir - + Bu sayfa, MQTT ayarları, komutları ve sensörler için genel yapılandırma ayarlarını içerir, yukarıdaki farklı sekmelere göz atın. - + Yetki ve Kimlik - + Cihaz adı - + İpucu: Göz atmak için bu alanları çift tıklayın - + Özel Yürütücü ve İkili - + Özel ve Yürütücü Adı - + saniye - + Bağlantı Kesildi İzin ve Süresi - + Uygulamak - + Sürüm - + İpucu: Rastgele oluşturmak için çift tıklayın - + Saklanmış! - + (otomatik oluşturmak için boş bırakın) - + Müşteri Kimliği - + İpucu: Göz atmak için bu alanları çift tıklayın - + Müşteri Sertifikası - + Kök Sertifikası - + Bayrağı &Kaldır Kullan - + &Güvenilmeyen Sertifikalara İzin Ver - + &Yapılandırmayı Temizle - + (emin değilseniz varsayılanı bırakın) - + Komutlar ve sensörler MQTT aracılığıyla gönderilir. Lütfen sunucunuz için kimlik bilgilerini sağlayın. HA eklentisini kullanıyorsanız, muhtemelen önceden ayarlanmış adresi kullanabilirsiniz. - + Keşif Öneki - + Şifre - + Kullanıcı adı - + Liman - + Aracı IP Adresi veya Ana Bilgisayar Adı - + &Gönder && Yapılandırmayı Etkinleştir - + &HASS.Agent'tan kopyala - + Yapılandırma kaydedildi! - + Durum - + sorgulanıyor.. - + İsim - + Tip - + Yenile - + &Kaldırmak - + &Yeni ekle - + &Değiştir - + &Gönder && Sensörleri Etkinleştir - + Sensörler kaydedildi! - + Lütfen görev yapılırken biraz bekleyin .. - + API Bağlantı Noktası Bağlama Oluştur - + Güvenlik Duvarı Kuralı Belirle - + HASS.Agent Port Rezervasyonu - + Bazı güncelleme sonrası görevler yapılırken lütfen biraz bekleyin.. - + Uydu Hizmetini Yapılandırma - + API Bağlantı Noktası Bağlama Oluştur - + HASS.Agent Gönderi Güncellemesi - + HASS.Agent yeniden başlatılırken lütfen bekleyin.. - + Önceki örneğin kapanması bekleniyor.. - + HASS.Agent'ı Yeniden Başlatın - + HASS.Agent Yeniden Başlatıcı - + Uydu servisi yeniden kurulurken lütfen bekleyiniz.. - + Uydu Hizmetini Kaldır - + Uydu Hizmetini Yükle - + HASS.Agent Uydu Hizmetini Yeniden Yükleme - + Uydu hizmeti yapılandırılırken lütfen bekleyin.. - + Uydu Hizmetini Etkinleştir - + HASS.Agent Uydu Hizmetini Yapılandırma - + &Kapat - + Bu, eylem komutlarını yayınlayabileceğiniz MQTT konusudur: - + Panoya kopyala - + yardım ve örnekler - + MQTT Eylem Konusu - + &Kaldırmak - + &Değiştir - + &Yeni ekle - + &Komutları Sakla ve Etkinleştir - + İsim - + Tip - + Düşük Bütünlük - + Eylem - + Komut Yapılandırması - + &Mağaza Komutu - + &Yapılandırma - + &İsim - + Tanım - + &'Düşük Bütünlük' olarak çalıştır - + Bu nedir? - + Tip - + Seçilen Tip - + Hizmet - + ajan - + Yalnızca HASS.Agent! - + &Varlık Türü - + MQTT Eylem Konusunu Göster - + Eylem - + Emretmek - + Varlıklar alınıyor, lütfen bekleyin.. - + Hızlı Eylemler - + Hızlı İşlemleri &Depola - + &Yeni ekle - + &Değiştir - + &Kaldırmak - + &Ön izleme - + Alan adı - + varlık - + Eylem - + Kısayol tuşu - + Tanım - + Etkinleştirilmiş Fuzzy - + Hızlı Eylemler Yapılandırması - + &Mağaza Hızlı İşlemi - + Alan adı - + &Varlık - + İstenen ve Eylem - + &Tanım - + Varlıklar alınıyor, lütfen bekleyin.. - + kısayol tuşunu etkinleştir - + &kısayol tuşu kombinasyonu - + (isteğe bağlı, varlık adı yerine kullanılacaktır) - + Hızlı Eylem - + &Kaldırmak - + &Değiştir - + &Yeni ekle - + &Depola && Sensörleri Etkinleştir - + İsim - + Tip - + Yenile - + Sensör Yapılandırması - + &Mağaza Sensörü - + ayar 1 - + Seçilen Tip - + &İsim - + &Her güncelleme - + saniye - + Tanım - + Ayar 2 - + Ayar 3 - + Tip - + çok değerli Fuzzy - + Ajan - + Hizmet - + Yalnızca HASS.Agent! - + sensör - + Genel - + MQTT - + Komutlar - + Sensörler - + Uydu Servis Yapılandırması - + &Kapat - + Home Assistant platformu için Windows tabanlı bir istemci. - + Bu uygulama açık kaynak kodlu ve tamamen ücretsizdir, lütfen kullanılan bileşenlerin proje sayfalarını bireysel lisansları için kontrol edin: - + Sıkı çalışmalarını biz fanilerle paylaşma nezaketini gösteren bu projelerin geliştiricilerine büyük bir 'teşekkür ederim'. - + Ve tabi ki; Paulus Shoutsen ve Home Assistant :-) yaratan ve bakımını yapan tüm geliştirici ekibine teşekkürler - + Daha da fazla sevgiyle yaratılan orijinal - + HASS.Agent Ekibi şu anda bağış kabul etmemektedir. Ancak bu aracı beğendiyseniz orijinal (LAB02 Araştırması) geliştiricilere bir fincan kahve satın alarak destekleyin: - + Hakkında - + Genel - + Harici Araçlar - + Ev Yardımcısı API'sı - + Kısayol tuşu - + Yerel depolama - + Kerestecilik - + MQTT - + Bildirimler - + Uydu Hizmeti - + Başlatmak - + Güncellemeler - + &Hakkında - + &Yardım && İletişim - + &Yapılandırmayı Kaydet - + Kapat &Kaydetmeden - + Yapılandırma - + Ne yapmak istersin? - + &Tekrar başlat - + &Saklamak - + &Çıkış - + İletişim Kutusundan Çık - + &Kapat - + HASS.Agent ile sorun yaşıyorsanız ve herhangi bir sensör, komut veya genel destek ve geri bildirim için desteğe ihtiyacınız varsa, bize ulaşmanın birkaç yolu vardır: - + Hakkında - + Ana Sayfa Asistan Forumu - + GitHub Sorunları - + Diğer HA kullanıcılarının da size yardımcı olabileceğine ek olarak her şeyden biraz! - + Hataları bildirin, özellik istekleri gönderin, en son değişiklikleri görün vb. - + HASS.Agent'ı kurma ve kullanma konusunda yardım alın, hataları bildirin veya genel sohbete katılın! - + HASS.Agent belgelerine ve kullanım örneklerine göz atın. - + Yardım - + HASS.Agent'ı göster - + Hızlı İşlemleri Göster - + Yapılandırma - + Hızlı Eylemleri Yönet - + Yerel Sensörleri Yönetin - + Komutları Yönet - + Güncellemeleri kontrol et - + Bağış yapmak - + Yardım && İletişim - + Hakkında - + HASS.Agent'tan çıkın - + &Saklamak - + Kontroller - + U&uydu Hizmeti - + &yapılandırma - + &Hızlı İşlemler - + Yükleniyor.. - + Yükleniyor.. - + Sistem durumu - + Uydu Hizmeti: - + Komutlar: - + Sensörler: - + Hızlı İşlemler: - + Ev Asistanı API'sı: - + bildirim API'si: - + &Sonraki - + &Kapat - + &Öncesi - + HASS.Agent Katılımı - + Bilgi alınıyor, lütfen bekleyin.. - + Yeni bir sürüm mevcut: - + Sürüm notları - + &Güncellemeyi Yoksay - + Sürüm Sayfası - + HASS.Agent Güncellemesi - + Özel bir komut yürütün. Bu komutlar özel yükseltme olmadan çalışır. Yükseltilmiş olarak çalıştırmak için bir Zamanlanmış Görev oluşturun ve görevinizi yürütmek için komut olarak 'schtasks /Run /TN "TaskName"'i kullanın. Veya daha sıkı yürütme için 'düşük bütünlük olarak çalıştır'ı etkinleştirin. - + Komutu, yapılandırılmış özel yürütücü aracılığıyla yürütür (Yapılandırma -> Dış Araçlar'da). Komutunuz 'olduğu gibi' bir argüman olarak sağlanır, bu nedenle gerekirse kendi alıntılarınızı vb. sağlamanız gerekir. - + Makineyi hazırda bekletme moduna geçirir. - + Tek bir tuşa basmayı simüle eder. 'Keycode' metin kutusuna tıklayın ve simüle edilmesini istediğiniz tuşa basın. İlgili anahtar kodu sizin için girilecektir. @@ -1189,1714 +1189,1714 @@ TAB tuşu için lütfen LCTRL+TAB kullanın. Daha fazla tuşa ve/veya CTRL gibi değiştiricilere ihtiyacınız varsa, MultipleKeys komutunu kullanın. - + Varsayılan tarayıcınızda varsayılan olarak sağlanan URL'yi başlatır. 'Gizli' kullanmak için Yapılandırma -> Harici Araçlar'da belirli bir tarayıcı sağlayın. Yalnızca belirli bir URL'ye sahip bir pencere istiyorsanız (tam bir tarayıcı değil), bir 'WebView' komutu kullanın. - + Geçerli oturumu kilitler. - + Geçerli oturumun oturumunu kapatır. - + 'Sessiz' tuşunu simüle eder. - + 'Sonraki Medya' tuşunu simüle eder. - + 'Medya Duraklat/Oynat' tuşunu simüle eder. - + 'Önceki Medya' tuşunu simüle eder. - + 'Sesi Kısma' tuşunu simüle eder. - + 'Sesi Aç' tuşunu simüle eder. - + Birden fazla tuşa basmayı simüle eder. Her tuşun arasına [ ] koymanız gerekir, aksi takdirde HASS.Agent onları ayırt edemez. Diyelim ki X TAB Y SHIFT-Z'ye basmak istiyorsunuz, bu [X] [{TAB}] [Y] [+Z] olur. Kullanabileceğiniz birkaç numara vardır: - Bir parantezin basılmasını istiyorsanız, ondan kaçının, bu nedenle [ [\[] ve ] [\]] olur - Özel tuşlar { } arasında gidip gelir, örneğin {TAB} veya {UP} - SHIFT, CTRL için ^ ve ALT için % eklemek için bir tuşun önüne + koyun. Yani +C, SHIFT-C'dir. Veya +(CD), SHIFT-C ve SHIFT-D'dir, +CD ise SHIFT-C ve D'dir - Birden fazla basış için {z 15} kullanın, bu, Z'ye 15 kez basılacağı anlamına gelir. Daha fazla bilgi: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.sendkeys - + Bir Powershell komutu veya betiği yürütün. Bir komut dosyasının (*.ps1) konumunu veya tek satırlı bir komutu sağlayabilirsiniz. Bu, özel yükseklik olmadan çalışacaktır. - + Tüm sensör kontrollerini sıfırlar, tüm sensörleri değerlerini işlemeye ve göndermeye zorlar. Örneğin, bir HA yeniden başlatma sonrasında HASS.Agent'ı tüm sensörlerinizi güncellemeye zorlamak istiyorsanız kullanışlıdır. - + Bir dakika sonra makineyi yeniden başlatır. İpucu: Yanlışlıkla mı tetiklendi? Kapatmayı iptal etmek için 'shutdown /a' komutunu çalıştırın. - + Bir dakika sonra makineyi kapatır. İpucu: Yanlışlıkla mı tetiklendi? Kapatmayı iptal etmek için 'shutdown /a' komutunu çalıştırın. - + Makineyi uyku moduna geçirir. Not: Windows'taki bir sınırlama nedeniyle, bu yalnızca hazırda bekletme modu devre dışı bırakıldığında çalışır, aksi takdirde yalnızca hazırda bekletme moduna geçer. Bunu atlatmak için NirCmd (http://www.nirsoft.net/utils/nircmd.html) gibi bir şey kullanabilirsiniz. - + Lütfen tarayıcınızın ikili dosyasının konumunu girin! (.exe dosyası) - + Sağlanan tarayıcı ikili dosyası bulunamadı, lütfen yolun doğru olduğundan emin olun ve tekrar deneyin. - + Gizli bağımsız değişken sağlanmadı, bu nedenle tarayıcı muhtemelen normal şekilde başlatılacaktır. Devam etmek istiyor musun? - + Tarayıcınızı gizli modda başlatırken bir şeyler ters gitti! Daha fazla bilgi için lütfen günlükleri kontrol edin. - + Lütfen geçerli bir API anahtarı girin! - + Lütfen Ev Asistanınızın URI'si için bir değer girin. - + Bağlanılamadı, aşağıdaki hata döndürüldü: {0} - + Bağlantı Tamam! Ev Asistanı sürümü: {0} - + Görüntü önbelleği temizlendi! - + Temizlik.. - + Bildirimler şu anda devre dışı, lütfen bunları etkinleştirin ve HASS.Agent'ı yeniden başlatın, ardından tekrar deneyin. - + Test bildiriminin görünmesi gerekirdi, almadıysanız lütfen günlükleri kontrol edin veya sorun giderme ipuçları için belgelere bakın. Not: Bu, yalnızca yerel olarak bildirimlerin gösterilip gösterilmeyeceğini test eder! - + Bu bir test bildirimidir! - + Yürütülüyor, lütfen bekleyin.. - + Liman rezerve edilirken bir şeyler ters gitti! El ile yürütme gerekli ve panonuza bir komut kopyalandı, lütfen yükseltilmiş bir terminal açın ve komutu yapıştırın. Ayrıca Güvenlik Duvarı Kuralları bağlantı noktanızı değiştirmeyi unutmayın! - + Yüklü değil - + Engelli - + Koşma - + durduruldu - + Arızalı - + Hizmeti durdururken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Hizmet 'devre dışı' olarak ayarlanmıştır, bu nedenle başlatılamaz. Lütfen önce hizmeti etkinleştirin ve tekrar deneyin. - + Hizmeti başlatırken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Hizmeti devre dışı bırakırken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Hizmeti etkinleştirirken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Hizmeti yeniden yüklerken bir şeyler ters gitti, UAC istemine izin verdiniz mi? Daha fazla bilgi için HASS.Agent (hizmet değil) günlüklerini kontrol edin. - + Girişte Başlat devre dışı bırakılırken bir şeyler ters gitti, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Girişte Başlat devre dışı bırakılırken bir şeyler ters gitti, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Etkinleştirilmiş - + Girişte Başlamayı Devre Dışı Bırak - + Engelli - + Oturum Açıldığında Başlatmayı Etkinleştir - + Girişte Başlat etkinleştirildi! - + Girişte Başlat'ı şimdi etkinleştirmek istiyor musunuz? - + Girişte Başlat zaten etkinleştirildi, her şey hazır! - + Oturum Açılırken Başlat etkinleştiriliyor.. - + Bir şeyler yanlış gitti. Tekrar deneyebilir veya sonraki sayfaya atlayıp HASS.Agent'ın yeniden başlatılmasından sonra yeniden deneyebilirsiniz. - + Oturum Açıldığında Başlatmayı Etkinleştir - + Lütfen geçerli bir API anahtarı sağlayın. - + Lütfen Ev Asistanınızın URI'sini girin. - + Bağlanılamadı, aşağıdaki hata döndürüldü: {0} - + Bağlantı Tamam! Ev Asistanı sürümü: {0} - + Test yapmak.. - + Komutlarınız kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Uydu servisine bağlanılıyor, lütfen bekleyin.. - + Hizmete bağlanma başarısız oldu! - + Servis bulunamadı! Yapılandırma panelinden kurabilir ve yönetebilirsiniz. Çalışmaya başladığında, komutları ve sensörleri yapılandırmak için buraya geri gelin. - + Hizmetle iletişim başarısız oldu! - + Hizmetle iletişim kurulamıyor. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + Yetkisiz - + Servisle iletişime geçme yetkiniz yok. Doğru auth ID'niz varsa, şimdi ayarlayabilir ve tekrar deneyebilirsiniz. - + Ayarlar getirilemedi! - + Hizmet, ayarlarını isterken bir hata döndürdü. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + MQTT ayarları getirilemedi! - + Hizmet, MQTT ayarlarını isterken bir hata döndürdü. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + Yapılandırılan komutlar getirilemedi! - + Hizmet, yapılandırılmış komutlarını isterken bir hata döndürdü. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + Yapılandırılmış sensörler getirilemedi! - + Hizmet, yapılandırılmış sensörlerini isterken bir hata döndürdü. Daha fazla bilgi için günlükleri kontrol edin. Yapılandırma panelinden günlükleri açabilir ve hizmeti yönetebilirsiniz. - + Boş bir kimlik doğrulama kimliğinin saklanması, tüm HASS.Agent'ların hizmete erişmesine izin verecektir. Bunu istediğinden emin misin? - + Kaydederken bir hata oluştu, daha fazla bilgi için günlükleri kontrol edin. - + Lütfen bir cihaz adı girin! - + Lütfen önce bir yürütücü seçin. (İpucu: Gözatmak için çift tıklayın) - + Seçilen yürütücü bulunamadı, lütfen sağlanan yolun doğru olduğundan emin olun ve tekrar deneyin. - + Bu bilgisayardaki her HASS.Agent örneğinin uydu hizmetine bağlanmasını istemiyorsanız bir kimlik doğrulama kimliği ayarlayın. Yalnızca doğru kimliğe sahip örnekler bağlanabilir. Herkesin bağlanmasına izin vermek için boş bırakın. - + Bu, uydu hizmetinin kendisini Home Assistant'a kaydettiği addır. Varsayılan olarak, bilgisayarınızın adı artı '-uydu'dur. - + Uydu hizmetinin, MQTT aracısına bağlantının koptuğunu bildirmeden önce bekleyeceği süre. - + Durum alınırken hata oluştu, lütfen bilgi için günlükleri kontrol edin. - + Yapılandırma kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Sensörler kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Tüm adımlar başarıyla tamamlanmadı. Daha fazla bilgi için lütfen günlüklere bakın. - + Tüm adımlar başarıyla tamamlanmadı. Daha fazla bilgi için lütfen günlüklere bakın. - + HASS.Agent, {0} saniye sonra hala etkin. Lütfen tüm örnekleri kapatın ve manuel olarak yeniden başlatın. Daha fazla bilgi için günlükleri kontrol edin ve isteğe bağlı olarak geliştiricileri bilgilendirin. - + Tüm adımlar başarıyla tamamlanmadı, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Uydu Hizmetini Etkinleştir - + Uydu Hizmetini Devre Dışı Bırak - + Uydu Hizmetini Başlat - + Uydu Hizmetini Durdur - + İstenen hizmet durumu işlenirken bir şeyler ters gitti. Daha fazla bilgi için lütfen günlüklere bakın. - + Konu panoya kopyalandı! - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Komutlar kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Yeni Komut - + Mod Komutu - + Lütfen bir komut türü seçin! - + Lütfen geçerli bir komut türü seçin! - + Önce geçerli bir varlık türü seçin. - + Lütfen bir isim verin! - + Bu ada sahip bir komut zaten var, devam etmek istediğinizden emin misiniz? - + Bir komut sağlanmazsa, bu varlığı Home Assistant aracılığıyla yalnızca bir 'eylem' değeriyle kullanabilirsiniz, olduğu gibi çalıştırdığınızda herhangi bir işlem yapılmaz. Devam etmek istediğinizden emin misiniz? - + Bir komut veya komut dosyası girmezseniz, bu varlığı yalnızca Home Assistant aracılığıyla bir 'eylem' değeriyle kullanabilirsiniz. Olduğu gibi çalıştırmak hiçbir şey yapmaz. Bunu istediğinden emin misin? - + Lütfen bir anahtar kodu girin! - + Anahtarlar kontrol edilemedi: {0} - + Bir URL sağlanmazsa, bu varlığı Home Assistant aracılığıyla yalnızca bir 'eylem' değeriyle kullanabilirsiniz, olduğu gibi çalıştırdığınızda herhangi bir işlem yapılmaz. Devam etmek istediğinizden emin misiniz? - + Emretmek - + Komut veya Komut Dosyası - + Anahtar kod - + Anahtar kodları - + Gizli Modda Başlat - + Tarayıcı: Varsayılan Gizli modu etkinleştirmek için lütfen özel bir tarayıcı yapılandırın. - + URL - + Tarayıcı: {0} - + Yürütücü: Yok Lütfen bir yürütücü yapılandırın, aksi takdirde komutunuz çalışmayacaktır. - + Yürütücü: {0} - + Düşük bütünlük, komutunuzun kısıtlı ayrıcalıklarla yürütüleceği anlamına gelir. - + Bu, yalnızca belirli konumlardaki dosyaları kaydedip değiştirebileceği anlamına gelir, - + '%USERPROFILE%\AppData\LocalLow' klasörü gibi veya - + 'HKEY_CURRENT_USER\Software\AppDataLow' kayıt defteri anahtarı. - + Bundan etkilenmediğinden emin olmak için komutunuzu test etmelisiniz! - + yalnızca {0}! - + MQTT yöneticisi doğru şekilde yapılandırılmamış veya başlatma işlemini henüz tamamlamamıştır. - + Eksik yapılandırma nedeniyle varlıklarınız getirilemiyor, lütfen yapılandırma ekranında gerekli değerleri girin. - + Varlıklarınız getirilmeye çalışılırken bir hata oluştu! - + Yeni Hızlı Eylem - + Mod Hızlı Eylem - + Eksik yapılandırma nedeniyle varlıklarınız getirilemiyor, lütfen yapılandırma ekranında gerekli değerleri girin. - + Varlıklarınız getirilmeye çalışılırken bir hata oluştu. - + Lütfen bir varlık seçin! - + Lütfen bir alan seçin! - + Bilinmeyen işlem, lütfen geçerli bir işlem seçin. - + Kaydetme ve kaydetme, lütfen bekleyin.. - + Sensörler kaydedilirken bir hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Yeni Sensör - + Mod Sensörü - + Pencere Adı - + WMI Sorgusu - + WMI Kapsamı (isteğe bağlı) - + Kategori - + Tezgah - + Örnek (isteğe bağlı) - + İşlem - + Hizmet - + Lütfen bir sensör tipi seçin! - + Lütfen geçerli bir sensör tipi seçin! - + Lütfen bir isim verin! - + Bu ada sahip tek değerli bir sensör zaten var, devam etmek istediğinizden emin misiniz? - + Bu ada sahip çok değerli bir sensör zaten var, devam etmek istediğinizden emin misiniz? - + Lütfen 1 ile 43200 (12 saat) arasında bir aralık sağlayın! - + Lütfen bir pencere adı girin! - + Lütfen bir sorgu girin! - + Lütfen bir kategori ve örnek girin! - + Lütfen bir işlemin adını girin! - + Lütfen bir hizmetin adını girin! - + yalnızca {0}! - + Cihazınızın adını değiştirdiniz. Tüm sensörleriniz ve komutlarınız artık yayından kaldırılacak ve HASS.Agent daha sonra bunları yeniden yayınlamak için yeniden başlatılacaktır. Endişelenmeyin, mevcut adlarını koruyacaklar, böylece otomasyonlarınız veya komut dosyalarınız çalışmaya devam edecek. Not: ad 'temizlenecek', bu da harfler, rakamlar ve boşluklar dışındaki her şeyin bir alt çizgi ile değiştirileceği anlamına gelir. Bu, HA tarafından gereklidir. - + Yerel API'nin bağlantı noktasını değiştirdiniz. Bu yeni limanın rezerve edilmesi gerekiyor. Bunu yapmak için bir UAC isteği alacaksınız, lütfen onaylayın. - + Bir şeyler yanlış gitti! Lütfen gerekli komutu manuel olarak yürütün. Panonuza kopyalandı, sadece yükseltilmiş bir komut istemine yapıştırmanız gerekiyor. Güvenlik duvarı kuralınızın bağlantı noktasını da değiştirmeyi unutmayın. - + Liman başarıyla rezerve edildi! HASS.Agent şimdi yeni konfigürasyonu etkinleştirmek için yeniden başlatılacaktır. - + Yeniden başlatmaya hazırlanırken bir şeyler ters gitti. Lütfen manuel olarak yeniden başlatın. - + Yapılandırmanız kaydedildi. Çoğu değişiklik, HASS.Agent'ın yürürlüğe girmeden önce yeniden başlatılmasını gerektirir. Şimdi yeniden başlatmak istiyor musunuz? - + Ayarlarınız yüklenirken bir şeyler ters gitti. 'config' alt klasöründeki appsettings.json dosyasını kontrol edin veya yeni bir başlangıç yapmak için silin. - + HASS.Agent başlatılırken bir hata oluştu. Lütfen günlükleri kontrol edin ve GitHub'da bir hata raporu oluşturun. - + Yerel ve Sensörler Fuzzy - + &Komutlar - + Kontrol etme.. - + En son sürümü çalıştırıyorsunuz: {0}{1} - + Yeni bir BETA sürümü mevcut: - + HASS.Agent BETA Güncellemesi - + Yükleyiciyi &indirmek ve başlatmak istiyor musunuz? - + Sürüm sayfasına &getmek istiyor musunuz? - + Güncellemeyi yükle - + Beta Sürümünü Yükle - + Yayın Sayfasını Aç - + Beta Sürüm Sayfasını Aç - + İstek işleniyor, lütfen bekleyin.. - + İşleme.. - + HASS.Agent İlk Katılımı: [{0}/{1}] öğesini başlatın - + HASS.Agent İlk Katılım: Başlangıç [{0}/{1}] - + HASS.Agent İlk Katılım: Bildirimler [{0}/{1}] - + HASS.Agent İlk Katılım: Entegrasyon [{0}/{1}] - + HASS.Agent İlk Katılım: API [{0}/{1}] - + HASS.Agent İlk Katılımı: MQTT [{0}/{1}] - + HASS.Agent İlk Katılım: Kısayol Tuşu [{0}/{1}] - + HASS.Agent İlk Katılım: Güncellemeler [{0}/{1}] - + HASS.Agent İlk Katılımı: Tamamlandı [{0}/{1}] - + İlk katılım sürecini iptal etmek istediğinizden emin misiniz? İlerlemeniz kaydedilmeyecek ve bir sonraki başlatmada tekrar gösterilmeyecektir. - + Bilgi alınırken hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Güncellemeyi indirmeye hazırlanamıyor, daha fazla bilgi için günlükleri kontrol edin. Bunun yerine şimdi sürüm sayfası açılacaktır. - + Güncelleme indirilemiyor, daha fazla bilgi için günlükleri kontrol edin. Bunun yerine şimdi sürüm sayfası açılacaktır. - + İndirilen dosya sertifika kontrolünde BAŞARISIZ OLDU. Bu teknik bir hata olabileceği gibi tahrif edilmiş bir dosya da olabilir! Lütfen günlükleri kontrol edin ve bulguları içeren bir bilet gönderin. - + Yükleyici başlatılamıyor (UAC istemini onayladınız mı?), daha fazla bilgi için günlükleri kontrol edin. Bunun yerine şimdi sürüm sayfası açılacaktır. - + HASS API: Bağlantı kurulumu başarısız oldu. - + HASS API: İlk bağlantı başarısız oldu. - + HASS API: Bağlantı başarısız. - + İstemci sertifika dosyası bulunamadı. - + Bağlanamıyor, URI'yi kontrol edin. - + Yapılandırma getirilemiyor, lütfen API anahtarını kontrol edin. - + Bağlanamıyor, lütfen URI'yi ve yapılandırmayı kontrol edin. - + hızlı eylem: eylem başarısız oldu, bilgi için günlükleri kontrol edin - + hızlı eylem: eylem başarısız oldu, varlık bulunamadı - + MQTT: Bağlanırken hata oluştu - + MQTT: Bağlanılamadı - + MQTT: Bağlantı kesildi - + API'yi {0} bağlantı noktasına bağlamaya çalışırken hata oluştu. HASS.Agent'ın başka hiçbir örneğinin çalışmadığından ve bağlantı noktasının kullanılabilir ve kayıtlı olduğundan emin olun. - + Geçerli etkin pencerenin başlığını sağlar. - + Cihazınızın sesinin çeşitli yönleri hakkında bilgi sağlar: Mevcut en yüksek ses seviyesi (basit bir 'bir şey çalıyor' değeri olarak kullanılabilir). Varsayılan ses aygıtı: ad, durum ve ses düzeyi. Sesli oturumlarınızın özeti: uygulama adı, sessiz durumu, ses düzeyi ve mevcut en yüksek ses düzeyi. - + Mevcut şarj durumunu, tam şarjda tahmini dakika miktarını, yüzde olarak kalan şarjı, dakika cinsinden kalan şarjı ve elektrik hattı durumunu gösteren bir sensör sağlar. - + İlk CPU'nun mevcut yükünü yüzde olarak sağlar. - + İlk CPU'nun mevcut saat hızını sağlar. - + Geçerli ses seviyesini yüzde olarak sağlar. Şu anda varsayılan cihazınızın hacmini alıyor. - + Ekran miktarını, birincil ekranın adını ve ekran başına adını, çözünürlüğünü ve piksel başına bit sayısını gösteren bir sensör sağlar. - + Test amaçlı kukla sensör, 0 ile 100 arasında rastgele bir tamsayı değeri gönderir. - + Yüzde olarak ilk GPU'nun mevcut yükünü sağlar. - + İlk GPU'nun mevcut sıcaklığını sağlar. - + Kullanıcının en son ne zaman giriş yaptığını içeren bir tarih saat değeri sağlar. İsteğe bağlı olarak, sistem yapılandırılmış zaman penceresinde uyku/hazırda bekletme durumundan uyandırıldığında ve hiçbir kullanıcı etkinliği gerçekleştirilmediğinde sensörü geçerli tarihle günceller. - + Sistemin (yeniden) başlatıldığı son anı içeren bir tarih saat değeri sağlar. Önemli: Windows'un FastBoot seçeneği bu değeri atabilir, çünkü bu bir hazırda bekletme modudur. Güç Seçenekleri -> 'Güç düğmelerinin ne yapacağını seçin' -> 'Hızlı başlatmayı aç' seçeneğinin işaretini kaldırarak devre dışı bırakabilirsiniz. SSD'li modern makineler için pek bir fark yaratmaz, ancak devre dışı bırakmak, yeniden başlattıktan sonra temiz bir durum almanızı sağlar. - + Son sistem durumu değişikliğini sağlar: ApplicationStarted, Logoff, SystemShutdown, Resume, Suspend, ConsoleConnect, ConsoleDisconnect, RemoteConnect, RemoteDisconnect, SessionLock, SessionLogoff, SessionLogon, SessionRemoteControl ve SessionUnlock. - + Şu anda oturum açmış kullanıcının adını döndürür. Fuzzy - + Şu anda oturum açmış kullanıcıların json biçimli bir listesini döndürür. Fuzzy - + Yüzde olarak kullanılan bellek miktarını sağlar. - + Mikrofonun şu anda kullanılıp kullanılmadığına bağlı olarak bir bool değeri sağlar. Not: uydu hizmetinde kullanılırsa, kullanıcı alanı uygulamalarını algılamaz. - + Pencerenin o anda açık olup olmamasına bağlı olarak bir AÇIK/KAPALI değeri sağlar (etkin olması gerekmez). - + Seçilen ağ kartının/kartlarının kart bilgilerini, yapılandırmasını, aktarım ve paket istatistiklerini ve adreslerini (ip, mac, dhcp, dns) sağlar. Bu çok değerli bir sensördür. - + Bir performans sayacının değerlerini sağlar. Örneğin, yerleşik CPU yük sensörü şu değerleri kullanır: Kategori: İşlemci Sayacı: % İşlemci Zaman Örneği: _Toplam Sayaçları Windows' 'perfmon.exe' aracıyla keşfedebilirsiniz. - + İşlemin etkin örneklerinin sayısını sağlar. Fuzzy - + Sağlanan hizmetin durumunu döndürür: Bulunamadı, Durduruldu, StartPending, StopPending, Running, ContinuePending, PausePending veya Paused. 'Görünen ad' değil, 'Hizmet adı' sağladığınızdan emin olun. - + Geçerli oturum durumunu sağlar: Kilitli, Kilitli Değil veya Bilinmiyor. Oturum durumu değişikliklerini izlemek için bir LastSystemStateChangeSensor kullanın. - + Mevcut tüm çıkarılabilir olmayan disklerin etiketlerini, toplam boyutunu (MB), kullanılabilir alanı (MB), kullanılan alanı (MB) ve dosya sistemini sağlar. - + Geçerli kullanıcı durumunu sağlar: NotPresent, Busy, RunningDirect3dFullScreen, PresentationMode, Kabul Ediyor, QuietTime veya RunningWindowsStoreApp. Örneğin, bildirimlerin mi yoksa TTS mesajlarının mı gönderileceğini belirlemek için kullanılabilir. - + Web kamerasının şu anda kullanılıp kullanılmadığına bağlı olarak bir bool değeri sağlar. Not: uydu hizmetinde kullanılırsa, kullanıcı alanı uygulamalarını algılamaz. - + Bekleyen sürücü güncellemelerinin miktarını gösteren bir sensör, bekleyen yazılım güncellemelerinin miktarını gösteren bir sensör, bekleyen tüm sürücü güncelleme bilgilerini (başlık, kb makale kimlikleri, gizli, tür ve kategoriler) içeren bir sensör ve bekleyenler için aynısını içeren bir sensör sağlar. yazılım güncellemeleri. Bu maliyetli bir istektir, bu nedenle önerilen aralık 15 dakikadır (900 saniye). Ancak 10 dakika ile sınırlandırılmıştır, daha düşük bir değer verirseniz, bilinen son listeyi alırsınız. - + WMI sorgusunun sonucunu sağlar. - + Ayarlar yüklenirken hata oluştu: {0} - + Başlangıç ayarları kaydedilirken hata oluştu: {0} - + Ayarlar kaydedilirken hata oluştu: {0} - + Komutlar yüklenirken hata oluştu: {0} - + Komutları depolarken hata oluştu: {0} - + Hızlı işlemler yüklenirken hata oluştu: {0} - + Hızlı işlemler kaydedilirken hata oluştu: {0} - + Sensör yüklenirken hata oluştu: {0} - + Sensörler kaydedilirken hata oluştu: {0} - + MQTT: - + Wiki - + Meşgul, lütfen bekleyin.. - + Arayüz ve Dil - + veya - + Bitiş - + Arayüz ve Dil - + Yapılandırma eksik - + bağlı - + Bağlanıyor.. - + Bağlantı kesildi - + Hata - + Gelenek - + ÖzelYürütücü - + hazırda bekletme - + Anahtar - + LaunchUrl - + Kilit - + Oturumu Kapat - + MedyaSesi Kapat - + MedyaSonraki - + MedyaOynatDuraklat - + MedyaÖnceki - + MedyaSesi Azalt - + MedyaSesi Açma - + Çoklu Tuşlar - + Güç kalkanı - + Tüm Sensörleri Yayınla - + Tekrar başlat - + Kapat - + Uyumak - + Kabul EdiyorBildirimler - + Meşgul - + Mevcut değil - + Sunum modu - + Sessiz zaman - + KoşuDirect3dFullScreen - + ÇalışanWindowsStoreApp - + KonsolBağlantısı - + Konsol Bağlantısını Kes - + HassAgentUydu HizmetiBaşladı - + HassAgentBaşladı - + Oturumu Kapat - + Uzak Bağlantı - + UzakBağlantıyı Kes - + Sürdürmek - + Oturum Kilidi - + OturumOturumu Kapatma - + OturumOturumu - + OturumUzaktan Kontrol - + Oturum Kilidini Aç - + Askıya almak - + Sistem Kapatma - + ActiveWindow - + Ses - + pil - + İşlemci Yükü - + GeçerliSaatHızı - + Geçerli Hacim - + Görüntülemek - + kukla - + GpuYük - + GpuSıcaklığı - + SonAktif - + Son Önyükleme - + LastSystemStateChange - + Kayıtlı Kullanıcı - + Kayıtlı Kullanıcılar - + Hafıza kullanımı - + MikrofonAktif - + Adlandırılmış Pencere - + - + PerformansSayacı - + SüreçAktif - + Hizmet Durumu - + Oturum Durumu - + Depolamak - + Kullanıcı Bildirimi - + Web kamerasıAktif - + Windows Güncellemeleri - + WmiQuery - + Otomasyon - + İklim - + Örtmek - + GirişBoole - + Işık - + Medya oynatıcı - + Faliyet alani, sahne - + Senaryo - + Değiştirmek - + Kapat - + Kapalı - + Açık - + Açık - + Duraklat - + Oyna - + Durmak - + Aç/Kapat - + Buton - + Işık - + Kilit - + Siren - + Değiştirmek - + Bağlanıyor.. - + Engelli - + Arızalı - + Yükleniyor.. - + Koşma - + durduruldu - + Kilitli - + Bilinmeyen - + kilidi açıldı - + Coğrafi Konum - + Herşey - + &Ölçek - + Test Performansı Sayacı - + WMI Sorgusunu Test Et - + Ağ kartı - + Önce bir kategori ve sayaç girin. - + Test başarıyla yürütüldü, sonuç değeri: {0} - + Test yürütülemedi: {0} Günlükler klasörünü açmak istiyor musunuz? - + Önce bir WMI sorgusu girin. - + Sorgu başarıyla yürütüldü, sonuç değeri: {0} - + Sorgu yürütülemedi: {0} Günlükler klasörünü açmak istiyor musunuz? - + Kapsamınız hatalı biçimlendirilmiş gibi görünüyor, muhtemelen şöyle başlamalıdır: \\.\ROOT\ Girdiğiniz kapsam: {0} İpucu: kapsamı ve sorgu alanlarını değiştirmediğinizden emin olun. Hâlâ mevcut değerleri kullanmak istiyor musunuz? - + Uygulama Başladı - + Uydu hizmetini, oturum açmak zorunda kalmadan sensörleri ve komutları çalıştırmak için kullanabilirsiniz. Tüm türler mevcut değildir, örneğin 'LaunchUrl' komutu yalnızca normal bir komut olarak eklenebilir. - + Bilinen Son Değer - + API'yi {0} bağlantı noktasına bağlamaya çalışırken hata oluştu. HASS.Agent'ın başka hiçbir örneğinin çalışmadığından ve bağlantı noktasının kullanılabilir ve kayıtlı olduğundan emin olun. - + SendWindowToFront - + Web Görünümü - + Sağlanan URL ile bir pencere gösterir. Bu, 'LaunchUrl' komutundan farklıdır, çünkü tam teşekküllü bir tarayıcı yüklemez, yalnızca kendi penceresinde sağlanan URL'yi yükler. Bunu, örneğin Home Assistant'ın kontrol panelini hızlı bir şekilde göstermek için kullanabilirsiniz. Varsayılan olarak, çerezleri süresiz olarak saklar, bu nedenle yalnızca bir kez oturum açmanız gerekir. - + HASS.Ajan Komutları - + Belirtilen işlemi arar ve ana penceresini öne göndermeye çalışır. Uygulama simge durumuna küçültülürse geri yüklenir. Örnek: VLC'yi ön plana göndermek istiyorsanız, 'vlc' kullanın. - + Komutu yapılandırmazsanız, bu varlığı yalnızca Home Assistant aracılığıyla bir 'eylem' değeriyle kullanabilirsiniz ve varsayılan ayarlar kullanılarak görünür, olduğu gibi çalıştırıldığında herhangi bir işlem yapılmaz. Bunu yapmak istediğinden emin misin? - + Ses Önbelleğini Temizle - + Temizlik.. - + Ses önbelleği temizlendi! - + Web Görünümü Önbelleğini Temizle - + Temizlik.. - + WebView önbelleği temizlendi! - + Görünüşe göre alternatif bir ölçeklendirme kullanıyorsunuz. Bu, HASS.Agent'ın bazı bölümlerinin amaçlandığı gibi görünmemesine neden olabilir. Lütfen kullanılamayan yönleri GitHub'da bildirin. Teşekkürler! Not: Bu mesaj yalnızca bir kez gösterilir. - + Depolanan komut ayarları yüklenemiyor, varsayılana sıfırlanıyor. - + Komut ve Parametreleri Yapılandır - + Bağlantı Noktası ve Rezervasyonu Yürüt - + &Yerel API'yi Etkinleştir - + HASS.Agent'ın kendi yerel API'si vardır, bu nedenle Home Assistant istek gönderebilir (örneğin bir bildirim göndermek için). Buradan global olarak yapılandırabilir ve daha sonra bağımlı bölümleri (şu anda bildirimler ve mediaplayer) yapılandırabilirsiniz. Not: Yeni entegrasyonun çalışması için bu gerekli değildir. Yalnızca MQTT kullanmıyorsanız etkinleştirin ve kullanın. - + İstekleri dinleyebilmek için HASS.Agent'ın portunun ayrılmış ve güvenlik duvarınızda açılmış olması gerekir. Bunu sizin için yaptırmak için bu düğmeyi kullanabilirsiniz. - + &Liman - + Ses Önbelleği Konumu - + günler - + Görüntü Önbelleği Konumu - + Sesi şunun için sakla: - + Resimleri şunun için sakla: - + Her seferinde önbelleği temizle - + Web Görünümü Önbellek Konumu - + Medya Oynatıcı ve Belgeler - + &Medya Oynatıcı İşlevselliğini Etkinleştir - + HASS.Agent, Home Assistant için bir medya oynatıcı görevi görebilir, böylece çalmakta olan herhangi bir medyayı görebilir ve kontrol edebilir ve metinden konuşmaya gönderebilirsiniz. MQTT'yi etkinleştirdiyseniz, cihazınız otomatik olarak eklenir. Aksi takdirde, yerel API'yi kullanmak için entegrasyonu manuel olarak yapılandırın. - + Bir şey çalışmıyorsa, aşağıdaki adımları denediğinizden emin olun: - HASS.Agent entegrasyonunu kurun - Home Assistant'ı yeniden başlatın - MQTT etkinken HASS.Agent'ın etkin olduğundan emin olun! - Cihazınız otomatik olarak bir varlık olarak algılanmalı ve eklenmelidir - İsteğe bağlı olarak: yerel API'yi kullanarak manuel olarak ekleyin - + Yerel API devre dışıdır, ancak medya oynatıcının çalışması için buna ihtiyacı vardır. - + &TLS - + Yerel API devre dışıdır, ancak medya oynatıcının çalışması için buna ihtiyacı vardır. - + Önizlemeyi Göster - + &Varsayılan Menüyü Göster - + &Web Görünümünü Göster - + &Sayfayı arka planda yüklü tut - + Sağ tıklandığında tepsi simgesinin davranışını kontrol edin. - + (Bu, ekstra kaynaklar kullanır, ancak yükleme süresini azaltır.) - + Boyut (px) - + &WebView URL'si (Örneğin, Home Assistant Dashboard URL'niz) - + Yerel API - + Medya oynatıcı - + Tepsi ikonu - + Giriş dilinizin '{0}' varsayılan CTRL-ALT-Q kısayol tuşuyla çakıştığı biliniyor. Lütfen kendinizinkini ayarlayın. - + Giriş diliniz '{0}' bilinmiyor ve varsayılan CTRL-ALT-Q kısayol tuşuyla çakışabilir. Lütfen emin olmak için kontrol edin. Varsa, listeye eklenebilmesi için GitHub'da bir bilet açmayı düşünün. - + Anahtar bulunamadı - + parantezler eksik, tüm tuşları [ ] ile başlatın ve kapatın - + Anahtarlar ayrıştırılırken hata oluştu, daha fazla bilgi için lütfen günlükleri kontrol edin. - + Açık parantezlerin sayısı ('['), kapalı parantezlerin sayısına karşılık gelmez. (']')! ({0} - {1}) - + belgeler - + Belgeler ve Kullanım Örnekleri - + Güncellemeleri kontrol et - + Yerel API: - + Uydu Hizmetini Yönet - + Bildirimleri kullanmak için Home Assistant'ta HASS.Agent-notifier entegrasyonunu kurmanız ve yapılandırmanız gerekir. Bu, HACS'ı kullanmak çok kolaydır, ancak manuel olarak da kurabilirsiniz. Daha fazla bilgi için aşağıdaki bağlantıyı ziyaret edin. - + Aşağıdaki adımları uyguladığınızdan emin olun: - HASS.Agent-Notifier ve/veya HASS.Agent-MediaPlayer entegrasyonunu kurun - Home Assistant'ı yeniden başlatın -Bir bildirim ve/veya media_player varlığı yapılandırın -Home Assistant'ı yeniden başlatın - + Aynı şey medya oynatıcı için de geçerlidir; bu entegrasyon, cihazınızı bir media_player varlığı olarak kontrol etmenize, neyin oynatıldığını görmenize ve metinden konuşmaya göndermenize olanak tanır. - + HASS.Agent-MediaPlayer GitHub Sayfası - + HASS.Agent-Entegrasyon GitHub Sayfası - + Evet, bağlantı noktasında yerel API'yi &etkinleştirin - + &Medya Oynatıcıyı ve metinden konuşmaya (TTS) etkinleştirin - + &Bildirimleri Etkinleştir - + HASS.Agent'ın kendi dahili API'si vardır, bu nedenle Home Assistant istek gönderebilir (bildirimler veya metinden konuşmaya gibi). Etkinleştirmek istiyor musunuz? - + Hangi modülleri etkinleştirmek istediğinizi seçebilirsiniz. HA entegrasyonları gerektirirler, ancak merak etmeyin, sonraki sayfa bunları nasıl kuracağınız konusunda size daha fazla bilgi verecektir. - + Not: 5115 varsayılan bağlantı noktasıdır, yalnızca Home Assistant'ta değiştirdiyseniz değiştirin. - + &TLS - + Cihaz adınız, HA tarafından izin verilmeyen bazı karakterler içeriyordu (yalnızca harfler, rakamlar ve boşluklar). Son ad: {0} Bu sürümü kullanmak istiyor musunuz? - + HASS.Agent Katılımı - + saklanmış! - + &TLS - + &Kaydetmek - + &Her zaman ekranda ortalanmış olarak göster - + Pencerenin &başlık çubuğunu göster - + Pencereyi 'Her zaman &Üstte' olarak ayarla - + Web görünümü komutunuzun boyutunu ve konumunu ayarlamak için bu pencereyi sürükleyip yeniden boyutlandırın. - + Konum - + Boyut - + İpucu: Bir Web Görünümünü kapatmak için ESCAPE'e basın. - + &URL - + Web Görünümü Yapılandırması - + Web Görünümü - + Sağladığınız anahtar kodu geçerli bir numara değil! Lütfen anahtar kodu alanının odakta olduğundan emin olun ve simüle etmek istediğiniz tuşa basın, ardından anahtar kodu sizin için oluşturulmalıdır. - + Cihaz Adını ve Sanitasyonunu Etkinleştir - + Durum Bildirimlerini Etkinleştir - + HASS.Agent, HA'nın kabul edeceğinden emin olmak için cihaz adınızı temizleyecektir, adınızın olduğu gibi kabul edileceğinden eminseniz aşağıdaki bu kuralı geçersiz kılabilirsiniz. - + HASS.Agent, bir modülün durumu değiştiğinde bildirim gönderir, bu bildirimleri almak isteyip istemediğinizi aşağıdan ayarlayabilirsiniz. - + Cihazınızın adını değiştirdiniz. Tüm sensörleriniz ve komutlarınız artık yayından kaldırılacak ve HASS.Agent yeniden başlatıldıktan sonra yeniden yayınlanacaktır. Merak etme! Otomasyonlarınız ve komut dosyalarınızın çalışmaya devam etmesi için mevcut adlarını koruyacaklar. Not: Temizliği devre dışı bıraktınız, bu nedenle cihaz adınızın Home Assistant tarafından kabul edildiğinden emin olun. - + Yazıcılar - + MonitörUyku - + MonitörUyandırma - + Açık - + Kapat - + karartılmış - + Bilinmeyen - + MonitörGüç Durumu - + PowershellSensor - + Sesi Ayarla - + Maksimize - + Küçültülmüş - + Normal - + Bilinmeyen - + Gizlenmiş - + Pencere Durumu - + Web kamerasıSüreci - + MikrofonSüreci - + Tüm monitörleri uyku (düşük güç) moduna geçirir. - + 'Yukarı ok' tuş basımını simüle ederek tüm monitörleri uyandırmaya çalışır. - + Geçerli varsayılan ses cihazının sesini belirtilen düzeye ayarlar. - + Lütfen istenen ses seviyesi olarak 0-100 arasında bir değer giriniz! - + Emretmek - + Bir hacim değeri girmezseniz, bu varlığı yalnızca Home Assistant aracılığıyla bir 'eylem' değeriyle kullanabilirsiniz. Olduğu gibi çalıştırmak hiçbir şey yapmaz. Bunu istediğinden emin misin? - + Sağladığınız ad, desteklenmeyen karakterler içeriyor ve çalışmayacak. Önerilen sürüm: {0} Bu sürümü kullanmak istiyor musunuz? - + Sağladığınız API jetonu geçerli görünmüyor, lütfen jetonun tamamını seçtiğinizden emin olun (CTRL + A kullanmayın veya çift tıklamayın). Geçerli bir API anahtarı, iki noktayla ayrılmış üç bölüm içerir. Yine de bu anahtarı kullanmak istediğinizden emin misiniz? - + Sağladığınız URI geçerli görünmüyor, geçerli bir URI aşağıdakilerden biri gibi görünebilir: - http://homeassistant.local:8123 - http://192.168.0.1:8123 Kullanmak istediğinizden emin misiniz? yine de bu URI? - + Test yapmak.. - + hem yerel API hem de MQTT devre dışı bırakıldı, ancak entegrasyonun çalışması için en az birine ihtiyacı var - + MQTT'yi etkinleştir - + MQTT etkinleştirilmezse komutlar ve sensörler çalışmayacaktır! - + hem yerel API hem de MQTT devre dışı bırakıldı, ancak entegrasyonun çalışması için en az birine ihtiyacı var - + &Hizmeti Yönet - + Hizmet şu anda durduruldu ve yapılandırılamıyor. Lütfen yapılandırmak için önce hizmeti başlatın. - + Servisi yönetmek istiyorsanız (komut ve sensör ekleyin, ayarları değiştirin) buradan veya ana penceredeki 'uydu servisi' butonunu kullanarak yapabilirsiniz. - + Fare sol tıklamasıyla varsayılan menüyü göster - + Home Assistant API jetonunuz doğru görünmüyor. Tüm belirteci seçtiğinizden emin olun (CTRL+A kullanmayın veya çift tıklamayın). Üç bölüm içermelidir (iki nokta ile ayrılmış). Bu şekilde kullanmak istediğinizden emin misiniz? - + Ev Asistanı URI'niz doğru görünmüyor. 'http://homeassistant.local:8123' veya 'https://192.168.0.1:8123' gibi görünmelidir. Bu şekilde kullanmak istediğinizden emin misiniz? - + MQTT broker URI'niz doğru görünmüyor. 'homeassistant.local' veya '192.168.0.1' gibi görünmelidir. Bu şekilde kullanmak istediğinizden emin misiniz? - + &Kapat - + Zaten bağış yaptım, ana penceredeki düğmeyi gizle. - + HASS.Agent tamamen ücretsizdir ve her zaman kısıtlama olmaksızın bu şekilde kalacaktır! Ancak, bu aracı (ve destek ve dokümanlar gibi onu çevreleyen her şeyi) geliştirmek ve sürdürmek çok zaman alır. Çoğu geliştirici gibi, ben de kafein kullanıyorum - bu yüzden eğer onu ayırabilirseniz, bir fincan kahve her zaman çok değerlidir! - + Bağış yapmak - + URL ayarlanmadı! Lütfen web görünümünü Yapılandırma -> Tepsi Simgesi aracılığıyla yapılandırın. - + Güncellemeleri kontrol et - + Sağladığınız API jetonu geçerli görünmüyor, lütfen jetonun tamamını seçtiğinizden emin olun (CTRL + A kullanmayın veya çift tıklamayın). Geçerli bir API anahtarı, iki noktayla ayrılmış üç bölüm içerir. Yine de bu anahtarı kullanmak istediğinizden emin misiniz? - + Sağladığınız URI geçerli görünmüyor, geçerli bir URI aşağıdakilerden biri gibi görünebilir: - http://homeassistant.local:8123 - http://192.168.0.1:8123 Kullanmak istediğinizden emin misiniz? yine de bu URI? - + Bu aracı (ve onu çevreleyen her şeyi) geliştirmek ve sürdürmek çok zaman alır. Çoğu geliştirici gibi, ben de kafein kullanıyorum - bu yüzden eğer onu ayırabilirseniz, bir fincan kahve her zaman çok değerlidir! - + İpucu: Hakkında Penceresinde başka bağış yöntemleri de mevcuttur. - + &Media Player'ı etkinleştir (metinden sese dahil) - + &Bildirimleri Etkinleştir - + MQTT'yi etkinleştir - + HASS.Agent Gönderi Güncellemesi - + Bulunan bluetooth cihazlarının miktarını gösteren bir sensör sağlar. Cihazlar ve bağlı durumları nitelik olarak eklenir. - + Bulunan bluetooth LE cihazlarının miktarını gösteren bir sensör sağlar. Cihazlar ve bağlı durumları nitelik olarak eklenir. Yalnızca son rapordan bu yana görülen cihazları gösterir, ör. sensör yayınlandığında liste temizlenir. - + Geçerli enlem, boylam ve yüksekliğinizi virgülle ayrılmış bir değer olarak döndürür. Windows'un konum hizmetlerinin etkinleştirildiğinden emin olun! Windows sürümünüze bağlı olarak bu, yeni kontrol panelinde -> 'gizlilik ve güvenlik' -> 'konum'da bulunabilir. - - Şu anda mikrofonu kullanan işlemin adını sağlar. Not: uydu hizmetinde kullanılırsa, kullanıcı alanı uygulamalarını algılamaz. - - + Son monitör güç durumu değişikliğini sağlar: Soluk, Güç Kapalı, Güç Açık ve Bilinmiyor. - - Sağlanan Powershell komutunun veya komut dosyasının sonucunu döndürür. Sonucu metne dönüştürür. + + Sağlanan Powershell komutunun veya betiğinin sonucunu döndürür. +Not: Home Assistant'ın 255 karaktere kadar yükü kabul ettiğini lütfen unutmayın. + +Sonucu metne dönüştürür. - + Yüklü tüm yazıcılar ve sıraları hakkında bilgi sağlar. - + Şu anda web kamerasını kullanan işlemin adını sağlar. Not: uydu hizmetinde kullanılırsa, kullanıcı alanı uygulamalarını algılamaz. - + İşlem penceresinin mevcut durumunu sağlar: Gizli, Büyütülmüş, Küçültülmüş, Normal ve Bilinmeyen. - + powershell komutu veya komut dosyası - + Sağladığınız ad, desteklenmeyen karakterler içeriyor ve çalışmayacak. Önerilen sürüm: {0} Bu sürümü kullanmak istiyor musunuz? - + Test Komutu/Komut Dosyası - + Lütfen bir komut veya komut dosyası girin! - + Test başarıyla yürütüldü, sonuç değeri: {0} - + Test yürütülemedi: {0} Günlükler klasörünü açmak istiyor musunuz? - + BluetoothCihazları - + BluetoothLeCihazlar - + Önemli hata, lütfen bilgi için günlükleri kontrol edin! - + Zaman aşımı süresi doldu - + bilinmeyen sebep - + Hizmet Yöneticisini açamıyor - + hizmeti açamıyor - + Başlangıç modu yapılandırılırken hata oluştu, daha fazla bilgi için lütfen günlükleri kontrol edin. - + Başlangıç modu ayarlanırken hata oluştu, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Microsoft'un WebView2 çalışma zamanı makinenizde bulunamadı. Bu genellikle yükleyici tarafından gerçekleştirilir, ancak manuel olarak yükleyebilirsiniz. Çalışma zamanı yükleyicisini indirmek istiyor musunuz? - + WebView başlatılırken bir şeyler ters gitti! Lütfen günlüklerinizi kontrol edin ve daha fazla yardım için bir GitHub sorunu açın. - + domain - + Lütfen kullanılabilirliğin göz ardı edilmesinin, HASS.Agent çevrimdışı olsa bile Home Assistant'ın belirli bir sensör için her zaman son değeri görüntülemesine neden olacağını unutmayın. - + Kullanılab&ilirliği yoksay - + &Treat URI Action öğeleri, Android Companion App'in yaptığı gibi (açık) - + Bildirim Giriş Metni - + Bildirim Giriş Başlığı - + Evet - + Hayır - + Hiçbiri - + Seçilen radyo cihazını açar/kapatır. Radyo cihazlarının kullanılabilirliği HASS.Agent'ın kurulu olduğu cihaza bağlıdır. - + Lütfen radyo cihazını seçin! - + Radyo cihazı - + Radyo Komutu - + Hata, lütfen daha fazla bilgi için günlükleri kontrol edin. - + Komutlar dönüştürülürken hata oluştu! - + Çoklu değer sensörleri dönüştürülürken hata oluştu! - + Tek değerli sensörler dönüştürülürken hata oluştu! - + Varlık adlarını değiştirme Home Assistant 2023.8 gereksinimlerini karşılamak için - + Sensör adındaki cihaz adı, 2023.8'den itibaren Ev Asistanı sürümlerinde sorunlara neden olabilir. - + Eylemi zorla ne zaman sistem uykudan/hazırda bekletme modundan uyanır - + Sağlanan ses cihazında sağlanan uygulamanın ses düzeyini ve sessiz durumunu belirtilen düzeye ayarlar. Komut/yükün JSON formatında olması gerekir. Tüm olası seçeneklere örnek: { @@ -2908,48 +2908,123 @@ Komut/yükün JSON formatında olması gerekir. Tüm olası seçeneklere örnek: Hiçbir "playbackDevice" sağlanmazsa, HASS.Agent varsayılan olanı kullanacaktır. Hiçbir "volume" sağlanmazsa, HASS.Agent yalnızca sessiz durumunu ayarlayacaktır. -Eğer "mute" sağlanmadıysa, HASS.Agent sağlanan uygulamanın sesini açacaktır. +Eğer "mute" sağlanmadıysa, HASS.Agent sağlanan uygulamanın sesini açacaktır. + +Gelişmiş seçenek: yalnızca uygulamanın belirli bir oturumu için ses seviyesini ayarlamak üzere ek "sessionId" sağlanabilir, oturum kimliği "Sesli Oturumlar" sensöründen alınabilir: +{ + "playbackDevice":"Speakers (THX SpatialAudio)", + "applicationName":"Discord", + "volume":50, + "sessionid":"<LONG SESSION ID FROM SENSOR>" +} - + Lütfen geçerli bir JSON dizesi girin! - + Dahili cihaz sensöründen veri sağlar. Sensörün kullanılabilirliği cihaza bağlıdır; bazı durumlarda sensör bulunmayabilir. - + Dahili Sensör - + DahiliCihazSensörü - + Komut adındaki cihaz adı, 2023.8'den itibaren Home Assistant sürümlerinde sorunlara neden olabilir. - + Şu anda etkin olan sanal masaüstünün kimliğini sağlar. - + Sağlanan Sanal Masaüstünü etkinleştirir. Masaüstü Kimliği "ActiveDesktop" sensöründen alınabilir. - + Makinenizde Sanal Masaüstü yönetimi mevcut değil. Bunun nedeni genellikle sanal masaüstü yönetim kitaplığının Windows sürümünüzü destekleyecek şekilde güncellenmemesidir. - + HASS.Agent yapılandırmasının taşınması orijinal versiyondan - + Tarafından sevgiyle güncellendi ve sürdürüldü - + HASS.Agent'ın orijinal versiyonunu geliştirdikleri için LAB02 Research ve Sam'e daha da büyük bir 'teşekkür ederim' - bu olmasaydı bu olmazdı! - + Uydu servisiyle iletişimde hata! - + Uydu hizmeti başlatılırken hata oluştu! + + + Sistem için varsayılan ses çıkışını ayarlar. +Yük olarak ses cihazı adını gerektirir. + + + Ses Cihazı Adı + + + JSON Komut Yükü + + + Hacim (0 ile 100 arasında) + + + Bir kamera varlığı biçiminde bir ekran görüntüsü sensörü sağlarsınız. +Ekran numarası sistem konfigürasyonuna bağlıdır - 0'dan başlar. + + + Ekran numarası + + + Sistem için varsayılan ses girişini ayarlar (varsayılan iletişim cihazı dahil). +Yük olarak ses cihazı adına ihtiyacınız vardır. + + + Hazırda bekletme modundan uyandıktan sonra ek süreyi göz ardı et + + + Modern tepsi simgesini kullan + + + Halihazırda mikrofonu kullanan işlemlerin sayısını sağlar; ayrıca sensörün özniteliklerinde süreçlerin adını da sağlar. + +Not: Uydu hizmetinde kullanılırsa kullanıcı alanı uygulamalarını algılamaz. + + + NFC + + + Gelişmiş Ayarlar + + + Ge&lişmiş Ayarlar + + + Uyarı, aşağıdaki ayarların (geçersiz kılmaların) eklenmesi sensörü bozabilir, dikkatli kullanın! + + + Tepsi Simgesi Web Görünümünü gösterir/gizler. +"Yapılandırma -> Tepsi Simgesi"nde yapılandırılması gerekir + + + Tetiklemek + + + Buton + + + Basmak + + + Mevcut enlem, boylam ve yüksekliğinizi konum/cihaz izleyici olarak döndürür, doğrudan bir harita üzerinde görüntülemek ve izlemek için. + +Windows'un konum servislerinin etkinleştirildiğinden emin olun! + +Windows sürümünüze bağlı olarak, bu, 'gizlilik ve güvenlik' > 'konum' > yeni kontrol panelinde bulunabilir. \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Resources/modern-tray-icon.ico b/src/HASS.Agent/HASS.Agent/Resources/modern-tray-icon.ico new file mode 100644 index 00000000..4bc49856 Binary files /dev/null and b/src/HASS.Agent/HASS.Agent/Resources/modern-tray-icon.ico differ diff --git a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs index e01731c8..9cc9996a 100644 --- a/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Sensors/SensorsManager.cs @@ -1,4 +1,5 @@ using HASS.Agent.Extensions; +using HASS.Agent.Managers; using HASS.Agent.Models.Internal; using HASS.Agent.Resources.Localization; using HASS.Agent.Settings; @@ -6,7 +7,10 @@ using HASS.Agent.Shared.Extensions; using HASS.Agent.Shared.Models.Config; using HASS.Agent.Shared.Models.HomeAssistant; +using MQTTnet; +using Newtonsoft.Json; using Serilog; +using static HASS.Agent.Shared.Functions.Inputs; namespace HASS.Agent.Sensors { @@ -20,6 +24,8 @@ internal static class SensorsManager private static bool _active = true; private static bool _pause; + private static bool _discoveryPublished = false; + private static DateTime _lastAutoDiscoPublish = DateTime.MinValue; /// @@ -96,32 +102,50 @@ private static async void Process() // optionally flag as the first real run if (!firstRunDone) firstRunDone = true; - // publish availability & sensor autodisco's every 30 sec + // publish availability & autodiscovery every 30 sec if ((DateTime.Now - _lastAutoDiscoPublish).TotalSeconds > 30) { - // let hass know we're still here await Variables.MqttManager.AnnounceAvailabilityAsync(); - // publish the autodisco's - if (SingleValueSensorsPresent()) + if (!_discoveryPublished) { - foreach (var sensor in Variables.SingleValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (SingleValueSensorsPresent()) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await sensor.PublishAutoDiscoveryConfigAsync(); + foreach (var sensor in Variables.SingleValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await sensor.PublishAutoDiscoveryConfigAsync(); + } } - } - if (MultiValueSensorsPresent()) - { - foreach (var sensor in Variables.MultiValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + if (MultiValueSensorsPresent()) { - if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; - await sensor.PublishAutoDiscoveryConfigAsync(); + foreach (var sensor in Variables.MultiValueSensors.TakeWhile(_ => !_pause).TakeWhile(_ => _active)) + { + if (_pause || Variables.MqttManager.GetStatus() != MqttStatus.Connected) continue; + await sensor.PublishAutoDiscoveryConfigAsync(); + } } + + //if (RadioManager.AvailableNFCReader.Any()) + if (true) + { + var tagScannerConfigMesage = new MqttApplicationMessageBuilder() + .WithTopic($"{Variables.AppSettings.MqttDiscoveryPrefix}/tag/{Variables.DeviceConfig.Name}/config") + .WithPayload(JsonConvert.SerializeObject(new + { + topic = $"hass.agent/devices/{Variables.DeviceConfig.Name}/tag_scanned", + value_template = "{{ value_json.UID }}" + })) + .WithRetainFlag(Variables.AppSettings.MqttUseRetainFlag) + .Build(); + + await Variables.MqttManager.PublishAsync(tagScannerConfigMesage); + } + + _discoveryPublished = true; } - // log moment _lastAutoDiscoPublish = DateTime.Now; } @@ -442,6 +466,13 @@ internal static void LoadSensorInfo() Languages.SensorsManager_GeoLocationSensorDescription, 30, false, true, false); + SensorInfoCards.Add(sensorInfoCard.SensorType, sensorInfoCard); + // ================================= + + sensorInfoCard = new SensorInfoCard(SensorType.DeviceTrackerSensor, + Languages.SensorsManager_DeviceTrackerSensorDescription, + 30, false, true, false); + SensorInfoCards.Add(sensorInfoCard.SensorType, sensorInfoCard); // ================================= @@ -661,6 +692,14 @@ internal static void LoadSensorInfo() SensorInfoCards.Add(sensorInfoCard.SensorType, sensorInfoCard); // ================================= + + sensorInfoCard = new SensorInfoCard(SensorType.ScreenshotSensor, + Languages.SensorsManager_ScreenshotSensorDescription, + 10, false, true, false); + + SensorInfoCards.Add(sensorInfoCard.SensorType, sensorInfoCard); + + // ================================= } } } diff --git a/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto b/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto index 169b6263..a21c9157 100644 --- a/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto +++ b/src/HASS.Agent/HASS.Agent/Service/Protos/hassagentsatellite.proto @@ -134,6 +134,10 @@ message RpcConfiguredServerSensor { string instance = 9; string name = 10; string entityName = 11; + string advancedSettings = 12; + bool applyRounding = 13; + int32 roundValue = 14; + bool ignoreAvailability = 15; } message RpcConfiguredServerCommand { diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs index 7639fc74..15305e16 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredCommands.cs @@ -160,6 +160,9 @@ internal static AbstractCommand ConvertConfiguredToAbstract(ConfiguredCommand co case CommandType.WebViewCommand: abstractCommand = new WebViewCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); break; + case CommandType.TrayWebViewCommand: + abstractCommand = new TrayWebViewCommand(command.EntityName, command.Name, command.EntityType, command.Id.ToString()); + break; case CommandType.MonitorSleepCommand: abstractCommand = new MonitorSleepCommand(command.EntityName, command.Name, command.EntityType, command.Id.ToString()); break; @@ -175,6 +178,12 @@ internal static AbstractCommand ConvertConfiguredToAbstract(ConfiguredCommand co case CommandType.SetApplicationVolumeCommand: abstractCommand = new SetApplicationVolumeCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); break; + case CommandType.SetAudioOutputCommand: + abstractCommand = new SetAudioOutputCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); + break; + case CommandType.SetAudioInputCommand: + abstractCommand = new SetAudioInputCommand(command.EntityName, command.Name, command.Command, command.EntityType, command.Id.ToString()); + break; default: Log.Error("[SETTINGS_COMMANDS] [{name}] Unknown configured command type: {type}", command.EntityName, command.Type.ToString()); break; diff --git a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs index 6dce967d..fc0a007c 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/StoredSensors.cs @@ -3,6 +3,7 @@ using HASS.Agent.Extensions; using HASS.Agent.HomeAssistant.Sensors.GeneralSensors.MultiValue; using HASS.Agent.HomeAssistant.Sensors.GeneralSensors.SingleValue; +using HASS.Agent.Managers.DeviceSensors; using HASS.Agent.Resources.Localization; using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Extensions; @@ -20,71 +21,71 @@ namespace HASS.Agent.Settings { - /// - /// Handles loading and storing sensors - /// - internal static class StoredSensors - { - /// - /// Load all stored sensors - /// - /// - internal static async Task LoadAsync() - { - try - { - // set empty lists - Variables.SingleValueSensors = new List(); - Variables.MultiValueSensors = new List(); - - // check for existing file - if (!File.Exists(Variables.SensorsFile)) - { - // none yet - Log.Information("[SETTINGS_SENSORS] Config not found, no entities loaded"); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Stopped); - return true; - } - - // read the content - var sensorsRaw = await File.ReadAllTextAsync(Variables.SensorsFile); - if (string.IsNullOrWhiteSpace(sensorsRaw)) - { - Log.Information("[SETTINGS_SENSORS] Config is empty, no entities loaded"); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Stopped); - return true; - } - - // deserialize - var configuredSensors = JsonConvert.DeserializeObject>(sensorsRaw); - - // null-check - if (configuredSensors == null) - { - Log.Error("[SETTINGS_SENSORS] Error loading entities: returned null object"); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); - return false; - } - - // convert to abstract sensors - await Task.Run(delegate - { - foreach (var sensor in configuredSensors) - { - if (sensor.IsSingleValue()) Variables.SingleValueSensors.Add(ConvertConfiguredToAbstractSingleValue(sensor)); - else Variables.MultiValueSensors.Add(ConvertConfiguredToAbstractMultiValue(sensor)); - } - }); - - // all good - Log.Information("[SETTINGS_SENSORS] Loaded {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Ok); - return true; - } - catch (Exception ex) - { - Log.Fatal(ex, "[SETTINGS_SENSORS] Error loading entities: {err}", ex.Message); - Variables.MainForm?.ShowMessageBox(string.Format(Languages.StoredSensors_Load_MessageBox1, ex.Message), true); + /// + /// Handles loading and storing sensors + /// + internal static class StoredSensors + { + /// + /// Load all stored sensors + /// + /// + internal static async Task LoadAsync() + { + try + { + // set empty lists + Variables.SingleValueSensors = new List(); + Variables.MultiValueSensors = new List(); + + // check for existing file + if (!File.Exists(Variables.SensorsFile)) + { + // none yet + Log.Information("[SETTINGS_SENSORS] Config not found, no entities loaded"); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Stopped); + return true; + } + + // read the content + var sensorsRaw = await File.ReadAllTextAsync(Variables.SensorsFile); + if (string.IsNullOrWhiteSpace(sensorsRaw)) + { + Log.Information("[SETTINGS_SENSORS] Config is empty, no entities loaded"); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Stopped); + return true; + } + + // deserialize + var configuredSensors = JsonConvert.DeserializeObject>(sensorsRaw); + + // null-check + if (configuredSensors == null) + { + Log.Error("[SETTINGS_SENSORS] Error loading entities: returned null object"); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); + return false; + } + + // convert to abstract sensors + await Task.Run(delegate + { + foreach (var sensor in configuredSensors) + { + if (sensor.IsSingleValue()) Variables.SingleValueSensors.Add(ConvertConfiguredToAbstractSingleValue(sensor)); + else Variables.MultiValueSensors.Add(ConvertConfiguredToAbstractMultiValue(sensor)); + } + }); + + // all good + Log.Information("[SETTINGS_SENSORS] Loaded {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Ok); + return true; + } + catch (Exception ex) + { + Log.Fatal(ex, "[SETTINGS_SENSORS] Error loading entities: {err}", ex.Message); + Variables.MainForm?.ShowMessageBox(string.Format(Languages.StoredSensors_Load_MessageBox1, ex.Message), true); Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); return false; @@ -103,147 +104,154 @@ internal static AbstractSingleValueSensor ConvertConfiguredToAbstractSingleValue switch (sensor.Type) { case SensorType.UserNotificationStateSensor: - abstractSensor = new UserNotificationStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new UserNotificationStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.DummySensor: - abstractSensor = new DummySensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new DummySensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.CurrentClockSpeedSensor: - abstractSensor = new CurrentClockSpeedSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CurrentClockSpeedSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.CpuLoadSensor: - abstractSensor = new CpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.MemoryUsageSensor: - abstractSensor = new MemoryUsageSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MemoryUsageSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.ApplyRounding, sensor.Round, sensor.AdvancedSettings); break; case SensorType.ActiveWindowSensor: - abstractSensor = new ActiveWindowSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ActiveWindowSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ActiveDesktopSensor: - abstractSensor = new ActiveDesktopSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ActiveDesktopSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.NamedWindowSensor: - abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString()); + abstractSensor = new NamedWindowSensor(sensor.WindowName, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); + break; + case SensorType.LastActiveSensor: + abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; - case SensorType.LastActiveSensor: - abstractSensor = new LastActiveSensor(sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.LastSystemStateChangeSensor: - abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + case SensorType.LastSystemStateChangeSensor: + abstractSensor = new LastSystemStateChangeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LastBootSensor: - abstractSensor = new LastBootSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LastBootSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WebcamActiveSensor: - abstractSensor = new WebcamActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WebcamActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MicrophoneActiveSensor: - abstractSensor = new MicrophoneActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MicrophoneActiveSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.SessionStateSensor: - abstractSensor = new SessionStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new SessionStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.CurrentVolumeSensor: - abstractSensor = new CurrentVolumeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new CurrentVolumeSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GpuLoadSensor: - abstractSensor = new GpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GpuLoadSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GpuTemperatureSensor: - abstractSensor = new GpuTemperatureSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GpuTemperatureSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WmiQuerySensor: - abstractSensor = new WmiQuerySensor(sensor.Query, sensor.Scope, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WmiQuerySensor(sensor.Query, sensor.Scope, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.PerformanceCounterSensor: - abstractSensor = new PerformanceCounterSensor(sensor.Category, sensor.Counter, sensor.Instance, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new PerformanceCounterSensor(sensor.Category, sensor.Counter, sensor.Instance, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ProcessActiveSensor: - abstractSensor = new ProcessActiveSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ProcessActiveSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.ServiceStateSensor: - abstractSensor = new ServiceStateSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new ServiceStateSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LoggedUsersSensor: - abstractSensor = new LoggedUsersSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LoggedUsersSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.LoggedUserSensor: - abstractSensor = new LoggedUserSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new LoggedUserSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.GeoLocationSensor: - abstractSensor = new GeoLocationSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new GeoLocationSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); + break; + case SensorType.DeviceTrackerSensor: + abstractSensor = new DeviceTrackerSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MonitorPowerStateSensor: - abstractSensor = new MonitorPowerStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MonitorPowerStateSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.PowershellSensor: - abstractSensor = new PowershellSensor(sensor.Query, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new PowershellSensor(sensor.Query, sensor.ApplyRounding, sensor.Round, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WindowStateSensor: - abstractSensor = new WindowStateSensor(sensor.Query, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString()); + abstractSensor = new WindowStateSensor(sensor.Query, sensor.EntityName, sensor.Name, sensor.UpdateInterval, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.MicrophoneProcessSensor: - abstractSensor = new MicrophoneProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new MicrophoneProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.WebcamProcessSensor: - abstractSensor = new WebcamProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new WebcamProcessSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.BluetoothDevicesSensor: - abstractSensor = new BluetoothDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new BluetoothDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.BluetoothLeDevicesSensor: - abstractSensor = new BluetoothLeDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new BluetoothLeDevicesSensor(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; case SensorType.InternalDeviceSensor: - abstractSensor = new InternalDeviceSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + abstractSensor = new HomeAssistant.Sensors.GeneralSensors.SingleValue.InternalDeviceSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); + break; + case SensorType.ScreenshotSensor: + abstractSensor = new ScreenshotSensor(sensor.Query, sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString(), sensor.AdvancedSettings); break; default: Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured single-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); break; } - abstractSensor.IgnoreAvailability = sensor.IgnoreAvailability; + if (abstractSensor != null) + abstractSensor.IgnoreAvailability = sensor.IgnoreAvailability; return abstractSensor; } - /// - /// Convert a multi-value 'ConfiguredSensor' (local storage, UI) to an 'AbstractSensor' (MQTT) - /// - /// - /// - internal static AbstractMultiValueSensor ConvertConfiguredToAbstractMultiValue(ConfiguredSensor sensor) - { - AbstractMultiValueSensor abstractSensor = null; - - switch (sensor.Type) - { - case SensorType.StorageSensors: - abstractSensor = new StorageSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.NetworkSensors: - abstractSensor = new NetworkSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Query, sensor.Id.ToString()); - break; - case SensorType.WindowsUpdatesSensors: - abstractSensor = new WindowsUpdatesSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.BatterySensors: - abstractSensor = new BatterySensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.DisplaySensors: - abstractSensor = new DisplaySensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.AudioSensors: - abstractSensor = new AudioSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - case SensorType.PrintersSensors: - abstractSensor = new PrintersSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); - break; - default: - Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured multi-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); - break; - } + /// + /// Convert a multi-value 'ConfiguredSensor' (local storage, UI) to an 'AbstractSensor' (MQTT) + /// + /// + /// + internal static AbstractMultiValueSensor ConvertConfiguredToAbstractMultiValue(ConfiguredSensor sensor) + { + AbstractMultiValueSensor abstractSensor = null; + + switch (sensor.Type) + { + case SensorType.StorageSensors: + abstractSensor = new StorageSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.NetworkSensors: + abstractSensor = new NetworkSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Query, sensor.Id.ToString()); + break; + case SensorType.WindowsUpdatesSensors: + abstractSensor = new WindowsUpdatesSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.BatterySensors: + abstractSensor = new BatterySensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.DisplaySensors: + abstractSensor = new DisplaySensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.AudioSensors: + abstractSensor = new AudioSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + case SensorType.PrintersSensors: + abstractSensor = new PrintersSensors(sensor.UpdateInterval, sensor.EntityName, sensor.Name, sensor.Id.ToString()); + break; + default: + Log.Error("[SETTINGS_SENSORS] [{name}] Unknown configured multi-value sensor type: {type}", sensor.EntityName, sensor.Type.ToString()); + break; + } abstractSensor.IgnoreAvailability = sensor.IgnoreAvailability; @@ -260,136 +268,144 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract switch (sensor) { case WmiQuerySensor wmiSensor: - { - _ = Enum.TryParse(wmiSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(wmiSensor.Id), - EntityName = wmiSensor.EntityName, - Name = wmiSensor.Name, - Type = type, - UpdateInterval = wmiSensor.UpdateIntervalSeconds, - IgnoreAvailability = wmiSensor.IgnoreAvailability, - Scope = wmiSensor.Scope, - Query = wmiSensor.Query, - ApplyRounding = wmiSensor.ApplyRounding, - Round= wmiSensor.Round - }; - } + _ = Enum.TryParse(wmiSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(wmiSensor.Id), + EntityName = wmiSensor.EntityName, + Name = wmiSensor.Name, + Type = type, + UpdateInterval = wmiSensor.UpdateIntervalSeconds, + IgnoreAvailability = wmiSensor.IgnoreAvailability, + Scope = wmiSensor.Scope, + Query = wmiSensor.Query, + ApplyRounding = wmiSensor.ApplyRounding, + Round = wmiSensor.Round, + AdvancedSettings = wmiSensor.AdvancedSettings + }; + } case NamedWindowSensor namedWindowSensor: - { - _ = Enum.TryParse(namedWindowSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(namedWindowSensor.Id), - EntityName = namedWindowSensor.EntityName, - Name = namedWindowSensor.Name, - Type = type, - UpdateInterval = namedWindowSensor.UpdateIntervalSeconds, - IgnoreAvailability = namedWindowSensor.IgnoreAvailability, - WindowName = namedWindowSensor.WindowName - }; - } + _ = Enum.TryParse(namedWindowSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(namedWindowSensor.Id), + EntityName = namedWindowSensor.EntityName, + Name = namedWindowSensor.Name, + Type = type, + UpdateInterval = namedWindowSensor.UpdateIntervalSeconds, + IgnoreAvailability = namedWindowSensor.IgnoreAvailability, + WindowName = namedWindowSensor.WindowName, + AdvancedSettings = namedWindowSensor.AdvancedSettings + }; + } case PerformanceCounterSensor performanceCounterSensor: - { - _ = Enum.TryParse(performanceCounterSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(performanceCounterSensor.Id), - EntityName = performanceCounterSensor.EntityName, - Name = performanceCounterSensor.Name, - Type = type, - UpdateInterval = performanceCounterSensor.UpdateIntervalSeconds, - IgnoreAvailability = performanceCounterSensor.IgnoreAvailability, - Category = performanceCounterSensor.CategoryName, - Counter = performanceCounterSensor.CounterName, - Instance = performanceCounterSensor.InstanceName, - ApplyRounding = performanceCounterSensor.ApplyRounding, - Round = performanceCounterSensor.Round - }; - } + _ = Enum.TryParse(performanceCounterSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(performanceCounterSensor.Id), + EntityName = performanceCounterSensor.EntityName, + Name = performanceCounterSensor.Name, + Type = type, + UpdateInterval = performanceCounterSensor.UpdateIntervalSeconds, + IgnoreAvailability = performanceCounterSensor.IgnoreAvailability, + Category = performanceCounterSensor.CategoryName, + Counter = performanceCounterSensor.CounterName, + Instance = performanceCounterSensor.InstanceName, + ApplyRounding = performanceCounterSensor.ApplyRounding, + Round = performanceCounterSensor.Round, + AdvancedSettings = performanceCounterSensor.AdvancedSettings + }; + } case ProcessActiveSensor processActiveSensor: - { - _ = Enum.TryParse(processActiveSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(processActiveSensor.Id), - EntityName = processActiveSensor.EntityName, - Name = processActiveSensor.Name, - Type = type, - UpdateInterval = processActiveSensor.UpdateIntervalSeconds, - IgnoreAvailability = processActiveSensor.IgnoreAvailability, - Query = processActiveSensor.ProcessName - }; - } + _ = Enum.TryParse(processActiveSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(processActiveSensor.Id), + EntityName = processActiveSensor.EntityName, + Name = processActiveSensor.Name, + Type = type, + UpdateInterval = processActiveSensor.UpdateIntervalSeconds, + IgnoreAvailability = processActiveSensor.IgnoreAvailability, + Query = processActiveSensor.ProcessName, + AdvancedSettings = processActiveSensor.AdvancedSettings + }; + } case ServiceStateSensor serviceStateSensor: - { - _ = Enum.TryParse(serviceStateSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(serviceStateSensor.Id), - EntityName = serviceStateSensor.EntityName, - Name = serviceStateSensor.Name, - Type = type, - UpdateInterval = serviceStateSensor.UpdateIntervalSeconds, - IgnoreAvailability = serviceStateSensor.IgnoreAvailability, - Query = serviceStateSensor.ServiceName - }; - } + _ = Enum.TryParse(serviceStateSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(serviceStateSensor.Id), + EntityName = serviceStateSensor.EntityName, + Name = serviceStateSensor.Name, + Type = type, + UpdateInterval = serviceStateSensor.UpdateIntervalSeconds, + IgnoreAvailability = serviceStateSensor.IgnoreAvailability, + Query = serviceStateSensor.ServiceName, + AdvancedSettings = serviceStateSensor.AdvancedSettings + }; + } case PowershellSensor powershellSensor: - { - _ = Enum.TryParse(powershellSensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(powershellSensor.Id), - EntityName = powershellSensor.EntityName, - Name = powershellSensor.Name, - Type = type, - UpdateInterval = powershellSensor.UpdateIntervalSeconds, - IgnoreAvailability = powershellSensor.IgnoreAvailability, - Query = powershellSensor.Command, - ApplyRounding = powershellSensor.ApplyRounding, - Round = powershellSensor.Round - }; - } + _ = Enum.TryParse(powershellSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(powershellSensor.Id), + EntityName = powershellSensor.EntityName, + Name = powershellSensor.Name, + Type = type, + UpdateInterval = powershellSensor.UpdateIntervalSeconds, + IgnoreAvailability = powershellSensor.IgnoreAvailability, + Query = powershellSensor.Command, + ApplyRounding = powershellSensor.ApplyRounding, + Round = powershellSensor.Round, + AdvancedSettings = powershellSensor.AdvancedSettings + }; + } - case LastActiveSensor lastActiveSensor: - { - _ = Enum.TryParse(lastActiveSensor.GetType().Name, out var type); - return new ConfiguredSensor - { - Id = Guid.Parse(lastActiveSensor.Id), - EntityName = lastActiveSensor.EntityName, - Name = lastActiveSensor.Name, - Type = type, - UpdateInterval = lastActiveSensor.UpdateIntervalSeconds, - IgnoreAvailability = lastActiveSensor.IgnoreAvailability, - ApplyRounding = lastActiveSensor.ApplyRounding, - Round = lastActiveSensor.Round - }; - } - - case WindowStateSensor windowStateSensor: - { - _ = Enum.TryParse(windowStateSensor.GetType().Name, out var type); - return new ConfiguredSensor + case LastActiveSensor lastActiveSensor: { - Id = Guid.Parse(windowStateSensor.Id), - EntityName = windowStateSensor.EntityName, - Name = windowStateSensor.Name, - Type = type, - UpdateInterval = windowStateSensor.UpdateIntervalSeconds, - IgnoreAvailability = windowStateSensor.IgnoreAvailability, - Query = windowStateSensor.ProcessName - }; - } + _ = Enum.TryParse(lastActiveSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(lastActiveSensor.Id), + EntityName = lastActiveSensor.EntityName, + Name = lastActiveSensor.Name, + Type = type, + UpdateInterval = lastActiveSensor.UpdateIntervalSeconds, + IgnoreAvailability = lastActiveSensor.IgnoreAvailability, + ApplyRounding = lastActiveSensor.ApplyRounding, + Round = lastActiveSensor.Round, + AdvancedSettings = lastActiveSensor.AdvancedSettings + }; + } + + case WindowStateSensor windowStateSensor: + { + _ = Enum.TryParse(windowStateSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(windowStateSensor.Id), + EntityName = windowStateSensor.EntityName, + Name = windowStateSensor.Name, + Type = type, + UpdateInterval = windowStateSensor.UpdateIntervalSeconds, + IgnoreAvailability = windowStateSensor.IgnoreAvailability, + Query = windowStateSensor.ProcessName, + AdvancedSettings = windowStateSensor.AdvancedSettings + }; + } - case InternalDeviceSensor internalDeviceSensor: + case HomeAssistant.Sensors.GeneralSensors.SingleValue.InternalDeviceSensor internalDeviceSensor: { _ = Enum.TryParse(internalDeviceSensor.GetType().Name, out var type); return new ConfiguredSensor @@ -399,24 +415,42 @@ internal static ConfiguredSensor ConvertAbstractSingleValueToConfigured(Abstract Name = internalDeviceSensor.Name, Type = type, UpdateInterval = internalDeviceSensor.UpdateIntervalSeconds, - IgnoreAvailability = internalDeviceSensor.IgnoreAvailability, - Query = internalDeviceSensor.SensorType.ToString() + IgnoreAvailability = internalDeviceSensor.IgnoreAvailability, + Query = internalDeviceSensor.SensorType.ToString(), + AdvancedSettings = internalDeviceSensor.AdvancedSettings + }; + } + + case ScreenshotSensor screenshotSensor: + { + _ = Enum.TryParse(screenshotSensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(screenshotSensor.Id), + EntityName = screenshotSensor.EntityName, + Name = screenshotSensor.Name, + Type = type, + UpdateInterval = screenshotSensor.UpdateIntervalSeconds, + IgnoreAvailability = screenshotSensor.IgnoreAvailability, + Query = screenshotSensor.ScreenIndex.ToString(), + AdvancedSettings = screenshotSensor.AdvancedSettings }; } default: - { - _ = Enum.TryParse(sensor.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.Name, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(sensor.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.Name, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability, + AdvancedSettings = sensor.AdvancedSettings + }; + } } } @@ -430,147 +464,147 @@ internal static ConfiguredSensor ConvertAbstractMultiValueToConfigured(AbstractM switch (sensor) { case StorageSensors storageSensors: - { - _ = Enum.TryParse(storageSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(storageSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case NetworkSensors networkSensors: - { - _ = Enum.TryParse(networkSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Query = networkSensors.NetworkCard, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(networkSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Query = networkSensors.NetworkCard, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case WindowsUpdatesSensors windowsUpdatesSensors: - { - _ = Enum.TryParse(windowsUpdatesSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(windowsUpdatesSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case BatterySensors batterySensors: - { - _ = Enum.TryParse(batterySensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(batterySensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case DisplaySensors displaySensors: - { - _ = Enum.TryParse(displaySensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(displaySensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case AudioSensors audioSensors: - { - _ = Enum.TryParse(audioSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; - } + _ = Enum.TryParse(audioSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } case PrintersSensors printersSensors: - { - _ = Enum.TryParse(printersSensors.GetType().Name, out var type); - return new ConfiguredSensor { - Id = Guid.Parse(sensor.Id), - EntityName = sensor.EntityName, - Name = sensor.EntityName, - Type = type, - UpdateInterval = sensor.UpdateIntervalSeconds, - IgnoreAvailability = sensor.IgnoreAvailability - }; + _ = Enum.TryParse(printersSensors.GetType().Name, out var type); + return new ConfiguredSensor + { + Id = Guid.Parse(sensor.Id), + EntityName = sensor.EntityName, + Name = sensor.EntityName, + Type = type, + UpdateInterval = sensor.UpdateIntervalSeconds, + IgnoreAvailability = sensor.IgnoreAvailability + }; + } + } + + return null; + } + + /// + /// Store all current sensors + /// + /// + internal static bool Store() + { + try + { + // check config dir + if (!Directory.Exists(Variables.ConfigPath)) + { + // create + Directory.CreateDirectory(Variables.ConfigPath); } + + // convert single-value sensors + var configuredSensors = Variables.SingleValueSensors.Select(ConvertAbstractSingleValueToConfigured).Where(configuredSensor => configuredSensor != null).ToList(); + + // convert multi-value sensors + var configuredMultiValueSensors = Variables.MultiValueSensors.Select(ConvertAbstractMultiValueToConfigured).Where(configuredSensor => configuredSensor != null).ToList(); + configuredSensors = configuredSensors.Concat(configuredMultiValueSensors).ToList(); + + // serialize to file + var sensors = JsonConvert.SerializeObject(configuredSensors, Formatting.Indented); + File.WriteAllText(Variables.SensorsFile, sensors); + + // done + Log.Information("[SETTINGS_SENSORS] Stored {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Ok); + return true; } + catch (Exception ex) + { + Log.Fatal(ex, "[SETTINGS_SENSORS] Error storing entities: {err}", ex.Message); + Variables.MainForm?.ShowMessageBox(string.Format(Languages.StoredSensors_Store_MessageBox1, ex.Message), true); - return null; - } - - /// - /// Store all current sensors - /// - /// - internal static bool Store() - { - try - { - // check config dir - if (!Directory.Exists(Variables.ConfigPath)) - { - // create - Directory.CreateDirectory(Variables.ConfigPath); - } - - // convert single-value sensors - var configuredSensors = Variables.SingleValueSensors.Select(ConvertAbstractSingleValueToConfigured).Where(configuredSensor => configuredSensor != null).ToList(); - - // convert multi-value sensors - var configuredMultiValueSensors = Variables.MultiValueSensors.Select(ConvertAbstractMultiValueToConfigured).Where(configuredSensor => configuredSensor != null).ToList(); - configuredSensors = configuredSensors.Concat(configuredMultiValueSensors).ToList(); - - // serialize to file - var sensors = JsonConvert.SerializeObject(configuredSensors, Formatting.Indented); - File.WriteAllText(Variables.SensorsFile, sensors); - - // done - Log.Information("[SETTINGS_SENSORS] Stored {count} entities", (Variables.SingleValueSensors.Count + Variables.MultiValueSensors.Count)); - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Ok); - return true; - } - catch (Exception ex) - { - Log.Fatal(ex, "[SETTINGS_SENSORS] Error storing entities: {err}", ex.Message); - Variables.MainForm?.ShowMessageBox(string.Format(Languages.StoredSensors_Store_MessageBox1, ex.Message), true); - - Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); - return false; - } - } - } + Variables.MainForm?.SetSensorsStatus(ComponentStatus.Failed); + return false; + } + } + } } diff --git a/src/HASS.Agent/HASS.Agent/Variables.cs b/src/HASS.Agent/HASS.Agent/Variables.cs index 5fe6cca1..c8f67547 100644 --- a/src/HASS.Agent/HASS.Agent/Variables.cs +++ b/src/HASS.Agent/HASS.Agent/Variables.cs @@ -5,7 +5,6 @@ using System.Net.Http; using System.Reflection; using Windows.Media.Playback; -using CoreAudio; using Grapevine; using HASS.Agent.Forms; using HASS.Agent.Functions; @@ -123,7 +122,6 @@ public static class Variables /// /// Media /// - internal static MMDeviceEnumerator AudioDeviceEnumerator { get; set; } internal static MediaPlayer MediaPlayer { get; set; } ///