From f3f34a999b8fb3fa4a27b9ac41b25dce4f4c4015 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 Aug 2025 02:36:52 +0000 Subject: [PATCH 1/3] Initial plan From 8d7b7e59ff04fa751aa8063f1584d33c4043ea0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 Aug 2025 02:42:22 +0000 Subject: [PATCH 2/3] Fix critical bugs and improve JSDoc documentation Co-authored-by: bthos <13679349+bthos@users.noreply.github.com> --- wb-rules-modules/module_ActionButtons.js | 131 +++++++++++++---------- wb-rules-modules/module_Utilities.js | 13 +-- wb-rules/rules_Buttons.js | 61 ++++++++--- wb-rules/virtual_Weather.js | 111 ++++++++++++------- 4 files changed, 205 insertions(+), 111 deletions(-) diff --git a/wb-rules-modules/module_ActionButtons.js b/wb-rules-modules/module_ActionButtons.js index 61ea7ea..972b20a 100644 --- a/wb-rules-modules/module_ActionButtons.js +++ b/wb-rules-modules/module_ActionButtons.js @@ -1,79 +1,110 @@ /** + * Wirenboard Multi-Action Button Module * Version: 0.3.2 * - * Function that identifies what kind of button press was performed: - * - Short press: single, double, triple, etc. - you can add more if you need - * - Long press (and release) - * - Long press (without release) - * Script also assigns an action for each type of button press. - * - * @param {string} trigger - Name of device and control in the following format: "/". - * @param {object} action - Defines actions to be taken for each type of button press. - * Key: "singlePress" or "doublePress" or "triplePress" or "longPress" or "longRelease". - * Value: Object having the following structure {func: , prop: } - * Example: - * { - * singlePress: {func: myFunc1, prop: ["wb-mr6c_1", "K1"]}, - * doublePress: {func: myFunc2, prop: ["wb-mrgbw-d_2", "RGB", "255;177;85"]}, - * triplePress: {func: myFunc3, prop: []}, - * longPress: {func: myFunc4, prop: []}, - * longRelease: {func: myFunc5, prop: []} - * } - * @param {number} timeToNextPress - Time (ms) after button up to wait for the next press before reseting the counter. Default is 300 ms. - * @param {number} timeOfLongPress - Time (ms) after button down to be considered as as a long press. Default is 1000 ms (1 sec). - * @param {number} intervalOfRepeat - Time (ms) before repeating action specified in LongPress action. Default is 100 ms. - * - * Note: In case longRelease function defined, longPress function will repeate till button is released. - * In case longRelease function not defined, only one action will be executed for longPress. + * This module provides functionality to detect and handle different types of button press events: + * - Single, double, triple short presses (extensible to more) + * - Long press with optional release detection + * - Configurable timing parameters + * - Repeating actions during long press */ var ActionButtons = {}; +/** + * Registers button press handlers for a specific trigger device/control + * + * @param {string} trigger - Device and control name in format "/" (e.g., "wb-gpio/EXT1_IN1") + * @param {object} action - Configuration object defining actions for different press types + * @param {object} [action.singlePress] - Single press action: {func: Function, prop: Array} + * @param {object} [action.doublePress] - Double press action: {func: Function, prop: Array} + * @param {object} [action.triplePress] - Triple press action: {func: Function, prop: Array} + * @param {object} [action.longPress] - Long press action: {func: Function, prop: Array} + * @param {object} [action.longRelease] - Long press release action: {func: Function, prop: Array} + * @param {number} [timeToNextPress=300] - Time (ms) to wait for next press before processing action + * @param {number} [timeOfLongPress=1000] - Time (ms) to consider press as long press + * @param {number} [intervalOfRepeat=100] - Time (ms) interval for repeating longPress action + * + * @example + * // Basic usage with single and double press + * ActionButtons.onButtonPress( + * "wb-gpio/EXT1_IN1", + * { + * singlePress: {func: switchRelay, prop: ["wb-mr6c_33", "K1"]}, + * doublePress: {func: switchRelayWithAutoOff, prop: ["wb-mr6c_33", "K2"]} + * } + * ); + * + * @example + * // Advanced usage with custom timing + * ActionButtons.onButtonPress( + * "wb-gpio/EXT1_IN2", + * { + * longPress: {func: switchRelay, prop: ["wb-mr6c_33", "K4"]} + * }, + * 300, 800 // Custom timing: 300ms between presses, 800ms for long press + * ); + * + * @note If longRelease is defined, longPress will repeat until button is released. + * If longRelease is not defined, longPress executes only once. + */ ActionButtons.onButtonPress = function (trigger, action, timeToNextPress, timeOfLongPress, intervalOfRepeat) { + // Input validation + if (typeof trigger !== "string" || !trigger.includes("/")) { + throw new Error("ActionButtons: trigger must be a string in format 'device/control'"); + } + + if (typeof action !== "object" || action === null) { + throw new Error("ActionButtons: action must be an object"); + } + // Set default values if not passed into function timeToNextPress = timeToNextPress || 300; timeOfLongPress = timeOfLongPress || 1000; intervalOfRepeat = intervalOfRepeat || 100; var buttonPressedCounter = 0; - // var actionRepeatCounter = 0; + var actionRepeatCounter = 0; var timerWaitNextShortPress = undefined; var timerLongPress = undefined; var timerWaitLongRelease = undefined; var isLongPressed = false; var isLongReleased = false; + // Generate unique rule name from trigger var ruleName = "on_button_press_" + trigger.replace("/", "_"); - log("LOG::Define WB Rule::", ruleName); + log("ActionButtons: Defining rule:", ruleName); defineRule(ruleName, { whenChanged: trigger, then: function (newValue, devName, cellName) { - // If button is pressed, wait for a Long Press + // Button pressed - start long press detection if (newValue) { - + // Clear any existing timers if (typeof timerWaitNextShortPress == "number") { - log("LOG::timerWaitNextShortPress(1)::", timerWaitNextShortPress); clearTimeout(timerWaitNextShortPress); timerWaitNextShortPress = undefined; } if (typeof timerLongPress == "number") { - log("LOG::timerLongPress::", timerLongPress); clearTimeout(timerLongPress); timerLongPress = undefined; } + + // Start long press timer timerLongPress = setTimeout(function () { - // Long Press identified, we will skip short press + // Long press detected isLongPressed = true; isLongReleased = false; buttonPressedCounter = 0; actionRepeatCounter = 1; + + // Execute long press action if (typeof action.longPress === "object") { if (typeof action.longPress.func === "function") { action.longPress.func.apply(this, action.longPress.prop); - // If Long Release action defined, we will repeat Long Press action till not released. Otherwise only 1 Long Press action is executed + // Setup repeating action if longRelease is defined if (typeof action.longRelease === "object") { if (typeof action.longRelease.func === "function") { timerWaitLongRelease = setInterval(function () { @@ -83,7 +114,6 @@ ActionButtons.onButtonPress = function (trigger, action, timeToNextPress, timeOf action.longPress.func.apply(this, action.longPress.prop); } } - // log(">>>>>> long press - press (" + actionRepeatCounter++ + ") <<<<<<"); } if(isLongReleased) { clearInterval(timerWaitLongRelease); @@ -91,78 +121,71 @@ ActionButtons.onButtonPress = function (trigger, action, timeToNextPress, timeOf }, intervalOfRepeat); } } - } } - // log(">>>>>> long press - press (" + actionRepeatCounter++ + ") <<<<<<"); timerLongPress = undefined; }, timeOfLongPress); } - // If button is released, then it is not a Long Press, start to count clicks + // Button released - handle short press counting or long press release else { if (!isLongPressed) { + // Handle short press sequence if (typeof timerLongPress == "number") { - log("LOG::timerLongPress::", timerLongPress); clearTimeout(timerLongPress); timerLongPress = undefined; } + buttonPressedCounter += 1; + if (typeof timerWaitNextShortPress == "number") { - log("LOG::timerWaitNextShortPress(2)::", timerWaitNextShortPress); clearTimeout(timerWaitNextShortPress); timerWaitNextShortPress = undefined; } + + // Start timer to wait for additional presses timerWaitNextShortPress = setTimeout(function () { switch (buttonPressedCounter) { - // Counter equals 1 - it's a single short press case 1: + // Single press detected if (typeof action.singlePress === "object") { if (typeof action.singlePress.func === "function") { action.singlePress.func.apply(this, action.singlePress.prop); } } - // log(">>>>>> short press - single <<<<<<"); break; - // Counter equals 2 - it's a double short press case 2: + // Double press detected if (typeof action.doublePress === "object") { if (typeof action.doublePress.func === "function") { action.doublePress.func.apply(this, action.doublePress.prop); } } - // log(">>>>>> short press - double <<<<<<"); break; - // Counter equals 3 - it's a triple short press case 3: + // Triple press detected if (typeof action.triplePress === "object") { if (typeof action.triplePress.func === "function") { action.triplePress.func.apply(this, action.triplePress.prop); } } - // log(">>>>>> short press - triple <<<<<<"); break; - // You can add more cases here to track more clicks + // Additional cases can be added here for more click patterns } - // Reset the counter + // Reset counter and timer buttonPressedCounter = 0; timerWaitNextShortPress = undefined; }, timeToNextPress); } - - // Catch button released after long press + + // Handle long press release else { if (typeof action.longRelease === "object") { if (typeof action.longRelease.func === "function") { - // if (typeof action.longRelease.prop === "array") { - action.longRelease.func.apply(this, action.longRelease.prop); - // } else { - // action.longRelease.func.apply(this, []); - // } + action.longRelease.func.apply(this, action.longRelease.prop); } } - // log(">>>>>> long press - release <<<<<<"); isLongPressed = false; isLongReleased = true; } diff --git a/wb-rules-modules/module_Utilities.js b/wb-rules-modules/module_Utilities.js index 4a729e7..0e7479a 100644 --- a/wb-rules-modules/module_Utilities.js +++ b/wb-rules-modules/module_Utilities.js @@ -16,12 +16,12 @@ Utilities.toCapitals = function (str, lower) { } /** - * Initialize MQTT Discovery topic for Home Assisstant - * @param {string} device - * @param {string} control + * Initialize MQTT Discovery topic for Home Assistant + * @param {string} device Device name/identifier + * @param {string} control Control name/identifier * @param {string} device_type Supported device_type: switch, light, cover, sensor - * @param {str} suggested_area - * @returns {boolean} + * @param {string} suggested_area Suggested area for the device + * @returns {boolean} Returns true if MQTT message was sent successfully */ Utilities.mqttDiscovery = function (device, control, device_type, suggested_area) { @@ -61,12 +61,13 @@ Utilities.mqttDiscovery = function (device, control, device_type, suggested_area case "sensor": break; default: - message = JSON.stringify(entity); + var message = JSON.stringify(entity); log("LOG::",message); break; } if (execute) { + var message = JSON.stringify(entity); runShellCommand("mosquitto_pub -t '" + error + "' -r -m '0'"); runShellCommand("mosquitto_pub -t '" + key + "' -m '" + message +"'"); } diff --git a/wb-rules/rules_Buttons.js b/wb-rules/rules_Buttons.js index 3084fbe..ec5948e 100644 --- a/wb-rules/rules_Buttons.js +++ b/wb-rules/rules_Buttons.js @@ -1,55 +1,90 @@ var room = require("module_ActionButtons"); /** - * Helper Functions + * Helper Functions for Button Actions + */ + +/** + * Toggles a relay control state + * @param {string} device - Device name/identifier (e.g., "wb-mr6c_33") + * @param {string} control - Control name (e.g., "K1") */ function switchRelay(device, control) { dev[device+"/"+control] = !dev[device + "/" + control]; } + +/** + * Toggles a relay control and sets its auto-on state + * @param {string} device - Device name/identifier (e.g., "wb-mr6c_33") + * @param {string} control - Control name (e.g., "K1") + */ function switchRelayWithAutoOn(device, control) { dev[device+"/"+control] = !dev[device + "/" + control]; dev[device+"/"+control+"_auto_on"] = !dev[device + "/" + control]; } + +/** + * Toggles a relay control and sets its auto-off state + * @param {string} device - Device name/identifier (e.g., "wb-mr6c_33") + * @param {string} control - Control name (e.g., "K1") + */ function switchRelayWithAutoOff(device, control) { dev[device+"/"+control] = !dev[device + "/" + control]; dev[device+"/"+control+"_auto_off"] = !dev[device + "/" + control]; } + +/** + * Switches RGB dimmer state - toggles between off (0;0;0) and saved RGB value + * @param {string} relayDevice - Relay device name that stores the RGB value + * @param {string} relayControl - Relay control name + * @param {string} dimmerDevice - Dimmer device name to control + */ function switchDimmerRGB(relayDevice, relayControl, dimmerDevice) { dev[relayDevice+"/"+relayControl] = true; if (dev[dimmerDevice+"/RGB"] !== "0;0;0") { dev[dimmerDevice+"/RGB"] = "0;0;0"; } else { - // log(dev[relayDevice+"/RGB"]); dev[dimmerDevice+"/RGB"] = dev[relayDevice + "/RGB"]; } } + +/** + * Sets a random RGB color for both relay and dimmer devices + * @param {string} relayDevice - Relay device name to store RGB value + * @param {string} relayControl - Relay control name + * @param {string} dimmerDevice - Dimmer device name to control + */ function setRandomRGB(relayDevice, relayControl, dimmerDevice) { dev[relayDevice+"/"+relayControl] = true; dev[relayDevice + "/RGB"] = "" + Math.floor(Math.random() * 255) + ";" + Math.floor(Math.random() * 255) + ";" + Math.floor(Math.random() * 255); - // log(relayDevice, "/RGB: ", dev[relayDevice + "/RGB"]); dev[dimmerDevice+"/RGB"] = dev[relayDevice + "/RGB"]; } + +/** + * Generates a random RGB color with specified brightness level + * @param {number} brightness - Brightness level from 0 to 5 (0 being darkest, 5 brightest) + * @returns {string} RGB color string in format "R;G;B" (e.g., "255;128;64") + */ function getRandColor(brightness) { - // Six levels of brightness from 0 to 5, 0 being the darkest + // Generate base random RGB values (0-255) var rgb = [Math.random() * 256, Math.random() * 256, Math.random() * 256]; - var mix = [brightness*51, brightness*51, brightness*51]; //51 => 255/5 + // Calculate brightness mix (51 = 255/5 for 5 brightness levels) + var mix = [brightness*51, brightness*51, brightness*51]; + // Blend random color with brightness and average the result var mixedrgb = [rgb[0] + mix[0], rgb[1] + mix[1], rgb[2] + mix[2]].map(function(x){ return Math.round(x/2.0)}) return mixedrgb.join(";"); } +/** + * Executes a function permanently with given parameters + * @param {Function} func - The function to execute + * @param {Array} prop - Array of parameters to pass to the function + */ function runPermanentAction(func, prop) { - if (typeof func === "function") { func.apply(this, prop); } } -function disalarmLeakage(leakage, sensor) { - // leakage.forEach(leakageDevice => { - // if (dev[leakageDevice+"/"+"Alarm"]) { - - // } - // }); -} //////////////////////////////////// diff --git a/wb-rules/virtual_Weather.js b/wb-rules/virtual_Weather.js index 1a0db8d..2a75485 100644 --- a/wb-rules/virtual_Weather.js +++ b/wb-rules/virtual_Weather.js @@ -1,8 +1,20 @@ -// Location coordinates -var latitude = "NN.NN"; -var longitude = "NN.NN"; +/** + * Virtual Weather Device for Wirenboard + * + * This module creates a virtual weather device that fetches weather data from OpenWeatherMap API + * and provides various weather-related controls and displays. + * + * Configuration required: + * - latitude: Geographic latitude coordinate + * - longitude: Geographic longitude coordinate + * - appid: API key from https://home.openweathermap.org/api_keys + */ + +// Location coordinates - CONFIGURE THESE VALUES +var latitude = "NN.NN"; // Replace with your latitude +var longitude = "NN.NN"; // Replace with your longitude // Generate Key here: https://home.openweathermap.org/api_keys -var appid = ""; +var appid = ""; // Replace with your API key defineVirtualDevice("weather", { title: "Weather", @@ -73,50 +85,72 @@ defineVirtualDevice("weather", { } }); +/** + * Parses weather data from OpenWeatherMap API response and updates virtual device controls + * + * This function reads the weather data JSON file downloaded by the API call, + * processes the data, and updates all weather-related device controls with + * formatted values including temperature, humidity, wind, and astronomical data. + * + * @throws {Error} If weather data file cannot be read or parsed + */ function getWeather() { - var weather_data = readConfig("/usr/weather/data.json"); - var directions = ["north", "north-west", "west", "south-west", "south", "south-east", "east", "north-east"]; - - var lastUpdated = new Date( format(weather_data.dt)*1000 ); - dev["weather/last_updated"] = lastUpdated.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); - dev["weather/description"] = format(weather_data.weather[0].description); - dev["weather/icon"] = "http://openweathermap.org/img/wn/" + format(weather_data.weather[0].icon) + "@4x.png"; - dev["weather/temperature"] = parseFloat( format(weather_data.main.temp) ); - dev["weather/feels_like"] = parseFloat( format(weather_data.main.feels_like) ); - dev["weather/temperature_min"] = parseFloat( format(weather_data.main.temp_min) ); - dev["weather/temperature_max"] = parseFloat( format(weather_data.main.temp_max) ); - dev["weather/pressure"] = parseFloat( format(weather_data.main.pressure) ); - dev["weather/humidity"] = parseFloat( format(weather_data.main.humidity) ); - dev["weather/wind_speed"] = parseFloat( format(weather_data.wind.speed) ); - - var angle = parseFloat( format(weather_data.wind.deg) ); - dev["weather/wind_direction"] = angle + "° " + directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]; + try { + var weather_data = readConfig("/usr/weather/data.json"); + var directions = ["north", "north-west", "west", "south-west", "south", "south-east", "east", "north-east"]; - dev["weather/clouds"] = format(weather_data.clouds.all) + "%"; - dev["weather/clouds_description"] = dev["weather/clouds"] + " (" + dev["weather/description"] + ")"; + // Parse and format timestamp + var lastUpdated = new Date( format(weather_data.dt)*1000 ); + dev["weather/last_updated"] = lastUpdated.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); + + // Weather description and icon + dev["weather/description"] = format(weather_data.weather[0].description); + dev["weather/icon"] = "http://openweathermap.org/img/wn/" + format(weather_data.weather[0].icon) + "@4x.png"; + + // Temperature data + dev["weather/temperature"] = parseFloat( format(weather_data.main.temp) ); + dev["weather/feels_like"] = parseFloat( format(weather_data.main.feels_like) ); + dev["weather/temperature_min"] = parseFloat( format(weather_data.main.temp_min) ); + dev["weather/temperature_max"] = parseFloat( format(weather_data.main.temp_max) ); + + // Atmospheric data + dev["weather/pressure"] = parseFloat( format(weather_data.main.pressure) ); + dev["weather/humidity"] = parseFloat( format(weather_data.main.humidity) ); + + // Wind data + dev["weather/wind_speed"] = parseFloat( format(weather_data.wind.speed) ); + var angle = parseFloat( format(weather_data.wind.deg) ); + dev["weather/wind_direction"] = angle + "° " + directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]; - var dateSunrise = new Date( format(weather_data.sys.sunrise)*1000 ); - var dateSunset = new Date( format(weather_data.sys.sunset)*1000 ); - // dev["weather/sunrise"] = dateSunrise.toString(); - // dev["weather/sunset"] = dateSunset.toString(); - dev["weather/sunrise"] = dateSunrise.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); - dev["weather/sunset"] = dateSunset.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); + // Cloud data + dev["weather/clouds"] = format(weather_data.clouds.all) + "%"; + dev["weather/clouds_description"] = dev["weather/clouds"] + " (" + dev["weather/description"] + ")"; - // log("Time: {}".format(weather_data.dt)); - // log("Temperature is: {}".format(weather_data.main.temp)); - // log("Humidity is: {}".format(weather_data.main.humidity)); - // log("Wind speed is: {}".format(weather_data.wind.speed)); + // Sunrise/sunset times + var dateSunrise = new Date( format(weather_data.sys.sunrise)*1000 ); + var dateSunset = new Date( format(weather_data.sys.sunset)*1000 ); + dev["weather/sunrise"] = dateSunrise.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); + dev["weather/sunset"] = dateSunset.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); + + } catch (error) { + log("Weather: Error processing weather data -", error); + } } -defineRule("weather_call", { // Periodic call to weather API +// Weather API call rule - executes every 30 minutes +defineRule("weather_call", { when: cron("@every 30m"), then: function() { + if (latitude === "NN.NN" || longitude === "NN.NN" || appid.includes(" Date: Fri, 29 Aug 2025 02:44:46 +0000 Subject: [PATCH 3/3] Enhance README.md with comprehensive documentation and improve example code Co-authored-by: bthos <13679349+bthos@users.noreply.github.com> --- README.md | 278 ++++++++++++++++++++++++++++++++++---- wb-rules/rules_Buttons.js | 41 +++++- 2 files changed, 291 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 954831a..0019607 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,264 @@ -# Multi-action Button module for Wirenboard +# Multi-action Button Module for Wirenboard -Function that identifies what kind of button press was performed: -- Short press: single, double, triple, etc. - you can add more if you need -- Long press (and release) -- Long press (without release) +A comprehensive module for the Wirenboard rule engine (@wirenboard/wb-rules) that provides advanced button press detection and handling capabilities. -Script also assigns an action for each type of button press. +## Features +This module can identify and handle different types of button press patterns: +- **Short press**: single, double, triple, etc. (extensible to more patterns) +- **Long press**: with optional automatic repetition +- **Long press with release**: separate actions for press and release events +- **Configurable timing**: customize detection thresholds and repeat intervals +- **Action assignment**: define specific functions to execute for each press type - @param {string} trigger - Name of device and control in the following format: "/". +## Installation - @param {object} action - Defines actions to be taken for each type of button press. +1. Copy the module files to your Wirenboard device: + ```bash + # Copy the main ActionButtons module + cp wb-rules-modules/module_ActionButtons.js /etc/wb-rules-modules/ + + # Copy utility module (optional, for MQTT Discovery) + cp wb-rules-modules/module_Utilities.js /etc/wb-rules-modules/ + ``` -> Key: "singlePress" or "doublePress" or "triplePress" or "longPress" or "longRelease". +2. Copy example rule files (optional): + ```bash + # Copy button handling examples + cp wb-rules/rules_Buttons.js /etc/wb-rules/ + + # Copy weather virtual device example + cp wb-rules/virtual_Weather.js /etc/wb-rules/ + ``` -> Value: Object having the following structure {func: , prop: } +3. Restart the wb-rules service: + ```bash + systemctl restart wb-rules + ``` -Example: +## Quick Start +```javascript +// Import the module +var room = require("module_ActionButtons"); + +// Basic single and double press detection +room.ActionButtons.onButtonPress( + "wb-gpio/EXT1_IN1", // Button trigger (device/control) + { + singlePress: { + func: switchRelay, + prop: ["wb-mr6c_33", "K1"] + }, + doublePress: { + func: switchRelayWithAutoOff, + prop: ["wb-mr6c_33", "K2"] + } + } +); +``` + +## API Reference + +### ActionButtons.onButtonPress(trigger, action, timeToNextPress, timeOfLongPress, intervalOfRepeat) + +Registers button press handlers for a specific device control. + +#### Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `trigger` | string | **required** | Device and control name in format `"/"` | +| `action` | object | **required** | Configuration object defining actions for different press types | +| `timeToNextPress` | number | 300 | Time (ms) to wait for next press before processing action | +| `timeOfLongPress` | number | 1000 | Time (ms) to consider press as long press | +| `intervalOfRepeat` | number | 100 | Time (ms) interval for repeating longPress action | + +#### Action Object Structure + +The `action` parameter should contain one or more of these properties: + +```javascript +{ + singlePress: {func: Function, prop: Array}, // Single press action + doublePress: {func: Function, prop: Array}, // Double press action + triplePress: {func: Function, prop: Array}, // Triple press action + longPress: {func: Function, prop: Array}, // Long press action + longRelease: {func: Function, prop: Array} // Long press release action +} +``` + +- `func`: Function to execute +- `prop`: Array of parameters to pass to the function + +## Examples + +### Basic Usage + +```javascript +var room = require("module_ActionButtons"); + +// Simple relay toggle on single press +room.ActionButtons.onButtonPress( + "wb-gpio/EXT1_IN1", + { + singlePress: { + func: switchRelay, + prop: ["wb-mr6c_33", "K1"] + } + } +); +``` + +### Multi-Action Configuration + +```javascript +// Multiple press types with different actions +room.ActionButtons.onButtonPress( + "wb-gpio/EXT1_IN1", + { + singlePress: { + func: switchRelay, + prop: ["wb-mr6c_33", "K1"] + }, + doublePress: { + func: switchRelayWithAutoOff, + prop: ["wb-mr6c_33", "K2"] + }, + triplePress: { + func: setRandomRGB, + prop: ["wb-mrgbw-d_1", "RGB", "wb-led_1"] + }, + longPress: { + func: switchRelayWithAutoOff, + prop: ["wb-mr6c_33", "K3"] + } + } +); +``` + +### Custom Timing + +```javascript +// Custom timing: 500ms between presses, 800ms for long press +room.ActionButtons.onButtonPress( + "wb-gpio/EXT1_IN2", + { + longPress: { + func: switchRelay, + prop: ["wb-mr6c_33", "K4"] + } + }, + 500, // timeToNextPress + 800 // timeOfLongPress +); +``` + +### Long Press with Repetition + +```javascript +// Long press that repeats every 200ms until released +room.ActionButtons.onButtonPress( + "wb-gpio/EXT1_IN3", + { + longPress: { + func: dimmerUp, // Function called repeatedly + prop: ["wb-dimmer_1"] + }, + longRelease: { + func: dimmerStop, // Function called on release + prop: ["wb-dimmer_1"] + } + }, + 300, // timeToNextPress + 1000, // timeOfLongPress + 200 // intervalOfRepeat +); +``` + +## Helper Functions + +The repository includes several helper functions for common Wirenboard operations: + +### Relay Control Functions + +```javascript +// Toggle relay state +function switchRelay(device, control) + +// Toggle relay with auto-on functionality +function switchRelayWithAutoOn(device, control) + +// Toggle relay with auto-off functionality +function switchRelayWithAutoOff(device, control) ``` - { - singlePress: {func: myFunc1, prop: ["wb-mr6c_1", "K1"]}, - doublePress: {func: myFunc2, prop: ["wb-mrgbw-d_2", "RGB", "255;177;85"]}, - triplePress: {func: myFunc3, prop: []}, - longPress: {func: myFunc4, prop: []}, - longRelease: {func: myFunc5, prop: []} - } + +### RGB/Dimmer Functions + +```javascript +// Switch RGB dimmer (toggle between off and saved color) +function switchDimmerRGB(relayDevice, relayControl, dimmerDevice) + +// Set random RGB color +function setRandomRGB(relayDevice, relayControl, dimmerDevice) + +// Generate random color with brightness level (0-5) +function getRandColor(brightness) ``` - @param {number} timeToNextPress - Time (ms) after button up to wait for the next press before reseting the counter. Default is 300 ms. - - @param {number} timeOfLongPress - Time (ms) after button down to be considered as as a long press. Default is 1000 ms (1 sec). - - @param {number} intervalOfRepeat - Time (ms) before repeating action specified in LongPress action. Default is 100 ms. - -Note: In case longRelease function defined, longPress function will repeate till button is released. - In case longRelease function not defined, only one action will be executed for longPress. +## Additional Modules + +### Utilities Module + +Provides utility functions for MQTT Discovery and text formatting: + +```javascript +var utils = require("module_Utilities"); + +// Capitalize text +var title = utils.Utilities.toCapitals("my device name"); + +// Setup MQTT Discovery for Home Assistant +utils.Utilities.mqttDiscovery("wb-mr6c_33", "K1", "switch", "Living Room"); +``` + +### Virtual Weather Device + +Example implementation of a weather data virtual device using OpenWeatherMap API: + +1. Configure your location and API key in `virtual_Weather.js` +2. The device automatically fetches weather data every 30 minutes +3. Manual updates available via the `get_update` button + +## Troubleshooting + +### Common Issues + +1. **Button not responding**: Check that the trigger device/control path is correct +2. **Function not found**: Ensure helper functions are defined before calling `onButtonPress` +3. **Timer conflicts**: Each button trigger creates its own isolated timer context + +### Debug Mode + +Enable debug logging by modifying the log statements in the module: + +```javascript +// Change this line in module_ActionButtons.js +log("ActionButtons: Defining rule:", ruleName); +``` + +### Testing + +Test button functionality using the Wirenboard web interface: +1. Navigate to "Devices & Controls" +2. Find your GPIO input device +3. Manually toggle the control to simulate button presses +4. Check the logs for action execution + +## License + +This project is released under CC0 1.0 Universal (Public Domain). See [LICENSE](LICENSE) for details. + +## Contributing + +Feel free to submit issues and enhancement requests. This module is designed to be easily extensible for additional button press patterns and timing configurations. diff --git a/wb-rules/rules_Buttons.js b/wb-rules/rules_Buttons.js index ec5948e..439f371 100644 --- a/wb-rules/rules_Buttons.js +++ b/wb-rules/rules_Buttons.js @@ -1,3 +1,19 @@ +/** + * Button Action Rules Example + * + * This file demonstrates how to use the Multi-Action Button Module + * for Wirenboard devices. It includes: + * + * - Helper functions for common button actions + * - Example button configurations + * - Best practices for button handling + * + * To use this file: + * 1. Modify device names to match your hardware + * 2. Customize functions for your specific needs + * 3. Add or remove button configurations as required + */ + var room = require("module_ActionButtons"); /** @@ -86,8 +102,17 @@ function runPermanentAction(func, prop) { } } //////////////////////////////////// +// Button Configuration Examples +//////////////////////////////////// - +/** + * Example 1: Multi-action button with single, double, and long press + * + * This configuration demonstrates: + * - Single press: Toggle K1 relay + * - Double press: Toggle K2 relay with auto-off + * - Long press: Toggle K3 relay with auto-off + */ room.ActionButtons.onButtonPress( "wb-gpio/EXT1_IN1", { @@ -105,6 +130,14 @@ room.ActionButtons.onButtonPress( } } ); + +/** + * Example 2: Simple long press button with custom timing + * + * This configuration demonstrates: + * - Long press only (no short press actions) + * - Custom timing: 300ms wait time, 800ms long press threshold + */ room.ActionButtons.onButtonPress( "wb-gpio/EXT1_IN2", { @@ -113,5 +146,9 @@ room.ActionButtons.onButtonPress( prop: ["wb-mr6c_33", "K4"] } }, - 300, 800 + 300, // timeToNextPress: 300ms between presses + 800 // timeOfLongPress: 800ms to trigger long press ); + +// Log successful initialization +log("Button rules loaded successfully!");