al3x Posted 7 hours ago Share Posted 7 hours ago (edited) I have developed a script that turns on my light as soon as the Shelly BLU motion detects movement. This only happens after sunset. The sunset time is determined via an Astro service. When the Shelly BLU motion no longer detects movement, the light is turned off again. The script can be expanded, and additional events such as light intensity can be incorporated. The script runs on a Shelly Pro Dimmer 2PM. The required inputs are the MAC address of the BLU motion and the latitude and longitude of the BLU motion's location. // v0.3 /******************* START CHANGE HERE *******************/ let CONFIG = {   eventName: "shelly-blu",   debug: false,   active: false,   mac: "b0:c7:de:XX:XX:XX",   timeoutId: null,  // New timeout handler   onMotionChange: function (motion) {     if (motion) {       // If motion is detected, reset the timer to turn off the light       if (CONFIG.timeoutId) {         clearTimeout(CONFIG.timeoutId);         CONFIG.timeoutId = null;         console.log("Timer has been reset.");       }       // On motion, check if it is after sunset       checkSunsetTime(function(isAfterSunset) {         if (isAfterSunset) {           Shelly.call("Light.set", { id: 0, on: true });           console.log("Light turned on.");         } else {           console.log("It's before sunset. Light remains off.");         }       });     } else {       // If no more motion is detected, start a 3-minute timer to turn off the light       CONFIG.timeoutId = setTimeout(function () {         Shelly.call("Light.set", { id: 0, on: false });         console.log("Light turned off after 3 minutes.");         CONFIG.timeoutId = null; // Reset the timer       }, 180000);  // 3 minutes in milliseconds       console.log("3-minute timer started.");     }   }, }; /******************* STOP CHANGE HERE *******************/ let BTHOME_SVC_ID_STR = "fcd2"; let uint8 = 0; let int8 = 1; let uint16 = 2; let int16 = 3; let uint24 = 4; let int24 = 5; let BTH = {}; BTH[0x00] = { n: "pid", t: uint8 }; BTH[0x01] = { n: "battery", t: uint8, u: "%" }; BTH[0x02] = { n: "temperature", t: int16, f: 0.01, u: "tC" }; BTH[0x03] = { n: "humidity", t: uint16, f: 0.01, u: "%" }; BTH[0x05] = { n: "illuminance", t: uint24, f: 0.01 }; BTH[0x21] = { n: "motion", t: uint8 }; BTH[0x2d] = { n: "window", t: uint8 }; BTH[0x3a] = { n: "button", t: uint8 }; BTH[0x3f] = { n: "rotation", t: int16, f: 0.1 }; function logger(message, prefix) {   if (!CONFIG.debug) {     return;   }   let finalText = Array.isArray(message) ? message.map(item => JSON.stringify(item)).join(" ") : JSON.stringify(message);   console.log(`${prefix ? prefix + ":" : ""} ${finalText}`); } function getByteSize(type) {   if (type === uint8 || type === int8) return 1;   if (type === uint16 || type === int16) return 2;   if (type === uint24 || type === int24) return 3;   return 255; } let BTHomeDecoder = {   utoi: function (num, bitsz) {     let mask = 1 << (bitsz - 1);     return num & mask ? num - (1 << bitsz) : num;   },   getUInt8: function (buffer) {     return buffer.at(0);   },   getInt8: function (buffer) {     return this.utoi(this.getUInt8(buffer), 8);   },   getUInt16LE: function (buffer) {     return 0xffff & ((buffer.at(1) << 😎 | buffer.at(0));   },   getInt16LE: function (buffer) {     return this.utoi(this.getUInt16LE(buffer), 16);   },   getUInt24LE: function (buffer) {     return (       0x00ffffff & ((buffer.at(2) << 16) | (buffer.at(1) << 😎 | buffer.at(0))     );   },   getInt24LE: function (buffer) {     return this.utoi(this.getUInt24LE(buffer), 24);   },   getBufValue: function (type, buffer) {     if (buffer.length < getByteSize(type)) return null;     let res = null;     if (type === uint8) res = this.getUInt8(buffer);     if (type === int8) res = this.getInt8(buffer);     if (type === uint16) res = this.getUInt16LE(buffer);     if (type === int16) res = this.getInt16LE(buffer);     if (type === uint24) res = this.getUInt24LE(buffer);     if (type === int24) res = this.getInt24LE(buffer);     return res;   },   unpack: function (buffer) {     if (typeof buffer !== "string" || buffer.length === 0) return null;     let result = {};     let _dib = buffer.at(0);     result["encryption"] = _dib & 0x1 ? true : false;     result["BTHome_version"] = _dib >> 5;     if (result["BTHome_version"] !== 2) return null;     if (result["encryption"]) return result;     buffer = buffer.slice(1);     let _bth;     let _value;     while (buffer.length > 0) {       _bth = BTH[buffer.at(0)];       if (typeof _bth === "undefined") {         logger("unknown type", "BTH");         break;       }       buffer = buffer.slice(1);       _value = this.getBufValue(_bth.t, buffer);       if (_value === null) break;       if (typeof _bth.f !== "undefined") _value = _value * _bth.f;       result[_bth.n] = _value;       buffer = buffer.slice(getByteSize(_bth.t));     }     return result;   }, }; let lastPacketId = 0x100; // Function to retrieve sunset time function formatDate(date) {   let day = ("0" + date.getDate()).slice(-2);   let month = ("0" + (date.getMonth() + 1)).slice(-2); // Months are zero-based   let year = date.getFullYear();   let hours = ("0" + date.getHours()).slice(-2);   let minutes = ("0" + date.getMinutes()).slice(-2);   return day + "." + month + "." + year + " " + hours + ":" + minutes; } function fetchSunsetTime(callback) {   let astroServiceURL = "https://api.sunrise-sunset.org/json?lat=50.1234567&lng=9.1234567&formatted=0";      Shelly.call("HTTP.GET", { url: astroServiceURL }, function (result) {     if (result && result.code === 200) {       let data = JSON.parse(result.body);       if (data && data.results && data.results.sunset) {         let sunsetTime = new Date(data.results.sunset);         console.log("Sunset time: " + formatDate(sunsetTime));         callback(sunsetTime);       } else {         console.error("Error: Invalid data structure");         callback(null);       }     } else {       console.error("Error retrieving sunset time");       callback(null);     }   }); } // Function to check if it's after sunset function checkSunsetTime(callback) {   fetchSunsetTime(function(sunsetTime) {     if (sunsetTime) {       let currentTime = new Date();       callback(currentTime >= sunsetTime); // Return true if after sunset     } else {       callback(false);     }   }); } // Callback for BLE scanner function bleScanCallback(event, result) {   if (event !== BLE.Scanner.SCAN_RESULT) {     return;   }   if (     typeof result.service_data === "undefined" ||     typeof result.service_data[BTHOME_SVC_ID_STR] === "undefined"   ) {     logger("Missing service_data member", "Error");     return;   }   let unpackedData = BTHomeDecoder.unpack(     result.service_data[BTHOME_SVC_ID_STR]   );   if (     unpackedData === null ||     typeof unpackedData === "undefined" ||     unpackedData["encryption"]   ) {     logger("Encrypted devices are not supported", "Error");     return;   }   if (lastPacketId === unpackedData.pid) {     return;   }   lastPacketId = unpackedData.pid;   unpackedData["rssi"] = result.rssi;   unpackedData["mac"] = result.addr;   logger([unpackedData, result], "RECEIVED");   if (unpackedData["mac"] !== CONFIG.mac) {     return;   }   if (typeof unpackedData.motion !== "undefined") {     CONFIG.onMotionChange(unpackedData.motion === 1);   } } BLE.Scanner.Start({ duration: BLE.SCANNING_DURATION_CONTINUOUS }, bleScanCallback);  Edited 6 hours ago by al3x Quote Translate Revert translation? English (American) Finnish French German Italian Portuguese (European) Spanish Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.