Hello Jan and all other Beckhoff enthusiasts out there.
I’ve automated my entire house with a Beckhoff PLC control, based on a CX9020.
I use the Beckhoff TwinCat Building Automation Framework to realize most of my houses control logic. I do highly recomment the use of this framework for home control tasks with Beckhoff.
Beside the TwinCat runtime, the CX9020 comes also with an OPC XML-DA server that can be used to read and write the PLC’s variables through a SOAP webservice protocol.
The server’s binaries can be found under \Hard Disk\WWW on the CX9020 Windows CE image.
The service should already be running. I did not configure anything in that case on my CX9020.
The service can be reached with http POST under
http://<yoursCxIpAddress>/TcPlcDataServiceDa.dll
Here is an example on how I can ask for the current state value of a light and a sunblind trough the OPC XML-DA SOAP protocol in the POST body:
POST http://<yoursCxIpAddress>/TcPlcDataServiceDa.dll HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: Read
Cache-Control: no-cache
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><Read xmlns="http://opcfoundation.org/webservices/XMLDA/1.0/">
<Options ReturnItemName="true" ReturnErrorText="false" ClientRequestHandle="1"/><ItemList>
<Items ItemName="PLC1.arrLightingStates[1].lrAverageControlValue"/>
<Items ItemName="PLC1.arrSunblindStates[1].lrAveragePosition"/>
</ItemList></Read></soap:Body></soap:Envelope>
And here is the response SOAP from the OPC Server:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://opcfoundation.org/webservices/XMLDA/1.0/">
<SOAP-ENV:Body>
<ns1:ReadResponse>
<ns1:ReadResult xsi:type="ns1:ReplyBase" ServerState="running" RevisedLocaleID="en" ReplyTime="2018-05-10T08:19:07.270+00:00" RcvTime="2018-05-10T08:19:07.269+00:00" ClientRequestHandle="1"></ns1:ReadResult>
<ns1:RItemList xsi:type="ns1:ReplyItemList">
<ns1:Items xsi:type="ns1:ItemValue" ItemName="PLC1.arrLightingStates[1].lrAverageControlValue">
<ns1:Value xsi:type="xsd:double">0</ns1:Value>
<ns1:Quality xsi:type="ns1:OPCQuality" VendorField="0" LimitField="none" QualityField="good"></ns1:Quality>
</ns1:Items>
<ns1:Items xsi:type="ns1:ItemValue" ItemName="PLC1.arrSunblindStates[1].lrAveragePosition">
<ns1:Value xsi:type="xsd:double">0</ns1:Value>
<ns1:Quality xsi:type="ns1:OPCQuality" VendorField="0" LimitField="none" QualityField="good"></ns1:Quality>
</ns1:Items>
</ns1:RItemList>
</ns1:ReadResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The important things here are the specific names of the PLC variables to address.
"PLC1.arrLightingStates[1].lrAverageControlValue"
"PLC1.arrSunblindStates[1].lrAveragePosition"
In my case this PLC variables are generated by the TwinCat Building Automation Framework.
These variables might differ in your project and depend on your PLC setting.
So far the theory on how to communicate to a Beckhoff OPC XML-DA Server on CX devices through http POST and SOAP.
And now how I adapted this in an OpenHab rule using the sendHttpPostRequest command for the request.
Then using XPATH to parse the response to an array of raw values, that I use to postUpdate the value states to the OH items at the end.
rule "<CX_Light_Sunblind_Lesen>"
when
Time cron "0/1 * * ? * * *" //every 1 seconds
then
var String url = "http://<yoursCxIpAddress>/TcPlcDataServiceDa.dll"
var String contentType = "text/xml"
var String content = '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Read xmlns="http://opcfoundation.org/webservices/XMLDA/1.0/">
<Options ReturnItemName="true" ReturnErrorText="false" ClientRequestHandle="1"/>
<ItemList>
<Items ItemName="PLC1.arrLightingStates[1].lrAverageControlValue"/>
<Items ItemName="PLC1.arrLightingStates[2].lrAverageControlValue"/>
<Items ItemName="PLC1.arrSunblindStates[1].lrAveragePosition"/>
<Items ItemName="PLC1.arrSunblindStates[2].lrAveragePosition"/>
</ItemList>
</Read>
</soap:Body>
</soap:Envelope>'
var String result = sendHttpPostRequest(url, contentType, content)
//Lightings
var lightings = transform("XPATH", "concat('0',';',
//*[name()='ns1:Items'][@ItemName='PLC1.arrLightingStates[1].lrAverageControlValue'],';',
//*[name()='ns1:Items'][@ItemName='PLC1.arrLightingStates[2].lrAverageControlValue'])", result).split(";")
AL_Entre_Treppenhaus_Licht_EG.postUpdate(transform("SCALE", "OnOff.scale", lightings.get(1)))
AL_Entre_Treppenhaus_Licht_OG.postUpdate(transform("SCALE", "OnOff.scale", lightings.get(2)))
//Sunblinds
var DateTime startSunnblindsTransform = now
var sunblindStates = transform("XPATH", "concat('0',';',
//*[name()='ns1:Items'][@ItemName='PLC1.arrSunblindStates[1].lrAveragePosition'],';',
//*[name()='ns1:Items'][@ItemName='PLC1.arrSunblindStates[2].lrAveragePosition'])", result).split(";")
EG_KuecheEssen_Storre_Kueche.postUpdate(new PercentType(sunblindStates.get(1)))
EG_KuecheEssen_Storre_Essen.postUpdate(new PercentType(sunblindStates.get(2)))
end
This example can easily be extended with more light and sunblind states, or other variables depending on your PLC setup.
For me this construct works now with around 120 lights, 25 sunblinds, 20 HVAC and a few other parameters. It allows me to pull all my PLC variable states in a 1 second cycle to OH, what is quite nice.
I also managed to write all my OH commands in a similar manner direct do the PLC variables as well.
By the way, my OH runs on a raspbian pi 3, which limits the capability for all the XPATH parsing, but with the above parsing of all values in one step to an array I could reduce the parsing demands as good as possible so far.
If someone needs more detailed information on that variant of an Beckhoff <=> OH adaption, please ask.