Skip to content

Commit d5191be

Browse files
committed
feat(epson/projector): refactor driver
1 parent 1920f42 commit d5191be

File tree

2 files changed

+70
-47
lines changed

2 files changed

+70
-47
lines changed

drivers/epson/projector/esc_vp21.cr

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,39 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
2828
@unmute_volume : Float64 = 60.0
2929

3030
def on_load
31-
transport.tokenizer = Tokenizer.new("\r")
3231
self[:type] = :projector
3332
end
3433

3534
def connected
35+
@ready = false
36+
self[:ready] = false
37+
38+
schedule.in(20.seconds) do
39+
if !@ready
40+
logger.error { "Epson failed to be ready after 20 seconds. Reconnecting..." }
41+
disconnect
42+
end
43+
end
44+
3645
# Have to init comms
37-
send("ESC/VP.net\x10\x03\x00\x00\x00\x00")
46+
send("ESC/VP.net\x10\x03\x00\x00\x00\x00", priority: 99)
3847
schedule.every(52.seconds, true) { do_poll }
3948
end
4049

4150
def disconnected
51+
transport.tokenizer = nil
4252
schedule.clear
4353
end
4454

4555
def power(state : Bool)
4656
if state
4757
@power_target = true
4858
logger.debug { "-- epson Proj, requested to power on" }
49-
do_send(:power, "ON", delay: 40.seconds, name: "power", priority: 99)
59+
do_send(:power, "ON", timeout: 110.seconds, delay: 5.seconds, name: "power", priority: 99)
5060
else
5161
@power_target = false
5262
logger.debug { "-- epson Proj, requested to power off" }
53-
do_send(:power, "OFF", delay: 10.seconds, name: "power", priority: 99)
63+
do_send(:power, "OFF", timeout: 140.seconds, delay: 5.seconds, name: "power", priority: 99)
5464
end
5565
@power_stable = false
5666
self[:power] = state
@@ -64,8 +74,8 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
6474

6575
def switch_to(input : Input)
6676
logger.debug { "-- epson Proj, requested to switch to: #{input}" }
67-
do_send(:input, input.value.to_s(16), name: :input)
6877
mute(false, layer: MuteLayer::Video)
78+
do_send(:input, input.value.to_s(16), name: :input, timeout: 6.seconds, delay: 1.second)
6979

7080
# for a responsive UI
7181
self[:input] = input # for a responsive UI
@@ -74,7 +84,7 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
7484
end
7585

7686
def input?
77-
do_send(:input, priority: 0, wait: false)
87+
do_send(:input, priority: 0)
7888
self[:input]?.try(&.as_s?)
7989
end
8090

@@ -84,7 +94,7 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
8494
percentage = vol / 100.0
8595
vol_actual = (percentage * 255.0).round_away.to_i
8696

87-
@unmute_volume = self[:volume].as_f if (muted = vol == 0.0) && self[:volume]?
97+
@unmute_volume = self[:volume].as_f if (muted = vol.zero?) && self[:volume]?
8898
do_send(:volume, vol_actual, **options, name: :volume)
8999

90100
# for a responsive UI
@@ -94,7 +104,7 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
94104
end
95105

96106
def volume?
97-
do_send(:volume, priority: 0, wait: false)
107+
do_send(:volume, priority: 0)
98108
self[:volume]?.try(&.as_f)
99109
end
100110

@@ -104,21 +114,17 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
104114
layer : MuteLayer = MuteLayer::AudioVideo,
105115
)
106116
case layer
107-
when .audio_video?
108-
do_send(:video_mute, state ? "ON" : "OFF", name: :video_mute, wait: false)
109-
do_send(:av_mute, state ? "ON" : "OFF", name: :mute, wait: false)
110-
# do_send(:av_mute, name: :mute?, priority: 0, retries: 0)
111-
when .video?
112-
do_send(:video_mute, state ? "ON" : "OFF", name: :video_mute, wait: false)
113-
# video_mute?
117+
when .video?, .audio_video?
118+
do_send(:av_mute, state ? "ON" : "OFF", name: :mute)
119+
video_mute?
114120
when .audio?
115121
val = state ? 0.0 : @unmute_volume
116122
volume(val)
117123
end
118124
end
119125

120126
def video_mute?
121-
do_send(:video_mute, priority: 0, wait: false)
127+
do_send(:av_mute, priority: 0)
122128
!!self[:video_mute]?.try(&.as_bool)
123129
end
124130

@@ -144,6 +150,8 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
144150
"14: exhaust shutter error",
145151
"15: obstacle detection error",
146152
"16: IF board discernment error",
153+
"17: Communication error of 'Stack projection function'",
154+
"18: I2C error",
147155
]
148156

149157
def inspect_error
@@ -162,10 +170,24 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
162170
RESPONSE = COMMAND.to_h.invert
163171

164172
def received(data, task)
173+
data = String.new(data)
174+
logger.debug { "<< Received from Epson Proj: #{data.inspect}" }
175+
176+
# cleanup the data
177+
data = data.strip.strip(':').strip
178+
179+
# projector returns ":" on success
165180
return task.try(&.success) if data.size <= 2
166-
# Because we see sometimes see responses like ':::PWR'
167-
data = String.new(data[1..-2]).lstrip(':')
168-
logger.debug { "<< Received from Epson Proj: #{data}" }
181+
182+
if !@ready
183+
if data.includes?("ESC/VP.net")
184+
logger.debug { "-- Epson projector ready to accept commands" }
185+
transport.tokenizer = Tokenizer.new(":")
186+
@ready = true
187+
self[:ready] = true
188+
end
189+
return task.try(&.success)
190+
end
169191

170192
# Handle IMEVENT messages
171193
if data.starts_with?("IMEVENT=")
@@ -207,9 +229,9 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
207229
self[:video_mute] = false unless powered
208230
end
209231
when :av_mute
210-
self[:video_mute] = self[:audio_mute] = data[1] == "ON"
211-
self[:volume] = 0.0
232+
self[:video_mute] = data[1] == "ON"
212233
when :video_mute
234+
# we don't use this command
213235
self[:video_mute] = data[1] == "ON"
214236
when :volume
215237
# convert to a percentage
@@ -235,7 +257,7 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
235257
volume?
236258
video_mute?
237259
end
238-
do_send(:lamp, priority: 20, wait: false)
260+
do_send(:lamp, priority: 20)
239261
end
240262

241263
private def parse_imevent(data : String)
@@ -314,7 +336,7 @@ class Epson::Projector::EscVp21 < PlaceOS::Driver
314336
private def do_send(command, param = nil, **options)
315337
command = COMMAND[command]
316338
cmd = param ? "#{command} #{param}\r" : "#{command}?\r"
317-
logger.debug { ">> Sending to Epson Proj: #{command}: #{cmd}" }
339+
logger.debug { ">> Sending to Epson Proj - #{command}: #{cmd}" }
318340
send(cmd, **options)
319341
end
320342
end

drivers/epson/projector/esc_vp21_spec.cr

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,72 +3,73 @@ require "placeos-driver/spec"
33
DriverSpecs.mock_driver "Epson::Projector::EscVp21" do
44
# connected
55
should_send("ESC/VP.net\x10\x03\x00\x00\x00\x00")
6-
responds(":\r")
6+
responds("ESC/VP.net\r")
7+
status[:ready].should eq(true)
78
# do_poll
89
# power?
910
should_send("PWR?\r")
10-
responds(":PWR=01\r")
11+
responds("PWR=01\r:")
1112
status[:power].should eq(true)
1213
# input?
1314
should_send("SOURCE?\r")
14-
responds(":SOURCE=30\r")
15+
responds("SOURCE=30\r:")
1516
status[:input].should eq("HDMI")
1617
# volume?
17-
responds(":VOL=0\r")
18+
responds("VOL=0\r:")
1819
status[:volume].should eq(0)
1920
# lamp
20-
responds(":LAMP=20\r")
21+
responds("LAMP=20\r:")
2122
status[:lamp_usage].should eq(20)
2223

2324
# IMEVENT test - projector on
24-
transmit(":IMEVENT=0001 03 00000000 00000000 T1 F1\r")
25+
transmit("IMEVENT=0001 03 00000000 00000000 T1 F1\r:")
2526
status[:power].should eq(true)
2627
status[:warming].should eq(false)
2728
status[:cooling].should eq(false)
2829

2930
exec(:mute)
30-
responds(":\r")
31-
responds(":MUTE=ON\r")
31+
should_send("MUTE ON\r")
32+
responds("\r:")
33+
should_send("MUTE?\r")
34+
responds("MUTE=ON\r:")
3235
status[:video_mute].should eq(true)
33-
status[:audio_mute].should eq(true)
34-
status[:volume].should eq(0)
3536

3637
exec(:switch_to, "HDBaseT")
38+
should_send("MUTE OFF\r")
39+
responds("\r:")
3740
should_send("SOURCE 80\r")
38-
responds(":\r")
39-
responds(":SOURCE=80\r")
41+
responds("\r:")
42+
sleep 1.second # delay after switching source
43+
should_send("MUTE?\r")
44+
responds("MUTE=OFF\r:")
45+
should_send("SOURCE?\r")
46+
responds("SOURCE=80\r:")
4047
status[:input].should eq("HDBaseT")
4148
status[:video_mute].should eq(false)
4249

43-
exec(:mute_audio, false)
44-
should_send("VOL 153\r")
45-
responds(":\r")
46-
responds(":VOL=255\r")
47-
status[:volume].should eq(100)
48-
status[:audio_mute].should eq(false)
49-
5050
exec(:volume, 80)
5151
should_send("VOL 204\r")
52-
responds(":\r")
53-
responds(":VOL=204\r")
52+
responds("\r:")
53+
should_send("VOL?\r")
54+
responds("VOL=204\r:")
5455
status[:volume].should eq(80)
5556
status[:audio_mute].should eq(false)
5657

5758
# Additional IMEVENT tests
5859
# Test warming state
59-
transmit(":IMEVENT=0001 02 00000000 00000000 T1 F1\r")
60+
transmit("IMEVENT=0001 02 00000000 00000000 T1 F1\r:")
6061
status[:power].should eq(false)
6162
status[:warming].should eq(true)
6263
status[:cooling].should eq(false)
6364

6465
# Test cooling state
65-
transmit(":IMEVENT=0001 04 00000000 00000000 T1 F1\r")
66+
transmit("IMEVENT=0001 04 00000000 00000000 T1 F1\r:")
6667
status[:power].should eq(false)
6768
status[:warming].should eq(false)
6869
status[:cooling].should eq(true)
6970

7071
# Test with warnings and alarms
71-
transmit(":IMEVENT=0001 03 00000003 00000007 T1 F1\r")
72+
transmit("IMEVENT=0001 03 00000003 00000007 T1 F1\r:")
7273
status[:power].should eq(true)
7374
status[:warnings].should eq(["Lamp life", "No signal"])
7475
status[:alarms].should eq(["Lamp ON failure", "Lamp lid", "Lamp burnout"])

0 commit comments

Comments
 (0)