iobroker.assistant 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"esphome.js","sourceRoot":"","sources":["../../../src/lib/voice/esphome.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqHA,sCAGC;AAxHD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8CAAgC;AAChC,iDAawB;AACxB,uDAAkH;AAClH,+BAAmE;AACnE,+BAAiC;AAMjC,8DAA8D;AAC9D,MAAM,KAAK,GAAG;IACV,KAAK,EAAE,CAAC;IACR,SAAS,EAAE,CAAC;IACZ,OAAO,EAAE,CAAC;IACV,SAAS,EAAE,CAAC;IACZ,OAAO,EAAE,CAAC;IACV,YAAY,EAAE,CAAC;IACf,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,CAAC;IACZ,OAAO,EAAE,CAAC;IACV,aAAa,EAAE,EAAE;IACjB,aAAa,EAAE,EAAE;IACjB,WAAW,EAAE,EAAE;CACT,CAAC;AAEX,uGAAuG;AACvG,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAC9B,qEAAqE;AACrE,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,6CAA6C;AAC7C,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,mEAAmE;AACnE,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,mGAAmG;AACnG,MAAM,YAAY,GAAG,MAAM,CAAC;AAC5B,sFAAsF;AACtF,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC,yGAAyG;AACzG,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC;;;GAGG;AACU,QAAA,oBAAoB,GAAG,GAAG,CAAC;AACxC;;;;GAIG;AACH,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,kGAAkG;AAClG,MAAM,sBAAsB,GAAG,IAAI,CAAC;AACpC,6FAA6F;AAC7F,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B;;;GAGG;AACH,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,4GAA4G;AAC5G,SAAgB,aAAa,CAAC,MAAqB;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC;AAmGD,+CAA+C;AAClC,QAAA,WAAW,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAW,CAAC;AAmB1F;;;GAGG;AACH,MAAa,iBAAiB;IAGG;IAFZ,WAAW,GAAwB,EAAE,CAAC;IAEvD,YAA6B,IAA8B;QAA9B,SAAI,GAAJ,IAAI,CAA0B;IAAG,CAAC;IAE/D,KAAK,CAAC,KAAK;QACP,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;gBACrB,SAAS;YACb,CAAC;YACD,MAAM,UAAU,GAAG,IAAI,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5D,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAClC,UAAU,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,IAAI;QACN,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,0DAA0D;IAC1D,OAAO;QACH,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACzE,CAAC;IAED,4FAA4F;IAC5F,SAAS,CAAC,MAAc;QACpB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,EAAE,cAAc,IAAI,IAAI,CAAC;IACnG,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,MAAc,EAAE,GAAa;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC;QAC/E,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,gBAAgB;QACZ,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACtD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB,EAAE,CAAC,CAAC;QAC/C,OAAO,MAAM,CAAC,MAAM,CAAC;IACzB,CAAC;IAED,gGAAgG;IAChG,QAAQ,CAAC,MAAc;QACnB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IAClG,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,MAAc,EAAE,QAAgB,EAAE,KAAc;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC;QAC/E,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9D,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,MAAqB,EAAE,KAAiB;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC;QAC/F,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9C,OAAO,OAAO,CAAC,MAAM,CAAC;IAC1B,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,CAAC,MAAqB,EAAE,GAAW,EAAE,UAAkB,EAAE,iBAAiB,GAAG,KAAK;QAC5F,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC;QAC/F,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO,CAAC,CAAC;QACb,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CACb,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CACZ,CAAC;aACI,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,iBAAiB,CAAC;aAC5C,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,UAAU,YAAa,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CACrG,CACJ,CAAC;QACF,OAAO,OAAO,CAAC,MAAM,CAAC;IAC1B,CAAC;CACJ;AAvGD,8CAuGC;AAaD,0EAA0E;AAC1E,MAAM,iBAAiB;IAiCE;IAhCrB,oGAAoG;IACpG,UAAU,CAAS;IACnB,MAAM,GAAG,KAAK,CAAC;IACf,0FAA0F;IAC1F,cAAc,GAA0B,IAAI,CAAC;IAC7C;;;;OAIG;IACK,YAAY,GAAwB,EAAE,CAAC;IAC/C,oFAAoF;IAC3E,QAAQ,GAAG,IAAI,gCAAc,EAAE,CAAC;IAExB,IAAI,CAAS;IACb,IAAI,CAAS;IACb,IAAI,CAAS;IAEtB,MAAM,GAAsB,IAAI,CAAC;IACjC,EAAE,GAAU,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,SAAS,GAA0B,IAAI,CAAC;IACxC,cAAc,GAA0B,IAAI,CAAC;IAC7C,WAAW,GAAG,CAAC,CAAC;IAChB,QAAQ,GAAG,KAAK,CAAC;IACjB,OAAO,GAAmB,IAAI,CAAC;IAC/B,aAAa,GAA0B,IAAI,CAAC;IACnC,QAAQ,CAAS;IAClC,qEAAqE;IACpD,WAAW,CAAS;IAErC,YACI,MAAqB,EACJ,IAA8B;QAA9B,SAAI,GAAJ,IAAI,CAA0B;QAE/C,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,YAAY,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;IAChC,CAAC;IAED,mGAAmG;IAEnG,KAAK;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,IAAI;QACN,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,MAAM,EAAE,CAAC;YACT,IAAI,CAAC;gBACD,MAAM,CAAC,KAAK,CAAC,IAAA,qBAAM,EAAC,IAAI,iBAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC;YACrD,CAAC;YAAC,MAAM,CAAC;gBACL,qDAAqD;YACzD,CAAC;YACD,MAAM,CAAC,OAAO,EAAE,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;IAEO,OAAO;QACX,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,OAAO;QACX,CAAC;QACD,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;QACrB,2FAA2F;QAC3F,mEAAmE;QACnE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QAEvB,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE;YAC3E,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,YAAY,CAAC,CAAC;YACnE,IAAI,CAAC,SAAS,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAExB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE;YACnB,mFAAmF;YACnF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACpB,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,IAAI,CAAC,cAAc,EAAE,CAAC;YAC1B,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,cAAc;QAClB,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,8BAA8B,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC7B,CAAC;IAEO,iBAAiB;QACrB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACvC,OAAO;QACX,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC3B,IAAI,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC,EAAE,YAAY,CAAC,CAAC;QACjB,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAEO,WAAW;QACf,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QAC1B,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC/B,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACjC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC9B,CAAC;IACL,CAAC;IAEO,UAAU;QACd,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAChE,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,KAAqB;QAChC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC;IAED,mGAAmG;IAE3F,IAAI,CAAC,OAAkB;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YAC9B,OAAO;QACX,CAAC;QACD,IAAI,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,IAAA,qBAAM,EAAC,OAAO,CAAC,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,UAAU,mBAAoB,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7F,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,IAAY;QACvB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACjE,IAAI,MAAM,CAAC;QACX,IAAI,CAAC;YACD,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAA,qBAAM,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,UAAU,KAAM,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3E,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,UAAU,kBAAkB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC9E,SAAS;YACb,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBAChC,SAAS,CAAC,sDAAsD;YACpE,CAAC;YACD,IAAI,CAAC;gBACD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAC9C,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,WAAW,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,IAAI,qBAAsB,CAAW,CAAC,OAAO,EAAE,CACvF,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAEO,SAAS;QACb,MAAM,KAAK,GAAG,IAAI,iBAAE,CAAC,YAAY,EAAE,CAAC;QACpC,KAAK,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;QAC1C,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC5B,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAEjB,MAAM,IAAI,GAAG,IAAI,iBAAE,CAAC,qBAAqB,EAAE,CAAC;QAC5C,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhB,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACtC,+FAA+F;QAC/F,uEAAuE;QACvE,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAE,CAAC,mBAAmB,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAE,CAAC,sBAAsB,EAAE,CAAC,CAAC;QAE3C,MAAM,SAAS,GAAG,IAAI,iBAAE,CAAC,8BAA8B,EAAE,CAAC;QAC1D,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC7B,SAAS,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAErB,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAE7B,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;YAC9B,IAAI,EAAE,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,wCAAwC,CAAC,CAAC;gBACvF,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBACvB,OAAO;YACX,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QACpC,CAAC,EAAE,gBAAgB,CAAC,CAAC;QACrB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAEO,SAAS,CAAC,IAAY,EAAE,OAAgB;QAC5C,QAAQ,IAAI,EAAE,CAAC;YACX,KAAK,eAAe,CAAC,CAAC,CAAC;gBACnB,MAAM,KAAK,GAAG,OAAwB,CAAC;gBACvC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CACf,WAAW,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,aAAa,EAAE,SAAS,KAAK,CAAC,kBAAkB,EAAE,IAAI,KAAK,CAAC,kBAAkB,EAAE,GAAG,CACrH,CAAC;gBACF,OAAO;YACX,CAAC;YAED,KAAK,wBAAwB;gBACzB,IAAK,OAAkC,CAAC,kBAAkB,EAAE,EAAE,CAAC;oBAC3D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CACf,WAAW,IAAI,CAAC,IAAI,wEAAwE,CAC/F,CAAC;oBACF,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBAC3B,CAAC;gBACD,OAAO;YAEX,KAAK,oBAAoB,CAAC,CAAC,CAAC;gBACxB,MAAM,IAAI,GAAG,OAA6B,CAAC;gBAC3C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC;gBAC9C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,6BAA6B,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,QAAQ,EAAE,OAAO,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9H,CAAC;gBACF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACpB,OAAO;YACX,CAAC;YAED,KAAK,qCAAqC,CAAC,CAAC,CAAC;gBACzC,MAAM,MAAM,GAAG,OAA8C,CAAC;gBAC9D,sFAAsF;gBACtF,wCAAwC;gBACxC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;gBAC/B,IAAI,CAAC,cAAc,GAAG;oBAClB,MAAM,EAAE,MAAM,CAAC,sBAAsB,EAAE;oBACvC,SAAS,EAAE,MAAM;yBACZ,yBAAyB,EAAE;yBAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;wBACP,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE;wBACb,MAAM,EAAE,CAAC,CAAC,WAAW,EAAE;wBACvB,SAAS,EAAE,CAAC,CAAC,uBAAuB,EAAE;qBACzC,CAAC,CAAC;yBACF,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBACrD,GAAG,EAAE,MAAM,CAAC,qBAAqB,EAAE;iBACtC,CAAC;gBACF,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC;gBACxE,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC/D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,WAAW,IAAI,CAAC,UAAU,gBAAgB,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACvH,CAAC;gBACF,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;gBAC5C,MAAM,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBACzF,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;oBACjB,iFAAiF;oBACjF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,WAAW,IAAI,CAAC,UAAU,qCAAqC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;wBACjF,mDAAmD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,iBAAiB;wBACtF,kCAAkC,CACzC,CAAC;gBACN,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;gBACzE,OAAO;YACX,CAAC;YAED,KAAK,uBAAuB;gBACxB,IAAI,CAAC,iBAAiB,CAAC,OAAmC,CAAC,CAAC;gBAC5D,OAAO;YAEX,KAAK,qBAAqB;gBACtB,IAAI,CAAC,OAAO,CAAC,OAA8B,CAAC,CAAC;gBAC7C,OAAO;YAEX,KAAK,gCAAgC;gBACjC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;oBACrB,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;oBACjC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC9B,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CACf,WAAW,IAAI,CAAC,UAAU,gCAAiC,OAA0C,CAAC,UAAU,EAAE,GAAG,CACxH,CAAC;gBACF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACpB,OAAO;YAEX,KAAK,aAAa;gBACd,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAE,CAAC,YAAY,EAAE,CAAC,CAAC;gBACjC,OAAO;YAEX,KAAK,cAAc;gBACf,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;gBACrB,OAAO;YAEX,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACpB,oFAAoF;gBACpF,MAAM,IAAI,GAAG,IAAI,iBAAE,CAAC,eAAe,EAAE,CAAC;gBACtC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;gBACpD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAChB,OAAO;YACX,CAAC;YAED,KAAK,mBAAmB;gBACpB,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAE,CAAC,kBAAkB,EAAE,CAAC,CAAC;gBACvC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBACvB,OAAO;YAEX,KAAK,0BAA0B,CAAC,CAAC,CAAC;gBAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAClC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBACd,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CACf,WAAW,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,eAAe,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClG,CAAC;gBACN,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBACzD,OAAO;YACX,CAAC;YAED;gBACI,IAAI,IAAA,qCAAmB,EAAC,IAAI,CAAC,EAAE,CAAC;oBAC5B,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBACrD,CAAC;qBAAM,IAAI,IAAA,sCAAoB,EAAC,IAAI,CAAC,EAAE,CAAC;oBACpC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;oBACvD,IAAI,MAAM,EAAE,CAAC;wBACT,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;oBAClE,CAAC;gBACL,CAAC;gBACD,OAAO;QACf,CAAC;IACL,CAAC;IAED,mFAAmF;IACnF,SAAS,CAAC,QAAgB,EAAE,KAAc;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ;iBACtB,IAAI,EAAE;iBACN,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;iBACpB,IAAI,CAAC,IAAI,CAAC,CAAC;YAChB,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACjF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,MAAM;gBACF,CAAC,CAAC,WAAW,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,oBAAoB,QAAQ,YAAY,OAAO,EAAE;gBACvG,CAAC,CAAC,WAAW,IAAI,CAAC,UAAU,gBAAgB,QAAQ,YAAY,KAAK,GAAG,CAC/E,CAAC;YACF,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,UAAU,KAAK,QAAQ,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3F,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,gFAAgF;IAChF,cAAc,CAAC,KAAiB;QAC5B,MAAM,OAAO,GAAG,IAAI,iBAAE,CAAC,gCAAgC,EAAE,CAAC;QAC1D,OAAO,CAAC,YAAY,CAAC,mBAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7B,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrE,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACnE,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,mGAAmG;IAE3F,iBAAiB,CAAC,OAAiC;QACvD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;YACtB,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,OAAO;QACX,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,UAAU,4CAA4C,CAAC,CAAC;YAC5F,OAAO;QACX,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,UAAU,WAAW,MAAM,IAAI,aAAa,GAAG,CAAC,CAAC;QAErF,MAAM,QAAQ,GAAG,IAAI,iBAAE,CAAC,sBAAsB,EAAE,CAAC;QACjD,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,wCAAwC;QAC7D,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEpB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,6BAA6B,mBAAmB,KAAK,CAAC,CAAC;YACpG,KAAK,IAAI,CAAC,aAAa,EAAE,CAAC;QAC9B,CAAC,EAAE,mBAAmB,CAAC,CAAC;QACxB,KAAK,CAAC,KAAK,EAAE,CAAC;QAEd,IAAI,CAAC,OAAO,GAAG;YACX,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,IAAI,oBAAc,CAAC;gBACzB,UAAU,EAAE,eAAe;gBAC3B,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,SAAS;gBAC3C,mFAAmF;gBACnF,oFAAoF;gBACpF,MAAM,EAAE,aAAa;gBACrB,QAAQ,EAAE,IAAI;gBACd,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,sBAAsB;gBAC9D,aAAa,EAAE,IAAI,CAAC,WAAW,IAAI,4BAAoB;gBACvD,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,4BAAoB,CAAC,GAAG,aAAa,CAAC;aACtF,CAAC;YACF,cAAc,EAAE,OAAO,CAAC,iBAAiB,EAAE;YAC3C,QAAQ,EAAE,MAAM,IAAI,EAAE;YACtB,KAAK;YACL,MAAM,EAAE,KAAK;SAChB,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC5B,IAAI,MAAM,EAAE,CAAC;YACT,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACpC,CAAC;IAEO,OAAO,CAAC,KAA0B;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YAC7B,OAAO;QACX,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACf,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzC,IAAI,GAAG,EAAE,CAAC;gBACN,KAAK,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;gBAC7B,OAAO;YACX,CAAC;QACL,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACjB,KAAK,IAAI,CAAC,aAAa,EAAE,CAAC;QAC9B,CAAC;IACL,CAAC;IAED,4EAA4E;IAC5E,oGAAoG;IAC5F,OAAO,CAAC,OAAgB,EAAE,GAAU,EAAE,MAAqB;QAC/D,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YACxC,OAAO;QACX,CAAC;QACD,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;QAC3B,IAAI,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;gBAClB,MAAM,EAAE,IAAI,CAAC,UAAU;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,MAAM;gBACN,GAAG;gBACH,UAAU,EAAE,eAAe;gBAC3B,GAAG,EAAE;oBACD,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;oBACxB,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC;oBACxC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC;oBACpC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC;oBACpC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;iBACnC;aACJ,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,UAAU,oCAAqC,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9G,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,GAAe;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YAC7B,OAAO;QACX,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAE5B,wDAAwD;QACxD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAE9B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,eAAe,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CACf,WAAW,IAAI,CAAC,UAAU,cAAc,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;YAC3D,aAAa,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI;YAC1G,uBAAuB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CACjH,CAAC;QAEF,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;YAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YACrD,IAAI,GAAG,CAAC,MAAM,IAAI,OAAO,IAAI,IAAI,GAAG,iBAAiB,EAAE,CAAC;gBACpD,8FAA8F;gBAC9F,yEAAyE;gBACzE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,WAAW,IAAI,CAAC,UAAU,oDAAoD,OAAO,WAAW;oBAC5F,8BAA8B,IAAI,yDAAyD;oBAC3F,+DAA+D,IAAI,CAAC,GAAG,CACnE,EAAE,EACF,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CACxC,GAAG,CACX,CAAC;YACN,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CACf,WAAW,IAAI,CAAC,UAAU,wBACtB,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,EACzD,2BAA2B,CAC9B,CAAC;YACN,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YAC1E,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACpB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC1B,IAAI,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACtG,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC;YACpC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACR,qGAAqG;gBACrG,sFAAsF;gBACtF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,YAAY,IAAI,CAAC,UAAU,wDAAwD,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CACrH,CAAC;gBACF,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;gBACxC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACpB,OAAO;YACX,CAAC;YAED,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC/B,MAAM,MAAM,GAAG,CACX,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;gBACzB,MAAM,EAAE,IAAI,CAAC,UAAU;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,KAAK;gBACL,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;gBACnC,GAAG,EAAE;oBACD,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACvC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;oBACnD,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;oBACnD,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;iBAClD;aACJ,CAAC,CACL,CAAC,IAAI,EAAE,CAAC;YACT,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;YAC3F,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YAEpF,IAAI,CAAC,MAAM,EAAE,CAAC;gBACV,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACpB,OAAO;YACX,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5B,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC/F,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE;gBAClC,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ;gBACzB,OAAO,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,IAAI;aACnD,CAAC,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YAE7C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACxB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YACnC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACjF,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,MAAM,MAAM,GAAI,CAAW,CAAC,OAAO,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,UAAU,6BAA6B,MAAM,EAAE,CAAC,CAAC;YACrF,mEAAmE;YACnE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,YAAY,CAAC,GAAa;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAClE,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACvD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,WAAW,IAAI,CAAC,UAAU,0BAA0B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc;gBAChF,cAAc,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAiB,EAAE,CAC5D,CAAC;QACN,CAAC;QACD,IAAI,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,IAAI,GAAG,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACd,WAAW,IAAI,CAAC,UAAU,mBAAmB,GAAG,iCAAiC,KAAK;iBACjF,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;iBACb,IAAI,CAAC,IAAI,CAAC,EAAE,CACpB,CAAC;YACF,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,iBAAE,CAAC,8BAA8B,EAAE,CAAC;QACxD,OAAO,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,uBAAuB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC;QACpG,4FAA4F;QAC5F,0FAA0F;QAC1F,qCAAqC;QACrC,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IAED;;;;;;OAMG;IACH,qBAAqB,CAAC,QAAyB,IAAI;QAC/C,MAAM,OAAO,GAAG,IAAI,iBAAE,CAAC,kCAAkC,EAAE,CAAC;QAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,IAAI,EAAE,CAAC;QACrD,OAAO,CAAC,wBAAwB,CAC5B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YACf,MAAM,CAAC,GAAG,IAAI,iBAAE,CAAC,8BAA8B,EAAE,CAAC;YAClD,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC,CAAC,uBAAuB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC3C,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YACxB,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,OAAO,CAAC,CAAC;QACb,CAAC,CAAC,CACL,CAAC;QACF,oEAAoE;QACpE,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAChC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,2EAA2E;IACnE,YAAY;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO;QACX,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;QACtB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,mGAAmG;IAEnG;;;OAGG;IACH,QAAQ,CAAC,GAAW,EAAE,UAAkB,EAAE,iBAAiB,GAAG,KAAK;QAC/D,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YAC9B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,iBAAE,CAAC,6BAA6B,EAAE,CAAC;QACvD,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;QAClD,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpB,OAAO,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;QAClC,8FAA8F;QAC9F,qEAAqE;QACrE,OAAO,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACK,OAAO,CAAC,GAAW,EAAE,UAAkB;QAC3C,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAA,cAAQ,EAAC,GAAG,EAAE,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1F,CAAC;IAED,qFAAqF;IAC7E,QAAQ,CAAC,IAAY;QACzB,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACxE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,WAAW,CAAC;QACjE,gDAAgD;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1D,OAAO,UAAU,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC;IAChE,CAAC;IAED,gFAAgF;IACxE,kBAAkB;QACtB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;YACjC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC,EAAE,mBAAmB,CAAC,CAAC;QACxB,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;IAEO,KAAK,CAAC,IAAY,EAAE,IAA6B;QACrD,MAAM,OAAO,GAAG,IAAI,iBAAE,CAAC,2BAA2B,EAAE,CAAC;QACrD,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,KAAK,GAA8B,EAAE,CAAC;QAC5C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,GAAG,IAAI,iBAAE,CAAC,uBAAuB,EAAE,CAAC;YAC9C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACnB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QACD,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;CACJ","sourcesContent":["/**\n * ESPHome voice satellites — the fourth transport, next to the UDP server (`voiceServer.ts`) and the\n * Wyoming endpoint (`wyoming.ts`).\n *\n * Unlike those two, this one is a **client**: an ESPHome voice satellite is a *server* that waits on\n * TCP 6053 for the home-automation controller to connect and drive it. We therefore dial out to each\n * configured device and play the role Home Assistant normally plays. That covers the ThirdReality\n * Voice & Music Assistant (Dev Edition), the Home Assistant Voice PE and any box running\n * OHF-Voice/linux-voice-assistant.\n *\n * Flow for one utterance (verified against the firmware's `src/satellite/Satellite.cpp`):\n *\n * device → VoiceAssistantRequest{start, wake_word_phrase} wake word fired on the device\n * we → VoiceAssistantResponse{port:0} 0 = audio over the API, no UDP\n * we → RUN_START, WAKE_WORD_END, STT_START, STT_VAD_START\n * device → VoiceAssistantAudio … 16 kHz mono s16le, *until told to stop*\n * we → STT_VAD_END ← the device has no VAD of its own\n * we → STT_END{text}, INTENT_START, INTENT_END, TTS_START{text}, TTS_END{url}, RUN_END\n * device → fetches the URL over HTTP, plays it, VoiceAssistantAnnounceFinished\n *\n * Two consequences shape this file: end-of-speech detection is ours (`vad.ts`), and the spoken reply\n * has to be reachable over HTTP (`mediaServer.ts`) rather than streamed down the socket.\n */\nimport * as net from 'node:net';\nimport {\n pb,\n encode,\n decode,\n type PbMessage,\n type AuthenticationResponse,\n type DeviceInfoResponse,\n type HelloResponse,\n type VoiceAssistantAnnounceFinished,\n type VoiceAssistantAudio,\n type VoiceAssistantConfigurationResponse,\n type VoiceAssistantEventData,\n type VoiceAssistantRequestMsg,\n} from './esphomeProto';\nimport { EntityRegistry, isEntityStateMessage, isListEntityMessage, type EsphomeEntity } from './esphomeEntities';\nimport { SpeechDetector, type SpeechEnd, type Bytes } from './vad';\nimport { pcmToWav } from './stt';\nimport type { DiscardedCapture, DiscardReason } from './captures';\nimport type { SttEngine } from './stt';\nimport type { TtsEngine } from './tts';\nimport type { SatelliteState } from './protocol';\n\n/** `VoiceAssistantEvent` (api.proto) — the subset we emit. */\nconst EVENT = {\n ERROR: 0,\n RUN_START: 1,\n RUN_END: 2,\n STT_START: 3,\n STT_END: 4,\n INTENT_START: 5,\n INTENT_END: 6,\n TTS_START: 7,\n TTS_END: 8,\n WAKE_WORD_END: 10,\n STT_VAD_START: 11,\n STT_VAD_END: 12,\n} as const;\n\n/** `SubscribeVoiceAssistantRequest.flags`: audio over the API socket instead of a UDP side channel. */\nconst SUBSCRIBE_API_AUDIO = 1;\n/** ESPHome voice satellites always capture at 16 kHz mono 16-bit. */\nconst MIC_SAMPLE_RATE = 16000;\n/**\n * Leading audio to keep out of the end-of-speech analysis. These devices acknowledge the wake word\n * with a chirp through their own speaker and hear it back on their own microphone, far louder than\n * the speech that follows. Without this the chirp alone satisfies the detector's speech test — so\n * `sawSpeech` stops meaning \"somebody said something\" and a false wake is billed to STT — and it\n * drags the noise floor up with it.\n *\n * Measured over four captures from a ThirdReality Voice & Music Assistant (RMS per 50 ms, remarkably\n * repeatable across all four):\n *\n * ms 0 50 100 150 200 250 300 350 400 450 500 550 600\n * rms ~20 ~900 ~11k ~19k ~14k ~1.3k ~990 ~780 ~610 ~370 ~330 ~165 ~180\n *\n * so the chirp is still above the gate at 500 ms and only clears it around 550–600. 600 ms is the\n * smallest value that rejects a chirp-only wake while still detecting the speech in every one of the\n * four captures, with no change to when the utterance closes. The audio itself is untouched and still\n * goes to STT in full, so a speaker who starts inside this window loses nothing but a late gate open.\n */\nconst WAKE_CHIRP_MS = 600;\n/** Default API port of an ESPHome device. */\nconst DEFAULT_PORT = 6053;\n/** Keepalive interval; two missed replies count as a dead link. */\nconst PING_INTERVAL_MS = 20_000;\n/** Wait between reconnect attempts. Devices reboot, and a Wi-Fi speaker drops off now and then. */\nconst RECONNECT_MS = 10_000;\n/** Give up on a pipeline that produced no audio at all (device wedged mid-stream). */\nconst PIPELINE_TIMEOUT_MS = 30_000;\n/** How long we wait for the device to report that it finished playing before forcing it back to idle. */\nconst PLAYBACK_TIMEOUT_MS = 60_000;\n/**\n * Default lowest RMS that counts as speech (the noise floor can only raise the gate above it). Fits the\n * ThirdReality speaker; quieter microphones get their own value per device ({@link EsphomeDevice.speechLevel}).\n */\nexport const DEFAULT_SPEECH_LEVEL = 250;\n/**\n * Close the microphone when nobody has started speaking this long after the wake word. Without it a false wake\n * kept the stream open for the full 12 s cap: on a ThirdReality speaker with a custom wake word that was ten\n * times in one night, each 12.6 s of an open mic (and of music ducked, if any played).\n */\nconst NO_SPEECH_MS = 5000;\n/** The same after a follow-up question re-opened the mic: the user may need a moment to think. */\nconst NO_SPEECH_FOLLOW_UP_MS = 8000;\n/** Silence threshold as a share of the speech threshold — keeps the default pair 250/150. */\nconst SILENCE_SHARE = 0.6;\n/**\n * A dropped utterance whose loudest frame reached this share of the speech threshold was probably a voice\n * that was too quiet, not an empty room — worth telling the user where to lower the threshold.\n */\nconst QUIET_VOICE_SHARE = 0.4;\n\n/** The configured speech threshold of a device, or 0 for the default. Table cells may arrive as strings. */\nexport function speechLevelOf(device: EsphomeDevice): number {\n const level = Math.round(Number(device.speechLevel));\n return Number.isFinite(level) && level > 0 ? level : 0;\n}\n\nexport interface EsphomeDevice {\n /** Host name or IP of the satellite. */\n ip: string;\n /** API port; empty/0 → 6053. */\n port?: number;\n /** API password, if one is configured on the device. */\n password?: string;\n /** Room this satellite belongs to (drives `satellites.<room>.*` and the answer context). */\n room?: string;\n /**\n * Lowest RMS that counts as speech on this device (empty/0 → {@link DEFAULT_SPEECH_LEVEL}). Microphone\n * levels differ a lot: a ThirdReality speaker hears normal speech at RMS 350–470, a phone running a\n * satellite app at under 200, so one fixed floor cannot fit both.\n */\n speechLevel?: number | string | null;\n}\n\nexport interface EsphomeSatellitesOptions {\n devices: EsphomeDevice[];\n /** ISO-639-1 language hint for STT/TTS ('' = auto/provider default). */\n language: string;\n stt: SttEngine;\n tts: TtsEngine;\n /**\n * Produce the assistant reply for a transcribed utterance. `audioMs` is the length of the recording,\n * `vad` what the voice activity detection made of it (both for the request journal).\n */\n answer: (\n question: string,\n ctx: {\n device: string;\n room: string;\n wakeWord: string;\n sttMs: number;\n audioMs: number;\n vad: { peak: number; noiseFloor: number; startLevel: number; endLevel: number };\n },\n ) => Promise<string>;\n /** The reply was synthesised: how long that took and how long it plays (for the request journal). */\n onSpoken?: (device: string, info: { ms: number; audioMs: number }) => void;\n /** Optional STT vocabulary hints (device/room names) to bias recognition; re-read per utterance. */\n getHints?: () => string[] | Promise<string[]>;\n /** Where spoken replies are published for the device to fetch. */\n media: MediaHost;\n /**\n * Host the device should call back on. Empty → the local address of our socket to that device,\n * which is automatically the right interface on a multi-homed host.\n */\n mediaHost?: string;\n /** Silence that ends an utterance, in ms ('' / 0 → the detector default). */\n silenceMs?: number;\n log: ioBroker.Logger;\n /** Notified on every satellite state transition (idle/listening/processing/speaking/offline). */\n onStatus?: (device: string, room: string, state: SatelliteState | 'offline') => void;\n /** Notified with the device's wake-word configuration, on connect and after every change. */\n onWakeWords?: (device: string, room: string, config: WakeWordConfig) => void;\n /** Notified once per connect with everything the device exposes besides the voice pipeline. */\n onEntities?: (device: string, room: string, entities: EsphomeEntity[]) => void;\n /** Notified whenever one of those entities changes (and once per entity right after connecting). */\n onEntityState?: (device: string, room: string, entity: EsphomeEntity) => void;\n /** True when the reply asks something back, so the device should re-open its mic without a wake word. */\n expectsFollowUp?: (answer: string, device: string) => boolean;\n /**\n * A recording that never became a request: dropped as silence, or returned empty by the speech recognition.\n * For diagnosis (the adapter stores it when asked to); absent → nothing is kept.\n */\n onDiscarded?: (capture: DiscardedCapture) => void;\n /**\n * Custom wake words to offer the devices, re-read for every configuration request. The device lists\n * them next to its own and downloads one (from the media server) only when it is selected.\n */\n externalWakeWords?: () => ExternalWakeWordOffer[];\n}\n\n/** A custom wake word as offered to the devices; the files are on the media server under `path`. */\nexport interface ExternalWakeWordOffer {\n id: string;\n phrase: string;\n languages: string[];\n /** Size of the `.tflite` in bytes. */\n size: number;\n /** SHA-256 of the `.tflite`, lower-case hex. */\n sha256: string;\n /** URL path of the JSON config, e.g. `/wakeword/hey_hannah_1a2b3c4d.json`; the model sits next to it. */\n path: string;\n}\n\n/** What a device reports about its wake words: what the firmware ships plus the custom ones we offered. */\nexport interface WakeWordConfig {\n /** Ids currently listening, e.g. `['okay_nabu']`. */\n active: string[];\n /** Every wake word the device can use, with the spoken phrase and the languages it was trained on. */\n available: { id: string; phrase: string; languages: string[] }[];\n /** How many may listen at once — 2 on the ThirdReality speaker, which has a sensitivity per slot. */\n max: number;\n}\n\n/** `VoiceAssistantTimerEvent` in api.proto. */\nexport const TIMER_EVENT = { started: 0, updated: 1, cancelled: 2, finished: 3 } as const;\n\n/** One assistant timer, as the device wants to hear about it. */\nexport interface TimerEvent {\n type: keyof typeof TIMER_EVENT;\n id: string;\n name: string;\n totalSeconds: number;\n secondsLeft: number;\n /** False once it has finished or been cancelled — the device clears its ring on that. */\n active: boolean;\n}\n\n/** The slice of `MediaServer` this module needs (kept narrow so tests can pass a stub). */\nexport interface MediaHost {\n port: number;\n publish(body: Buffer, contentType?: string, ttlMs?: number): string;\n}\n\n/**\n * All configured ESPHome satellites. Owns one connection per device and offers the same surface the\n * adapter already uses for the UDP server: `devices()` and `announce()`.\n */\nexport class EsphomeSatellites {\n private readonly connections: EsphomeConnection[] = [];\n\n constructor(private readonly opts: EsphomeSatellitesOptions) {}\n\n async start(): Promise<void> {\n for (const device of this.opts.devices) {\n if (!device.ip?.trim()) {\n continue;\n }\n const connection = new EsphomeConnection(device, this.opts);\n this.connections.push(connection);\n connection.start();\n }\n if (!this.connections.length) {\n this.opts.log.warn('ESPHome satellites enabled, but no device addresses are configured.');\n }\n return Promise.resolve();\n }\n\n async stop(): Promise<void> {\n await Promise.all(this.connections.map(c => c.stop()));\n this.connections.length = 0;\n }\n\n /** Device names of the satellites currently connected. */\n devices(): string[] {\n return this.connections.filter(c => c.online).map(c => c.deviceName);\n }\n\n /** Wake-word configuration of one connected satellite, or null if it is unknown/offline. */\n wakeWords(device: string): WakeWordConfig | null {\n return this.connections.find(c => c.online && c.deviceName === device)?.wakeWordConfig ?? null;\n }\n\n /**\n * Choose which wake words listen on one satellite. Ids come from {@link WakeWordConfig.available};\n * the device stores the selection itself, so it survives a restart of the adapter.\n */\n setWakeWords(device: string, ids: string[]): boolean {\n const target = this.connections.find(c => c.online && c.deviceName === device);\n if (!target) {\n return false;\n }\n target.setWakeWords(ids);\n return true;\n }\n\n /**\n * Offer the current custom wake words to every connected satellite again — after one was uploaded\n * or deleted. Each device answers with its new configuration (→ `onWakeWords`).\n */\n refreshWakeWords(): number {\n const online = this.connections.filter(c => c.online);\n online.forEach(c => c.requestWakeWordConfig());\n return online.length;\n }\n\n /** Everything one satellite exposes besides the voice pipeline (gain, noise suppression, …). */\n entities(device: string): EsphomeEntity[] {\n return this.connections.find(c => c.online && c.deviceName === device)?.entities.list() ?? [];\n }\n\n /**\n * Write one of those entities. Returns false when the satellite is offline, the entity is unknown\n * or read-only, or the value is not one the device accepts.\n */\n setEntity(device: string, objectId: string, value: unknown): boolean {\n const target = this.connections.find(c => c.online && c.deviceName === device);\n return target ? target.setEntity(objectId, value) : false;\n }\n\n /**\n * Mirror an assistant timer onto the satellites so they can show it on their own LED ring and ring\n * it themselves. `device` null broadcasts. Returns how many devices were told.\n */\n timerEvent(device: string | null, event: TimerEvent): number {\n const targets = this.connections.filter(c => c.online && (!device || c.deviceName === device));\n targets.forEach(c => c.sendTimerEvent(event));\n return targets.length;\n }\n\n /**\n * Play a ready PCM buffer on one satellite (by device name) or all of them. Returns how many\n * devices it was handed to.\n *\n * With `startConversation` the device opens its microphone as soon as the clip has played, without\n * a wake word — that is how a question gets its answer (see `PendingQuestions`).\n */\n async announce(device: string | null, pcm: Buffer, sampleRate: number, startConversation = false): Promise<number> {\n const targets = this.connections.filter(c => c.online && (!device || c.deviceName === device));\n if (!targets.length) {\n return 0;\n }\n await Promise.all(\n targets.map(c =>\n c\n .announce(pcm, sampleRate, startConversation)\n .catch(e => this.opts.log.warn(`announce to ${c.deviceName} failed: ${(e as Error).message}`)),\n ),\n );\n return targets.length;\n }\n}\n\n/** State of the utterance currently being captured. */\ninterface Capture {\n chunks: Buffer[];\n detector: SpeechDetector;\n conversationId: string;\n /** Wake word that opened this capture; '' when the mic was re-opened for a follow-up/question. */\n wakeWord: string;\n timer: NodeJS.Timeout;\n closed: boolean;\n}\n\n/** One satellite: socket lifecycle, handshake, and the voice pipeline. */\nclass EsphomeConnection {\n /** Device name as reported by the device; falls back to its address until the handshake answers. */\n deviceName: string;\n online = false;\n /** Last wake-word configuration the device reported; null until the handshake answers. */\n wakeWordConfig: WakeWordConfig | null = null;\n /**\n * One entry per configuration request in flight, in send order (the device answers each one, in\n * order): the ids a preceding `SetConfiguration` asked for, or null. The read-back is the only\n * failure signal the device gives.\n */\n private configChecks: (string[] | null)[] = [];\n /** Everything else the device exposes — rebuilt from scratch on every reconnect. */\n readonly entities = new EntityRegistry();\n\n private readonly host: string;\n private readonly port: number;\n private readonly room: string;\n\n private socket: net.Socket | null = null;\n private rx: Bytes = Buffer.alloc(0);\n private pingTimer: NodeJS.Timeout | null = null;\n private reconnectTimer: NodeJS.Timeout | null = null;\n private missedPings = 0;\n private stopping = false;\n private capture: Capture | null = null;\n private playbackTimer: NodeJS.Timeout | null = null;\n private readonly password: string;\n /** Configured speech threshold, 0 = {@link DEFAULT_SPEECH_LEVEL}. */\n private readonly speechLevel: number;\n\n constructor(\n device: EsphomeDevice,\n private readonly opts: EsphomeSatellitesOptions,\n ) {\n this.host = device.ip.trim();\n this.port = device.port || DEFAULT_PORT;\n this.room = (device.room || '').trim();\n this.password = device.password || '';\n this.speechLevel = speechLevelOf(device);\n this.deviceName = this.host;\n }\n\n // ── lifecycle ───────────────────────────────────────────────────────────────────────────────────\n\n start(): void {\n this.connect();\n }\n\n async stop(): Promise<void> {\n this.stopping = true;\n this.clearTimers();\n this.abortCapture();\n const socket = this.socket;\n this.socket = null;\n if (socket) {\n try {\n socket.write(encode(new pb.DisconnectRequest()));\n } catch {\n /* socket already gone — nothing to say goodbye on */\n }\n socket.destroy();\n }\n this.setOffline();\n return Promise.resolve();\n }\n\n private connect(): void {\n if (this.stopping) {\n return;\n }\n this.rx = Buffer.alloc(0);\n this.missedPings = 0;\n // The device re-announces everything after a reconnect, and the keys are not stable across\n // reboots, so starting from a clean slate is the only safe option.\n this.entities.clear();\n this.configChecks = [];\n\n const socket = net.createConnection({ host: this.host, port: this.port }, () => {\n this.opts.log.debug(`ESPHome ${this.host}:${this.port} connected`);\n this.handshake();\n });\n this.socket = socket;\n socket.setNoDelay(true);\n\n socket.on('data', data => this.onData(data));\n socket.on('error', e => {\n // A satellite that is switched off is normal, so this is not an error-level event.\n this.opts.log.debug(`ESPHome ${this.host}: ${e.message}`);\n });\n socket.on('close', () => {\n if (this.socket === socket) {\n this.socket = null;\n this.onDisconnected();\n }\n });\n }\n\n private onDisconnected(): void {\n this.clearTimers();\n this.abortCapture();\n if (this.online) {\n this.opts.log.info(`ESPHome satellite offline: ${this.deviceName}`);\n }\n this.setOffline();\n this.scheduleReconnect();\n }\n\n private scheduleReconnect(): void {\n if (this.stopping || this.reconnectTimer) {\n return;\n }\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, RECONNECT_MS);\n this.reconnectTimer.unref();\n }\n\n private clearTimers(): void {\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n this.playbackTimer = null;\n }\n }\n\n private setOffline(): void {\n if (this.online) {\n this.online = false;\n this.opts.onStatus?.(this.deviceName, this.room, 'offline');\n }\n }\n\n private status(state: SatelliteState): void {\n this.opts.onStatus?.(this.deviceName, this.room, state);\n }\n\n // ── protocol plumbing ───────────────────────────────────────────────────────────────────────────\n\n private send(message: PbMessage): void {\n const socket = this.socket;\n if (!socket || socket.destroyed) {\n return;\n }\n try {\n socket.write(encode(message));\n } catch (e) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: send failed — ${(e as Error).message}`);\n }\n }\n\n private onData(data: Buffer): void {\n this.rx = this.rx.length ? Buffer.concat([this.rx, data]) : data;\n let frames;\n try {\n ({ frames, rest: this.rx } = decode(this.rx));\n } catch (e) {\n this.opts.log.error(`ESPHome ${this.deviceName}: ${(e as Error).message}`);\n this.socket?.destroy();\n return;\n }\n for (const frame of frames) {\n if (frame.error) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: cannot parse ${frame.name}`);\n continue;\n }\n if (!frame.name || !frame.message) {\n continue; // unknown id — already skipped by its declared length\n }\n try {\n this.onMessage(frame.name, frame.message);\n } catch (e) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: ${frame.name} handler failed — ${(e as Error).message}`,\n );\n }\n }\n }\n\n private handshake(): void {\n const hello = new pb.HelloRequest();\n hello.setClientInfo('ioBroker.assistant');\n hello.setApiVersionMajor(1);\n hello.setApiVersionMinor(10);\n this.send(hello);\n\n const auth = new pb.AuthenticationRequest();\n auth.setPassword(this.password);\n this.send(auth);\n\n this.send(new pb.DeviceInfoRequest());\n // Ask for the entity announcements *before* subscribing to their states: a state message whose\n // entity we have not seen yet has nothing to attach to and is dropped.\n this.send(new pb.ListEntitiesRequest());\n this.send(new pb.SubscribeStatesRequest());\n\n const subscribe = new pb.SubscribeVoiceAssistantRequest();\n subscribe.setSubscribe(true);\n subscribe.setFlags(SUBSCRIBE_API_AUDIO);\n this.send(subscribe);\n\n this.requestWakeWordConfig();\n\n this.pingTimer = setInterval(() => {\n if (++this.missedPings > 2) {\n this.opts.log.info(`ESPHome ${this.deviceName}: no reply to keepalive — reconnecting`);\n this.socket?.destroy();\n return;\n }\n this.send(new pb.PingRequest());\n }, PING_INTERVAL_MS);\n this.pingTimer.unref();\n }\n\n private onMessage(name: string, message: unknown): void {\n switch (name) {\n case 'HelloResponse': {\n const hello = message as HelloResponse;\n this.opts.log.debug(\n `ESPHome ${this.host}: ${hello.getServerInfo()} (API ${hello.getApiVersionMajor()}.${hello.getApiVersionMinor()})`,\n );\n return;\n }\n\n case 'AuthenticationResponse':\n if ((message as AuthenticationResponse).getInvalidPassword()) {\n this.opts.log.error(\n `ESPHome ${this.host}: API password rejected — check the device's password in the settings.`,\n );\n this.socket?.destroy();\n }\n return;\n\n case 'DeviceInfoResponse': {\n const info = message as DeviceInfoResponse;\n this.deviceName = info.getName() || this.host;\n this.online = true;\n this.opts.log.info(\n `ESPHome satellite online: ${this.deviceName} (${info.getModel()}) @ ${this.host}${this.room ? `, room ${this.room}` : ''}`,\n );\n this.status('idle');\n return;\n }\n\n case 'VoiceAssistantConfigurationResponse': {\n const config = message as VoiceAssistantConfigurationResponse;\n // A custom wake word the device already downloaded is listed twice: once from its own\n // model folder and once from our offer.\n const seen = new Set<string>();\n this.wakeWordConfig = {\n active: config.getActiveWakeWordsList(),\n available: config\n .getAvailableWakeWordsList()\n .map(w => ({\n id: w.getId(),\n phrase: w.getWakeWord(),\n languages: w.getTrainedLanguagesList(),\n }))\n .filter(w => !seen.has(w.id) && !!seen.add(w.id)),\n max: config.getMaxActiveWakeWords(),\n };\n const active = this.wakeWordConfig.active.join(', ') || '(none active)';\n const available = this.wakeWordConfig.available.map(w => w.id);\n this.opts.log.info(\n `ESPHome ${this.deviceName}: wake words ${active}${available.length ? ` (available: ${available.join(', ')})` : ''}`,\n );\n const requested = this.configChecks.shift();\n const missing = requested?.filter(id => !this.wakeWordConfig?.active.includes(id)) ?? [];\n if (missing.length) {\n // The device answers a model it cannot fetch or load with silence, not an error.\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: could not activate wake word(s) ${missing.join(', ')}. ` +\n `For an uploaded wake word the device must reach ${this.mediaUrl('/')} and the model ` +\n `must be a microWakeWord .tflite.`,\n );\n }\n this.opts.onWakeWords?.(this.deviceName, this.room, this.wakeWordConfig);\n return;\n }\n\n case 'VoiceAssistantRequest':\n this.onPipelineRequest(message as VoiceAssistantRequestMsg);\n return;\n\n case 'VoiceAssistantAudio':\n this.onAudio(message as VoiceAssistantAudio);\n return;\n\n case 'VoiceAssistantAnnounceFinished':\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n this.playbackTimer = null;\n }\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: playback finished (success=${(message as VoiceAssistantAnnounceFinished).getSuccess()})`,\n );\n this.status('idle');\n return;\n\n case 'PingRequest':\n this.send(new pb.PingResponse());\n return;\n\n case 'PingResponse':\n this.missedPings = 0;\n return;\n\n case 'GetTimeRequest': {\n // The device has no RTC and syncs its clock from us — its timer ring depends on it.\n const time = new pb.GetTimeResponse();\n time.setEpochSeconds(Math.floor(Date.now() / 1000));\n this.send(time);\n return;\n }\n\n case 'DisconnectRequest':\n this.send(new pb.DisconnectResponse());\n this.socket?.destroy();\n return;\n\n case 'ListEntitiesDoneResponse': {\n const list = this.entities.list();\n if (list.length) {\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: ${list.length} entities — ${list.map(e => e.objectId).join(', ')}`,\n );\n }\n this.opts.onEntities?.(this.deviceName, this.room, list);\n return;\n }\n\n default:\n if (isListEntityMessage(name)) {\n this.entities.addFromAnnouncement(name, message);\n } else if (isEntityStateMessage(name)) {\n const entity = this.entities.applyState(name, message);\n if (entity) {\n this.opts.onEntityState?.(this.deviceName, this.room, entity);\n }\n }\n return;\n }\n }\n\n /** Write one of the device's entities. See {@link EsphomeSatellites.setEntity}. */\n setEntity(objectId: string, value: unknown): boolean {\n const message = this.entities.command(objectId, value);\n if (!message) {\n const entity = this.entities.get(objectId);\n const known = this.entities\n .list()\n .map(e => e.objectId)\n .join(', ');\n const options = entity?.options ? ` (one of: ${entity.options.join(', ')})` : '';\n this.opts.log.warn(\n entity\n ? `ESPHome ${this.deviceName}: ${JSON.stringify(value)} is not a value \"${objectId}\" accepts${options}`\n : `ESPHome ${this.deviceName}: no entity \"${objectId}\" (have: ${known})`,\n );\n return false;\n }\n this.send(message);\n this.opts.log.debug(`ESPHome ${this.deviceName}: ${objectId} := ${JSON.stringify(value)}`);\n return true;\n }\n\n /** Push one assistant timer to the device so it can show and ring it itself. */\n sendTimerEvent(event: TimerEvent): void {\n const message = new pb.VoiceAssistantTimerEventResponse();\n message.setEventType(TIMER_EVENT[event.type]);\n message.setTimerId(event.id);\n message.setName(event.name);\n message.setTotalSeconds(Math.max(0, Math.round(event.totalSeconds)));\n message.setSecondsLeft(Math.max(0, Math.round(event.secondsLeft)));\n message.setIsActive(event.active);\n this.send(message);\n }\n\n // ── voice pipeline ──────────────────────────────────────────────────────────────────────────────\n\n private onPipelineRequest(request: VoiceAssistantRequestMsg): void {\n if (!request.getStart()) {\n this.abortCapture();\n return;\n }\n if (this.capture) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: pipeline already running — ignoring wake`);\n return;\n }\n\n const phrase = request.getWakeWordPhrase();\n this.opts.log.debug(`ESPHome ${this.deviceName}: wake \"${phrase || '(follow-up)'}\"`);\n\n const response = new pb.VoiceAssistantResponse();\n response.setPort(0); // 0 = stream the audio over this socket\n response.setError(false);\n this.send(response);\n\n const timer = setTimeout(() => {\n this.opts.log.warn(`ESPHome ${this.deviceName}: no end of speech within ${PIPELINE_TIMEOUT_MS} ms`);\n void this.finishCapture();\n }, PIPELINE_TIMEOUT_MS);\n timer.unref();\n\n this.capture = {\n chunks: [],\n detector: new SpeechDetector({\n sampleRate: MIC_SAMPLE_RATE,\n silenceMs: this.opts.silenceMs || undefined,\n // Microphone gain differs wildly between these devices, so fixed RMS levels do not\n // travel: derive them from the room instead, once the wake chirp is out of the way.\n skipMs: WAKE_CHIRP_MS,\n adaptive: true,\n startTimeoutMs: phrase ? NO_SPEECH_MS : NO_SPEECH_FOLLOW_UP_MS,\n minStartLevel: this.speechLevel || DEFAULT_SPEECH_LEVEL,\n minEndLevel: Math.round((this.speechLevel || DEFAULT_SPEECH_LEVEL) * SILENCE_SHARE),\n }),\n conversationId: request.getConversationId(),\n wakeWord: phrase || '',\n timer,\n closed: false,\n };\n\n this.status('listening');\n this.event(EVENT.RUN_START);\n if (phrase) {\n this.event(EVENT.WAKE_WORD_END, { wake_word_phrase: phrase });\n }\n this.event(EVENT.STT_START);\n this.event(EVENT.STT_VAD_START);\n }\n\n private onAudio(audio: VoiceAssistantAudio): void {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n const chunk = Buffer.from(audio.getData_asU8());\n if (chunk.length) {\n capture.chunks.push(chunk);\n const end = capture.detector.push(chunk);\n if (end) {\n void this.finishCapture(end);\n return;\n }\n }\n if (audio.getEnd()) {\n void this.finishCapture();\n }\n }\n\n /** Stop the mic stream and run STT → answer → TTS for what was captured. */\n /** Hand a recording that never became a request to {@link EsphomeSatellitesOptions.onDiscarded}. */\n private discard(capture: Capture, pcm: Bytes, reason: DiscardReason): void {\n if (!this.opts.onDiscarded || !pcm.length) {\n return;\n }\n const d = capture.detector;\n try {\n this.opts.onDiscarded({\n device: this.deviceName,\n room: this.room,\n wakeWord: capture.wakeWord,\n reason,\n pcm,\n sampleRate: MIC_SAMPLE_RATE,\n vad: {\n peak: Math.round(d.peak),\n analysedPeak: Math.round(d.analysedPeak),\n noiseFloor: Math.round(d.noiseFloor),\n startLevel: Math.round(d.startLevel),\n endLevel: Math.round(d.endLevel),\n },\n });\n } catch (e) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: keeping the recording failed — ${(e as Error).message}`);\n }\n }\n\n private async finishCapture(end?: SpeechEnd): Promise<void> {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n capture.closed = true;\n this.capture = null;\n clearTimeout(capture.timer);\n\n // Send this first: the device streams until it sees it.\n this.event(EVENT.STT_VAD_END);\n\n const pcm = Buffer.concat(capture.chunks);\n const seconds = pcm.length / 2 / MIC_SAMPLE_RATE;\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: captured ${seconds.toFixed(2)} s ` +\n `(peak RMS ${Math.round(capture.detector.peak)}, noise floor ${Math.round(capture.detector.noiseFloor)}, ` +\n `speech/silence gate ${Math.round(capture.detector.startLevel)}/${Math.round(capture.detector.endLevel)})`,\n );\n\n if (!pcm.length || !capture.detector.sawSpeech) {\n const loudest = Math.round(capture.detector.analysedPeak);\n const gate = Math.round(capture.detector.startLevel);\n if (pcm.length && loudest >= gate * QUIET_VOICE_SHARE) {\n // Not an empty room: something came close to the gate. Typically a microphone that is quieter\n // than the default threshold expects (a phone) — say where to change it.\n this.opts.log.info(\n `ESPHome ${this.deviceName}: dropped the utterance — the loudest sound (RMS ${loudest}) stayed ` +\n `below the speech threshold ${gate}. If somebody spoke, lower \"Speech threshold\" for this ` +\n `device in the ESPHome satellites table (Voice tab), e.g. to ${Math.max(\n 30,\n Math.round((loudest * 0.6) / 10) * 10,\n )}.`,\n );\n } else {\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: nothing but silence${\n end === 'no-speech' ? ' (nobody started speaking)' : ''\n } — dropping the utterance`,\n );\n }\n this.discard(capture, pcm, end === 'no-speech' ? 'no-speech' : 'silence');\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n this.status('processing');\n try {\n const hints = this.opts.getHints ? await this.opts.getHints() : undefined;\n const sttStart = Date.now();\n const text = (await this.opts.stt.transcribe(pcm, MIC_SAMPLE_RATE, this.opts.language, hints)).trim();\n const sttMs = Date.now() - sttStart;\n this.event(EVENT.STT_END, { text });\n if (!text) {\n // Speech was heard, so the user waits for an answer: say why none comes. Typically a word in another\n // language than the STT listens in (a German \"Ja\" to a Russian STT comes back empty).\n this.opts.log.info(\n `Voice Q (${this.deviceName}): (empty) — speech recognition returned no text for ${seconds.toFixed(1)} s of audio`,\n );\n this.discard(capture, pcm, 'empty-stt');\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n this.event(EVENT.INTENT_START);\n const answer = (\n await this.opts.answer(text, {\n device: this.deviceName,\n room: this.room,\n wakeWord: capture.wakeWord,\n sttMs,\n audioMs: Math.round(seconds * 1000),\n vad: {\n peak: Math.round(capture.detector.peak),\n noiseFloor: Math.round(capture.detector.noiseFloor),\n startLevel: Math.round(capture.detector.startLevel),\n endLevel: Math.round(capture.detector.endLevel),\n },\n })\n ).trim();\n const followUp = !!answer && this.opts.expectsFollowUp?.(answer, this.deviceName) === true;\n this.event(EVENT.INTENT_END, followUp ? { continue_conversation: '1' } : undefined);\n\n if (!answer) {\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n const ttsStart = Date.now();\n const { pcm: ttsPcm, sampleRate } = await this.opts.tts.synthesize(answer, this.opts.language);\n this.opts.onSpoken?.(this.deviceName, {\n ms: Date.now() - ttsStart,\n audioMs: (ttsPcm.length / 2 / sampleRate) * 1000,\n });\n const url = this.publish(ttsPcm, sampleRate);\n\n this.status('speaking');\n this.event(EVENT.TTS_START, { text: answer });\n this.event(EVENT.TTS_END, { url });\n this.event(EVENT.RUN_END, followUp ? { continue_conversation: '1' } : undefined);\n this.armPlaybackTimeout();\n } catch (e) {\n const reason = (e as Error).message;\n this.opts.log.error(`ESPHome ${this.deviceName}: voice pipeline failed — ${reason}`);\n // Without this the device stays ducked and its LED keeps spinning.\n this.event(EVENT.ERROR, { code: 'pipeline-failed', message: reason });\n this.event(EVENT.RUN_END);\n this.status('idle');\n }\n }\n\n /**\n * Select which wake words listen on this device. Unknown ids are dropped and the list is trimmed\n * to what the firmware allows, because the device answers a rejected list with silence rather than\n * an error — better to send something valid and report what we changed.\n *\n * The device persists the selection and echoes it back in a `VoiceAssistantConfigurationResponse`,\n * so we ask for one instead of assuming the write took.\n */\n setWakeWords(ids: string[]): void {\n const known = this.wakeWordConfig?.available.map(w => w.id) ?? [];\n const wanted = [...new Set(ids.map(i => i.trim()).filter(Boolean))];\n const unknown = wanted.filter(i => !known.includes(i));\n if (unknown.length) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: unknown wake word(s) ${unknown.join(', ')} — ignored. ` +\n `Available: ${known.join(', ') || '(none reported)'}`,\n );\n }\n let valid = wanted.filter(i => known.includes(i));\n const max = this.wakeWordConfig?.max || 0;\n if (max > 0 && valid.length > max) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: device allows ${max} active wake word(s), keeping ${valid\n .slice(0, max)\n .join(', ')}`,\n );\n valid = valid.slice(0, max);\n }\n\n const message = new pb.VoiceAssistantSetConfiguration();\n message.setActiveWakeWordsList(valid);\n this.send(message);\n this.opts.log.info(`ESPHome ${this.deviceName}: wake words set to ${valid.join(', ') || '(none)'}`);\n // Read back, so the reported state is the device's and not our guess. The device resolves a\n // custom wake word (and downloads it) while handling the SetConfiguration, so this answer\n // already tells whether that worked.\n this.requestWakeWordConfig(valid);\n }\n\n /**\n * Ask the device for its wake-word configuration. The request always carries the current custom\n * wake words: the firmware replaces its list of offered models with every request, so one sent\n * without them would make an uploaded wake word vanish from the device until the next offer.\n *\n * @param check ids a SetConfiguration just asked for — warned about if the answer lacks them\n */\n requestWakeWordConfig(check: string[] | null = null): void {\n const request = new pb.VoiceAssistantConfigurationRequest();\n const offers = this.opts.externalWakeWords?.() ?? [];\n request.setExternalWakeWordsList(\n offers.map(offer => {\n const w = new pb.VoiceAssistantExternalWakeWord();\n w.setId(offer.id);\n w.setWakeWord(offer.phrase);\n w.setTrainedLanguagesList(offer.languages);\n w.setModelType('micro');\n w.setModelSize(offer.size);\n w.setModelHash(offer.sha256);\n w.setUrl(this.mediaUrl(offer.path));\n return w;\n }),\n );\n // A device that never answers must not let this grow without bound.\n if (this.configChecks.length < 16) {\n this.configChecks.push(check);\n }\n this.send(request);\n }\n\n /** Drop a half-finished capture (device went away, or told us to stop). */\n private abortCapture(): void {\n const capture = this.capture;\n if (!capture) {\n return;\n }\n this.capture = null;\n capture.closed = true;\n clearTimeout(capture.timer);\n }\n\n // ── announcements ───────────────────────────────────────────────────────────────────────────────\n\n /**\n * Speak a ready PCM buffer on this device (timers, alarms, `tts.text`, jingles). With\n * `startConversation` the microphone opens once the clip has played, to catch the answer to a question.\n */\n announce(pcm: Buffer, sampleRate: number, startConversation = false): Promise<void> {\n if (!this.online || !pcm.length) {\n return Promise.resolve();\n }\n const request = new pb.VoiceAssistantAnnounceRequest();\n request.setMediaId(this.publish(pcm, sampleRate));\n request.setText('');\n request.setPreannounceMediaId('');\n // Needs feature flag 32 (START_CONVERSATION); on a device without it the clip still plays and\n // only the microphone stays shut, so there is nothing to guard here.\n request.setStartConversation(startConversation);\n this.status('speaking');\n this.send(request);\n this.armPlaybackTimeout();\n return Promise.resolve();\n }\n\n /**\n * Publish PCM as a WAV on the media server and build the URL the device should fetch.\n *\n * The host matters: on a multi-homed ioBroker machine the device can usually only reach one of the\n * addresses, and the local end of *this* socket is by definition one it can talk to.\n */\n private publish(pcm: Buffer, sampleRate: number): string {\n return this.mediaUrl(this.opts.media.publish(pcmToWav(pcm, sampleRate), 'audio/wav'));\n }\n\n /** Absolute URL of a path on the media server, with a host this device can reach. */\n private mediaUrl(path: string): string {\n const local = (this.socket?.localAddress || '').replace(/^::ffff:/, '');\n const host = this.opts.mediaHost?.trim() || local || '127.0.0.1';\n // An IPv6 literal has to be bracketed in a URL.\n const authority = host.includes(':') ? `[${host}]` : host;\n return `http://${authority}:${this.opts.media.port}${path}`;\n }\n\n /** Fall back to `idle` if the device never reports that it finished playing. */\n private armPlaybackTimeout(): void {\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n }\n this.playbackTimer = setTimeout(() => {\n this.playbackTimer = null;\n this.status('idle');\n }, PLAYBACK_TIMEOUT_MS);\n this.playbackTimer.unref();\n }\n\n private event(type: number, data?: Record<string, string>): void {\n const message = new pb.VoiceAssistantEventResponse();\n message.setEventType(type);\n const items: VoiceAssistantEventData[] = [];\n for (const [name, value] of Object.entries(data || {})) {\n const item = new pb.VoiceAssistantEventData();\n item.setName(name);\n item.setValue(value);\n items.push(item);\n }\n message.setDataList(items);\n this.send(message);\n }\n}\n"]}
1
+ {"version":3,"file":"esphome.js","sourceRoot":"","sources":["../../../src/lib/voice/esphome.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESPHome voice satellites — the fourth transport, next to the UDP server (`voiceServer.ts`) and the\n * Wyoming endpoint (`wyoming.ts`).\n *\n * Unlike those two, this one is a **client**: an ESPHome voice satellite is a *server* that waits on\n * TCP 6053 for the home-automation controller to connect and drive it. We therefore dial out to each\n * configured device and play the role Home Assistant normally plays. That covers the ThirdReality\n * Voice & Music Assistant (Dev Edition), the Home Assistant Voice PE and any box running\n * OHF-Voice/linux-voice-assistant.\n *\n * Flow for one utterance (verified against the firmware's `src/satellite/Satellite.cpp`):\n *\n * device → VoiceAssistantRequest{start, wake_word_phrase} wake word fired on the device\n * we → VoiceAssistantResponse{port:0} 0 = audio over the API, no UDP\n * we → RUN_START, WAKE_WORD_END, STT_START, STT_VAD_START\n * device → VoiceAssistantAudio … 16 kHz mono s16le, *until told to stop*\n * we → STT_VAD_END ← the device has no VAD of its own\n * we → STT_END{text}, INTENT_START, INTENT_END, TTS_START{text}, TTS_END{url}, RUN_END\n * device → fetches the URL over HTTP, plays it, VoiceAssistantAnnounceFinished\n *\n * Two consequences shape this file: end-of-speech detection is ours (`vad.ts`), and the spoken reply\n * has to be reachable over HTTP (`mediaServer.ts`) rather than streamed down the socket.\n */\nimport * as net from 'node:net';\nimport {\n pb,\n encode,\n decode,\n type PbMessage,\n type AuthenticationResponse,\n type DeviceInfoResponse,\n type HelloResponse,\n type VoiceAssistantAnnounceFinished,\n type VoiceAssistantAudio,\n type VoiceAssistantConfigurationResponse,\n type VoiceAssistantEventData,\n type VoiceAssistantRequestMsg,\n} from './esphomeProto';\nimport { EntityRegistry, isEntityStateMessage, isListEntityMessage, type EsphomeEntity } from './esphomeEntities';\nimport { SpeechDetector, type SpeechEnd, type Bytes, type VadReport } from './vad';\nimport { prepareForStt } from './speechAudio';\nimport { pcmToWav } from './stt';\nimport { levelProfile, type DiscardedCapture, type DiscardReason } from './captures';\nimport type { SttEngine } from './stt';\nimport type { TtsEngine } from './tts';\nimport type { SatelliteState } from './protocol';\n\n/** `VoiceAssistantEvent` (api.proto) — the subset we emit. */\nconst EVENT = {\n ERROR: 0,\n RUN_START: 1,\n RUN_END: 2,\n STT_START: 3,\n STT_END: 4,\n INTENT_START: 5,\n INTENT_END: 6,\n TTS_START: 7,\n TTS_END: 8,\n WAKE_WORD_END: 10,\n STT_VAD_START: 11,\n STT_VAD_END: 12,\n} as const;\n\n/** `SubscribeVoiceAssistantRequest.flags`: audio over the API socket instead of a UDP side channel. */\nconst SUBSCRIBE_API_AUDIO = 1;\n/** ESPHome voice satellites always capture at 16 kHz mono 16-bit. */\nconst MIC_SAMPLE_RATE = 16000;\n/**\n * Leading audio to keep out of the end-of-speech analysis. These devices acknowledge the wake word\n * with a chirp through their own speaker and hear it back on their own microphone, far louder than\n * the speech that follows. Without this the chirp alone satisfies the detector's speech test — so\n * `sawSpeech` stops meaning \"somebody said something\" and a false wake is billed to STT — and it\n * drags the noise floor up with it.\n *\n * Measured over four captures from a ThirdReality Voice & Music Assistant (RMS per 50 ms, remarkably\n * repeatable across all four):\n *\n * ms 0 50 100 150 200 250 300 350 400 450 500 550 600\n * rms ~20 ~900 ~11k ~19k ~14k ~1.3k ~990 ~780 ~610 ~370 ~330 ~165 ~180\n *\n * so the chirp is still above the gate at 500 ms and only clears it around 550–600. 600 ms is the\n * smallest value that rejects a chirp-only wake while still detecting the speech in every one of the\n * four captures, with no change to when the utterance closes. Only the analysis skips it: STT gets the audio\n * from 400 ms before the detected speech (`speechAudio.ts`), so a speaker who starts inside this window loses\n * nothing but a late gate open.\n */\nconst WAKE_CHIRP_MS = 600;\n/** Default API port of an ESPHome device. */\nconst DEFAULT_PORT = 6053;\n/** Keepalive interval; two missed replies count as a dead link. */\nconst PING_INTERVAL_MS = 20_000;\n/** Wait between reconnect attempts. Devices reboot, and a Wi-Fi speaker drops off now and then. */\nconst RECONNECT_MS = 10_000;\n/** Give up on a pipeline that produced no audio at all (device wedged mid-stream). */\nconst PIPELINE_TIMEOUT_MS = 30_000;\n/** How long we wait for the device to report that it finished playing before forcing it back to idle. */\nconst PLAYBACK_TIMEOUT_MS = 60_000;\n/**\n * Default lowest RMS that counts as speech (the noise floor can only raise the gate above it). Fits the\n * ThirdReality speaker; quieter microphones get their own value per device ({@link EsphomeDevice.speechLevel}).\n */\nexport const DEFAULT_SPEECH_LEVEL = 250;\n/**\n * Close the microphone when nobody has started speaking this long after the wake word. Without it a false wake\n * kept the stream open for the full 12 s cap: on a ThirdReality speaker with a custom wake word that was ten\n * times in one night, each 12.6 s of an open mic (and of music ducked, if any played).\n */\nconst NO_SPEECH_MS = 5000;\n/** The same after a follow-up question re-opened the mic: the user may need a moment to think. */\nconst NO_SPEECH_FOLLOW_UP_MS = 8000;\n/** Silence that ends an utterance when the configuration leaves it empty. */\nconst DEFAULT_SILENCE_MS = 900;\n/**\n * After this much speech a pause may be {@link LONG_PAUSE_FACTOR} times as long before the utterance ends: whoever\n * talks that long is dictating a request and pauses to think, while \"Licht an\" should still close quickly. Field\n * report 2026-11-03: \"Я хочу, чтоб ты создал задачу, чтобы утром говорить мне…\" was cut off right there.\n */\nconst LONG_REQUEST_MS = 2500;\nconst LONG_PAUSE_FACTOR = 1.5;\n/** Silence threshold as a share of the speech threshold — keeps the default pair 250/150. */\nconst SILENCE_SHARE = 0.6;\n/**\n * A dropped utterance whose loudest frame reached this share of the speech threshold was probably a voice\n * that was too quiet, not an empty room — worth telling the user where to lower the threshold.\n */\nconst QUIET_VOICE_SHARE = 0.4;\n\n/** The configured speech threshold of a device, or 0 for the default. Table cells may arrive as strings. */\nexport function speechLevelOf(device: EsphomeDevice): number {\n const level = Math.round(Number(device.speechLevel));\n return Number.isFinite(level) && level > 0 ? level : 0;\n}\n\nexport interface EsphomeDevice {\n /** Host name or IP of the satellite. */\n ip: string;\n /** API port; empty/0 → 6053. */\n port?: number;\n /** API password, if one is configured on the device. */\n password?: string;\n /** Room this satellite belongs to (drives `satellites.<room>.*` and the answer context). */\n room?: string;\n /**\n * Lowest RMS that counts as speech on this device (empty/0 → {@link DEFAULT_SPEECH_LEVEL}). Microphone\n * levels differ a lot: a ThirdReality speaker hears normal speech at RMS 350–470, a phone running a\n * satellite app at under 200, so one fixed floor cannot fit both.\n */\n speechLevel?: number | string | null;\n}\n\nexport interface EsphomeSatellitesOptions {\n devices: EsphomeDevice[];\n /** ISO-639-1 language hint for STT/TTS ('' = auto/provider default). */\n language: string;\n stt: SttEngine;\n tts: TtsEngine;\n /**\n * Produce the assistant reply for a transcribed utterance. `audioMs` is the length of the recording,\n * `vad` what the voice activity detection made of it (both for the request journal).\n */\n answer: (\n question: string,\n ctx: {\n device: string;\n room: string;\n wakeWord: string;\n sttMs: number;\n audioMs: number;\n vad: VadReport;\n },\n ) => Promise<string>;\n /** The reply was synthesised: how long that took and how long it plays (for the request journal). */\n onSpoken?: (device: string, info: { ms: number; audioMs: number }) => void;\n /** Optional STT vocabulary hints (device/room names) to bias recognition; re-read per utterance. */\n getHints?: () => string[] | Promise<string[]>;\n /** Where spoken replies are published for the device to fetch. */\n media: MediaHost;\n /**\n * Host the device should call back on. Empty → the local address of our socket to that device,\n * which is automatically the right interface on a multi-homed host.\n */\n mediaHost?: string;\n /** Silence that ends an utterance, in ms ('' / 0 → the detector default). */\n silenceMs?: number;\n log: ioBroker.Logger;\n /** Notified on every satellite state transition (idle/listening/processing/speaking/offline). */\n onStatus?: (device: string, room: string, state: SatelliteState | 'offline') => void;\n /** Notified with the device's wake-word configuration, on connect and after every change. */\n onWakeWords?: (device: string, room: string, config: WakeWordConfig) => void;\n /** Notified once per connect with everything the device exposes besides the voice pipeline. */\n onEntities?: (device: string, room: string, entities: EsphomeEntity[]) => void;\n /** Notified whenever one of those entities changes (and once per entity right after connecting). */\n onEntityState?: (device: string, room: string, entity: EsphomeEntity) => void;\n /** True when the reply asks something back, so the device should re-open its mic without a wake word. */\n expectsFollowUp?: (answer: string, device: string) => boolean;\n /**\n * A recording that never became a request: dropped as silence, or returned empty by the speech recognition.\n * For diagnosis (the adapter stores it when asked to); absent → nothing is kept.\n */\n onDiscarded?: (capture: DiscardedCapture) => void;\n /**\n * Custom wake words to offer the devices, re-read for every configuration request. The device lists\n * them next to its own and downloads one (from the media server) only when it is selected.\n */\n externalWakeWords?: () => ExternalWakeWordOffer[];\n}\n\n/** A custom wake word as offered to the devices; the files are on the media server under `path`. */\nexport interface ExternalWakeWordOffer {\n id: string;\n phrase: string;\n languages: string[];\n /** Size of the `.tflite` in bytes. */\n size: number;\n /** SHA-256 of the `.tflite`, lower-case hex. */\n sha256: string;\n /** URL path of the JSON config, e.g. `/wakeword/hey_hannah_1a2b3c4d.json`; the model sits next to it. */\n path: string;\n}\n\n/** What a device reports about its wake words: what the firmware ships plus the custom ones we offered. */\nexport interface WakeWordConfig {\n /** Ids currently listening, e.g. `['okay_nabu']`. */\n active: string[];\n /** Every wake word the device can use, with the spoken phrase and the languages it was trained on. */\n available: { id: string; phrase: string; languages: string[] }[];\n /** How many may listen at once — 2 on the ThirdReality speaker, which has a sensitivity per slot. */\n max: number;\n}\n\n/** `VoiceAssistantTimerEvent` in api.proto. */\nexport const TIMER_EVENT = { started: 0, updated: 1, cancelled: 2, finished: 3 } as const;\n\n/** One assistant timer, as the device wants to hear about it. */\nexport interface TimerEvent {\n type: keyof typeof TIMER_EVENT;\n id: string;\n name: string;\n totalSeconds: number;\n secondsLeft: number;\n /** False once it has finished or been cancelled — the device clears its ring on that. */\n active: boolean;\n}\n\n/** The slice of `MediaServer` this module needs (kept narrow so tests can pass a stub). */\nexport interface MediaHost {\n port: number;\n publish(body: Buffer, contentType?: string, ttlMs?: number): string;\n}\n\n/**\n * All configured ESPHome satellites. Owns one connection per device and offers the same surface the\n * adapter already uses for the UDP server: `devices()` and `announce()`.\n */\nexport class EsphomeSatellites {\n private readonly connections: EsphomeConnection[] = [];\n\n constructor(private readonly opts: EsphomeSatellitesOptions) {}\n\n async start(): Promise<void> {\n for (const device of this.opts.devices) {\n if (!device.ip?.trim()) {\n continue;\n }\n const connection = new EsphomeConnection(device, this.opts);\n this.connections.push(connection);\n connection.start();\n }\n if (!this.connections.length) {\n this.opts.log.warn('ESPHome satellites enabled, but no device addresses are configured.');\n }\n return Promise.resolve();\n }\n\n async stop(): Promise<void> {\n await Promise.all(this.connections.map(c => c.stop()));\n this.connections.length = 0;\n }\n\n /** Device names of the satellites currently connected. */\n devices(): string[] {\n return this.connections.filter(c => c.online).map(c => c.deviceName);\n }\n\n /** Wake-word configuration of one connected satellite, or null if it is unknown/offline. */\n wakeWords(device: string): WakeWordConfig | null {\n return this.connections.find(c => c.online && c.deviceName === device)?.wakeWordConfig ?? null;\n }\n\n /**\n * Choose which wake words listen on one satellite. Ids come from {@link WakeWordConfig.available};\n * the device stores the selection itself, so it survives a restart of the adapter.\n */\n setWakeWords(device: string, ids: string[]): boolean {\n const target = this.connections.find(c => c.online && c.deviceName === device);\n if (!target) {\n return false;\n }\n target.setWakeWords(ids);\n return true;\n }\n\n /**\n * Offer the current custom wake words to every connected satellite again — after one was uploaded\n * or deleted. Each device answers with its new configuration (→ `onWakeWords`).\n */\n refreshWakeWords(): number {\n const online = this.connections.filter(c => c.online);\n online.forEach(c => c.requestWakeWordConfig());\n return online.length;\n }\n\n /** Everything one satellite exposes besides the voice pipeline (gain, noise suppression, …). */\n entities(device: string): EsphomeEntity[] {\n return this.connections.find(c => c.online && c.deviceName === device)?.entities.list() ?? [];\n }\n\n /**\n * Write one of those entities. Returns false when the satellite is offline, the entity is unknown\n * or read-only, or the value is not one the device accepts.\n */\n setEntity(device: string, objectId: string, value: unknown): boolean {\n const target = this.connections.find(c => c.online && c.deviceName === device);\n return target ? target.setEntity(objectId, value) : false;\n }\n\n /**\n * Mirror an assistant timer onto the satellites so they can show it on their own LED ring and ring\n * it themselves. `device` null broadcasts. Returns how many devices were told.\n */\n timerEvent(device: string | null, event: TimerEvent): number {\n const targets = this.connections.filter(c => c.online && (!device || c.deviceName === device));\n targets.forEach(c => c.sendTimerEvent(event));\n return targets.length;\n }\n\n /**\n * Play a ready PCM buffer on one satellite (by device name) or all of them. Returns how many\n * devices it was handed to.\n *\n * With `startConversation` the device opens its microphone as soon as the clip has played, without\n * a wake word — that is how a question gets its answer (see `PendingQuestions`).\n */\n async announce(device: string | null, pcm: Buffer, sampleRate: number, startConversation = false): Promise<number> {\n const targets = this.connections.filter(c => c.online && (!device || c.deviceName === device));\n if (!targets.length) {\n return 0;\n }\n await Promise.all(\n targets.map(c =>\n c\n .announce(pcm, sampleRate, startConversation)\n .catch(e => this.opts.log.warn(`announce to ${c.deviceName} failed: ${(e as Error).message}`)),\n ),\n );\n return targets.length;\n }\n}\n\n/** State of the utterance currently being captured. */\ninterface Capture {\n chunks: Buffer[];\n detector: SpeechDetector;\n conversationId: string;\n /** Wake word that opened this capture; '' when the mic was re-opened for a follow-up/question. */\n wakeWord: string;\n timer: NodeJS.Timeout;\n closed: boolean;\n}\n\n/** One satellite: socket lifecycle, handshake, and the voice pipeline. */\nclass EsphomeConnection {\n /** Device name as reported by the device; falls back to its address until the handshake answers. */\n deviceName: string;\n online = false;\n /** Last wake-word configuration the device reported; null until the handshake answers. */\n wakeWordConfig: WakeWordConfig | null = null;\n /**\n * One entry per configuration request in flight, in send order (the device answers each one, in\n * order): the ids a preceding `SetConfiguration` asked for, or null. The read-back is the only\n * failure signal the device gives.\n */\n private configChecks: (string[] | null)[] = [];\n /** Everything else the device exposes — rebuilt from scratch on every reconnect. */\n readonly entities = new EntityRegistry();\n\n private readonly host: string;\n private readonly port: number;\n private readonly room: string;\n\n private socket: net.Socket | null = null;\n private rx: Bytes = Buffer.alloc(0);\n private pingTimer: NodeJS.Timeout | null = null;\n private reconnectTimer: NodeJS.Timeout | null = null;\n private missedPings = 0;\n private stopping = false;\n private capture: Capture | null = null;\n private playbackTimer: NodeJS.Timeout | null = null;\n private readonly password: string;\n /** Configured speech threshold, 0 = {@link DEFAULT_SPEECH_LEVEL}. */\n private readonly speechLevel: number;\n\n constructor(\n device: EsphomeDevice,\n private readonly opts: EsphomeSatellitesOptions,\n ) {\n this.host = device.ip.trim();\n this.port = device.port || DEFAULT_PORT;\n this.room = (device.room || '').trim();\n this.password = device.password || '';\n this.speechLevel = speechLevelOf(device);\n this.deviceName = this.host;\n }\n\n // ── lifecycle ───────────────────────────────────────────────────────────────────────────────────\n\n start(): void {\n this.connect();\n }\n\n async stop(): Promise<void> {\n this.stopping = true;\n this.clearTimers();\n this.abortCapture();\n const socket = this.socket;\n this.socket = null;\n if (socket) {\n try {\n socket.write(encode(new pb.DisconnectRequest()));\n } catch {\n /* socket already gone — nothing to say goodbye on */\n }\n socket.destroy();\n }\n this.setOffline();\n return Promise.resolve();\n }\n\n private connect(): void {\n if (this.stopping) {\n return;\n }\n this.rx = Buffer.alloc(0);\n this.missedPings = 0;\n // The device re-announces everything after a reconnect, and the keys are not stable across\n // reboots, so starting from a clean slate is the only safe option.\n this.entities.clear();\n this.configChecks = [];\n\n const socket = net.createConnection({ host: this.host, port: this.port }, () => {\n this.opts.log.debug(`ESPHome ${this.host}:${this.port} connected`);\n this.handshake();\n });\n this.socket = socket;\n socket.setNoDelay(true);\n\n socket.on('data', data => this.onData(data));\n socket.on('error', e => {\n // A satellite that is switched off is normal, so this is not an error-level event.\n this.opts.log.debug(`ESPHome ${this.host}: ${e.message}`);\n });\n socket.on('close', () => {\n if (this.socket === socket) {\n this.socket = null;\n this.onDisconnected();\n }\n });\n }\n\n private onDisconnected(): void {\n this.clearTimers();\n this.abortCapture();\n if (this.online) {\n this.opts.log.info(`ESPHome satellite offline: ${this.deviceName}`);\n }\n this.setOffline();\n this.scheduleReconnect();\n }\n\n private scheduleReconnect(): void {\n if (this.stopping || this.reconnectTimer) {\n return;\n }\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, RECONNECT_MS);\n this.reconnectTimer.unref();\n }\n\n private clearTimers(): void {\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n this.playbackTimer = null;\n }\n }\n\n private setOffline(): void {\n if (this.online) {\n this.online = false;\n this.opts.onStatus?.(this.deviceName, this.room, 'offline');\n }\n }\n\n private status(state: SatelliteState): void {\n this.opts.onStatus?.(this.deviceName, this.room, state);\n }\n\n // ── protocol plumbing ───────────────────────────────────────────────────────────────────────────\n\n private send(message: PbMessage): void {\n const socket = this.socket;\n if (!socket || socket.destroyed) {\n return;\n }\n try {\n socket.write(encode(message));\n } catch (e) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: send failed — ${(e as Error).message}`);\n }\n }\n\n private onData(data: Buffer): void {\n this.rx = this.rx.length ? Buffer.concat([this.rx, data]) : data;\n let frames;\n try {\n ({ frames, rest: this.rx } = decode(this.rx));\n } catch (e) {\n this.opts.log.error(`ESPHome ${this.deviceName}: ${(e as Error).message}`);\n this.socket?.destroy();\n return;\n }\n for (const frame of frames) {\n if (frame.error) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: cannot parse ${frame.name}`);\n continue;\n }\n if (!frame.name || !frame.message) {\n continue; // unknown id — already skipped by its declared length\n }\n try {\n this.onMessage(frame.name, frame.message);\n } catch (e) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: ${frame.name} handler failed — ${(e as Error).message}`,\n );\n }\n }\n }\n\n private handshake(): void {\n const hello = new pb.HelloRequest();\n hello.setClientInfo('ioBroker.assistant');\n hello.setApiVersionMajor(1);\n hello.setApiVersionMinor(10);\n this.send(hello);\n\n const auth = new pb.AuthenticationRequest();\n auth.setPassword(this.password);\n this.send(auth);\n\n this.send(new pb.DeviceInfoRequest());\n // Ask for the entity announcements *before* subscribing to their states: a state message whose\n // entity we have not seen yet has nothing to attach to and is dropped.\n this.send(new pb.ListEntitiesRequest());\n this.send(new pb.SubscribeStatesRequest());\n\n const subscribe = new pb.SubscribeVoiceAssistantRequest();\n subscribe.setSubscribe(true);\n subscribe.setFlags(SUBSCRIBE_API_AUDIO);\n this.send(subscribe);\n\n this.requestWakeWordConfig();\n\n this.pingTimer = setInterval(() => {\n if (++this.missedPings > 2) {\n this.opts.log.info(`ESPHome ${this.deviceName}: no reply to keepalive — reconnecting`);\n this.socket?.destroy();\n return;\n }\n this.send(new pb.PingRequest());\n }, PING_INTERVAL_MS);\n this.pingTimer.unref();\n }\n\n private onMessage(name: string, message: unknown): void {\n switch (name) {\n case 'HelloResponse': {\n const hello = message as HelloResponse;\n this.opts.log.debug(\n `ESPHome ${this.host}: ${hello.getServerInfo()} (API ${hello.getApiVersionMajor()}.${hello.getApiVersionMinor()})`,\n );\n return;\n }\n\n case 'AuthenticationResponse':\n if ((message as AuthenticationResponse).getInvalidPassword()) {\n this.opts.log.error(\n `ESPHome ${this.host}: API password rejected — check the device's password in the settings.`,\n );\n this.socket?.destroy();\n }\n return;\n\n case 'DeviceInfoResponse': {\n const info = message as DeviceInfoResponse;\n this.deviceName = info.getName() || this.host;\n this.online = true;\n this.opts.log.info(\n `ESPHome satellite online: ${this.deviceName} (${info.getModel()}) @ ${this.host}${this.room ? `, room ${this.room}` : ''}`,\n );\n this.status('idle');\n return;\n }\n\n case 'VoiceAssistantConfigurationResponse': {\n const config = message as VoiceAssistantConfigurationResponse;\n // A custom wake word the device already downloaded is listed twice: once from its own\n // model folder and once from our offer.\n const seen = new Set<string>();\n this.wakeWordConfig = {\n active: config.getActiveWakeWordsList(),\n available: config\n .getAvailableWakeWordsList()\n .map(w => ({\n id: w.getId(),\n phrase: w.getWakeWord(),\n languages: w.getTrainedLanguagesList(),\n }))\n .filter(w => !seen.has(w.id) && !!seen.add(w.id)),\n max: config.getMaxActiveWakeWords(),\n };\n const active = this.wakeWordConfig.active.join(', ') || '(none active)';\n const available = this.wakeWordConfig.available.map(w => w.id);\n this.opts.log.info(\n `ESPHome ${this.deviceName}: wake words ${active}${available.length ? ` (available: ${available.join(', ')})` : ''}`,\n );\n const requested = this.configChecks.shift();\n const missing = requested?.filter(id => !this.wakeWordConfig?.active.includes(id)) ?? [];\n if (missing.length) {\n // The device answers a model it cannot fetch or load with silence, not an error.\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: could not activate wake word(s) ${missing.join(', ')}. ` +\n `For an uploaded wake word the device must reach ${this.mediaUrl('/')} and the model ` +\n `must be a microWakeWord .tflite.`,\n );\n }\n this.opts.onWakeWords?.(this.deviceName, this.room, this.wakeWordConfig);\n return;\n }\n\n case 'VoiceAssistantRequest':\n this.onPipelineRequest(message as VoiceAssistantRequestMsg);\n return;\n\n case 'VoiceAssistantAudio':\n this.onAudio(message as VoiceAssistantAudio);\n return;\n\n case 'VoiceAssistantAnnounceFinished':\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n this.playbackTimer = null;\n }\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: playback finished (success=${(message as VoiceAssistantAnnounceFinished).getSuccess()})`,\n );\n this.status('idle');\n return;\n\n case 'PingRequest':\n this.send(new pb.PingResponse());\n return;\n\n case 'PingResponse':\n this.missedPings = 0;\n return;\n\n case 'GetTimeRequest': {\n // The device has no RTC and syncs its clock from us — its timer ring depends on it.\n const time = new pb.GetTimeResponse();\n time.setEpochSeconds(Math.floor(Date.now() / 1000));\n this.send(time);\n return;\n }\n\n case 'DisconnectRequest':\n this.send(new pb.DisconnectResponse());\n this.socket?.destroy();\n return;\n\n case 'ListEntitiesDoneResponse': {\n const list = this.entities.list();\n if (list.length) {\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: ${list.length} entities — ${list.map(e => e.objectId).join(', ')}`,\n );\n }\n this.opts.onEntities?.(this.deviceName, this.room, list);\n return;\n }\n\n default:\n if (isListEntityMessage(name)) {\n this.entities.addFromAnnouncement(name, message);\n } else if (isEntityStateMessage(name)) {\n const entity = this.entities.applyState(name, message);\n if (entity) {\n this.opts.onEntityState?.(this.deviceName, this.room, entity);\n }\n }\n return;\n }\n }\n\n /** Write one of the device's entities. See {@link EsphomeSatellites.setEntity}. */\n setEntity(objectId: string, value: unknown): boolean {\n const message = this.entities.command(objectId, value);\n if (!message) {\n const entity = this.entities.get(objectId);\n const known = this.entities\n .list()\n .map(e => e.objectId)\n .join(', ');\n const options = entity?.options ? ` (one of: ${entity.options.join(', ')})` : '';\n this.opts.log.warn(\n entity\n ? `ESPHome ${this.deviceName}: ${JSON.stringify(value)} is not a value \"${objectId}\" accepts${options}`\n : `ESPHome ${this.deviceName}: no entity \"${objectId}\" (have: ${known})`,\n );\n return false;\n }\n this.send(message);\n this.opts.log.debug(`ESPHome ${this.deviceName}: ${objectId} := ${JSON.stringify(value)}`);\n return true;\n }\n\n /** Push one assistant timer to the device so it can show and ring it itself. */\n sendTimerEvent(event: TimerEvent): void {\n const message = new pb.VoiceAssistantTimerEventResponse();\n message.setEventType(TIMER_EVENT[event.type]);\n message.setTimerId(event.id);\n message.setName(event.name);\n message.setTotalSeconds(Math.max(0, Math.round(event.totalSeconds)));\n message.setSecondsLeft(Math.max(0, Math.round(event.secondsLeft)));\n message.setIsActive(event.active);\n this.send(message);\n }\n\n // ── voice pipeline ──────────────────────────────────────────────────────────────────────────────\n\n private onPipelineRequest(request: VoiceAssistantRequestMsg): void {\n if (!request.getStart()) {\n this.abortCapture();\n return;\n }\n if (this.capture) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: pipeline already running — ignoring wake`);\n return;\n }\n\n const phrase = request.getWakeWordPhrase();\n this.opts.log.debug(`ESPHome ${this.deviceName}: wake \"${phrase || '(follow-up)'}\"`);\n\n const response = new pb.VoiceAssistantResponse();\n response.setPort(0); // 0 = stream the audio over this socket\n response.setError(false);\n this.send(response);\n\n const timer = setTimeout(() => {\n this.opts.log.warn(`ESPHome ${this.deviceName}: no end of speech within ${PIPELINE_TIMEOUT_MS} ms`);\n void this.finishCapture();\n }, PIPELINE_TIMEOUT_MS);\n timer.unref();\n\n const silenceMs = this.opts.silenceMs || DEFAULT_SILENCE_MS;\n this.capture = {\n chunks: [],\n detector: new SpeechDetector({\n sampleRate: MIC_SAMPLE_RATE,\n silenceMs,\n longSilenceMs: Math.round(silenceMs * LONG_PAUSE_FACTOR),\n longAfterMs: LONG_REQUEST_MS,\n // Microphone gain differs wildly between these devices, so fixed RMS levels do not\n // travel: derive them from the room instead, once the wake chirp is out of the way.\n skipMs: WAKE_CHIRP_MS,\n adaptive: true,\n startTimeoutMs: phrase ? NO_SPEECH_MS : NO_SPEECH_FOLLOW_UP_MS,\n minStartLevel: this.speechLevel || DEFAULT_SPEECH_LEVEL,\n minEndLevel: Math.round((this.speechLevel || DEFAULT_SPEECH_LEVEL) * SILENCE_SHARE),\n }),\n conversationId: request.getConversationId(),\n wakeWord: phrase || '',\n timer,\n closed: false,\n };\n\n this.status('listening');\n this.event(EVENT.RUN_START);\n if (phrase) {\n this.event(EVENT.WAKE_WORD_END, { wake_word_phrase: phrase });\n }\n this.event(EVENT.STT_START);\n this.event(EVENT.STT_VAD_START);\n }\n\n private onAudio(audio: VoiceAssistantAudio): void {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n const chunk = Buffer.from(audio.getData_asU8());\n if (chunk.length) {\n capture.chunks.push(chunk);\n const end = capture.detector.push(chunk);\n if (end) {\n void this.finishCapture(end);\n return;\n }\n }\n if (audio.getEnd()) {\n void this.finishCapture();\n }\n }\n\n /** Hand a recording that never became a request to {@link EsphomeSatellitesOptions.onDiscarded}. */\n private discard(capture: Capture, pcm: Bytes, reason: DiscardReason, vad: VadReport): void {\n if (!this.opts.onDiscarded || !pcm.length) {\n return;\n }\n try {\n this.opts.onDiscarded({\n device: this.deviceName,\n room: this.room,\n wakeWord: capture.wakeWord,\n reason,\n pcm,\n sampleRate: MIC_SAMPLE_RATE,\n // The store writes its own level profile next to it.\n vad: { ...vad, levels: undefined },\n });\n } catch (e) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: keeping the recording failed — ${(e as Error).message}`);\n }\n }\n\n /** Stop the mic stream and run STT → answer → TTS for what was captured. */\n private async finishCapture(end?: SpeechEnd): Promise<void> {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n capture.closed = true;\n this.capture = null;\n clearTimeout(capture.timer);\n\n // Send this first: the device streams until it sees it.\n this.event(EVENT.STT_VAD_END);\n\n const pcm = Buffer.concat(capture.chunks);\n const seconds = pcm.length / 2 / MIC_SAMPLE_RATE;\n const vad: VadReport = { ...capture.detector.report(), end: end ?? 'stream' };\n const startedAt =\n vad.speechStartMs !== undefined ? `, speech from ${(vad.speechStartMs / 1000).toFixed(2)} s` : '';\n const endedBy = vad.end === 'speech-end' ? `ended after ${vad.silenceMs} ms of silence` : `ended by ${vad.end}`;\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: captured ${seconds.toFixed(2)} s ` +\n `(peak RMS ${vad.peak}, noise floor ${vad.noiseFloor}, speech/silence gate ${vad.startLevel}/${vad.endLevel}` +\n `${startedAt}, ${endedBy})`,\n );\n\n if (!pcm.length || !capture.detector.sawSpeech) {\n const loudest = Math.round(capture.detector.analysedPeak);\n const gate = Math.round(capture.detector.startLevel);\n if (pcm.length && loudest >= gate * QUIET_VOICE_SHARE) {\n // Not an empty room: something came close to the gate. Typically a microphone that is quieter\n // than the default threshold expects (a phone) — say where to change it.\n this.opts.log.info(\n `ESPHome ${this.deviceName}: dropped the utterance — the loudest sound (RMS ${loudest}) stayed ` +\n `below the speech threshold ${gate}. If somebody spoke, lower \"Speech threshold\" for this ` +\n `device in the ESPHome satellites table (Voice tab), e.g. to ${Math.max(\n 30,\n Math.round((loudest * 0.6) / 10) * 10,\n )}.`,\n );\n } else {\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: nothing but silence${\n end === 'no-speech' ? ' (nobody started speaking)' : ''\n } — dropping the utterance`,\n );\n }\n this.discard(capture, pcm, end === 'no-speech' ? 'no-speech' : 'silence', vad);\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n this.status('processing');\n try {\n const hints = this.opts.getHints ? await this.opts.getHints() : undefined;\n const speech = prepareForStt(pcm, MIC_SAMPLE_RATE, { startMs: vad.speechStartMs, endLevel: vad.endLevel });\n vad.stt = { fromMs: speech.fromMs, toMs: speech.toMs, gain: speech.gain };\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: speech recognition gets ${(speech.fromMs / 1000).toFixed(2)}–` +\n `${(speech.toMs / 1000).toFixed(2)} s${speech.gain > 1 ? `, amplified ×${speech.gain}` : ''}`,\n );\n const sttStart = Date.now();\n const text = (\n await this.opts.stt.transcribe(speech.pcm, MIC_SAMPLE_RATE, this.opts.language, hints)\n ).trim();\n const sttMs = Date.now() - sttStart;\n this.event(EVENT.STT_END, { text });\n if (!text) {\n // Speech was heard, so the user waits for an answer: say why none comes. Typically a word in another\n // language than the STT listens in (a German \"Ja\" to a Russian STT comes back empty).\n this.opts.log.info(\n `Voice Q (${this.deviceName}): (empty) — speech recognition returned no text for ${seconds.toFixed(1)} s of audio`,\n );\n this.discard(capture, pcm, 'empty-stt', vad);\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n this.event(EVENT.INTENT_START);\n const answer = (\n await this.opts.answer(text, {\n device: this.deviceName,\n room: this.room,\n wakeWord: capture.wakeWord,\n sttMs,\n audioMs: Math.round(seconds * 1000),\n // Numbers only, no audio: shows where a request was cut off without keeping recordings.\n vad: { ...vad, levels: levelProfile(pcm, MIC_SAMPLE_RATE) },\n })\n ).trim();\n const followUp = !!answer && this.opts.expectsFollowUp?.(answer, this.deviceName) === true;\n this.event(EVENT.INTENT_END, followUp ? { continue_conversation: '1' } : undefined);\n\n if (!answer) {\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n const ttsStart = Date.now();\n const { pcm: ttsPcm, sampleRate } = await this.opts.tts.synthesize(answer, this.opts.language);\n this.opts.onSpoken?.(this.deviceName, {\n ms: Date.now() - ttsStart,\n audioMs: (ttsPcm.length / 2 / sampleRate) * 1000,\n });\n const url = this.publish(ttsPcm, sampleRate);\n\n this.status('speaking');\n this.event(EVENT.TTS_START, { text: answer });\n this.event(EVENT.TTS_END, { url });\n this.event(EVENT.RUN_END, followUp ? { continue_conversation: '1' } : undefined);\n this.armPlaybackTimeout();\n } catch (e) {\n const reason = (e as Error).message;\n this.opts.log.error(`ESPHome ${this.deviceName}: voice pipeline failed — ${reason}`);\n // Without this the device stays ducked and its LED keeps spinning.\n this.event(EVENT.ERROR, { code: 'pipeline-failed', message: reason });\n this.event(EVENT.RUN_END);\n this.status('idle');\n }\n }\n\n /**\n * Select which wake words listen on this device. Unknown ids are dropped and the list is trimmed\n * to what the firmware allows, because the device answers a rejected list with silence rather than\n * an error — better to send something valid and report what we changed.\n *\n * The device persists the selection and echoes it back in a `VoiceAssistantConfigurationResponse`,\n * so we ask for one instead of assuming the write took.\n */\n setWakeWords(ids: string[]): void {\n const known = this.wakeWordConfig?.available.map(w => w.id) ?? [];\n const wanted = [...new Set(ids.map(i => i.trim()).filter(Boolean))];\n const unknown = wanted.filter(i => !known.includes(i));\n if (unknown.length) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: unknown wake word(s) ${unknown.join(', ')} — ignored. ` +\n `Available: ${known.join(', ') || '(none reported)'}`,\n );\n }\n let valid = wanted.filter(i => known.includes(i));\n const max = this.wakeWordConfig?.max || 0;\n if (max > 0 && valid.length > max) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: device allows ${max} active wake word(s), keeping ${valid\n .slice(0, max)\n .join(', ')}`,\n );\n valid = valid.slice(0, max);\n }\n\n const message = new pb.VoiceAssistantSetConfiguration();\n message.setActiveWakeWordsList(valid);\n this.send(message);\n this.opts.log.info(`ESPHome ${this.deviceName}: wake words set to ${valid.join(', ') || '(none)'}`);\n // Read back, so the reported state is the device's and not our guess. The device resolves a\n // custom wake word (and downloads it) while handling the SetConfiguration, so this answer\n // already tells whether that worked.\n this.requestWakeWordConfig(valid);\n }\n\n /**\n * Ask the device for its wake-word configuration. The request always carries the current custom\n * wake words: the firmware replaces its list of offered models with every request, so one sent\n * without them would make an uploaded wake word vanish from the device until the next offer.\n *\n * @param check ids a SetConfiguration just asked for — warned about if the answer lacks them\n */\n requestWakeWordConfig(check: string[] | null = null): void {\n const request = new pb.VoiceAssistantConfigurationRequest();\n const offers = this.opts.externalWakeWords?.() ?? [];\n request.setExternalWakeWordsList(\n offers.map(offer => {\n const w = new pb.VoiceAssistantExternalWakeWord();\n w.setId(offer.id);\n w.setWakeWord(offer.phrase);\n w.setTrainedLanguagesList(offer.languages);\n w.setModelType('micro');\n w.setModelSize(offer.size);\n w.setModelHash(offer.sha256);\n w.setUrl(this.mediaUrl(offer.path));\n return w;\n }),\n );\n // A device that never answers must not let this grow without bound.\n if (this.configChecks.length < 16) {\n this.configChecks.push(check);\n }\n this.send(request);\n }\n\n /** Drop a half-finished capture (device went away, or told us to stop). */\n private abortCapture(): void {\n const capture = this.capture;\n if (!capture) {\n return;\n }\n this.capture = null;\n capture.closed = true;\n clearTimeout(capture.timer);\n }\n\n // ── announcements ───────────────────────────────────────────────────────────────────────────────\n\n /**\n * Speak a ready PCM buffer on this device (timers, alarms, `tts.text`, jingles). With\n * `startConversation` the microphone opens once the clip has played, to catch the answer to a question.\n */\n announce(pcm: Buffer, sampleRate: number, startConversation = false): Promise<void> {\n if (!this.online || !pcm.length) {\n return Promise.resolve();\n }\n const request = new pb.VoiceAssistantAnnounceRequest();\n request.setMediaId(this.publish(pcm, sampleRate));\n request.setText('');\n request.setPreannounceMediaId('');\n // Needs feature flag 32 (START_CONVERSATION); on a device without it the clip still plays and\n // only the microphone stays shut, so there is nothing to guard here.\n request.setStartConversation(startConversation);\n this.status('speaking');\n this.send(request);\n this.armPlaybackTimeout();\n return Promise.resolve();\n }\n\n /**\n * Publish PCM as a WAV on the media server and build the URL the device should fetch.\n *\n * The host matters: on a multi-homed ioBroker machine the device can usually only reach one of the\n * addresses, and the local end of *this* socket is by definition one it can talk to.\n */\n private publish(pcm: Buffer, sampleRate: number): string {\n return this.mediaUrl(this.opts.media.publish(pcmToWav(pcm, sampleRate), 'audio/wav'));\n }\n\n /** Absolute URL of a path on the media server, with a host this device can reach. */\n private mediaUrl(path: string): string {\n const local = (this.socket?.localAddress || '').replace(/^::ffff:/, '');\n const host = this.opts.mediaHost?.trim() || local || '127.0.0.1';\n // An IPv6 literal has to be bracketed in a URL.\n const authority = host.includes(':') ? `[${host}]` : host;\n return `http://${authority}:${this.opts.media.port}${path}`;\n }\n\n /** Fall back to `idle` if the device never reports that it finished playing. */\n private armPlaybackTimeout(): void {\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n }\n this.playbackTimer = setTimeout(() => {\n this.playbackTimer = null;\n this.status('idle');\n }, PLAYBACK_TIMEOUT_MS);\n this.playbackTimer.unref();\n }\n\n private event(type: number, data?: Record<string, string>): void {\n const message = new pb.VoiceAssistantEventResponse();\n message.setEventType(type);\n const items: VoiceAssistantEventData[] = [];\n for (const [name, value] of Object.entries(data || {})) {\n const item = new pb.VoiceAssistantEventData();\n item.setName(name);\n item.setValue(value);\n items.push(item);\n }\n message.setDataList(items);\n this.send(message);\n }\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EntityRegistry = exports.UPDATE_COMMANDS = exports.MEDIA_COMMANDS = void 0;
4
+ exports.float32 = float32;
4
5
  exports.isListEntityMessage = isListEntityMessage;
5
6
  exports.isEntityStateMessage = isEntityStateMessage;
6
7
  /**
@@ -35,6 +36,14 @@ exports.MEDIA_COMMANDS = {
35
36
  };
36
37
  /** `UpdateCommand` in api.proto. */
37
38
  exports.UPDATE_COMMANDS = { none: 0, install: 1, check: 2 };
39
+ /**
40
+ * A `float` field of the ESPHome API as the number the device meant. The protobuf floats are 32 bit, so 0.5 from
41
+ * the device arrives as 0.5000000237487257 and a step of 0.001 as 0.0010000000474974513 — shown as is in ioBroker
42
+ * and in the settings dialog. float32 carries about 7 significant digits; everything after that is noise.
43
+ */
44
+ function float32(value) {
45
+ return Number.isFinite(value) ? Number(value.toPrecision(7)) : value;
46
+ }
38
47
  /** Clamp to the device's own range — it ignores anything outside and we would never notice. */
39
48
  function clamp(entity, value) {
40
49
  const lo = entity.min ?? Number.NEGATIVE_INFINITY;
@@ -61,7 +70,7 @@ const KINDS = [
61
70
  readState: m => {
62
71
  const state = m;
63
72
  // `missing_state` means the device has no reading yet — not that the value is 0.
64
- return state.getMissingState() ? undefined : state.getState();
73
+ return state.getMissingState() ? undefined : float32(state.getState());
65
74
  },
66
75
  command: () => null,
67
76
  },
@@ -107,11 +116,16 @@ const KINDS = [
107
116
  writable: true,
108
117
  describe: m => {
109
118
  const n = m;
110
- return { min: n.getMinValue(), max: n.getMaxValue(), step: n.getStep(), unit: n.getUnitOfMeasurement() };
119
+ return {
120
+ min: float32(n.getMinValue()),
121
+ max: float32(n.getMaxValue()),
122
+ step: float32(n.getStep()),
123
+ unit: n.getUnitOfMeasurement(),
124
+ };
111
125
  },
112
126
  readState: m => {
113
127
  const n = m;
114
- return n.getMissingState() ? undefined : n.getState();
128
+ return n.getMissingState() ? undefined : float32(n.getState());
115
129
  },
116
130
  command: (entity, value) => {
117
131
  const num = Number(value);
@@ -171,7 +185,7 @@ const KINDS = [
171
185
  latestVersion: u.getLatestVersion(),
172
186
  title: u.getTitle(),
173
187
  inProgress: u.getInProgress(),
174
- progress: u.getProgress(),
188
+ progress: float32(u.getProgress()),
175
189
  releaseUrl: u.getReleaseUrl(),
176
190
  };
177
191
  },
@@ -194,7 +208,7 @@ const KINDS = [
194
208
  describe: m => ({ supportsPause: m.getSupportsPause() }),
195
209
  readState: m => {
196
210
  const p = m;
197
- return { state: MEDIA_STATES[p.getState()] ?? 'none', volume: p.getVolume(), muted: p.getMuted() };
211
+ return { state: MEDIA_STATES[p.getState()] ?? 'none', volume: float32(p.getVolume()), muted: p.getMuted() };
198
212
  },
199
213
  command: (entity, value) => {
200
214
  const message = new esphomeProto_1.pb.MediaPlayerCommandRequest();
@@ -1 +1 @@
1
- {"version":3,"file":"esphomeEntities.js","sourceRoot":"","sources":["../../../src/lib/voice/esphomeEntities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ESPHome entity layer: everything a voice satellite exposes *besides* the voice pipeline.\n *\n * A device announces its entities once, in response to `ListEntitiesRequest`, and then pushes a state\n * message whenever one changes. Each entity kind has its own pair of messages plus a command message to\n * write it back — `SwitchCommandRequest`, `NumberCommandRequest` and so on, all addressed by the\n * `fixed32 key` from the announcement.\n *\n * This is deliberately driven by {@link KINDS} rather than by the object ids of any one product: a\n * ThirdReality speaker offers microphone gain, noise suppression and two wake-word sensitivities, a Home\n * Assistant Voice PE offers a different set, and both work without a change here. Only entity kinds a\n * voice satellite can plausibly carry are handled; lights, covers and the rest of the ESPHome zoo are\n * ignored, because this adapter drives speakers, not a general ESPHome bridge.\n */\nimport { pb, type PbMessage } from './esphomeProto';\n\n/** Entity kinds we understand. */\nexport type EntityKind =\n 'switch' | 'number' | 'select' | 'event' | 'update' | 'mediaPlayer' | 'sensor' | 'binarySensor' | 'textSensor';\n\n/** `EntityCategory` in api.proto — config/diagnostic entities are settings, not primary controls. */\nexport type EntityCategory = 'none' | 'config' | 'diagnostic';\nconst CATEGORIES: EntityCategory[] = ['none', 'config', 'diagnostic'];\n\n/** One entity as announced by the device, plus its last known value. */\nexport interface EsphomeEntity {\n kind: EntityKind;\n /** Stable id within the device (`mic_volume`), used as the ioBroker state name. */\n objectId: string;\n /** Wire handle the command messages address. */\n key: number;\n /** Human-readable name as the device labels it. */\n name: string;\n category: EntityCategory;\n icon?: string;\n /** Whether a value can be written back (false for `event`, which is read-only). */\n writable: boolean;\n /** number: range and unit. */\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n /** sensor: how many decimals the device considers meaningful. */\n decimals?: number;\n /** select: allowed values. */\n options?: string[];\n /** event: the types the device can fire. */\n eventTypes?: string[];\n /** mediaPlayer: whether it accepts a pause command. */\n supportsPause?: boolean;\n /** Last value the device reported; undefined until the first state message. */\n value?: EntityValue;\n}\n\n/** A value as it travels to and from ioBroker. `update` reports an object, the rest are scalars. */\nexport type EntityValue = boolean | number | string | UpdateInfo | MediaPlayerValue;\n\nexport interface UpdateInfo {\n currentVersion: string;\n latestVersion: string;\n title: string;\n inProgress: boolean;\n progress: number;\n releaseUrl: string;\n}\n\nexport interface MediaPlayerValue {\n state: 'none' | 'idle' | 'playing' | 'paused' | 'announcing' | 'off' | 'on';\n volume: number;\n muted: boolean;\n}\n\nconst MEDIA_STATES = ['none', 'idle', 'playing', 'paused', 'announcing', 'off', 'on'] as const;\n\n/** `MediaPlayerCommand` in api.proto, by the name we accept from ioBroker. */\nexport const MEDIA_COMMANDS: Record<string, number> = {\n play: 0,\n pause: 1,\n stop: 2,\n mute: 3,\n unmute: 4,\n toggle: 5,\n volume_up: 6,\n volume_down: 7,\n turn_on: 12,\n turn_off: 13,\n};\n\n/** `UpdateCommand` in api.proto. */\nexport const UPDATE_COMMANDS: Record<string, number> = { none: 0, install: 1, check: 2 };\n\n/**\n * Minimal structural typing over the generated announcement classes. Every `ListEntities*Response`\n * shares the first four fields, which is what makes the table-driven approach below possible.\n */\ninterface ListEntityMsg {\n getObjectId(): string;\n getKey(): number;\n getName(): string;\n getEntityCategory(): number;\n getIcon?(): string;\n getDisabledByDefault?(): boolean;\n}\n\n/** How one entity kind is announced, reported and written. */\ninterface KindSpec {\n kind: EntityKind;\n /** `ListEntities…Response` message name. */\n listMessage: string;\n /** `…StateResponse` message name (absent for `event`, which pushes `EventResponse`). */\n stateMessage: string;\n writable: boolean;\n /** Pull the kind-specific announcement fields out. */\n describe?: (msg: unknown) => Partial<EsphomeEntity>;\n /** Read the value out of a state message. */\n readState: (msg: unknown) => EntityValue | undefined;\n /** Build the command message that writes `value`, or null when the value makes no sense. */\n command: (entity: EsphomeEntity, value: unknown) => PbMessage | null;\n}\n\n/** Clamp to the device's own range — it ignores anything outside and we would never notice. */\nfunction clamp(entity: EsphomeEntity, value: number): number {\n const lo = entity.min ?? Number.NEGATIVE_INFINITY;\n const hi = entity.max ?? Number.POSITIVE_INFINITY;\n return Math.min(hi, Math.max(lo, value));\n}\n\nconst KINDS: KindSpec[] = [\n // ── read-only measurements ────────────────────────────────────────────────────────────────────\n // A voice satellite is also a box in a room: it usually reports its own temperature, whether\n // something was detected, or a status text. Read-only, so they only ever produce states.\n {\n kind: 'sensor',\n listMessage: 'ListEntitiesSensorResponse',\n stateMessage: 'SensorStateResponse',\n writable: false,\n describe: m => {\n const sensor = m as { getUnitOfMeasurement(): string; getAccuracyDecimals(): number };\n return {\n unit: sensor.getUnitOfMeasurement() || undefined,\n // The device tells us how many decimals are meaningful; more would be noise.\n decimals: sensor.getAccuracyDecimals(),\n };\n },\n readState: m => {\n const state = m as { getState(): number; getMissingState(): boolean };\n // `missing_state` means the device has no reading yet — not that the value is 0.\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'binarySensor',\n listMessage: 'ListEntitiesBinarySensorResponse',\n stateMessage: 'BinarySensorStateResponse',\n writable: false,\n readState: m => {\n const state = m as { getState(): boolean; getMissingState(): boolean };\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'textSensor',\n listMessage: 'ListEntitiesTextSensorResponse',\n stateMessage: 'TextSensorStateResponse',\n writable: false,\n readState: m => {\n const state = m as { getState(): string; getMissingState(): boolean };\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'switch',\n listMessage: 'ListEntitiesSwitchResponse',\n stateMessage: 'SwitchStateResponse',\n writable: true,\n readState: m => (m as { getState(): boolean }).getState(),\n command: (entity, value) => {\n const message = new pb.SwitchCommandRequest();\n message.setKey(entity.key);\n message.setState(value === true || value === 'true' || value === 1);\n return message;\n },\n },\n {\n kind: 'number',\n listMessage: 'ListEntitiesNumberResponse',\n stateMessage: 'NumberStateResponse',\n writable: true,\n describe: m => {\n const n = m as {\n getMinValue(): number;\n getMaxValue(): number;\n getStep(): number;\n getUnitOfMeasurement(): string;\n };\n return { min: n.getMinValue(), max: n.getMaxValue(), step: n.getStep(), unit: n.getUnitOfMeasurement() };\n },\n readState: m => {\n const n = m as { getState(): number; getMissingState(): boolean };\n return n.getMissingState() ? undefined : n.getState();\n },\n command: (entity, value) => {\n const num = Number(value);\n if (!Number.isFinite(num)) {\n return null;\n }\n const message = new pb.NumberCommandRequest();\n message.setKey(entity.key);\n message.setState(clamp(entity, num));\n return message;\n },\n },\n {\n kind: 'select',\n listMessage: 'ListEntitiesSelectResponse',\n stateMessage: 'SelectStateResponse',\n writable: true,\n describe: m => ({ options: (m as { getOptionsList(): string[] }).getOptionsList() }),\n readState: m => {\n const s = m as { getState(): string; getMissingState(): boolean };\n return s.getMissingState() ? undefined : s.getState();\n },\n command: (entity, value) => {\n // Match case-insensitively: the device only accepts one of its own options verbatim.\n const wanted = String(value).trim().toLowerCase();\n const option = entity.options?.find(o => o.toLowerCase() === wanted);\n if (!option) {\n return null;\n }\n const message = new pb.SelectCommandRequest();\n message.setKey(entity.key);\n message.setState(option);\n return message;\n },\n },\n {\n kind: 'event',\n listMessage: 'ListEntitiesEventResponse',\n stateMessage: 'EventResponse',\n writable: false,\n describe: m => ({ eventTypes: (m as { getEventTypesList(): string[] }).getEventTypesList() }),\n readState: m => (m as { getEventType(): string }).getEventType(),\n command: () => null,\n },\n {\n kind: 'update',\n listMessage: 'ListEntitiesUpdateResponse',\n stateMessage: 'UpdateStateResponse',\n writable: true,\n readState: m => {\n const u = m as {\n getMissingState(): boolean;\n getCurrentVersion(): string;\n getLatestVersion(): string;\n getTitle(): string;\n getInProgress(): boolean;\n getProgress(): number;\n getReleaseUrl(): string;\n };\n if (u.getMissingState()) {\n return undefined;\n }\n return {\n currentVersion: u.getCurrentVersion(),\n latestVersion: u.getLatestVersion(),\n title: u.getTitle(),\n inProgress: u.getInProgress(),\n progress: u.getProgress(),\n releaseUrl: u.getReleaseUrl(),\n };\n },\n command: (entity, value) => {\n const command = UPDATE_COMMANDS[String(value).trim().toLowerCase()];\n if (command === undefined || command === 0) {\n return null;\n }\n const message = new pb.UpdateCommandRequest();\n message.setKey(entity.key);\n message.setCommand(command);\n return message;\n },\n },\n {\n kind: 'mediaPlayer',\n listMessage: 'ListEntitiesMediaPlayerResponse',\n stateMessage: 'MediaPlayerStateResponse',\n writable: true,\n describe: m => ({ supportsPause: (m as { getSupportsPause(): boolean }).getSupportsPause() }),\n readState: m => {\n const p = m as { getState(): number; getVolume(): number; getMuted(): boolean };\n return { state: MEDIA_STATES[p.getState()] ?? 'none', volume: p.getVolume(), muted: p.getMuted() };\n },\n command: (entity, value) => {\n const message = new pb.MediaPlayerCommandRequest();\n message.setKey(entity.key);\n // A number is a volume (0…1), a string is one of the named commands.\n if (typeof value === 'number' || (typeof value === 'string' && /^[\\d.]+$/.test(value.trim()))) {\n message.setHasVolume(true);\n message.setVolume(Math.min(1, Math.max(0, Number(value))));\n return message;\n }\n const command = MEDIA_COMMANDS[String(value).trim().toLowerCase()];\n if (command === undefined) {\n return null;\n }\n message.setHasCommand(true);\n message.setCommand(command);\n return message;\n },\n },\n];\n\nconst BY_LIST_MESSAGE = new Map(KINDS.map(k => [k.listMessage, k]));\nconst BY_STATE_MESSAGE = new Map(KINDS.map(k => [k.stateMessage, k]));\nconst BY_KIND = new Map(KINDS.map(k => [k.kind, k]));\n\n/** True when this message announces an entity kind we handle. */\nexport function isListEntityMessage(name: string): boolean {\n return BY_LIST_MESSAGE.has(name);\n}\n\n/** True when this message carries a state for an entity kind we handle. */\nexport function isEntityStateMessage(name: string): boolean {\n return BY_STATE_MESSAGE.has(name);\n}\n\n/**\n * The entities of one device. Announcements arrive once after `ListEntitiesRequest`; state messages\n * arrive continuously and are matched back by `key`.\n */\nexport class EntityRegistry {\n private readonly byKey = new Map<number, EsphomeEntity>();\n private readonly byObjectId = new Map<string, EsphomeEntity>();\n\n /** Every entity the device announced, in announcement order. */\n list(): EsphomeEntity[] {\n return [...this.byKey.values()];\n }\n\n get(objectId: string): EsphomeEntity | undefined {\n return this.byObjectId.get(objectId);\n }\n\n /** Forget everything — the device re-announces after every reconnect. */\n clear(): void {\n this.byKey.clear();\n this.byObjectId.clear();\n }\n\n /** Take a `ListEntities…Response`. Returns the entity, or null for a kind we ignore. */\n addFromAnnouncement(name: string, message: unknown): EsphomeEntity | null {\n const spec = BY_LIST_MESSAGE.get(name);\n if (!spec) {\n return null;\n }\n const base = message as ListEntityMsg;\n const entity: EsphomeEntity = {\n kind: spec.kind,\n objectId: base.getObjectId(),\n key: base.getKey(),\n name: base.getName(),\n category: CATEGORIES[base.getEntityCategory()] ?? 'none',\n icon: base.getIcon?.() || undefined,\n writable: spec.writable,\n ...spec.describe?.(message),\n };\n this.byKey.set(entity.key, entity);\n this.byObjectId.set(entity.objectId, entity);\n return entity;\n }\n\n /**\n * Take a `…StateResponse`. Returns the updated entity, or null when it belongs to an entity the\n * device never announced — which happens legitimately, because the device pushes the current state\n * of everything as soon as we subscribe, possibly before its announcements have arrived.\n */\n applyState(name: string, message: unknown): EsphomeEntity | null {\n const spec = BY_STATE_MESSAGE.get(name);\n if (!spec) {\n return null;\n }\n const entity = this.byKey.get((message as { getKey(): number }).getKey());\n if (!entity) {\n return null;\n }\n const value = spec.readState(message);\n if (value === undefined) {\n return null;\n }\n entity.value = value;\n return entity;\n }\n\n /**\n * Build the command message that writes `value` to `objectId`, or null when the entity is unknown,\n * read-only, or the value is not one it accepts.\n */\n command(objectId: string, value: unknown): PbMessage | null {\n const entity = this.byObjectId.get(objectId);\n if (!entity?.writable) {\n return null;\n }\n return BY_KIND.get(entity.kind)?.command(entity, value) ?? null;\n }\n}\n"]}
1
+ {"version":3,"file":"esphomeEntities.js","sourceRoot":"","sources":["../../../src/lib/voice/esphomeEntities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ESPHome entity layer: everything a voice satellite exposes *besides* the voice pipeline.\n *\n * A device announces its entities once, in response to `ListEntitiesRequest`, and then pushes a state\n * message whenever one changes. Each entity kind has its own pair of messages plus a command message to\n * write it back — `SwitchCommandRequest`, `NumberCommandRequest` and so on, all addressed by the\n * `fixed32 key` from the announcement.\n *\n * This is deliberately driven by {@link KINDS} rather than by the object ids of any one product: a\n * ThirdReality speaker offers microphone gain, noise suppression and two wake-word sensitivities, a Home\n * Assistant Voice PE offers a different set, and both work without a change here. Only entity kinds a\n * voice satellite can plausibly carry are handled; lights, covers and the rest of the ESPHome zoo are\n * ignored, because this adapter drives speakers, not a general ESPHome bridge.\n */\nimport { pb, type PbMessage } from './esphomeProto';\n\n/** Entity kinds we understand. */\nexport type EntityKind =\n 'switch' | 'number' | 'select' | 'event' | 'update' | 'mediaPlayer' | 'sensor' | 'binarySensor' | 'textSensor';\n\n/** `EntityCategory` in api.proto — config/diagnostic entities are settings, not primary controls. */\nexport type EntityCategory = 'none' | 'config' | 'diagnostic';\nconst CATEGORIES: EntityCategory[] = ['none', 'config', 'diagnostic'];\n\n/** One entity as announced by the device, plus its last known value. */\nexport interface EsphomeEntity {\n kind: EntityKind;\n /** Stable id within the device (`mic_volume`), used as the ioBroker state name. */\n objectId: string;\n /** Wire handle the command messages address. */\n key: number;\n /** Human-readable name as the device labels it. */\n name: string;\n category: EntityCategory;\n icon?: string;\n /** Whether a value can be written back (false for `event`, which is read-only). */\n writable: boolean;\n /** number: range and unit. */\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n /** sensor: how many decimals the device considers meaningful. */\n decimals?: number;\n /** select: allowed values. */\n options?: string[];\n /** event: the types the device can fire. */\n eventTypes?: string[];\n /** mediaPlayer: whether it accepts a pause command. */\n supportsPause?: boolean;\n /** Last value the device reported; undefined until the first state message. */\n value?: EntityValue;\n}\n\n/** A value as it travels to and from ioBroker. `update` reports an object, the rest are scalars. */\nexport type EntityValue = boolean | number | string | UpdateInfo | MediaPlayerValue;\n\nexport interface UpdateInfo {\n currentVersion: string;\n latestVersion: string;\n title: string;\n inProgress: boolean;\n progress: number;\n releaseUrl: string;\n}\n\nexport interface MediaPlayerValue {\n state: 'none' | 'idle' | 'playing' | 'paused' | 'announcing' | 'off' | 'on';\n volume: number;\n muted: boolean;\n}\n\nconst MEDIA_STATES = ['none', 'idle', 'playing', 'paused', 'announcing', 'off', 'on'] as const;\n\n/** `MediaPlayerCommand` in api.proto, by the name we accept from ioBroker. */\nexport const MEDIA_COMMANDS: Record<string, number> = {\n play: 0,\n pause: 1,\n stop: 2,\n mute: 3,\n unmute: 4,\n toggle: 5,\n volume_up: 6,\n volume_down: 7,\n turn_on: 12,\n turn_off: 13,\n};\n\n/** `UpdateCommand` in api.proto. */\nexport const UPDATE_COMMANDS: Record<string, number> = { none: 0, install: 1, check: 2 };\n\n/**\n * Minimal structural typing over the generated announcement classes. Every `ListEntities*Response`\n * shares the first four fields, which is what makes the table-driven approach below possible.\n */\ninterface ListEntityMsg {\n getObjectId(): string;\n getKey(): number;\n getName(): string;\n getEntityCategory(): number;\n getIcon?(): string;\n getDisabledByDefault?(): boolean;\n}\n\n/** How one entity kind is announced, reported and written. */\ninterface KindSpec {\n kind: EntityKind;\n /** `ListEntities…Response` message name. */\n listMessage: string;\n /** `…StateResponse` message name (absent for `event`, which pushes `EventResponse`). */\n stateMessage: string;\n writable: boolean;\n /** Pull the kind-specific announcement fields out. */\n describe?: (msg: unknown) => Partial<EsphomeEntity>;\n /** Read the value out of a state message. */\n readState: (msg: unknown) => EntityValue | undefined;\n /** Build the command message that writes `value`, or null when the value makes no sense. */\n command: (entity: EsphomeEntity, value: unknown) => PbMessage | null;\n}\n\n/**\n * A `float` field of the ESPHome API as the number the device meant. The protobuf floats are 32 bit, so 0.5 from\n * the device arrives as 0.5000000237487257 and a step of 0.001 as 0.0010000000474974513 — shown as is in ioBroker\n * and in the settings dialog. float32 carries about 7 significant digits; everything after that is noise.\n */\nexport function float32(value: number): number {\n return Number.isFinite(value) ? Number(value.toPrecision(7)) : value;\n}\n\n/** Clamp to the device's own range — it ignores anything outside and we would never notice. */\nfunction clamp(entity: EsphomeEntity, value: number): number {\n const lo = entity.min ?? Number.NEGATIVE_INFINITY;\n const hi = entity.max ?? Number.POSITIVE_INFINITY;\n return Math.min(hi, Math.max(lo, value));\n}\n\nconst KINDS: KindSpec[] = [\n // ── read-only measurements ────────────────────────────────────────────────────────────────────\n // A voice satellite is also a box in a room: it usually reports its own temperature, whether\n // something was detected, or a status text. Read-only, so they only ever produce states.\n {\n kind: 'sensor',\n listMessage: 'ListEntitiesSensorResponse',\n stateMessage: 'SensorStateResponse',\n writable: false,\n describe: m => {\n const sensor = m as { getUnitOfMeasurement(): string; getAccuracyDecimals(): number };\n return {\n unit: sensor.getUnitOfMeasurement() || undefined,\n // The device tells us how many decimals are meaningful; more would be noise.\n decimals: sensor.getAccuracyDecimals(),\n };\n },\n readState: m => {\n const state = m as { getState(): number; getMissingState(): boolean };\n // `missing_state` means the device has no reading yet — not that the value is 0.\n return state.getMissingState() ? undefined : float32(state.getState());\n },\n command: () => null,\n },\n {\n kind: 'binarySensor',\n listMessage: 'ListEntitiesBinarySensorResponse',\n stateMessage: 'BinarySensorStateResponse',\n writable: false,\n readState: m => {\n const state = m as { getState(): boolean; getMissingState(): boolean };\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'textSensor',\n listMessage: 'ListEntitiesTextSensorResponse',\n stateMessage: 'TextSensorStateResponse',\n writable: false,\n readState: m => {\n const state = m as { getState(): string; getMissingState(): boolean };\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'switch',\n listMessage: 'ListEntitiesSwitchResponse',\n stateMessage: 'SwitchStateResponse',\n writable: true,\n readState: m => (m as { getState(): boolean }).getState(),\n command: (entity, value) => {\n const message = new pb.SwitchCommandRequest();\n message.setKey(entity.key);\n message.setState(value === true || value === 'true' || value === 1);\n return message;\n },\n },\n {\n kind: 'number',\n listMessage: 'ListEntitiesNumberResponse',\n stateMessage: 'NumberStateResponse',\n writable: true,\n describe: m => {\n const n = m as {\n getMinValue(): number;\n getMaxValue(): number;\n getStep(): number;\n getUnitOfMeasurement(): string;\n };\n return {\n min: float32(n.getMinValue()),\n max: float32(n.getMaxValue()),\n step: float32(n.getStep()),\n unit: n.getUnitOfMeasurement(),\n };\n },\n readState: m => {\n const n = m as { getState(): number; getMissingState(): boolean };\n return n.getMissingState() ? undefined : float32(n.getState());\n },\n command: (entity, value) => {\n const num = Number(value);\n if (!Number.isFinite(num)) {\n return null;\n }\n const message = new pb.NumberCommandRequest();\n message.setKey(entity.key);\n message.setState(clamp(entity, num));\n return message;\n },\n },\n {\n kind: 'select',\n listMessage: 'ListEntitiesSelectResponse',\n stateMessage: 'SelectStateResponse',\n writable: true,\n describe: m => ({ options: (m as { getOptionsList(): string[] }).getOptionsList() }),\n readState: m => {\n const s = m as { getState(): string; getMissingState(): boolean };\n return s.getMissingState() ? undefined : s.getState();\n },\n command: (entity, value) => {\n // Match case-insensitively: the device only accepts one of its own options verbatim.\n const wanted = String(value).trim().toLowerCase();\n const option = entity.options?.find(o => o.toLowerCase() === wanted);\n if (!option) {\n return null;\n }\n const message = new pb.SelectCommandRequest();\n message.setKey(entity.key);\n message.setState(option);\n return message;\n },\n },\n {\n kind: 'event',\n listMessage: 'ListEntitiesEventResponse',\n stateMessage: 'EventResponse',\n writable: false,\n describe: m => ({ eventTypes: (m as { getEventTypesList(): string[] }).getEventTypesList() }),\n readState: m => (m as { getEventType(): string }).getEventType(),\n command: () => null,\n },\n {\n kind: 'update',\n listMessage: 'ListEntitiesUpdateResponse',\n stateMessage: 'UpdateStateResponse',\n writable: true,\n readState: m => {\n const u = m as {\n getMissingState(): boolean;\n getCurrentVersion(): string;\n getLatestVersion(): string;\n getTitle(): string;\n getInProgress(): boolean;\n getProgress(): number;\n getReleaseUrl(): string;\n };\n if (u.getMissingState()) {\n return undefined;\n }\n return {\n currentVersion: u.getCurrentVersion(),\n latestVersion: u.getLatestVersion(),\n title: u.getTitle(),\n inProgress: u.getInProgress(),\n progress: float32(u.getProgress()),\n releaseUrl: u.getReleaseUrl(),\n };\n },\n command: (entity, value) => {\n const command = UPDATE_COMMANDS[String(value).trim().toLowerCase()];\n if (command === undefined || command === 0) {\n return null;\n }\n const message = new pb.UpdateCommandRequest();\n message.setKey(entity.key);\n message.setCommand(command);\n return message;\n },\n },\n {\n kind: 'mediaPlayer',\n listMessage: 'ListEntitiesMediaPlayerResponse',\n stateMessage: 'MediaPlayerStateResponse',\n writable: true,\n describe: m => ({ supportsPause: (m as { getSupportsPause(): boolean }).getSupportsPause() }),\n readState: m => {\n const p = m as { getState(): number; getVolume(): number; getMuted(): boolean };\n return { state: MEDIA_STATES[p.getState()] ?? 'none', volume: float32(p.getVolume()), muted: p.getMuted() };\n },\n command: (entity, value) => {\n const message = new pb.MediaPlayerCommandRequest();\n message.setKey(entity.key);\n // A number is a volume (0…1), a string is one of the named commands.\n if (typeof value === 'number' || (typeof value === 'string' && /^[\\d.]+$/.test(value.trim()))) {\n message.setHasVolume(true);\n message.setVolume(Math.min(1, Math.max(0, Number(value))));\n return message;\n }\n const command = MEDIA_COMMANDS[String(value).trim().toLowerCase()];\n if (command === undefined) {\n return null;\n }\n message.setHasCommand(true);\n message.setCommand(command);\n return message;\n },\n },\n];\n\nconst BY_LIST_MESSAGE = new Map(KINDS.map(k => [k.listMessage, k]));\nconst BY_STATE_MESSAGE = new Map(KINDS.map(k => [k.stateMessage, k]));\nconst BY_KIND = new Map(KINDS.map(k => [k.kind, k]));\n\n/** True when this message announces an entity kind we handle. */\nexport function isListEntityMessage(name: string): boolean {\n return BY_LIST_MESSAGE.has(name);\n}\n\n/** True when this message carries a state for an entity kind we handle. */\nexport function isEntityStateMessage(name: string): boolean {\n return BY_STATE_MESSAGE.has(name);\n}\n\n/**\n * The entities of one device. Announcements arrive once after `ListEntitiesRequest`; state messages\n * arrive continuously and are matched back by `key`.\n */\nexport class EntityRegistry {\n private readonly byKey = new Map<number, EsphomeEntity>();\n private readonly byObjectId = new Map<string, EsphomeEntity>();\n\n /** Every entity the device announced, in announcement order. */\n list(): EsphomeEntity[] {\n return [...this.byKey.values()];\n }\n\n get(objectId: string): EsphomeEntity | undefined {\n return this.byObjectId.get(objectId);\n }\n\n /** Forget everything — the device re-announces after every reconnect. */\n clear(): void {\n this.byKey.clear();\n this.byObjectId.clear();\n }\n\n /** Take a `ListEntities…Response`. Returns the entity, or null for a kind we ignore. */\n addFromAnnouncement(name: string, message: unknown): EsphomeEntity | null {\n const spec = BY_LIST_MESSAGE.get(name);\n if (!spec) {\n return null;\n }\n const base = message as ListEntityMsg;\n const entity: EsphomeEntity = {\n kind: spec.kind,\n objectId: base.getObjectId(),\n key: base.getKey(),\n name: base.getName(),\n category: CATEGORIES[base.getEntityCategory()] ?? 'none',\n icon: base.getIcon?.() || undefined,\n writable: spec.writable,\n ...spec.describe?.(message),\n };\n this.byKey.set(entity.key, entity);\n this.byObjectId.set(entity.objectId, entity);\n return entity;\n }\n\n /**\n * Take a `…StateResponse`. Returns the updated entity, or null when it belongs to an entity the\n * device never announced — which happens legitimately, because the device pushes the current state\n * of everything as soon as we subscribe, possibly before its announcements have arrived.\n */\n applyState(name: string, message: unknown): EsphomeEntity | null {\n const spec = BY_STATE_MESSAGE.get(name);\n if (!spec) {\n return null;\n }\n const entity = this.byKey.get((message as { getKey(): number }).getKey());\n if (!entity) {\n return null;\n }\n const value = spec.readState(message);\n if (value === undefined) {\n return null;\n }\n entity.value = value;\n return entity;\n }\n\n /**\n * Build the command message that writes `value` to `objectId`, or null when the entity is unknown,\n * read-only, or the value is not one it accepts.\n */\n command(objectId: string, value: unknown): PbMessage | null {\n const entity = this.byObjectId.get(objectId);\n if (!entity?.writable) {\n return null;\n }\n return BY_KIND.get(entity.kind)?.command(entity, value) ?? null;\n }\n}\n"]}