@vldmit/leap-client 1.7.9 → 1.7.10
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.
- package/lib/index.js +2 -2
- package/lib/index.js.map +3 -3
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var wt=Object.create;var N=Object.defineProperty,$t=Object.defineProperties,Dt=Object.getOwnPropertyDescriptor,Ct=Object.getOwnPropertyDescriptors,Et=Object.getOwnPropertyNames,be=Object.getOwnPropertySymbols,kt=Object.getPrototypeOf,Te=Object.prototype.hasOwnProperty,Ot=Object.prototype.propertyIsEnumerable;var Ae=(n,
|
|
2
|
-
`,i=>i!=null?s(i):t())})}};var D=(0,xe.get)("Connection"),ke=8083,Lt=8081,Nt=1e3,B=class extends j{constructor(e,t){super();this.secure=!1;this.teardown=!1;this.requests=new Map;this.subscriptions=new Map;this.onResponse=e=>{let t=e.Header.ClientTag;if(t==null){this.emit("Message",e);return}let s=this.requests.get(t);s!=null&&(clearTimeout(s.timeout),this.requests.delete(t),s.resolve(e));let i=this.subscriptions.get(t);i!=null&&i.callback(e)};this.onSocketData=e=>{this.secure?this.parse(e,this.onResponse):this.emit("Message",JSON.parse(e.toString()))};this.onSocketDisconnect=()=>{this.teardown||this.emit("Disconnect")};this.onSocketError=e=>{this.emit("Error",e)};this.host=e,this.secure=t!=null,this.certificate=l({ca:"",cert:"",key:""},t!=null?t:this.authorityCertificate())}static reachable(e){return new Promise(t=>{let s=new Be.default.Socket,i=o=>{s.destroy(),t(o)};s.setTimeout(Nt),s.once("error",()=>i(!1)),s.once("timeout",()=>i(!1)),s.connect(ke,e,()=>i(!0))})}connect(){return new Promise((e,t)=>{this.teardown=!1,this.socket=void 0;let s=this.secure?Lt:ke,i=[...this.subscriptions.values()],o=new z(this.host,s,this.certificate);D.info(`connect ${this.host}:${s} secure=${this.secure} pendingSubscriptions=${i.length}`),o.on("Data",this.onSocketData),o.on("Error",this.onSocketError),o.on("Disconnect",this.onSocketDisconnect),o.connect().then(c=>{D.info(`socket up ${this.host}:${s} protocol=${c}; physicalAccess secure=${this.secure}`),this.physicalAccess(this.secure).then(()=>{let a=[];if(this.subscriptions.clear(),this.socket=o,this.secure)for(let d of i)a.push(this.subscribe(d.url,d.listener));Promise.all(a).then(()=>{D.info(`emit Connect ${this.host} protocol=${c}`),this.emit("Connect",c),e()}).catch(d=>{D.error(`resubscribe failed ${this.host}: ${d instanceof Error?d.message:String(d)}`),t(d)})}).catch(a=>{D.error(`physicalAccess failed ${this.host}: ${a instanceof Error?a.message:String(a)}`),t(a)})}).catch(c=>{D.error(`socket connect failed ${this.host}:${s}: ${c instanceof Error?c.message:String(c)}`),t(c)})})}disconnect(){var e;this.teardown=!0,this.secure&&this.drainRequests(),this.subscriptions.clear(),(e=this.socket)==null||e.disconnect()}read(e){return new Promise((t,s)=>{let i=(0,M.v4)();if(!this.secure)return s(new Error("Only available for secure connections"));this.sendRequest(i,"ReadRequest",e).then(o=>{var p,u;let c=o.Body,a=(p=o.Header)==null?void 0:p.MessageBodyType;if(c==null)return D.info(`read ${e} -> empty body status=${((u=o.Header)==null?void 0:u.StatusCode)||"?"}`),s(new Error(`${e} no body`));if(a==="ExceptionDetail"||o.Body instanceof k){let h=o.Body instanceof k?o.Body.Message:typeof o.Body=="string"?o.Body:"Unknown exception";return D.info(`read ${e} -> exception: ${h}`),s(new Error(h))}let d=Array.isArray(c)?`array(${c.length})`:typeof c=="object"?`object(${Object.keys(c).slice(0,12).join(",")})`:typeof c;return D.debug(`read ${e} -> ok ${d}`),t(o.Body)}).catch(o=>{D.info(`read ${e} -> fail: ${o instanceof Error?o.message:String(o)}`),s(o)})})}authenticate(e){return new Promise((t,s)=>{var c;if(this.secure)return s(new Error("Only available for physical connections"));let i={Header:{RequestType:"Execute",Url:"/pair",ClientTag:"get-cert"},Body:{CommandType:"CSR",Parameters:{CSR:e.cert,DisplayName:"get_lutron_cert.py",DeviceUID:"000000000000",Role:"Admin"}}},o=setTimeout(()=>s(new Error("Authentication timeout exceeded")),5e3);this.once("Message",a=>{clearTimeout(o),t({ca:a.Body.SigningResult.RootCertificate,cert:a.Body.SigningResult.Certificate,key:Le.pki.privateKeyToPem(e.key)})}),(c=this.socket)==null||c.write(i)})}update(e,t){return new Promise((s,i)=>{let o=(0,M.v4)();if(!this.secure)return i(new Error("Only available for secure connections"));this.sendRequest(o,"UpdateRequest",e,t).then(c=>c.Body instanceof k?i(new Error(c.Body.Message)):s(c.Body)).catch(c=>i(c))})}command(e,t){return new Promise((s,i)=>{let o=(0,M.v4)();if(!this.secure)return i(new Error("Only available for secure connections"));this.sendRequest(o,"CreateRequest",e,t).then(()=>s()).catch(c=>i(c))})}subscribe(e,t){return new Promise((s,i)=>{if(!this.secure)return i(new Error("Only available for secure connections"));let o=(0,M.v4)();this.sendRequest(o,"SubscribeRequest",e).then(c=>{c.Header.StatusCode!=null&&c.Header.StatusCode.isSuccessful()&&this.subscriptions.set(o,{url:e,listener:t,callback:a=>t(a.Body)}),s()}).catch(c=>i(c))})}drainRequests(){for(let e of this.requests.keys()){let t=this.requests.get(e);clearTimeout(t.timeout)}this.requests.clear()}sendRequest(e,t,s,i){return new Promise((o,c)=>{if(this.requests.has(e)){let d=this.requests.get(e);d.reject(new Error(`tag "${e}" reused`)),clearTimeout(d.timeout),this.requests.delete(e)}let a={CommuniqueType:t,Header:{ClientTag:e,Url:s},Body:i};if(this.socket==null)return c(new Error("Connection not established"));this.socket.write(a).then(()=>{this.requests.set(e,{message:a,resolve:o,reject:c,timeout:setTimeout(()=>{this.requests.delete(e),c(new Error("Request timeout"))},15e3)})}).catch(d=>c(d))})}authorityCertificate(){let e=Oe.default.resolve(__dirname,"../authority");if(me.default.existsSync(e)){let t=me.default.readFileSync(e);if(t==null)return null;let s=Re.BSON.deserialize(t);return s.ca=Buffer.from(s.ca,"base64").toString("utf8"),s.key=Buffer.from(s.key,"base64").toString("utf8"),s.cert=Buffer.from(s.cert,"base64").toString("utf8"),s}return null}physicalAccess(e){return new Promise((t,s)=>{if(e)return t();let i=setTimeout(()=>s(new Error("Physical timeout exceeded")),6e4);this.once("Message",o=>o.Body.Status.Permissions.includes("PhysicalAccess")?(clearTimeout(i),t()):s(new Error("Unknown pairing error")))})}};var G=class{constructor(r){let e=r.addresses.find(t=>t.family===Ne.HostAddressFamily.IPv4)||r.addresses[0];this.connection=new B(e.address)}authenticate(){return T(this,null,function*(){return new Promise((r,e)=>{this.connection.connect().then(()=>{this.createCertificateRequest("mkellsy-mqtt-lutron").then(t=>{this.connection.authenticate(t).then(s=>r(s)).catch(s=>e(s))}).catch(t=>e(t))}).catch(t=>e(t))})})}createCertificateRequest(r){return new Promise((e,t)=>{_.pki.rsa.generateKeyPair({bits:2048},(s,i)=>{if(s==null){let o=_.pki.createCertificationRequest();return o.publicKey=i.publicKey,o.setSubject([{name:"commonName",value:r}]),o.sign(i.privateKey),e({key:i.privateKey,cert:_.pki.certificationRequestToPem(o)})}return t(new Error("Error generating RSA keys"))})})}};var E=g(require("fs")),Z=g(require("path")),he=g(require("os")),le=require("bson"),Me=require("js-logger");var J=(0,Me.get)("Context"),R=class{constructor(){this.context={};try{let r=this.open("pairing")||{},e=Object.keys(r);for(let s=0;s<e.length;s++)r[e[s]]=this.decrypt(r[e[s]]);this.context=r;let t=this.processors;J.info(`pairing loaded: ${t.length} processor(s) [${t.join(", ")||"none"}]`)}catch(r){J.error(`failed to load pairing from ~/.leap/pairing: ${r instanceof Error?r.message:String(r)}`),this.context={}}}get processors(){return Object.keys(this.context).filter(r=>r!=="authority")}has(r){return this.context[r]!=null}get(r){return this.context[r]}set(r,e){this.context[r.id]=l({},e),this.save("pairing",this.context)}decrypt(r){return r==null?null:(r.ca=Buffer.from(r.ca,"base64").toString("utf8"),r.key=Buffer.from(r.key,"base64").toString("utf8"),r.cert=Buffer.from(r.cert,"base64").toString("utf8"),r)}encrypt(r){return r==null?null:(r.ca=Buffer.from(r.ca).toString("base64"),r.key=Buffer.from(r.key).toString("base64"),r.cert=Buffer.from(r.cert).toString("base64"),r)}open(r){let e=Z.default.join(he.default.homedir(),".leap"),t=Z.default.join(e,r);if(E.default.existsSync(e)||E.default.mkdirSync(e),E.default.existsSync(t)){let s=E.default.readFileSync(t),i=s!=null&&typeof s.length=="number"?s.length:0;return J.info(`opened ${t} (${i} bytes)`),le.BSON.deserialize(s)}return J.warn(`missing pairing file: ${t}`),null}save(r,e){let t=Z.default.join(he.default.homedir(),".leap");E.default.existsSync(t)||E.default.mkdirSync(t);let s=l({},e),i=Object.keys(s);for(let o=0;o<i.length;o++)s[i[o]]=this.encrypt(s[i[o]]);E.default.writeFileSync(Z.default.join(t,r),le.BSON.serialize(s))}};var Ze=g(require("os")),Ue=g(require("path")),Ie=g(require("deep-equal")),Ke=g(require("flat-cache")),qe=require("js-logger"),He=require("@mkellsy/event-emitter"),V=require("tinkerhub-mdns"),fe=require("@mkellsy/hap-device"),x=(0,qe.get)("Discovery"),L=class extends He.EventEmitter{constructor(){super();this.onAvailable=e=>{let t=e.data.get("systype");if(!this.isProcessorService(e)){x.debug(`mDNS ignore service id=${e.id} systype=${String(t)}`);return}let s;try{s=this.parseProcessorAddress(e)}catch(c){x.error(`mDNS parse failed for service id=${e.id}: ${c instanceof Error?c.message:String(c)}`);return}let i=(s.addresses||[]).map(c=>c.address).join(","),o=this.isProcessorCached(s);x.info(`mDNS processor id=${s.id} type=${s.type} addresses=[${i}] cached=${o}`),o||this.emit("Discovered",s),this.cacheProcessor(s)};this.cache=Ke.default.load("discovery",Ue.default.join(Ze.default.homedir(),".leap")),this.cached=this.cache.getKey("/hosts")||[],this.cache.setKey("/hosts",this.cached),this.cache.save(!0)}search(){this.stop(),x.info(`search start: ${this.cached.length} cached host(s)`);for(let e=0;e<this.cached.length;e++){let t=this.cached[e],s=(t.addresses||[]).map(i=>i.address).join(",");x.info(`emit cached host id=${t.id} type=${t.type} addresses=[${s}]`),this.emit("Discovered",t)}this.discovery=new V.MDNSServiceDiscovery({type:"lutron",protocol:V.Protocol.TCP}),this.discovery.onAvailable(this.onAvailable),x.info("mDNS browse started for _lutron._tcp")}stop(){var e;(e=this.discovery)==null||e.destroy()}isProcessorCached(e){return this.cached.find(t=>(0,Ie.default)(t,e))!=null}isProcessorService(e){let t=e.data.get("systype");return!(t==null||typeof t=="boolean")}cacheProcessor(e){let t=this.cached.findIndex(s=>s.id===e.id);t>=0?this.cached[t]=e:this.cached.push(e),this.cache.setKey("/hosts",this.cached),this.cache.save()}parseProcessorAddress(e){var i;let t=this.discovery.serviceData.get(e.id).SRV._record.target,s=[];for(let o=0;o<((i=e.addresses)==null?void 0:i.length);o++)s.push({address:e.addresses[o].host,family:/^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i.test(e.addresses[o].host)?fe.HostAddressFamily.IPv6:fe.HostAddressFamily.IPv4});return{id:t.match(new RegExp("[Ll]utron-(?<id>\\w+)\\.local")).groups.id.toUpperCase(),type:String(e.data.get("systype")),addresses:s}}};var At=require("js-logger"),U=g(require("colors")),pe=require("@mkellsy/hap-device"),Tt=require("@mkellsy/event-emitter");var je=require("js-logger"),ze=g(require("flat-cache")),Fe=g(require("colors")),_e=g(require("os")),Ge=g(require("path")),Je=require("@mkellsy/event-emitter"),Mt=2e4,W=class extends Je.EventEmitter{constructor(e,t){super();this.discovered=new Map;this.onConnect=()=>{this.log.info("connected"),this.emit("Connect",this.connection)};this.onMessage=e=>{this.log.debug("message"),this.emit("Message",e)};this.onDisconnect=()=>{this.log.info("disconnected"),this.emit("Disconnect")};this.onError=e=>{this.logger.error(`connection error: ${(e==null?void 0:e.message)!=null?e.message:String(e)}`),this.emit("Error",e)};this.uuid=e,this.logger=(0,je.get)(`Processor ${Fe.default.dim(this.id)}`),this.connection=t,this.cache=ze.default.load(e,Ge.default.join(_e.default.homedir(),".leap")),this.connection.on("Connect",this.onConnect),this.connection.on("Message",this.onMessage),this.connection.on("Error",this.onError),this.connection.once("Disconnect",this.onDisconnect)}get id(){return this.uuid}get log(){return this.logger}get devices(){return this.discovered}connect(){return new Promise((e,t)=>{this.logger.info("connect() starting"),this.connection.connect().then(()=>{this.logger.info("connect() socket/session ready; starting heartbeat"),this.startHeartbeat(),e()}).catch(s=>{this.logger.error(`connect() failed: ${s instanceof Error?s.message:String(s)}`),t(s)})})}disconnect(){this.stopHeartbeat(),this.connection.disconnect()}clear(){for(let e of this.cache.keys())this.cache.removeKey(e);this.cache.removeCacheFile(),this.cache.save()}isValidCacheEntry(e){return e!=null&&typeof e=="object"}dropCacheKey(e){this.cache.removeKey(e),this.cache.save(!0)}ping(){return this.read("/server/1/status/ping")}read(e){return this.connection.read(e)}project(){return new Promise((e,t)=>{let s=this.cache.getKey("/project");if(s!=null)return e(s);this.connection.read("/project").then(i=>{this.cache.setKey("/project",i),this.cache.save(!0),e(i)}).catch(i=>t(i))})}system(){return new Promise((e,t)=>{let s=this.cache.getKey("/device?where=IsThisDevice:true");if(s!=null)return e(s);this.connection.read("/device?where=IsThisDevice:true").then(i=>{if(i[0]!=null)return this.cache.setKey("/device?where=IsThisDevice:true",i[0]),this.cache.save(!0),e(i[0]);t(new Error("No system device found"))}).catch(i=>t(i))})}areas(){return new Promise((e,t)=>{let s=this.cache.getKey("/area");if(s!=null)return e(s);this.connection.read("/area").then(i=>{this.cache.setKey("/area",i),this.cache.save(!0),e(i)}).catch(i=>t(i))})}timeclocks(){return new Promise((e,t)=>{let s=this.cache.getKey("/timeclock");if(s!=null)return e(s);this.connection.read("/timeclock").then(i=>{this.cache.setKey("/timeclock",i),this.cache.save(!0),e(i)}).catch(i=>t(i))})}zones(e){return new Promise((t,s)=>{let i=this.cache.getKey(`${e.href}/associatedzone`);if(i!=null)return t(i);this.connection.read(`${e.href}/associatedzone`).then(o=>{this.cache.setKey(`${e.href}/associatedzone`,o),this.cache.save(!0),t(o)}).catch(o=>s(o))})}status(e){return this.read(`${e.href}/status`)}statuses(e){return new Promise((t,s)=>{let i=[];i.push(this.read("/zone/status")),i.push(this.read("/area/status")),e==="RadioRa3Processor"&&i.push(this.read("/timeclock/status")),Promise.all(i).then(([o,c,a])=>t([...o,...c,...a||[]])).catch(o=>s(o))})}controls(e){return new Promise((t,s)=>{let i=this.cache.getKey(`${e.href}/associatedcontrolstation`);if(i!=null)return t(i);this.connection.read(`${e.href}/associatedcontrolstation`).then(o=>{this.cache.setKey(`${e.href}/associatedcontrolstation`,o),this.cache.save(!0),t(o)}).catch(o=>s(o))})}device(e){return new Promise((t,s)=>{let i=this.cache.getKey(e.href);if(this.isValidCacheEntry(i)&&typeof i.DeviceType=="string")return t(i);i!=null&&(this.logger.warn(`dropping invalid cache for ${e.href}: ${typeof i=="string"?i:typeof i}`),this.dropCacheKey(e.href)),this.connection.read(e.href).then(o=>{if(!this.isValidCacheEntry(o)||typeof o.DeviceType!="string")return s(new Error(`invalid device payload for ${e.href}`));this.cache.setKey(e.href,o),this.cache.save(!0),t(o)}).catch(o=>s(o))})}buttons(e){return new Promise((t,s)=>{let i=`${e.href}/buttongroup/expanded`,o=this.cache.getKey(i);if(this.isValidCacheEntry(o)&&Array.isArray(o))return t(o);o!=null&&(this.logger.warn(`dropping invalid cache for ${i}: ${typeof o=="string"?o:typeof o}`),this.dropCacheKey(i)),this.connection.read(`${e.href}/buttongroup/expanded`).then(c=>{if(!Array.isArray(c))return s(new Error(`invalid button group payload for ${e.href}`));this.cache.setKey(i,c),this.cache.save(!0),t(c)}).catch(c=>s(c))})}update(e,t,s){return this.connection.update(`${e.href}/${t}`,s)}command(e,t){return this.connection.command(`${e.href}/commandprocessor`,{Command:t})}subscribe(e,t){return this.connection.subscribe(e.href,t)}startHeartbeat(){this.stopHeartbeat(),this.ping().then(()=>{this.logger.debug("heartbeat ping ok")}).catch(e=>{this.logger.warn(`heartbeat ping failed: ${e instanceof Error?e.message:String(e)}`)}).finally(()=>{this.heartbeatTimeout=setTimeout(()=>this.startHeartbeat(),Mt)})}stopHeartbeat(){clearTimeout(this.heartbeatTimeout),this.heartbeatTimeout=void 0}};var S=require("@mkellsy/hap-device");var Xe=g(require("deep-equal")),et=require("@mkellsy/hap-device");var Ve=require("js-logger"),We=require("@mkellsy/hap-device"),Ye=g(require("colors")),Qe=require("@mkellsy/event-emitter"),y=class extends Qe.EventEmitter{constructor(e,t,s,i,o){super();this.initialized=!1;this.fields=new Map;this.ready=Promise.resolve();this.processor=t,this.deviceAddress=i.href,this.deviceName=i.Name,this.deviceArea=s,this.deviceAreaPath=s.Path||s.Name||"",this.deviceType=e,this.logger=(0,Ve.get)(`Device ${Ye.default.dim(this.id)}`),this.state=o}get manufacturer(){return"Lutron Electronics Co., Inc"}get id(){var e;return`LEAP-${this.processor.id}-${We.DeviceType[this.deviceType].toUpperCase()}-${(e=this.deviceAddress)==null?void 0:e.split("/")[2]}`}get name(){return this.deviceName}get room(){return this.area.Name}get areaPath(){return this.deviceAreaPath||this.room}get capabilities(){return Object.fromEntries(this.fields)}get log(){return this.logger}get address(){return{href:this.deviceAddress}}get type(){return this.deviceType}get area(){return this.deviceArea}get status(){return this.state}};var Y=class extends y{constructor(r,e,t){super(et.DeviceType.Contact,r,e,t,{state:"Open"}),this.fields.set("state",{type:"String",values:["Open","Closed"]})}update(r){let e=l({},this.status);r.CCOLevel!=null&&(this.state.state=r.CCOLevel),this.initialized&&!(0,Xe.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(r){return this.processor.command(this.address,{CommandType:"GoToCCOLevel",CCOLevelParameters:{CCOLevel:r.state}})}};var tt=g(require("deep-equal")),st=require("@mkellsy/hap-device");var Q=class extends y{constructor(r,e,t){super(st.DeviceType.Dimmer,r,e,t,{state:"Off",level:0}),this.fields.set("state",{type:"String",values:["On","Off"]}),this.fields.set("level",{type:"Integer",min:0,max:100})}update(r){let e=l({},this.status);r.Level!=null&&(this.state.state=r.Level>0?"On":"Off",this.state.level=r.Level),this.initialized&&!(0,tt.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(r){return this.processor.command(this.address,{CommandType:"GoToLevel",Parameter:[{Type:"Level",Value:r.state==="Off"?0:r.level}]})}};var rt=g(require("deep-equal")),it=require("@mkellsy/hap-device");var X=class extends y{constructor(r,e,t){super(it.DeviceType.Fan,r,e,t,{state:"Off",speed:0}),this.fields.set("state",{type:"String",values:["On","Off"]}),this.fields.set("speed",{type:"Integer",min:0,max:7})}update(r){let e=l({},this.status),t=this.parseFanSpeed(r.FanSpeed);this.state=$(l({},e),{state:t>0?"On":"Off",speed:t}),this.initialized&&!(0,rt.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(r){let e=r.state==="Off"?"Off":this.lookupFanSpeed(r.speed);return this.processor.command(this.address,{CommandType:"GoToFanSpeed",FanSpeedParameters:[{FanSpeed:e}]})}lookupFanSpeed(r){switch(r){case 1:return"Low";case 2:case 3:return"Medium";case 4:case 5:return"MediumHigh";case 6:case 7:return"High";default:return"Off"}}parseFanSpeed(r){switch(r){case"Low":return 1;case"Medium":return 3;case"MediumHigh":return 5;case"High":return 7;default:return 0}}};var ge=g(require("colors")),ot=require("@mkellsy/hap-device");var ee=class extends y{constructor(e,t,s){super(ot.DeviceType.Keypad,e,t,s,{led:{href:"/unknown"},state:"Off"});this.buttons=[];(s.DeviceType==="SunnataKeypad"||s.DeviceType==="SunnataHybridKeypad")&&(this.ready=this.processor.buttons(this.address).then(i=>T(this,null,function*(){var o;if(!Array.isArray(i))throw new Error(`button groups not an array for ${s.DeviceType} ${this.address.href}`);for(let c=0;c<i.length;c++)for(let a=0;a<((o=i[c].Buttons)==null?void 0:o.length);a++){let d=i[c].Buttons[a],u={id:`LEAP-${this.processor.id}-BUTTON-${d.href.split("/")[2]}`,index:d.ButtonNumber,name:(d.Engraving||{}).Text||d.Name,led:d.AssociatedLED};this.buttons.push(u);try{yield this.processor.subscribe({href:`${d.href}/status/event`},h=>{h.ButtonEvent.EventType==="Press"&&(this.emit("Action",this,u,"Press"),setTimeout(()=>this.emit("Action",this,u,"Release"),100))})}catch(h){this.log.error(ge.default.red(h instanceof Error?h.message:String(h)))}}this.log.info(`keypad buttons ready count=${this.buttons.length} type=${s.DeviceType} href=${this.address.href}`)})).catch(i=>{throw this.log.error(ge.default.red(i.message)),i}))}update(){this.initialized=!0}set(e){return this.processor.update(e.led,"status",{LEDStatus:{State:e.state==="On"?"On":"Off"}})}};var ve=g(require("colors")),at=require("@mkellsy/hap-device");var nt=new Map([["Pico2Button",new Map([[0,[1,!1]],[2,[2,!1]]])],["Pico2ButtonRaiseLower",new Map([[0,[1,!1]],[2,[4,!1]],[3,[2,!0]],[4,[3,!0]]])],["Pico3Button",new Map([[0,[1,!1]],[1,[2,!1]],[2,[3,!1]]])],["Pico3ButtonRaiseLower",new Map([[0,[1,!1]],[1,[3,!1]],[2,[5,!1]],[3,[2,!0]],[4,[4,!0]]])],["Pico4Button2Group",new Map([[1,[1,!1]],[2,[2,!1]],[3,[3,!1]],[4,[4,!1]]])],["Pico4ButtonScene",new Map([[1,[1,!1]],[2,[2,!1]],[3,[3,!1]],[4,[4,!1]]])],["Pico4ButtonZone",new Map([[1,[1,!1]],[2,[2,!1]],[3,[3,!1]],[4,[4,!1]]])],["PaddleSwitchPico",new Map([[0,[1,!1]],[2,[2,!1]]])],["Pico4Button",new Map([[1,[1,!1]],[2,[2,!1]],[3,[3,!1]],[4,[4,!1]]])]]);var C=require("@mkellsy/hap-device"),ct=require("@mkellsy/event-emitter");var te=class extends ct.EventEmitter{constructor(e,t,s,i){super();this.state=C.TriggerState.Idle;this.index=s,this.processor=e,this.action=t,this.options=l({doubleClickSpeed:300,clickSpeed:450,raiseLower:!1},i),this.button={id:this.id,index:this.index,name:(this.action.Engraving||{}).Text||this.action.Name},this.options.raiseLower===!0&&(this.button.raiseLower=!0)}get id(){return`LEAP-${this.processor.id}-BUTTON-${this.action.href.split("/")[2]}`}get definition(){return this.button}reset(){this.state=C.TriggerState.Idle,this.timer&&clearTimeout(this.timer),this.timer=void 0}update(e){let t=()=>{this.reset(),this.options.clickSpeed!==0&&this.emit("LongPress",this.button)},s=()=>{this.reset(),this.emit("Press",this.button)};switch(this.state){case C.TriggerState.Idle:{if(e.ButtonEvent.EventType==="Press"&&this.options.clickSpeed>0){this.state=C.TriggerState.Down,this.timer=setTimeout(t,this.options.clickSpeed);return}if(e.ButtonEvent.EventType==="Press"){this.state=C.TriggerState.Down,s();return}break}case C.TriggerState.Down:{if(e.ButtonEvent.EventType==="Release"){this.state=C.TriggerState.Up,this.timer&&clearTimeout(this.timer),this.options.doubleClickSpeed>0&&(this.timer=setTimeout(s,this.options.doubleClickSpeed+(this.options.raiseLower?250:0)));return}this.reset();break}case C.TriggerState.Up:{if(e.ButtonEvent.EventType==="Press"&&this.timer){if(this.reset(),this.options.doubleClickSpeed===0)return;this.emit("DoublePress",this.button);return}this.reset();break}}}};var se=class extends y{constructor(e,t,s){super(at.DeviceType.Remote,e,t,s,{state:"Unknown"});this.buttons=[];this.triggers=new Map;this.set=()=>Promise.resolve();this.ready=this.processor.buttons(this.address).then(i=>T(this,null,function*(){var c,a,d;if(!Array.isArray(i))throw new Error(`button groups not an array for ${s.DeviceType} ${this.address.href}`);let o=nt.get(s.DeviceType);o==null&&this.log.warn(`no ButtonMap for DeviceType=${s.DeviceType}; buttons will be unmapped`);for(let p=0;p<i.length;p++)for(let u=0;u<((c=i[p].Buttons)==null?void 0:c.length);u++){let h=i[p].Buttons[u],m=o==null?void 0:o.get(h.ButtonNumber),b=(a=m==null?void 0:m[0])!=null?a:h.ButtonNumber+1,f=(d=m==null?void 0:m[1])!=null?d:!1,A=new te(this.processor,h,b,{raiseLower:f});A.on("Press",v=>{this.emit("Action",this,v,"Press"),setTimeout(()=>this.emit("Action",this,v,"Release"),100)}),A.on("DoublePress",v=>{this.emit("Action",this,v,"DoublePress"),setTimeout(()=>this.emit("Action",this,v,"Release"),100)}),A.on("LongPress",v=>{this.emit("Action",this,v,"LongPress"),setTimeout(()=>this.emit("Action",this,v,"Release"),100)}),this.triggers.set(h.href,A),this.buttons.push(A.definition);try{yield this.processor.subscribe({href:`${h.href}/status/event`},v=>this.triggers.get(h.href).update(v))}catch(v){this.log.error(ve.default.red(v instanceof Error?v.message:String(v)))}}this.log.info(`remote buttons ready count=${this.buttons.length} type=${s.DeviceType} href=${this.address.href}`)})).catch(i=>{throw this.log.error(ve.default.red(i.message)),i})}update(){this.initialized=!0}};var dt=g(require("deep-equal")),ut=require("@mkellsy/hap-device");var re=class extends y{constructor(e,t,s){super(ut.DeviceType.Occupancy,e,t,s,{state:"Unoccupied"});this.set=()=>Promise.resolve()}update(e){let t=l({},this.status);e.OccupancyStatus!=null&&(this.state.state=e.OccupancyStatus==="Occupied"?"Occupied":"Unoccupied"),(0,dt.default)(this.state,t)||this.emit("Update",this,this.state),this.initialized=!0}};var pt=g(require("deep-equal")),mt=require("@mkellsy/hap-device");var ie=class extends y{constructor(r,e,t){super(mt.DeviceType.Shade,r,e,t,{state:"Closed",level:0,tilt:0}),this.fields.set("state",{type:"String",values:["Open","Closed"]}),this.fields.set("level",{type:"Integer",min:0,max:100}),this.fields.set("tilt",{type:"Integer",min:0,max:100})}update(r){let e=l({},this.status);r.Level!=null&&(this.state.state=r.Level>0?"Open":"Closed",this.state.level=r.Level),r.Tilt!=null&&(this.state.tilt=r.Tilt),this.initialized&&!(0,pt.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(r){let e=[];return e.push(this.processor.command(this.address,{CommandType:"GoToLevel",Parameter:[{Type:"Level",Value:r.state==="Closed"?0:r.level}]})),(r.tilt!=null||r.state==="Closed")&&e.push(this.processor.command(this.address,{CommandType:"TiltParameters",TiltParameters:{Tilt:r.state==="Closed"?0:r.tilt}})),Promise.all(e)}};var ht=g(require("deep-equal")),lt=require("@mkellsy/hap-device");var oe=class extends y{constructor(r,e,t){super(lt.DeviceType.Strip,r,e,t,{state:"Off",level:0,luminance:1800}),this.fields.set("state",{type:"String",values:["On","Off"]}),this.fields.set("level",{type:"Integer",min:0,max:100}),this.fields.set("luminance",{type:"Integer",min:1800,max:3e3})}update(r){let e=l({},this.status);r.Level!=null&&(this.state.state=r.Level>0?"On":"Off",this.state.level=r.Level),r.ColorTuningStatus!=null&&r.ColorTuningStatus.WhiteTuningLevel!=null&&r.ColorTuningStatus.WhiteTuningLevel.Kelvin!=null&&(this.state.luminance=r.ColorTuningStatus.WhiteTuningLevel.Kelvin),this.initialized&&!(0,ht.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(r){return r.state==="Off"?this.processor.command(this.address,{CommandType:"GoToWhiteTuningLevel",WhiteTuningLevelParameters:{Level:0}}):this.processor.command(this.address,{CommandType:"GoToWhiteTuningLevel",WhiteTuningLevelParameters:{Level:r.level,WhiteTuningLevel:{Kelvin:r.luminance}}})}};var ft=g(require("deep-equal")),gt=require("@mkellsy/hap-device");var ne=class extends y{constructor(r,e,t){super(gt.DeviceType.Switch,r,e,t,{state:"Off"}),this.fields.set("state",{type:"String",values:["On","Off"]})}update(r){let e=l({},this.status);this.state=$(l({},e),{state:r.SwitchedLevel||"Unknown"}),this.initialized&&!(0,ft.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(r){return this.processor.command(this.address,{CommandType:"GoToLevel",Parameter:[{Type:"Level",Value:r.state==="On"?100:0}]})}};var vt=g(require("deep-equal")),yt=require("@mkellsy/hap-device");var ce=class extends y{constructor(e,t,s){super(yt.DeviceType.Timeclock,e,t,s,{state:"Off"});this.set=()=>Promise.resolve();this.fields.set("state",{type:"String",values:["On","Off"]})}update(e){let t=l({},this.status);this.state=$(l({},t),{state:e.EnabledState==="Enabled"?"On":"Off"}),this.initialized&&!(0,vt.default)(this.state,t)&&this.emit("Update",this,this.state),this.initialized=!0}};var St=require("@mkellsy/hap-device");var ae=class extends y{constructor(e,t,s){super(St.DeviceType.Unknown,e,t,s,{state:"Unknown"});this.set=()=>Promise.resolve()}update(){}};function de(n,r,e){var s;switch(ye(e.ControlType||e.DeviceType)){case S.DeviceType.Contact:return new Y(n,r,e);case S.DeviceType.Dimmer:return new Q(n,r,e);case S.DeviceType.Fan:return new X(n,r,e);case S.DeviceType.Keypad:return new ee(n,r,e);case S.DeviceType.Occupancy:return new re(n,r,{href:`/occupancy/${(s=r.href)==null?void 0:s.split("/")[2]}`,Name:e.Name});case S.DeviceType.Remote:return new se(n,r,e);case S.DeviceType.Shade:return new ie(n,r,e);case S.DeviceType.Strip:return new oe(n,r,e);case S.DeviceType.Switch:return new ne(n,r,e);case S.DeviceType.Timeclock:return new ce(n,r,e);default:return new ae(n,r,e)}}function ye(n){switch(n){case"Switched":case"PowPakSwitch":case"OutdoorPlugInSwitch":return S.DeviceType.Switch;case"Dimmed":case"PlugInDimmer":return S.DeviceType.Dimmer;case"Shade":return S.DeviceType.Shade;case"Timeclock":return S.DeviceType.Timeclock;case"WhiteTune":return S.DeviceType.Strip;case"FanSpeed":return S.DeviceType.Fan;case"Pico2Button":case"Pico2ButtonRaiseLower":case"Pico3Button":case"Pico4Button":case"Pico3ButtonRaiseLower":case"Pico4Button2Group":case"Pico4ButtonScene":case"Pico4ButtonZone":case"PaddleSwitchPico":return S.DeviceType.Remote;case"SunnataDimmer":case"SunnataSwitch":case"SunnataKeypad":case"SunnataHybridKeypad":return S.DeviceType.Keypad;case"RPSCeilingMountedOccupancySensor":return S.DeviceType.Occupancy;case"CCO":return S.DeviceType.Contact;default:return S.DeviceType.Unknown}}function Pt(n){if(n==null||typeof n!="object"||n.AddressedState!=="Addressed")return!1;switch(n.DeviceType){case"Pico2Button":case"Pico2ButtonRaiseLower":case"Pico3Button":case"Pico4Button":case"Pico3ButtonRaiseLower":case"Pico4Button2Group":case"Pico4ButtonScene":case"Pico4ButtonZone":case"PaddleSwitchPico":return!0;case"SunnataKeypad":case"SunnataHybridKeypad":return!0;case"RPSCeilingMountedOccupancySensor":return!0;default:return!1}}var w={info:n=>console.log(`[LEAP][MetaProbe] ${n}`),warn:n=>console.warn(`[LEAP][MetaProbe] ${n}`)},Zt=["/area","/zone","/device","/controlstation","/occupancygroup","/group","/areagroup","/zonegroup","/loadgroup","/devicegroup","/virtualbutton","/preset","/area/status","/zone/status","/server/1/status/ping","/project","/clientsetting","/link","/button","/buttongroup","/timeclock"];function Ut(n){if(n==null)return"null";if(Array.isArray(n)){let r=n[0],e=r!=null&&typeof r=="object"?Object.keys(r).join(","):"";return`array(len=${n.length}${e?` sampleKeys=${e}`:""})`}if(typeof n=="object"){let e=Object.keys(n);return`object(keys=${e.slice(0,20).join(",")}${e.length>20?",...":""})`}return typeof n=="string"?`string(len=${n.length} preview=${JSON.stringify(n.slice(0,200))})`:`${typeof n}:${String(n).slice(0,120)}`}function Se(n,r){var o;if(r==null)return"";let e=new Map(n.map(c=>[c.href,c])),t=[],s=r,i=new Set;for(;s!=null&&!i.has(s);){i.add(s);let c=e.get(s);if(c==null)break;t.unshift(c.Name),s=(o=c.Parent)==null?void 0:o.href}return t.join(" / ")}function bt(n,r){return T(this,null,function*(){var e,t,s,i,o,c,a,d;w.info(`===== LEAP metadata probe start processor=${n.id} areas=${r.length} =====`);for(let p of r)w.info(`AREA href=${p.href} name=${JSON.stringify(p.Name)} leaf=${p.IsLeaf} parent=${((e=p.Parent)==null?void 0:e.href)||"none"} path=${JSON.stringify(Se(r,p.href))} keys=${Object.keys(p).join(",")}`);for(let p of r.filter(u=>u.IsLeaf)){let u=Se(r,p.href);try{let h=yield n.zones(p);for(let m of h)w.info(`ZONE area=${JSON.stringify(u)} name=${JSON.stringify(m.Name)} href=${m.href} control=${m.ControlType} category=${((t=m.Category)==null?void 0:t.Type)||""} isLight=${(i=(s=m.Category)==null?void 0:s.IsLight)!=null?i:"?"} assocArea=${((o=m.AssociatedArea)==null?void 0:o.href)||""} device=${((c=m.Device)==null?void 0:c.href)||""} keys=${Object.keys(m).join(",")}`)}catch(h){w.warn(`ZONE list failed for ${p.href}: ${h instanceof Error?h.message:String(h)}`)}try{let h=yield n.controls(p);for(let m of h){let b=(m.AssociatedGangedDevices||[]).map(f=>{var A,v;return`${((A=f.Device)==null?void 0:A.DeviceType)||"?"}@${((v=f.Device)==null?void 0:v.href)||"?"}`}).join(",");w.info(`STATION area=${JSON.stringify(u)} name=${JSON.stringify(m.Name)} href=${m.href} ganged=[${b}] keys=${Object.keys(m).join(",")}`)}}catch(h){w.warn(`STATION list failed for ${p.href}: ${h instanceof Error?h.message:String(h)}`)}}for(let p of Zt)try{let u=yield n.read(p);w.info(`PROBE ok url=${p} body=${Ut(u)}`),Array.isArray(u)&&u.length>0&&u.length<=5?w.info(`PROBE sample url=${p} ${JSON.stringify(u).slice(0,1500)}`):Array.isArray(u)&&u.length>5?w.info(`PROBE sample url=${p} first=${JSON.stringify(u[0]).slice(0,800)}`):u!=null&&typeof u=="object"&&!Array.isArray(u)&&w.info(`PROBE sample url=${p} ${JSON.stringify(u).slice(0,1200)}`)}catch(u){w.info(`PROBE fail url=${p}: ${u instanceof Error?u.message:String(u)}`)}for(let p of n.devices.values()){let u=p.area,h=p,m=typeof h.areaPath=="string"?h.areaPath:Se(r,u==null?void 0:u.href);w.info(`DEVICE type=${p.type} name=${JSON.stringify(p.name)} room=${JSON.stringify(p.room)} areaPath=${JSON.stringify(m)} id=${p.id} href=${((a=p.address)==null?void 0:a.href)||""} areaHref=${(u==null?void 0:u.href)||""} areaParent=${((d=u==null?void 0:u.Parent)==null?void 0:d.href)||""}`)}w.info(`===== LEAP metadata probe done processor=${n.id} =====`)})}var P=(0,At.get)("Client"),ue=5e3,I=class extends Tt.EventEmitter{constructor(e){var s,i;super(1/0);this.discovered=new Map;this.areaPaths=new Map;this.onDiscovered=e=>{this.discovered.delete(e.id);let t=(e.addresses||[]).map(o=>`${o.family}:${o.address}`).join(", ");if(P.info(`Discovered host id=${e.id} type=${e.type} addresses=[${t}]`),!this.context.has(e.id)){P.warn(`skipping host id=${e.id}: not in pairing (paired=[${this.context.processors.join(", ")||"none"}])`);return}let s=e.addresses.find(o=>o.family===pe.HostAddressFamily.IPv4)||e.addresses[0];if(s==null){P.error(`host id=${e.id} has no addresses`);return}P.info(`connecting to processor id=${e.id} at ${s.address}`);let i=new W(e.id,new B(s.address,this.context.get(e.id)));this.discovered.set(e.id,i),i.log.info(`Processor ${U.default.green(s.address)}`),i.on("Disconnect",()=>{P.warn(`processor ${e.id} disconnected; retry in ${ue}ms`),setTimeout(()=>this.onDiscovered(e),ue)}).on("Connect",()=>{P.info(`processor ${e.id} Connect event \u2014 loading system/project/areas (refresh=${this.refresh})`),this.refresh&&i.clear(),Promise.all([i.system(),i.project(),i.areas()]).then(([o,c,a])=>{var h;let d=o==null?void 0:o.FirmwareImage.Firmware.DisplayName,p=o==null?void 0:o.DeviceType,u=[];this.rebuildAreaPaths(a),P.info(`processor ${e.id} system loaded firmware=${d||"Unknown"} type=${p} areas=${(h=a==null?void 0:a.length)!=null?h:0}`),i.log.info(`Firmware ${U.default.green(d||"Unknown")}`),i.log.info(c.ProductType),i.subscribe({href:"/zone/status"},m=>{for(let b of m){let f=i.devices.get(b.Zone.href);f!=null&&f.update(b)}}).then(()=>P.info(`processor ${e.id} subscribed /zone/status`)).catch(m=>this.onProcessorError(e,m)),i.subscribe({href:"/area/status"},m=>{var b;for(let f of m){let A=i.devices.get(`/occupancy/${(b=f.href)==null?void 0:b.split("/")[2]}`);A!=null&&f.OccupancyStatus!=null&&A.update(f)}}).then(()=>P.info(`processor ${e.id} subscribed /area/status`)).catch(m=>this.onProcessorError(e,m)),p==="RadioRa3Processor"&&i.subscribe({href:"/timeclock/status"},m=>{for(let b of m){let f=i.devices.get(b.Timeclock.href);f!=null&&f.update(b)}}).then(()=>P.info(`processor ${e.id} subscribed /timeclock/status`)).catch(m=>this.onProcessorError(e,m));for(let m of a)u.push(new Promise(b=>{this.discoverZones(i,m).then(()=>b())})),u.push(new Promise(b=>{this.discoverControls(i,m).then(()=>b())}));p==="RadioRa3Processor"&&u.push(new Promise(m=>{this.discoverTimeclocks(i).then(()=>m())})),P.info(`processor ${e.id} discovering devices (${u.length} wait tasks)`),Promise.all(u).then(()=>T(this,null,function*(){let m=[...i.devices.values()];yield Promise.all(m.map(f=>T(this,null,function*(){let A=f.ready;if(A!=null)try{yield A}catch(v){P.warn(`device ${f.name} button init failed: ${v instanceof Error?v.message:String(v)}`)}})));let b=m.length;P.info(`processor ${e.id} discovery complete: ${b} devices; loading statuses`),i.statuses(p).then(f=>{for(let A of f){let v=i.devices.get((A.Zone||{}).href||""),Pe=i.devices.get(`/occupancy/${(A.href||"").split("/")[2]}`);v!=null&&v.update(A),Pe!=null&&A.OccupancyStatus!=null&&Pe.update(A)}P.info(`processor ${e.id} applied ${f.length} status record(s)`)}).catch(f=>{P.error(`processor ${e.id} statuses failed: ${f instanceof Error?f.message:String(f)}`)}),i.log.info(`discovered ${U.default.green(b.toString())} devices`),bt(i,a).catch(f=>{P.error(`metadata probe failed: ${f instanceof Error?f.message:String(f)}`)}),P.info(`processor ${e.id} emitting Available with ${b} devices`),this.emit("Available",m)}))}).catch(o=>this.onProcessorError(e,o))}).on("Error",o=>this.onProcessorError(e,o)),i.connect().catch(o=>this.onProcessorError(e,o))};this.onDeviceUpdate=(e,t)=>{this.emit("Update",e,t)};this.onDeviceAction=(e,t,s)=>{this.emit("Action",e,t,s)};this.onProcessorError=(e,t)=>{let s=(t==null?void 0:t.message)!=null?t.message:String(t);if(s.match(/ENOTFOUND|ENETUNREACH|EHOSTUNREACH|ECONNRESET|EPIPE|ECONNREFUSED|ETIMEDOUT/g)!=null){P.warn(`processor ${e.id} network error: ${s}; retry in ${ue}ms`),setTimeout(()=>this.onDiscovered(e),ue);return}P.error(U.default.red(`processor ${e.id} error: ${s}`))};this.context=new R,this.discovery=new L,this.refresh=e===!0;let t=(i=(s=this.context)==null?void 0:s.processors)!=null?i:[];P.info(`Client start refresh=${this.refresh} paired=[${t.join(", ")||"none"}]`),this.discovery.on("Discovered",this.onDiscovered).search()}get processors(){return[...this.discovered.keys()]}processor(e){return this.discovered.get(e)}close(){this.discovery.stop();for(let e of this.discovered.values())e.disconnect();this.discovered.clear()}rebuildAreaPaths(e){var s;let t=new Map(e.map(i=>[i.href,i]));this.areaPaths.clear();for(let i of e){let o=[],c=i.href,a=new Set;for(;c!=null&&!a.has(c);){a.add(c);let d=t.get(c);if(d==null)break;o.unshift(d.Name),c=(s=d.Parent)==null?void 0:s.href}this.areaPaths.set(i.href,o.join(" / ")||i.Name)}}withAreaPath(e){return $(l({},e),{Path:this.areaPaths.get(e.href)||e.Name})}discoverZones(e,t){return new Promise(s=>{if(!t.IsLeaf)return s();let i=this.withAreaPath(t);e.zones(t).then(o=>{for(let c of o){let a=de(e,i,c).on("Update",this.onDeviceUpdate).on("Action",this.onDeviceAction);e.devices.set(c.href,a)}s()}).catch(()=>s())})}discoverTimeclocks(e){return new Promise(t=>{e.timeclocks().then(s=>{for(let i of s){let o=de(e,{href:i.href,Name:i.Name,ControlType:"Timeclock",Parent:i.Parent,IsLeaf:!0,AssociatedZones:[],AssociatedControlStations:[],AssociatedOccupancyGroups:[],Path:i.Name},$(l({},i),{ControlType:"Timeclock"})).on("Update",this.onDeviceUpdate);e.devices.set(i.href,o)}t()}).catch(()=>t())})}discoverControls(e,t){return new Promise(s=>{if(!t.IsLeaf)return s();let i=this.withAreaPath(t);e.controls(t).then(o=>T(this,null,function*(){if(o==null||o.length===0)return s();yield Promise.all(o.map(c=>T(this,null,function*(){var d;let a=yield this.discoverPositions(e,c);for(let p of a){let h=ye(p.DeviceType)===pe.DeviceType.Occupancy?`/occupancy/${(d=t.href)==null?void 0:d.split("/")[2]}`:p.href,m=de(e,i,$(l({},p),{Name:`${t.Name} ${c.Name} ${p.Name}`})).on("Update",this.onDeviceUpdate).on("Action",this.onDeviceAction);e.devices.set(h,m)}}))),s()})).catch(()=>s())})}discoverPositions(e,t){return new Promise(s=>{if(t.AssociatedGangedDevices==null)return s([]);let i=[];for(let o of t.AssociatedGangedDevices)i.push(e.device(o.Device));Promise.allSettled(i).then(o=>{let c=[];for(let a of o){if(a.status==="rejected"){P.warn(`control station ${t.Name||t.href}: device fetch failed: ${a.reason instanceof Error?a.reason.message:String(a.reason)}`);continue}if(Pt(a.value))c.push(a.value);else{let d=a.value;P.info(`skipping non-addressable ${t.Name||"?"} type=${(d==null?void 0:d.DeviceType)||typeof d} state=${(d==null?void 0:d.AddressedState)||"?"} href=${(d==null?void 0:d.href)||"?"}`)}}s(c)}).catch(()=>s([]))})}};function It(n){return new I(n)}function Kt(){return new Promise((n,r)=>{let e=new L,t=new R;e.on("Discovered",s=>{t.get(s.id)==null&&new G(s).authenticate().then(o=>{t.set(s,o),n()}).catch(o=>r(o)).finally(()=>e.stop())}),e.search()})}0&&(module.exports={Client,connect,pair});
|
|
1
|
+
var wt=Object.create;var N=Object.defineProperty,$t=Object.defineProperties,Dt=Object.getOwnPropertyDescriptor,Ct=Object.getOwnPropertyDescriptors,Et=Object.getOwnPropertyNames,be=Object.getOwnPropertySymbols,kt=Object.getPrototypeOf,Te=Object.prototype.hasOwnProperty,Ot=Object.prototype.propertyIsEnumerable;var Ae=(n,i,e)=>i in n?N(n,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[i]=e,l=(n,i)=>{for(var e in i||(i={}))Te.call(i,e)&&Ae(n,e,i[e]);if(be)for(var e of be(i))Ot.call(i,e)&&Ae(n,e,i[e]);return n},$=(n,i)=>$t(n,Ct(i));var Bt=(n,i)=>{for(var e in i)N(n,e,{get:i[e],enumerable:!0})},we=(n,i,e,t)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of Et(i))!Te.call(n,s)&&s!==e&&N(n,s,{get:()=>i[s],enumerable:!(t=Dt(i,s))||t.enumerable});return n};var v=(n,i,e)=>(e=n!=null?wt(kt(n)):{},we(i||!n||!n.__esModule?N(e,"default",{value:n,enumerable:!0}):e,n)),Rt=n=>we(N({},"__esModule",{value:!0}),n);var T=(n,i,e)=>new Promise((t,s)=>{var r=a=>{try{c(e.next(a))}catch(d){s(d)}},o=a=>{try{c(e.throw(a))}catch(d){s(d)}},c=a=>a.done?t(a.value):Promise.resolve(a.value).then(r,o);c((e=e.apply(n,i)).next())});var qt={};Bt(qt,{Client:()=>I,connect:()=>It,pair:()=>Kt});module.exports=Rt(qt);var _=require("node-forge"),Ne=require("@mkellsy/hap-device");var me=v(require("fs")),Oe=v(require("path")),Be=v(require("net")),Re=require("bson"),xe=require("js-logger"),Le=require("node-forge"),M=require("uuid");var $e=require("@mkellsy/event-emitter");var K=class{};var q=class n{constructor(i,e){this.message=i,this.code=e}static fromString(i){let e=i==null?void 0:i.split(" ",2);if(e==null||e.length===1)return new n(i);let t=parseInt(e[0],10);return Number.isNaN(t)?new n(i):new n(e[1],t)}isSuccessful(){return this.code!==void 0&&this.code>=200&&this.code<300}};var H=class n{constructor(){this.Header=new K}static parse(i){let e=JSON.parse(i),t=e.Header.StatusCode==null?void 0:q.fromString(e.Header.StatusCode),s=Object.assign({},e.Header,{StatusCode:t,MessageBodyType:e.Header.MessageBodyType});if(s.MessageBodyType==null)return Object.assign(new n,{Header:s});let r=Object.keys(e.Body||{})[0],o=r!=null&&e.Body[r]||void 0;return Object.assign(new n,e,{Header:s,Body:o})}};var j=class extends $e.EventEmitter{constructor(){super(...arguments);this.buffer=""}parse(e,t){let s=this.buffer+e.toString(),r=s.split(/\r?\n/);if(r.length-1===0){this.buffer=s;return}this.buffer=r[r.length-1]||"";for(let o of r.slice(0,r.length-1))t(H.parse(o))}};var k=class{constructor(){this.Message=""}};var Ce=require("@mkellsy/event-emitter"),Ee=require("js-logger"),F=require("tls"),O=(0,Ee.get)("Socket"),xt=1e4,De=3e4,z=class extends Ce.EventEmitter{constructor(e,t,s){super();this.onSocketData=e=>{this.emit("Data",e)};this.onSocketTimeout=()=>{O.warn(`TLS inactivity timeout ${this.host}:${this.port} (${De}ms)`),this.emit("Error",new Error("connect ETIMEDOUT"))};this.onSocketClose=()=>{O.info(`TLS close ${this.host}:${this.port}`),this.emit("Disconnect")};this.onSocketError=e=>{O.error(`TLS error ${this.host}:${this.port}: ${e.message}`),this.emit("Error",e)};this.host=e,this.port=t,this.certificate=s}connect(){return new Promise((e,t)=>{O.info(`TLS connect ${this.host}:${this.port}`);let s=(0,F.connect)(this.port,this.host,{secureContext:(0,F.createSecureContext)(this.certificate),secureProtocol:"TLS_method",rejectUnauthorized:!1});s.once("secureConnect",()=>{this.connection=s,this.connection.off("error",t),this.connection.on("timeout",this.onSocketTimeout),this.connection.on("error",this.onSocketError),this.connection.on("close",this.onSocketClose),this.connection.on("data",this.onSocketData),this.connection.setKeepAlive(!0,xt),this.connection.setTimeout(De);let r=this.connection.getProtocol()||"Unknown";O.info(`TLS secureConnect ${this.host}:${this.port} protocol=${r}`),e(r)}),s.once("error",r=>{O.error(`TLS connect error ${this.host}:${this.port}: ${r.message}`),t(r)})})}disconnect(){var e,t;(e=this.connection)==null||e.end(),(t=this.connection)==null||t.destroy()}write(e){return new Promise((t,s)=>{if(this.connection==null)return s(new Error("connection not established"));this.connection.write(`${JSON.stringify(e)}
|
|
2
|
+
`,r=>r!=null?s(r):t())})}};var D=(0,xe.get)("Connection"),ke=8083,Lt=8081,Nt=1e3,B=class extends j{constructor(e,t){super();this.secure=!1;this.teardown=!1;this.requests=new Map;this.subscriptions=new Map;this.onResponse=e=>{let t=e.Header.ClientTag;if(t==null){this.emit("Message",e);return}let s=this.requests.get(t);s!=null&&(clearTimeout(s.timeout),this.requests.delete(t),s.resolve(e));let r=this.subscriptions.get(t);r!=null&&r.callback(e)};this.onSocketData=e=>{this.secure?this.parse(e,this.onResponse):this.emit("Message",JSON.parse(e.toString()))};this.onSocketDisconnect=()=>{this.teardown||this.emit("Disconnect")};this.onSocketError=e=>{this.emit("Error",e)};this.host=e,this.secure=t!=null,this.certificate=l({ca:"",cert:"",key:""},t!=null?t:this.authorityCertificate())}static reachable(e){return new Promise(t=>{let s=new Be.default.Socket,r=o=>{s.destroy(),t(o)};s.setTimeout(Nt),s.once("error",()=>r(!1)),s.once("timeout",()=>r(!1)),s.connect(ke,e,()=>r(!0))})}connect(){return new Promise((e,t)=>{this.teardown=!1,this.socket=void 0;let s=this.secure?Lt:ke,r=[...this.subscriptions.values()],o=new z(this.host,s,this.certificate);D.info(`connect ${this.host}:${s} secure=${this.secure} pendingSubscriptions=${r.length}`),o.on("Data",this.onSocketData),o.on("Error",this.onSocketError),o.on("Disconnect",this.onSocketDisconnect),o.connect().then(c=>{D.info(`socket up ${this.host}:${s} protocol=${c}; physicalAccess secure=${this.secure}`),this.physicalAccess(this.secure).then(()=>{let a=[];if(this.subscriptions.clear(),this.socket=o,this.secure)for(let d of r)a.push(this.subscribe(d.url,d.listener));Promise.all(a).then(()=>{D.info(`emit Connect ${this.host} protocol=${c}`),this.emit("Connect",c),e()}).catch(d=>{D.error(`resubscribe failed ${this.host}: ${d instanceof Error?d.message:String(d)}`),t(d)})}).catch(a=>{D.error(`physicalAccess failed ${this.host}: ${a instanceof Error?a.message:String(a)}`),t(a)})}).catch(c=>{D.error(`socket connect failed ${this.host}:${s}: ${c instanceof Error?c.message:String(c)}`),t(c)})})}disconnect(){var e;this.teardown=!0,this.secure&&this.drainRequests(),this.subscriptions.clear(),(e=this.socket)==null||e.disconnect()}read(e){return new Promise((t,s)=>{let r=(0,M.v4)();if(!this.secure)return s(new Error("Only available for secure connections"));this.sendRequest(r,"ReadRequest",e).then(o=>{var p,u;let c=o.Body,a=(p=o.Header)==null?void 0:p.MessageBodyType;if(c==null)return D.info(`read ${e} -> empty body status=${((u=o.Header)==null?void 0:u.StatusCode)||"?"}`),s(new Error(`${e} no body`));if(a==="ExceptionDetail"||o.Body instanceof k){let h=o.Body instanceof k?o.Body.Message:typeof o.Body=="string"?o.Body:"Unknown exception";return D.info(`read ${e} -> exception: ${h}`),s(new Error(h))}let d=Array.isArray(c)?`array(${c.length})`:typeof c=="object"?`object(${Object.keys(c).slice(0,12).join(",")})`:typeof c;return D.debug(`read ${e} -> ok ${d}`),t(o.Body)}).catch(o=>{D.info(`read ${e} -> fail: ${o instanceof Error?o.message:String(o)}`),s(o)})})}authenticate(e){return new Promise((t,s)=>{var c;if(this.secure)return s(new Error("Only available for physical connections"));let r={Header:{RequestType:"Execute",Url:"/pair",ClientTag:"get-cert"},Body:{CommandType:"CSR",Parameters:{CSR:e.cert,DisplayName:"get_lutron_cert.py",DeviceUID:"000000000000",Role:"Admin"}}},o=setTimeout(()=>s(new Error("Authentication timeout exceeded")),5e3);this.once("Message",a=>{clearTimeout(o),t({ca:a.Body.SigningResult.RootCertificate,cert:a.Body.SigningResult.Certificate,key:Le.pki.privateKeyToPem(e.key)})}),(c=this.socket)==null||c.write(r)})}update(e,t){return new Promise((s,r)=>{let o=(0,M.v4)();if(!this.secure)return r(new Error("Only available for secure connections"));this.sendRequest(o,"UpdateRequest",e,t).then(c=>c.Body instanceof k?r(new Error(c.Body.Message)):s(c.Body)).catch(c=>r(c))})}command(e,t){return new Promise((s,r)=>{let o=(0,M.v4)();if(!this.secure)return r(new Error("Only available for secure connections"));this.sendRequest(o,"CreateRequest",e,t).then(()=>s()).catch(c=>r(c))})}subscribe(e,t){return new Promise((s,r)=>{if(!this.secure)return r(new Error("Only available for secure connections"));let o=(0,M.v4)();this.sendRequest(o,"SubscribeRequest",e).then(c=>{c.Header.StatusCode!=null&&c.Header.StatusCode.isSuccessful()&&this.subscriptions.set(o,{url:e,listener:t,callback:a=>t(a.Body)}),s()}).catch(c=>r(c))})}drainRequests(){for(let e of this.requests.keys()){let t=this.requests.get(e);clearTimeout(t.timeout)}this.requests.clear()}sendRequest(e,t,s,r){return new Promise((o,c)=>{if(this.requests.has(e)){let d=this.requests.get(e);d.reject(new Error(`tag "${e}" reused`)),clearTimeout(d.timeout),this.requests.delete(e)}let a={CommuniqueType:t,Header:{ClientTag:e,Url:s},Body:r};if(this.socket==null)return c(new Error("Connection not established"));this.socket.write(a).then(()=>{this.requests.set(e,{message:a,resolve:o,reject:c,timeout:setTimeout(()=>{this.requests.delete(e),c(new Error("Request timeout"))},15e3)})}).catch(d=>c(d))})}authorityCertificate(){let e=Oe.default.resolve(__dirname,"../authority");if(me.default.existsSync(e)){let t=me.default.readFileSync(e);if(t==null)return null;let s=Re.BSON.deserialize(t);return s.ca=Buffer.from(s.ca,"base64").toString("utf8"),s.key=Buffer.from(s.key,"base64").toString("utf8"),s.cert=Buffer.from(s.cert,"base64").toString("utf8"),s}return null}physicalAccess(e){return new Promise((t,s)=>{if(e)return t();let r=setTimeout(()=>s(new Error("Physical timeout exceeded")),6e4);this.once("Message",o=>o.Body.Status.Permissions.includes("PhysicalAccess")?(clearTimeout(r),t()):s(new Error("Unknown pairing error")))})}};var G=class{constructor(i){let e=i.addresses.find(t=>t.family===Ne.HostAddressFamily.IPv4)||i.addresses[0];this.connection=new B(e.address)}authenticate(){return T(this,null,function*(){return new Promise((i,e)=>{this.connection.connect().then(()=>{this.createCertificateRequest("mkellsy-mqtt-lutron").then(t=>{this.connection.authenticate(t).then(s=>i(s)).catch(s=>e(s))}).catch(t=>e(t))}).catch(t=>e(t))})})}createCertificateRequest(i){return new Promise((e,t)=>{_.pki.rsa.generateKeyPair({bits:2048},(s,r)=>{if(s==null){let o=_.pki.createCertificationRequest();return o.publicKey=r.publicKey,o.setSubject([{name:"commonName",value:i}]),o.sign(r.privateKey),e({key:r.privateKey,cert:_.pki.certificationRequestToPem(o)})}return t(new Error("Error generating RSA keys"))})})}};var E=v(require("fs")),Z=v(require("path")),he=v(require("os")),le=require("bson"),Me=require("js-logger");var J=(0,Me.get)("Context"),R=class{constructor(){this.context={};try{let i=this.open("pairing")||{},e=Object.keys(i);for(let s=0;s<e.length;s++)i[e[s]]=this.decrypt(i[e[s]]);this.context=i;let t=this.processors;J.info(`pairing loaded: ${t.length} processor(s) [${t.join(", ")||"none"}]`)}catch(i){J.error(`failed to load pairing from ~/.leap/pairing: ${i instanceof Error?i.message:String(i)}`),this.context={}}}get processors(){return Object.keys(this.context).filter(i=>i!=="authority")}has(i){return this.context[i]!=null}get(i){return this.context[i]}set(i,e){this.context[i.id]=l({},e),this.save("pairing",this.context)}decrypt(i){return i==null?null:(i.ca=Buffer.from(i.ca,"base64").toString("utf8"),i.key=Buffer.from(i.key,"base64").toString("utf8"),i.cert=Buffer.from(i.cert,"base64").toString("utf8"),i)}encrypt(i){return i==null?null:(i.ca=Buffer.from(i.ca).toString("base64"),i.key=Buffer.from(i.key).toString("base64"),i.cert=Buffer.from(i.cert).toString("base64"),i)}open(i){let e=Z.default.join(he.default.homedir(),".leap"),t=Z.default.join(e,i);if(E.default.existsSync(e)||E.default.mkdirSync(e),E.default.existsSync(t)){let s=E.default.readFileSync(t),r=s!=null&&typeof s.length=="number"?s.length:0;return J.info(`opened ${t} (${r} bytes)`),le.BSON.deserialize(s)}return J.warn(`missing pairing file: ${t}`),null}save(i,e){let t=Z.default.join(he.default.homedir(),".leap");E.default.existsSync(t)||E.default.mkdirSync(t);let s=l({},e),r=Object.keys(s);for(let o=0;o<r.length;o++)s[r[o]]=this.encrypt(s[r[o]]);E.default.writeFileSync(Z.default.join(t,i),le.BSON.serialize(s))}};var Ze=v(require("os")),Ue=v(require("path")),Ie=v(require("deep-equal")),Ke=v(require("flat-cache")),qe=require("js-logger"),He=require("@mkellsy/event-emitter"),V=require("tinkerhub-mdns"),fe=require("@mkellsy/hap-device"),x=(0,qe.get)("Discovery"),L=class extends He.EventEmitter{constructor(){super();this.onAvailable=e=>{let t=e.data.get("systype");if(!this.isProcessorService(e)){x.debug(`mDNS ignore service id=${e.id} systype=${String(t)}`);return}let s;try{s=this.parseProcessorAddress(e)}catch(c){x.error(`mDNS parse failed for service id=${e.id}: ${c instanceof Error?c.message:String(c)}`);return}let r=(s.addresses||[]).map(c=>c.address).join(","),o=this.isProcessorCached(s);x.info(`mDNS processor id=${s.id} type=${s.type} addresses=[${r}] cached=${o}`),o||this.emit("Discovered",s),this.cacheProcessor(s)};this.cache=Ke.default.load("discovery",Ue.default.join(Ze.default.homedir(),".leap")),this.cached=this.cache.getKey("/hosts")||[],this.cache.setKey("/hosts",this.cached),this.cache.save(!0)}search(){this.stop(),x.info(`search start: ${this.cached.length} cached host(s)`);for(let e=0;e<this.cached.length;e++){let t=this.cached[e],s=(t.addresses||[]).map(r=>r.address).join(",");x.info(`emit cached host id=${t.id} type=${t.type} addresses=[${s}]`),this.emit("Discovered",t)}this.discovery=new V.MDNSServiceDiscovery({type:"lutron",protocol:V.Protocol.TCP}),this.discovery.onAvailable(this.onAvailable),x.info("mDNS browse started for _lutron._tcp")}stop(){var e;(e=this.discovery)==null||e.destroy()}isProcessorCached(e){return this.cached.find(t=>(0,Ie.default)(t,e))!=null}isProcessorService(e){let t=e.data.get("systype");return!(t==null||typeof t=="boolean")}cacheProcessor(e){let t=this.cached.findIndex(s=>s.id===e.id);t>=0?this.cached[t]=e:this.cached.push(e),this.cache.setKey("/hosts",this.cached),this.cache.save()}parseProcessorAddress(e){var r;let t=this.discovery.serviceData.get(e.id).SRV._record.target,s=[];for(let o=0;o<((r=e.addresses)==null?void 0:r.length);o++)s.push({address:e.addresses[o].host,family:/^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i.test(e.addresses[o].host)?fe.HostAddressFamily.IPv6:fe.HostAddressFamily.IPv4});return{id:t.match(new RegExp("[Ll]utron-(?<id>\\w+)\\.local")).groups.id.toUpperCase(),type:String(e.data.get("systype")),addresses:s}}};var At=require("js-logger"),U=v(require("colors")),pe=require("@mkellsy/hap-device"),Tt=require("@mkellsy/event-emitter");var je=require("js-logger"),ze=v(require("flat-cache")),Fe=v(require("colors")),_e=v(require("os")),Ge=v(require("path")),Je=require("@mkellsy/event-emitter"),Mt=2e4,W=class extends Je.EventEmitter{constructor(e,t){super();this.discovered=new Map;this.onConnect=()=>{this.log.info("connected"),this.emit("Connect",this.connection)};this.onMessage=e=>{this.log.debug("message"),this.emit("Message",e)};this.onDisconnect=()=>{this.log.info("disconnected"),this.emit("Disconnect")};this.onError=e=>{this.logger.error(`connection error: ${(e==null?void 0:e.message)!=null?e.message:String(e)}`),this.emit("Error",e)};this.uuid=e,this.logger=(0,je.get)(`Processor ${Fe.default.dim(this.id)}`),this.connection=t,this.cache=ze.default.load(e,Ge.default.join(_e.default.homedir(),".leap")),this.connection.on("Connect",this.onConnect),this.connection.on("Message",this.onMessage),this.connection.on("Error",this.onError),this.connection.once("Disconnect",this.onDisconnect)}get id(){return this.uuid}get log(){return this.logger}get devices(){return this.discovered}connect(){return new Promise((e,t)=>{this.logger.info("connect() starting"),this.connection.connect().then(()=>{this.logger.info("connect() socket/session ready; starting heartbeat"),this.startHeartbeat(),e()}).catch(s=>{this.logger.error(`connect() failed: ${s instanceof Error?s.message:String(s)}`),t(s)})})}disconnect(){this.stopHeartbeat(),this.connection.disconnect()}clear(){for(let e of this.cache.keys())this.cache.removeKey(e);this.cache.removeCacheFile(),this.cache.save()}isValidCacheEntry(e){return e!=null&&typeof e=="object"}dropCacheKey(e){this.cache.removeKey(e),this.cache.save(!0)}ping(){return this.read("/server/1/status/ping")}read(e){return this.connection.read(e)}project(){return new Promise((e,t)=>{let s=this.cache.getKey("/project");if(s!=null)return e(s);this.connection.read("/project").then(r=>{this.cache.setKey("/project",r),this.cache.save(!0),e(r)}).catch(r=>t(r))})}system(){return new Promise((e,t)=>{let s=this.cache.getKey("/device?where=IsThisDevice:true");if(s!=null)return e(s);this.connection.read("/device?where=IsThisDevice:true").then(r=>{if(r[0]!=null)return this.cache.setKey("/device?where=IsThisDevice:true",r[0]),this.cache.save(!0),e(r[0]);t(new Error("No system device found"))}).catch(r=>t(r))})}areas(){return new Promise((e,t)=>{let s=this.cache.getKey("/area");if(s!=null)return e(s);this.connection.read("/area").then(r=>{this.cache.setKey("/area",r),this.cache.save(!0),e(r)}).catch(r=>t(r))})}timeclocks(){return new Promise((e,t)=>{let s=this.cache.getKey("/timeclock");if(s!=null)return e(s);this.connection.read("/timeclock").then(r=>{this.cache.setKey("/timeclock",r),this.cache.save(!0),e(r)}).catch(r=>t(r))})}zones(e){return new Promise((t,s)=>{let r=this.cache.getKey(`${e.href}/associatedzone`);if(r!=null)return t(r);this.connection.read(`${e.href}/associatedzone`).then(o=>{this.cache.setKey(`${e.href}/associatedzone`,o),this.cache.save(!0),t(o)}).catch(o=>s(o))})}status(e){return this.read(`${e.href}/status`)}statuses(e){return new Promise((t,s)=>{let r=[];r.push(this.read("/zone/status")),r.push(this.read("/area/status")),e==="RadioRa3Processor"&&r.push(this.read("/timeclock/status")),Promise.all(r).then(([o,c,a])=>t([...o,...c,...a||[]])).catch(o=>s(o))})}controls(e){return new Promise((t,s)=>{let r=this.cache.getKey(`${e.href}/associatedcontrolstation`);if(r!=null)return t(r);this.connection.read(`${e.href}/associatedcontrolstation`).then(o=>{this.cache.setKey(`${e.href}/associatedcontrolstation`,o),this.cache.save(!0),t(o)}).catch(o=>s(o))})}device(e){return new Promise((t,s)=>{let r=this.cache.getKey(e.href);if(this.isValidCacheEntry(r)&&typeof r.DeviceType=="string")return t(r);r!=null&&(this.logger.warn(`dropping invalid cache for ${e.href}: ${typeof r=="string"?r:typeof r}`),this.dropCacheKey(e.href)),this.connection.read(e.href).then(o=>{if(!this.isValidCacheEntry(o)||typeof o.DeviceType!="string")return s(new Error(`invalid device payload for ${e.href}`));this.cache.setKey(e.href,o),this.cache.save(!0),t(o)}).catch(o=>s(o))})}buttons(e){return new Promise((t,s)=>{let r=`${e.href}/buttongroup/expanded`,o=this.cache.getKey(r);if(this.isValidCacheEntry(o)&&Array.isArray(o))return t(o);o!=null&&(this.logger.warn(`dropping invalid cache for ${r}: ${typeof o=="string"?o:typeof o}`),this.dropCacheKey(r)),this.connection.read(`${e.href}/buttongroup/expanded`).then(c=>{if(!Array.isArray(c))return s(new Error(`invalid button group payload for ${e.href}`));this.cache.setKey(r,c),this.cache.save(!0),t(c)}).catch(c=>s(c))})}update(e,t,s){return this.connection.update(`${e.href}/${t}`,s)}command(e,t){return this.connection.command(`${e.href}/commandprocessor`,{Command:t})}subscribe(e,t){return this.connection.subscribe(e.href,t)}startHeartbeat(){this.stopHeartbeat(),this.ping().then(()=>{this.logger.debug("heartbeat ping ok")}).catch(e=>{this.logger.warn(`heartbeat ping failed: ${e instanceof Error?e.message:String(e)}`)}).finally(()=>{this.heartbeatTimeout=setTimeout(()=>this.startHeartbeat(),Mt)})}stopHeartbeat(){clearTimeout(this.heartbeatTimeout),this.heartbeatTimeout=void 0}};var P=require("@mkellsy/hap-device");var Xe=v(require("deep-equal")),et=require("@mkellsy/hap-device");var Ve=require("js-logger"),We=require("@mkellsy/hap-device"),Ye=v(require("colors")),Qe=require("@mkellsy/event-emitter"),y=class extends Qe.EventEmitter{constructor(e,t,s,r,o){super();this.initialized=!1;this.fields=new Map;this.ready=Promise.resolve();this.processor=t,this.deviceAddress=r.href,this.deviceName=r.Name,this.deviceArea=s,this.deviceAreaPath=s.Path||s.Name||"",this.deviceType=e,this.logger=(0,Ve.get)(`Device ${Ye.default.dim(this.id)}`),this.state=o}get manufacturer(){return"Lutron Electronics Co., Inc"}get id(){var e;return`LEAP-${this.processor.id}-${We.DeviceType[this.deviceType].toUpperCase()}-${(e=this.deviceAddress)==null?void 0:e.split("/")[2]}`}get name(){return this.deviceName}get room(){return this.area.Name}get areaPath(){return this.deviceAreaPath||this.room}get capabilities(){return Object.fromEntries(this.fields)}get log(){return this.logger}get address(){return{href:this.deviceAddress}}get type(){return this.deviceType}get area(){return this.deviceArea}get status(){return this.state}};var Y=class extends y{constructor(i,e,t){super(et.DeviceType.Contact,i,e,t,{state:"Open"}),this.fields.set("state",{type:"String",values:["Open","Closed"]})}update(i){let e=l({},this.status);i.CCOLevel!=null&&(this.state.state=i.CCOLevel),this.initialized&&!(0,Xe.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(i){return this.processor.command(this.address,{CommandType:"GoToCCOLevel",CCOLevelParameters:{CCOLevel:i.state}})}};var tt=v(require("deep-equal")),st=require("@mkellsy/hap-device");var Q=class extends y{constructor(i,e,t){super(st.DeviceType.Dimmer,i,e,t,{state:"Off",level:0}),this.fields.set("state",{type:"String",values:["On","Off"]}),this.fields.set("level",{type:"Integer",min:0,max:100})}update(i){let e=l({},this.status);i.Level!=null&&(this.state.state=i.Level>0?"On":"Off",this.state.level=i.Level),this.initialized&&!(0,tt.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(i){return this.processor.command(this.address,{CommandType:"GoToLevel",Parameter:[{Type:"Level",Value:i.state==="Off"?0:i.level}]})}};var rt=v(require("deep-equal")),it=require("@mkellsy/hap-device");var X=class extends y{constructor(i,e,t){super(it.DeviceType.Fan,i,e,t,{state:"Off",speed:0}),this.fields.set("state",{type:"String",values:["On","Off"]}),this.fields.set("speed",{type:"Integer",min:0,max:7})}update(i){let e=l({},this.status),t=this.parseFanSpeed(i.FanSpeed);this.state=$(l({},e),{state:t>0?"On":"Off",speed:t}),this.initialized&&!(0,rt.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(i){let e=i.state==="Off"?"Off":this.lookupFanSpeed(i.speed);return this.processor.command(this.address,{CommandType:"GoToFanSpeed",FanSpeedParameters:[{FanSpeed:e}]})}lookupFanSpeed(i){switch(i){case 1:return"Low";case 2:case 3:return"Medium";case 4:case 5:return"MediumHigh";case 6:case 7:return"High";default:return"Off"}}parseFanSpeed(i){switch(i){case"Low":return 1;case"Medium":return 3;case"MediumHigh":return 5;case"High":return 7;default:return 0}}};var ge=v(require("colors")),ot=require("@mkellsy/hap-device");var ee=class extends y{constructor(e,t,s){super(ot.DeviceType.Keypad,e,t,s,{led:{href:"/unknown"},state:"Off"});this.buttons=[];(s.DeviceType==="SunnataKeypad"||s.DeviceType==="SunnataHybridKeypad")&&(this.ready=this.processor.buttons(this.address).then(r=>T(this,null,function*(){var o;if(!Array.isArray(r))throw new Error(`button groups not an array for ${s.DeviceType} ${this.address.href}`);for(let c=0;c<r.length;c++)for(let a=0;a<((o=r[c].Buttons)==null?void 0:o.length);a++){let d=r[c].Buttons[a],u={id:`LEAP-${this.processor.id}-BUTTON-${d.href.split("/")[2]}`,index:d.ButtonNumber,name:(d.Engraving||{}).Text||d.Name,led:d.AssociatedLED};this.buttons.push(u);try{yield this.processor.subscribe({href:`${d.href}/status/event`},h=>{h.ButtonEvent.EventType==="Press"&&(this.emit("Action",this,u,"Press"),setTimeout(()=>this.emit("Action",this,u,"Release"),100))})}catch(h){this.log.error(ge.default.red(h instanceof Error?h.message:String(h)))}}this.log.info(`keypad buttons ready count=${this.buttons.length} type=${s.DeviceType} href=${this.address.href}`)})).catch(r=>{throw this.log.error(ge.default.red(r.message)),r}))}update(){this.initialized=!0}set(e){return this.processor.update(e.led,"status",{LEDStatus:{State:e.state==="On"?"On":"Off"}})}};var ve=v(require("colors")),at=require("@mkellsy/hap-device");var nt=new Map([["Pico2Button",new Map([[0,[1,!1]],[2,[2,!1]]])],["Pico2ButtonRaiseLower",new Map([[0,[1,!1]],[2,[4,!1]],[3,[2,!0]],[4,[3,!0]]])],["Pico3Button",new Map([[0,[1,!1]],[1,[2,!1]],[2,[3,!1]]])],["Pico3ButtonRaiseLower",new Map([[0,[1,!1]],[1,[3,!1]],[2,[5,!1]],[3,[2,!0]],[4,[4,!0]]])],["Pico4Button2Group",new Map([[1,[1,!1]],[2,[2,!1]],[3,[3,!1]],[4,[4,!1]]])],["Pico4ButtonScene",new Map([[1,[1,!1]],[2,[2,!1]],[3,[3,!1]],[4,[4,!1]]])],["Pico4ButtonZone",new Map([[1,[1,!1]],[2,[2,!1]],[3,[3,!1]],[4,[4,!1]]])],["PaddleSwitchPico",new Map([[0,[1,!1]],[2,[2,!1]]])],["Pico4Button",new Map([[1,[1,!1]],[2,[2,!1]],[3,[3,!1]],[4,[4,!1]]])]]);var C=require("@mkellsy/hap-device"),ct=require("@mkellsy/event-emitter");var te=class extends ct.EventEmitter{constructor(e,t,s,r){super();this.state=C.TriggerState.Idle;this.index=s,this.processor=e,this.action=t,this.options=l({doubleClickSpeed:300,clickSpeed:450,raiseLower:!1},r),this.button={id:this.id,index:this.index,name:(this.action.Engraving||{}).Text||this.action.Name},this.options.raiseLower===!0&&(this.button.raiseLower=!0)}get id(){return`LEAP-${this.processor.id}-BUTTON-${this.action.href.split("/")[2]}`}get definition(){return this.button}reset(){this.state=C.TriggerState.Idle,this.timer&&clearTimeout(this.timer),this.timer=void 0}update(e){var r;if(this.options.raiseLower===!0){let o=(r=e.ButtonEvent)==null?void 0:r.EventType;o==="Press"?this.emit("Press",this.button):o==="Release"&&this.emit("Release",this.button);return}let t=()=>{this.reset(),this.options.clickSpeed!==0&&this.emit("LongPress",this.button)},s=()=>{this.reset(),this.emit("Press",this.button)};switch(this.state){case C.TriggerState.Idle:{if(e.ButtonEvent.EventType==="Press"&&this.options.clickSpeed>0){this.state=C.TriggerState.Down,this.timer=setTimeout(t,this.options.clickSpeed);return}if(e.ButtonEvent.EventType==="Press"){this.state=C.TriggerState.Down,s();return}break}case C.TriggerState.Down:{if(e.ButtonEvent.EventType==="Release"){this.state=C.TriggerState.Up,this.timer&&clearTimeout(this.timer),this.options.doubleClickSpeed>0&&(this.timer=setTimeout(s,this.options.doubleClickSpeed+(this.options.raiseLower?250:0)));return}this.reset();break}case C.TriggerState.Up:{if(e.ButtonEvent.EventType==="Press"&&this.timer){if(this.reset(),this.options.doubleClickSpeed===0)return;this.emit("DoublePress",this.button);return}this.reset();break}}}};var se=class extends y{constructor(e,t,s){super(at.DeviceType.Remote,e,t,s,{state:"Unknown"});this.buttons=[];this.triggers=new Map;this.set=()=>Promise.resolve();this.ready=this.processor.buttons(this.address).then(r=>T(this,null,function*(){var c,a,d;if(!Array.isArray(r))throw new Error(`button groups not an array for ${s.DeviceType} ${this.address.href}`);let o=nt.get(s.DeviceType);o==null&&this.log.warn(`no ButtonMap for DeviceType=${s.DeviceType}; buttons will be unmapped`);for(let p=0;p<r.length;p++)for(let u=0;u<((c=r[p].Buttons)==null?void 0:c.length);u++){let h=r[p].Buttons[u],m=o==null?void 0:o.get(h.ButtonNumber),A=(a=m==null?void 0:m[0])!=null?a:h.ButtonNumber+1,f=(d=m==null?void 0:m[1])!=null?d:!1,S=new te(this.processor,h,A,{raiseLower:f});f?(S.on("Press",g=>{this.emit("Action",this,g,"Press")}),S.on("Release",g=>{this.emit("Action",this,g,"Release")})):(S.on("Press",g=>{this.emit("Action",this,g,"Press"),setTimeout(()=>this.emit("Action",this,g,"Release"),100)}),S.on("DoublePress",g=>{this.emit("Action",this,g,"DoublePress"),setTimeout(()=>this.emit("Action",this,g,"Release"),100)}),S.on("LongPress",g=>{this.emit("Action",this,g,"LongPress"),setTimeout(()=>this.emit("Action",this,g,"Release"),100)})),this.triggers.set(h.href,S),this.buttons.push(S.definition);try{yield this.processor.subscribe({href:`${h.href}/status/event`},g=>this.triggers.get(h.href).update(g))}catch(g){this.log.error(ve.default.red(g instanceof Error?g.message:String(g)))}}this.log.info(`remote buttons ready count=${this.buttons.length} type=${s.DeviceType} href=${this.address.href}`)})).catch(r=>{throw this.log.error(ve.default.red(r.message)),r})}update(){this.initialized=!0}};var dt=v(require("deep-equal")),ut=require("@mkellsy/hap-device");var re=class extends y{constructor(e,t,s){super(ut.DeviceType.Occupancy,e,t,s,{state:"Unoccupied"});this.set=()=>Promise.resolve()}update(e){let t=l({},this.status);e.OccupancyStatus!=null&&(this.state.state=e.OccupancyStatus==="Occupied"?"Occupied":"Unoccupied"),(0,dt.default)(this.state,t)||this.emit("Update",this,this.state),this.initialized=!0}};var pt=v(require("deep-equal")),mt=require("@mkellsy/hap-device");var ie=class extends y{constructor(i,e,t){super(mt.DeviceType.Shade,i,e,t,{state:"Closed",level:0,tilt:0}),this.fields.set("state",{type:"String",values:["Open","Closed"]}),this.fields.set("level",{type:"Integer",min:0,max:100}),this.fields.set("tilt",{type:"Integer",min:0,max:100})}update(i){let e=l({},this.status);i.Level!=null&&(this.state.state=i.Level>0?"Open":"Closed",this.state.level=i.Level),i.Tilt!=null&&(this.state.tilt=i.Tilt),this.initialized&&!(0,pt.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(i){let e=[];return e.push(this.processor.command(this.address,{CommandType:"GoToLevel",Parameter:[{Type:"Level",Value:i.state==="Closed"?0:i.level}]})),(i.tilt!=null||i.state==="Closed")&&e.push(this.processor.command(this.address,{CommandType:"TiltParameters",TiltParameters:{Tilt:i.state==="Closed"?0:i.tilt}})),Promise.all(e)}};var ht=v(require("deep-equal")),lt=require("@mkellsy/hap-device");var oe=class extends y{constructor(i,e,t){super(lt.DeviceType.Strip,i,e,t,{state:"Off",level:0,luminance:1800}),this.fields.set("state",{type:"String",values:["On","Off"]}),this.fields.set("level",{type:"Integer",min:0,max:100}),this.fields.set("luminance",{type:"Integer",min:1800,max:3e3})}update(i){let e=l({},this.status);i.Level!=null&&(this.state.state=i.Level>0?"On":"Off",this.state.level=i.Level),i.ColorTuningStatus!=null&&i.ColorTuningStatus.WhiteTuningLevel!=null&&i.ColorTuningStatus.WhiteTuningLevel.Kelvin!=null&&(this.state.luminance=i.ColorTuningStatus.WhiteTuningLevel.Kelvin),this.initialized&&!(0,ht.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(i){return i.state==="Off"?this.processor.command(this.address,{CommandType:"GoToWhiteTuningLevel",WhiteTuningLevelParameters:{Level:0}}):this.processor.command(this.address,{CommandType:"GoToWhiteTuningLevel",WhiteTuningLevelParameters:{Level:i.level,WhiteTuningLevel:{Kelvin:i.luminance}}})}};var ft=v(require("deep-equal")),gt=require("@mkellsy/hap-device");var ne=class extends y{constructor(i,e,t){super(gt.DeviceType.Switch,i,e,t,{state:"Off"}),this.fields.set("state",{type:"String",values:["On","Off"]})}update(i){let e=l({},this.status);this.state=$(l({},e),{state:i.SwitchedLevel||"Unknown"}),this.initialized&&!(0,ft.default)(this.state,e)&&this.emit("Update",this,this.state),this.initialized=!0}set(i){return this.processor.command(this.address,{CommandType:"GoToLevel",Parameter:[{Type:"Level",Value:i.state==="On"?100:0}]})}};var vt=v(require("deep-equal")),yt=require("@mkellsy/hap-device");var ce=class extends y{constructor(e,t,s){super(yt.DeviceType.Timeclock,e,t,s,{state:"Off"});this.set=()=>Promise.resolve();this.fields.set("state",{type:"String",values:["On","Off"]})}update(e){let t=l({},this.status);this.state=$(l({},t),{state:e.EnabledState==="Enabled"?"On":"Off"}),this.initialized&&!(0,vt.default)(this.state,t)&&this.emit("Update",this,this.state),this.initialized=!0}};var St=require("@mkellsy/hap-device");var ae=class extends y{constructor(e,t,s){super(St.DeviceType.Unknown,e,t,s,{state:"Unknown"});this.set=()=>Promise.resolve()}update(){}};function de(n,i,e){var s;switch(ye(e.ControlType||e.DeviceType)){case P.DeviceType.Contact:return new Y(n,i,e);case P.DeviceType.Dimmer:return new Q(n,i,e);case P.DeviceType.Fan:return new X(n,i,e);case P.DeviceType.Keypad:return new ee(n,i,e);case P.DeviceType.Occupancy:return new re(n,i,{href:`/occupancy/${(s=i.href)==null?void 0:s.split("/")[2]}`,Name:e.Name});case P.DeviceType.Remote:return new se(n,i,e);case P.DeviceType.Shade:return new ie(n,i,e);case P.DeviceType.Strip:return new oe(n,i,e);case P.DeviceType.Switch:return new ne(n,i,e);case P.DeviceType.Timeclock:return new ce(n,i,e);default:return new ae(n,i,e)}}function ye(n){switch(n){case"Switched":case"PowPakSwitch":case"OutdoorPlugInSwitch":return P.DeviceType.Switch;case"Dimmed":case"PlugInDimmer":return P.DeviceType.Dimmer;case"Shade":return P.DeviceType.Shade;case"Timeclock":return P.DeviceType.Timeclock;case"WhiteTune":return P.DeviceType.Strip;case"FanSpeed":return P.DeviceType.Fan;case"Pico2Button":case"Pico2ButtonRaiseLower":case"Pico3Button":case"Pico4Button":case"Pico3ButtonRaiseLower":case"Pico4Button2Group":case"Pico4ButtonScene":case"Pico4ButtonZone":case"PaddleSwitchPico":return P.DeviceType.Remote;case"SunnataDimmer":case"SunnataSwitch":case"SunnataKeypad":case"SunnataHybridKeypad":return P.DeviceType.Keypad;case"RPSCeilingMountedOccupancySensor":return P.DeviceType.Occupancy;case"CCO":return P.DeviceType.Contact;default:return P.DeviceType.Unknown}}function Pt(n){if(n==null||typeof n!="object"||n.AddressedState!=="Addressed")return!1;switch(n.DeviceType){case"Pico2Button":case"Pico2ButtonRaiseLower":case"Pico3Button":case"Pico4Button":case"Pico3ButtonRaiseLower":case"Pico4Button2Group":case"Pico4ButtonScene":case"Pico4ButtonZone":case"PaddleSwitchPico":return!0;case"SunnataKeypad":case"SunnataHybridKeypad":return!0;case"RPSCeilingMountedOccupancySensor":return!0;default:return!1}}var w={info:n=>console.log(`[LEAP][MetaProbe] ${n}`),warn:n=>console.warn(`[LEAP][MetaProbe] ${n}`)},Zt=["/area","/zone","/device","/controlstation","/occupancygroup","/group","/areagroup","/zonegroup","/loadgroup","/devicegroup","/virtualbutton","/preset","/area/status","/zone/status","/server/1/status/ping","/project","/clientsetting","/link","/button","/buttongroup","/timeclock"];function Ut(n){if(n==null)return"null";if(Array.isArray(n)){let i=n[0],e=i!=null&&typeof i=="object"?Object.keys(i).join(","):"";return`array(len=${n.length}${e?` sampleKeys=${e}`:""})`}if(typeof n=="object"){let e=Object.keys(n);return`object(keys=${e.slice(0,20).join(",")}${e.length>20?",...":""})`}return typeof n=="string"?`string(len=${n.length} preview=${JSON.stringify(n.slice(0,200))})`:`${typeof n}:${String(n).slice(0,120)}`}function Se(n,i){var o;if(i==null)return"";let e=new Map(n.map(c=>[c.href,c])),t=[],s=i,r=new Set;for(;s!=null&&!r.has(s);){r.add(s);let c=e.get(s);if(c==null)break;t.unshift(c.Name),s=(o=c.Parent)==null?void 0:o.href}return t.join(" / ")}function bt(n,i){return T(this,null,function*(){var e,t,s,r,o,c,a,d;w.info(`===== LEAP metadata probe start processor=${n.id} areas=${i.length} =====`);for(let p of i)w.info(`AREA href=${p.href} name=${JSON.stringify(p.Name)} leaf=${p.IsLeaf} parent=${((e=p.Parent)==null?void 0:e.href)||"none"} path=${JSON.stringify(Se(i,p.href))} keys=${Object.keys(p).join(",")}`);for(let p of i.filter(u=>u.IsLeaf)){let u=Se(i,p.href);try{let h=yield n.zones(p);for(let m of h)w.info(`ZONE area=${JSON.stringify(u)} name=${JSON.stringify(m.Name)} href=${m.href} control=${m.ControlType} category=${((t=m.Category)==null?void 0:t.Type)||""} isLight=${(r=(s=m.Category)==null?void 0:s.IsLight)!=null?r:"?"} assocArea=${((o=m.AssociatedArea)==null?void 0:o.href)||""} device=${((c=m.Device)==null?void 0:c.href)||""} keys=${Object.keys(m).join(",")}`)}catch(h){w.warn(`ZONE list failed for ${p.href}: ${h instanceof Error?h.message:String(h)}`)}try{let h=yield n.controls(p);for(let m of h){let A=(m.AssociatedGangedDevices||[]).map(f=>{var S,g;return`${((S=f.Device)==null?void 0:S.DeviceType)||"?"}@${((g=f.Device)==null?void 0:g.href)||"?"}`}).join(",");w.info(`STATION area=${JSON.stringify(u)} name=${JSON.stringify(m.Name)} href=${m.href} ganged=[${A}] keys=${Object.keys(m).join(",")}`)}}catch(h){w.warn(`STATION list failed for ${p.href}: ${h instanceof Error?h.message:String(h)}`)}}for(let p of Zt)try{let u=yield n.read(p);w.info(`PROBE ok url=${p} body=${Ut(u)}`),Array.isArray(u)&&u.length>0&&u.length<=5?w.info(`PROBE sample url=${p} ${JSON.stringify(u).slice(0,1500)}`):Array.isArray(u)&&u.length>5?w.info(`PROBE sample url=${p} first=${JSON.stringify(u[0]).slice(0,800)}`):u!=null&&typeof u=="object"&&!Array.isArray(u)&&w.info(`PROBE sample url=${p} ${JSON.stringify(u).slice(0,1200)}`)}catch(u){w.info(`PROBE fail url=${p}: ${u instanceof Error?u.message:String(u)}`)}for(let p of n.devices.values()){let u=p.area,h=p,m=typeof h.areaPath=="string"?h.areaPath:Se(i,u==null?void 0:u.href);w.info(`DEVICE type=${p.type} name=${JSON.stringify(p.name)} room=${JSON.stringify(p.room)} areaPath=${JSON.stringify(m)} id=${p.id} href=${((a=p.address)==null?void 0:a.href)||""} areaHref=${(u==null?void 0:u.href)||""} areaParent=${((d=u==null?void 0:u.Parent)==null?void 0:d.href)||""}`)}w.info(`===== LEAP metadata probe done processor=${n.id} =====`)})}var b=(0,At.get)("Client"),ue=5e3,I=class extends Tt.EventEmitter{constructor(e){var s,r;super(1/0);this.discovered=new Map;this.areaPaths=new Map;this.onDiscovered=e=>{this.discovered.delete(e.id);let t=(e.addresses||[]).map(o=>`${o.family}:${o.address}`).join(", ");if(b.info(`Discovered host id=${e.id} type=${e.type} addresses=[${t}]`),!this.context.has(e.id)){b.warn(`skipping host id=${e.id}: not in pairing (paired=[${this.context.processors.join(", ")||"none"}])`);return}let s=e.addresses.find(o=>o.family===pe.HostAddressFamily.IPv4)||e.addresses[0];if(s==null){b.error(`host id=${e.id} has no addresses`);return}b.info(`connecting to processor id=${e.id} at ${s.address}`);let r=new W(e.id,new B(s.address,this.context.get(e.id)));this.discovered.set(e.id,r),r.log.info(`Processor ${U.default.green(s.address)}`),r.on("Disconnect",()=>{b.warn(`processor ${e.id} disconnected; retry in ${ue}ms`),setTimeout(()=>this.onDiscovered(e),ue)}).on("Connect",()=>{b.info(`processor ${e.id} Connect event \u2014 loading system/project/areas (refresh=${this.refresh})`),this.refresh&&r.clear(),Promise.all([r.system(),r.project(),r.areas()]).then(([o,c,a])=>{var h;let d=o==null?void 0:o.FirmwareImage.Firmware.DisplayName,p=o==null?void 0:o.DeviceType,u=[];this.rebuildAreaPaths(a),b.info(`processor ${e.id} system loaded firmware=${d||"Unknown"} type=${p} areas=${(h=a==null?void 0:a.length)!=null?h:0}`),r.log.info(`Firmware ${U.default.green(d||"Unknown")}`),r.log.info(c.ProductType),r.subscribe({href:"/zone/status"},m=>{for(let A of m){let f=r.devices.get(A.Zone.href);f!=null&&f.update(A)}}).then(()=>b.info(`processor ${e.id} subscribed /zone/status`)).catch(m=>this.onProcessorError(e,m)),r.subscribe({href:"/area/status"},m=>{var A;for(let f of m){let S=r.devices.get(`/occupancy/${(A=f.href)==null?void 0:A.split("/")[2]}`);S!=null&&f.OccupancyStatus!=null&&S.update(f)}}).then(()=>b.info(`processor ${e.id} subscribed /area/status`)).catch(m=>this.onProcessorError(e,m)),p==="RadioRa3Processor"&&r.subscribe({href:"/timeclock/status"},m=>{for(let A of m){let f=r.devices.get(A.Timeclock.href);f!=null&&f.update(A)}}).then(()=>b.info(`processor ${e.id} subscribed /timeclock/status`)).catch(m=>this.onProcessorError(e,m));for(let m of a)u.push(new Promise(A=>{this.discoverZones(r,m).then(()=>A())})),u.push(new Promise(A=>{this.discoverControls(r,m).then(()=>A())}));p==="RadioRa3Processor"&&u.push(new Promise(m=>{this.discoverTimeclocks(r).then(()=>m())})),b.info(`processor ${e.id} discovering devices (${u.length} wait tasks)`),Promise.all(u).then(()=>T(this,null,function*(){let m=[...r.devices.values()];yield Promise.all(m.map(f=>T(this,null,function*(){let S=f.ready;if(S!=null)try{yield S}catch(g){b.warn(`device ${f.name} button init failed: ${g instanceof Error?g.message:String(g)}`)}})));let A=m.length;b.info(`processor ${e.id} discovery complete: ${A} devices; loading statuses`),r.statuses(p).then(f=>{for(let S of f){let g=r.devices.get((S.Zone||{}).href||""),Pe=r.devices.get(`/occupancy/${(S.href||"").split("/")[2]}`);g!=null&&g.update(S),Pe!=null&&S.OccupancyStatus!=null&&Pe.update(S)}b.info(`processor ${e.id} applied ${f.length} status record(s)`)}).catch(f=>{b.error(`processor ${e.id} statuses failed: ${f instanceof Error?f.message:String(f)}`)}),r.log.info(`discovered ${U.default.green(A.toString())} devices`),bt(r,a).catch(f=>{b.error(`metadata probe failed: ${f instanceof Error?f.message:String(f)}`)}),b.info(`processor ${e.id} emitting Available with ${A} devices`),this.emit("Available",m)}))}).catch(o=>this.onProcessorError(e,o))}).on("Error",o=>this.onProcessorError(e,o)),r.connect().catch(o=>this.onProcessorError(e,o))};this.onDeviceUpdate=(e,t)=>{this.emit("Update",e,t)};this.onDeviceAction=(e,t,s)=>{this.emit("Action",e,t,s)};this.onProcessorError=(e,t)=>{let s=(t==null?void 0:t.message)!=null?t.message:String(t);if(s.match(/ENOTFOUND|ENETUNREACH|EHOSTUNREACH|ECONNRESET|EPIPE|ECONNREFUSED|ETIMEDOUT/g)!=null){b.warn(`processor ${e.id} network error: ${s}; retry in ${ue}ms`),setTimeout(()=>this.onDiscovered(e),ue);return}b.error(U.default.red(`processor ${e.id} error: ${s}`))};this.context=new R,this.discovery=new L,this.refresh=e===!0;let t=(r=(s=this.context)==null?void 0:s.processors)!=null?r:[];b.info(`Client start refresh=${this.refresh} paired=[${t.join(", ")||"none"}]`),this.discovery.on("Discovered",this.onDiscovered).search()}get processors(){return[...this.discovered.keys()]}processor(e){return this.discovered.get(e)}close(){this.discovery.stop();for(let e of this.discovered.values())e.disconnect();this.discovered.clear()}rebuildAreaPaths(e){var s;let t=new Map(e.map(r=>[r.href,r]));this.areaPaths.clear();for(let r of e){let o=[],c=r.href,a=new Set;for(;c!=null&&!a.has(c);){a.add(c);let d=t.get(c);if(d==null)break;o.unshift(d.Name),c=(s=d.Parent)==null?void 0:s.href}this.areaPaths.set(r.href,o.join(" / ")||r.Name)}}withAreaPath(e){return $(l({},e),{Path:this.areaPaths.get(e.href)||e.Name})}discoverZones(e,t){return new Promise(s=>{if(!t.IsLeaf)return s();let r=this.withAreaPath(t);e.zones(t).then(o=>{for(let c of o){let a=de(e,r,c).on("Update",this.onDeviceUpdate).on("Action",this.onDeviceAction);e.devices.set(c.href,a)}s()}).catch(()=>s())})}discoverTimeclocks(e){return new Promise(t=>{e.timeclocks().then(s=>{for(let r of s){let o=de(e,{href:r.href,Name:r.Name,ControlType:"Timeclock",Parent:r.Parent,IsLeaf:!0,AssociatedZones:[],AssociatedControlStations:[],AssociatedOccupancyGroups:[],Path:r.Name},$(l({},r),{ControlType:"Timeclock"})).on("Update",this.onDeviceUpdate);e.devices.set(r.href,o)}t()}).catch(()=>t())})}discoverControls(e,t){return new Promise(s=>{if(!t.IsLeaf)return s();let r=this.withAreaPath(t);e.controls(t).then(o=>T(this,null,function*(){if(o==null||o.length===0)return s();yield Promise.all(o.map(c=>T(this,null,function*(){var d;let a=yield this.discoverPositions(e,c);for(let p of a){let h=ye(p.DeviceType)===pe.DeviceType.Occupancy?`/occupancy/${(d=t.href)==null?void 0:d.split("/")[2]}`:p.href,m=de(e,r,$(l({},p),{Name:`${t.Name} ${c.Name} ${p.Name}`})).on("Update",this.onDeviceUpdate).on("Action",this.onDeviceAction);e.devices.set(h,m)}}))),s()})).catch(()=>s())})}discoverPositions(e,t){return new Promise(s=>{if(t.AssociatedGangedDevices==null)return s([]);let r=[];for(let o of t.AssociatedGangedDevices)r.push(e.device(o.Device));Promise.allSettled(r).then(o=>{let c=[];for(let a of o){if(a.status==="rejected"){b.warn(`control station ${t.Name||t.href}: device fetch failed: ${a.reason instanceof Error?a.reason.message:String(a.reason)}`);continue}if(Pt(a.value))c.push(a.value);else{let d=a.value;b.info(`skipping non-addressable ${t.Name||"?"} type=${(d==null?void 0:d.DeviceType)||typeof d} state=${(d==null?void 0:d.AddressedState)||"?"} href=${(d==null?void 0:d.href)||"?"}`)}}s(c)}).catch(()=>s([]))})}};function It(n){return new I(n)}function Kt(){return new Promise((n,i)=>{let e=new L,t=new R;e.on("Discovered",s=>{t.get(s.id)==null&&new G(s).authenticate().then(o=>{t.set(s,o),n()}).catch(o=>i(o)).finally(()=>e.stop())}),e.search()})}0&&(module.exports={Client,connect,pair});
|
|
3
3
|
//# sourceMappingURL=index.js.map
|