Hier möchte ich nun mein Projekt vorstellen:
Anbindung einer Lüftungsanlage WAC350 von Westaflex via ModBus. Verwendete Hardware:
Raspi B3+ und ein ModBus Steckmodul: https://www.hwhardsoft.de/deutsch/proje ... 85-shield/
Sowie RJ11/RJ45 Adapter: https://www.kab24.de/kommunikation/kab2 ... 1553093850 Ein großes Dankeschön geht in erste Linie an rossko57 vom Englischen OpenHab Forum. Er hat hier als "Ghostwriter" für mich fungiert.
Da ich zunächst alles über die PaperUI erstellt hatte, habe ich rossko57 meine Files aus der RestAPI zur Verfügung gestellt.
Den Original Thread findet Ihr übrigens hier: https://community.openhab.org/t/setup-m ... ac/70573/6
Des weiteren möchte ich Udo, Madmike, und PeterPan für die Unterstützung an vielen Stellen Danken!
Los gehts:
Here we describe how to use Modbus binding 2.x to allow openHAB to interact with a Westaflex WAC350 aircon, other models are probably similar.
WAC350 provides a serial port, so you’d probably use an RS485 USB dongle on your OH host, or an RS485 shield, then connect with twisted pair cable and RJ45 connector. For a long cable run, you may need to arrange a terminator resistor for your RS485 cable.
RJ45 Port 1 on the unit is reserved for the ‘FLEX’ controller panel, Port 2 is the one for external systems like openHAB, these are separate serial buses.
Follow the WAC manual to set up Port 2 serial parameters – typically 19200 baud, no parity, slave ID 1
We’ll also need to know the Modbus register map for this device, as found in the manual. https://drive.google.com/open?id=1iU30j ... 50DvMpvutZ
The WAC350 has several read-only ‘coils’ for binary signals, some writeable coils for control,
read-only ‘input’ registers for numeric sensing, and writeable ‘holding’ registers for control.
That’s all we need to know to set up Modbus binding.
In this example we only use a few of the available registers.
We need to set up Things for the Modbus binding. A serial Bridge thing to define the basic slave, and poller Bridge things for each group of registers we want to access. Finally data Things that describe how to interpret each register.
You have a choice; you may add Things using PaperUI and edit parameters onscreen, for the OH system to save in its database.
Or you may add Things via a text file with your editor, say wac350.things. (You can also view these, but not edit, in PaperUI.)
Don’t try to mix these methods.
I’m going to list settings in the xxx.things style, as it’s more compact than providing a lot of PaperUI screenshots.
westaflex.things:
Code: Alles auswählen
// define the serial path to Westaflex
Bridge modbus:serial:wac350
[ port="/dev/ttyS0", baud=19200, stopBits="1.0", parity="none", dataBits=8, encoding="rtu" ] {
// define a block of coils to read
Bridge poller wcoil [ start=17 length=1 refresh=10000 type="coil" ] {
// selected coils of interest
Thing data wc17 [ readStart="17", readValueType="bit", writeStart="17", writeType="coil" ]
}
// define block of inputs to read
Bridge poller winputA [ start=0 length=10 refresh=5000 type="input" ] {
// selected input reg
Thing data wi00 [ readStart="0", readValueType="int16", readTransform="JS(divide10.js)" ]
Thing data wi03 [ readStart="3", readValueType="int16", readTransform="JS(divide10.js)" ]
Thing data wi06 [ readStart="6", readValueType="int16", readTransform="JS(divide10.js)" ]
Thing data wi09 [ readStart="9", readValueType="int16", readTransform="JS(divide10.js)" ]
}
// define another block of inputs to read
Bridge poller winputB [ start=12 length=5 refresh=5000 type="input" ] {
Thing data wi13 [ readStart="13", readValueType="int16" ]
Thing data wi14 [ readStart="14", readValueType="int16" ]
Thing data wi15 [ readStart="15", readValueType="int16" ]
Thing data wi16 [ readStart="16", readValueType="int16" ]
}
// define block of holdings to read/write
Bridge poller wholdA [ start=0 length=2 refresh=10000 type="holding" ] {
Thing data wh00 [ readStart="0", readValueType="int16", writeStart="0", writeValueType="int16", writeType="holding" ]
Thing data wh01 [ readStart="1", readValueType="int16", writeStart="1", writeValueType="int16", writeType="holding" ]
}
// define another block of holdings to read/write
Bridge poller wholdB [ start=300 length=10 refresh=10000 type="holding" ] {
Thing data wh305 [ readStart="305", readValueType="uint16" ]
}
}
You will need to make sure the transformation service is installed in OH, and then create this file and put it in/conf/transform/
divide10.js:
Code: Alles auswählen
// Wrap everything in a function (no global variable pollution)
(function(inputData) {
// on read: the polled number as string
return parseFloat(inputData) / 10;
})(input)
At this stage, you should be polling your Westaflex over Modbus error free.
To make the data available to openHAB rules and Uis, you must link Items to the Modbus channels.
westaflex.items:
Code: Alles auswählen
Group WAC350_Sensors "Westaflex sensors"
Number Zuluft "Zuluft [%.0f]" (WAC350_Sensors) { channel="modbus:data:winputA:wi00:number" }
Number Abluft "Abluft [%.0f]" (WAC350_Sensors) { channel="modbus:data:winputA:wi03:number" }
Number Fort_Luft "Fortluft [%.0f]" (WAC350_Sensors) { channel="modbus:data:winputA:wi06:number" }
Number Aussenluft "Aussenluft [%.0f]" (WAC350_Sensors) { channel="modbus:data:winputA:wi09:number" }
Number Abluftfeuchte "Abluftfeuchte [%.0f]" (WAC350_Sensors) { channel="modbus:data:winputB:wi13:number" }
Number Bypassklappe "Bypassklappe [%.0f]" (WAC350_Sensors) { channel="modbus:data:winputB:wi14:number" }
Number Luefter1 "Luefter1 [%.0f]" (WAC350_Sensors) { channel="modbus:data:winputB:wi15:number" }
Number Luefter2 "Luefter2 [%.0f]" (WAC350_Sensors) { channel="modbus:data:winputB:wi16:number" }
Number LuefterStufe "LuefterStufe [%.0f]" (WAC350_Sensors) { channel="modbus:data:wholdA:wh00:number" }
Number ZuluftTempSet "Solltemperatur [%.0f]" (WAC350_Sensors) { channel="modbus:data:wholdA:wh01:number" }
Number FilterTimer "Filter Timer [%.0f]" { channel="modbus:data:wholdB:wh305:number" }
Switch FanStandby_Switch "FanStandby_Switch" { channel="modbus:data:wcoil:wc17:switch" }
westaflexplus.items:
Code: Alles auswählen
// additional Items for rules
// for calculated efficiency
Number Wirkungsgrad "Wirkungsgrad" (WAC350_Sensors)
// set your outdoor temp limits and a control
Switch Lueftung_min_max_Temp "Lueftung_min_max_Temp"
Number:Temperature AussenGrenzMax_Set "AussenGrenzMax_Set"
Number:Temperature AussenGrenzMin_Set "AussenGrenzMin_Set"
// set your timer controls
Switch WAC350_Zeitschaltung_01
Switch WAC350_Zeitschaltung_02
// timer hours and mins controls
Number Deko_WECKER_H "Start Stunde [%s]"
Number Deko_WECKER_M "Start Minute [%s]"
Number Deko_WECKER_Ho "Start Stunde [%s]"
Number Deko_WECKER_Mo "Start Minute [%s]"
westaflex.rules:
Code: Alles auswählen
// Beginn der Datei
// globale Variablen
var Long lLStufe
var Timer Deko = null
rule "Aussenluft manuell"
when
Item AussenTemp changed // your outdoor temp sensor
then
if(Lueftung_min_max_Temp.state != ON) return; // wenn Schalter aus(nicht an) , keine Aktionen
if(!(AussenTemp.state instanceof Number)) {
logWarn("Lüftung manuell", "AussenTemp.state not a Number: {}",AussenTemp.state) // numerisch ?
return;
}
if (AussenTemp.state >= AussenGrenzMax_Set.state && FanStandby_Switch.state != OFF) {
// above max limit
FanStandby_Switch.sendCommand(ON)
logInfo("Lüftung manuell", "LuefterStufe ist jetzt: {} AussenTemp beträgt {}",LuefterStufe.state,AussenTemp.state )
}
else if (AussenTemp.state <= AussenGrenzMin_Set.state && FanStandby_Switch.state != ON) { // below min limit
FanStandby_Switch.sendCommand(OFF)
logInfo("Lüftung manuell", "LuefterStufe ist jetzt: {} AussenTemp beträgt {}",LuefterStufe.state,AussenTemp.state)
}
end
rule "luefter geändert"
when
Item LuefterStufe changed
then
lLStufe = now.millis
end
rule "berechne Wirkungsgrad" // calculate efficiency
when
Item Abluft changed or
Item Zuluft changed or
Item Aussenluft changed
then
if(lLStufe + 15*60*1000 < now.millis && LuefterStufe.state.toString != "0"){
//Lüfter läuft bereits mindestens 15 Minuten auf der aktuellen Stufe
if(!(Abluft.state instanceof Number)) {
logWarn("Wirkungsgrad", "Abluft.state not a Number: {}",Abluft.state)
return;
}
if(!(Zuluft.state instanceof Number)) {
logWarn("Wirkungsgrad", "Zuluft.state not a Number: {}",Zuluft.state)
return;
}
if(!(Aussenluft.state instanceof Number)) {
logWarn("Wirkungsgrad", "Aussenluft.state not a Number: {}", Aussenluft.state)
return;
}
val Number Aussenluft = (Aussenluft.state as Number)
val Number nDivisor = (Abluft.state as Number) - Aussenluft //Wert fuer Berechung gem. Westaflex
val Number nFaktor = (Zuluft.state as Number) - Aussenluft //Wert fuer Berechung gem. Westaflex
if(nDivisor == 0) {
logWarn("Wirkungsgrad", "Aussentemperatur = Ablufttemperatur, Division durch 0!")
Wirkungsgrad.postUpdate(NULL)
} else
Wirkungsgrad.postUpdate((100*nFaktor/nDivisor).intValue) //Berechung gem. Westaflex
}
end
rule "Wecker Deko"
when
Time cron "0 * * * * ?" //Abfrage jede Minute
then
if(WAC350_Zeitschaltung_01.state == ON) {
var sollMinute = (Deko_WECKER_M.state as DecimalType).intValue
var sollStunde = (Deko_WECKER_H.state as DecimalType).intValue
var sollMinuteo = (Deko_WECKER_Mo.state as DecimalType).intValue
var sollStundeo = (Deko_WECKER_Ho.state as DecimalType).intValue
if (sollMinute == now.getMinuteOfHour && sollStunde == now.getHourOfDay) {
LuefterStufe.sendCommand(1)
} else if (sollMinuteo == now.getMinuteOfHour && sollStundeo == now.getHourOfDay) {
LuefterStufe.sendCommand(0)
}
}
if(WAC350_Zeitschaltung_02.state == ON) {
var sollMinute = (Deko_WECKER_Ma.state as DecimalType).intValue
var sollStunde = (Deko_WECKER_Ha.state as DecimalType).intValue
var sollMinuteo = (Deko_WECKER_Maa.state as DecimalType).intValue
var sollStundeo = (Deko_WECKER_Haa.state as DecimalType).intValue
if (sollMinute == now.getMinuteOfHour && sollStunde == now.getHourOfDay) {
LuefterStufe.sendCommand(2)
} else if (sollMinuteo == now.getMinuteOfHour && sollStundeo == now.getHourOfDay) {
LuefterStufe.sendCommand(1)
}
}
end
Code: Alles auswählen
// globale Variablen müssen zu Beginn der rules Datei deklariert werden!
var Timer tFilter = null
rule "Filterwarnung"
when
Item FilterTimer changed
then
if(tFilter === null && (FilterTimer.state >2000 )) {
tFilter = createTimer(now.plusHours(12), [ |
tFilter = null
])
sendBroadcastNotification("Erinnerung !! Filterwechsel bei 2160h! Aktueller Wert "+FilterTimer.state.toString+"h") //Allgemeine Warnung an alle
}
end
Code: Alles auswählen
Switch item=FanStandby_Switch label="Lüftung Standby" icon="switch"
Text label="Lüftung manuell" icon="flow" {
Switch item=Lueftung_min_max_Temp label="Lüftung manuell" icon="switch"
Text item=AussenTemp label="AussenTemp aktuell [%.1f °C]" icon="temperature" valuecolor=[>27="red",>20="orange",>10="purple",>5="green",<=5="blue"]
Setpoint item=AussenGrenzMax_Set label="AussenTemp Lüftung Stop [%.1f °C]" icon="temperature" minValue=15 maxValue=24 step=0.5
Setpoint item=AussenGrenzMin_Set label="AussenTemp Lüftung Start [%.1f °C]" icon="temperature" minValue=13 maxValue=20 step=0.5
}
Text item=Lueftung_min_max_Temp label="Lüftung manuell ist Aktiv" icon="switch" visibility=[Lueftung_min_max_Temp==ON]
Text label="Zeitschaltuhr" icon="time" {
Switch item=WAC350_Zeitschaltung_01 label="Stufe: Stop/Klein" icon="time" mappings=[ON="Timer an", OFF="Timer aus"]
Setpoint item=Deko_WECKER_H minValue=0 maxValue=23 step=1
Setpoint item=Deko_WECKER_M minValue=0 maxValue=59 step=1
Setpoint item=Deko_WECKER_Ho minValue=0 maxValue=23 step=1
Setpoint item=Deko_WECKER_Mo minValue=0 maxValue=59 step=1
Selection item=LuefterStufe label="Lüfterstufe" icon="fan" mappings=[0="Stopp", 1="Klein", 2="Mittel", 3="Hoch"]
Switch item=WAC350_Zeitschaltung_02 label="Stufe: Mittel/Klein" icon="time" mappings=[ON="Timer an", OFF="Timer aus"]
Setpoint item=Deko_WECKER_Ha minValue=0 maxValue=23 step=1
Setpoint item=Deko_WECKER_Ma minValue=0 maxValue=59 step=1
Setpoint item=Deko_WECKER_Haa minValue=0 maxValue=23 step=1
Setpoint item=Deko_WECKER_Maa minValue=0 maxValue=59 step=1
}
Text item=WAC350_Zeitschaltung_01 label="Zeitschaltuhr Stop-Klein ist AKTIV" icon="time" visibility=[WAC350_Zeitschaltung_01==ON]
Text item=WAC350_Zeitschaltung_02 label="Zeitschaltuhr Mittel-Klein ist AKTIV" icon="time" visibility=[WAC350_Zeitschaltung_02==ON]
Selection item=LuefterStufe label="Lüfterstufe" icon="fan" mappings=[0="Stopp", 1="Klein", 2="Mittel", 3="Hoch"]
Setpoint item=ZuluftTempSet label="Solltemperatur [%.1f °C]" icon="temperature" minValue=15 maxValue=25 step=1
Text item=Luefter1 label="Lüfter 1 Drehzahl [%s %%]" icon="fan"
Text item=Luefter2 label="Lüfter 2 Drehzahl [%s %%]" icon="fan"
Text item=Abluft label="Abluft aktuell [%.1f °C]" icon="temperature" valuecolor=[>27="red",>20="orange",>10="purple",>5="green",<=5="blue"]
Text item=Zuluft label="Zuluft aktuell [%.1f °C]" icon="temperature" valuecolor=[>27="red",>20="orange",>10="purple",>5="green",<=5="blue"]
Text item=Aussenluft label="Aussenluft aktuell [%.1f °C]" icon="temperature" valuecolor=[>27="red",>20="orange",>10="purple",>5="green",<=5="blue"]
Text item=Fort_Luft label="Fortluft aktuell [%.1f °C]" icon="temperature" valuecolor=[>27="red",>20="orange",>10="purple",>5="green",<=5="blue"]
Text item=Abluftfeuchte label="Abluftfeuchte aktuell [%s %%]" icon="humidity" valuecolor=[>70="red",>60="orange",>50="purple",>35="green",<=25="blue"]
Text item=Wirkungsgrad label="Wirkungsgrad aktuell [%s %%]" icon="qualityofservice"
Text item=Bypassklappe label="Bypass: [MAP(de.map):%s]" icon="switch"
Text item=FilterTimer label="Filtertimer [%s h]" icon="time" valuecolor=[>2159="red",>2000="orange",>0="green"]
Text label="Filter tauschen bei 2160h!" icon="error" visibility=[FilterTimer>=2000]
Switch item=FiltertimerReset label="Reset Filtertimer" icon="error" visibility=[FilterTimer>=2000,FilterTimer==0]
Übersicht:
Viel Spass damit!