Skip to content

Commit a8a05ba

Browse files
committed
Add a bunch of convenience features
Microsoft Windows only: - Only allow a single instance - Always on top option - Touch/click doesn’t steal focus - Events triggered without having to activate the window fist - Automatically try to reconnect with exponential backoff - Implemented command timeouts per spec - Fixed volume calculations and display at extremes - Disable shortcuts for disabled buttons - Disable volume buttons at extremes - Improved debugging system - Update readme and screenshots
1 parent 6500199 commit a8a05ba

11 files changed

Lines changed: 172 additions & 62 deletions

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ Denon Remote
44
Control [Denon Professional DN-500AV surround preamplifier](https://www.denonpro.com/index.php/products/view/dn-500av)
55
remotely.
66

7-
![Screenshot](screenshot-v0.5.0-main.png)
7+
![Screenshot](screenshot-v0.7.0-main.png)
88

9-
![Settings Screenshot](screenshot-v0.5.0-settings.png)
9+
![Settings Screenshot](screenshot-v0.7.0-settings.png)
1010

1111
Author: Raphael Doursenaud <rdoursenaud+denonremote@gmail.com>
1212

@@ -19,17 +19,22 @@ Fonts used:
1919
- [Unicode Power Symbol](https://unicodepowersymbol.com/) Copyright (c) 2013 Joe Loughry licensed under MIT
2020
- [Free Serif](https://savannah.gnu.org/projects/freefont/) licensed under GPLv3
2121

22+
2223
### Features
2324

25+
2426
#### Target hardware
2527

2628
- [x] Denon Professional DN-500AV (Seems based on the same platform as the Denon AVR-1912 and AVR-2112CI.)
2729
- [ ] More? Contributions welcome!
2830

31+
2932
#### Communication
3033

3134
- [x] Ethernet
32-
- [x] Using [Twisted](https://twistedmatrix.com):
35+
- [x] Using [Twisted](https://twistedmatrix.com)
36+
- [x] connection status detection
37+
- [x] automatically try to reconnect with exponential backoff
3338
- [ ] RS-232? also using Twisted
3439
- [ ] General MIDI input using [Mido](https://mido.readthedocs.io/en/latest/)
3540
- [ ] Define control scheme.
@@ -87,6 +92,7 @@ Fonts used:
8792
- [x] Update the GUI
8893
- [ ] Import EQ settings
8994
- [ ] From [REW](https://www.roomeqwizard.com/) value file
95+
- [ ] Only use negative values! You can’t compensate a destructive room mode by adding energy to it.
9096
- [ ] Full Profiles/presets?
9197

9298
##### GUI
@@ -98,7 +104,11 @@ Fonts used:
98104
- [ ] Left/Right VolPreset +/-
99105
- [ ] PgUp/PgDwn SrcPreset +/-
100106
- [x] Systray/Taskbar support using [pystray](https://pypi.org/project/pystray/)
101-
- [ ] Only one instance should be allowed
107+
- [x] Only one instance is allowed (Microsoft Windows only)
108+
- [X] Option to make window stay always on top (Microsoft Windows only)
109+
- [x] Touch doesn’t activate the window and doesn’t steal focus (Microsoft Windows only)
110+
- [x] Trigger events without having to activate the window first (Microsoft Windows only)
111+
- [ ] Draw it on the first touch enabled display if available instead of the main one
102112

103113
##### Windows executable
104114

@@ -161,7 +171,7 @@ PHP
161171
Python:
162172

163173
- https://github.com/jeroenvds/denonremote (XBMC plugin)
164-
- https://github.com/Tom360V/DenonAvr (Similar objectives?
174+
- https://github.com/Tom360V/DenonAvr (Similar objectives?)
165175
- https://github.com/toebsen/python-denonavr (HTTP RESTful server)
166176
- https://github.com/MrJavaWolf/DenonPhoneController (Landline phone controller)
167177
- https://github.com/troykelly/python-denon-avr-serial-over-ip (Library)

denonremote/denon/communication.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from twisted.internet import task, reactor
66
from twisted.internet.protocol import ClientFactory
77
from twisted.protocols.basic import LineOnlyReceiver
8+
from twisted.protocols.policies import TimeoutMixin
89

910
from denon.dn500av import DN500AVMessage, DN500AVFormat
1011

@@ -15,12 +16,20 @@
1516
# See: https://twistedmatrix.com/documents/15.4.0/api/twisted.internet.serialport.SerialPort.html
1617

1718

18-
class DenonProtocol(LineOnlyReceiver):
19+
class DenonProtocol(LineOnlyReceiver, TimeoutMixin):
1920
# From DN-500 manual (DN-500AVEM_ENG_CD-ROM_v00.pdf) page 91 (97 in PDF form)
2021
MAX_LENGTH = 135
21-
DELAY = 0.04 # in seconds. The documentation requires 200 ms. 40 ms seems safe.
22+
DELAY = 0.04
23+
"""
24+
Delay between messages in seconds.
25+
The documentation requires 200 ms. 40 ms seems safe.
26+
"""
27+
TIMEOUT = 0.2
28+
"""
29+
Requests shall time out if no reply is received in under 200 ms.
30+
"""
2231
delimiter = b'\r'
23-
ongoing_calls = 0 # Delay handling. FIXME: should timeout after 200 ms.
32+
ongoing_calls = 0 # Delay handling.
2433

2534
def connectionMade(self):
2635
logger.debug("Connection made")
@@ -38,14 +47,22 @@ def sendLine(self, line):
3847
# A request is made. We need to delay the next calls
3948
self.ongoing_calls += 1
4049
logger.debug("Ongoing calls for delay: %s", self.ongoing_calls)
41-
delay = 0
50+
delay = 0 # Send now
4251
if self.ongoing_calls > 0:
43-
delay = self.DELAY * (self.ongoing_calls - 1)
52+
delay = self.DELAY * (self.ongoing_calls - 1) # Send after other messages
4453
logger.debug("Will send line: %s in %f seconds", line, delay)
4554
return task.deferLater(reactor, delay=delay,
46-
callable=super().sendLine, line=line)
55+
callable=self.sendLineWithTimeout, line=line)
56+
57+
def sendLineWithTimeout(self, line):
58+
timeout = self.TIMEOUT if self.timeOut is None else self.timeOut + self.TIMEOUT
59+
self.setTimeout(timeout)
60+
del timeout
61+
super().sendLine(line)
4762

4863
def lineReceived(self, line):
64+
self.resetTimeout()
65+
self.setTimeout(None)
4966
if self.ongoing_calls:
5067
# We received a reply
5168
self.ongoing_calls -= 1

denonremote/denon/dn500av.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -83,42 +83,46 @@ def compute_master_volume_label(value, zerodb_ref=MASTER_VOLUME_ZERODB_REF):
8383
"""Convert Master Volume ASCII value to dB"""
8484
# TODO: Handle absolute values
8585
label = '---.-dB'
86+
result = None
8687
if int(value[:2]) < MASTER_VOLUME_MIN or int(value[:2]) > MASTER_VOLUME_MAX:
8788
logger.error("Master volume value %s out of bounds (%s-%s)", value, MASTER_VOLUME_MIN, MASTER_VOLUME_MAX)
8889
# Quirks
89-
if value == '99':
90-
result = "-∞dB"
91-
elif value == '995':
92-
result = "-80.5dB"
9390
elif len(value) == 2:
94-
# General case
95-
result = str(float(value) - zerodb_ref)
91+
if value == '99':
92+
result = "" # Minus inf
93+
else:
94+
# General case
95+
result = str(float(value) - zerodb_ref)
9696
elif len(value) == 3:
9797
# Handle undocumented special case for half dB
9898

99-
# Hardcode values around 0 because of computing sign uncertainty
100-
# FIXME: test '985' which seems invalid
101-
if value == str((zerodb_ref - 1)) + '5':
102-
result = "-0.5"
103-
elif value == str(zerodb_ref) + '5':
104-
result = "0.5"
99+
if value == '995':
100+
result = "-80.5"
105101
else:
106-
value = int(value[:2]) # Drop the third digit
107-
offset = 0
108-
if value < zerodb_ref:
109-
offset = 1
110-
logger.debug("Add offset %i to dB calculation with value %i5", offset, value)
111-
result = str(int(value + offset - zerodb_ref)) + ".5"
102+
103+
# Hardcode values around 0 because of computing sign uncertainty
104+
if value == str((zerodb_ref - 1)) + '5':
105+
result = "-0.5"
106+
elif value == str(zerodb_ref) + '5':
107+
result = "0.5"
108+
else:
109+
value = int(value[:2]) # Drop the third digit
110+
offset = 0
111+
if value < zerodb_ref:
112+
offset = 1
113+
logger.debug("Add offset %i to dB calculation with value %i5", offset, value)
114+
result = str(int(value + offset - zerodb_ref)) + ".5"
112115
else:
113116
raise ValueError
114117

115118
# Format label with fixed width like the actual display:
116119
# [ NEG SIGN or EMPTY ] [ DIGIT er EMPTY ] [ DIGIT ] [ DOT ] [ DIGIT ] [ d ] [ B ]
117-
label = "%s%s%s.%sdB" % (
118-
result[0] if result[0] == '-' else " ",
119-
" " if len(result) <= 3 or result[-4] == '-' else result[-4],
120-
result[-3],
121-
result[-1])
120+
if result:
121+
label = "%s%s%s.%sdB" % (
122+
result[0] if result[0] == '-' else " ",
123+
" " if len(result) <= 3 or result[-4] == '-' else result[-4],
124+
result[-3],
125+
result[-1])
122126

123127
logger.debug(label)
124128
return label

denonremote/denonremote.kv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ FloatLayout:
6060
id: mode_k20
6161
text: "SMPTE/K-20"
6262
group: 'mode_ref'
63+
state: 'down' # Default
6364
on_press: app.mode_changed(self)
6465

6566
ForcedToggleButton
6667
id: mode_ebu
6768
text: "EBU"
68-
state: 'down' # Default
6969
group: 'mode_ref'
7070
on_press: app.mode_changed(self)
7171

@@ -131,7 +131,7 @@ FloatLayout:
131131

132132
TextInput:
133133
id: volume_display
134-
text: "---.-dB"
134+
text: "---.-dB" # TODO: decorrelate display from serial commands (SI mandates a space before the unit)
135135
font_name: 'RobotoMono-Regular'
136136
font_size: 36
137137
halign: 'center'

0 commit comments

Comments
 (0)