Skip to content

Instantly share code, notes, and snippets.

@MDAR
Last active August 4, 2026 13:11
Show Gist options
  • Select an option

  • Save MDAR/878f9ca068ba05d1ba19912952e235b2 to your computer and use it in GitHub Desktop.

Select an option

Save MDAR/878f9ca068ba05d1ba19912952e235b2 to your computer and use it in GitHub Desktop.
{Velbus} [openHAB] Text based configuration for Velbus modules.

Text files for adding www.velbus.eu modules and channels to www.openhab.org instances While the MainUI is the prefered way of adding Things and Items, I have found that for Bulk editing, Text based Items files are the fastest to create, debug and update.

My method of adding Things to openHAB is always in the MainUI, as updates in the Binding can be easily accommodated by deleting the Thing, and adding it again so that it uses the new profile (Bug are corrected and new features added)

Bulk editing the Items files is easy with a find and replace. Applications like Notepad++ have a fraud we're you can find and replace in other files in the same directory.

simply replace the velbus:vmbgpod:VelbusNetworkBridge:05 parts with the correct ID for the Thing channel. you can find these by going to Settings in the openHAB mainUI, go the Things and find the module in question and click on the Channels tab. there is a double box icon by each channel that will copy the correct code to your clipboard.

The openHAB V5 Colour Rule works with the ColourPicker Group items from the items files

2026 July 1st - Ligting (Switched, dimmmed and Colour) & Heating HomeKit metadata has been live tested by Jack Warboys

// MDAR Limited - Velbus openHAB Items template (generic, per-room/per-module catalogue)
// AAA_OH5_Generic_template Alexa V3.items
//
// FOLDER: this file MUST live in $OPENHAB_CONF/items/ - openHAB scans config
// folders by name, not by file content, so a .items file placed anywhere
// else (or a non-.items file placed in items/) will not load. This is the
// underlying reason the files in this collection cannot be merged across
// type - .things content belongs only in things/, .rules only in rules/,
// .items only in items/, regardless of how related their content is.
//
// ============================================================================
// HOW THE FILES IN THIS COLLECTION RELATE TO EACH OTHER
// ============================================================================
// velbusbridge1.things -> defines the Bridge Thing. Every channel string
// below starts with the bridge instance name
// chosen there (default: VelbusNetworkBridge).
// THIS FILE -> per-module Item templates. Copy the block(s) for
// the modules actually fitted, find/replace the
// address segment and room-derived name prefix.
// WholeHouseThermostat.items -> whole-house aggregation Groups. Required if you
// use the CabinGPO_HeatingRelay / CabinGPO_CoolingRelay
// / CabinGPO_Thermostat style blocks below, since
// those join groups defined in that file, not here.
// Colour_OH5.rules -> consumes the ColourPickers Group defined below.
// Required if any Color item joins that group.
// ============================================================================
//
// TEMPLATING CONVENTION - read this before adopting any block below
// ============================================================================
// Every line in this file is commented out ("//") by default - it is a menu,
// not a working configuration. To adopt an entry: uncomment it, then
// find/replace three things together, consistently:
// 1. The address segment in the channel string (e.g. "05", "71", "0C", "D2",
// "VMB8DC-20-0xD8") - this is a placeholder from the reference install and
// WILL be different on every project. There is nothing special about any
// one address below versus another; ALL of them need replacing.
// 2. The Item name prefix (e.g. CabinGPO_, Loft1RY_, BathroomColour) - this
// should reflect the room/zone the module is physically installed in.
// 3. The Label string (the quoted text after the Item name) - same room/zone
// wording, for consistency in the UI and with voice assistants.
// The bridge instance name (VelbusNetworkBridge) almost never needs to change
// per-room - it only changes if you are using a different/named bridge entirely
// (see velbusbridge1.things and the multi-segment note in that file).
//
// The room/zone assignment is the ONE judgement call a human or commissioning
// agent needs to make when adopting this template - everything else (which
// channel ID to use, which metadata syntax, which Group to join) is fixed by
// the module type and does not need re-deriving per project.
//
// ============================================================================
// HOMEKIT METADATA - SUGGESTIONS, NOT RULES
// ============================================================================
// HomeKit tags have been added throughout this file as a starting point, not
// a definitive prescription - in several places (the colour pickers, the
// fan/outlet relays, the PIR sensor bank) there's a genuine judgement call
// about which HomeKit accessory type best represents a given Velbus channel,
// and the choice made here is one reasonable option among others, not the
// only correct one. Where a clean mapping genuinely doesn't exist (the
// momentary DoorRelease switch, the button-mimicking strings, rainfall/wind
// speed sensors), no tag has been added rather than forcing a misleading one
// - see the inline notes at each of those points for why.
//
// IMPORTANT: Alexa and Google Assistant metadata in this file has been
// tested against real devices over an extended period. The HomeKit metadata
// has NOT been tested against real Apple hardware or the Home app - there is
// no Apple ecosystem available to validate against. It has been written
// carefully against the official HomeKit add-on documentation, but you
// should treat every homekit= tag in this file as "should work, please
// verify" rather than "confirmed working", and validate against your own
// iPhone/iPad/Home app before relying on it.
//
// Documentation links (correct as of 30 June 2026 - openHAB docs URLs of the
// form openhab.org/docs/... and openhab.org/addons/... always redirect to
// the LATEST released version's documentation, which may have moved on from
// what's described here or from what you have installed. If something here
// doesn't match what you see in MainUI, check which openHAB version you're
// actually running and look for the matching archived docs - the openHAB
// docs site keeps version-pinned copies, e.g. v50.openhab.org for 5.0,
// v43.openhab.org for 4.3, down through v34/v33/v32/v31/v30 for the 3.x
// series and v2.openhab.org for 2.x):
// - HomeKit add-on: https://www.openhab.org/addons/integrations/homekit/
// - Alexa skill: https://www.openhab.org/docs/ecosystem/alexa/
// - Google Assistant: https://www.openhab.org/docs/ecosystem/google-assistant/
//
// ============================================================================
// CHANGELOG
// ============================================================================
// June 2026 - Added HomeKit metadata throughout (dimmers/relays/colour
// pickers/PIR/weather - see the HOMEKIT METADATA section above for the
// important caveats on this). Previously only the thermostat block and
// the Door/Roof blinds carried HomeKit tags.
// June 2026 - Merged with "AAA_OH5_Generic_template Alexa V3 WITH preset
// dissemination.items" - that file's only real difference was whether
// "preset dissemination" (whole-house averaging of Comfort/Day/Night/
// Antifrost setpoints) was active, and that toggle now lives correctly in
// WholeHouseThermostat.items, not here. The two files' Item catalogues were
// otherwise near-identical; nothing unique from either was dropped in the
// merge.
// Also fixed in this pass: duplicate CabinGPO_Button01 (CH1 and CH8) ->
// CH8 renamed CabinGPO_Button08; VMB7IN_0A_Counter_2 pointed at channel
// "counter" instead of "counter2"; "VMB2DC0-20" typo -> "VMB2DC-20";
// "Coffee AMchine" -> "Coffee Machine"; Alexa V3 colour tags standardised
// on BrightnessController.brightness,PowerController.powerState,
// ColorController.color (was inconsistent invalid "Light.brightness" style
// syntax in one of the two source files).
//
// May 20th 2024 - Serious update to Alexa Thermostat. Adding Alexa metadata
// in V4.1.2 for thermostats in the UI is broken - text file / manual using
// the CODE window is the only option. See this forum post -
// https://community.openhab.org/t/edit-metadata-results-in-spinning-wheel-while-loading/153937/7
// April 28th 2024 - added HomeKit to thermostat
// April 24th 2024 - tidy up the layout of this file
// Oct 5th 2023 - add motion sensor with profile conversion from Pressed to
// ON; add MotionSensor metadata for Alexa
// Aug 8th 2022 - For use with openHAB3 onwards. Alexa Version 3 metadata.
// Semantic modelling, see also the Semantic building template. Groups for
// combining lights or seeing how many circuits are on.
// Dec 30th 2025 - Add VMB2DC-20, VMB8DC-20 and VMB4LEDPWM-20
// String VelbusRefresh "Velbus Refresh"
// String ExternalIP "External IP"
// Group CabinGPO_Thermostat "Cabin Thermostat" <temperature> { ga="Thermostat" [roomHint="Cabin", thermostatModes="off=SAFE,heat=DAY,eco=NIGHT,on=DAY"] , alexa="Endpoint.Thermostat" [name="Cabin Thermostat", description="Velbus Thermostat in Cabin"] , homekit = "Thermostat" }
// Number CabinGPO_CurrentTemperature "Cabin Current Temperature" <heating> ( CabinGPO_Thermostat ,WholeHouse_CurrentTemperature ) { channel="velbus:vmbgpod:VelbusNetworkBridge:05:input#CH33" , ga="thermostatTemperatureAmbient" , alexa="TemperatureSensor.temperature" , homekit = "CurrentTemperature" } // Set to Ch9 or Ch33
// Number CabinGPO_CurrentTargetTemperature "Cabin Current Target Temperature" <heating> ( CabinGPO_Thermostat ,WholeHouse_CurrentTargetTemperature ) { channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#currentTemperatureSetpoint" , ga="thermostatTemperatureSetpoint" , alexa="ThermostatController.targetSetpoint" , homekit = "TargetTemperature" }
// String CabinGPO_ThermostatMode "Cabin Thermostat mode" <heating> ( CabinGPO_Thermostat, WholeHouse_ThermostatMode ) { channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#mode" , ga="thermostatMode" , alexa="ModeController.mode" [friendlyNames="@Setting.Preset", supportedModes="SAFE=Safe,NIGHT=Night,DAY=Day,COMFORT=Comfort"] , homekit = "TargetHeatingCoolingMode" [HEAT="COMFORT", AUTO="DAY", COOL="NIGHT", OFF="SAFE"] }
// String CabinGPO_ThermostatOperatingMode "Cabin Thermostat operating mode" <heating> ( CabinGPO_Thermostat, WholeHouse_ThermostatOperatingMode ) { channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#operatingMode" , alexa="ThermostatController.thermostatMode" [COOL="COOLING",HEAT="HEATING"] , homekit = "CurrentHeatingCoolingMode" [HEAT="HEATING", COOL="COOLING"] }
//
// The four preset target items below join the optional whole-house preset
// aggregation groups (WholeHouse_HeatingComfortTarget etc) which are defined,
// commented out by default, in WholeHouseThermostat.items. Enable both ends
// together if you want whole-house averaged preset targets.
//
// Number CabinGPO_HeatingComfortTarget "Cabin Heating Comfort Target Temperature" <temperature> {channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#heatingModeComfortTemperatureSetpoint" }
// Number CabinGPO_HeatingDayTarget "Cabin Heating Day Target Temperature" <temperature> {channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#heatingModeDayTemperatureSetpoint" }
// Number CabinGPO_HeatingNightTarget "Cabin Heating Night Target Temperature" <temperature> {channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#heatingModeNightTemperatureSetpoint" }
// Number CabinGPO_HeatingAntifrostTarget "Cabin Heating Antifrost Target Temperature"<temperature> {channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#heatingModeAntiFrostTemperatureSetpoint" }
//
//
// Number CabinGPO_CoolingComfortTarget "Cabin Cooling Comfort Target Temperature" <temperature> {channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#coolingModeComfortTemperatureSetpoint" }
// Number CabinGPO_CoolingDayTarget "Cabin Cooling Day Target Temperature" <temperature> {channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#coolingModeDayTemperatureSetpoint" }
// Number CabinGPO_CoolingNightTarget "Cabin Cooling Night Target Temperature" <temperature> {channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#coolingModeNightTemperatureSetpoint" }
// Number CabinGPO_CoolingSafeTarget "Cabin Cooling Safe Target Temperature" <temperature> {channel="velbus:vmbgpod:VelbusNetworkBridge:05:thermostat#coolingModeSafeTemperatureSetpoint" }
// Edit the following to suit the relay channels in use. These join the
// always-on relay aggregation groups defined in WholeHouseThermostat.items.
// Not tagged for HomeKit by default since they're normally driven by the
// thermostat logic rather than tapped manually - add homekit="ContactSensor" [name="Heating Active"]
// to either line if you want state visibility in the Home app, where OPEN = "valve open"
// Switch CabinGPO_HeatingRelay "Cabin Heating Relay" <radiator> (WholeHouse_HeatingRelay_Count,WholeHouse_HeatingRelay) {channel="velbus:vmb4ryld:VelbusNetworkBridge:02:CH4"}
// Switch CabinGPO_CoolingRelay "Cabin Cooling Relay" <climate> (WholeHouse_CoolingRelay_Count,WholeHouse_CoolingRelay) {channel="velbus:vmb1ryno:VelbusNetworkBridge:71:CH3"}
// Group:Switch:OR(ON,OFF) LoungeAllLights "Lounge lights any on"
// Group:Number:COUNT("ON") LoungeAllLightCount "Lounge How many lights on"
// Group:Dimmer:AVG LoungeLXGroup "Group of lights"
// Group ColourPickers "All Colour Pickers"
// Edit the following to suit the Dimmer channels in use - Any colour item should be put into the (ColourPickers) group
// Color BathroomColour "Bathroom Colour" <colorpicker> (ColourPickers) {ga="light", alexa="BrightnessController.brightness,PowerController.powerState,ColorController.color" [name="Colour Light", description="Smart Colour Light"], homekit="Lighting, Lighting.Hue, Lighting.Brightness, Lighting.Saturation" [name="Bathroom Colour"]}
// Dimmer BathroomColour_RED "Bathroom Red" <slider> {channel="velbus:vmb4dc:VelbusNetworkBridge:BF:CH1"}
// Dimmer BathroomColour_GREEN "Bathroom Green" <slider> {channel="velbus:vmb4dc:VelbusNetworkBridge:BF:CH2"}
// Dimmer BathroomColour_BLUE "Bathroom Blue" <slider> {channel="velbus:vmb4dc:VelbusNetworkBridge:BF:CH3"}
// Dimmer BathroomColour_WHITE "Bathroom White" <slider> {channel="velbus:vmb4dc:VelbusNetworkBridge:BF:CH4", ga="Light" [roomHint="Bathroom"], alexa="Light" [name="Dimmable Light", description="Super Smart Dimming Light"]}
//
//
//
// Color MidRoomColour "Middle Bedroom Colour" <colorpicker> (ColourPickers) {ga="light", alexa="BrightnessController.brightness,PowerController.powerState,ColorController.color", homekit="Lighting, Lighting.Hue, Lighting.Brightness, Lighting.Saturation" [name="Middle Bedroom Colour"]} // tag the Color item only - HomeKit talks to this, not the individual _RED/_GREEN/_BLUE/_WHITE Dimmer items below, the Colour rule fans the command out the same way it already does for Alexa/Google
// Dimmer MidRoomColour_RED "Middle Bedroom Red" <slider> {channel="velbus:vmb4dc:VelbusNetworkBridge:D2:CH1"}
// Dimmer MidRoomColour_GREEN "Middle Bedroom Green" <slider> {channel="velbus:vmb4dc:VelbusNetworkBridge:D2:CH2"}
// Dimmer MidRoomColour_BLUE "Middle Bedroom Blue" <slider> {channel="velbus:vmb4dc:VelbusNetworkBridge:D2:CH3"}
// Dimmer MidRoomColour_WHITE "Middle Bedroom White" <slider> {channel="velbus:vmb4dc:VelbusNetworkBridge:D2:CH4", ga="Light" [roomHint="Middle Bedroom"], alexa="Light" [name="Dimmable Light", description="White Smart Dimming Light"]}
// Mains dimmer
// Dimmer MainsDimmer01 "Mains Dimmer" <slider> {channel="velbus:vmbdmir:VelbusNetworkBridge:D1:CH1", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="Mains Dimmer"]}
// VMB2DC-20
// Group:Dimmer:AVG VMB2DC_AVG "2DC Average"
// Dimmer VMB2DCCh01 "2DC Ch 1" <slider> (VMB2DC_AVG) {channel="velbus:vmb2dc-20:VelbusNetworkBridge:D2:brightness#CH1", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="2DC Channel 1"]}
// Dimmer VMB2DCCh02 "2DC Ch 2" <slider> (VMB2DC_AVG) {channel="velbus:vmb2dc-20:VelbusNetworkBridge:D2:brightness#CH2", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="2DC Channel 2"]}
// String VMB2DCCh01fade "2DC Ch 1 Fade mode" {channel="velbus:vmb2dc-20:VelbusNetworkBridge:D2:fade-mode#CH1"}
// String VMB2DCCh02fade "2DC Ch 2 Fade mode" {channel="velbus:vmb2dc-20:VelbusNetworkBridge:D2:fade-mode#CH2"}
// Number VMB2DCCh01Scene "2DC Ch 1 Scene" {channel="velbus:vmb2dc-20:VelbusNetworkBridge:D2:scene#CH1"}
// Number VMB2DCCh02Scene "2DC Ch 2 Scene" {channel="velbus:vmb2dc-20:VelbusNetworkBridge:D2:scene#CH2"}
// VMB8DC-20
// Group:Dimmer:AVG VMB8DC_Group "VMB8DC Average" <light>
// Dimmer VMB8DC20_Ch01 "8DC Ch 1" <slider> (VMB8DC_Group) {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:brightness#CH1", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="8DC Channel 1"]}
// Dimmer VMB8DC20_Ch02 "8DC Ch 2" <slider> (VMB8DC_Group) {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:brightness#CH2", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="8DC Channel 2"]}
// Dimmer VMB8DC20_Ch03 "8DC Ch 3" <slider> (VMB8DC_Group) {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:brightness#CH3", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="8DC Channel 3"]}
// Dimmer VMB8DC20_Ch04 "8DC Ch 4" <slider> (VMB8DC_Group) {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:brightness#CH4", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="8DC Channel 4"]}
// Dimmer VMB8DC20_Ch05 "8DC Ch 5" <slider> (VMB8DC_Group) {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:brightness#CH5", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="8DC Channel 5"]}
// Dimmer VMB8DC20_Ch06 "8DC Ch 6" <slider> (VMB8DC_Group) {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:brightness#CH6", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="8DC Channel 6"]}
// Dimmer VMB8DC20_Ch07 "8DC Ch 7" <slider> (VMB8DC_Group) {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:brightness#CH7", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="8DC Channel 7"]}
// Dimmer VMB8DC20_Ch08 "8DC Ch 8" <slider> (VMB8DC_Group) {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:brightness#CH8", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="8DC Channel 8"]}
// String VMB8DC20_Ch01_fade "8DC Ch 1 Fade mode" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:fade-mode#CH1"}
// String VMB8DC20_Ch02_fade "8DC Ch 2 Fade mode" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:fade-mode#CH2"}
// String VMB8DC20_Ch03_fade "8DC Ch 3 Fade mode" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:fade-mode#CH3"}
// String VMB8DC20_Ch04_fade "8DC Ch 4 Fade mode" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:fade-mode#CH4"}
// String VMB8DC20_Ch05_fade "8DC Ch 5 Fade mode" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:fade-mode#CH5"}
// String VMB8DC20_Ch06_fade "8DC Ch 6 Fade mode" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:fade-mode#CH6"}
// String VMB8DC20_Ch07_fade "8DC Ch 7 Fade mode" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:fade-mode#CH7"}
// String VMB8DC20_Ch08_fade "8DC Ch 8 Fade mode" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:fade-mode#CH8"}
// Number VMB8DC20_Ch01_scene "8DC Ch 1 Scene" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:scene#CH1"}
// Number VMB8DC20_Ch02_scene "8DC Ch 2 Scene" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:scene#CH2"}
// Number VMB8DC20_Ch03_scene "8DC Ch 3 Scene" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:scene#CH3"}
// Number VMB8DC20_Ch04_scene "8DC Ch 4 Scene" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:scene#CH4"}
// Number VMB8DC20_Ch05_scene "8DC Ch 5 Scene" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:scene#CH5"}
// Number VMB8DC20_Ch06_scene "8DC Ch 6 Scene" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:scene#CH6"}
// Number VMB8DC20_Ch07_scene "8DC Ch 7 Scene" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:scene#CH7"}
// Number VMB8DC20_Ch08_scene "8DC Ch 8 Scene" {channel="velbus:vmb8dc-20:VelbusNetworkBridge:VMB8DC-20-0xD8:scene#CH8"}
// VMB4LEDPWM-20
// Group:Dimmer:AVG VMB4LEDPWM_Group "VMB4LEDPWM Average" <light>
// Dimmer VMB4LEDPWM20_Ch01 "4LED Ch 1" <slider> (VMB4LEDPWM_Group) {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:brightness#CH1", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="4LED Channel 1"]} // if used as independent channels - do not also tag the VL1 Color item below if using that grouping instead
// Dimmer VMB4LEDPWM20_Ch02 "4LED Ch 2" <slider> (VMB4LEDPWM_Group) {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:brightness#CH2", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="4LED Channel 2"]} // if used as independent channels - do not also tag the VL1 Color item below if using that grouping instead
// Dimmer VMB4LEDPWM20_Ch03 "4LED Ch 3" <slider> (VMB4LEDPWM_Group) {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:brightness#CH3", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="4LED Channel 3"]} // if used as independent channels - do not also tag the VL1 Color item below if using that grouping instead
// Dimmer VMB4LEDPWM20_Ch04 "4LED Ch 4" <slider> (VMB4LEDPWM_Group) {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:brightness#CH4", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Brightness" [name="4LED Channel 4"]} // if used as independent channels - do not also tag the VL1 Color item below if using that grouping instead
// String VMB4LEDPWM20_Ch01_fade "4LED Ch 1 Fade mode" {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:fade-mode#CH1"}
// String VMB4LEDPWM20_Ch02_fade "4LED Ch 2 Fade mode" {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:fade-mode#CH2"}
// String VMB4LEDPWM20_Ch03_fade "4LED Ch 3 Fade mode" {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:fade-mode#CH3"}
// String VMB4LEDPWM20_Ch04_fade "4LED Ch 4 Fade mode" {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:fade-mode#CH4"}
// Number VMB4LEDPWM20_Ch01_scene "4LED Ch 1 Scene" {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:scene#CH1"}
// Number VMB4LEDPWM20_Ch02_scene "4LED Ch 2 Scene" {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:scene#CH2"}
// Number VMB4LEDPWM20_Ch03_scene "4LED Ch 3 Scene" {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:scene#CH3"}
// Number VMB4LEDPWM20_Ch04_scene "4LED Ch 4 Scene" {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:scene#CH4"}
//
//
// Color VMB4LEDPWM20_Color "4LED Colour Picker" <color> {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:virtual-light#VL1", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting, Lighting.Hue, Lighting.Brightness, Lighting.Saturation" [name="4LED Colour Picker"]} // only tag this OR the four channels above as Lighting, not both
// Dimmer VMB4LEDPWM20_WHT "4LED White portion" <slider> {channel="velbus:vmb4ledpwm-20:VelbusNetworkBridge:0C:dim-white#DW1", ga="Light" [roomHint="Model"], alexa="Light", homekit="Lighting" [name="4LED White"]}
// Lighting - Change to suit the relay you are using - VMBxRYxxxxxx
// Switch LightingRelay01 "Lighting Relay 01" <switch> {channel="velbus:vmb4ryno:VelbusNetworkBridge:74:CH1", alexa="Light.PowerState" [name="Light Switch", description="Super Smart Light"], homekit="Lighting" [name="Lighting Relay 1"] , expire="1h,command=OFF"} // will reset to OFF after 1 hour
// Switch LightingRelay02 "Lighting Relay 02" <switch> {channel="velbus:vmb4ryno:VelbusNetworkBridge:74:CH2", alexa="Light.PowerState" [name="Light Switch", description="Super Smart Light"], homekit="Lighting" [name="Lighting Relay 2"]}
// Switch LightingRelay03 "Lighting Relay 03" <switch> {channel="velbus:vmb4ryno:VelbusNetworkBridge:74:CH3", alexa="Light.PowerState" [name="Light Switch", description="Super Smart Light"], homekit="Lighting" [name="Lighting Relay 3"]}
// Switch LightingRelay04 "Lighting Relay 04" <switch> {channel="velbus:vmb4ryno:VelbusNetworkBridge:74:CH4", alexa="Light.PowerState" [name="Light Switch", description="Super Smart Light"], homekit="Lighting" [name="Lighting Relay 4"]}
// VMB7IN - Energy monitoring / pulse counter, Add Unit of Measurement and icon as required
// Number VMB7IN_0A_Counter_1 "Electricity Usage Total" <energy> {channel="velbus:vmb7in:VelbusNetworkBridge:0A:counter#counter1"}
// Number VMB7IN_0A_Counter_1_Current "Electricity Usage Current" <energy> {channel="velbus:vmb7in:VelbusNetworkBridge:0A:counter#counter1Current"}
// Number VMB7IN_0A_Counter_2 "Gas Usage" <gas> {channel="velbus:vmb7in:VelbusNetworkBridge:0A:counter#counter2"}
// Number VMB7IN_0A_Counter_2_Current "Gas Usage Current" <gas> {channel="velbus:vmb7in:VelbusNetworkBridge:0A:counter#counter2Current"}
// Number VMB7IN_0A_Counter_3 "All Water usage" <flow> {channel="velbus:vmb7in:VelbusNetworkBridge:0A:counter#counter3"}
// Number VMB7IN_0A_Counter_3_Current "All Water usage Current" <flow> {channel="velbus:vmb7in:VelbusNetworkBridge:0A:counter#counter3Current"}
// Number VMB7IN_0A_Counter_4 "Hot Water usage" <flow> {channel="velbus:vmb7in:VelbusNetworkBridge:0A:counter#counter4"}
// Number VMB7IN_0A_Counter_4_Current "Hot Water usage Current" <flow> {channel="velbus:vmb7in:VelbusNetworkBridge:0A:counter#counter4Current"}
// Edit the following to suit the relay channels in use
// Switch Loft1RY_CH1 "Loft 12V PSU" <switch> {channel="velbus:vmb1ryno:VelbusNetworkBridge:71:CH1" , ga="Outlet" , alexa="Switch" [name="Socket", description="Switched socket"], homekit="Outlet" [name="Loft 12V PSU"]}
// Switch Loft1RY_CH2 "Loft 1RY Unused" {channel="velbus:vmb1ryno:VelbusNetworkBridge:71:CH2" , ga="Outlet" , alexa="Coffeemaker" [name="Coffee", description="Coffee Machine"]}
// Switch Loft1RY_CH3 "Loft 1RY Lounge Fan" {channel="velbus:vmb1ryno:VelbusNetworkBridge:71:CH3" , ga="Fan" , alexa="Fan", homekit="BasicFan" [name="Lounge Fan"]}
// Switch Loft1RY_CH4 "Lounge as Bedroom" <bedroom_blue> {channel="velbus:vmb1ryno:VelbusNetworkBridge:71:CH4" , ga="Outlet" [ tfaAck=true ] , alexa="Switch" [name="Desk Fan", description="My desk fan"], homekit="Outlet" [name="Desk Fan"]} // or homekit="BasicFan" if you prefer it shown as a fan accessory rather than an outlet
// Switch DoorRelease "Door Release" {channel="velbus:vmb1ryno:VelbusNetworkBridge:67:CH1" , expire="5s,command=OFF", ga="SecuritySystem" [ tfaPin="1234" ,roomHint="Hallway"], alexa="LockState" [inverted="true"]} // not tagged for HomeKit - this is a momentary pulse (expire=5s), HomeKit's Lock accessory expects a persistent Current/Target state pair, not a momentary release, so it doesn't map cleanly without extra logic
// Switch Loft1RY_CH5 "House Awake" <alarm> {channel="velbus:vmb1ryno:VelbusNetworkBridge:71:CH5"}
// Meteo channels
// Number:Temperature Meteo_Temperature "Weather Station Temperature" <temperature> {channel="velbus:vmbmeteo:VelbusNetworkBridge:FE:weatherStation#CH10", homekit="TemperatureSensor"}
// Number:Length Meteo_Rainfall "Weather Station Rainfall" <rain> {channel="velbus:vmbmeteo:VelbusNetworkBridge:FE:weatherStation#CH11"}
// Number:Illuminance Meteo_LightLevel "Weather Station Light Level" <sun> {channel="velbus:vmbmeteo:VelbusNetworkBridge:FE:weatherStation#CH12", homekit="LightSensor"} // no native HomeKit accessory for rainfall or wind speed, so those two channels above are left untagged
// Number:Speed Meteo_Windspeed "Weather Station Wind Speed" <wind> {channel="velbus:vmbmeteo:VelbusNetworkBridge:FE:weatherStation#CH13"}
// Motor controllers - blinds, curtains, windows, projection screens etc
// or Alexa command PercentageController.percentage
// Rollershutter Door "Door" {channel="velbus:vmb2ble:VelbusNetworkBridge:61:CH1" , alexa="Blind,PositionState" [name="Large Window Blind", description="Largest Window blind"] , homekit="WindowCovering, WindowCovering.CurrentPosition, WindowCovering.TargetPosition, WindowCovering.PositionState"}
// Rollershutter Roof "Roof" {channel="velbus:vmb2ble:VelbusNetworkBridge:61:CH2" , alexa="Blind,PositionState" [name="Small Window Blind", description="Smallest Window blind", capabilityNames="@Setting.Position", supportedCommands="UP=@Value.Up:@Value.Open,DOWN=@Value.Down:@Value.Close,STOP=@Value.Stop", supportedRange="0:100:1", unitOfMeasure="Percent", actionMappings="Close=DOWN,Open=UP,Lower=DOWN,Raise=UP,Stop=STOP", stateMappings="Closed=100,Open=0:99"] , homekit="WindowCovering, WindowCovering.CurrentPosition, WindowCovering.TargetPosition, WindowCovering.PositionState"}
// Ensure these groups are active in at least 1 Items file and add the Minute and Hour limiting rule
// Group needed for the ClockAlarm_OH5.rules file
// Group Velbus_AlarmTimes "Velbus Alarm Clocks"
//
// Velbus clock alarms come in two distinct flavours, and a real install will
// often use both at once - which one applies to a given module is set in
// VelbusLink (or by the AI commissioning agent) as that module's local/global
// mode, not chosen here:
//
// LOCAL mode - the module keeps its own independent alarm times, set via
// the per-module clockAlarm channel block immediately below.
// Typical use: bedrooms, where each room wants its own
// wake/bed times.
//
// GLOBAL mode - the module follows times broadcast by the bridge to Velbus
// address 0x00, set via the Global_ClockAlarm block further
// down (which binds to the bridge Thing itself, not a
// per-module address). Typical use: communal areas that
// should all follow one unified schedule.
//
// So: one Global_ClockAlarm block covers every module you've set to global
// mode collectively. Each module set to local mode needs its own copy of the
// CabinGPO_ClockAlarm-style block below, addressed to that module.
// Clock Alarm — exploring with CabinGPO as the test panel before duplicating to other rooms.
// Structure: each Wake/Bed slot is a Group:DateTime — the MainUI time-picker targets the GROUP
// itself; its two Number members (Hour/Minute) are the real Velbus-bound channels. The rule
// (ClockAlarm_OH5.rules) finds the Hour/Minute members by name and pushes the split values to them.
// Velbus_AlarmTimes only needs to exist once across all Items files if reused for other rooms.
//
// Add ONLY if this module is set to LOCAL alarm mode (see note above)
// DateTime CabinGPO_CAW1 "Cabin Alarm Wake 1" (Velbus_AlarmTimes)
// Switch CabinGPO_ClockAlarm_ClockAlarm1Enabled "Cabin Alarm 1 Enabled" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm1Enabled"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// Number CabinGPO_ClockAlarm_ClockAlarm1WakeupHour "Cabin Alarm 1 Wake Hour" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm1WakeupHour"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// Number CabinGPO_ClockAlarm_ClockAlarm1WakeupMinute "Cabin Alarm 1 Wake Minute" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm1WakeupMinute"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// DateTime CainGPO_CAB1 "Cabin Alarm Bed 1" (Velbus_AlarmTimes)
// Number CabinGPO_ClockAlarm_ClockAlarm1BedtimeHour "Cabin Alarm 1 Bed Hour" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm1BedtimeHour"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// Number CabinGPO_ClockAlarm_ClockAlarm1BedtimeMinute "Cabin Alarm 1 Bed Minute" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm1BedtimeMinute"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// DateTime CabinGPO_CAW2 "Cabin Alarm Wake 2" (Velbus_AlarmTimes)
// Switch CabinGPO_ClockAlarm_ClockAlarm2Enabled "Cabin Alarm 2 Enabled" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm2Enabled"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// Number CabinGPO_ClockAlarm_ClockAlarm2WakeupHour "Cabin Alarm 2 Wake Hour" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm2WakeupHour"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// Number CabinGPO_ClockAlarm_ClockAlarm2WakeupMinute "Cabin Alarm 2 Wake Minute" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm2WakeupMinute"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// DateTime CainGPO_CAB2 "Caib Alarm Bed 2" (Velbus_AlarmTimes)
// Number CabinGPO_ClockAlarm_ClockAlarm2BedtimeHour "Cabin Alarm 2 Bed Hour" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm2BedtimeHour"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// Number CabinGPO_ClockAlarm_ClockAlarm2BedtimeMinute "Cabin Alarm 2 Bed Minute" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:clockAlarm#clockAlarm2BedtimeMinute"} // Add ONLY if this module is set to LOCAL alarm mode (see note above)
// Global mode block - binds to the bridge's own clockAlarm channel group
// (broadcast to address 0x00), not a per-module one - see
// velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#... below
// DateTime Global_CAW1 "Global Alarm Wake 1" (Velbus_AlarmTimes)
// Switch Global_ClockAlarm_ClockAlarm1Enabled "Global Alarm 1 Enabled" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm1Enabled"}
// Number Global_ClockAlarm_ClockAlarm1WakeupHour "Global Alarm 1 Wake Hour" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm1WakeupHour"}
// Number Global_ClockAlarm_ClockAlarm1WakeupMinute "Global Alarm 1 Wake Minute" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm1WakeupMinute"}
// DateTime Global_CAB1 "Global Alarm Bed 1" (Velbus_AlarmTimes)
// Number Global_ClockAlarm_ClockAlarm1BedtimeHour "Global Alarm 1 Bed Hour" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm1BedtimeHour"}
// Number Global_ClockAlarm_ClockAlarm1BedtimeMinute "Global Alarm 1 Bed Minute" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm1BedtimeMinute"}
// DateTime Global_CAW2 "Global Alarm Wake 2" (Velbus_AlarmTimes)
// Switch Global_ClockAlarm_ClockAlarm2Enabled "Global Alarm 2 Enabled" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm2Enabled"}
// Number Global_ClockAlarm_ClockAlarm2WakeupHour "Global Alarm 2 Wake Hour" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm2WakeupHour"}
// Number Global_ClockAlarm_ClockAlarm2WakeupMinute "Global Alarm 2 Wake Minute" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm2WakeupMinute"}
// DateTime Global_CAB2 "Global Alarm Bed 2" (Velbus_AlarmTimes)
// Number Global_ClockAlarm_ClockAlarm2BedtimeHour "Global Alarm 2 Bed Hour" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm2BedtimeHour"}
// Number Global_ClockAlarm_ClockAlarm2BedtimeMinute "Global Alarm 2 Bed Minute" {channel="velbus:networkbridge:VelbusNetworkBridge:bridgeClockAlarm#clockAlarm2BedtimeMinute"}
// OLED Extras
// String CabinGPO_Memo "Cabin Memotext" <text> {channel="velbus:vmbgpod:CabinOD:05:oledDisplay#MEMO", ga="text"}
// Switch CabinGPO_Screensaver "Cabin Screen saver" <screen> {channel="velbus:vmbgpod:CabinOD:05:oledDisplay#SCREENSAVER"}
// Button mimicking - Send "PRESSED" or "LONG_PRESSED" to this string
//
// Not tagged for HomeKit by default - HomeKit's StatelessProgrammableSwitch
// expects single/double/long press events (0/1/2 or ON/OPEN style), not the
// "PRESSED"/"RELEASED" strings these channels produce directly. A profile or
// rule is needed to translate between the two before tagging - see the
// AccessoryGroup/StatelessProgrammableSwitch example near the bottom of the
// HomeKit add-on docs if you want to expose a bank of these as a scene
// controller in the Home app.
// String CabinGPO_Button01 "CabinGPO Button 1" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:button#CH1", expire="1s,state=RELEASED"} //reverts to Released after 1 second
// String CabinGPO_Button02 "CabinGPO Button 2" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:button#CH2", expire="1s,state=RELEASED"}
// String CabinGPO_Button03 "CabinGPO Button 3" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:button#CH3", expire="1s,state=RELEASED"}
// String CabinGPO_Button04 "CabinGPO Button 4" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:button#CH4", expire="1s,state=RELEASED"}
// String CabinGPO_Button05 "CabinGPO Button 5" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:button#CH5", expire="1s,state=RELEASED"}
// String CabinGPO_Button06 "CabinGPO Button 6" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:button#CH6", expire="1s,state=RELEASED"}
// String CabinGPO_Button07 "CabinGPO Button 7" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:button#CH7", expire="1s,state=RELEASED"}
// String CabinGPO_Button08 "CabinGPO Button 8" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:button#CH8", expire="1s,state=RELEASED"}
// Button triggers, example given for a VMBGP4PIR, where channels 5 to 8 are
// String CabinGP4PIR_Dark "Cabin Dark" {channel="velbus:vmbgp4pir:VelbusNetworkBridge:05:input#CH5"[profile="system:rawbutton-on-off-switch"], alexa="MotionSensor"} // Profile converts "PRESSED" & "RELEASED" in ON and OFF
// String CabinGP4PIR_Motion "Cabin Motion" {channel="velbus:vmbgp4pir:VelbusNetworkBridge:05:input#CH6"[profile="system:rawbutton-on-off-switch"], alexa="MotionSensor", homekit="MotionSensor"} // Profile converts "PRESSED" & "RELEASED" in ON and OFF. Only tag ONE of the four PIR channels for HomeKit - Dark/MotionLD/Absence are the same physical sensor in different modes, tagging more than one creates duplicate accessories
// String CabinGP4PIR_MotionLD "Cabin Motion LD" {channel="velbus:vmbgp4pir:VelbusNetworkBridge:05:input#CH7"[profile="system:rawbutton-on-off-switch"], alexa="MotionSensor"} // Profile converts "PRESSED" & "RELEASED" in ON and OFF
// String CabinGP4PIR_Absence "Cabin Absence" {channel="velbus:vmbgp4pir:VelbusNetworkBridge:05:input#CH8"[profile="system:rawbutton-on-off-switch"], alexa="MotionSensor"} // Profile converts "PRESSED" & "RELEASED" in ON and OFF
// LED Feedback
// String CabinGPO_LED_Feedback_BT1 "Cabin LED Feedback Button 1" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:feedback#CH1"}
// String CabinGPO_LED_Feedback_BT2 "Cabin LED Feedback Button 2" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:feedback#CH2"}
// String CabinGPO_LED_Feedback_BT3 "Cabin LED Feedback Button 3" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:feedback#CH3"}
// String CabinGPO_LED_Feedback_BT4 "Cabin LED Feedback Button 4" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:feedback#CH4"}
// String CabinGPO_LED_Feedback_BT5 "Cabin LED Feedback Button 5" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:feedback#CH5"}
// String CabinGPO_LED_Feedback_BT6 "Cabin LED Feedback Button 6" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:feedback#CH6"}
// String CabinGPO_LED_Feedback_BT7 "Cabin LED Feedback Button 7" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:feedback#CH7"}
// String CabinGPO_LED_Feedback_BT8 "Cabin LED Feedback Button 8" {channel="velbus:vmbgpod:VelbusNetworkBridge:05:feedback#CH8"}
// Useful information
// https://www.openhab.org/docs/ecosystem/alexa/
// https://www.openhab.org/docs/ecosystem/google-assistant/
// https://www.openhab.org/addons/integrations/homekit/

Adding and refreshing openHAB Things via the REST API - instructions for OpenCode or other AI Agent

Request MarkDown documents for ALL Velbus modules usewd in the project

The Markdown files are acurately created from the publicly accessible PDF files on the Velbus GitHub page www.github.com/velbus Each PDF and MarkDown file contains the memory map template which can be used to identify the

  • Names used for the module and the channels
  • The actions assigned between module channels
  • Whether a Themorstat feature is active (and should be used) or inactive (and should be ignored)

Velbus actions are described here - https://cdn.velleman.eu/downloads/velbus/00_general/guide_velbus_actions_descriptions_en.html and https://cdn.velleman.eu/downloads/velbus/00_general/guide_velbus_actions_by_module_en.html

Two methods - know which one applies before starting

This document covers two genuinely different REST API workflows for getting a Velbus Thing into openHAB. They are not interchangeable, and using the wrong one for the situation either breaks the "Items don't need editing" guarantee or does unnecessary manual work:

Discovery + Inbox approval (the primary method, use this by default). This is the REST API equivalent of MainUI's Things → Inbox → Scan → approve flow, and it's the method to use for commissioning any module that hasn't been added to openHAB before. It lets openHAB's own discovery service find the module on the bus and report back its type and address - you approve what it found rather than hand-typing a configuration you could get wrong.

Direct POST of a Thing object (secondary, narrow use case only). Use this only when refreshing a Thing whose UID you already know - typically, recreating it after a binding update so it picks up newly added channels, while keeping the same UID so linked Items in the .items files don't need editing. This method does not go through discovery at all; it skips straight to creating the Thing object from a configuration you supply. Don't use it for first-time commissioning of a module you haven't met before - that's what Discovery is for.

If in doubt: does a Thing with this physical module's address already exist in openHAB? No → Discovery. Yes, and you're refreshing it after a binding update → Direct POST, preserving the existing UID.

Authentication

Use an API token, not username/password. The token goes in the Authorization: Bearer {TOKEN} header. Never embed a token in committed scripts or print it in logs - if you've been given one in this session, treat it as a secret.

AUTH="Authorization: Bearer ${OPENHAB_API_TOKEN}"
BASE="http://${OPENHAB_HOST}:8080/rest"

Before touching any file in this template collection

This Thing-creation workflow does not happen in isolation - it sits alongside a set of .things/.items/.rules files that reference each other. Two rules apply before you write or edit anything, REST API or file:

Read the relationship-map header first. Every file in this collection (velbusbridge1.things, the AAA template .items file, WholeHouseThermostat.items, Colour_OH5.rules) opens with a "HOW THE FILES IN THIS COLLECTION RELATE TO EACH OTHER" block. Read it before editing that file or any file it references - it tells you which Groups, channels, or naming conventions are defined elsewhere rather than locally. Guessing at cross-file dependencies instead of reading this is exactly how bugs like a duplicated Item name or an Item joining a Group that doesn't exist yet get introduced.

Folder placement is determined by file type, not negotiable, and fails silently if wrong. openHAB scans $OPENHAB_CONF/things/, items/, rules/, sitemaps/ (etc) by folder, not by file content. A .things file placed in items/, or any file with the wrong extension for its folder, will not load - and openHAB will not generally raise a loud error for this, it will simply behave as if that configuration doesn't exist. Before writing any file as part of this or any related task: confirm the destination folder matches the file's purpose (Things → things/, Items → items/, Rules → rules/), and never assume a file can be merged into a different type's file just because the content is closely related - the bridge file, the items template, and the colour rule all reference each other constantly but must stay in three separate files for exactly this reason.

Step 0 - look up the bridge UID, don't assume it

The bridge Thing for this install is loaded from a text .things file (see velbusbridge1.things), not created via the API. There are two bridge types, and the instance ID is deliberately kept identical across both variants of each type so that swapping which one is active never requires touching any .items or .rules file:

Bridge velbus:networkbridge:VelbusNetworkBridge "Velbus Network Bridge - Loopback" @ "Loft"
  [ address="127.0.0.1", port=6000, timeUpdateInterval="360", reconnectionInterval="15" ]

Bridge velbus:bridge:VelbusBridge "Velbus USB / RS232 Bridge - ttyACM0" @ "Control"
  [ port="/dev/ttyACM0" ]
  • velbus:networkbridge (TCP, e.g. Velserv/Velbus_PB_Server/the upcoming Velbus TCP gateway) - config keys address, port, timeUpdateInterval, reconnectionInterval.
  • velbus:bridge (direct USB/RS232) - config key port only, e.g. /dev/ttyACM0 on Linux or com5 on Windows.

These are two different thingTypeUIDs, not two configurations of the same type - don't conflate them. The instance ID after the type (VelbusNetworkBridge or VelbusBridge) is chosen in the .things file, not auto-generated, and is not guaranteed to be identical across other installs (a different site could use a different instance ID). Never hard-code it - look it up:

curl -s -H "$AUTH" "$BASE/things" | jq '.[] | select(.thingTypeUID | startswith("velbus:network") or startswith("velbus:bridge")) | {UID, label, thingTypeUID}'

This matters for both methods below: Discovery results carry a bridgeUID field telling you which bridge found them (essential once more than one segment exists - see the multi-segment note); the Direct POST method needs the bridge UID explicitly in the request body.

Multi-segment note: once a single openHAB instance is connected to more than one isolated Velbus-TCP segment (planned, see the multi-segment note in velbusbridge1.things), there will be more than one bridge Thing active at once and the bridge instance name stops being a fixed constant you can assume - it becomes the identifier that tells you which physical segment a module lives on. Always check a Discovery result's bridgeUID field (or ask which segment a Direct POST Thing belongs to) rather than defaulting to VelbusNetworkBridge once multiple segments exist, since the same Velbus module address can validly exist on more than one isolated segment with two different bridge names.


PRIMARY METHOD - Discovery and Inbox approval

Confirmed against the current openhab-core source (DiscoveryResource.java / InboxResource.java), not just the conceptual docs page, so these endpoints and field names reflect actual behaviour.

Step 1 - trigger a scan

curl -s -X POST -H "$AUTH" "$BASE/discovery/bindings/velbus/scan"

Returns an integer: the discovery timeout in seconds. The scan runs asynchronously - don't check the inbox immediately, wait at least that long first. If the binding ID velbus returns 404, list supported bindings to confirm the exact ID:

curl -s -H "$AUTH" "$BASE/discovery"

Step 2 - read the inbox

curl -s -H "$AUTH" "$BASE/inbox?includeIgnored=false" | jq '.[] | select(.thingTypeUID | startswith("velbus:"))'

Each entry's JSON shape is fixed and worth knowing exactly rather than guessing field names:

{
  "thingUID": "velbus:vmb4ryld-20:VelbusNetworkBridge:09",
  "thingTypeUID": "velbus:vmb4ryld-20",
  "bridgeUID": "velbus:networkbridge:VelbusNetworkBridge",
  "label": "...",
  "flag": "NEW",
  "properties": { "...": "..." },
  "representationProperty": "..."
}

thingUID here is the auto-generated default - it's a candidate, not fixed, you can still override the final ID segment at approval time (Step 4). properties typically carries things like address and serial/build info, useful for matching a discovery result against the physical module you're actually commissioning (cross-reference against what's expected from the commissioning conversation - don't approve by type alone if more than one module of the same type is in the inbox at once).

Step 3 - match results to the commissioning plan

By this point you should already know, from the commissioning conversation, which physical module (by address) is meant to become which Thing in which zone. Match each inbox entry's properties (address) against that plan before approving anything. If an inbox entry doesn't match anything expected

  • wrong segment, stray module, leftover from previous testing - don't approve it; flag it or ignore it (Step 6) instead.

Step 4 - approve

curl -s -X POST -H "$AUTH" -H "Content-Type: text/plain" \
  --data "" \
  "$BASE/inbox/velbus%3Avmb4ryld-20%3AVelbusNetworkBridge%3A09/approve"

The endpoint takes an optional plain-text body (label) and an optional query parameter (newThingId) - this maps directly onto the two approval choices MainUI itself offers:

  • Accept default naming: POST with an empty body and no query parameter, as above.
  • Set your own module ThingID: add ?newThingId=09 (or whatever ID you want for the final UID segment) and put a custom label in the request body, e.g.:
curl -s -X POST -H "$AUTH" -H "Content-Type: text/plain" \
  --data "Utility Room Relay" \
  "$BASE/inbox/velbus%3Avmb4ryld-20%3AVelbusNetworkBridge%3A09/approve?newThingId=09"

As with MainUI: the Thing's label can be changed later, but the Thing ID is fixed from the moment of approval - there is no endpoint to change it afterwards, only to delete and re-add. Decide on the final ID at approval time, not after.

Possible responses: 200 success, 400 invalid newThingId, 404 the discovery result wasn't found (probably already approved or expired), 409 no binding handler could create the Thing.

Step 5 - verify

curl -s -H "$AUTH" "$BASE/things/velbus%3Avmb4ryld-20%3AVelbusNetworkBridge%3A09" | jq '.statusInfo'

ONLINE is success. If OFFLINE, check the status description before assuming the approval itself failed - it usually means a configuration or communication issue with the underlying module, not a problem with the approve call.

Step 6 - clean up unwanted inbox entries

curl -s -X POST -H "$AUTH" "$BASE/inbox/{thingUID}/ignore"     # hide without deleting
curl -s -X DELETE -H "$AUTH" "$BASE/inbox/{thingUID}"          # remove outright

SECONDARY METHOD - Direct POST for known-UID refresh

Use this only for the specific case described at the top of this document: refreshing an existing Thing (typically after a binding update added new channels) while deliberately preserving its current UID, so linked Items keep working without edits. This does not involve Discovery or the Inbox at all.

When a Thing is created this way with no channels array in the POST body, openHAB derives the full channel list from the current ThingType definition supplied by the installed binding - so every channel the binding currently exposes appears automatically, including ones added since the Thing was first set up. Do not hand-write a channels array; let openHAB populate it from the binding.

Step 1 - confirm the exact thingTypeUID

curl -s -H "$AUTH" "$BASE/thing-types" | jq '.[] | select(.UID | startswith("velbus:")) | {UID, label}'
curl -s -H "$AUTH" "$BASE/thing-types/velbus%3Avmb4ryld-20" | jq .

Note the URL-encoding of the colon (%3A) when the UID is used as a path segment.

Step 2 - confirm the Thing already exists

curl -s -H "$AUTH" "$BASE/things/velbus%3Avmb4ryld-20%3AVelbusNetworkBridge%3A02"

A 200 with a body confirms it exists and this is genuinely a refresh. If it's a 404, stop - this is first-time commissioning, use the Discovery method above instead, not this one.

Step 2a - save the full configuration object before touching anything

curl -s -H "$AUTH" "$BASE/things/velbus%3Avmb4ryld-20%3AVelbusNetworkBridge%3A02" \
  | jq '{UID, label, location, bridgeUID, configuration}' > /tmp/thing_backup.json

This is your only source of truth for the existing configuration object, including any CH1..CHn label override keys - these are not necessarily reflected anywhere in the .items files. Don't reconstruct it from memory.

Step 3 - delete the existing Thing

curl -s -X DELETE -H "$AUTH" "$BASE/things/velbus%3Avmb4ryld-20%3AVelbusNetworkBridge%3A02"

Confirm the delete actually completed (poll GET until 404) before re-creating - this can be asynchronous, and a too-fast POST of the same UID can 409.

Step 4 - build the POST body from the backup

{
  "UID": "velbus:vmb4ryld-20:VelbusNetworkBridge:02",
  "thingTypeUID": "velbus:vmb4ryld-20",
  "label": "VMB4RYLD-20 Utility Room",
  "bridgeUID": "velbus:networkbridge:VelbusNetworkBridge",
  "configuration": {
    "address": "02"
  }
}

Confirmed against a live Thing on this install: bridgeUID is velbus:networkbridge:VelbusNetworkBridge, and address is a bare decimal/hex string with no 0x prefix, e.g. "61".

Critical detail that's easy to miss: configuration on a Velbus Thing is not just address. Per-channel friendly-name overrides also live in this same object as flat keys, e.g.:

"configuration": {
  "address": "61",
  "CH1": "TV Room",
  "CH2": "Lounge Main",
  "CH3": "Lounge Wall",
  "CH4": "Channel 4"
}

If you only preserve address and drop these keys when you recreate the Thing, you'll get the right channels but every channel label reverts to the binding default. Copy the entire configuration object from /tmp/thing_backup.json, not just the address.

curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
  -d @thing.json \
  "$BASE/things"

Step 5 - verify

curl -s -H "$AUTH" "$BASE/things/velbus%3Avmb4ryld-20%3AVelbusNetworkBridge%3A02" | jq '.channels[] | {id, channelTypeUID}'

Compare against the channel list before the delete and against what thing-types showed in Step 1. Flag any new channel IDs that have appeared - those need new .items entries, which is a separate task.

curl -s -H "$AUTH" "$BASE/things/velbus%3Avmb4ryld-20%3AVelbusNetworkBridge%3A02" | jq '.statusInfo'

ONLINE is success. OFFLINE with a COMMUNICATION_ERROR or CONFIGURATION_ERROR description usually means the address in the configuration object is wrong, or the bridge isn't reachable.


Adopting the matching Items entries (applies to either method)

Neither method above creates any Items - only the Thing and its Channels. The Items still come from the AAA template .items file, using its find/replace convention (read that file's header in full before adopting any block).

By the time you reach this step you should already know, from the commissioning conversation, which zone/room each relay, dimmer, etc channel belongs to - that assignment is the one piece of information this whole workflow cannot derive on its own, so don't guess at it or leave it as the template's example room name (Cabin, Loft, Bathroom etc). For each Channel on the Thing you just created or refreshed:

  1. Find the matching block in the AAA template for that module type.
  2. Replace the address segment in the channel string with the Thing's actual address (the one used in the POST body or confirmed via Discovery).
  3. Replace the Item name prefix and Label with the zone/room name from commissioning, consistently across both.
  4. If the Thing is on a bridge other than VelbusNetworkBridge (see the multi-segment note in Step 0), replace the bridge segment too.
  5. If the Item needs to join a whole-house aggregation Group (relay or thermostat style items do, by default - see the template's own inline notes), confirm the matching Group exists and is uncommented in WholeHouseThermostat.items before relying on it.

Common mistakes to avoid

  • Using Direct POST for first-time commissioning of a module that's never been added before - this means hand-authoring a configuration you could get wrong, and skips the address-matching openHAB's own discovery service already does for you. Use Discovery instead.
  • Using Discovery to refresh a Thing you want to keep a specific known UID for - Discovery generates its own candidate thingUID, and while you can override the final ID segment with newThingId at approval time, this is more error-prone for an exact-match refresh than just reading the existing Thing's full config and POSTing it back directly. Use Direct POST for that case.
  • Approving an inbox entry without first matching its properties (address) against what's actually expected from the commissioning conversation - especially risky once more than one module of the same type, or more than one bridge segment, is involved.
  • Including a hand-built channels array in a Direct POST body - this defeats the entire point of that method, since it freezes the channel list at whatever you typed rather than letting it track the binding.
  • POSTing a Direct refresh without first confirming the address/UID/full configuration object matches the previous Thing - this is the one detail that breaks the "Items don't need editing" promise if it's wrong.
  • Forgetting that thingTypeUID and UID segments containing a colon need URL-encoding (%3A) when used as a path segment, but appear with literal colons inside JSON bodies.
  • Treating a 200 response as confirmation a Thing is working - it only confirms the request was accepted; check statusInfo separately in both methods.

When to fall back to MainUI instead

Both REST methods above cover the great majority of Velbus commissioning. Fall back to MainUI specifically when a binding's discovery process requires interactive steps no API call can drive - e.g. a physical pairing button-press window, or a binding whose discovery service genuinely has no programmatic scan trigger exposed. Once such a Thing has been discovered and approved once via MainUI, subsequent refreshes can still go through the Direct POST method above, since by then the UID and configuration are known. object matches the previous Thing - this is the one detail that breaks the "Items don't need editing" promise if it's wrong.

  • Forgetting that thingTypeUID and UID segments containing a colon need URL-encoding (%3A) when used as a path segment, but appear with literal colons inside JSON bodies.
  • Treating a 200 response as confirmation a Thing is working - it only confirms the request was accepted; check statusInfo separately in both methods

openHAB Items generator for Velbus installations

A prompt for Claude AI by MDAR Limited (mdar.co.uk)


How to use this file

You are reading a prompt file - a set of instructions designed to be given to Claude, the AI assistant made by Anthropic.

To use it:

  1. Open a new conversation at claude.ai
  2. Upload this file along with your Velbus VLP project file
  3. Type something like: "Please read the prompt file I've uploaded and follow its instructions using my VLP file."
  4. Claude will take it from there

What you'll get at the end: A ready-to-deploy set of openHAB text configuration files, generated from your real Velbus installation data. These drop directly into your openHAB items/, things/ and rules/ folders.

What you'll need before you start:

  • Your VLP project file, exported from VelbusLink (File → Save As)
  • To know your openHAB bridge instance name (look in your $OPENHAB_CONF/things/ folder — the name after velbus:networkbridge: or velbus:bridge: in your bridge .things file)
  • 10 to 15 minutes to answer a few questions about your rooms and devices

This prompt has been used successfully to generate complete openHAB configurations for real Velbus installations, including multi-room residential properties with dimming, RGBW colour lighting, roller blinds, thermostats, and energy monitoring. The more completely you have named your channels in VelbusLink, the faster the process goes.


Instructions for Claude

You are helping a Velbus home automation user generate a complete, deployable package of openHAB text configuration files from their VelbusLink project file (VLP format).

You have been given two uploaded files: this prompt, and a .vlp project file. If you cannot see the VLP file, ask the user to upload it before proceeding.

Instantiate the canonical MDAR template blocks.

Uploaded template files define the authoritative implementation. Preserve formatting, comments, metadata, Group memberships and structure. Only substitute documented variables such as:

bridge identifier module address Item prefix labels channel names optional feature blocks.

What a VLP file is

A VLP file is the project file saved by VelbusLink, the official configuration software for Velbus building automation systems made by Velleman (velbus.eu). It is XML format and contains every module on the Velbus bus: its type, address, firmware build number, and a complete memory map including all channel names the installer assigned. It is the single authoritative record of how a Velbus installation is configured.

The non-negotiable rule: Python before AI

This is a two-stage process. Stage 1 must complete before Stage 2 begins.

Stage 1 uses Python to extract ground truth from the VLP. Channel names, module addresses, module types, and firmware versions are stored as raw bytes in the VLP memory map. Python reads them with perfect accuracy. If you attempt to decode hex memory blobs yourself from the raw XML, you will make errors. Do not do this.

Stage 2 uses the structured data Python extracted, combined with answers from the user, to generate the openHAB files. Stage 2 does not begin until you have verified the Python output with the user.

If the user has uploaded the VLP but you have not yet run the Python extraction, your first action is always to write and run the Stage 1 script. Never generate openHAB files from the raw VLP.


STAGE 1 - Python extraction

VLP format reference

VLP files are XML with this structure:

<Modules>
  <Module build="2538" address="61" type="VMB8DC-20" serial="66AF">
    <Caption>GF LX Dim vmb8dc-20 (0x61)</Caption>
    <Memory>000102...</Memory>
  </Module>
</Modules>
  • type - Velbus module model (e.g. VMB8DC-20)
  • address - bus address in hex, no 0x prefix (e.g. 61)
  • build - firmware build number (not a date — Velbus official docs are wrong about this encoding)
  • <Caption> - the engineer label set in VelbusLink
  • <Memory> - the entire module memory map as a flat hex string. Channel names live here at known byte offsets per module type.

Channel name memory offsets by module family

Each channel name occupies 16 bytes: ASCII text, terminated at the first 0x00 or 0xFF byte, remainder padded with 0xFF.

Module family Channel name start offset Bytes per channel Max channels
VMB8DC-20, VMB2DC-20 0x000 16 8
VMB4DC 0x000 16 4
VMB4LEDPWM-20 0x000 16 4
VMB4RYLD, VMB4RYNO (all variants) 0x000 16 4
VMB1RYNO, VMB1RYNO-10, VMB1RYNOS 0x000 16 1
VMBDMI-R 0x000 16 1
VMBGP1, VMBGP2, VMBGP4, VMBGPOD, VMBGP4PIR 0x000 16 8
VMBEL1, VMBEL2, VMBEL4, VMBELO 0x000 16 8
VMB7IN 0x000 16 4
VMB2BLE, VMB1BL 0x000 16 2
VMBMETEO Fixed labels — no user-editable channel names

For module types not in this table, consult the Velbus module protocol repository at https://github.com/velbus/moduleprotocol for the relevant PDF, or ask the user to upload the protocol PDF for that module type so the correct offsets can be determined.

Stage 1 Python script

Write this script to the working directory, run it against the uploaded VLP file, and paste the output before proceeding. No non-stdlib dependencies are required.

#!/usr/bin/env python3
"""
VLP → openHAB manifest extractor
Stage 1 of the openHAB Items generator for Velbus installations.
Run this first. Do not generate openHAB files until this output is verified.
"""

import xml.etree.ElementTree as ET
import json
import sys
import os

# ── CONFIGURE ────────────────────────────────────────────────────────────────
VLP_PATH = "/mnt/user-data/uploads/your_installation.vlp"  # � set to actual filename
OUTPUT_PATH = "/tmp/vlp_manifest.json"
# ─────────────────────────────────────────────────────────────────────────────

CHANNEL_NAME_OFFSETS = {
    "VMB8DC-20":      (0x000, 16, 8),
    "VMB2DC-20":      (0x000, 16, 8),
    "VMB4DC":         (0x000, 16, 4),
    "VMB4LEDPWM-20":  (0x000, 16, 4),
    "VMB4RYLD":       (0x000, 16, 4),
    "VMB4RYNO":       (0x000, 16, 4),
    "VMB4RYLD-20":    (0x000, 16, 4),
    "VMB4RYNO-20":    (0x000, 16, 4),
    "VMB4RYLD-10":    (0x000, 16, 4),
    "VMB4RYNO-10":    (0x000, 16, 4),
    "VMB1RYNO":       (0x000, 16, 1),
    "VMB1RYNO-10":    (0x000, 16, 1),
    "VMB1RYNOS":      (0x000, 16, 1),
    "VMBDMI-R":       (0x000, 16, 1),
    "VMBGP1":         (0x000, 16, 8),
    "VMBGP2":         (0x000, 16, 8),
    "VMBGP4":         (0x000, 16, 8),
    "VMBGPOD":        (0x000, 16, 8),
    "VMBGP4PIR":      (0x000, 16, 8),
    "VMBEL1":         (0x000, 16, 8),
    "VMBEL2":         (0x000, 16, 8),
    "VMBEL4":         (0x000, 16, 8),
    "VMBELO":         (0x000, 16, 8),
    "VMB7IN":         (0x000, 16, 4),
    "VMB2BLE":        (0x000, 16, 2),
    "VMB1BL":         (0x000, 16, 1),
}

FIXED_LABEL_TYPES = {"VMBMETEO", "VMBPSUMNGR-20"}


def decode_channel_name(raw: bytes) -> str:
    chars = []
    for b in raw:
        if b == 0x00 or b == 0xFF:
            break
        if 0x20 <= b <= 0x7E:
            chars.append(chr(b))
    return "".join(chars).strip()


def decode_channels(memory_hex: str, mod_type: str) -> dict:
    result = {"channel_names": {}, "warnings": []}

    if mod_type in FIXED_LABEL_TYPES:
        result["warnings"].append(
            f"{mod_type} has fixed channel labels — no user-editable names in memory."
        )
        return result

    if mod_type not in CHANNEL_NAME_OFFSETS:
        result["warnings"].append(
            f"Unknown module type '{mod_type}'. Channel names not decoded. "
            f"See https://github.com/velbus/moduleprotocol for the protocol PDF "
            f"for this module type."
        )
        return result

    try:
        raw = bytes.fromhex(memory_hex)
    except ValueError as e:
        result["warnings"].append(f"Memory hex decode failed: {e}")
        return result

    start, stride, max_ch = CHANNEL_NAME_OFFSETS[mod_type]
    for ch in range(max_ch):
        offset = start + ch * stride
        if offset + stride > len(raw):
            result["warnings"].append(
                f"Memory too short for channel {ch + 1} at offset 0x{offset:03X}"
            )
            break
        name = decode_channel_name(raw[offset:offset + stride])
        result["channel_names"][f"CH{ch + 1}"] = name if name else None

    return result


def classify_module(mod_type: str) -> str:
    relay_types = {
        "VMB4RYLD", "VMB4RYNO", "VMB4RYLD-20", "VMB4RYNO-20",
        "VMB4RYLD-10", "VMB4RYNO-10", "VMB1RYNO", "VMB1RYNO-10", "VMB1RYNOS",
    }
    dimmer_types = {"VMB8DC-20", "VMB2DC-20", "VMB4DC", "VMBDMI-R"}
    led_types = {"VMB4LEDPWM-20"}
    blind_types = {"VMB2BLE", "VMB1BL"}
    panel_types = {
        "VMBGP1", "VMBGP2", "VMBGP4", "VMBGPOD",
        "VMBEL1", "VMBEL2", "VMBEL4", "VMBELO",
    }
    pir_types = {"VMBGP4PIR", "VMBELPIR", "VMBPIR-20"}
    counter_types = {"VMB7IN"}
    meteo_types = {"VMBMETEO"}

    if mod_type in relay_types:   return "relay"
    if mod_type in dimmer_types:  return "dimmer"
    if mod_type in led_types:     return "led_pwm"
    if mod_type in blind_types:   return "blind"
    if mod_type in panel_types:   return "glass_panel"
    if mod_type in pir_types:     return "pir"
    if mod_type in counter_types: return "counter"
    if mod_type in meteo_types:   return "meteo"
    return "unknown"


def extract_manifest(vlp_path: str) -> dict:
    tree = ET.parse(vlp_path)
    root = tree.getroot()
    modules_el = root.find("Modules")
    if modules_el is None:
        raise ValueError("No <Modules> element found in VLP file.")

    manifest = {
        "vlp_file": os.path.basename(vlp_path),
        "modules": [],
        "summary": {},
        "unknown_types": [],
    }

    type_counts = {}
    for mod in modules_el.findall("Module"):
        mod_type   = mod.get("type", "UNKNOWN")
        address    = mod.get("address", "??")
        build      = mod.get("build", "??")
        serial     = mod.get("serial", "")
        caption_el = mod.find("Caption")
        caption    = caption_el.text.strip() if caption_el is not None else ""
        memory_el  = mod.find("Memory")
        memory_hex = memory_el.text.strip() if memory_el is not None else ""

        role     = classify_module(mod_type)
        ch_data  = decode_channels(memory_hex, mod_type)

        entry = {
            "type":           mod_type,
            "address":        address,
            "build":          build,
            "serial":         serial,
            "caption":        caption,
            "role":           role,
            "thing_type_uid": f"velbus:{mod_type.lower()}",
            "channel_names":  ch_data["channel_names"],
            "warnings":       ch_data["warnings"],
        }
        manifest["modules"].append(entry)
        type_counts[mod_type] = type_counts.get(mod_type, 0) + 1
        if role == "unknown":
            manifest["unknown_types"].append(mod_type)

    manifest["summary"] = {
        "total_modules":        len(manifest["modules"]),
        "by_type":              type_counts,
        "roles_present":        sorted(set(m["role"] for m in manifest["modules"])),
        "has_colour_pickers":   any(m["role"] == "led_pwm"      for m in manifest["modules"]),
        "has_thermostats":      any(m["role"] == "glass_panel"  for m in manifest["modules"]),
        "has_blinds":           any(m["role"] == "blind"        for m in manifest["modules"]),
        "has_counters":         any(m["role"] == "counter"      for m in manifest["modules"]),
        "modules_with_warnings": [m["address"] for m in manifest["modules"] if m["warnings"]],
    }

    return manifest


if __name__ == "__main__":
    vlp = sys.argv[1] if len(sys.argv) > 1 else VLP_PATH
    try:
        manifest = extract_manifest(vlp)
    except Exception as e:
        print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)

    output = json.dumps(manifest, indent=2)
    with open(OUTPUT_PATH, "w") as f:
        f.write(output)
    print(f"Manifest written to {OUTPUT_PATH}")
    print(output)

After running Stage 1

Show the JSON output to the user and confirm:

  • The total module count matches what they expect from their installation.
  • Any modules_with_warnings entries are reviewed together. An unknown module type means channel names were not decoded for that module — ask the user whether Items are needed for it before proceeding.
  • The roles_present list is correct — this determines exactly which output files the package needs.

If unknown_types contains entries, for each one either find the correct memory offsets from https://github.com/velbus/moduleprotocol and re-run, or confirm with the user that module does not need Items generated.

Do not proceed to Stage 2 with unresolved unknown types.


STAGE 2 — Questions for the user, then file generation

Before writing any files, ask the user these questions. Ask them as a natural conversation, grouped by topic — not as a numbered form.

Bridge name. Ask the user for the bridge instance name from their .things file. Explain briefly: this is the word after velbus:networkbridge: or velbus:bridge: on the Bridge line. It is typically something like VelbusNetworkBridge but must not be assumed. The files will not work if this is wrong.

Is the bridge .things file already deployed? If yes, it does not need to be in the output package. If no, include it.

Room and function names. Review the channel names from the JSON with the user. Where channel names already contain clear room context (e.g. "TV Room", "Lounge Main") confirm them rather than asking again. Where channel names are generic ("Channel 1", "Output 3") or empty, ask what the channel controls and which room it is in. Keep this to one question per module block — do not interrogate channel by channel unless the module has a genuine mix of named and unnamed channels.

Thermostat confirmation (only if summary.has_thermostats is true). Glass panel modules (VMBGP, VMBEL families) can exist without their thermostat being enabled in VelbusLink. Ask which panel modules have their thermostat active. Those modules need the full five-item thermostat block; panels without an active thermostat only need button and feedback Items.

Whole-house thermostat aggregation (only if any thermostats confirmed above). Ask whether they want whole-house averaged temperature and mode groups. These live in WholeHouseThermostat.items — explain this is optional and only useful if they have more than one thermostat zone and want a single "whole house" view.

Colour picker mode (only if summary.has_colour_pickers is true). For each VMB4LEDPWM-20 module, ask which grouping mode is set in VelbusLink: four independent channels, RGB plus separate white, or full RGBW. The mode determines which Items and whether the colour rule is needed.

Voice assistant metadata. Ask which of Alexa, Google Assistant and Apple HomeKit the user has active with openHAB. Include only the relevant metadata tags. Note: HomeKit metadata is included as a best-effort suggestion — it has not been tested against real Apple hardware and the user should validate it themselves.

Output package rules

Derive the required files from summary.roles_present and the answers above. Every dependency of a generated Item must be in the same package — generating Items that join Groups defined in a file that isn't in the package would be like selling a car without wheels.

Condition File included
Always [InstallationName]_items.items
Bridge not already deployed velbusbridge1.things
Any thermostat module present WholeHouseThermostat.items
Any VMB4LEDPWM-20 VMB8DC-20 or VMB4DC controlling RGB or RGBS light fixtures Colour_OH5.rules

Naming convention

Follow this pattern throughout:

  • Item name (code-safe, no spaces): {RoomPrefix}_{Function} e.g. TVRoom_MainLX, Kitchen_HeatingRelay, MidBed_ColourPicker
  • Item label (human-readable, in quotes): the channel name from the VLP or the name confirmed with the user e.g. "TV Room Main Light"
  • Channel string address: bare hex from the VLP, no 0x prefix

Reference documentation

When selecting channel IDs, metadata syntax and Group membership patterns, consult these sources rather than working from memory:

openHAB docs at openhab.org always redirect to the latest version. If the user is on an older openHAB release, use https://v{version}.openhab.org for version-pinned documentation.

Header for every generated file

Each file must open with a header stating what it is, which installation it was generated for, which VLP it came from, the date, the correct destination folder, and which other files in this package it depends on. Example for the Items file:

// Generated by MDAR platform_oh openHAB generator
// Installation: [Name]
// Source VLP: [filename].vlp    Date: [date]
//
// FOLDER: $OPENHAB_CONF/items/
//
// HOW THE FILES IN THIS PACKAGE RELATE TO EACH OTHER:
// velbusbridge1.things → defines the Bridge Thing referenced by every
//   channel string below.
// WholeHouseThermostat.items → defines the Groups that thermostat and
//   relay Items below join via their parentheses.
// Colour_OH5.rules → required because [module] is configured in colour
//   mode and the ColourPickers Group defined below triggers that rule.

Quality checks before delivering

Before presenting the completed package to the user, verify:

  1. Every channel string uses the bridge name the user confirmed — not a guess or a placeholder.
  2. Every Item joining a WholeHouseThermostat Group has that Group present and uncommented in WholeHouseThermostat.items.
  3. If Colour_OH5.rules is in the package, at least one Color Item exists and is a member of the ColourPickers Group.
  4. No Item name is duplicated across the whole package.
  5. Every channel string address uses the bare hex from the VLP — no 0x prefix.
  6. Do not treat { } characters inside // comment lines as structural brace errors — in openHAB Items files, commented-out lines routinely contain unmatched braces as part of a deliberate toggle convention. The openHAB parser ignores them completely.

A note on this prompt

This prompt was developed by MDAR Limited (mdar.co.uk), the UK distributor for Velbus building automation systems (velbus.eu), with 14 years of field experience across residential and commercial Velbus installations.

The two-stage approach (Python extraction first, AI generation second) is deliberate. Channel names and module addresses are facts, not guesses — Python reads them correctly from the binary memory maps every time. The AI's role is to take those facts, have a sensible conversation about rooms and functions, then produce correctly structured openHAB configuration files following the conventions established across many real installations.

For questions, updates, or to contribute improved module type offsets, the MDAR openHAB template collection lives at: https://gist.github.com/MDAR/878f9ca068ba05d1ba19912952e235b2

// Stuart Hanlon - MDAR Ltd - Velbus.eu
// Rule for splitting a Clock Alarm Group:DateTime picker into its real Hour/Minute channel Items.
//
// Structure: the MainUI time-picker / Sitemap Input (inputHint="time") targets a Group:DateTime
// named <Prefix>_CAW<N> (Wake) or <Prefix>_CAB<N> (Bed), e.g. "EnSuite_CAW1" or "Test_CAB2".
// The real Velbus-bound Hour/Minute Items keep their full template naming — no renaming needed —
// because that naming is itself derivable directly from the group name:
// <Prefix>_CAW<N> -> <Prefix>_ClockAlarm_ClockAlarm<N>WakeupHour / WakeupMinute
// <Prefix>_CAB<N> -> <Prefix>_ClockAlarm_ClockAlarm<N>BedtimeHour / BedtimeMinute
// This avoids GroupItem/.members entirely (same philosophy as the Colour rule — derive the
// target Item names from triggeringItem.name and sendCommand by name).
import java.time.ZoneId
rule "Clock Alarm Time Split"
when
Member of Velbus_AlarmTimes received command
then
val cmd = receivedCommand
if (cmd === null) {
// nothing to split
}
else {
val dt = cmd as DateTimeType
logInfo("ClockAlarm", "\n"+dt)
// getZonedDateTime() with no argument is deprecated (silently uses system zone) —
// pass the zone explicitly instead, which is the non-deprecated overload.
val zdt = dt.getZonedDateTime(ZoneId.systemDefault())
val hourValue = zdt.hour
val minuteValue = zdt.minute
// Parse "<Prefix>_CA[W|B]<N>" into its parts.
val fullName = triggeringItem.name
val parts = fullName.split("_CA", 2)
val prefix = parts.get(0)
val suffix = parts.get(1) // e.g. "W1" or "B2"
val typeChar = suffix.substring(0, 1) // "W" or "B"
val alarmNum = suffix.substring(1) // "1" or "2"
val wakeBed = if (typeChar == "W") "Wakeup" else "Bedtime"
val hourItemName = prefix + "_ClockAlarm_ClockAlarm" + alarmNum + wakeBed + "Hour"
val minItemName = prefix + "_ClockAlarm_ClockAlarm" + alarmNum + wakeBed + "Minute"
logInfo("ClockAlarm", "\n"+triggeringItem.name.toString+"\n Wake/Bed time set to "+hourValue+":"+minuteValue)
sendCommand(hourItemName, hourValue.toString)
sendCommand(minItemName, minuteValue.toString)
// The Group:DateTime item itself has no channel/binding, so nothing else ever sets its
// own state — without this, the widget keeps showing its empty placeholder (--:--)
// forever, even though the commands above are being processed correctly.
// Pass 'dt' (explicitly typed as DateTimeType, a State) here, not 'cmd' — cmd's broader
// Command-interface type caused Xtend to resolve the wrong postUpdate overload
// (Item, Number) and fail, since a DateTimeType isn't a Number.
postUpdate(triggeringItem, dt)
}
end
// Stuart Hanlon - MDAR Ltd - Velbus.eu
// Colour_OH5.rules - Rule for converting Colourpicker HSB to RGBW
// Aug 2026 - now with a seperate Brightness channel
//
// FOLDER: $OPENHAB_CONF/rules/ - see the folder note in the AAA template
// .items file's header for why this can't be merged with the Items it
// depends on, despite the tight coupling (it reads the ColourPickers Group
// and the _RED/_GREEN/_BLUE/_WHITE Item naming pattern defined there).
//
// ============================================================================
// HOW THIS FILE RELATES TO THE OTHERS IN THIS COLLECTION
// ============================================================================
// This rule triggers on "Member of ColourPickers received command" - the
// ColourPickers Group is defined in AAA_OH5_Generic_template Alexa V3.items.
// Any Color item added to that Group needs its companion _RED/_GREEN/_BLUE/
// _WHITE Dimmer items present too (same naming convention, see that file) -
// this rule looks them up by name pattern (triggeringItem.name + "_RED" etc),
// it does not reference specific channels itself.
// ============================================================================
//
// Fix applied: the ON/OFF shortcuts and the HSB-split branch must be a single
// if / else if / else chain. The previous version used two separate if
// statements, so when a Colour item received ON, the first if correctly sent
// "0,0,100" - but the second if then evaluated "is state OFF?", found it
// wasn't, and fell into the else branch anyway. That branch tries to cast
// triggeringItem.state (still a plain ON/OFF at that point, not an HSBType)
// via hsbToRgbw(), which either throws a ClassCastException in the log on
// every ON/OFF command, or produces garbage RGBW values that get sent right
// after the correct ones - a race that can visibly flicker the light or
// silently override the colour you just set.
rule "colourPicker"
when
Member of ColourPickers received command
then
// Create a loop to restart the rule with correct values for ON and OFF
// logInfo("Colour","Colour = "+triggeringItem.name.toString+" Changed to "+triggeringItem.state)
if (triggeringItem.state.toString == "ON") {sendCommand(triggeringItem.name.toString,"0,0,100")}
if (triggeringItem.state.toString == "OFF") {sendCommand(triggeringItem.name.toString,"0,0,0")}
else
{
// Split RGBW values from HSL and send to components
// OH5 solution, special thanks to @Hans_Lree and @rikoshak
// var HSBType hsbValue = triggeringItem.state as HSBType
// var rgbw = hsbToRgbw(hsbValue)
var rgbw = hsbToRgbw(triggeringItem.state as HSBType)
var Number redValue = rgbw.get(0)
var Number greenValue = rgbw.get(1)
var Number blueValue = rgbw.get(2)
var Number whiteValue = rgbw.get(3)
// Brightness value
var Number brightnessValue = (triggeringItem.state as HSBType).getBrightness()
// logInfo("Colour",triggeringItem.name.toString+"\n RGBW HEX Values ;\n RED = "+redValue+"\n GREEN = "+greenValue+"\n BLUE = "+blueValue+"\n WHITE = "+whiteValue)
// logInfo("Colour",triggeringItem.name.toString+"\n RGBW & Brightness Values ;\n RED = "+redValue+"% \n GREEN = "+greenValue+"%\n BLUE = "+blueValue+"%\n WHITE+ = "+whiteValue+"%\n BRIGHTNESS = "+brightnessValue+"%")
// Convert HEX value to %, only required if destination item doesn't support 0 to 255
if (redValue >0) {redValue = (redValue/255) *100}
if (greenValue >0) {greenValue = (greenValue/255) *100}
if (blueValue >0) {blueValue = (blueValue/255) *100}
if (whiteValue>0) {whiteValue = (whiteValue/255) *100}
var RED = triggeringItem.name+"_RED"
var GREEN = triggeringItem.name+"_GREEN"
var BLUE = triggeringItem.name+"_BLUE"
var WHITE = triggeringItem.name+"_WHITE"
// var BRIGHTNESS = triggeringItem.name+"_BRIGHTNESS"
logInfo("Colour","\n"+triggeringItem.name.toString+"\n RGBW & Brightness Values ;\n RED = "+redValue+"% \n GREEN = "+greenValue+"%\n BLUE = "+blueValue+"%\n WHITE = "+whiteValue+"%\n BRIGHTNESS = "+brightnessValue+"%")
sendCommand(RED,redValue.toString)
sendCommand(GREEN,greenValue.toString)
sendCommand(BLUE,blueValue.toString)
sendCommand(WHITE,whiteValue.toString)
// ## sendCommand(BRIGHTNESS,brightnessValue.toString)
} // End of IF statement
end
rule "QLC ALL OFF to SubMaster"
when Item QLC_Ch200_Dim changed to 0
then
QLC_SubMaster_1.sendCommand(0)
end

{Velbus} [openHAB] Text files — Amendment: Working with AI Agents

This section supplements the existing "Request MarkDown documents for ALL Velbus modules used in the project" note. It's written from a session where an AI agent (Claude) was asked to build openHAB Items files from a VelbusLink .vlp export and an openHAB Things JSON export, and had to be corrected repeatedly before the heating relay/thermostat map was actually right. The goal here is to save the next person (and the next agent) that back-and-forth.

1. Confirm which protocol document actually matches the hardware, every time

"VMBGP4" is not one document. There is a -2 family (older glass panels) and a -20 family (newer glass panels), each with multiple memory map versions keyed to firmware build number. Using the wrong one doesn't error out — it silently returns plausible-looking garbage (offsets that happen to contain some byte, decoded against the wrong field name).

Before decoding anything, the agent must:

  • Read the type= attribute on the <Module> tag (e.g. VMBGP4-2 vs VMBGP4-20) and match it to the exact protocol document for that variant.
  • Read the build= attribute and select the correct memory map section for that build range (e.g. VMB4RYNO has separate maps for builds ≤0812, 0817–0818, 1019–1022, and 1025+; VMBGP1/2/4 "-2" has map v0 for builds ≤1350 and v1 for 1415+; the "-20" family is different again).
  • Sanity-check the decode against a known-good example before trusting it more broadly — in this session, decoding was only trusted once it independently reproduced a pairing the client had already manually confirmed from VelbusLink's GUI.

2. Ask whether Markdown/reference docs already exist before fetching PDFs

The agent's first instinct was to web-search and fetch the official Velleman protocol PDFs. This wasted tokens and time. The client already had accurate Markdown conversions of the exact PDFs needed. The lesson for the agent: ask "do you already have documentation for this?" before reaching for a general web search, especially for anything module-specific and protocol-level.

3. Sub-address FF is a hard stop, not a soft signal

A Glass Panel with FF in its secondary address slot cannot broadcast thermostat trigger packets (Heater/Boost/Pump/Cooler/Alarm) — there's no bus address for it to send from, regardless of whether the setpoint bytes are populated with valid-looking temperatures. This is worth checking first, before looking at anything else on that panel, because it eliminates entire branches of "what's this wired to?" investigation immediately.

Do not treat "the setpoint bytes are FF (blank)" as the test — they often aren't blank even on a disabled panel (factory defaults persist). The test is the sub-address itself.

4. A single Heater/Boost/Pump/Cooler trigger can fan out to multiple relay channels

Don't assume 1:1. In this installation, one thermostat's Heater output was wired to a dedicated UFH valve, a shared circulation pump serving several rooms, and a whole-house boiler-call relay — three separate relay channels from one trigger. An agent that finds one match and stops looking will produce an incomplete map. Scan the entire linked-action table (all ~37 slots × 4 channels) for every occurrence of a given source address + bit, not just the first hit.

5. Never infer physical room layout or adjacency

The agent guessed that "Stairs" lighting should logically merge into the "Hall" file because they're probably physically adjacent. This is not the agent's information to have. Only the installer, the client, or someone who has actually stood in the building knows the layout. If a room/zone has no obvious grouping, ask — don't reach for spatial common sense as if it were data.

6. Relay channels are binary Switches, never Dimmers, regardless of the label

A channel named "ES Colour" is a Switch that toggles power to a colour-changing fixture — it is not an analogue dimmer/colour channel unless the module's actual itemType is Dimmer/Color. Check the real channel type in the Things export; don't infer capability from a friendly label.

7. One relay module very often serves multiple, unrelated rooms

Don't group Items files by physical module or by the module's VelbusLink Caption. A single VMB4RYNO with a caption like "Study Hall" or "Lamps / Vent Unit" routinely has channel 1 wired to one room and channel 3 wired to a completely different one. Group at the channel level, using each channel's own label, not at the module level.

8. The Temp Sensor channel's own name is the canonical room name — not the module Caption

VelbusLink Captions are installer shorthand and can be stale or use different terminology than what's actually programmed into the panel (e.g. a Caption of "Bed Left" where the panel's own Temp Sensor is named "Master Bedroom" — the latter is what appears on the physical OLED/glass display, and is what the client actually calls the room).

9. An action byte outside your reference document's range is unverified, not stale

A byte found during decoding that falls outside the range covered by whatever protocol reference the agent has on hand is unverified, nothing more. In this session the agent's reference document for VMB4RYNO stopped at raw byte 0x1E, and a real, currently-configured link used byte 0x26 — well past that cutoff. The agent's first mistake was concluding this meant the link was "probably stale or decommissioned" (wrong: the reference was merely incomplete, not the firmware). The agent's second mistake, after being corrected, was guessing the byte's meaning by counting forward from the last documented entry and pattern-matching against a general action-numbering guide — without checking whether the guessed action was already assigned to a different byte value earlier in the same table. It was: the byte the agent guessed for 0x26 turned out to already belong to 0x00 in that table. A byte value can only mean one thing within a single memory-map version; a second guess built the same way as the first is exposed to the same failure.

The actual answer, confirmed directly in VelbusLink: the link was action 705. Toggle inhibit, sourced not from a thermostat's Boost trigger as assumed, but from a separate, purpose-named virtual channel ("Kit Boost Inhib") — a manual inhibit control, unrelated to automatic heating logic. Neither of the agent's two guesses was right, and no amount of positional pattern-matching from an incomplete document would have reached it. The only reliable route was asking the person with VelbusLink open to read the actual configured action off the screen.

The lesson: when a decoded value falls outside documented territory, say so plainly and ask someone with the authoring tool open to confirm it, rather than offering an inferred best guess — even a second, more careful-sounding guess is still a guess, and presenting it with confidence risks the person building an automation model around something that was never actually verified.

10. The same byte position can mean different things depending on what the source actually is

Related to the above: decoding a "linked source" entry generally means decoding two things — an address, and a bit/channel number relative to that address. The bit-number byte only means "which thermostat trigger" (Heater/Boost/Pump/Cooler/Alarm, via a fixed status bitmask) when the source address is genuinely a thermostat's own sub-address broadcasting its status packet. Many Velbus modules (GPOD units in particular) also expose dozens of general-purpose virtual/scene trigger channels on the same bus, addressed and numbered independently of any thermostat bitmask. An agent that assumes one fixed decoding scheme for "bit number" regardless of what kind of channel the source address represents will misattribute a link — in this session, a manual inhibit switch on a spare virtual channel was initially misread as the neighbouring room's thermostat Boost demand, because both happened to use a similar-looking byte in a similar position. Confirm what kind of channel the source address actually is before interpreting the bit-number byte against any particular scheme.

11. "The channel exists in the Things export" ≠ "the channel is in use"

openHAB bindings expose a fixed channel template per module type regardless of whether that feature is actually wired up on site — clock alarms on a plain input module, thermostat channels on a panel with no working sub-address, trigger channels with no linked action anywhere. Presence in the export is not evidence of real-world use. Cross-check against the VLP memory before building Items for anything that looks templated rather than deliberately configured.

Two methods — know which one applies before starting

  1. Read-only survey (naming, obvious channel purpose, what's clearly unused) — the openHAB Things JSON and VelbusLink Captions are usually enough on their own.
  2. Wiring/action verification (what actually triggers what, whether a feature is live) — requires decoding the actual <Memory> blob against the correct protocol document for that exact module variant and build. Don't attempt method 2 with method 1's tools; don't skip method 2 and present a guess from method 1 as if it were confirmed.
// Please note that I have not tested the USB port access bridges in this file.
//
// The Bridge (internal) name is also the same for each, so that they can be interachanged without having to edit other files (*.items and *.rules for example)
// Network bridge, for use with TCP servers like Velserv, Velbus_PB_Server or the upcoming Velbus TCP gateway. Running on the same machine
Bridge velbus:networkbridge:VelbusNetworkBridge "Velbus Network Bridge - Loopback" @ "Loft" [ address="127.0.0.1", port=6000, timeUpdateInterval="360", reconnectionInterval="15"]
// Network bridge, for use with TCP servers like Velserv, Velbus_PB_Server or the upcoming Velbus TCP gateway, running on a different machine on your LAN
// Bridge velbus:networkbridge:VelbusNetworkBridge "Velbus Network Bridge - at 192.168.0.100" @ "Control" [ address="192.168.0.100", port=6000 ]
// USB bridge for use on Linux with symlink ttyACM0
// Bridge velbus:bridge:VelbusBridge "Velbus USB / RS232 Bridge - ttyACM0" @ "Control" [ port="/dev/ttyACM0" ]
// USB bridge for use with Windows, using Com5
// Bridge velbus:bridge:VelbusBridge "Velbus USB / RS232 Bridge - Com5" @ "Control" [ port="com5" ]
// MDAR Limited - Velbus WholeHouse Thermostat aggregation
// WholeHouseThermostat.items
//
// FOLDER: $OPENHAB_CONF/items/ - see the folder note in the AAA template
// file's header for why .items, .things and .rules content cannot be merged
// across files despite their close relationship.
//
// ============================================================================
// HOW THE FILES IN THIS COLLECTION RELATE TO EACH OTHER
// ============================================================================
// velbusbridge1.things -> defines the Bridge Thing referenced below.
// AAA_OH5_Generic_template Alexa V3.items
// -> per-room thermostat/relay items that join
// the Groups defined in THIS FILE by name.
// THIS FILE -> whole-house aggregation Groups only - it does
// not define any per-room Items itself.
// Colour_OH5.rules -> unrelated to this file.
// ============================================================================
//
// HomeKit note: the homekit= tags on the groups below have not been tested
// against real Apple hardware - see the full HOMEKIT METADATA disclaimer at
// the top of the AAA template .items file for why, and validate against
// your own Home app before relying on them. Docs (correct as of 30 June
// 2026, redirects to latest version - check for an archived/version-pinned
// copy if it doesn't match your install): https://www.openhab.org/addons/integrations/homekit/
//
// TEMPLATING NOTE - this applies to every channel string in this whole
// collection, not just this file: addresses (e.g. "03", "0A", "71") are
// placeholders from the reference install and are expected to change on
// every project. The bridge instance name (VelbusNetworkBridge) is the only
// segment of a channel string that usually stays the same across a project -
// see the AAA template file's header for the full find/replace convention.
//
// June 2026 - Merged the previously separate "WholeHouseThermostat.items"
// and "WholeHouseThermostat With Preset dissemination.items" files into one.
// The only real difference between them was whether the optional preset
// target aggregation groups (Comfort/Day/Night/Antifrost, per heating and
// cooling) were active. That toggle is now a single commented block below -
// uncomment it if you want per-preset whole-house averages, leave it
// commented if you only need the core thermostat plus relay aggregation.
// ============================================================
// CORE THERMOSTAT GROUP - always active, required by the per-room
// CabinGPO_Thermostat (and equivalent) items in the AAA template files
// ============================================================
Group WholeHouse_Thermostat "Whole House Thermostat" { ga="Thermostat" [roomHint="House", thermostatModes="off=SAFE,heat=DAY,eco=NIGHT,on=DAY"] , alexa="Endpoint.Thermostat" [name="WholeHouse Thermostat", description="Group of all Velbus Thermostats"] , homekit = "Thermostat" }
Group:Number:AVG WholeHouse_CurrentTemperature "Whole House Current Temperature" (WholeHouse_Thermostat) { ga="thermostatTemperatureAmbient" , alexa="TemperatureSensor.temperature" , homekit = "CurrentTemperature" }
Group:Number:AVG WholeHouse_CurrentTargetTemperature "Whole House Current Target Temperature" (WholeHouse_Thermostat) { ga="thermostatTemperatureSetpoint" , alexa="ThermostatController.targetSetpoint" , homekit = "TargetTemperature" }
Group:String WholeHouse_ThermostatMode "Whole House Thermostat mode" (WholeHouse_Thermostat) { ga="thermostatMode" , alexa="ModeController.mode" [friendlyNames="@Setting.Preset", supportedModes="SAFE=Safe,NIGHT=Night,DAY=Day,COMFORT=Comfort"] , homekit = "TargetHeatingCoolingMode" [HEAT="COMFORT", AUTO="DAY", COOL="NIGHT", OFF="SAFE"] }
Group:String WholeHouse_ThermostatOperatingMode "Whole House Thermostat operating mode" (WholeHouse_Thermostat) { alexa="ThermostatController.thermostatMode" [COOL="COOLING",HEAT="HEATING"] , homekit = "CurrentHeatingCoolingMode" [HEAT="HEATING", COOL="COOLING"] }
// ============================================================
// HEATING / COOLING RELAY AGGREGATION - always active, required by the
// per-room CabinGPO_HeatingRelay / CabinGPO_CoolingRelay items in the AAA
// template files (they join these groups via parentheses)
// ============================================================
Group:Switch:OR(ON,OFF) WholeHouse_HeatingRelay "Whole House Heating Relay" <radiator>
Group:Switch:OR(ON,OFF) WholeHouse_CoolingRelay "Whole House Cooling Relay" <climate>
Group:Number:COUNT(ON) WholeHouse_HeatingRelay_Count "WholeHouse Heating Number of [%s]" <radiator>
Number WholeHouse_HeatingRelay_CountLogging "WholeHouse Heating Logged Number of [%s]" <radiator>
Group:Number:COUNT(ON) WholeHouse_CoolingRelay_Count "WholeHouse Cooling Number of [%s]" <climate>
Number WholeHouse_CoolingRelay_CountLogging "WholeHouse Cooling logged Number of [%s]" <climate>
// ============================================================
// PRESET DISSEMINATION (OPTIONAL) - uncomment this whole block only if you
// want whole-house averages of each thermostat's Comfort/Day/Night/Antifrost
// preset targets. Each room's CabinGPO_HeatingComfortTarget (etc) item joins
// these groups automatically via the parentheses already in the AAA
// template files - you don't need to add anything there, just uncomment here.
// ============================================================
// Group:Number:AVG WholeHouse_HeatingComfortTarget "WholeHouse Heating Comfort Target Temperature" <temperature>
// Group:Number:AVG WholeHouse_HeatingDayTarget "WholeHouse Heating Day Target Temperature" <temperature>
// Group:Number:AVG WholeHouse_HeatingNightTarget "WholeHouse Heating Night Target Temperature" <temperature>
// Group:Number:AVG WholeHouse_HeatingAntifrostTarget "WholeHouse Heating Antifrost Target Temperature" <temperature>
//
// Group:Number:AVG WholeHouse_CoolingComfortTarget "WholeHouse Cooling Comfort Target Temperature" <temperature>
// Group:Number:AVG WholeHouse_CoolingDayTarget "WholeHouse Cooling Day Target Temperature" <temperature>
// Group:Number:AVG WholeHouse_CoolingNightTarget "WholeHouse Cooling Night Target Temperature" <temperature>
// Group:Number:AVG WholeHouse_CoolingSafeTarget "WholeHouse Cooling Safe Target Temperature" <temperature>
// ============================================================
// SAFE MODE OVERRIDE - example only, like every other line in this
// collection: find/replace the address (03) and channel (CH2) for your
// project before adopting.
// ============================================================
// Switch WholeHouse_ForceSafeMode "Whole House - Forced into SAFE mode" {channel="velbus:vmb4ryld:VelbusNetworkBridge:03:CH2"}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment