[RELEASE] HubConnect - Share Devices across Multiple Hubs (even SmartThings!)

I kinda figured this - just though maybe the bulb driver for smarrthings was overlooked. No biggie..

still leaves me scratching my head over how to make the devices controllable on my watch. -- I do not see any magic in the device handler - it may just be a matter of having all the bits present.

Anyways thanks for your time. I guess I will shelve this until I have time to setup webcore.

@csteele

Can you check out the "HubConnect RM Global Variable Connector" for 2.0RC1. It doesn't look correct. It doesn't seem correct.

HubConnect RM Global Variable Connector

/*

  • Copyright 2019-2020 Steve White, Retail Media Concepts LLC.
  • 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.

*/
def getDriverVersion() {[platform: "Universal", major: 2, minor: 0, build: 0]}

metadata
{
definition(name: "HubConnect RM Global Variable Connector", namespace: "shackrat", author: "Steve White", importUrl: "https://raw.githubusercontent.com/HubitatCommunity/HubConnect/master/Hubitat/drivers/HubConnect-RM-Global-Variable-Connector.groovy")
{
capability "Sensor"
capability "AccelerationSensor"
capability "CarbonDioxideMeasurement"
capability "CarbonMonoxideDetector"
capability "ContactSensor"
capability "IlluminanceMeasurement"
capability "MotionSensor"
capability "PresenceSensor"
capability "RelativeHumidityMeasurement"
capability "SmokeDetector"
capability "TemperatureMeasurement"
capability "WaterSensor"
capability "Refresh"

	attribute "version", "string"
	attribute "variable", "string"

	command "accelerationActive"
	command "accelerationInactive"
	command "arrived"
	command "close"
	command "COClear"
	command "CODetected"
	command "departed"
	command "dry"
	command "motionActive"
	command "motionInactive"
	command "open"
	command "setCarbonDioxide",    [[type: "NUMBER"]]
	command "setIlluminance",      [[type: "NUMBER"]]
	command "setRelativeHumidity", [[type: "NUMBER"]]
	command "setTemperature",      [[type: "NUMBER"]]
	command "setVariable",         [[type: "string"]]
	command "smokeClear"
	command "smokeDetected"
	command "wet"
	command "sync"
}

}

/*
sendEvent for each Device Info page's button

*/
def accelerationActive() { parent.sendDeviceEvent(device.deviceNetworkId, "acceleration", "active") }
def accelerationInactive() { parent.sendDeviceEvent(device.deviceNetworkId, "acceleration", "inactive") }
def arrived() { parent.sendDeviceEvent(device.deviceNetworkId, "presence", "arrived") }
def close() { parent.sendDeviceEvent(device.deviceNetworkId, "close", "close") }
def COClear() { parent.sendDeviceEvent(device.deviceNetworkId, "carbonDioxide", "clear") }
def CODetected() { parent.sendDeviceEvent(device.deviceNetworkId, "carbonDioxide", "detected") }
def departed() { parent.sendDeviceEvent(device.deviceNetworkId, "departed", "departed") }
def dry() { parent.sendDeviceEvent(device.deviceNetworkId, "water", "dry") }
def motionActive() { parent.sendDeviceEvent(device.deviceNetworkId, "motion","active") }
def motionInactive() { parent.sendDeviceEvent(device.deviceNetworkId, "motion", "inactive") }
def open() { parent.sendDeviceEvent(device.deviceNetworkId, "open", "open") }
def setCarbonDioxide(val) { parent.sendDeviceEvent(device.deviceNetworkId, "setCarbonDioxide", [val]) }
def setIlluminance(val) { parent.sendDeviceEvent(device.deviceNetworkId, "setIlluminance", [val]) }
def setRelativeHumidity(val) { parent.sendDeviceEvent(device.deviceNetworkId, "setRelativeHumidity", [val]) }
def setTemperature(val) { parent.sendDeviceEvent(device.deviceNetworkId, "setTemperature", [val]) }
def setVariable(val) { parent.sendDeviceEvent(device.deviceNetworkId, "setVariable", [val]) }
def smokeClear() { parent.sendDeviceEvent(device.deviceNetworkId, "smoke", "clear") }
def smokeDetected() { parent.sendDeviceEvent(device.deviceNetworkId, "smoke", "detected") }
def wet() { parent.sendDeviceEvent(device.deviceNetworkId, "water", "wet") }

/*
installed

Doesn't do much other than call initialize().

*/
def installed()
{
initialize()
}

/*
updated

Doesn't do much other than call initialize().

*/
def updated()
{
initialize()
}

/*
initialize

Doesn't do much other than call refresh().

*/
def initialize()
{
refresh()
}

/*
uninstalled

Reports to the remote that this device is being uninstalled.

*/
def uninstalled()
{
// Report
parent?.sendDeviceEvent(device.deviceNetworkId, "uninstalled")
}

/*
parse

In a virtual world this should never be called.

*/
def parse(String description)
{
log.trace "Msg: Description is $description"
}

/*
refresh

Refreshes the device by requesting an update from the client hub.

*/
def refresh()
{
// The server will update status
parent.sendDeviceEvent(device.deviceNetworkId, "refresh")
}

/*
sync

Synchronizes the device details with the parent.

*/
def sync()
{
// The server will respond with updated status and details
parent.syncDevice(device.deviceNetworkId, "ContactSensor")
sendEvent([name: "version", value: "v${driverVersion.major}.${driverVersion.minor}.${driverVersion.build}"])
}

Thanks, Alan

My connection to my SmartThings has become very inconsistent lately then I noticed this in my logs:

My ST will show online but if I click into the HC app, it shows offline and says I must continue setting up the remote hub but the ST hub is setup correctly in the app. Unfortunately, it's affecting the Ecobee integration on my ST hub and I am not able to control it consistently. The Ecosmart Zigbee remotes that I have paired to ST seem to work ok in HE.

Delete the HubConnect Remote Hub device from your Server hub's Device list and pretend to exchange the key.

The two errors have one commonality: state.hubDeviceDNI and if that is null. then you don't have a parent/child relationship anymore.

That, along with zgm and local control were the main reasons I switched to Hubitat. I couldn’t believe SmartThings didn’t have a way to hold down a button to dim up or down like an actual dimmer switch.

I'm looking into moving my devices that use websocket or/and require polling (and thus, slow down my hub) to a new HE hub and using HubConnect to mirror those devices in my main HE hub.

The devices are:

  1. LG TV ([PORT] LG Smart TV Discovery 2012+)
  2. Tasmota light bulbs with presence sensor ([RELEASE] Tasmota for HE - Auto-detecting Tasmota drivers + Tasmota firmware 7.x/8.x for HE (for use with Tuya, Sonoff and other ESP devices))
  3. Ecobee Suite ([RELEASE] Universal Ecobee Suite, Version 1.8.01)
  4. Logitech Harmony Remote ([Release] Logitech Harmony Hub Driver v0.1.20200301)

All of these devices use custom drivers that don't appear to be available in the "Universal Drivers" folder. Please can you clarify how I can use virtual devices for all these and retain full functionality of the native drivers?

Thanks!

My Ecobee Suite includes a set of custom HubConnect drivers to allow you to mirror devices using HubConnect.

That said, you will find that it is better/more efficient to centralize all your Ecobee Suite-related automations on the same hub that is running Ecobee Suite, and mirroring any non-Ecobee Suite devices from other hubs to the Ecobee Suite hub...

Also, be sure that you are running the latest Ecobee Suite - the more recent updates significantly improve the operational efficiency of the Suite when running on Hubitat.

2 Likes

@spelcheck

Hopefully you can help a poor user. I'm attempting to move the User App "Follow Me" from my primary (original) hub to my secondary (new hub). I've deleted original devices from the Primary hub and the drivers and app code. I then wrote the below "HubConnect zFolllow Me".

HubConnect zFolllow Me

/*

  • Copyright 2019 Steve White
  • 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.

*/
metadata
{
definition(name: "HubConnect zFollow Me", namespace: "shackrat", author: "Alan Eisen")
{
capability "Initialize"
capability "Actuator"
capability "Speech Synthesis"
capability "Music Player"
capability "Notification"

    command "playAnnouncement", 	[[name:"Text*", type:"STRING", description:"Text to play"], 
									 [name:"Volume Level", type:"NUMBER", description: "Volume level (0-100)"], 
									 [name:"Restore Volume Level",type:"NUMBER", description: "Restore volume (0-100)"]]
    command "playAnnouncement", 	[[name:"Text*", type: "STRING", description:"Text to play"], 
									 [name:"Title*", type:"STRING", description: "Title to display on Echo Show devices"], 
									 [name:"Volume Level", type:"NUMBER", description: "Volume level (0-100)"], 
									 [name:"Restore Volume Level",type:"NUMBER", description: "Restore volume (0-100)"]]
    command "playAnnouncementAll",	[[name:"Text*", type:"STRING", description:"Text to play"], 
									 [name:"Title*", type:"STRING", description: "Title to display on Echo Show devices"]]
	command "playTextAndRestore", 	[[name:"Text*", type:"STRING", description:"Text to play"]]
    command "playTrackAndRestore", 	[[name:"Track URI*", type:"STRING", description:"URI/URL of track to play"]]
    command "setVolume", 			[[name:"Volume Level*", type:"NUMBER", description: "Volume level (0-100)"]]
	command "setVolumeSpeakAndRestore", 
									[[name:"Volume Level*", type:"NUMBER", description:"Volume level (0-100)"],
									 [name:"Text*", type:"STRING", description:"Text to speak"],
									 [name:"Restore Volume Level",type:"NUMBER", description: "Restore volume (0-100)"]]										 
    command "setVolumeAndSpeak", 	[[name:"Volume Level*", type:"NUMBER", description:"Volume level (0-100)"], 
									 [name:"Text*", type:"STRING", description:"Text to speak"]]
	command "sendFollowMeSpeaker", 	[[name:"Follow Me Request*", type:"JSON_OBJECT", description:"JSON-encoded command string (see source)"]]
    
    command "sendQueue", ["string", "string", "string"]
    command "updateVersion"

    attribute "whatDidISay", "string"
	attribute "latestMessage", "string"
    attribute "latestMessageDateTime", "string"
	attribute "speakerStatus1", "string"
	attribute "speakerStatus2", "string"
	attribute "speakerStatus3", "string"
	attribute "speakerStatus4", "string"
    
    attribute "queue1", "string"
    attribute "queue2", "string"
    attribute "queue3", "string"
    attribute "queue4", "string"
    attribute "queue5", "string"
    
    attribute "dwDriverInfo", "string"
    command "updateVersion"
}

}

def setVersion(){
appName = "FollowMeDriver"
version = "v2.1.1"
dwInfo = "${appName}:${version}"
sendEvent(name: "dwDriverInfo", value: dwInfo, displayed: true)
}

def updateVersion() {
log.info "In updateVersion"
setVersion()
}

def playAnnouncement(String message, String title, volume=null, restoreVolume=null) {
parent.sendDeviceEvent(device.deviceNetworkId, "playAnnouncement", message, title, volume, restoreVolume)
}

def playAnnouncement(String message, volume=null, restoreVolume=null) {
parent.sendDeviceEvent(device.deviceNetworkId, "playAnnouncement", message, volume, restoreVolume)
}

def playAnnouncementAll(String message, title=null) {
parent.sendDeviceEvent(device.deviceNetworkId, "playAnnouncementAll", message, title)
}

def deviceNotification(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "deviceNotification", message)
}

def playText(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "playText", message)
}

def playTextAndRestore(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "playTextAndRestore", message)
}

def playTrack(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "playTrack", message)
}

def playTrackAndRestore(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "playTrackAndRestore", message)
}

def restoreTrack(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "restoreTrack", message)
}

def resumeTrack(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "resumeTrack", message)
}

def setTrack(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "setTrack", message)
}

def setVolume(volume) {
parent.sendDeviceEvent(device.deviceNetworkId, "setVolume", volume)
}

def setVolumeAndSpeak(volume, message) {
parent.sendDeviceEvent(device.deviceNetworkId, "setVolumeAndSpeak", volume, message)
}

def speak(message) {
parent.sendDeviceEvent(device.deviceNetworkId, "speak", message)
}

def sendFollowMeSpeaker(status) {
parent.sendDeviceEvent(device.deviceNetworkId, "sendFollowMeSpeaker", status)
}

def sendQueue(ps, theMessage, duration) {
}

def installed()
{
initialize()
}

/*
updated
*/
def updated()
{
initialize()
}

/*
initialize
*/
def initialize()
{
refresh()
}

/*
refresh
*/
def refresh()
{
// The server will update status
parent.sendDeviceEvent(device.deviceNetworkId, "refresh")
}

/*
sync
*/
def sync()
{
// The server will respond with updated status and details
parent.syncDevice(device.deviceNetworkId, "omnipurpose")
sendEvent([name: "version", value: "v${driverVersion.major}.${driverVersion.minor}.${driverVersion.build}"])
}

I then created the following custom driver definition on the primary hub:

image

XML

{
"exported": 1,
"author": null,
"name": "HubConnect zFollow Me",
"drivers": {
"FollowMe": {
"driver": "HubConnect zFollow Me",
"selector": "speechSynthesis",
"attr": [
"speechSynthesis",
"latestMessage",
"latestMessageDateTime",
"speakerStatus1",
"speakerStatus2",
"speakerStatus3",
"speakerStatus4",
"queue1",
"queue2",
"queue3",
"queue4",
"queue5",
"dwDriverInfo",
"list1",
"sMap1S",
"sMap2S",
"sMap3S",
"sMap4S",
"speakerMap",
"speechTop",
"speakerMapS",
"count"
]
}
}
}

I then "sent" the "Follow Me" device created with the original driver (see below) from the primary hub to the secondary hub.

Follow Me Driver

/**

  • **************** Follow Me Driver ****************
  • Design Usage:
  • This driver formats Speech data to be displayed on Hubitat's Dashboards and also acts as a proxy speaker to 'Follow Me'.
  • Copyright 2019-2020 Bryan Turcotte (@bptworld)
  • This App is free. If you like and use this app, please be sure to mention it on the Hubitat forums! Thanks.
  • Remember...I am not a programmer, everything I do takes a lot of time and research (then MORE research)!
  • Donations are never necessary but always appreciated. Donations to support development efforts are accepted via:
  • Paypal at: https://paypal.me/bptworld

  • 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.

  • If modifying this project, please keep the above header intact and add your comments/credits below - Thank you! - @BPTWorld
  • App and Driver updates can be found at GitHub - bptworld/Hubitat: Apps for use with Hubitat Elevation

  • Changes:
  • 2.1.8 - 05/30/20 - Fixed the bug with Speaker Map
  • 2.1.7 - 05/29/20 - Renamed attributes to be 'Smartly' friendly
  • 2.1.6 - 05/25/20 - Added bug fix by @djw1191, thanks!
  • 2.1.5 - 05/16/20 - Minor change
  • 2.1.4 - 05/12/20 - All tiles now scroll
  • 2.1.3 - 05/11/20 - Added more code traps
  • 2.1.2 - 04/21/20 - Code cleanup, Added optional Text formatting, modified whatDidISay list code by @alan564923 (thank you!)
  • 2.1.1 - 03/18/20 - Fixed message priority features
  • 2.1.0 - 11/14/19 - Name changed to match Follow Me. Major rework. Changes to work with the updated Follow Me (V2.0.5+)

  • 1.0.0 - 01/27/19 - Initial release
    */

import groovy.json.*

metadata {
definition (name: "Follow Me Driver", namespace: "BPTWorld", author: "Bryan Turcotte", importUrl: "https://raw.githubusercontent.com/bptworld/Hubitat/master/Apps/Follow%20Me/FM-driver.groovy") {
capability "Initialize"
capability "Actuator"
capability "Speech Synthesis"
capability "Music Player"
capability "Notification"

    command "playAnnouncement", 	[[name:"Text*", type:"STRING", description:"Text to play"], 
                                     [name:"Volume Level", type:"NUMBER", description: "Volume level (0-100)"], 
                                     [name:"Restore Volume Level",type:"NUMBER", description: "Restore volume (0-100)"]]
    command "playAnnouncement", 	[[name:"Text*", type: "STRING", description:"Text to play"], 
                                     [name:"Title*", type:"STRING", description: "Title to display on Echo Show devices"], 
                                     [name:"Volume Level", type:"NUMBER", description: "Volume level (0-100)"], 
                                     [name:"Restore Volume Level",type:"NUMBER", description: "Restore volume (0-100)"]]
    command "playAnnouncementAll",	[[name:"Text*", type:"STRING", description:"Text to play"], 
                                     [name:"Title*", type:"STRING", description: "Title to display on Echo Show devices"]]
    command "playTextAndRestore", 	[[name:"Text*", type:"STRING", description:"Text to play"]]
    command "playTrackAndRestore", 	[[name:"Track URI*", type:"STRING", description:"URI/URL of track to play"]]
    command "setVolume", 			[[name:"Volume Level*", type:"NUMBER", description: "Volume level (0-100)"]]
    command "setVolumeSpeakAndRestore", 
        [[name:"Volume Level*", type:"NUMBER", description:"Volume level (0-100)"],
         [name:"Text*", type:"STRING", description:"Text to speak"],
         [name:"Restore Volume Level",type:"NUMBER", description: "Restore volume (0-100)"]]										 
    command "setVolumeAndSpeak", 	[[name:"Volume Level*", type:"NUMBER", description:"Volume level (0-100)"], 
                                     [name:"Text*", type:"STRING", description:"Text to speak"]]
    command "sendFollowMeSpeaker", 	[[name:"Follow Me Request*", type:"JSON_OBJECT", description:"JSON-encoded command string (see source)"]]

    command "sendQueue", ["string", "string", "string"]

    attribute "bpt-whatDidISay", "string"
    attribute "whatDidISayCount", "string"
    attribute "latestMessage", "string"
    attribute "latestMessageDateTime", "string"
    attribute "bpt-speakerStatus1", "string"
    attribute "bpt-speakerStatus2", "string"
    attribute "bpt-speakerStatus3", "string"

    attribute "bpt-queue1", "string"
    attribute "bpt-queue2", "string"
    attribute "bpt-queue3", "string"
    attribute "bpt-queue4", "string"
    attribute "bpt-queue5", "string"
}
preferences() {    	
    section(){
        input("fontSize", "text", title: "Font Size", required: true, defaultValue: "15")
        input("fontFamily", "text", title: "Font Family (optional)<br>ie. Lucida Sans Typewriter", required: false)
        input("hourType", "bool", title: "Time Selection<br>(Off for 24h, On for 12h)", required: false, defaultValue: false)
        input("clearData", "bool", title: "Reset All Data", required: false, defaultValue: false)
        input("logEnable", "bool", title: "Enable logging", required: false, defaultValue: false)
    }
}

}

// Queue's for Home Tracker
def sendQueue(ps, theMessage, duration) {
log.info "Follow Me - NEW Home Tracker - ps: ${ps} - duration: ${duration} - theMessage: ${theMessage}"
}

// -- code by @storageanarchy - Thank you for showing me how to pass the variables!
String composeMessageMap(method, message, priority=null, speakLevel=null, returnLevel=null, title='') {
return JsonOutput.toJson([method: method as String, message: message as String, priority: priority as String, speakLevel: speakLevel, returnLevel: returnLevel, title: title as String])
}

def playAnnouncement(String message, volume=null, restoreVolume=null) {
if(logEnable) log.debug "In playAnnouncement"
speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('playAnnouncement', state.speechReceivedFULL, 'N:X', volume, restoreVolume)
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def playAnnouncement(String message, String title, volume=null, restoreVolume=null) {
if(logEnable) log.debug "In playAnnouncement"
speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('playAnnouncement', state.speechReceivedFULL, 'N:X', volume, restoreVolume, title)
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def playAnnouncementAll(String message, title=null) {
if(logEnable) log.debug "In playAnnouncementAll"
speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('playAnnouncementAll', state.speechReceivedFULL, 'N:X')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def deviceNotification(message) {
if(logEnable) log.debug "In deviceNotification"
speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('deviceNotification', state.speechReceivedFULL, 'X:X')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def playText(message) {
if(logEnable) log.debug "In playText"
speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('playText', state.speechReceivedFULL, 'X:X')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def playTextAndRestore(message) {
if(logEnable) log.debug "In playTextAndRestore"
//state.speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('playTextAndRestore', state.speechReceivedFULL, 'X:X')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def playTrack(message) {
if(logEnable) log.debug "In playTrack"
theMessage = composeMessageMap('playTrack', state.speechReceivedFULL, 'X:X')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def playTrackAndRestore(message) {
if(logEnable) log.debug "In playTrackAndRestore"
//NB - Maybe shouldn't strip the URL encoding, as this is supposed to be a URL
state.speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('playTrackAndRestore', state.speechReceivedFULL, 'X:0')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def restoreTrack(message) {
if(logEnable) log.debug "In restoreTrack"
theMessage = composeMessageMap('restoreTrack', state.speechReceivedFULL, 'X:X')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def resumeTrack(message) {
if(logEnable) log.debug "In resumeTrack"
theMessage = composeMessageMap('resumeTrack', state.speechReceivedFULL, 'X:X')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def setTrack(message) {
if(logEnable) log.debug "In setTrack"
theMessage = composeMessageMap('setTrack', state.speechReceivedFULL, 'X:X')
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def setVolume(volume) {
if(logEnable) log.debug "In setVolume"
theMessage = composeMessageMap('setVolume', '', 'X:X', volume, null, null)
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def setVolumeSpeakAndRestore(volume, message, restoreVolume) {
if(logEnable) log.debug "In setVolumeSpeakAndRestore"
speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('setVolumeSpeakAndRestore', state.speechReceivedFULL, 'N:X', volume, restoreVolume)
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def setVolumeAndSpeak(volume, message) {
if(logEnable) log.debug "In setVolumeAndSpeak"
speechReceivedFULL = message.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('setVolumeAndSpeak', state.speechReceivedFULL, 'N:X', volume)
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
}

def speak(message) {
if(logEnable) log.debug "In speak - message: ${message}"
priorityHandler(message)
// returned priority,lastSpoken
speechReceivedFULL = lastSpoken.replace("%20"," ").replace("%5B","[").replace("%5D","]")
theMessage = composeMessageMap('speak', speechReceivedFULL, priority)
if(logEnable) log.debug "In speak - theMessage: ${theMessage}"
sendEvent(name: "latestMessage", value: theMessage, isStateChange: true)
latestMessageDate()
populateMap(priority,lastSpoken)
}

def priorityHandler(message) {
if(logEnable) log.debug "In priorityHandler - message: ${message}"

if(message.startsWith("[")) {
	tmp = message.substring(0,6)
	if(logEnable) log.debug "n priorityHandler - tmp: ${tmp}"
	def (prior, msgA) = tmp.split(']')
	priority = prior.drop(1)
	msgA = message.substring(5)
	lastSpoken = msgA
} else {
    priority = "X:X"
    lastSpoken = message
}

if(logEnable) log.debug "In priorityHandler - priority: ${priority} - lastSpoken: ${lastSpoken}"
return [priority,lastSpoken]   

}

def populateMap(priority,speech) {
if(logEnable) log.debug "In populateMap - Received new Speech! ${speech}"
speechReceived = speech.take(80)

try {
    def thePriority = priority.split(":")
    priorityValue = thePriority[0]
    priorityVoice = thePriority[1]
    if(logEnable) log.debug "In populateMap - priorityValue: ${priorityValue} - priorityVoice: ${priorityVoice}"
} catch (e) {
    log.warn "Follow Me Driver - Something went wrong with your speech priority formatting. Please check your syntax. ie. [N:1]"
    if(logEnable) log.error "In populateMap - ${e}"
    priorityValue = "X"
    priorityVoice = "X"
}

if((priorityValue.toUpperCase().contains("L")) || (priorityValue.toUpperCase().contains("N")) || (priorityValue.toUpperCase().contains("H"))) {
    if(priorityValue.toUpperCase().contains("L")) { lastSpoken = "<span style='color:yellow'>${speech}</span>" }
    if(priorityValue.toUpperCase().contains("N")) { lastSpoken = "${speech}" }
    if(priorityValue.toUpperCase().contains("H")) { lastSpoken = "<span style='color:red'>${speech}</span>" }
    if(logEnable) log.debug "In populateMap - Contains(L,N,H) - lastSpoken: ${lastSpoken}"
} else {
    lastSpoken = "${speech}"
    if(logEnable) log.debug "In populateMap - Does NOT Contain(L,N,H) - lastSpoken: ${lastSpoken}"
}

if(logEnable) log.debug "In populateMap - lastSpoken: ${lastSpoken}"

try {
    if(state.list1 == null) state.list1 = []

    getDateTime()
    last = "${newdate} - ${lastSpoken}"
    state.list1.add(0,last)  

    if(state.list1) {
        listSize1 = state.list1.size()
    } else {
        listSize1 = 0
    }

    int intNumOfLines = 10
    if (listSize1 > intNumOfLines) state.list1.removeAt(intNumOfLines)
    String result1 = state.list1.join(";")
    def lines1 = result1.split(";")

    if(logEnable) log.debug "In makeList - All - listSize1: ${listSize1} - intNumOfLines: ${intNumOfLines}"

    if(fontFamily) {
        theData1 = "<div style='overflow:auto;height:90%'><table style='text-align:left;font-size:${fontSize}px;font-family:${fontFamily}'><tr><td>"
    } else {
        theData1 = "<div style='overflow:auto;height:90%'><table style='text-align:left;font-size:${fontSize}px'><tr><td>"
    }
    for (i=0; i<intNumOfLines && i<listSize1 && theData1.length() < 927;i++)
    theData1 += "${lines1[i]}<br>"

    theData1 += "</table></div>"
    if(logEnable) log.debug "theData1 - ${theData1.replace("<","!")}"       

    dataCharCount1 = theData1.length()
    if(dataCharCount1 <= 1024) {
        if(logEnable) log.debug "What did I Say Attribute - theData1 - ${dataCharCount1} Characters"
    } else {
        theData1 = "Too many characters to display on Dashboard (${dataCharCount1})"
    }

    sendEvent(name: "bpt-whatDidISay", value: theData1)
    sendEvent(name: "whatDidISayCount", value: dataCharCount1)
} catch(e) {
    log.error "Follow Me Driver - ${e}"  
}

}

def installed(){
log.info "Follow Me Driver has been Installed"
clearSpeechData()
}

def updated() {
log.info "Follow Me Driver has been Updated"
cleanUp()
if(clearData) runIn(2,clearSpeechData)
}

def initialize() {
log.info "In initialize"
}

def getDateTime() {
def date = new Date()
if(hourType == false) newdate=date.format("MM-d HH:mm")
if(hourType == true) newdate=date.format("MM-d hh:mm a")
return newdate
}

def latestMessageDate() {
def date = new Date()
latestMessageDateTime = date
sendEvent(name: "latestMessageDateTime", value: date)
}

def clearDataOff(){
log.info "Follow Me Driver has cleared the data"
device.updateSetting("clearData",[value:"false",type:"bool"])
}

def clearSpeechData(){
if(logEnable) log.debug "Follow Me Driver - clearing the data"
state.list1 = []

sMap1S = "Waiting for Data"
sMap2S = "Waiting for Data"
sMap3S = "Waiting for Data"
sendEvent(name: "bpt-speakerStatus1", value: sMap1S)
sendEvent(name: "bpt-speakerStatus2", value: sMap2S)
sendEvent(name: "bpt-speakerStatus3", value: sMap3S)
speechTop = "Waiting for Data..."
sendEvent(name: "whatDidISay", value: speechTop)
if (clearData) runIn(2,clearDataOff)

}

def sendFollowMeSpeaker(status) {
def (sName, sStatus) = status.split(':')
if(sName == null) sName = "blank"
if(sStatus == null) sStatus = "not found"
if(logEnable) log.debug "In sendFollowMeSpeaker - sName: ${sName} - sStatus: ${sStatus}"
if(state.speakerMap == null) state.speakerMap = [:]
state.speakerMap.put(sName, sStatus)

def tblhead = "<div style='overflow:auto;height:90%'><table width=100% style='line-height:1.00;font-size:${fontSize}px;text-align:left'>"
def line = "" 
def tbl = tblhead
def tileCount = 1
theDevices = state.speakerMap.sort { a, b -> a.key <=> b.key }

theDevices.each { it ->
    status = it.value

    if(status == "true") line = "<tr><td>${it.key}<td style='color:green;font-size:${fontSize}px'>Active"
    if(status == "false") line = "<tr><td>${it.key}<td style='color:red;font-size:${fontSize}px'>Inactive"
    if(status == "speaking") line = "<tr><td>${it.key}<td style='color:blue;font-size:${fontSize}px'>Speaking"

    totalLength = tbl.length() + line.length()
    if(logEnable) log.debug "In sendFollowMeSpeaker - tbl Count: ${tbl.length()} - line Count: ${line.length()} - Total Count: ${totalLength}"
    if (totalLength < 1009) {
        tbl += line
    } else {
        tbl += "</table></div>"
        if(logEnable) log.debug "${tbl}"
        tbl = tblhead + line
        if(tileCount == 1) sendEvent(name: "bpt-speakerStatus1", value: tbl)
        if(tileCount == 2) sendEvent(name: "bpt-speakerStatus2", value: tbl)
        if(tileCount == 3) sendEvent(name: "bpt-speakerStatus3", value: tbl)
        tileCount = tileCount + 1
    }
}

if (tbl != tblhead) {
    tbl += "</table></div>"
    if(logEnable) log.debug "${tbl}"
    if(tileCount == 1) sendEvent(name: "bpt-speakerStatus1", value: tbl)
    if(tileCount == 2) sendEvent(name: "bpt-speakerStatus2", value: tbl)
    if(tileCount == 3) sendEvent(name: "bpt-speakerStatus3", value: tbl)
    tileCount = tileCount + 1
}

for(x=tileCount;x<4;x++) {
    if(tileCount == 1) sendEvent(name: "bpt-speakerStatus1", value: "No Data")
    if(tileCount == 2) sendEvent(name: "bpt-speakerStatus2", value: "No Data")
    if(tileCount == 3) sendEvent(name: "bpt-speakerStatus3", value: "No Data")
}

}

private cleanUp() {
// Cleaning up the driver from previous versions
state.remove("sMap1S")
state.remove("sMap2S")
state.remove("sMap3S")
state.remove("sMap4S")
state.remove("speechTop")
state.remove("speakerMapS")
state.remove("count")

sendEvent(name: "speakerStatus1", value: "No longer used")
sendEvent(name: "speakerStatus2", value: "No longer used")
sendEvent(name: "speakerStatus3", value: "No longer used")
sendEvent(name: "whatDidISay", value: "No longer used")

}

When I reference the "Follow Me" device for a notification, like in this rule:
image

I get this error in the log on the primary:

app:21982020-06-04 08:01:20.198 am errorgroovy.lang.MissingMethodException: No signature of method: java.lang.String.call() is applicable for argument types: (java.lang.String, java.lang.String, java.lang.String) values: [192.168.1.202:358, deviceNotification, The system is in null mode] Possible solutions: wait(), any(), trim(), grep(), collect(), dump() on line 100 (sendDeviceEvent)

app:14822020-06-04 08:01:20.069 am infoAction: Notify Follow Me: 'The system is in %mode% mode'

Your help is appreciated!
Alan

I need some help mirroring hubitat dimmer and switch devices to ST. I have the server instance on hubitat and connecting to a client instance on ST. I installed the universal dimmer and switch DTHs in smartthings. I then selected which switches and dimmers to connect to the remote client instance on ST. But nothing happens in live logging on ST and nothing shows up in ST. Help?

Which Version of HubConnect?? v1.64 [Release] or v2 [Public Beta] ?

1.63

I had removed my v1.6 instances of HubConnect.. but I took a Development hub and soft reset it back to Empty. I then installed HubConnect Server and Server Instance as well as HubConnect Remote Hub driver.

My SmartThing hub was already connected to my Production HubConnect, so I cleared that connection via:

Screen Shot 2020-06-06 at 11.15.33 AM

I then installed the v1.6.4 SmartThings Remote Client. I installed HubConnect on Hubitat and created a new Remote. I copied the Key to SmartThings and it said Connected right away.

I then created a Virtual Switch and a Virtual Dimmer on Hubitat and selected them to be sent to SmartThings. They were created on SmartThings and I was able to turn the switch on and off from either end. Same with the dimmer.

I did this mostly to re-familiarize myself with v1.6.4 and I must admit it did go quite easily. However, I've probably installed HubConnect 50 times over the past year... maybe it's just afraid of me :smiley:

So, what should I take away from this then? I am choosing to send physical dimmers and switch to ST - that should be fine I presume...

I'm saying.. the hints you gave about the problem led me to do... what I wrote above. (Install v1.6.4 to test it out.) Only you know how well that works to match your scenario. If not, and that's what I believe, then you'll have to give me more directions about how to duplicate the problem. I believe you have a problem with the install, but from here, I can't tell what. Since I haven't used v1.6.4 in many months, I reminded myself of the process.

@alan564923 sorry bud, I have no experience with this.

I don't think I'm going to be much help either... I don't know what "Follow Me" is.. or how to use it. Although clearly you have built up something... :slight_smile:

I tried to duplicate what you explain. I installed the HubConnect Driver, on a connected hub I installed the original FollowMe driver. I created a Virtual device to use it and then mirrored it back to the first hub. All that seemed to go well...

app:105 2020-06-07 08:47:06.112 pm info  Received event from ZeeFourth/pseudoFollowMe: [speakerStatus3, No longer used null]
app:105 2020-06-07 08:47:06.013 pm info  Received event from ZeeFourth/pseudoFollowMe: [speakerStatus1, No longer used null]
app:105 2020-06-07 08:47:05.923 pm info  Received event from ZeeFourth/pseudoFollowMe: [whatDidISay, No longer used null]
app:105 2020-06-07 08:47:05.850 pm info  Received event from ZeeFourth/pseudoFollowMe: [speakerStatus2, No longer used null]
app:105 2020-06-07 08:46:37.276 pm trace Received ping from ZeeFourth.
app:105 2020-06-07 08:45:59.900 pm info  Sending refresh command to HubConnect Server.
app:105 2020-06-07 08:45:58.475 pm trace Creating Device HubConnect zFollow Me - pseudoFollowMe... 192.168.7.65:316...

No "error groovy" at any rate :smiley:

But what to do with it is unknown.. like a dog, I chased after a car, caught it and now don't know what to do with it. :smiley:

On the Hub with the "Real Driver" logs show:

dev:316 2020-06-07 08:47:04.725 pm info Follow Me Driver has been Updated
app:193 2020-06-07 08:45:59.293 pm info Skipping event subscriptions...  Using event socket to send events to server.
app:193 2020-06-07 08:45:59.162 pm warn The device [pseudoFollowMe] does not support the command refresh.
app:193 2020-06-07 08:45:59.146 pm info Received command from server: ["pseudoFollowMe": refresh]
app:193 2020-06-07 08:45:57.353 pm info Sending custom devices to remote: FollowMe - [[id:316, label:pseudoFollowMe, attr:[[name:latestMessage, value:null, unit:], [name:latestMessageDateTime, value:null, unit:]]]]

I was really hoping someone that recognized "Follow Me" would have jumped in. @bptworld

In the "real device/driver" info page, I saw a Set Volume button and I know what to do with that !! LOL So I put in 25 and clicked the button.

On the mirrored device, there's a message and the Log shows:

app:105 2020-06-07 08:58:49.538 pm info Received event from ZeeFourth/pseudoFollowMe: [latestMessage, {"method":"setVolume","message":"","priority":"X:X","speakLevel":25,"returnLevel":null,"title":null} null]

Which seems like good news to me.

With out searching for an hour... What is it that your trying to do? The only reason I can see to create a driver for HubConnect is to get the Dashboard tiles to move across hubs?

Edit: The one thing I have done from day one is add the following to the HubConnect SpeechSynthesis driver.

Put this up top:
capability "Notification"

And this down below:

/*
	deviceNotification
    
	send a command.
*/
def deviceNotification(value)
{
	parent.sendDeviceEvent(device.deviceNetworkId, "deviceNotification", [value])
}

I've mentioned it here before but was shot down.

1 Like

Not sure why you would even need to do that. If all your devices are mirrored, Follow Me should run on the HubConenct 'server' with rules aimed at the virtual devices there.

I use FM via HC and it's fine. All my rules and Apps run on the HC 'server' and all devices are on another hub. Follow on my dashes works great!

Brain asks the best question, What are you trying to do? It sounds to me like your making life hard on yourself.

2 Likes

My bad. I saw the "Developer" tag and just assumed. Went back and tagged the right person!

1 Like

@bptword @csteele:

Between the 2 of you I found "my" error (not surprising). As noted in @csteele post, the "Set Volume" worked fine so unlike me, you tested another function and found it worked so that identified the issue as "deviceNotification".

So to @bptworld's post, he state to make sure I had the "deviceNotification" capability and code. Turned out I didn't have the "deviceNotification", but did have the code. So the remote code didn't know what to do.

I also added the "Refresh" capability to get rid of that error.

You assistance was invaluable.

Alan

1 Like