Getting Ubi Sensor Data the Hard Way... Or how I stopped worrying and learned to love the XMLslurper

I’m getting the same “java.lang.NullPointerException: Cannot invoke method writeTo() on null object @ line 137” error and I suspect you’re correct about the multiple Ubis (I have three).

Ok, making some progress…

Had to change line 137 from:

doc[0].children[1].children[5].children[1].writeTo(x)

To:

doc[0].children[1].children[6].children[1].writeTo(x)

to adjust for restructured page.

Also made the following change to cater for the fact that I have the Ubi Portal report in Celsius rather than Fahrenheit (and again a slightly different page structure):

// Following was original code - based on Ubi Portal reporting temps in Fahrenheit
//        v.temp = linesAsList[6].minus("document.write(toFahrenheit(").minus("));") //temp in C
//      v.temp = Math.round((Double.parseDouble(v.temp) * 1.8 + 32)*100) /100 // Temp in F

// This is my code - based on Ubi Portal reporting temps in Celsius and wanting to report in Celsius
        v.temp = linesAsList[4].minus("document.write(").minus(".toFixed(2));") //temp in C
        v.temp = Math.round(Double.parseDouble(v.temp))

Note this is only reporting sensors for the first Ubi listed in the Portal…more kludging to come…

As is typical for this type of screen-scraping - it’s very sensitive to changes at the Ubi Portal. It will be good to have a proper API based approach :slight_smile:

I installed both the device type(shows up in Things on app) and the SmartApp which also shows up.
The problem is that on the app after the authentication which is successful when I press “Next” it doesn’t show up anything.In the Connect smartapp code in the final line there is a clientid.Do I have to change that there?

Also when you copy-paste the code it gives you an error.You go on App Setting s and enable OAuth so as to be published.
Am I missing sth?

@DarcRanger - Here is my version of @pstuart’s ps_Ubi which has been modified to support multiple Ubis.

Once you have the device type installed and published, you create multiple Ubi devices, one for each Ubi you have, so each Ubi becomes a “thing”. When you configure each device you need to specify which “Ubi number” it is (1, 2, 3, …), based upon the order in which it appears in the Ubi portal.

Note that I’ve also changed the code to work with temperatures in Celsius rather than Fahrenheit (we use the metric system here downunder). If you need Fahrenheit then hopefully you can see what needs changing back in the code.

/**
 *  sn_Ubi
 *
 *  Portions Copyright 2014 patrick@patrickstuart.com
 *  Portions Copyright 2014 Steven Newnham
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 *  Based upon ps_Ubi by Patrick Stuart
 *  Changes support multiple Ubis and use of temperatures in Celsius
 *
 */

preferences {
    input("username", "text", title: "Username", description: "Your Ubi Portal username (usually an email address)")
    input("password", "password", title: "Password", description: "Your Ubi password")
    input("ubinumber", "number", title: "Ubi number", description: "Which Ubi is this in the Portal (1, 2, 3, etc.)")
    }
    
metadata {
    definition (name: "sn_Ubi", namespace: "snewnham", author: "Steven Newnham") {
        capability "Polling"
        capability "Relative Humidity Measurement"
        capability "Illuminance Measurement"
        capability "Temperature Measurement"
        
        
        attribute "airPressure", "string"
        attribute "soundLevel", "string"
    }

    simulator {
    
    }

    // Note: Value entries for background colours changed to match Celsius scale
    //       Not a direct Fahrenheit to Celsius conversion as original values
    //       didn't make much sense at the high end
    tiles {
        valueTile("temperature", "device.temperature", width: 1, height: 1, canChangeIcon: false) {
            state("temperature", label: '${currentValue}°', unit:"C", backgroundColors: [
                    [value: 0, color: "#153591"],
                    [value: 7, color: "#1e9cbb"],
                    [value: 15, color: "#90d2a7"],
                    [value: 25, color: "#44b621"],
                    [value: 30, color: "#f1d801"],
                    [value: 35, color: "#d04e00"],
                    [value: 40, color: "#bc2323"]
                ]
            )
        }
        valueTile("humidity", "device.humidity", inactiveLabel: false) {
            state "default", label:'${currentValue}%', unit:"Humidity"
        }
        valueTile("illuminance", "device.illuminance", inactiveLabel: false) {
            state "luminosity", label:'${currentValue}lux', unit:"Light"
        }
        valueTile("airPressure", "device.airPressure", inactiveLabel: false) {
            state "default", label:'${currentValue}Kpa', unit:"Air Pressure"
        }
        valueTile("soundLevel", "device.soundLevel", inactiveLabel: false) {
            state "default", label:'${currentValue}db', unit:"Sound"
        }
        standardTile("refresh", "device.poll", inactiveLabel: false, decoration: "flat") {
            state "default", action:"polling.poll", icon:"st.secondary.refresh"
        }
        
        
        main "temperature"
        details(["temperature", "humidity", "illuminance", "airPressure", "soundLevel", "refresh"])
    }
}

// parse events into attributes
def parse(String description) {
    log.debug "Parsing '${description}'"
    // TODO: handle 'humidity' attribute
    // TODO: handle 'illuminance' attribute
    // TODO: handle 'temperature' attribute
    // TODO: handle 'carbonMonoxide' attribute

}

// handle commands
def poll() {
    // log.debug "Executing 'poll'"
    
    // TODO: handle 'poll' command
    login()
}




def login() {   
    def params = [
        uri: 'https://portal.theubi.com/login.do',
        headers: [
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Encoding': 'sdch',
        'Host': 'portal.theubi.com',
        'DNT': '1',
        'Origin': 'https://portal.theubi.com/login.jsp',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36'
            ],
        body: [j_username: username, j_password: password]
    ]

    data.cookies = ''

    httpPost(params) { response ->
        log.debug "Request was successful, $response.status"
        //log.debug response.headers
        response.getHeaders('Set-Cookie').each {
            String cookie = it.value.split(';')[0]
            //log.debug "Adding cookie to collection: $cookie"
            data.cookies = data.cookies + cookie + ';'
        }
        //log.debug "Cookies: $data.cookies"

    }
       

    def params2 = [
        uri: "https://portal.theubi.com/room/",
        headers: [
          'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
          'Accept-Encoding': 'plain', 
          'Cookie': data.cookies        
          ],
        ]

    httpGet(params2) { response2 ->
        log.debug "Request was successful, $response2.status"
        def doc = response2.data
        def x = new StringWriter()
        doc[0].children[1].children[6].children[1].writeTo(x)
        def linesAsList = x.toString().minus(" ").split( /\n|\r|\n\r|\r\n/ ).collect { it.replace( "'", '' ) }
//      log.debug linesAsList
        def v = [:]
        
        // Calculate an offset into the list of lines based upon which Ubi we're after
        int ubiOffset = (ubinumber.toInteger() - 1) * 15;
        
// Following was original code - based on Ubi Portal reporting temps in Fahrenheit
//        v.temp = linesAsList[6].minus("document.write(toFahrenheit(").minus("));") //temp in C
//      v.temp = Math.round((Double.parseDouble(v.temp) * 1.8 + 32)*100) /100 // Temp in F

// This is my code - based on Ubi Portal reporting temps in Celsius and wanting to report in Celsius
        v.temp = linesAsList[(ubiOffset + 4)].minus("document.write(").minus(".toFixed(2));") //temp in C
        v.temp = Math.round(Double.parseDouble(v.temp))

        v.humidity = Double.parseDouble(linesAsList[(ubiOffset + 11)].minus("Humidity").minus("%")) // humidity
        v.airPressure = linesAsList[(ubiOffset + 12)].minus("Air Pressure").minus("KPa") //Air Pressure
        //v.airPressure = Double.parseDouble(v.airPressure) / 100// into bar
        v.soundLevel = Double.parseDouble(linesAsList[(ubiOffset + 13)].minus("Sound level").minus("dB")) //Sound Level
        v.lightLevel = Double.parseDouble(linesAsList[(ubiOffset + 14)].minus("Light level").minus("lux")) //Light Level
        log.debug v
        
        sendEvent(name: 'temperature', value: v.temp, unit: "C")
        sendEvent(name: 'humidity', value: v.humidity)
        sendEvent(name: 'illuminance', value: v.lightLevel, unit: "lux")
        sendEvent(name: 'airPressure', value: v.airPressure.toString())
        sendEvent(name: 'soundLevel', value: v.soundLevel)
        
        }
    
}

You should be aware that this is screen-scraping the Ubi Portal HTML page, so it has a high likelihood of breaking in the future as changes occur to the Ubi Portal website. Also, each Ubi device will fetch a copy of the page, so it’s not the most efficient.

@pstuart has an API based approach in the works, pending the release of the API functionality by the Ubi powers that be - that will be a much better long term approach once it becomes available.

Which app are you talking about? If you’re talking about the “Ubi (Connect)” app in @pstuart’s Github repository, you don’t need that to use the ps_Ubi device type.

Once you’ve installed and published the ps_Ubi device type via the IDE you can create a device based upon it. When you configure it you need to provide your Ubi portal UserID and password. The device works by logging in to the Ubi portal and scraping the sensor data from the HTML page.

Once the Ubi thing is in your list of things, you can use it like any other SmartThings sensor - e.g. it will appear as a temperature sensor and a hygrometer (humidity sensor) in the ActiON (Web Dashboard) smartapp, etc. You can open the Ubi thing in your phones SmartThings app to see the various sensor readings available (temperature, humidity, illuminance, air pressure, sound level).

I had to change the code for it to work for me - most likely because I have more than one Ubi whilst @pstuart only has one at the moment. The layout of the HTML page from the portal didn’t match the original code for me. My changed code is posted in a prior post on this thread.

My UBI died. Support has been less then supportive. Trying another resurrect tonight, but no real way to factory reset this thing.

Getting pretty fed up with the device. Not ready for prime time.

@pstuart That’s not comforting…

@darrylb What do you want me to say, its all right, technology fails. It’s ok…

Support from Ubi has been essentially an episode of the IT Crowd. Have you tried turning it off and back on? Press the power button? What color are the leds?

Seriously? How about where you get a debug log, access to the console? Is ADB available over usb? Can I put it into fastboot?

It’s running android for pete’s sake, its not rocket science.

Ok - OT, but - The IT Crowd - isn’t that an excellent series?

Please tell me you’ve watched Series 1, Episode 1 from the DVD’s with the Leet speak subtitles turned on - the breadth of their in-jokes is truly inspirational. :laughing:

I will have to, didn’t know that option existed. I have the dvds.

Just don’t watch the American pilot if the IT Crowd with Joe McCale its painful… Even with Moss.

Honestly, I’ve been less impressed with Ubi lately. For a while it was working pretty well for me, but I think they did an update a while back and it’s been a bit more spotty since then.

On the good side, it’s less likely to false trigger, especially from the TV.

On the bad side, it’s less likely to understand me, seems to be a little harder to get a true trigger, and HTTP sends to Ubi can be slow/late. (For example, I have Ubi make an announcement when my car gets home. ST also home my garage door at the same time. Yesterday when I got home, my door opened. I parked my car, got out, when in the house, dropped my bag in the living room, and only THEN did Ubi announce that I was home.)

Ok, prompted by the “Smart Alarm 2.1.0” thread (http://community.smartthings.com/t/smart-alarm-2-1-0-is-available/6542), I’ve added the SpeechSynthesis device capability to my version of the Ubi device type.

To enable this you’ll need the device type code below.

You’ll also need to use the Ubi portal to create a new “lesson” for each of your Ubi’s. It should be triggered by a HTTP request and as a result use a voice utterance of “${text}” (without the quotes).
I.e. On the Ubi Portal:

  1. Select your Ubi
  2. Click “Add Lesson
  3. Use the “Trigger” dropdown to select “HTTP Request
  4. Use the “Add an action” dropdown to select “Voice
    announcement
  5. In the text area, enter the string “${text}” (without the
    quotes)
  6. Copy the access token (I find it easiest to copy out of the blue URL
    text) - we’ll use this later, so save it somewhere
  7. Click “Save to Ubi

Repeat the above steps for each one of your Ubis.

Update your SmartThings device type for the Ubi with my updated code, below. Refer to earlier posts in this thread if you need to know how to do this (and reminders about Celsius vs. Fahrenheit, etc.).

Once you’ve published the new device type for yourself, copy the access token(s) into the new “Speech Token” parameter on your SmartThings Ubi device(s).

This is easiet done via the IDE (My Devices --> select your Ubi --> Preferences (edit)), but you can set it via your mobile device if you prefer.

Note that you’ll need to set up a separate lesson for each Ubi you have and therefore you’ll have a different access token for each Ubi, entered into each corresponding Ubi device in SmartThings.

That’s it - you should now be able to use any of your Ubis as a SpeechSynthesis device with SmartApps such as Smart Alarm

 /**
 *  sn_Ubi
 *
 *  Portions Copyright 2014 patrick@patrickstuart.com
 *  Portions Copyright 2014 Steven Newnham
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 *  Based upon ps_Ubi by Patrick Stuart
 *  Changes support:
 *    - multiple Ubis
 *    - use of temperatures in Celsius
 *    - capability "Speech Synthesis" - allows use of Ubi as a device with SmartApps looking for this capability
 *
 *  speechToken is the access token for a Ubi Lesson, triggered via a HTTP request to the Ubi Portal. The lesson should
 *  use a voice announcement of ${text}  (i.e. take the "text" parameter from the HTTP request and convert it to speech).
 */

preferences {
    input("username", "text", title: "Username", description: "Your Ubi Portal username (usually an email address)")
    input("password", "password", title: "Password", description: "Your Ubi password")
    input("ubinumber", "number", title: "Ubi number", description: "Which Ubi is this in the Portal (1, 2, 3, etc.)")
    input("speechToken", "text", title: "Speech Token", description: "Token for the speech lesson on this UBI")
    }
    
metadata {
    definition (name: "sn_Ubi", namespace: "snewnham", author: "Steven Newnham") {
        capability "Polling"
        capability "Relative Humidity Measurement"
        capability "Illuminance Measurement"
        capability "Temperature Measurement"
         capability "Speech Synthesis"       
        
        attribute "airPressure", "string"
        attribute "soundLevel", "string"
    }

    simulator {
    
    }

    // Note: Value entries for background colours changed to match Celsius scale
    //       Not a direct Fahrenheit to Celsius conversion as original values
    //       didn't make much sense at the high end
    tiles {
        valueTile("temperature", "device.temperature", width: 1, height: 1, canChangeIcon: false) {
            state("temperature", label: '${currentValue}°', unit:"C", backgroundColors: [
                    [value: 0, color: "#153591"],
                    [value: 7, color: "#1e9cbb"],
                    [value: 15, color: "#90d2a7"],
                    [value: 25, color: "#44b621"],
                    [value: 30, color: "#f1d801"],
                    [value: 35, color: "#d04e00"],
                    [value: 40, color: "#bc2323"]
                ]
            )
        }
        valueTile("humidity", "device.humidity", inactiveLabel: false) {
            state "default", label:'${currentValue}%', unit:"Humidity"
        }
        valueTile("illuminance", "device.illuminance", inactiveLabel: false) {
            state "luminosity", label:'${currentValue}lux', unit:"Light"
        }
        valueTile("airPressure", "device.airPressure", inactiveLabel: false) {
            state "default", label:'${currentValue}Kpa', unit:"Air Pressure"
        }
        valueTile("soundLevel", "device.soundLevel", inactiveLabel: false) {
            state "default", label:'${currentValue}db', unit:"Sound"
        }
        standardTile("refresh", "device.poll", inactiveLabel: false, decoration: "flat") {
            state "default", action:"polling.poll", icon:"st.secondary.refresh"
        }
        
        
        main "temperature"
        details(["temperature", "humidity", "illuminance", "airPressure", "soundLevel", "refresh"])
    }
}

// parse events into attributes
def parse(String description) {
    log.debug "Parsing '${description}'"
    // TODO: handle 'humidity' attribute
    // TODO: handle 'illuminance' attribute
    // TODO: handle 'temperature' attribute
    // TODO: handle 'carbonMonoxide' attribute

}

// handle commands
def poll() {
    // log.debug "Executing 'poll'"
    
    // TODO: handle 'poll' command
    login()
}




def login() {   
    def params = [
        uri: 'https://portal.theubi.com/login.do',
        headers: [
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Encoding': 'sdch',
        'Host': 'portal.theubi.com',
        'DNT': '1',
        'Origin': 'https://portal.theubi.com/login.jsp',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36'
            ],
        body: [j_username: username, j_password: password]
    ]

    data.cookies = ''

    httpPost(params) { response ->
        log.debug "Request was successful, $response.status"
        //log.debug response.headers
        response.getHeaders('Set-Cookie').each {
            String cookie = it.value.split(';')[0]
            //log.debug "Adding cookie to collection: $cookie"
            data.cookies = data.cookies + cookie + ';'
        }
        //log.debug "Cookies: $data.cookies"

    }
       

    def params2 = [
        uri: "https://portal.theubi.com/room/",
        headers: [
          'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
          'Accept-Encoding': 'plain', 
          'Cookie': data.cookies        
          ]
        ]

    httpGet(params2) { response2 ->
        log.debug "Request was successful, $response2.status"
        def doc = response2.data
        def x = new StringWriter()
        doc[0].children[1].children[6].children[1].writeTo(x)
        def linesAsList = x.toString().minus(" ").split( /\n|\r|\n\r|\r\n/ ).collect { it.replace( "'", '' ) }
//      log.debug linesAsList
        def v = [:]
        
        // Calculate an offset into the list of lines based upon which Ubi we're after
        int ubiOffset = (ubinumber.toInteger() - 1) * 15;
        
// Following was original code - based on Ubi Portal reporting temps in Fahrenheit
//        v.temp = linesAsList[6].minus("document.write(toFahrenheit(").minus("));") //temp in C
//      v.temp = Math.round((Double.parseDouble(v.temp) * 1.8 + 32)*100) /100 // Temp in F

// This is my code - based on Ubi Portal reporting temps in Celsius and wanting to report in Celsius
        v.temp = linesAsList[(ubiOffset + 4)].minus("document.write(").minus(".toFixed(2));") //temp in C
        v.temp = Math.round(Double.parseDouble(v.temp))

        v.humidity = Double.parseDouble(linesAsList[(ubiOffset + 11)].minus("Humidity").minus("%")) // humidity
        v.airPressure = linesAsList[(ubiOffset + 12)].minus("Air Pressure").minus("KPa") //Air Pressure
        //v.airPressure = Double.parseDouble(v.airPressure) / 100// into bar
        v.soundLevel = Double.parseDouble(linesAsList[(ubiOffset + 13)].minus("Sound level").minus("dB")) //Sound Level
        v.lightLevel = Double.parseDouble(linesAsList[(ubiOffset + 14)].minus("Light level").minus("lux")) //Light Level
        log.debug v
        
        sendEvent(name: 'temperature', value: v.temp, unit: "C")
        sendEvent(name: 'humidity', value: v.humidity)
        sendEvent(name: 'illuminance', value: v.lightLevel, unit: "lux")
        sendEvent(name: 'airPressure', value: v.airPressure.toString())
        sendEvent(name: 'soundLevel', value: v.soundLevel)
        
        }
    
}


// SpeechSynthesis.speak
def speak(textToSpeak) {
    log.debug("speak(${textToSpeak})")
    def params = [
            uri: "https://portal.theubi.com",
            path: "/webapi/behaviour",
            query: [
                access_token : speechToken,
                text : textToSpeak
            ]
    ]

    httpGet(params) { response ->
        log.debug "Request return code, $response.status"
    }
}
1 Like

Thanks for the updates to the program @chuckles. I do have one problem and that is with evidently an old error that showed up earlier.

“java.lang.NullPointerException: Cannot invoke method writeTo() on null object @ line 160”

This error was the Children/writeTo(x) issue.

As noted above, it was fixed by children [5] to children[6]. I even went back to the non-speech version and now have the same issue. I will check this evening on another machine to see if that factors in.

@DarcRanger - Ubi have changed the Portal web page again.

You now need:

doc[0].children[1].children[7].children[1].writeTo(x)

This is why an API based approach will be much better than scraping the HTML page :slight_smile:

Updated version of code here:

/**
 *  sn_Ubi
 *
 *  Portions Copyright 2014 patrick@patrickstuart.com
 *  Portions Copyright 2014 Steven Newnham
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 *  Based upon ps_Ubi by Patrick Stuart
 *  Changes support:
 *    - multiple Ubis
 *    - use of temperatures in Celsius
 *    - capability "Speech Synthesis" - allows use of Ubi as a device with SmartApps looking for this capability
 *
 *  speechToken is the access token for a Ubi Lesson, triggered via a HTTP request to the Ubi Portal. The lesson should
 *  use a voice announcement of ${text}  (i.e. take the "text" parameter from the HTTP request and convert it to speech).
 */

preferences {
    input("username", "text", title: "Username", description: "Your Ubi Portal username (usually an email address)")
    input("password", "password", title: "Password", description: "Your Ubi password")
    input("ubinumber", "number", title: "Ubi number", description: "Which Ubi is this in the Portal (1, 2, 3, etc.)")
    input("speechToken", "text", title: "Speech Token", description: "Token for the speech lesson on this UBI")
    }
    
metadata {
    definition (name: "sn_Ubi", namespace: "snewnham", author: "Steven Newnham") {
        capability "Polling"
        capability "Relative Humidity Measurement"
        capability "Illuminance Measurement"
        capability "Temperature Measurement"
         capability "Speech Synthesis"       
        
        attribute "airPressure", "string"
        attribute "soundLevel", "string"
    }

    simulator {
    
    }

    // Note: Value entries for background colours changed to match Celsius scale
    //       Not a direct Fahrenheit to Celsius conversion as original values
    //       didn't make much sense at the high end
    tiles {
        valueTile("temperature", "device.temperature", width: 1, height: 1, canChangeIcon: false) {
            state("temperature", label: '${currentValue}°', unit:"C", backgroundColors: [
                    [value: 0, color: "#153591"],
                    [value: 7, color: "#1e9cbb"],
                    [value: 15, color: "#90d2a7"],
                    [value: 25, color: "#44b621"],
                    [value: 30, color: "#f1d801"],
                    [value: 35, color: "#d04e00"],
                    [value: 40, color: "#bc2323"]
                ]
            )
        }
        valueTile("humidity", "device.humidity", inactiveLabel: false) {
            state "default", label:'${currentValue}%', unit:"Humidity"
        }
        valueTile("illuminance", "device.illuminance", inactiveLabel: false) {
            state "luminosity", label:'${currentValue}lux', unit:"Light"
        }
        valueTile("airPressure", "device.airPressure", inactiveLabel: false) {
            state "default", label:'${currentValue}Kpa', unit:"Air Pressure"
        }
        valueTile("soundLevel", "device.soundLevel", inactiveLabel: false) {
            state "default", label:'${currentValue}db', unit:"Sound"
        }
        standardTile("refresh", "device.poll", inactiveLabel: false, decoration: "flat") {
            state "default", action:"polling.poll", icon:"st.secondary.refresh"
        }
        
        
        main "temperature"
        details(["temperature", "humidity", "illuminance", "airPressure", "soundLevel", "refresh"])
    }
}

// parse events into attributes
def parse(String description) {
    log.debug "Parsing '${description}'"
    // TODO: handle 'humidity' attribute
    // TODO: handle 'illuminance' attribute
    // TODO: handle 'temperature' attribute
    // TODO: handle 'carbonMonoxide' attribute

}

// handle commands
def poll() {
    // log.debug "Executing 'poll'"
    
    // TODO: handle 'poll' command
    login()
}




def login() {   
    def params = [
        uri: 'https://portal.theubi.com/login.do',
        headers: [
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Encoding': 'sdch',
        'Host': 'portal.theubi.com',
        'DNT': '1',
        'Origin': 'https://portal.theubi.com/login.jsp',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36'
            ],
        body: [j_username: username, j_password: password]
    ]

    data.cookies = ''

    httpPost(params) { response ->
        log.debug "Request was successful, $response.status"
        //log.debug response.headers
        response.getHeaders('Set-Cookie').each {
            String cookie = it.value.split(';')[0]
            //log.debug "Adding cookie to collection: $cookie"
            data.cookies = data.cookies + cookie + ';'
        }
        //log.debug "Cookies: $data.cookies"

    }
       

    def params2 = [
        uri: "https://portal.theubi.com/room/",
        headers: [
          'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
          'Accept-Encoding': 'plain', 
          'Cookie': data.cookies        
          ]
        ]

    httpGet(params2) { response2 ->
        log.debug "Request was successful, $response2.status"
        def doc = response2.data
        def x = new StringWriter()
//        log.debug doc[0].children[1].children[7].text()
        doc[0].children[1].children[7].children[1].writeTo(x)
        def linesAsList = x.toString().minus(" ").split( /\n|\r|\n\r|\r\n/ ).collect { it.replace( "'", '' ) }
//        log.debug linesAsList
        def v = [:]
        
        // Calculate an offset into the list of lines based upon which Ubi we're after
        int ubiOffset = (ubinumber.toInteger() - 1) * 15;
        
// Following was original code - based on Ubi Portal reporting temps in Fahrenheit
//        v.temp = linesAsList[6].minus("document.write(toFahrenheit(").minus("));") //temp in C
//      v.temp = Math.round((Double.parseDouble(v.temp) * 1.8 + 32)*100) /100 // Temp in F

// This is my code - based on Ubi Portal reporting temps in Celsius and wanting to report in Celsius
        v.temp = linesAsList[(ubiOffset + 4)].minus("document.write(").minus(".toFixed(2));") //temp in C
        v.temp = Math.round(Double.parseDouble(v.temp))

        v.humidity = Double.parseDouble(linesAsList[(ubiOffset + 11)].minus("Humidity").minus("%")) // humidity
        v.airPressure = linesAsList[(ubiOffset + 12)].minus("Air Pressure").minus("KPa") //Air Pressure
        //v.airPressure = Double.parseDouble(v.airPressure) / 100// into bar
        v.soundLevel = Double.parseDouble(linesAsList[(ubiOffset + 13)].minus("Sound level").minus("dB")) //Sound Level
        v.lightLevel = Double.parseDouble(linesAsList[(ubiOffset + 14)].minus("Light level").minus("lux")) //Light Level
        log.debug v
        
        sendEvent(name: 'temperature', value: v.temp, unit: "C")
        sendEvent(name: 'humidity', value: v.humidity)
        sendEvent(name: 'illuminance', value: v.lightLevel, unit: "lux")
        sendEvent(name: 'airPressure', value: v.airPressure.toString())
        sendEvent(name: 'soundLevel', value: v.soundLevel)
        
        }
    
}


// SpeechSynthesis.speak
def speak(textToSpeak) {
    log.debug("speak(${textToSpeak})")
    def params = [
            uri: "https://portal.theubi.com",
            path: "/webapi/behaviour",
            query: [
                access_token : speechToken,
                text : textToSpeak
            ]
    ]

    httpGet(params) { response ->
        log.debug "Request return code, $response.status"
    }
}

@chuckles, I must be missing a step, how do I trigger the Ubi to speak the sensor data? Trying to trigger it by using the token url is not working and is causing for force log out of Ubi portal.

@DarcRanger, there might be a little misunderstanding here…

The addition of the SpeechSynthesis capability doesn’t inherently add the ability to “speak the sensor data”.

The SmartThings system has a set of “device capabilities” which is used to abstract individual device types from SmartApps. (Ref: http://strtdtest.readthedocs.org/en/latest/device-type-developers-guide/overview.html ). A “device capability” is a predefined set of interfaces which define the functionality of that capability.

E.g. by having a common “switch” capability, any SmartApp which needs to either be triggered by a switch or needs to turn a switch on or off can simply be written to say it needs a device with a switch capability. It can then be used with a GE switch, a Leviton switch, a virtual switch tile, an Aeotec switch, etc. The SmartApp doesn’t need to know how to handle any of these, it just needs to know how to use a switch. The device type handlers for each of these products know how to talk to them, but present a common “switch capability” to the SmartApps.

If a device type handler (such as sn_Ubi in this case) declares that it supports the “Speech Synthesis” capability and implements the appropriate functions (of which, for this capability, there is only one - Speak() ) then any SmartApp which is written to use a Speech Synthesis device can now be used with one of your Ubis.

So - I’ve provided the ability for an Ubi to used by any SmartApp looking for a Speech Synthesis capability. A SmartApp could now be written to achieve what you’re after (speaking the sensor values). But when the SmartApp is written, it should be written such that it will accept humidity, temperature, sound, light and air pressure measurements from any device or devices capable of providing that information and speak the result through any device capable of speech synthesis.

When the user installs the SmartApp they’ll then be prompted to select which devices they want to use for each of these roles. The SmartThings systems uses the combination of what the SmartApp says it needs and what the device type handlers say they can provide to present the appropriate list of possible devices to the user as they select each device (as you would have seen when configuring most SmartApps).

Does this make sense now?

Once you’ve set up the lesson and configured the token in the Ubi device within SmartThings, you shouldn’t be trying to visit the URL of the lesson. That URL is called by the SmartThings back-end system when a SmartApp wants to use speech synthesis via your Ubi. The token doesn’t just identify the lesson, it is also an authentication mechanism and when you visit the Portal using it you’re most likely resetting the browser cookie the Portal uses to track your authentication (logged in) status - thus you’re getting logged out.

If you want to play with the lesson URL to see how it works, use a second browser (and thus a different set of cookies). Note that I don’t mean a second instance of the same browser, but a completely different browser (e.g. if you’re using IE to log into the Portal, play with the lesson URL in Firefox or Chrome).

In that second browser, if you visit the lesson URL with a query parameter “text” set to the URL encoded form of the text you want spoken, it should issue forth from the Ubi on which you defined the lesson.

e.g.

https://portal.theubi.com/webapi/behaviour?access_token=<put_your_access_token_here>?text=The%20magic%20of%20speach%20from%20the%20Intertubes

@chuckles

I completely for got this is a device and not a smartthings app. Thanks for the clarification.

I used the latest code with the SpeechSynthesis,made the behavior (I have only one Ubi).
The problem at first is on line 153.I change the children[7] to children[5]
and then it goes on line 165:

java.lang.NullPointerException: Cannot invoke method minus() on null object @ line 165

After I comment out it actually continues the same error on different lines (for the other sensors)
lines 168,169,171,172

We use Celsius too here in Greece.Any help with the code?Am I missing sth?

@Pstuart… PM me. We’ll get it fixed. Quick thing to try is connecting to a new power supply. Let me know if that works. We try to get things resolved quickly so I’m disappointed to hear that wasn’t the case for you.

@xneo1 - I suspect this is because you only have the one Ubi - the Ubi Portal page seems to change the page structure if there is only one Ubi vs. multiple Ubis. They also changed the page structure recently when the banner at the top of the page changed. As a quick guess, trying changing to children(6) and see if that works.

If that doesn’t work, there are a couple of lines in the code which are commented out which log the returned data and the parsed result. Un-comment them and look at what is being logged - you should be able to work out the appropriate code from that.