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.
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.
@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.
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:
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"
}
}
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
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
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.
I’ve got only one Ubi and I’m trying to use your device type. I’m not getting any data from the portal through to the Mobile App. Might I need to change child numbers?
@chrisb - that would be my best guess, the same as for @xneo1. If one of you get it working could you please post back here so others with only one Ubi can also know the appropriate value.
Hmm… I think I’m having a bigger issue… I’m not 100% sure I’m getting any data from the portal.
I’ve got the Device Type pulled up in the IDE and I’ve added my Ubi as a device. When I “install” it in the simulator I don’t see anything showing up in the logs. I haven’t done much at all with Device Types, so I might be completely off base, but I would expect this line:
log.debug “Request was successful, $response2.status”
To put something in the log after I hit the “poll” button in the tools section, but I’m not seeing anything. If I put something in the Speech Synth area that DOES work,
Apparently IDE logging wasn’t working earlier - it’s meant to be working again now though.
Woot!! Got it working. Thanks for the info and assist @chuckles
@xneo1, I was getting the same error you were and here’s what I did to fix it:
Leave the doc line alone. That should remain:
doc[0].children[1].children[7].children[1].writeTo(x)
Find the first v.temp line that reads like this:
v.temp = linesAsList[(ubiOffset + 4)].minus("document.write(").minus(".toFixed(2));")
This line needs to change. First, change the offset modifier to 6:
v.temp = linesAsList[(ubiOffset + 6)].minus("document.write(").minus(".toFixed(2));")
Next, you may need to change what is “subtracted” from the text. For me, being in the US, I was getting modifiers adding toFarhenheit. So I further changed this line to look like this:
v.temp = linesAsList[(ubiOffset + 6)].minus("document.write(").minus("toFahrenheit(").minus("));") //temp in C
This of course gave me the temp in C, so I had to convert this to F. (On a side note… why can’t the US get it’s head out of the sand and just switch already to the metric scale?!?) Obviously if you’re a meteric country don’t do this step. But my next v.temp line now looks like this:
v.temp = Math.round((Double.parseDouble(v.temp) * 1.8 + 32)) // Temp in F
Finally, I needed to clean up the temp tile at the top. Again, if you’re a “civilized” country when it comes to units of measure, skip this step. For the rest of us: Near the top (around line 50), find this section:
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"]
First, replace the unit from C to F. Then, change the values to:
31, 44, 59, 74, 84, 95, 99 (or where ever you’d like to see color changes based on what temp it is).
Save and publish, and that should do it.