package shelly import java.net.http.* class ShellySwitch( val name : String, val address : String, val id : int ) : TableRow( name ) { // All of my Shelly hardware contain a single switch with id 0. constructor( name : String, address : String ) : this( name, address, 0 ) /** * Initially, stateString is "?" which indicates we don't know the state. * It will (hopefully) be updated by updateStateInNewThread() from init. */ val stateString = stringProperty( "State", "?" ) /** * The initial value is guessed to be false, and will (hopefully) be updated soon too. * This BoolProperty can be used as Feature, i.e. you can add it to menus/toolbars. * NOTE. Until updateStateInNewThread() finishes, the value displayed may be wrong. * * Whenever the value changes, we send a message to the switch to turn it on/off. * * NOTE. One redundant POST is sent to the switch if our GUESS of OFF was wrong. */ val state = boolProperty( name, false ) init { contexts.add( Context( Shelly.SHELLY_SWITCH, this ) ) updateStateInNewThread() state.listen( this:>stateChanged ) } meth stateChanged() { Shelly.setAsync( address, id, state.value ) stateString.value = if (state.value) "On" else "" } override meth value( column : int ) = if ( column == 0 ) { stateString } else if (column == 2) { address } else { name } override meth icon(name: String) : ShellySwitch = this.apply { super.icon(name) } override meth iconName( column : int ) = if ( column == 1 ) { iconName } else { null } meth updateStateInNewThread() { newThread( "ShellyUpdateState", this:>getStatus, this:>onUpdateState ) } // The result is a HttpResponse, but a Feather bug prevents us from using a generic type // as a Function's type-argument. :-( meth getStatus() : Object = Shelly.getStatus( address, id ) // response is a HttpResponse, but a Feather bug prevents us from using a generic type // as a Function's type-argument. :-( meth onUpdateState( response : Object ) { try { val resp = response as HttpResponse val body = resp.body() // Parse the json using regex - we only need the "output" value. val pattern = Pattern.compile( "\"output\":(true|false)" ) val matcher = pattern.matcher( body ) if (matcher.find()) { val value = matcher.group(1) state.value = value == "true" stateString.value = if (state.value) "On" else "" } } catch (e : Exception) { printError( "Failed to update Shelly Switch $name" ) } } /** * Exactly the same as `state.value != state.value`. * * NOTE. If we toggle the state BEFORE updateStateInNewThread() has finished, * the toggle will have no effect if the switch is actually ON, because we GUESSED the switch is OFF. */ meth toggle() { state.value = ! state.value } /** * Exactly the same as `state.value = value`. */ meth set( value : bool ) { state.value = value } meth toggleFeature() = Action( name, Shelly::toggleAsync.curry( address ).curry( id ) ) .icon( iconName, coloredIcon ) meth setFeature( value : bool ) = Action( name, Shelly::set.curry( address ).curry( id ).curry( value ) ) .icon( iconName, coloredIcon ) }