Members Zyrkonim Posted July 8 Members Share Posted July 8 (edited) I am using two BLU Motion sensors to automate a Shelly 1PM Gen3 and control a relay connected to an air conditioner. The goal is to disconnect the relay after a specific period (currently set to 5 minutes but will be extended to 30 minutes) if no movement is detected during that time. Additionally, I have set a schedule to disable the automation at night (between 10 PM and 8 AM) and during weekends. As I am not an expert in creating scripts, I utilized some templates from the library and the help of ChatGPT to achieve a working solution. This is still a work in progress, as I plan to add more sensors in the future (such as window and motion sensors). The schedule functionality has not been fully tested yet due to the time consuming nature of following the actual schedule.  /******************* START CHANGE HERE *******************/ let CONFIG = {  debug: false,  active: false,  allowedMacAddresses: [   'E8:E0:7E:D0:AE:C3', 'B0:C7:DE:C4:D7:36'  ],  // To store the state of motion from each sensor  motionState: {   'E8:E0:7E:D0:AE:C3': false,   'B0:C7:DE:C4:D7:36': false  },  // Timer for turning off the relay  offTimer: null,  // Define the start and end times of the interval (in minutes)  intervalStartFriday: (5 * 24 * 60) + (15 * 60), // Friday 15:00  intervalEndSaturday: (6 * 24 * 60) + (21 * 60), // Saturday 21:00  nightlyIntervalStart: 22 * 60, // 22:00  nightlyIntervalEnd: 8 * 60, // 08:00  isWithinBlockedInterval: function() {   let now = new Date();   let day = now.getDay(); // 0 = Sunday, 1 = Monday, ..., 5 = Friday, 6 = Saturday   let hour = now.getHours();   let minute = now.getMinutes();   let currentTime = (day * 24 * 60) + (hour * 60) + minute;   // Check if the current time is within the blocked interval on Friday to Saturday   let withinWeekendBlockedInterval = currentTime >= CONFIG.intervalStartFriday && currentTime <= CONFIG.intervalEndSaturday;   // Check if the current time is within the nightly blocked interval   let withinNightlyBlockedInterval = (hour >= 22 && hour < 24) || (hour >= 0 && hour < 8);   return withinWeekendBlockedInterval || withinNightlyBlockedInterval;  },  motionHandler: function (motion, eventData) {   // Update the motion state of the corresponding sensor   CONFIG.motionState[eventData.address.toLowerCase()] = motion;   // Check if there is any motion detected by either sensor   let anyMotion = CONFIG.motionState['e8:e0:7e:d0:ae:c3'] || CONFIG.motionState['b0:c7:de:c4:d7:36'];   // If any motion is detected, clear the timer   if (anyMotion) {    if (CONFIG.offTimer) {     Timer.clear(CONFIG.offTimer);     CONFIG.offTimer = null;    }   } else {    // If no motion is detected from both sensors, set a timer to turn off the relay after 5 minutes    if (!CONFIG.offTimer && !CONFIG.isWithinBlockedInterval()) {     CONFIG.offTimer = Timer.set(300000, false, function () {      Shelly.call("Switch.Set", { id: 0, on: false }, function (result) {       console.log("Relay turned off due to no motion:", JSON.stringify(result));      });      CONFIG.offTimer = null; // Clear the timer reference     });    }   }   console.log("Motion detected from", eventData.address, ":", motion);  },  illuminanceHandler: function (illuminance, eventData) {   let topic = eventData.address + "/illuminance";   MQTT.publish(topic, String(illuminance));  },  onStatusUpdate: function (eventData) {   // Do nothing at the moment.  } }; /******************* STOP CHANGE HERE *******************/ let ALLTERCO_MFD_ID_STR = "0ba9"; let BTHOME_SVC_ID_STR = "fcd2"; let uint8 = 0; let int8 = 1; let uint16 = 2; let int16 = 3; let uint24 = 4; let int24 = 5; function logger(message, prefix) {  if (!CONFIG.debug) {   return;  }  let finalText = "";  if (Array.isArray(message)) {   for (let i = 0; i < message.length; i++) {    finalText = finalText + " " + JSON.stringify(message[i]);   }  } else {   finalText = JSON.stringify(message);  }  if (typeof prefix !== "string") {   prefix = "";  } else {   prefix = prefix + ":";  }  console.log(prefix, finalText); } 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 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) << 8) | 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) << 8) | 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;  }, }; function onReceivedPacket (data) {  if(CONFIG._processedMacAddresses !== null) {   if(CONFIG._processedMacAddresses.indexOf(data.address) < 0) {    logger(["Received event from", data.address, "outside of the allowed addresses"], "Info");    return;   }  }  if (   typeof CONFIG.motionHandler === "function" &&   typeof data.motion !== "undefined"  ) {   CONFIG.motionHandler(data.motion === 1, data);   logger("Motion handler called", "Info");  }  if (   typeof CONFIG.illuminanceHandler === "function" &&   typeof data.illuminance !== "undefined"  ) {   CONFIG.illuminanceHandler(data.illuminance, data);   logger("Illuminance handler called", "Info");  }  if (typeof CONFIG.onStatusUpdate === "function") {   CONFIG.onStatusUpdate(data);   logger("New status update", "Info");  } } let lastPacketId = 0x100; 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"  ) {   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.address = result.addr;  onReceivedPacket(unpackedData); } function init() {  if (typeof CONFIG === "undefined") {   console.log("Error: Undefined config");   return;  }  let BLEConfig = Shelly.getComponentConfig("ble");  if (!BLEConfig.enable) {   console.log(    "Error: The Bluetooth is not enabled, please enable it from settings"   );   return;  }  if (BLE.Scanner.isRunning()) {   console.log("Info: The BLE gateway is running, the BLE scan configuration is managed by the device");  }  else {   let bleScanner = BLE.Scanner.Start({     duration_ms: BLE.Scanner.INFINITE_SCAN,     active: CONFIG.active   });   if(!bleScanner) {    console.log("Error: Can not start new scanner");   }  }  if (   typeof CONFIG.allowedMacAddresses !== "undefined"  ) {   if(CONFIG.allowedMacAddresses !== null) {    CONFIG._processedMacAddresses =     CONFIG      .allowedMacAddresses      .map(function (mac) { return mac.toLowerCase(); })      .filter(function (value, index, array) { return array.indexOf(value) === index; })   }   else {    CONFIG._processedMacAddresses = null;   }  }  BLE.Scanner.Subscribe(BLEScanCallback); } init();  Key functionalities: Motion Detection: Monitors motion from specified BLE sensors. If motion is detected, it keeps the relay on. If no motion is detected for 5 minutes and it's not within blocked time intervals, it turns the relay off. Blocked Time Intervals: Prevents the relay from being turned off during specified times: Friday 15:00 to Saturday 21:00 Nightly from 22:00 to 08:00 BLE Scanning: Continuously scans for BLE devices, processes data from allowed MAC addresses, and handles motion and illuminance events. Logging: Provides optional debug logging for detailed information about the script’s operation. Edited July 8 by Zyrkonim 1 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.