Skip to content

Commit

Permalink
Merge pull request #53 from splattner/denon-telnet
Browse files Browse the repository at this point in the history
Implement Denon Telnet Interface
  • Loading branch information
splattner authored Oct 31, 2023
2 parents eeec944 + e07d8be commit 868c3a7
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 43 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b h1:VfPXB/wCGGt590QhD1bOpv2J/AmC/RJNTg/Q59HKSB0=
github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
Expand Down
24 changes: 20 additions & 4 deletions pkg/clients/denonavr/denonavrclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewDenonAVRClient(i *integration.Integration) *DenonAVRClient {

client.Messages = make(chan string)

inputSetting := integration.SetupDataSchemaSettings{
inputSetting_ipaddr := integration.SetupDataSchemaSettings{
Id: "ipaddr",
Label: integration.LanguageText{
En: "IP Address of your Denon Receiver",
Expand All @@ -45,6 +45,18 @@ func NewDenonAVRClient(i *integration.Integration) *DenonAVRClient {
},
}

inputSetting_telnet := integration.SetupDataSchemaSettings{
Id: "telnet",
Label: integration.LanguageText{
En: "Use telnet to communicate with your DenonAV",
},
Field: integration.SettingTypeCheckbox{
Checkbox: integration.SettingTypeCheckboxDefinition{
Value: false,
},
},
}

metadata := integration.DriverMetadata{
DriverId: "denonavr",
Developer: integration.Developer{
Expand All @@ -53,13 +65,13 @@ func NewDenonAVRClient(i *integration.Integration) *DenonAVRClient {
Name: integration.LanguageText{
En: "Denon AVR",
},
Version: "0.2.6",
Version: "0.2.7",
SetupDataSchema: integration.SetupDataSchema{
Title: integration.LanguageText{
En: "Configuration",
De: "KOnfiguration",
},
Settings: []integration.SetupDataSchemaSettings{inputSetting},
Settings: []integration.SetupDataSchemaSettings{inputSetting_ipaddr, inputSetting_telnet},
},
Icon: "custom:denon.png",
}
Expand Down Expand Up @@ -133,7 +145,11 @@ func (c *DenonAVRClient) denonHandleSetup(setup_data integration.SetupData) {

func (c *DenonAVRClient) setupDenon() {
if c.IntegrationDriver.SetupData != nil && c.IntegrationDriver.SetupData["ipaddr"] != "" {
c.denon = denonavr.NewDenonAVR(c.IntegrationDriver.SetupData["ipaddr"])
telnetEnabled, err := strconv.ParseBool(c.IntegrationDriver.SetupData["telnet"])
if err != nil {
telnetEnabled = false
}
c.denon = denonavr.NewDenonAVR(c.IntegrationDriver.SetupData["ipaddr"], telnetEnabled)
} else {
log.Error("Cannot setup Denon, missing setupData")
}
Expand Down
21 changes: 20 additions & 1 deletion pkg/denonavr/attributes.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package denonavr

import "reflect"
import (
"fmt"
"reflect"
)

// Set an attribute and call entity Change function if changed
func (d *DenonAVR) SetAttribute(name string, value interface{}) {

d.attributeMutex.Lock()
defer d.attributeMutex.Unlock()

changed := d.attributes[name] == nil || !reflect.DeepEqual(d.attributes[name], value)

d.attributes[name] = value
Expand All @@ -14,3 +20,16 @@ func (d *DenonAVR) SetAttribute(name string, value interface{}) {
}

}

func (d *DenonAVR) GetAttribute(name string) (interface{}, error) {

d.attributeMutex.Lock()
defer d.attributeMutex.Unlock()

if d.attributes[name] == nil {
return nil, fmt.Errorf("Attribute not Found")
}

return d.attributes[name], nil

}
19 changes: 17 additions & 2 deletions pkg/denonavr/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@ import (
log "github.com/sirupsen/logrus"
)

func (d *DenonAVR) sendCommandToDevice(denonCommandType DenonCommand, command string) (int, error) {
func (d *DenonAVR) sendCommandToDevice(cmd DenonCommand, payload string) (int, error) {

if d.telnetEnabled {

err := d.sendTelnetCommand(cmd, payload)
if err != nil {
return 404, err
}

return 200, nil
}

return d.sendHTTPCommand(cmd, payload)
}

func (d *DenonAVR) sendHTTPCommand(denonCommandType DenonCommand, command string) (int, error) {

url := "http://" + d.Host + COMMAND_URL + "?" + url.QueryEscape(string(denonCommandType)+command)
log.WithFields(log.Fields{
Expand All @@ -21,7 +36,7 @@ func (d *DenonAVR) sendCommandToDevice(denonCommandType DenonCommand, command st
return req.StatusCode, fmt.Errorf("error sending command: %w", err)
}

// Trigger a updata data, handled in the Listen Loop
// Trigger a update to get updated data handled in the Listen Loop
d.updateTrigger <- "update"

return req.StatusCode, nil
Expand Down
49 changes: 38 additions & 11 deletions pkg/denonavr/denonavr.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,31 @@ import (
"encoding/xml"
"io"
"sort"
"sync"
"time"

log "github.com/sirupsen/logrus"

"net/http"

"github.com/ziutek/telnet"
)

type DenonCommand string
type DenonZone string

const (
DenonCommandPower DenonCommand = "PW"
DennonCommandZoneMain DenonCommand = "ZM"
DenonCommandVolume DenonCommand = "MV"
DenonCommandMute DenonCommand = "MU"
DenonCommandSelectInput DenonCommand = "SI"
DenonCommandCursorControl DenonCommand = "MN"
DenonCommandNS DenonCommand = "NS"
DenonCommandMS DenonCommand = "MS"
DenonCommandVS DenonCommand = "VS"
DenonCommandPower DenonCommand = "PW"
DennonCommandZoneMain DenonCommand = "ZM"
DenonCommandMainZoneVolume DenonCommand = "MV"
DenonCommandMainZoneMute DenonCommand = "MU"
DenonCommandSelectInput DenonCommand = "SI"
DenonCommandCursorControl DenonCommand = "MN"
DenonCommandNS DenonCommand = "NS"
DenonCommandMS DenonCommand = "MS"
DenonCommandVS DenonCommand = "VS"
DenonCommandZone2 DenonCommand = "Z2"
DenonCommandZone3 DenonCommand = "Z3"
)

const (
Expand Down Expand Up @@ -75,20 +80,29 @@ type ValueLists struct {
type DenonAVR struct {
Host string

telnet *telnet.Conn

mainZoneData DenonXML

// Zone Status
zoneStatus map[DenonZone]DenonZoneStatus
netAudioStatus DenonNetAudioStatus

attributes map[string]interface{}
// Attributes
attributes map[string]interface{}
attributeMutex sync.Mutex

updateTrigger chan string

// Telnet
telnetEnabled bool
telnetEvents chan *TelnetEvent
telnetMutex sync.Mutex

entityChangedFunction map[string][]func(interface{})
}

func NewDenonAVR(host string) *DenonAVR {
func NewDenonAVR(host string, telnetEnabled bool) *DenonAVR {

denonavr := DenonAVR{}

Expand All @@ -103,6 +117,9 @@ func NewDenonAVR(host string) *DenonAVR {
denonavr.attributes = make(map[string]interface{})

denonavr.updateTrigger = make(chan string)
denonavr.telnetEvents = make(chan *TelnetEvent)

denonavr.telnetEnabled = telnetEnabled

return &denonavr
}
Expand Down Expand Up @@ -150,6 +167,16 @@ func (d *DenonAVR) StartListenLoop() {
ticker.Stop()
}()

// Start listening to telnet
if d.telnetEnabled {
go func() {
// just try to reconnect if connection lost
for {
d.listenTelnet()
}
}()
}

// do an intial update to make sure we have up to date values
d.updateAndNotify()

Expand Down
12 changes: 11 additions & 1 deletion pkg/denonavr/power.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package denonavr

import (
log "github.com/sirupsen/logrus"
)

func (d *DenonAVR) TurnOn() error {
if _, err := d.sendCommandToDevice(DenonCommandPower, "ON"); err != nil {
return err
Expand Down Expand Up @@ -30,7 +34,13 @@ func (d *DenonAVR) TogglePower() error {

func (d *DenonAVR) IsOn() bool {

switch d.mainZoneData.ZonePower {
mainzonepower, err := d.GetAttribute("MainZonePower")
if err != nil {
log.WithError(err).Error("MainZonePower attribute not found")
return false
}

switch mainzonepower.(string) {
case "ON":
return true
default:
Expand Down
Loading

0 comments on commit 868c3a7

Please sign in to comment.