import java.util.concurrent.locks.ReentrantLock import org.eclipse.smarthome.model.script.ScriptServiceUtil //import java.util.HashMap var ReentrantLock arduino_writer = new ReentrantLock var ReentrantLock auto_runner_lock = new ReentrantLock val Integer sensor_default_hyst = 20 val Integer sensor_default_dark = 80 val Integer sensor_default_sunshine = 200 val Integer default_lockout_time = 1 // default time in minutes to wait after engine run (valid for automation mode only) var Timer timer_main = null var String timer_queue = "" rule "Startup" when System started then logInfo ("STARTUP", "System Starting from startup rule "+HOME_AWAY.state) if (HOME_AWAY.state == OFF) { logInfo ("STARTUP", "It is off") } if ( AUTO_MODE.state == NULL ) { AUTO_MODE.postUpdate(0) } // The default auto mode is neutral if ( S_light_southffxDarkTreshold.state == NULL ) { S_light_southffxDarkTreshold.postUpdate(sensor_default_dark) } if ( S_light_southffxSunshineTreshold.state == NULL ) { S_light_southffxSunshineTreshold.postUpdate(sensor_default_sunshine) } if ( S_light_southffxHysteresis.state == NULL ) { S_light_southffxHysteresis.postUpdate(sensor_default_hyst) } // Reset timer timer_main = null // TODO: Temporary entered here until sensor is available (then delete next line) S_rain_southffxRAIN.postUpdate(0) // STARTUP SETTINGS PER WINDOW if ( Z1_S_PETER_BIGxPlants.state == NULL ) { Z1_S_PETER_BIGxPlants.postUpdate(1) } if ( Z1_S_PETER_BIGxDoorProtect.state == NULL ) { Z1_S_PETER_BIGxDoorProtect.postUpdate(0) } if ( Z1_S_PETER_BIGxRainProtect.state == NULL ) { Z1_S_PETER_BIGxRainProtect.postUpdate(1) } if ( Z1_S_PETER_BIGxCoolingAllowed.state == NULL ) { Z1_S_PETER_BIGxCoolingAllowed.postUpdate(0) } if ( Z1_S_PETER_BIGxLastRunTime.state == NULL ) { Z1_S_PETER_BIGxLastRunTime.postUpdate(new DateTimeType().toString) } // updating last run time on startup to allow all sensors startup and data transfer if ( Z1_S_PETER_BIGxArduinoID.state == NULL ) { Z1_S_PETER_BIGxArduinoID.postUpdate(11) } if ( Z1_S_PETER_BIGxEngineID.state == NULL ) { Z1_S_PETER_BIGxEngineID.postUpdate(1) } end rule "Timer_expired" when Item MAIN_TIMER_TRIGGER received command ON then logInfo ("Rollershutter", "Timer expired with:" + timer_queue) var String local_item_name = null try { auto_runner_lock.lock() if (timer_queue !== null) { //queue present var String local_timer_queue = timer_queue MAIN_TIMER_TRIGGER.postUpdate(OFF) timer_main = null while ((local_timer_queue !== null) && (local_timer_queue.length() >5 )) { local_item_name = local_timer_queue.split("-").get(0) local_timer_queue = local_timer_queue.replaceFirst(".*?-", "") logInfo ("Rollershutter", "Timer running:" + local_item_name + " remaining queue:" + local_timer_queue + "END") if ( (local_item_name !== null) && (local_item_name.length() > 5) ) { logInfo ("DEBUG", "Sending ON to:" + local_item_name.toString) sendCommand (local_item_name.toString, "ON") } // item correct } // loop through queue } //queue present } catch(Throwable t) {} finally { auto_runner_lock.unlock() } //finally end rule "direct_run_gZx_group" when Member of gZ_ALL received command then try { logInfo("Rollershutter", "Member " + triggeringItem.name + " received " + receivedCommand) arduino_writer.lock() val String split_part0 = triggeringItem.name.toString.split("x").get(0) val String split_part1 = triggeringItem.name.toString.split("x").get(1) val String split_part2 = triggeringItem.name.toString.split("x").get(2) if (receivedCommand == DOWN) { ARDUINO_SEND_Args.sendCommand(split_part1 + " " + split_part2 +" 1") ARDUINO_SEND.sendCommand(ON) postUpdate (split_part0 + "xLastRunDirection","1") postUpdate (split_part0 + "xLastRunTime", new DateTimeType().toString) } if (receivedCommand == UP) { // logInfo ("Rollershutter", "GOING UP " + split_part0 + "|" + split_part0 as String) ARDUINO_SEND_Args.sendCommand(split_part1 + " " + split_part2 +" 2") ARDUINO_SEND.sendCommand(ON) postUpdate (split_part0 + "xLastRunDirection","2") postUpdate (split_part0 + "xLastRunTime", new DateTimeType().toString) } if (receivedCommand == STOP) { ARDUINO_SEND_Args.sendCommand(split_part1 + " " + split_part2 +" 0") ARDUINO_SEND.sendCommand(ON) //postUpdate (split_part0 + "xLastRunDirection",0) //Commented out as for stop we keep last direction used before (important for automation recovery - if uncommented the automation will never pick up stopped run) postUpdate (split_part0 + "xLastRunTime", new DateTimeType().toString) } logInfo("Rollershutter", "Member " + triggeringItem.name + " received " + receivedCommand + " translated to" + split_part1 + "+" + split_part2 ) }catch(Throwable t) {} finally { arduino_writer.unlock() } end rule "Sensor triggers" when Member of gS_TRIGGER_ALL changed then val String split_part0 = triggeringItem.name.toString.split("x").get(0) val String local_tiggered_group_name = "g" + split_part0 + "xTRIGGER" logInfo("Rollershutter", "Triggers group:" + local_tiggered_group_name ) val local_tiggered_group_item = ScriptServiceUtil.getItemRegistry.getItem(local_tiggered_group_name) as GroupItem local_tiggered_group_item.getMembers.forEach [i | i.sendCommand (ON) ] end rule "South side re-calculation" when Member of gS_light_southffxTRIGGER received command ON then try { auto_runner_lock.lock() val String local_split_part0 = triggeringItem.name.toString.split("x").get(0) val local_auto_manual = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xAutoManual") as NumberItem val local_auto_mode = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xCoolingHeating") as NumberItem val local_plants = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xPlants") as NumberItem val local_door_protect = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xDoorProtect") as NumberItem val local_rain_protect = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xRainProtect") as NumberItem val local_cooling_allowed = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xCoolingAllowed") as NumberItem val local_arduino_id = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xArduinoID") as NumberItem val local_engine_id = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xEngineID") as NumberItem val local_last_run_time = new DateTime(ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xLastRunTime").state.toString) val local_last_run_direction = ScriptServiceUtil.getItemRegistry.getItem(local_split_part0 + "xLastRunDirection") as NumberItem val local_SLD = ScriptServiceUtil.getItemRegistry.getItem("S_light_southffxSLD") as NumberItem val local_RAIN = ScriptServiceUtil.getItemRegistry.getItem("S_rain_southffxRAIN") as NumberItem var local_calculated_run_direction = 0 // We can safelly use 0 as run direction is never calculated to stop var local_new_run_direction = 0 // We can safelly use 0 as run direction is never calculated to stop // TODO delete next line val local_stupid_variable = null // Check if we have all variables logInfo("Rollershutter", "SouthSide recalc " + local_split_part0 + " received " + receivedCommand ) triggeringItem.postUpdate(OFF) logInfo("DEBUG", "Values: AUTO_MANUAL:"+AUTO_MANUAL.state+" local_auto_manual:"+local_auto_manual.state+" AUTO_MODE:"+AUTO_MODE.state+" local_auto_mode:"+local_auto_mode.state+" HOME_AWAY:"+HOME_AWAY.state+" DAY_NIGHT:"+DAY_NIGHT.state+" local_SLD:"+local_SLD.state+" local_RAIN:"+local_RAIN.state+" local_plants:"+local_plants.state+" local_door_protect:"+local_door_protect.state+" local_rain_protect:"+local_rain_protect.state+" local_cooling_allowed:"+local_cooling_allowed.state+" local_last_run_time:"+local_last_run_time+" local_last_run_direction:"+local_last_run_direction.state) if ( ((AUTO_MANUAL.state != NULL) || (local_auto_manual.state !=NULL)) && ((AUTO_MODE.state != NULL) || (local_auto_mode.state != NULL)) && (HOME_AWAY.state != NULL) && (DAY_NIGHT.state != NULL) && (local_SLD.state != NULL) && (local_RAIN.state != NULL) && (local_plants.state != NULL) && (local_door_protect.state != NULL) && (local_rain_protect.state != NULL) && (local_cooling_allowed.state != NULL) && (local_arduino_id.state != NULL) && (local_engine_id.state != NULL) ) { if ( ((local_auto_manual.state == NULL) && (AUTO_MANUAL.state == ON)) || (local_auto_manual.state == 1) || ((local_auto_manual.state == 9) && (AUTO_MANUAL.state == ON))) { // Auto mode enabled var local_clean_chn = 0 // Calculate direction if (((local_auto_mode.state == NULL) || (local_auto_mode.state == 9)) && (AUTO_MODE.state != NULL)) { local_clean_chn = AUTO_MODE.state} if ((local_auto_mode.state == 0) || (local_auto_mode.state == 1) || (local_auto_mode.state == 2)) { local_clean_chn = local_auto_mode.state } // Raining and rain protection activated if ((local_RAIN.state == 1) && (local_rain_protect.state == 1) && (local_door_protect.state == 1) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } if ((local_RAIN.state == 1) && (local_rain_protect.state == 1) && (local_door_protect.state == 0) && (DAY_NIGHT.state == OFF) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } if ((local_RAIN.state == 1) && (local_rain_protect.state == 1) && (local_door_protect.state == 0) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 1 } // dry or (Raining, no rain protection), dark, cooling not allowed if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 2) && (local_cooling_allowed.state == 0) && (local_door_protect.state == 1) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 2) && (local_cooling_allowed.state == 0) && (local_door_protect.state == 0) && (DAY_NIGHT.state == OFF) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 2) && (local_cooling_allowed.state == 0) && (local_door_protect.state == 0) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 1 } // dry or (Raining, no rain protection), dark, cooling allowed, heating or neutral set if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 2) && (local_cooling_allowed.state == 1) && ((local_clean_chn == 0) || (local_clean_chn == 1)) && (local_door_protect.state == 1) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 2) && (local_cooling_allowed.state == 1) && ((local_clean_chn == 0) || (local_clean_chn == 1)) && (local_door_protect.state == 0) && (DAY_NIGHT.state == OFF) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } logInfo("DEBUG", "TEST1") if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 2) && (local_cooling_allowed.state == 1) && ((local_clean_chn == 0) || (local_clean_chn == 1)) && (local_door_protect.state == 0) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 1 } // dry or (Raining, no rain protection), dark, cooling allowed, cooling set if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 2) && (local_cooling_allowed.state == 1) && (local_clean_chn == 2) && (DAY_NIGHT.state == OFF) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 2 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 2) && (local_cooling_allowed.state == 1) && (local_clean_chn == 2) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 2 } // dry or (Raining, no rain protection), Light, neutral, home if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 1) && (local_clean_chn == 0) && (HOME_AWAY.state == OFF) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 2 } // dry or (Raining, no rain protection), Light, neutral, away if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 1) && (local_clean_chn == 0) && (HOME_AWAY.state == ON) && (local_plants.state == 1) ) { local_calculated_run_direction = 2 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 1) && (local_clean_chn == 0) && (HOME_AWAY.state == ON) && (local_plants.state == 0) ) { local_calculated_run_direction = 1 } // dry or (Raining, no rain protection), Light, cooling or heating logInfo("DEBUG", "TEST2") if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 1) && ((local_clean_chn == 2) || (local_clean_chn == 1)) && (local_plants.state == 1) && (DAY_NIGHT.state == OFF) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 2 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 1) && ((local_clean_chn == 2) || (local_clean_chn == 1)) && (local_plants.state == 1) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 2 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 1) && ((local_clean_chn == 2) || (local_clean_chn == 1)) && (local_plants.state == 0) && (local_door_protect.state == 1) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } logInfo("DEBUG", "TEST2.3") if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 1) && ((local_clean_chn == 2) || (local_clean_chn == 1)) && (local_plants.state == 0) && (local_door_protect.state == 0) && (DAY_NIGHT.state == OFF) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } logInfo("DEBUG", "TEST2.3.1") if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 1) && ((local_clean_chn == 2) || (local_clean_chn == 1)) && (local_plants.state == 0) && (local_door_protect.state == 0) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 1 } // dry or (Raining, no rain protection), Sun, neutral or heating logInfo("DEBUG", "TEST2.3.2:"+"local_RAIN="+local_RAIN.state+" local_rain_protect="+local_rain_protect.state+" local_RAIN="+local_RAIN.state+" local_SLD="+local_SLD.state+" local_clean_chn="+local_clean_chn+" HOME_AWAY="+HOME_AWAY.state+" DAY_NIGHT="+DAY_NIGHT.state+" local_calculated_run_direction="+local_calculated_run_direction+" local_stupid_variable"+local_stupid_variable+".") if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 0) && ((local_clean_chn == 2) || (local_clean_chn == 0)) && (HOME_AWAY.state == OFF) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 2 } logInfo("DEBUG", "TEST2.5") if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 0) && ((local_clean_chn == 2) || (local_clean_chn == 0)) && (HOME_AWAY.state == ON) && (local_plants.state == 1) ) { local_calculated_run_direction = 2 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 0) && ((local_clean_chn == 2) || (local_clean_chn == 0)) && (HOME_AWAY.state == ON) && (local_plants.state == 0) ) { local_calculated_run_direction = 1 } // dry or (Raining, no rain protection), Sun, cooling logInfo("DEBUG", "TEST2.7 ") if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 0) && (local_clean_chn == 1) && (local_door_protect.state == 1) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 0) && (local_clean_chn == 1) && (local_door_protect.state == 0) && (DAY_NIGHT.state == OFF) && (HOME_AWAY.state == ON) ) { local_calculated_run_direction = 1 } if ((((local_RAIN.state == 1) && (local_rain_protect.state == 0)) || (local_RAIN.state == 0)) && (local_SLD.state == 0) && (local_clean_chn == 1) && (local_door_protect.state == 0) && (DAY_NIGHT.state == ON) ) { local_calculated_run_direction = 1 } // Check calculated direction against last direction and against manual run logInfo("DEBUG", "TEST3") if (local_last_run_direction.state == NULL) { local_new_run_direction = local_calculated_run_direction } else { // Last direction is not NULL if (local_calculated_run_direction > 0) { // Calculation result in requested movement if (local_calculated_run_direction == local_last_run_direction.state) { postUpdate (local_split_part0 + "xLastRunDirection", (local_calculated_run_direction + 10)) } // If automation wants to go the same direction as last manual action switch back to automation else { if ((local_calculated_run_direction == 1) && (local_last_run_direction.state == 12) ) {local_new_run_direction = local_calculated_run_direction} if ((local_calculated_run_direction == 2) && (local_last_run_direction.state == 11) ) {local_new_run_direction = local_calculated_run_direction} } } // Calculation result in requested movement } // Last direction is not NULL // START MOTOR logInfo("Rollershutter", local_split_part0+" last run was "+local_last_run_direction.state+" calculated run "+local_calculated_run_direction+" new run "+local_new_run_direction) if (local_new_run_direction > 0) { // We have new run direction val local_run_time = now.toString val local_max_last_run_time = now.minusMinutes(default_lockout_time) if (local_max_last_run_time.isBefore (local_last_run_time)) { logInfo("DEBUG", "Last run check failed queing "+local_last_run_time+"min item: "+local_split_part0) timer_queue = timer_queue + triggeringItem.name.toString + "-" if (timer_main === null) { logInfo("DEBUG", "Starting timer for "+default_lockout_time+"min item: "+local_split_part0) timer_main = createTimer(now.plusMinutes(default_lockout_time), [| MAIN_TIMER_TRIGGER.sendCommand(ON) ]) // Timer creation } // start timer } // Timeout not yet expired else { // Execute command, there is no timeout issue, no trigger needed logInfo("DEBUG", "Executing Arduino "+local_arduino_id.state+" engine "+ local_engine_id.state + " direction "+local_new_run_direction+" time "+local_run_time) if (local_new_run_direction == 1) { //ARDUINO_SEND_Args.sendCommand(local_arduino_id.state + " " + local_engine_id.state +" 1") //ARDUINO_SEND.sendCommand(ON) postUpdate (local_split_part0 + "xLastRunDirection",11) postUpdate (local_split_part0 + "xLastRunTime", local_run_time) } if (local_new_run_direction == 2) { //ARDUINO_SEND_Args.sendCommand(local_arduino_id.state + " " + local_engine_id.state +" 2") //ARDUINO_SEND.sendCommand(ON) postUpdate (split_part0 + "xLastRunDirection",12) postUpdate (split_part0 + "xLastRunTime", local_run_time) } } // Execute command, there is no timeout issue, no trigger needed } // We have new run direction } // Auto mode enabled } // All varialbes exists }catch(Throwable t) {} finally { auto_runner_lock.unlock() } //finally end rule "South SLD" // 0:Sunshine 1:Light 2:Dark when Item S_light_southffxINRAW changed then //logInfo("Light", "Light changed input is " + S_light_southffxSLD.state + "/" + S_light_southffxINRAW.state) if ( S_light_southffxDarkTreshold.state == NULL ) { S_light_southffxDarkTreshold.postUpdate(sensor_default_dark) } if ( S_light_southffxSunshineTreshold.state == NULL ) { S_light_southffxSunshineTreshold.postUpdate(sensor_default_sunshine) } if ( S_light_southffxHysteresis.state == NULL ) { S_light_southffxHysteresis.postUpdate(sensor_default_hyst) } if ( S_light_southffxSLD.state == NULL ) { if ( (S_light_southffxINRAW.state as Number) <= (S_light_southffxDarkTreshold.state as Number) ) { S_light_southffxSLD.sendCommand (2) } else if ( (S_light_southffxINRAW.state as Number) <= (S_light_southffxSunshineTreshold.state as Number) ) { S_light_southffxSLD.sendCommand (1) } else if ( (S_light_southffxINRAW.state as Number) > (S_light_southffxSunshineTreshold.state as Number) ) { S_light_southffxSLD.sendCommand (0) } } else { if ( (S_light_southffxSLD.state as Number) == 0 ) { if ( (S_light_southffxINRAW.state as Number) <= (S_light_southffxDarkTreshold.state as Number) ) { S_light_southffxSLD.sendCommand (2) } else if ( (S_light_southffxINRAW.state as Number) <= ((S_light_southffxSunshineTreshold.state as Number) - (S_light_southffxHysteresis.state as Number)) ) { S_light_southffxSLD.sendCommand (1) } } if ( (S_light_southffxSLD.state as Number) == 1 ) { if ( (S_light_southffxINRAW.state as Number) >= ((S_light_southffxSunshineTreshold.state as Number) + (S_light_southffxHysteresis.state as Number)) ) { S_light_southffxSLD.sendCommand (0) } else if ( (S_light_southffxINRAW.state as Number) <= ((S_light_southffxDarkTreshold.state as Number) - (S_light_southffxHysteresis.state as Number)) ) { S_light_southffxSLD.sendCommand (2) } } if ( (S_light_southffxSLD.state as Number) == 2 ) { if ( (S_light_southffxINRAW.state as Number) >= (S_light_southffxSunshineTreshold.state as Number) ) { S_light_southffxSLD.sendCommand (0) } else if ( (S_light_southffxINRAW.state as Number) >= ((S_light_southffxDarkTreshold.state as Number) + (S_light_southffxHysteresis.state as Number)) ) { S_light_southffxSLD.sendCommand (1) } } } end rule "test run" when Member of gTEST received command then try { logInfo("TEST", "Before return") //return; logInfo("TEST", "After return") }catch(Throwable t) {} finally { logInfo("TEST", "Finally") } end rule "Test item name change" // based on https://community.openhab.org/t/design-pattern-working-with-groups-in-rules/20512 when Item Z1_S_TSTxMAIN received command then val String split_part0 = triggeringItem.name.toString.split("x").get(0) val String split_part1 = triggeringItem.name.toString.split("x").get(1) logInfo ("Test", "Split as " + split_part0 + " and " + split_part1) val local_ItemName = split_part0 + "xEngine" val local_EngineNumber = ScriptServiceUtil.getItemRegistry.getItem(local_ItemName) local_EngineNumber.postUpdate (47) logInfo ("Test", "Item value " + local_EngineNumber.state + "vs" + Z1_S_TSTxEngine.state) end