Need help porting LANnouncer DTH

I'm trying to port LANnouncer DTH but I can't get it working and I need help understanding this error message from HE when I try to save the code. I replaced the one instance of "physicalgraph" on line 266.

No signature of method: Script1.carouselTile() is applicable for argument types: (java.util.LinkedHashMap, java.lang.String, java.lang.String, Script1$_run_closure1$_closure6$_closure16) values: [[width:3, height:2], cameraDetails, device.image, Script1$_run_closure1$_closure6$_closure16@164bbe19]

Original ST code here:
http://www.keybounce.com/bin/LANdroid%20Alerter.groovy

What does the error mean? How do I fix it?

Thanks!

Hubitat doesn’t use the tiles or simulator sections, delete all of that as a start.

1 Like

Ah, that was it!

Thanks!

I’m looking for the same thing. Can you share the edited code that works? It will save me from a couple hours of tinkering.

I haven’t fully tested this yet but it seems to be ok so far.


/**
 *  LANnouncer Alerter (Formerly LANdroid - but Google didn't like that much.)
 *
 *  Requires the LANnouncer android app; https://play.google.com/store/apps/details?id=com.keybounce.lannouncer
 *  See http://www.keybounce.com/LANdroidHowTo/LANdroid.html for full downloads and instructions.
 *  SmartThings thread: https://community.smartthings.com/t/android-as-a-speech-alarm-device-released/30282/12
 *  
 * Note: Only Siren and Strobe from the U.I. or Alarm capabilities default to continuous.
 *
 *  Version 1.25 22 July 2016
 * 
 *
 *  Copyright 2015-2016 Tony McNamara
 *
 *  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.
 *
 * To Do: Add string return to Image Capture attribute
 *
 */

metadata {
    definition (name: "LANnouncer Alerter", namespace: "KeyBounce", author: "Tony McNamara") {
        capability "Alarm"
        capability "Speech Synthesis"
        capability "Notification"
        capability "Tone"
        capability "Image Capture"
        attribute  "LANdroidSMS","string"
        /* Per http://docs.smartthings.com/en/latest/device-type-developers-guide/overview.html#actuator-and-sensor */
        capability "Sensor"
        capability "Actuator"

        // Custom Commands
        /** Retrieve image, formatted for SmartThings, from camera by name. */
        command "chime"
        command "doorbell"
        command "ipCamSequence", ["number"]
        command "retrieveAndWait", ["string"]
        command "retrieveFirstAndWait"
        command "retrieveSecondAndWait"
    }
    preferences {
        input("DeviceLocalLan", "string", title:"Android IP Address", description:"Please enter your tablet's I.P. address", defaultValue:"" , required: false, displayDuringSetup: true)
        input("DevicePort", "string", title:"Android Port", description:"Port the Android device listens on", defaultValue:"1035", required: false, displayDuringSetup: true)
        input("ReplyOnEmpty", "bool", title:"Say Nothing", description:"When no speech is found, announce LANdroid?  (Needed for the speech and notify tiles to work)", defaultValue: true, displayDuringSetup: true)
        input("AlarmContinuous", "bool", title:"Continuous Alarm (vs 10 sec.)", description: "When on, the alarm will sound until Stop is issued.", defaultValue: false, displayDuringSetup: true)
    }

}

/** Generally matches TTSServer/app/build.gradle */
String getVersion() {return "24 built July 2016";}


/** Alarm Capability, Off.
 *  Turns off both the strobe and the alarm.
 *  Necessary when in continuous mode. 
 */
def off() {
    log.debug "Executing 'off'"
    sendEvent(name:"alarm", value:"off")
    def command="&ALARM=STOP&FLASH=STOP&"+getDoneString();
    return sendCommands(command)
}

/** Alarm Capability: Strobe
 *  Flashes the camera light, if any.
 */
def strobe() {
    log.debug "Executing 'strobe'"
    // For illustration, switch to siren and sendEvent after.
    def command= (AlarmContinuous?"&FLASH=CONTINUOUS&":"&FLASH=STROBE&")+getDoneString();
    // def command=(AlarmContinuous?"&ALARM=SIREN:CONTINUOUS&":"&ALARM=SIREN&")+getDoneString();
    def hubAction = sendCommands(command)
    sendEvent(name:"alarm", value:"strobe")
    return hubAction;
}

/** Alarm Capability: Siren 
 *  Sounds the siren, either continuous or for a brief period, depending on setting.
 *  If continuous, Stop should be called later.
 */
def siren() {
    log.debug "Executing 'siren'"
    sendEvent(name:"alarm", value:"siren")
    def command=(AlarmContinuous?"&ALARM=SIREN:CONTINUOUS&":"&ALARM=SIREN&")+getDoneString();
    return sendCommands(command);
}

/** Tone Capability: Beep
 *  Sounds a short beep
 */
def beep() {
    log.debug "Executing 'beep'"
    def command="&ALARM=CHIME&"+getDoneString()
    return sendCommands(command);
}

def both() {
    log.debug "Executing 'both'"
    sendEvent(name:"alarm", value:"both")
    def command="&ALARM=ON&FLASH=ON&"+getDoneString()
    if (AlarmContinuous)
    {
        command="&ALARM=SIREN:CONTINUOUS&FLASH=CONTINUOUS&"+getDoneString()
    }
    return sendCommands(command);
}

/** speechSynthesis Capability: Speak
 */
def speak(toSay) {
    log.debug "Executing 'speak'"
    if (!toSay?.trim()) {
        if (ReplyOnEmpty) {
            toSay = "LANnouncer Version ${version}"
        }
    }

    if (toSay?.trim()) {
        def command="&SPEAK="+toSay+"&"+getDoneString()
        return sendCommands(command)
    }
}

/** Notification capability: deviceNotification
 */
def deviceNotification(toToast) {
    log.debug "Executing notification with "+toToast
    if (!toToast?.trim()) {
        if (ReplyOnEmpty) {
            toToast = "LANnouncer Version ${version}";
        }
    }
    if (toToast?.trim()) {
        def command="&TOAST="+toToast+"&"+getDoneString()
        return sendCommands(command)
    }
}    

def chime() {
    log.debug "Executing 'chime'"
    // TODO: handle 'siren' command
    def command="&ALARM=CHIME&"+getDoneString()
    return sendCommands(command)
}

def doorbell() {
    log.debug "Executing 'doorbell'"
    // TODO: handle 'siren' command
    def command="&ALARM=DOORBELL&"+getDoneString()
    return sendCommands(command)
}

def ipCamSequence(cameraNumber) {
    def camera = (cameraNumber==1?"FIRST":"SECOND");
    def command="&RETRIEVESEQ="+cameraNumber+"&"+getDoneString()
    return sendIPCommand(command, true)
}


def retrieveFirstAndWait() {
    retrieveAndWait("FIRST");
}
def retrieveSecondAndWait() {
    retrieveAndWait("SECOND");
}
def retrieveAndWait(cameraName) {
    log.info("Requesting image from camera ${cameraName}");
    def command="&RETRIEVE="+cameraName+"&STSHRINK=TRUE&"+getDoneString()
    return sendIPCommand(command, true)
}


def take() {
    // This won't result in received file. Can't handle large or binaries in hub.
    log.debug "Executing 'take'"
    def command="&PHOTO=BACK&STSHRINK=TRUE&"+getDoneString()
    return sendIPCommand(command, true)
}

/** Send to IP and to SMS as appropriate 
 *  The caller MUST return the value, which is the hubAction.
 *  As of version 1.25, does not "send" the command so much as 
 *  request that the calling service send it.
 */
private sendCommands(command) {
    log.info "Command request: "+command
    sendSMSCommand(command)
    return sendIPCommand(command)
}

/** Prepares the hubAction to be executed.
 *  Pre-V25, this was executed in-line.  
 *  Now it is returned, not executed, and must be returned up the calling chain.
 */
private sendIPCommand(commandString, sendToS3 = false) {
    log.info "Sending command "+ commandString+" to "+DeviceLocalLan+":"+DevicePort
    if (DeviceLocalLan?.trim()) {
        def hosthex = convertIPtoHex(DeviceLocalLan)
        def porthex = convertPortToHex(DevicePort)
        device.deviceNetworkId = "$hosthex:$porthex"

        def headers = [:] 
        headers.put("HOST", "$DeviceLocalLan:$DevicePort")

        def method = "GET"

        def hubAction = new hubitat.device.HubAction(
            method: method,
            path: "/"+commandString,
            headers: headers
            );
        if (sendToS3 == true)
        {
            hubAction.options = [outputMsgToS3:true];
        }
        log.debug hubAction
        return hubAction;
    }
}

private sendSMSCommand(commandString) {
    def preface = "+@TTSSMS@+"
    def smsValue = preface+"&"+commandString
    state.lastsmscommand = smsValue
    sendEvent(name: "LANdroidSMS", value: smsValue, isStateChange: true)
    /*
    if (SMSPhone?.trim()) {
        sendSmsMessage(SMSPhone, preface+"&"+commandString)
    }
    */
}

private String getDoneString() {
    return "@DONE@"
}

def parse(String description) {
    log.debug "Parsing '${description}'"
    def map = parseLanMessage(description);
    log.debug "As LAN: " + map;
    if ((map.headers) && (map.headers.'Content-Type' != null) && (map.headers.'Content-Type'.contains("image/jpeg")) )
    {   //  Store the file
      if(map.body) 
      {
            storeImage(getPictureName(), map.body);
      }
    }
/* 'index:0F, mac:0073E023A13A, ip:C0A80114, port:040B, requestId:f9036fb2-9637-40b8-b2c5-71ba5a09fd3e, bucket:smartthings-device-conn-temp, key:fc8e3dfd-5035-40a2-8adc-a312926f9034' */

    else if (map.bucket && map.key)
    { //    S3 pointer; retrieve image from it to store.
        try {
            def s3ObjectContent; // Needed for scope of try-catch
            def imageBytes = getS3Object(map.bucket, map.key + ".jpg")

            if(imageBytes)
            {
                log.info ("Got image bytes; saving them.")
                s3ObjectContent = imageBytes.getObjectContent()
                def bytes = new ByteArrayInputStream(s3ObjectContent.bytes)
                storeImage(getPictureName(), bytes)
            }
        }
        catch(Exception e) 
        {
            log.error e
        }
        finally {
            //explicitly close the stream
            if (s3ObjectContent) { s3ObjectContent.close() }
        }        
    }
}


// Image Capture handling
/* Note that images are stored in https://graph.api.smartthings.com/api/s3/smartthings-smartsense-camera/[IMAGE-ID4], 
 * where [IMAGE-ID] is listed in the IDE under My Devices > [Camera] > Current States: Image. That page is updated as pictures are taken.
 */


private getPictureName() {
    def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')
    log.debug pictureUuid
    def picName = device.deviceNetworkId.replaceAll(':', '') + "_$pictureUuid" + ".jpg"
    return picName
}


private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02X', it.toInteger() ) }.join()
    log.debug "IP address entered is $ipAddress and the converted hex code is $hex"
    return hex

}

private String convertPortToHex(port) {
    String hexport = port.toString().format( '%04X', port.toInteger() )
    log.debug hexport
    return hexport
}

2 Likes

I tried the code you posted, and got this error
No signature of method: Script1.metadata() is applicable for argument types: (Script1$_run_closure1) values: [Script1$_run_closure1@f73d014] Possible solutions: metaClass(groovy.lang.Closure) on line 28

could you provide some help on fixing the error
Thanks

This should be added to Drivers Code not to Apps Code.

That might be your problem.

That was definitely my problem, not sure what I was thinking…
Thanks again

Driver works good, but it wasn't speaking phrases with spaces in it. I added

toSay = toSay.replaceAll("\s","%20") to the speak function and it works great now:

def speak(toSay) {
    log.debug "Executing 'speak'"
    if (!toSay?.trim()) {
        if (ReplyOnEmpty) {
            toSay = "LANnouncer Version ${version}"
        }
    }
    
	toSay = toSay.replaceAll("\\s","%20")
    
    if (toSay?.trim()) {
        def command="&SPEAK="+toSay+"&"+getDoneString()
        return sendCommands(command)
    }
}
2 Likes

W00T works like a champ !!

A little late, but here to say thanks. I had brute-forced all my messages to use %20 but this is much more elegant.

Hey guys. I just set up the device type for this and I have the app installed, I can get the app to do all of the dings, but I can't get speak to work. It shows up in the app, on my tablet but there is no speaking. Is there something else I need to do?

Is your "media" volume level high enough? Most Android phones don't sync ringer volume with media playback volume when you use the volume button to raise the volume with no sound playing.

Did you confirm that your TTS engine is working properly? Go to settings and test to make sure your TTS engine is functioning on your Android device.

Thanks to @jpark and @fpstassi I have LanNouncer up and running in HE, and am now issuing a few TTS mode announcement messages from @Cobra's Message Central. However, I am unable to get the Chime sound from HE or MC, but Chime sound does work in my SmartThings setup.

Any suggestions appreciated. What I want is a "Door Chimer" then the name of the door in a TTS message. I was planning two messages with MC: the chime; then a delayed "door name/contact is open"

Per the Lannouncer documentation using @|CHIME=ALARM works in a few ST SmartApps, but throws an invalid character on the |, and after fixing that Lannouncer did not receive anything beyond the = sign in HE . So I added two lines of code

toSay = toSay.replaceAll("[|]","%7C") 
toSay = toSay.replaceAll("[=]","%3D") 

Now the Lannouncer device Speak test using text string @|ALARM=CHIME sends:
+@TTSSMS@+&&SPEAK=@%7CALARM%3DCHIME&@DONE@**

Unfortunatly Lannouncer does not chime but now speaks something like "at sign gibberish Alarm equal sign Chime"

Edit: forgot to mention the TTS devices are 2 Fire HD8s and 1 old Android phone

I really want to get my SmartThings System moved over the HE.

So I made a quick mod to Message Central Child routine to produce the sounds when

  1. the message is set as Voice Message (SpeechSynth)
  2. the text is %chime% or %doorbell% case insensitive
    siren was coded but there is no way to stop it

Code for Message Central Child, routine def speechSynthNow(inMsg)

> 	            def soundType=inMsg.toUpperCase()
> //				if (soundType == '%SIREN%')
> //					speaker.siren()
> //				else
> 				if (soundType == '%CHIME%')
> 					speaker.chime()
> 				else	
> 				if (soundType == '%DOORBELL%')
> 					speaker.doorbell()
> 				else
> 					{
> 	            	               state.msg1 = state.fullPhrase
> 	            	               speaker.speak(state.msg1)
> 					state.timer1 = false
> 					startTimer1()
>  					}

Now I can continue on with migrating my Chime "contact sensor name" is open messages!

1 Like

Thanks! I've been using it "as is" but really wanted this functionality as well...

Is there any way to play "doorbell or chime", wait 2-3 seconds, speak message?

Thanks
J

There is no built in delay for various message parts, or message queueing in LanNouncer.

Message Central has a delay parameter I have not yet tried, so my plan is to issue the Chime on the contact open , then issue a second message with 1 second delay with the text.

One very strange thing I recently noticed with Lannouncer is the Sound effect and TTS will play simultaneously. Incoming TTS messages will kill any existing TTS messages that may be speaking.

I have it working, but the chime overlaps the spoken TTS text even with a delay coded on the second TTS message. I don't have time to figure it out, and really don't want to do any additional coding in Message Central Child, so for now it's going live.

The only other downside is the input Contact selection does not allow for multiple devices, so this has to be coded for each door contact. Again, not ideal, but it works.

@arnb
You could actually use two child apps configured for the same trigger and add a 'delay before speaking' of a second or two for the tts message

Thanks for the code for the 'chime'
I will incorporate it into the next version of MC

Andy

2 Likes

I understand your desire to keep your app pure, and trust you will do it correctly in your upcoming version.

My goal was to move my TTS messaging from ST to HE as quickly as possible. I did not take the time needed to totally understand the app's delay logic, and brute forced the commands into the code. For reasons I don't understand the delay does not seem to be working on the TTS message portion. I considerd coding the delay with the speak TTS commands, but did not feel like taking the time to test that in HE and your smartapp. So currently, the tone and beginning of of the TTS occur simultaneously.
Command Delay Example: deviceName.speak(msgout,[delay: 1800]) where 1800 is milli seconds.

The real problem is HE not properly handing the @|ALARM=CHIME as a TTS message embedded command as defined in the LanNouncer specifications. What I did is a workaround for this issue.