[SOLVED] BBC weather RSS Latest Title split to <64 characters for Velbus OLED (AKA Splitting Strings)

I wanted to be able to display a short string on the Velbus OLED panels that gave a description of the weather.

(I’m sure that Cédric will add the Edge Lit Oled panels to the Velbus binding when his schedule permits.)

I’ve managed to use a simple rule to copy “Now playing” information from music systems to the OLEDs of Velbus panels, but the weather is more of an issue because the BBC RSS feed can be longer than the 64 character limit of a Velbus panel.
(Also it takes too long to scroll across)

The only things I needed to add to openHAB2 were the RSS Feed binding and a Thing that polled the BBC RSS weather feed for my location.

The Virtual Switch called “OLED_Weather” is only there so that I can switch on or off the RSS feed on the OLED panels

I’ve created this rule that seems to do the trick.

I’ll confess that yet again I don’t really know what I’m doing, so the weather0 value isn’t used, but i don’t know how to achieve what I want without setting it up. - (Turns out that it is totally redundant)

Any suggestions for how to tidy this up would be most welcome.

// BBC RSS weather Title
// Obtained from "https://weather-broker-cdn.api.bbci.co.uk/en/forecast/rss/3day/2633697"  where 2633697 is the location code
// See this webpage for more information - https://www.bbc.co.uk/weather/about/17543675
// Obtain your location code by searching in this page - https://www.bbc.co.uk/weather and copy the code from the resulting URL
// Example "Latest Title" text = Today: Light Cloud, Minimum Temperature: 7°C (45°F) Maximum Temperature: 12°C (53°F)
//
// Needs to be split down to less than 64 characters for OLED Velbus panels
//
// For example - "Weather = Light Cloud, Min = 7°C Max = 12°C"
rule "BBC Weather RSS to Velbus Memo text"
when
    Item BBCWeatherRSS_LatestTitle received update or Item OLED_Weather changed
then 
    // logInfo("BBC Weather Info", "BBC weather RSS update = "+BBCWeatherRSS_LatestTitle.state)
    val weather0 = BBCWeatherRSS_LatestTitle.state.toString.split(',').get(0)
    val weather1 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(1)
    val weather2 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(2)
    val weather3 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(3)

    val condition = weather1.toString.split(',').get(0)
    val minT = weather2.toString.split('°').get(0)
    val maxT = weather3.toString.split('°').get(0)

    if (OLED_Weather.state == ON){
        TVRoomGPO_OledDisplay_Memo.sendCommand("Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C")
        BackBedroomGPO_OledDisplay_Memo.sendCommand("Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C")
        Cabin_Memo_Text.sendCommand("Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C")
        LoungeGPO_OledDisplay_Memo.sendCommand("Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C")
        logInfo("OLED MEMO","Weather string sent to OLEDs")
    }
        
    if (OLED_Weather.state == OFF){
        TVRoomGPO_OledDisplay_Memo.sendCommand("")
        BackBedroomGPO_OledDisplay_Memo.sendCommand("")
        Cabin_Memo_Text.sendCommand("")
        LoungeGPO_OledDisplay_Memo.sendCommand("")
        logInfo("OLED MEMO","Blank Msg sent to OLEDs")
    }

    logInfo("OLED MEMO weather","Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C")
end

useful links :

See this webpage for more information - https://www.bbc.co.uk/weather/about/17543675
Obtain your location code by searching in this page - https://www.bbc.co.uk/weather and copy the code from the resulting URL

Velbus OLED panels

Velbus Edge Lit Oled panels

??? the value isn’t used but you can’t solve the problem without using it? Then aren’t you using it?

I’m usually one to not recommend going out of our way to change code for efficiency purposes, but in this case it also avoids some duplicated code.

    // val weather0 = BBCWeatherRSS_LatestTitle.state.toString.split(',').get(0) let's try to not have this line since you say you don't use it.

    val parts = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(1)
    val weather1 = parts.get(1)
    val weather2 = parts.get(2)
    val weather3 = parts.get(3)

For the second part of the Rule, I like to apply Design Pattern: DRY, How Not to Repeat Yourself in Rules DSL. You essentially have the same code in both if statements. I prefer to calculate the message and only call the sendCommands once.

     var message = if(OLED_Weather.state == ON) "Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C" else ""
     var logMsg = if(OLED_Weather.state == ON) "Weather string sent to OLEDs" else "Blank Msg sent to OLEDs"

    TVRoomGPO_OledDisplay_Memo.sendCommand(message)
    BackBedroomGPO_OledDisplay_Memo.sendCommand(message)
    Cabin_Memo_Text.sendCommand(message)
    LoungeGPO_OledDisplay_Memo.sendCommand(message)
    logInfo("OLED MEMO",logMsg)

Now there is one more improvement we can make here. Instead of applying the DRY DP we use a Group. Put all of your Memo Items into the same Group, let’s call it Group:String Memos.

Then the bottom part of your Rule becomes

    Memos.sendCommand(message)
    logInfo("OLED MEMO",logMsg)

And we can even merge these lines with the lines above it:

    Memos.sendCommand(if(OLED_Weather.state == ON) "Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C" else "")
    logInfo("OLED MEMO", if(OLED_Weather.state == ON) "Weather string sent to OLEDs" else "Blank Msg sent to OLEDs")

Resulting in:

rule "BBC Weather RSS to Velbus Memo text"
when
    Item BBCWeatherRSS_LatestTitle received update or Item OLED_Weather changed
then 
    //		logInfo("BBC Weather Info", "BBC weather RSS update = "+BBCWeatherRSS_LatestTitle.state)


    val parts = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(1)
    val weather1 = parts.get(1)
    val weather2 = parts.get(2)
    val weather3 = parts.get(3)

    val condition = weather1.toString.split(',').get(0)
		
    val minT = weather2.toString.split('°').get(0)
	
    val maxT = weather3.toString.split('°').get(0)

    Memos.sendCommand(if(OLED_Weather.state == ON) "Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C" else "")
    logInfo("OLED MEMO", if(OLED_Weather.state == ON) "Weather string sent to OLEDs" else "Blank Msg sent to OLEDs")
end

Hi Rich,

Thanks for your informed advice (as always).

You’ve mentioned things I had no idea where possible.

Well, I really don’t understand how the split command works, is it splitting up to the stated character, or after it?

The incoming string uses a : mostly, but also a comma just to be annoying.

this line :
val weather0 = BBCWeatherRSS_LatestTitle.state.toString.split(',').get(0)

give a return of “Today: Light Cloud”

and val weather1 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(1)
creates a return of “Light Cloud” (the later being what I wanted)

I can see from your example that you take that first split and divide it again at the comma. :smile:

Where your PARTS variables must contain the various bits of text “between” the “:”

What is confusing me is the .get(0) vs .get(1) / .get(2) / .get(3) etc

After that, the rest of your amendments make perfect sense, even where you have an IF statement inside a command, I can see me using that in the future. :smile_cat:

I had tried grouping string items before, but it didn’t work, so I assumed it just wasn’t possible.

However I was trying to read values from multiple string items, rather than disseminate the same string to multiple items.

Your solution does look much nicer.

The ONLY reason I’ll not add that particular edit is that being able to comment out a single line to disable the MemoText on a single glass panel is quite easy, rather than having to edit the Item to remove the group.

:thinking:
However, thinking about it, adding or removing MemoText Items from a selection of String Groups would make it much easier for a Panel to “subscribe” to a range of different information.

FYI,

The Velbus OLED Glass displays simply scroll the latest Memo until the next update or “” {NULL message} is received, so as long as different messages aren’t pushed at EXACTLY the same time, I don’t think there would be a major problem.

Rich,

This second .get(1) is throwing an error val weather1 = parts.get(1)

52:    val parts = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(1)
53:    val weather1 = parts.get(1)
54:    val weather2 = parts.get(2)
55:    val weather3 = parts.get(3)

Error message –
2019-02-20 17:16:41.194 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'BBC Weather RSS to Velbus Memo text': 'get' is not a member of 'java.lang.String'; line 53, column 20, length 12

I’ve tried

52:    val parts = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(1)
53:    val weather1 = parts.toString.get(1)
54:    val weather2 = parts.toString.get(2)
55:    val weather3 = parts.toString.get(3)

But I saw the exact same error message.

However, I think I’m beginning to understand what’s happening with the .get(0) etc commands.

		val weather0 = BBCWeatherRSS_LatestTitle.state.toString.split(',').get(0)
//		val weather1 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(1)
		val weather1 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(2)
		val weather2 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(3)





		val condition = weather0.toString.split(':').get(1)
		

		val minT = weather1.toString.split('°').get(0)
		
		
		val maxT = weather2.toString.split('°').get(0)

Where .get(0) ‘gets’ the first block of text, between / up to the nominated characters.

and .get(1) gets the second block

and .get(2) get the third

etc etc

So splitting the weather0 variable at the ‘,’ and requesting .get(1) provides me with the “Light Cloud” text that I want to display.

So the incoming RSS text of

Today: Light Cloud, Minimum Temperature: 7°C (45°F) Maximum Temperature: 11°C (53°F)

can now be sent to the OLED panels as

xxxxxxxx.sendCommand("Weather = "+condition+" , Min = "+minT+"°C , Max = "+maxT+"°C")

which produces –

Weather = Light cloud , Min = 7°C , Max = 11°C

(Oh how I wait for the day that the text changes to “Bright Sunshine”, but as I live in the UK, I’ll not be holding my breath)

.
.
.
.
Update

Here’s a cruel twist

The RSS feed has just updated and now there isn’t a Maximum Temperature section, meaning that the rule is throwing an error of :3

[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'BBC Weather RSS to Velbus Memo text': 3

new RSS text

BBC weather RSS update = Tonight: Light Cloud, Minimum Temperature: 7°C (45°F)

after a lot of // and reloading, I realised that the “3” error was referring the lack of a 4th split @ ‘:’

Is there some cunning way of only requesting the 4th split of data is if it present in the string?

Some unknown bit of wizarding code to query the incoming text for the amount of ‘:’ in it?
Like =

if (weather0.left(5) == "Today"){
val weather2 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(3)
val maxT = weather2.toString.split('°').get(0)
} else {val maxT = "N/A"}

:thinking:

Alternatively…

Can I spilt the incoming text after the condition section, use ALL (whatever that might be) text after the “,” and simply replace "Minimum " with "Min " and (if it occurs) "Maximum " with "Max ".
And remove “Temperature” or replace it with " " ???

Hold On…

I think I have created a query that works… (I’m certain it can be better, but this works…)

rule "BBC Weather RSS to Velbus Memo text"
when
Item BBCWeatherRSS_LatestTitle received update or Item OLED_Weather changed

then 


logInfo("BBC Weather Info", "BBC weather RSS update = "+BBCWeatherRSS_LatestTitle.state)
var maxT = 0
var minT = 0
var weather2 = ""

	


	val weather0 = BBCWeatherRSS_LatestTitle.state.toString.split(',').get(0)

	val weather1 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(2)



	if (weather0.toString.contains ("Today")){
	weather2 = BBCWeatherRSS_LatestTitle.state.toString.split(':').get(3)
	maxT = weather2.toString.split('°').get(0)
	}



	if (weather0.toString.contains ("Tonight")){
	
	maxT = "N/A"

	}


		if (OLED_Weather.state == ON){
			TVRoomGPO_OledDisplay_Memo.sendCommand("Weather = "+condition+", Min = "+minT+"°C, Max = "+maxT+"°C")

end

Which currently produces…

Weather = Light Cloud, Min = 7°C, Max = N/A°C

with any luck, in the morning when the text starts with “Today”

I will get a Max temp too :slight_smile:

I think the key thing is the split command doesn’t actually change the String. It returns an array with copies of the String split on the given character. So the command

val weather0 = BBCWeatherRSS_LatestTitle.state.toString.split(',').get(0)

and you never use weather0 is a noop (does nothing). It doesn’t change BBCWeatherRSS_LatestTitle.state.toString for the succeeding lines so there is no reason for it to exist.

Which you stated and it appears to be born out in the code you don’t actually use. So why split it out in the first place?

Right, but I think the part you missed is that the weather1 line is operating on the entire text, not just on what is in weather0. Had you written it as:

val weather1 = weather0.split(":").get(1)

then weather0 would be a useful line. But as you should see, you don’t need that first line because the second Item in the list when you split the whole text on : will be the part you want anyway. So you can skip it.

My code should be (unless I have a typo) functionally equivalent to what your original code does. The important things to realize are:

  • calling split does not change the original text; each one of your weatherX lines is operating on the full text from the RSS feed.
  • split returns a list of the parts of the string split on the occurrences of ‘:’ in this case, excluding the ‘:’. So if you have the string foo:bar:baz, calling split(‘:’).get(0) will be foo and split(‘:’).get(2) will be baz. You are getting the Nth part of the String after the split. As with most programming, we start with 0 instead of 1

So just split the String once and save the list to a variable (parts). Then pull out the parts that you want and save those to their own variables.

You can’t use an aggregation function with String Items (what’s the AVG of a bunch of Strings?). Maybe you tried to do that?

This should be possible. I’d have to see what you tried.

In general, editing data (I’m calling Items data here for sake of argument) is safer than editing code. This is why I always fall on the side of making the changes in .items files and sometimes .map files and the like before I’d write a Rule that I would expect to need to edit in the future. It is easier to accidentally introduce subtle errors in code. If you introduce an error in a .items file, it will usually break the whole file and you immediately know.

Another good reason. Other approaches could be using an enable/disable proxy Item for each to turn it on or off. But Group membership would be much easier.

DOH! Dumb mistake. Get rid of the .get(1) at the end. It shouldn’t be there.

if(parts.size >= 4) parts.get(3)

So using the trinary operator (one line if statement)

val weather3 = if(parts.size >= 4) parts.toString.get(3) else ""

The size of the parts array tells you how many segments the String was split into.

You can do that too. Split on “,” and get(1). The use replace(“Minimum”, “Min”) and replace(“Maximum”, “Max”) and replace(“Temperature”, “”).

You can even do it in one line.

message = BBCWeatherRSS_LatestTitle.state.toString.split(',').get(1).replace("Minimum", "Min").replace("Maximum", "Max").replace("Temperature", "")
1 Like

A most excellent and informative reply as always.

Thank you Rich, I really feel like I’ve learnt a lot from this experience.

Now I need to find a dark room with soft walls, as my (mostly wires and cable driven) brain has had the work out of it’s life.

1 Like