HTTP Bindingen and transformations

Hi,

I am using some KMTronic LAN Ethernet IP 8 channels WEB Relay boards succesfully on 2.5.x. Because the worlds moves on and OH moves with it, I am starting migration to 4.x

I’ve get those cheap things working with the http-binding, but getting the state give me a small headache. I have the impression that it goes wrong when transformation is (not) done. Because of that the items go back to undef after sending the ON or OFF command.

This is the code of the thing :

UID: http:url:KMTronic1
label: KMTronic 8 Relais 1
thingTypeUID: http:url
configuration:
  authMode: BASIC
  ignoreSSLErrors: false
  baseURL: http://hostname/
  delay: 1000
  stateMethod: POST
  refresh: 30
  commandMethod: GET
  contentType: text/html
  timeout: 3000
  bufferSize: 2048
channels:
  - id: KMTronic-1-2
    channelTypeUID: http:switch
    label: KMTronic 1 Kanaal 2
    description: ""
    configuration:
      onValue: FF0201
      stateContent: XPATH:/response/relay2/text()
      offValue: FF0200
      stateExtension: status.xml
      commandExtension: "%2$s"
      stateTransformation: MAP:1ON0OFF.map
  - id: KMTronic-1-1
    channelTypeUID: http:switch
    label: KMTronic 1 Kanaal 1
    description: ""
    configuration:
      onValue: FF0101
      stateContent: XPATH:/response/relay1/text()
      offValue: FF0100
      stateExtension: status.xml
      commandExtension: "%2$s"
      stateTransformation: MAP:1ON0OFF.map

....

In the .map I’ve used all of these :

key=value
ON="1"
OFF="0"
onValue="1"
offValue="0"
ON=1
OFF=0
onValue=1
offValue=0
=default

In the http log
send (works fine) :

2024-04-18 21:53:59.941 [TRACE] [nding.http.internal.HttpThingHandler] - Sending to 'http://hostname/FF0101': Method = {GET}, Headers = {Accept-Encoding: gzip, User-Agent: Jetty/9.4.52.v20230823}, Content = {null}
2024-04-18 21:54:00.031 [TRACE] [p.internal.http.HttpResponseListener] - Received from 'http://hostname/FF0101': Code = {200}, Headers = {Connection: close, Cache-Control: no-cache}, Content = {<div>
<font size="5"> Licht Inkom Buiten              <span style="color:red">1 </span> </font>
<br />
<br />
<br />
<font size="5"> Licht Inkom Binnen              <span style="color:red">0 </span> </font>
<br />
<br />
<br />
<font size="5"> Licht Garage                    <span style="color:red">0 </span> </font>
<br />
<br />
<br />
<font size="5"> Driewegkraan                    <span style="color:red">0 </span> </font>
<br />
<br />
<br />
<font size="5"> Vat CV Kraan                    <span style="color:red">0 </span> </font>
<br />
<br />
<br />
<font size="5"> Thermostaat Berging 0           <span style="color:red">0 </span> </font>
<br />
<br />
<br />
<font size="5"> Schakelaar Pomp Vat      <span style="color:red">0 </span> </font>
<br />
<br />
<br />
<font size="5"> Schakelaar Pomp CV              <span style="color:red">0 </span> </font>
</div>

<div>
<p hidden>
Status: 1 0  0 0  0 0  0 0 
Relay1: Licht Inkom Buiten           
Relay2: Licht Inkom Binnen           
Relay3: Licht Garage                 
Relay4: Driewegkraan                 
Relay5: Vat CV Kraan    
Relay6: Thermostaat Berging 0        
Relay7: Schakelaar Pomp Vat          
Relay8: Schakelaar Pomp CV           
</p>

get state :

2024-04-18 21:33:49.899 [TRACE] [ttp.internal.http.RefreshingUrlCache] - Sending to 'http://hostname/status.xml': Method = {POST}, Headers = {Accept-Encoding: gzip, User-Agent: Jetty/9.4.52.v20230823}, Content = {XPATH:/response/relay8/text()}
2024-04-18 21:33:49.927 [TRACE] [p.internal.http.HttpResponseListener] - Received from 'http://hostname/status.xml': Code = {200}, Headers = {Connection: close, Content-Type: text/xml, Cache-Control: no-cache}, Content = {<response>
<relay0>1</relay0>
<relay1>0</relay1>
<relay2>0</relay2>
<relay3>0</relay3>
<relay4>0</relay4>
<relay5>1</relay5>
<relay6>0</relay6>
<relay7>0</relay7>
<relay8>1</relay8>
</response>

there are no errors in other log files.

I never used transformations in 2.5, so I think I am missing something at that level…

There is a whole bunch about this Thing definition that doesn’t make sense given the XML you are receiving.

It’s very weird to command the device using a GET and to get the current state of the device using a POST. Shouldn’t it be the other way around?

What’s with the onValue and offValue? “FF0101” (et al) does not appear anywhere in the XML in the state update.

stateContent is the data to send to the HTTP server when a POST or PUT is issued. It makes no sense to put an XPATH transformation there. It’s out going data, not incoming data. You can’t convert ON or OFF to XML using this XPATH expression.

You map only goes one way and I think it’s the wrong way. You’ve nothing to map 1, 0 to ON/OFF. Per the docs, when the thing defined to the left of the = is seen, the transform will return the thing defined to the right of the =. If this transform sees a 1 or 0 this map will return default.

You almost never use both a state transformation and onValue and offValue at the same time.

Maybe if you post your working 2.5 config it will make some sense.

@rlkoshak Thanks for your swift answer.

POST or GET doesn’t matter. The response from the device is exactly the same (PUT is not supported by the device).

What’s with the onValue and offValue? “FF0101” (et al) does not appear anywhere in the XML in the state update.

Correct. status.xml is only for the status.
FF0x01 is ON, FF0x00 is OFF.

stateContent is the data to send to the HTTP server when a POST or PUT is issued. It makes no sense to put an XPATH transformation there. It’s out going data, not incoming data. You can’t convert ON or OFF to XML using this XPATH expression.

“Content for state request (only used if method is POST/PUT)” . Where to put the XML structure to retrieve the value ?

You map only goes one way and I think it’s the wrong way. You’ve nothing to map 1, 0 to ON/OFF. Per the docs, when the thing defined to the left of the = is seen, the transform will return the thing defined to the right of the = . If this transform sees a 1 or 0 this map will return default .

Reversed it, no difference.

.items in 2.5 file for sending :

Switch SchakelaarBuiten "Schakelaar Buiten" { http=">[OFF:GET:http://hostname/FF0100] >[ON:GET:http://hostname/FF0101]" }

.items in 2.5 for status :

String StatusSchakelaarBuiten "Status Schakelaar Buiten" {http="<[http://hostname/status.xml:2500:XPATH(/response/relay1)]"}

And I update SchakelaarBuiten with StatusSchakelaarBuiten via a rule. Only for two (out of 32) relays I need an update, so in 2.5 I added it to some rules that already managed other stuf regarding those things.

At this point, this is just a “getting it right” moment. I just can put the item to a “follow” profile instead of “default” but then the item is not updated when an external source throws the switch on the device.

Smal note : on all browsers I get this when opening status.xml : This XML file does not appear to have any style information associated with it. The document tree is shown below. but Invoke-webrequest does show it exactly the same as in the http log.

Then why are you using POST and GET in your HTTP config? Pick one and stick to it.

Which appears nowhere in the XML you’ve shown so far which is supposedly what the HTTP server returns, in the HTML in the other call that you said works, nor does it appear in your map.

Where does this come from?

stateTransformation.

Well, of course everything else that’s wrong needs to be fixed before you’d see anything coming from this map anyway.

OK, something like this might work

UID: http:url:KMTronic1
label: KMTronic 8 Relais 1
thingTypeUID: http:url
configuration:
  authMode: BASIC
  ignoreSSLErrors: false
  baseURL: http://hostname/
  delay: 1000
  stateMethod: GET
  refresh: 30
  commandMethod: GET
  contentType: text/html
  timeout: 3000
  bufferSize: 2048
channels:
  - id: KMTronic-1-2
    channelTypeUID: http:switch
    label: KMTronic 1 Kanaal 2
    description: ""
    configuration:
      onValue: 1
      stateTransformation: XPATH:/response/relay2/text()
      offValue: 0
      stateExtension: status.xml
      commandExtension: %2$
      commandTransformation: JS(| (input == 'ON') ? "FF0100" : "FF0101")

Based on the XML from above, I’m assuming that 1 means ON and 0 means OFF.

Property Meaning
baseURL: http://hostname/ The root part of the URL, the stateExtension and commandExtension are appended to this
refresh: 30 Call the state URL every 30 seconds
stateMethod: GET HTTP command to retrieve state updates
commandMethod: GET HTTP command to send a command
stateExtension: status.xml Results in calling “http://hostname/status.xml” to get state updates for the Channel
commandExtension: %2$ Results in calling “http://hostname/FF0100” when ON and FF0101 when OFF. This is the result of the commandTransformation appended to the URL
stateTransformation: XPATH:/response/relay2/text() Transformation to apply to what you get back from the server when the state URL is called.
onValue: 1 This happens after the stateTransformation and maps a 1 to ON to update the Item with
offValue: 0 This happens after the stateTransformation and maps a 0 to OFF to update the Item with

commandTransformation: JS(| (input == "ON") ? "FF0100" : "FF0101") : This translates the ON/OFF. To avoid creating a separate map file per Channel, I use an inline JS transformation. It might be the case where you need to use input == '1' instead. I don’t know when/if the onValue/offValue gets applied for a command.

You don’t need a map file at all but you’ll need the JS Scripting add-on installed. You can use other rules languages if that works better for you.

If you did want to use a map instead, you’d omit the onValue and offValue properties and chain the MAP to the stateTransformation: XPATH:/response/relay2/text()∩MAP:relayMap.map with the relayMap.map being

1=ON
0=OFF

Hi @rlkoshak,

You got me running in the right direction and I got some insight in Transformations. I got i working with a .map file for one channel. As you stated, using that is not a ideal solution.

But I get this error using JS

Transformation service JS(| (INPUT == "1") ? "FF0101" for pattern "FF0100") not found!

of course, same for == "ON"

You missed this:

but you’ll need the JS Scripting add-on installed

Also note, it’s case sensitive. It must be input, not INPUT.

That was my first thought also, so I checked and JavaScript Scripting is installed (as I needed it for the Waste Collection Belgium rule template).
I even changed the logging of this to a seperate file, and the logs fills at start of OH, so I guess it is installed correctly as that rule works and the logs fills (last line in the JS Log : Changed state from satisfied to active)

I also noticed the capital letters. In the log it is in upper case, in the Command Transformation field it is lower case.

Code :

  - id: KMTronic-1-1
    channelTypeUID: http:switch
    label: KMTronic 1 Kanaal 1
    description: ""
    configuration:
      onValue: "1"
      commandTransformation: JS(| (input == "1") ? "FF0100":"FF0101")
      offValue: "0"
      stateExtension: status.xml
      commandExtension: "%2$s"
      stateTransformation: XPATH:/response/relay1/text()

Log :

Transformation service JS(| (INPUT == "1") ? "FF0101" for pattern "FF0100") not found!

Oh that’s right, the HTTP and MQTT bindings use a different syntax.

JS: | (input == "1") ? "FF0100" : "FF0101"

Separate the transformation type from the transformation config using : and not put it in parens. Note that elsewhere a transformation is used you use parens.

See Transformations | openHAB. The data passed into a script transform is always a string and the variable is always input, never INPUT.

Getting Closer :
Executing transformation ChannelStateTransformation{pattern=' | (input == "1") ? "FF0100" : "FF0101"', serviceName='JS'} failed: Could not get script for UID ' | (input == "1") '.

Which version of OH 4 are you running?

4.1.1

I’ve never used an inline transform like this in MQTT/HTTP so it might be sensitive to that space after the first :.

JS:|(input == "1") ? "FF0100" : "FF0101"

If that doesn’t fix it I’m out of ideas. It should work. It does work in other places (e.g. profiles, Item state description patterns). It should work here too.

We could try a different language. Rules DSL should look something like:

DSL:|if(input == "1") "FF0100" else "FF0101"

Success ! Sensitive to the space !

I understand correctly that for DSL no add-on has to be installed ?

Correct, though in general I recommend against development of new rules or transformations in Rules DSL. It’s a far less capable and limited environment than even Blockly.

Since i found some post from other OH users who are also struggeling with this device, I will make an how-to (there are some other caveats).
It’s always to start those documents with requierments : none.

I’ll put it in Setup, Configuration and Use, Installation or do you prefer an other location ?

1 Like

Tutorials & Examples > Solutions is a better place.

Will do next week.

Thanks again for the ongoing assistence !