From ad9699723a0486f44c91214507c4ad3d1ebd501b Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 8 Jun 2020 15:18:25 +0100 Subject: [PATCH 01/95] feat(samsung): initial commit --- drivers/samsung/md_series.cr | 104 +++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 drivers/samsung/md_series.cr diff --git a/drivers/samsung/md_series.cr b/drivers/samsung/md_series.cr new file mode 100644 index 00000000000..acddda8c537 --- /dev/null +++ b/drivers/samsung/md_series.cr @@ -0,0 +1,104 @@ +module Samsung; end + +# Documentation: https://drive.google.com/a/room.tools/file/d/135yRevYnI6BbZvRWjV51Ur0yKU5bQ_a-/view?usp=sharing +# Older Documentation: https://aca.im/driver_docs/Samsung/MDC%20Protocol%202015%20v13.7c.pdf + +class Samsung::Displays::MdSeries < PlaceOS::Driver + # Discovery Information + tcp_port 1515 + descriptive_name 'Samsung MD, DM & QM Series LCD' + generic_name :Display + + # Markdown description + description <<-DESC + For DM displays configure the following options: + + 1. Network Standby = ON + 2. Set Auto Standby = OFF + 3. Set Eco Solution, Auto Off = OFF + + Hard Power off displays each night and hard power ON in the morning. + DESC + + default_settings({ + display_id: 0 + }) + + # TODO: figure out how to define indicator \xAA + def init_tokenizer + buffer = Tokenizer.new do |io| + bytes = io.peek # for demonstration purposes + string = io.gets_to_end + + # (data length + header and checksum) + string[2].to_i + 4 + end + end + + def on_load + transport.tokenizer = init_tokenizer + on_update + + self[:volume_min] = 0 + self[:volume_max] = 100 + + # Meta data for inquiring interfaces + self[:type] = :lcd + self[:input_stable] = true + self[:input_target] ||= :hdmi + self[:power_stable] = true + end + + def on_update + @id = setting(:display_id) || 0 + @rs232 = setting(:rs232_control) || false + @blank = setting(:blank) + end + + def connected + do_poll + do_device_config unless self[:hard_off] + + schedule.every(30.seconds) do + logger.debug { "-- polling display" } + do_poll + end + end + + def disconnected + self[:power] = false unless @rs232 + schedule.clear + end + + CMD = Hash(Symbol | Int32, Symbol | Int32) { + :status => 0x00, + :hard_off => 0x11, # Completely powers off + :panel_mute => 0xF9, # Screen blanking / visual mute + :volume => 0x12, + :contrast => 0x24, + :brightness => 0x25, + :sharpness => 0x26, + :colour => 0x27, + :tint => 0x28, + :red_gain => 0x29, + :green_gain => 0x2A, + :blue_gain => 0x2B, + :input => 0x14, + :mode => 0x18, + :size => 0x19, + :pip => 0x3C, # picture in picture + :auto_adjust => 0x3D, + :wall_mode => 0x5C, # Video wall mode + :safety => 0x5D, + :wall_on => 0x84, # Video wall enabled + :wall_user => 0x89, # Video wall user control + :speaker => 0x68, + :net_standby => 0xB5, # Keep NIC active in standby + :eco_solution => 0xE6, # Eco options (auto power off) + :auto_power => 0x33, + :screen_split => 0xB2, # Tri / quad split (larger panels only) + :software_version => 0x0E, + :serial_number => 0x0B + } + CMD.merge!(CMD.invert) +end From 4acdd3c3054e8867e4173e1d7e32587f9b4bc87e Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 8 Jun 2020 15:34:14 +0100 Subject: [PATCH 02/95] fix(samsung): cleanup formatting and move to displays folder --- drivers/samsung/displays/md_series.cr | 189 ++++++++++++++++++++++++++ drivers/samsung/md_series.cr | 104 -------------- 2 files changed, 189 insertions(+), 104 deletions(-) create mode 100644 drivers/samsung/displays/md_series.cr delete mode 100644 drivers/samsung/md_series.cr diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr new file mode 100644 index 00000000000..dd47fd96c1b --- /dev/null +++ b/drivers/samsung/displays/md_series.cr @@ -0,0 +1,189 @@ +module Samsung; end + +# Documentation: https://drive.google.com/a/room.tools/file/d/135yRevYnI6BbZvRWjV51Ur0yKU5bQ_a-/view?usp=sharing +# Older Documentation: https://aca.im/driver_docs/Samsung/MDC%20Protocol%202015%20v13.7c.pdf + +class Samsung::Displays::MdSeries < PlaceOS::Driver + # Discovery Information + tcp_port 1515 + descriptive_name "Samsung MD, DM & QM Series LCD" + generic_name :Display + + # Markdown description + description <<-DESC + For DM displays configure the following 1: + + 1. Network Standby = ON + 2. Set Auto Standby = OFF + 3. Set Eco Solution, Auto Off = OFF + + Hard Power off displays each night and hard power ON in the morning. + DESC + + default_settings({ + display_id: 0, + }) + + # TODO: figure out how to define indicator \xAA + def init_tokenizer + buffer = Tokenizer.new do |io| + bytes = io.peek # for demonstration purposes + string = io.gets_to_end + + # (data length + header and checksum) + string[2].to_i + 4 + end + end + + def on_load + transport.tokenizer = init_tokenizer + on_update + + self[:volume_min] = 0 + self[:volume_max] = 100 + + # Meta data for inquiring interfaces + self[:type] = :lcd + self[:input_stable] = true + self[:input_target] ||= :hdmi + self[:power_stable] = true + end + + def on_update + @id = setting(:display_id) || 0 + @rs232 = setting(:rs232_control) || false + @blank = setting(:blank) + end + + def connected + do_poll + do_device_config unless self[:hard_off] + + schedule.every(30.seconds) do + logger.debug { "-- polling display" } + do_poll + end + end + + def disconnected + self[:power] = false unless @rs232 + schedule.clear + end + + CMD = Hash(Symbol | Int32, Symbol | Int32){ + :status => 0x00, + :hard_off => 0x11, # Completely powers off + :panel_mute => 0xF9, # Screen blanking / visual mute + :volume => 0x12, + :contrast => 0x24, + :brightness => 0x25, + :sharpness => 0x26, + :colour => 0x27, + :tint => 0x28, + :red_gain => 0x29, + :green_gain => 0x2A, + :blue_gain => 0x2B, + :input => 0x14, + :mode => 0x18, + :size => 0x19, + :pip => 0x3C, # picture in picture + :auto_adjust => 0x3D, + :wall_mode => 0x5C, # Video wall mode + :safety => 0x5D, + :wall_on => 0x84, # Video wall enabled + :wall_user => 0x89, # Video wall user control + :speaker => 0x68, + :net_standby => 0xB5, # Keep NIC active in standby + :eco_solution => 0xE6, # Eco options (auto power off) + :auto_power => 0x33, + :screen_split => 0xB2, # Tri / quad split (larger panels only) + :software_version => 0x0E, + :serial_number => 0x0B, + } + CMD.merge!(CMD.invert) + + # As true power off disconnects the server we only want to + # power off the panel. This doesn't work in video walls + # so if a nominal blank input is + def power(power, broadcast = nil) + power = self[:power_target] = is_affirmative?(power) + self[:power_stable] = false + + if power == Off + # Blank the screen before turning off panel if required + # required by some video walls where screens are chained + switch_to(@blank) if @blank && self[:power] + do_send(:panel_mute, 1) + elsif !@rs232 && !self[:connected] + wake(broadcast) + else + # Power on + do_send(:hard_off, 1) + do_send(:panel_mute, 0) + end + end + + def hard_off + do_send(:panel_mute, 0) if self[:power] + do_send(:hard_off, 0) + do_poll + end + + def power?(options = {} of Symbol => String, &block) + options[:emit] = block unless block.nil? + do_send(:panel_mute, "", options) + end + + # Adds mute states compatible with projectors + def mute(state = true) + should_mute = is_affirmative?(state) + power(!should_mute) + end + + def unmute + power(true) + end + + # check software version + def software_version? + do_send (:software_version) + end + + def serial_number? + do_send(:serial_number) + end + + # ability to send custom mdc commands via backoffice + def custom_mdc(command, value = "") + do_send(hex_to_byte(command).bytes[0], hex_to_byte(value).bytes) + end + + def set_timer(enable = true, volume = 0) + # set the time on the display + time_cmd = 0xA7 + time_request = [] of Int32 + t = Time.now + time_request << t.day + hour = t.hour + ampm = if hour > 12 + hour = hour - 12 + 0 # pm + else + 1 # am + end + time_request << hour + time_request << t.min + time_request << t.month + year = t.year.to_s(16).rjust(4, "0") + time_request << year[0..1].to_i(16) + time_request << year[2..-1].to_i(16) + time_request << ampm + + do_send time_cmd, time_request + + state = is_affirmative?(enable) ? "01" : "00" + vol = volume.to_s(16).rjust(2, "0") + # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply + custom_mdc "A4", "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" + end +end diff --git a/drivers/samsung/md_series.cr b/drivers/samsung/md_series.cr deleted file mode 100644 index acddda8c537..00000000000 --- a/drivers/samsung/md_series.cr +++ /dev/null @@ -1,104 +0,0 @@ -module Samsung; end - -# Documentation: https://drive.google.com/a/room.tools/file/d/135yRevYnI6BbZvRWjV51Ur0yKU5bQ_a-/view?usp=sharing -# Older Documentation: https://aca.im/driver_docs/Samsung/MDC%20Protocol%202015%20v13.7c.pdf - -class Samsung::Displays::MdSeries < PlaceOS::Driver - # Discovery Information - tcp_port 1515 - descriptive_name 'Samsung MD, DM & QM Series LCD' - generic_name :Display - - # Markdown description - description <<-DESC - For DM displays configure the following options: - - 1. Network Standby = ON - 2. Set Auto Standby = OFF - 3. Set Eco Solution, Auto Off = OFF - - Hard Power off displays each night and hard power ON in the morning. - DESC - - default_settings({ - display_id: 0 - }) - - # TODO: figure out how to define indicator \xAA - def init_tokenizer - buffer = Tokenizer.new do |io| - bytes = io.peek # for demonstration purposes - string = io.gets_to_end - - # (data length + header and checksum) - string[2].to_i + 4 - end - end - - def on_load - transport.tokenizer = init_tokenizer - on_update - - self[:volume_min] = 0 - self[:volume_max] = 100 - - # Meta data for inquiring interfaces - self[:type] = :lcd - self[:input_stable] = true - self[:input_target] ||= :hdmi - self[:power_stable] = true - end - - def on_update - @id = setting(:display_id) || 0 - @rs232 = setting(:rs232_control) || false - @blank = setting(:blank) - end - - def connected - do_poll - do_device_config unless self[:hard_off] - - schedule.every(30.seconds) do - logger.debug { "-- polling display" } - do_poll - end - end - - def disconnected - self[:power] = false unless @rs232 - schedule.clear - end - - CMD = Hash(Symbol | Int32, Symbol | Int32) { - :status => 0x00, - :hard_off => 0x11, # Completely powers off - :panel_mute => 0xF9, # Screen blanking / visual mute - :volume => 0x12, - :contrast => 0x24, - :brightness => 0x25, - :sharpness => 0x26, - :colour => 0x27, - :tint => 0x28, - :red_gain => 0x29, - :green_gain => 0x2A, - :blue_gain => 0x2B, - :input => 0x14, - :mode => 0x18, - :size => 0x19, - :pip => 0x3C, # picture in picture - :auto_adjust => 0x3D, - :wall_mode => 0x5C, # Video wall mode - :safety => 0x5D, - :wall_on => 0x84, # Video wall enabled - :wall_user => 0x89, # Video wall user control - :speaker => 0x68, - :net_standby => 0xB5, # Keep NIC active in standby - :eco_solution => 0xE6, # Eco options (auto power off) - :auto_power => 0x33, - :screen_split => 0xB2, # Tri / quad split (larger panels only) - :software_version => 0x0E, - :serial_number => 0x0B - } - CMD.merge!(CMD.invert) -end From fecf5b7c261655adb26e60b69ee91f874cf7cee3 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 8 Jun 2020 17:34:35 +0100 Subject: [PATCH 03/95] feat(samsung): port over more methods --- drivers/samsung/displays/md_series.cr | 114 ++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 5 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index dd47fd96c1b..276b2a2f2de 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -70,7 +70,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver schedule.clear end - CMD = Hash(Symbol | Int32, Symbol | Int32){ + CMD = Hash(Symbol | Int32, Symbol | Int32) { :status => 0x00, :hard_off => 0x11, # Completely powers off :panel_mute => 0xF9, # Screen blanking / visual mute @@ -129,9 +129,9 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver do_poll end - def power?(options = {} of Symbol => String, &block) + def power?(**options, &block) options[:emit] = block unless block.nil? - do_send(:panel_mute, "", options) + do_send(:panel_mute, "", **options) end # Adds mute states compatible with projectors @@ -161,7 +161,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def set_timer(enable = true, volume = 0) # set the time on the display time_cmd = 0xA7 - time_request = [] of Int32 + time_request = [] of Int t = Time.now time_request << t.day hour = t.hour @@ -184,6 +184,110 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver state = is_affirmative?(enable) ? "01" : "00" vol = volume.to_s(16).rjust(2, "0") # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply - custom_mdc "A4", "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" + custom_mdc( + "A4", + "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" + ) + end + + + INPUTS = Hash(Symbol | Int32, Symbol | Int32) { + :vga => 0x14, # pc in manual + :dvi => 0x18, + :dvi_video => 0x1F, + :hdmi => 0x21, + :hdmi_pc => 0x22, + :hdmi2 => 0x23, + :hdmi2_pc => 0x24, + :hdmi3 => 0x31, + :hdmi3_pc => 0x32, + :hdmi4 => 0x33, + :hdmi4_pc => 0x34, + :display_port => 0x25, + :dtv => 0x40, + :media => 0x60, + :widi => 0x61, + :magic_info => 0x20, + :whiteboard => 0x64 + } + INPUTS.merge!(INPUTS.invert) + + def switch_to(input, **options) + input = input.to_sym if input.class == String + self[:input_stable] = false + self[:input_target] = input + do_send(:input, INPUTS[input], **options) + end + + SCALE_MODE = Hash(Symbol | Int32, Symbol | Int32) { + :fill => 0x09, + :fit => 0x20 + } + SCALE_MODE.merge!(SCALE_MODE.invert) + + # TODO: check if used anywhere + # Activite the internal compositor. Can either split 3 or 4 ways. + def split(inputs = [:hdmi, :hdmi2, :hdmi3], layout = 0, scale = :fit, **options) + main_source = inputs.shift + + data = [ + 1, # enable + 0, # sound from screen section 1 + layout, # layout mode (1..6) + SCALE_MODE[scale], # scaling for main source + inputs.flat_map do |input| + input = input.to_sym if input.is_a? String + [INPUTS[input], SCALE_MODE[scale]] + end + ].flatten + + switch_to(main_source, options).then do + do_send(:screen_split, data, options) + end + end + + def volume(vol, options = {}) + vol = in_range(vol.to_i, 100) + do_send(:volume, vol, options) + end + + # Emulate mute + def mute_audio(val = true) + if is_affirmative? val + if not self[:audio_mute] + self[:audio_mute] = true + self[:previous_volume] = self[:volume] || 50 + volume 0 + end + else + unmute_audio + end + end + + def unmute_audio + if self[:audio_mute] + self[:audio_mute] = false + volume self[:previous_volume] + end + end + + private def do_send(command, data, **options) + data = data.to_a + + if command.is_a?(Symbol) + options[:name] = command if data.length > 0 # name unless status request + command = CMD[command] + end + + data = [command, @id, data.length] + data # Build request + data << (data.reduce(:+) & 0xFF) # Add checksum + data = [0xAA] + data # Add header + + logger.debug { "Sending to Samsung: #{byte_to_hex(array_to_str(data))}" } + + send(array_to_str(data), options).catch do |reason| + disconnect + thread.reject(reason) + end end end From d0bed9e5de48b4f0664be65e151b0891f5d0293b Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 12 Jun 2020 16:53:30 +0100 Subject: [PATCH 04/95] fix(samsung): update to pass driver test runner --- drivers/samsung/displays/md_series.cr | 385 +++++++++++---------- drivers/samsung/displays/md_series_spec.cr | 2 + 2 files changed, 197 insertions(+), 190 deletions(-) create mode 100644 drivers/samsung/displays/md_series_spec.cr diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 276b2a2f2de..5ef5b7fd26a 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -26,8 +26,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # TODO: figure out how to define indicator \xAA def init_tokenizer - buffer = Tokenizer.new do |io| - bytes = io.peek # for demonstration purposes + @buffer = Tokenizer.new do |io| + # bytes = io.peek # for demonstration purposes string = io.gets_to_end # (data length + header and checksum) @@ -50,9 +50,9 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end def on_update - @id = setting(:display_id) || 0 - @rs232 = setting(:rs232_control) || false - @blank = setting(:blank) + @id = setting(Int32, :display_id) || 0 + @rs232 = setting(Bool, :rs232_control) || false + @blank = setting(Bool, :blank) || false end def connected @@ -102,192 +102,197 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver } CMD.merge!(CMD.invert) - # As true power off disconnects the server we only want to - # power off the panel. This doesn't work in video walls - # so if a nominal blank input is - def power(power, broadcast = nil) - power = self[:power_target] = is_affirmative?(power) - self[:power_stable] = false - - if power == Off - # Blank the screen before turning off panel if required - # required by some video walls where screens are chained - switch_to(@blank) if @blank && self[:power] - do_send(:panel_mute, 1) - elsif !@rs232 && !self[:connected] - wake(broadcast) - else - # Power on - do_send(:hard_off, 1) - do_send(:panel_mute, 0) - end - end - - def hard_off - do_send(:panel_mute, 0) if self[:power] - do_send(:hard_off, 0) - do_poll - end - - def power?(**options, &block) - options[:emit] = block unless block.nil? - do_send(:panel_mute, "", **options) + # # As true power off disconnects the server we only want to + # # power off the panel. This doesn't work in video walls + # # so if a nominal blank input is + # def power(power, broadcast = nil) + # power = self[:power_target] = is_affirmative?(power) + # self[:power_stable] = false + + # if power == Off + # # Blank the screen before turning off panel if required + # # required by some video walls where screens are chained + # switch_to(@blank) if @blank && self[:power] + # do_send(:panel_mute, 1) + # elsif !@rs232 && !self[:connected] + # wake(broadcast) + # else + # # Power on + # do_send(:hard_off, 1) + # do_send(:panel_mute, 0) + # end + # end + + # def hard_off + # do_send(:panel_mute, 0) if self[:power] + # do_send(:hard_off, 0) + # do_poll + # end + + # def power?(**options, &block) + # options[:emit] = block unless block.nil? + # do_send(:panel_mute, "", **options) + # end + + # # Adds mute states compatible with projectors + # def mute(state = true) + # should_mute = is_affirmative?(state) + # power(!should_mute) + # end + + # def unmute + # power(true) + # end + + # # check software version + # def software_version? + # do_send (:software_version) + # end + + # def serial_number? + # do_send(:serial_number) + # end + + # # ability to send custom mdc commands via backoffice + # def custom_mdc(command, value = "") + # do_send(hex_to_byte(command).bytes[0], hex_to_byte(value).bytes) + # end + + # def set_timer(enable = true, volume = 0) + # # set the time on the display + # time_cmd = 0xA7 + # time_request = [] of Int + # t = Time.now + # time_request << t.day + # hour = t.hour + # ampm = if hour > 12 + # hour = hour - 12 + # 0 # pm + # else + # 1 # am + # end + # time_request << hour + # time_request << t.min + # time_request << t.month + # year = t.year.to_s(16).rjust(4, "0") + # time_request << year[0..1].to_i(16) + # time_request << year[2..-1].to_i(16) + # time_request << ampm + + # do_send time_cmd, time_request + + # state = is_affirmative?(enable) ? "01" : "00" + # vol = volume.to_s(16).rjust(2, "0") + # # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply + # custom_mdc( + # "A4", + # "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" + # ) + # end + + # INPUTS = Hash(Symbol | Int32, Symbol | Int32) { + # :vga => 0x14, # pc in manual + # :dvi => 0x18, + # :dvi_video => 0x1F, + # :hdmi => 0x21, + # :hdmi_pc => 0x22, + # :hdmi2 => 0x23, + # :hdmi2_pc => 0x24, + # :hdmi3 => 0x31, + # :hdmi3_pc => 0x32, + # :hdmi4 => 0x33, + # :hdmi4_pc => 0x34, + # :display_port => 0x25, + # :dtv => 0x40, + # :media => 0x60, + # :widi => 0x61, + # :magic_info => 0x20, + # :whiteboard => 0x64 + # } + # INPUTS.merge!(INPUTS.invert) + + # def switch_to(input, **options) + # input = input.to_sym if input.class == String + # self[:input_stable] = false + # self[:input_target] = input + # do_send(:input, INPUTS[input], **options) + # end + + # SCALE_MODE = Hash(Symbol | Int32, Symbol | Int32) { + # :fill => 0x09, + # :fit => 0x20 + # } + # SCALE_MODE.merge!(SCALE_MODE.invert) + + # # TODO: check if used anywhere + # # Activite the internal compositor. Can either split 3 or 4 ways. + # def split(inputs = [:hdmi, :hdmi2, :hdmi3], layout = 0, scale = :fit, **options) + # main_source = inputs.shift + + # data = [ + # 1, # enable + # 0, # sound from screen section 1 + # layout, # layout mode (1..6) + # SCALE_MODE[scale], # scaling for main source + # inputs.flat_map do |input| + # input = input.to_sym if input.is_a? String + # [INPUTS[input], SCALE_MODE[scale]] + # end + # ].flatten + + # switch_to(main_source, options).then do + # do_send(:screen_split, data, options) + # end + # end + + # def volume(vol, **options) + # vol = in_range(vol.to_i, 100) + # do_send(:volume, vol, options) + # end + + # # Emulate mute + # def mute_audio(val = true) + # if is_affirmative? val + # if not self[:audio_mute] + # self[:audio_mute] = true + # self[:previous_volume] = self[:volume] || 50 + # volume 0 + # end + # else + # unmute_audio + # end + # end + + # def unmute_audio + # if self[:audio_mute] + # self[:audio_mute] = false + # volume self[:previous_volume] + # end + # end + + # private def do_send(command, data, **options) + # data = data.to_a + + # if command.is_a?(Symbol) + # options[:name] = command if data.length > 0 # name unless status request + # command = CMD[command] + # end + + # data = [command, @id, data.length] + data # Build request + # data << (data.reduce(:+) & 0xFF) # Add checksum + # data = [0xAA] + data # Add header + + # logger.debug { "Sending to Samsung: #{byte_to_hex(array_to_str(data))}" } + + # send(array_to_str(data), options).catch do |reason| + # disconnect + # thread.reject(reason) + # end + # end + + def do_poll end - # Adds mute states compatible with projectors - def mute(state = true) - should_mute = is_affirmative?(state) - power(!should_mute) - end - - def unmute - power(true) - end - - # check software version - def software_version? - do_send (:software_version) - end - - def serial_number? - do_send(:serial_number) - end - - # ability to send custom mdc commands via backoffice - def custom_mdc(command, value = "") - do_send(hex_to_byte(command).bytes[0], hex_to_byte(value).bytes) - end - - def set_timer(enable = true, volume = 0) - # set the time on the display - time_cmd = 0xA7 - time_request = [] of Int - t = Time.now - time_request << t.day - hour = t.hour - ampm = if hour > 12 - hour = hour - 12 - 0 # pm - else - 1 # am - end - time_request << hour - time_request << t.min - time_request << t.month - year = t.year.to_s(16).rjust(4, "0") - time_request << year[0..1].to_i(16) - time_request << year[2..-1].to_i(16) - time_request << ampm - - do_send time_cmd, time_request - - state = is_affirmative?(enable) ? "01" : "00" - vol = volume.to_s(16).rjust(2, "0") - # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply - custom_mdc( - "A4", - "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" - ) - end - - - INPUTS = Hash(Symbol | Int32, Symbol | Int32) { - :vga => 0x14, # pc in manual - :dvi => 0x18, - :dvi_video => 0x1F, - :hdmi => 0x21, - :hdmi_pc => 0x22, - :hdmi2 => 0x23, - :hdmi2_pc => 0x24, - :hdmi3 => 0x31, - :hdmi3_pc => 0x32, - :hdmi4 => 0x33, - :hdmi4_pc => 0x34, - :display_port => 0x25, - :dtv => 0x40, - :media => 0x60, - :widi => 0x61, - :magic_info => 0x20, - :whiteboard => 0x64 - } - INPUTS.merge!(INPUTS.invert) - - def switch_to(input, **options) - input = input.to_sym if input.class == String - self[:input_stable] = false - self[:input_target] = input - do_send(:input, INPUTS[input], **options) - end - - SCALE_MODE = Hash(Symbol | Int32, Symbol | Int32) { - :fill => 0x09, - :fit => 0x20 - } - SCALE_MODE.merge!(SCALE_MODE.invert) - - # TODO: check if used anywhere - # Activite the internal compositor. Can either split 3 or 4 ways. - def split(inputs = [:hdmi, :hdmi2, :hdmi3], layout = 0, scale = :fit, **options) - main_source = inputs.shift - - data = [ - 1, # enable - 0, # sound from screen section 1 - layout, # layout mode (1..6) - SCALE_MODE[scale], # scaling for main source - inputs.flat_map do |input| - input = input.to_sym if input.is_a? String - [INPUTS[input], SCALE_MODE[scale]] - end - ].flatten - - switch_to(main_source, options).then do - do_send(:screen_split, data, options) - end - end - - def volume(vol, options = {}) - vol = in_range(vol.to_i, 100) - do_send(:volume, vol, options) - end - - # Emulate mute - def mute_audio(val = true) - if is_affirmative? val - if not self[:audio_mute] - self[:audio_mute] = true - self[:previous_volume] = self[:volume] || 50 - volume 0 - end - else - unmute_audio - end - end - - def unmute_audio - if self[:audio_mute] - self[:audio_mute] = false - volume self[:previous_volume] - end - end - - private def do_send(command, data, **options) - data = data.to_a - - if command.is_a?(Symbol) - options[:name] = command if data.length > 0 # name unless status request - command = CMD[command] - end - - data = [command, @id, data.length] + data # Build request - data << (data.reduce(:+) & 0xFF) # Add checksum - data = [0xAA] + data # Add header - - logger.debug { "Sending to Samsung: #{byte_to_hex(array_to_str(data))}" } - - send(array_to_str(data), options).catch do |reason| - disconnect - thread.reject(reason) - end + def do_device_config end end diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr new file mode 100644 index 00000000000..e6fca9f2ee6 --- /dev/null +++ b/drivers/samsung/displays/md_series_spec.cr @@ -0,0 +1,2 @@ +DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do +end \ No newline at end of file From 5574e838ce4e9699db72b975154b21a9da4aa26c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 15 Jun 2020 13:34:27 +0100 Subject: [PATCH 05/95] feat(samsung): port over power function --- drivers/samsung/displays/md_series.cr | 107 +++++++++++++------------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 5ef5b7fd26a..793f2978b14 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -24,6 +24,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver display_id: 0, }) + @blank : String = "" + # TODO: figure out how to define indicator \xAA def init_tokenizer @buffer = Tokenizer.new do |io| @@ -52,7 +54,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def on_update @id = setting(Int32, :display_id) || 0 @rs232 = setting(Bool, :rs232_control) || false - @blank = setting(Bool, :blank) || false + @blank = setting(String, :blank) end def connected @@ -102,26 +104,28 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver } CMD.merge!(CMD.invert) - # # As true power off disconnects the server we only want to - # # power off the panel. This doesn't work in video walls - # # so if a nominal blank input is + # As true power off disconnects the server we only want to + # power off the panel. This doesn't work in video walls + # so if a nominal blank input is + # TODO: find out if broadcast is needed # def power(power, broadcast = nil) - # power = self[:power_target] = is_affirmative?(power) - # self[:power_stable] = false - - # if power == Off - # # Blank the screen before turning off panel if required - # # required by some video walls where screens are chained - # switch_to(@blank) if @blank && self[:power] - # do_send(:panel_mute, 1) - # elsif !@rs232 && !self[:connected] - # wake(broadcast) - # else - # # Power on - # do_send(:hard_off, 1) - # do_send(:panel_mute, 0) - # end - # end + def power(power : Bool) + self[:power_target] = power + self[:power_stable] = false + + if !power + # Blank the screen before turning off panel if required + # required by some video walls where screens are chained + switch_to(@blank) if @blank && self[:power] + do_send(:panel_mute, 1) + # elsif !@rs232 && !self[:connected] + # wake(broadcast) + else + # Power on + do_send(:hard_off, 1) + do_send(:panel_mute, 0) + end + end # def hard_off # do_send(:panel_mute, 0) if self[:power] @@ -190,39 +194,38 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # ) # end - # INPUTS = Hash(Symbol | Int32, Symbol | Int32) { - # :vga => 0x14, # pc in manual - # :dvi => 0x18, - # :dvi_video => 0x1F, - # :hdmi => 0x21, - # :hdmi_pc => 0x22, - # :hdmi2 => 0x23, - # :hdmi2_pc => 0x24, - # :hdmi3 => 0x31, - # :hdmi3_pc => 0x32, - # :hdmi4 => 0x33, - # :hdmi4_pc => 0x34, - # :display_port => 0x25, - # :dtv => 0x40, - # :media => 0x60, - # :widi => 0x61, - # :magic_info => 0x20, - # :whiteboard => 0x64 - # } - # INPUTS.merge!(INPUTS.invert) - - # def switch_to(input, **options) - # input = input.to_sym if input.class == String - # self[:input_stable] = false - # self[:input_target] = input - # do_send(:input, INPUTS[input], **options) - # end + INPUTS = Hash(String, Int32) { + "vga" => 0x14, # pc in manual + "dvi" => 0x18, + "dvi_video" => 0x1F, + "hdmi" => 0x21, + "hdmi_pc" => 0x22, + "hdmi2" => 0x23, + "hdmi2_pc" => 0x24, + "hdmi3" => 0x31, + "hdmi3_pc" => 0x32, + "hdmi4" => 0x33, + "hdmi4_pc" => 0x34, + "display_port" => 0x25, + "dtv" => 0x40, + "media" => 0x60, + "widi" => 0x61, + "magic_info" => 0x20, + "whiteboard" => 0x64 + } + #INPUTS.merge!(INPUTS.invert) - # SCALE_MODE = Hash(Symbol | Int32, Symbol | Int32) { - # :fill => 0x09, - # :fit => 0x20 - # } - # SCALE_MODE.merge!(SCALE_MODE.invert) + def switch_to(input : String, **options) + self[:input_stable] = false + self[:input_target] = input + do_send(:input, INPUTS[input], **options) + end + + SCALE_MODE = Hash(Symbol | Int32, Symbol | Int32) { + :fill => 0x09, + :fit => 0x20 + } + SCALE_MODE.merge!(SCALE_MODE.invert) # # TODO: check if used anywhere # # Activite the internal compositor. Can either split 3 or 4 ways. From 68fb7a0482612bdf8e7c444fb03b6363ca62e978 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 15 Jun 2020 14:32:35 +0100 Subject: [PATCH 06/95] feat(samsung): port over volume methods --- drivers/samsung/displays/md_series.cr | 58 ++++++++++++++++----------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 793f2978b14..74420e7bba5 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -248,32 +248,42 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # end # end - # def volume(vol, **options) - # vol = in_range(vol.to_i, 100) - # do_send(:volume, vol, options) - # end + def in_range(val : Int32, max : Int32) : Int32 + min = 0 + if val < min + val = min + elsif val > max + val = max + end + val + end - # # Emulate mute - # def mute_audio(val = true) - # if is_affirmative? val - # if not self[:audio_mute] - # self[:audio_mute] = true - # self[:previous_volume] = self[:volume] || 50 - # volume 0 - # end - # else - # unmute_audio - # end - # end + def volume(vol : Int32, **options) + vol = in_range(vol, 100) + do_send(:volume, vol, **options) + end - # def unmute_audio - # if self[:audio_mute] - # self[:audio_mute] = false - # volume self[:previous_volume] - # end - # end + # Emulate mute + def mute_audio(val : Bool = true) + if val + if !self[:audio_mute] + self[:audio_mute] = true + self[:previous_volume] = self[:volume] || 50 + volume(0) + end + else + unmute_audio + end + end + + def unmute_audio + if self[:audio_mute] + self[:audio_mute] = false + self[:volume] = self[:previous_volume] + end + end - # private def do_send(command, data, **options) + private def do_send(command : Symbol, data : Int32, **options) # data = data.to_a # if command.is_a?(Symbol) @@ -291,7 +301,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # disconnect # thread.reject(reason) # end - # end + end def do_poll end From 27fd9a440b71d130e0a17923e8970f8029302849 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 15 Jun 2020 15:35:47 +0100 Subject: [PATCH 07/95] fix(samsung): force compiler to process type for previous_volume as Int32 --- drivers/samsung/displays/md_series.cr | 49 ++++++++++++++++----------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 74420e7bba5..113ef929cfb 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -221,13 +221,13 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver do_send(:input, INPUTS[input], **options) end - SCALE_MODE = Hash(Symbol | Int32, Symbol | Int32) { - :fill => 0x09, - :fit => 0x20 - } - SCALE_MODE.merge!(SCALE_MODE.invert) - # # TODO: check if used anywhere + # SCALE_MODE = Hash(Symbol | Int32, Symbol | Int32) { + # :fill => 0x09, + # :fit => 0x20 + # } + # SCALE_MODE.merge!(SCALE_MODE.invert) + # # Activite the internal compositor. Can either split 3 or 4 ways. # def split(inputs = [:hdmi, :hdmi2, :hdmi3], layout = 0, scale = :fit, **options) # main_source = inputs.shift @@ -279,21 +279,35 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def unmute_audio if self[:audio_mute] self[:audio_mute] = false - self[:volume] = self[:previous_volume] + # TODO: find out if there is a better way to to do this + vol = 50 if !self[:previous_volume].is_a?(Int32) + volume(vol.as(Int32)) end end - private def do_send(command : Symbol, data : Int32, **options) - # data = data.to_a + SPEAKER_MODES = { + :internal => 0, + :external => 1 + } - # if command.is_a?(Symbol) - # options[:name] = command if data.length > 0 # name unless status request - # command = CMD[command] - # end + def speaker_select(mode : Symbol, **options) + # TODO: figure out why this doesn't work + #do_send(:speaker, SPEAKER_MODES[mode], **options) + end + + def do_poll + do_send(:status, [] of Int32, priority: 0) + end + + private def do_send(command : Symbol, data : Int32 | Array, **options) + data = [data] if data.is_a?(Int32) + + # options[:name] = command if data.length > 0 # name unless status request + # command = CMD[command] - # data = [command, @id, data.length] + data # Build request - # data << (data.reduce(:+) & 0xFF) # Add checksum - # data = [0xAA] + data # Add header + # data = [command, @id, data.length] + data # Build request + # data << (data.reduce(:+) & 0xFF) # Add checksum + # data = [0xAA] + data # Add header # logger.debug { "Sending to Samsung: #{byte_to_hex(array_to_str(data))}" } @@ -303,9 +317,6 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # end end - def do_poll - end - def do_device_config end end From 47c7f4fbfdec4476c5f89e71b63b70c0114ebc6d Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 15 Jun 2020 15:45:03 +0100 Subject: [PATCH 08/95] feat(samsung): port over more methods --- drivers/samsung/displays/md_series.cr | 46 ++++++++++++++------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 113ef929cfb..4010a7ae3f4 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -127,35 +127,35 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end end - # def hard_off - # do_send(:panel_mute, 0) if self[:power] - # do_send(:hard_off, 0) - # do_poll - # end + def hard_off + do_send(:panel_mute, 0) if self[:power] + do_send(:hard_off, 0) + do_poll + end + # TODO: found out how to write this in crystal # def power?(**options, &block) # options[:emit] = block unless block.nil? # do_send(:panel_mute, "", **options) # end - # # Adds mute states compatible with projectors - # def mute(state = true) - # should_mute = is_affirmative?(state) - # power(!should_mute) - # end + # Adds mute states compatible with projectors + def mute(state : Bool = true) + power(!state) + end - # def unmute - # power(true) - # end + def unmute + power(true) + end - # # check software version - # def software_version? - # do_send (:software_version) - # end + # check software version + def software_version? + do_send(:software_version) + end - # def serial_number? - # do_send(:serial_number) - # end + def serial_number? + do_send(:serial_number) + end # # ability to send custom mdc commands via backoffice # def custom_mdc(command, value = "") @@ -292,14 +292,16 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def speaker_select(mode : Symbol, **options) # TODO: figure out why this doesn't work - #do_send(:speaker, SPEAKER_MODES[mode], **options) + # do_send(:speaker, SPEAKER_MODES[mode], **options) end def do_poll do_send(:status, [] of Int32, priority: 0) + # TODO: uncomment when power? is ported + # power? unless self[:hard_off] end - private def do_send(command : Symbol, data : Int32 | Array, **options) + private def do_send(command : Symbol, data : Int32 | Array = [] of Int32, **options) data = [data] if data.is_a?(Int32) # options[:name] = command if data.length > 0 # name unless status request From 57908c1bd05752497362b8d1c01fac31f54e570a Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 15 Jun 2020 16:21:31 +0100 Subject: [PATCH 09/95] feat(samsung): port over more methods --- drivers/samsung/displays/md_series.cr | 77 +++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 4010a7ae3f4..47e0b24f3fd 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -301,6 +301,80 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # power? unless self[:hard_off] end + # Enable power on (without WOL) + def network_standby(enable : Bool, **options) + state = enable ? 1 : 0 + do_send(:net_standby, state, **options) + end + + # Eco auto power off timer + def auto_off_timer(enable : Bool, **options) + state = enable ? 1 : 0 + do_send(:eco_solution, [0x81, state], **options) + end + + # Device auto power control (presumably signal based?) + def auto_power(enable : Bool, **options) + state = enable ? 1 : 0 + do_send(:auto_power, state, **options) + end + + # TODO: port to Crystal + # # Colour control + # [ + # :contrast, + # :brightness, + # :sharpness, + # :colour, + # :tint, + # :red_gain, + # :green_gain, + # :blue_gain + # ].each do |command| + # define_method command do |val, **options| + # val = in_range(val.to_i, 100) + # do_send(command, val, options) + # end + # end + + DEVICE_SETTINGS = [ + :network_standby, + :auto_off_timer, + :auto_power, + :contrast, + :brightness, + :sharpness, + :colour, + :tint, + :red_gain, + :green_gain, + :blue_gain +] + + def do_device_config + logger.debug { "Syncronising device state with settings" } + DEVICE_SETTINGS.each do |name| + value = setting(Int32, name) + # TODO: find out if these are equivalent + # __send__(name, value) unless value.nil? + do_send(name, value) unless value.nil? + end + end + + # TODO: check type for broadcast is correct + def wake(broadcast : String | Nil = nil) + mac = setting(String, :mac_address) + if mac + # config is the database model representing this device + wake_device(mac, broadcast) + info = "Wake on Lan for MAC #{mac}" + info += " directed to VLAN #{broadcast.as(String)}" if broadcast + logger.debug { info } + else + logger.debug { "No MAC address provided" } + end + end + private def do_send(command : Symbol, data : Int32 | Array = [] of Int32, **options) data = [data] if data.is_a?(Int32) @@ -318,7 +392,4 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # thread.reject(reason) # end end - - def do_device_config - end end From cbb88e144cfdca50c12a1ec85f3dfeaae2a3d8ed Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 11:30:17 +0100 Subject: [PATCH 10/95] fix(samsung): use enum to store constant values for commands --- drivers/samsung/displays/md_series.cr | 180 +++++++++++++------------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 47e0b24f3fd..e05231d4b30 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -72,37 +72,36 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver schedule.clear end - CMD = Hash(Symbol | Int32, Symbol | Int32) { - :status => 0x00, - :hard_off => 0x11, # Completely powers off - :panel_mute => 0xF9, # Screen blanking / visual mute - :volume => 0x12, - :contrast => 0x24, - :brightness => 0x25, - :sharpness => 0x26, - :colour => 0x27, - :tint => 0x28, - :red_gain => 0x29, - :green_gain => 0x2A, - :blue_gain => 0x2B, - :input => 0x14, - :mode => 0x18, - :size => 0x19, - :pip => 0x3C, # picture in picture - :auto_adjust => 0x3D, - :wall_mode => 0x5C, # Video wall mode - :safety => 0x5D, - :wall_on => 0x84, # Video wall enabled - :wall_user => 0x89, # Video wall user control - :speaker => 0x68, - :net_standby => 0xB5, # Keep NIC active in standby - :eco_solution => 0xE6, # Eco options (auto power off) - :auto_power => 0x33, - :screen_split => 0xB2, # Tri / quad split (larger panels only) - :software_version => 0x0E, - :serial_number => 0x0B, - } - CMD.merge!(CMD.invert) + enum COMMANDS + Status = 0x00 + Hard_off = 0x11 # Completely powers off + Panel_mute = 0xF9 # Screen blanking / visual mute + Volume = 0x12 + Contrast = 0x24 + Brightness = 0x25 + Sharpness = 0x26 + Colour = 0x27 + Tint = 0x28 + Red_gain = 0x29 + Green_gain = 0x2A + Blue_gain = 0x2B + Input = 0x14 + Mode = 0x18 + Size = 0x19 + Pip = 0x3C # picture in picture + Auto_adjust = 0x3D + Wall_mode = 0x5C # Video wall mode + Safety = 0x5D + Wall_on = 0x84 # Video wall enabled + Wall_user = 0x89 # Video wall user control + Speaker = 0x68 + Net_standby = 0xB5 # Keep NIC active in standby + Eco_solution = 0xE6 # Eco options (auto power off) + Auto_power = 0x33 + Screen_split = 0xB2 # Tri / quad split (larger panels only) + Software_version = 0x0E + Serial_number = 0x0B + end # As true power off disconnects the server we only want to # power off the panel. This doesn't work in video walls @@ -117,26 +116,26 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # Blank the screen before turning off panel if required # required by some video walls where screens are chained switch_to(@blank) if @blank && self[:power] - do_send(:panel_mute, 1) + do_send("panel_mute", 1) # elsif !@rs232 && !self[:connected] # wake(broadcast) else # Power on - do_send(:hard_off, 1) - do_send(:panel_mute, 0) + do_send("hard_off", 1) + do_send("panel_mute", 0) end end def hard_off - do_send(:panel_mute, 0) if self[:power] - do_send(:hard_off, 0) + do_send("panel_mute", 0) if self[:power] + do_send("hard_off", 0) do_poll end # TODO: found out how to write this in crystal # def power?(**options, &block) # options[:emit] = block unless block.nil? - # do_send(:panel_mute, "", **options) + # do_send("panel_mute", [], **options) # end # Adds mute states compatible with projectors @@ -150,11 +149,11 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # check software version def software_version? - do_send(:software_version) + do_send("software_version") end def serial_number? - do_send(:serial_number) + do_send("serial_number") end # # ability to send custom mdc commands via backoffice @@ -194,39 +193,37 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # ) # end - INPUTS = Hash(String, Int32) { - "vga" => 0x14, # pc in manual - "dvi" => 0x18, - "dvi_video" => 0x1F, - "hdmi" => 0x21, - "hdmi_pc" => 0x22, - "hdmi2" => 0x23, - "hdmi2_pc" => 0x24, - "hdmi3" => 0x31, - "hdmi3_pc" => 0x32, - "hdmi4" => 0x33, - "hdmi4_pc" => 0x34, - "display_port" => 0x25, - "dtv" => 0x40, - "media" => 0x60, - "widi" => 0x61, - "magic_info" => 0x20, - "whiteboard" => 0x64 - } - #INPUTS.merge!(INPUTS.invert) + enum INPUTS + Vga = 0x14 # pc in manual + Dvi = 0x18 + Dvi_video = 0x1F + Hdmi = 0x21 + Hdmi_pc = 0x22 + Hdmi2 = 0x23 + Hdmi2_pc = 0x24 + Hdmi3 = 0x31 + Hdmi3_pc = 0x32 + Hdmi4 = 0x33 + Hdmi4_pc = 0x34 + Display_port = 0x25 + Dtv = 0x40 + Media = 0x60 + Widi = 0x61 + Magic_info = 0x20 + Whiteboard = 0x64 + end def switch_to(input : String, **options) self[:input_stable] = false self[:input_target] = input - do_send(:input, INPUTS[input], **options) + do_send("input", INPUTS.parse(input).value, **options) end # # TODO: check if used anywhere - # SCALE_MODE = Hash(Symbol | Int32, Symbol | Int32) { - # :fill => 0x09, - # :fit => 0x20 - # } - # SCALE_MODE.merge!(SCALE_MODE.invert) + # enum SCALE_MODE + # fill = 0x09 + # fit = 0x20 + # end # # Activite the internal compositor. Can either split 3 or 4 ways. # def split(inputs = [:hdmi, :hdmi2, :hdmi3], layout = 0, scale = :fit, **options) @@ -244,7 +241,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # ].flatten # switch_to(main_source, options).then do - # do_send(:screen_split, data, options) + # do_send("screen_split", data, options) # end # end @@ -260,7 +257,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def volume(vol : Int32, **options) vol = in_range(vol, 100) - do_send(:volume, vol, **options) + do_send("volume", vol, **options) end # Emulate mute @@ -285,18 +282,18 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end end - SPEAKER_MODES = { - :internal => 0, - :external => 1 - } + enum SPEAKERMODES + Internal = 0 + External = 1 + end def speaker_select(mode : Symbol, **options) # TODO: figure out why this doesn't work - # do_send(:speaker, SPEAKER_MODES[mode], **options) + # do_send("speaker", SPEAKERMODES[mode], **options) end def do_poll - do_send(:status, [] of Int32, priority: 0) + do_send("status", [] of Int32, priority: 0) # TODO: uncomment when power? is ported # power? unless self[:hard_off] end @@ -304,19 +301,19 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # Enable power on (without WOL) def network_standby(enable : Bool, **options) state = enable ? 1 : 0 - do_send(:net_standby, state, **options) + do_send("net_standby", state, **options) end # Eco auto power off timer def auto_off_timer(enable : Bool, **options) state = enable ? 1 : 0 - do_send(:eco_solution, [0x81, state], **options) + do_send("eco_solution", [0x81, state], **options) end # Device auto power control (presumably signal based?) def auto_power(enable : Bool, **options) state = enable ? 1 : 0 - do_send(:auto_power, state, **options) + do_send("auto_power", state, **options) end # TODO: port to Crystal @@ -338,17 +335,17 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # end DEVICE_SETTINGS = [ - :network_standby, - :auto_off_timer, - :auto_power, - :contrast, - :brightness, - :sharpness, - :colour, - :tint, - :red_gain, - :green_gain, - :blue_gain + "network_standby", + "auto_off_timer", + "auto_power", + "contrast", + "brightness", + "sharpness", + "colour", + "tint", + "red_gain", + "green_gain", + "blue_gain", ] def do_device_config @@ -375,11 +372,16 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end end - private def do_send(command : Symbol, data : Int32 | Array = [] of Int32, **options) + enum RESPONSESTATUS + Ack = 0x41 + Nak = 0x4e + end + + private def do_send(command : String, data : Int32 | Array = [] of Int32, **options) data = [data] if data.is_a?(Int32) # options[:name] = command if data.length > 0 # name unless status request - # command = CMD[command] + command = COMMANDS.parse(command) # data = [command, @id, data.length] + data # Build request # data << (data.reduce(:+) & 0xFF) # Add checksum From dfaaf81c67f719a1507ea1aeb8cedea66e5cc961 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 12:24:46 +0100 Subject: [PATCH 11/95] fix(samsung): cleanup types --- drivers/samsung/displays/md_series.cr | 37 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index e05231d4b30..e4759ab3d44 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -52,8 +52,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end def on_update - @id = setting(Int32, :display_id) || 0 - @rs232 = setting(Bool, :rs232_control) || false + @id = setting(Int32?, :display_id) || 0 + @rs232 = setting(Bool?, :rs232_control) || false @blank = setting(String, :blank) end @@ -106,9 +106,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # As true power off disconnects the server we only want to # power off the panel. This doesn't work in video walls # so if a nominal blank input is - # TODO: find out if broadcast is needed - # def power(power, broadcast = nil) - def power(power : Bool) + def power(power : Bool, broadcast : String? = nil) self[:power_target] = power self[:power_stable] = false @@ -117,8 +115,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # required by some video walls where screens are chained switch_to(@blank) if @blank && self[:power] do_send("panel_mute", 1) - # elsif !@rs232 && !self[:connected] - # wake(broadcast) + elsif !@rs232 && !self[:connected] + wake(broadcast) else # Power on do_send("hard_off", 1) @@ -265,7 +263,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver if val if !self[:audio_mute] self[:audio_mute] = true - self[:previous_volume] = self[:volume] || 50 + self[:previous_volume] = self[:volume].as_i? || 50 volume(0) end else @@ -275,10 +273,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def unmute_audio if self[:audio_mute] - self[:audio_mute] = false - # TODO: find out if there is a better way to to do this - vol = 50 if !self[:previous_volume].is_a?(Int32) - volume(vol.as(Int32)) + self[:audio_mute] = false + volume(self[:previous_volume].as_i? || 50) end end @@ -359,13 +355,13 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end # TODO: check type for broadcast is correct - def wake(broadcast : String | Nil = nil) + def wake(broadcast : String? = nil) mac = setting(String, :mac_address) if mac # config is the database model representing this device wake_device(mac, broadcast) info = "Wake on Lan for MAC #{mac}" - info += " directed to VLAN #{broadcast.as(String)}" if broadcast + info += " directed to VLAN #{broadcast}" if broadcast logger.debug { info } else logger.debug { "No MAC address provided" } @@ -377,6 +373,19 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver Nak = 0x4e end + def received(data, task) + logger.debug { "Samsung sent: #{data}" } + end + + def check_power_state + return if self[:power_stable] + if self[:power] == self[:power_target] + self[:power_stable] = true + else + power(self[:power_target].as_bool) + end + end + private def do_send(command : String, data : Int32 | Array = [] of Int32, **options) data = [data] if data.is_a?(Int32) From 62cebc0535148775b0b3246a2c4be8589c0601c5 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 12:50:33 +0100 Subject: [PATCH 12/95] chore(samsung): cleanup todos --- drivers/samsung/displays/md_series.cr | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index e4759ab3d44..80fff77c3e7 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -106,6 +106,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # As true power off disconnects the server we only want to # power off the panel. This doesn't work in video walls # so if a nominal blank input is + # TODO: check type for broadcast is correct def power(power : Bool, broadcast : String? = nil) self[:power_target] = power self[:power_stable] = false @@ -130,11 +131,12 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver do_poll end - # TODO: found out how to write this in crystal + # TODO: figure out what block is for # def power?(**options, &block) - # options[:emit] = block unless block.nil? - # do_send("panel_mute", [], **options) - # end + def power?(**options) + # options[:emit] = block unless block.nil? + do_send("panel_mute", [] of Int32, **options) + end # Adds mute states compatible with projectors def mute(state : Bool = true) @@ -283,15 +285,13 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver External = 1 end - def speaker_select(mode : Symbol, **options) - # TODO: figure out why this doesn't work - # do_send("speaker", SPEAKERMODES[mode], **options) + def speaker_select(mode : String, **options) + do_send("speaker", SPEAKERMODES.parse(mode).value, **options) end def do_poll do_send("status", [] of Int32, priority: 0) - # TODO: uncomment when power? is ported - # power? unless self[:hard_off] + power? unless self[:hard_off] end # Enable power on (without WOL) @@ -312,7 +312,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver do_send("auto_power", state, **options) end - # TODO: port to Crystal + # TODO: figure out this does and port to Crystal # # Colour control # [ # :contrast, From 02334a0ab34d5ef5148984f4d8467facc09c39ed Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 13:08:02 +0100 Subject: [PATCH 13/95] fix(samsung): initialise @blank as nil --- drivers/samsung/displays/md_series.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 80fff77c3e7..1fc0364cb70 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -24,7 +24,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver display_id: 0, }) - @blank : String = "" + @blank : String? = nil # TODO: figure out how to define indicator \xAA def init_tokenizer @@ -54,7 +54,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def on_update @id = setting(Int32?, :display_id) || 0 @rs232 = setting(Bool?, :rs232_control) || false - @blank = setting(String, :blank) + @blank = setting(String?, :blank) end def connected @@ -114,7 +114,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver if !power # Blank the screen before turning off panel if required # required by some video walls where screens are chained - switch_to(@blank) if @blank && self[:power] + switch_to(@blank.as(String)) if @blank && self[:power] do_send("panel_mute", 1) elsif !@rs232 && !self[:connected] wake(broadcast) From 494ff4c99b81a49f25f8401f81a617b15d90a263 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 17:32:38 +0100 Subject: [PATCH 14/95] fix(samsung): add spec tests --- drivers/samsung/displays/md_series.cr | 29 +++++++++++++++------- drivers/samsung/displays/md_series_spec.cr | 3 +++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 1fc0364cb70..4f60eab9462 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -24,6 +24,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver display_id: 0, }) + @id : Int32 = 0 + @rs232 : Bool = false @blank : String? = nil # TODO: figure out how to define indicator \xAA @@ -52,8 +54,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end def on_update - @id = setting(Int32?, :display_id) || 0 - @rs232 = setting(Bool?, :rs232_control) || false + @id = setting(Int32, :display_id) + @rs232 = setting(Bool, :rs232_control) @blank = setting(String?, :blank) end @@ -386,21 +388,30 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end end - private def do_send(command : String, data : Int32 | Array = [] of Int32, **options) + private def do_send(command : String, data : Int32 | Array(Int32) = [] of Int32, **options) data = [data] if data.is_a?(Int32) - # options[:name] = command if data.length > 0 # name unless status request - command = COMMANDS.parse(command) + # # options[:name] = command if data.length > 0 # name unless status request + command = COMMANDS.parse(command).value - # data = [command, @id, data.length] + data # Build request - # data << (data.reduce(:+) & 0xFF) # Add checksum - # data = [0xAA] + data # Add header + data = [command, @id, data.size] + data # Build request + # TODO: write equivalent in crystal + # # data << (data.reduce(:+) & 0xFF) # Add checksum + data = [0xAA] + data # Add header - # logger.debug { "Sending to Samsung: #{byte_to_hex(array_to_str(data))}" } + data = byte_to_hex(data) + logger.debug { "Sending to Samsung: #{data}}" } + data = data.hexbytes + logger.debug { "hexbytes = #{data}" } + send(data, **options) # send(array_to_str(data), options).catch do |reason| # disconnect # thread.reject(reason) # end end + + def byte_to_hex(bytes : Array(Int32)) : String + bytes.map { |n| "%02X" % (n & 0xFF) }.join + end end diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr index e6fca9f2ee6..3dca6cd2b94 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/md_series_spec.cr @@ -1,2 +1,5 @@ DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do + exec(:volume, 24) + # header + command + id + size + value + should_send("\xAA\x12\x00\x01\x18") end \ No newline at end of file From dd178cc8df954089dde5eaa73e3c9f9265310a94 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 20:53:51 +0100 Subject: [PATCH 15/95] fix(samsung): use safe getter to get self values --- drivers/samsung/displays/md_series.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 4f60eab9462..73c4012b2f9 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -61,7 +61,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def connected do_poll - do_device_config unless self[:hard_off] + do_device_config unless self[:hard_off]? schedule.every(30.seconds) do logger.debug { "-- polling display" } @@ -314,7 +314,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver do_send("auto_power", state, **options) end - # TODO: figure out this does and port to Crystal + # TODO: figure out what this does and port to Crystal # # Colour control # [ # :contrast, From b0cda526e008a2f86e503b22b15e41ebfa238e9e Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 21:08:21 +0100 Subject: [PATCH 16/95] fix(samsung): use type safe getters when trying to access self values everywhere --- drivers/samsung/displays/md_series.cr | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 73c4012b2f9..3ad3dfa4453 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -116,9 +116,9 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver if !power # Blank the screen before turning off panel if required # required by some video walls where screens are chained - switch_to(@blank.as(String)) if @blank && self[:power] + switch_to(@blank.as(String)) if @blank && self[:power]? do_send("panel_mute", 1) - elsif !@rs232 && !self[:connected] + elsif !@rs232 && !self[:connected]? wake(broadcast) else # Power on @@ -128,7 +128,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end def hard_off - do_send("panel_mute", 0) if self[:power] + do_send("panel_mute", 0) if self[:power]? do_send("hard_off", 0) do_poll end @@ -265,7 +265,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # Emulate mute def mute_audio(val : Bool = true) if val - if !self[:audio_mute] + if !self[:audio_mute]? self[:audio_mute] = true self[:previous_volume] = self[:volume].as_i? || 50 volume(0) @@ -276,7 +276,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end def unmute_audio - if self[:audio_mute] + if self[:audio_mute]? self[:audio_mute] = false volume(self[:previous_volume].as_i? || 50) end @@ -293,7 +293,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def do_poll do_send("status", [] of Int32, priority: 0) - power? unless self[:hard_off] + power? unless self[:hard_off]? end # Enable power on (without WOL) @@ -380,8 +380,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end def check_power_state - return if self[:power_stable] - if self[:power] == self[:power_target] + return if self[:power_stable]? + if self[:power]? == self[:power_target]? self[:power_stable] = true else power(self[:power_target].as_bool) From 9dff2a50ec5ef45f76cfcd3ffa51d0c99628632b Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 21:26:30 +0100 Subject: [PATCH 17/95] fix(samsung): fix all warnings when running with spec --- drivers/samsung/displays/md_series_spec.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr index 3dca6cd2b94..dfaa5f4d019 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/md_series_spec.cr @@ -2,4 +2,6 @@ DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do exec(:volume, 24) # header + command + id + size + value should_send("\xAA\x12\x00\x01\x18") + #responds("\xAA\xFF\x00\x03\x41\x12Volume") + #status[:volume].should eq(24) end \ No newline at end of file From eb1492500a9e6498acc912a6fc17a757777a6e7b Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 16 Jun 2020 21:27:23 +0100 Subject: [PATCH 18/95] fix(samsung): commit correct file --- drivers/samsung/displays/md_series.cr | 52 ++++++++++++++------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 3ad3dfa4453..f0890a1e01f 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -22,11 +22,13 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver default_settings({ display_id: 0, + rs232_control: false, + blank: nil, }) @id : Int32 = 0 @rs232 : Bool = false - @blank : String? = nil + @blank : String? # TODO: figure out how to define indicator \xAA def init_tokenizer @@ -61,7 +63,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def connected do_poll - do_device_config unless self[:hard_off]? + # do_device_config unless self[:hard_off]? schedule.every(30.seconds) do logger.debug { "-- polling display" } @@ -332,29 +334,29 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # end # end - DEVICE_SETTINGS = [ - "network_standby", - "auto_off_timer", - "auto_power", - "contrast", - "brightness", - "sharpness", - "colour", - "tint", - "red_gain", - "green_gain", - "blue_gain", -] - - def do_device_config - logger.debug { "Syncronising device state with settings" } - DEVICE_SETTINGS.each do |name| - value = setting(Int32, name) - # TODO: find out if these are equivalent - # __send__(name, value) unless value.nil? - do_send(name, value) unless value.nil? - end - end +# DEVICE_SETTINGS = [ +# :network_standby, +# :auto_off_timer, +# :auto_power, +# :contrast, +# :brightness, +# :sharpness, +# :colour, +# :tint, +# :red_gain, +# :green_gain, +# :blue_gain, +# ] + +# def do_device_config +# logger.debug { "Syncronising device state with settings" } +# DEVICE_SETTINGS.each do |name| +# value = setting(Int32?, name) +# # TODO: find out if these are equivalent +# # __send__(name, value) unless value.nil? +# do_send(name.to_s, value.as(Int32)) unless value.nil? +# end +# end # TODO: check type for broadcast is correct def wake(broadcast : String? = nil) From a342c93726e141683e8869e37110a37795e38ff4 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 17 Jun 2020 11:48:52 +0100 Subject: [PATCH 19/95] fix(samsung): add check for sending of data from connected method in spec --- drivers/samsung/displays/md_series.cr | 67 +++++++++++----------- drivers/samsung/displays/md_series_spec.cr | 18 ++++-- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index f0890a1e01f..1e5710679f4 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -37,7 +37,9 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver string = io.gets_to_end # (data length + header and checksum) - string[2].to_i + 4 + # original ruby code with support for indicator and variable length message + # string[2].to_i + 4 + string[3].to_i + 4 # move data length index + 1 as response will include indicator end end @@ -76,37 +78,6 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver schedule.clear end - enum COMMANDS - Status = 0x00 - Hard_off = 0x11 # Completely powers off - Panel_mute = 0xF9 # Screen blanking / visual mute - Volume = 0x12 - Contrast = 0x24 - Brightness = 0x25 - Sharpness = 0x26 - Colour = 0x27 - Tint = 0x28 - Red_gain = 0x29 - Green_gain = 0x2A - Blue_gain = 0x2B - Input = 0x14 - Mode = 0x18 - Size = 0x19 - Pip = 0x3C # picture in picture - Auto_adjust = 0x3D - Wall_mode = 0x5C # Video wall mode - Safety = 0x5D - Wall_on = 0x84 # Video wall enabled - Wall_user = 0x89 # Video wall user control - Speaker = 0x68 - Net_standby = 0xB5 # Keep NIC active in standby - Eco_solution = 0xE6 # Eco options (auto power off) - Auto_power = 0x33 - Screen_split = 0xB2 # Tri / quad split (larger panels only) - Software_version = 0x0E - Serial_number = 0x0B - end - # As true power off disconnects the server we only want to # power off the panel. This doesn't work in video walls # so if a nominal blank input is @@ -379,6 +350,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def received(data, task) logger.debug { "Samsung sent: #{data}" } + task.try &.success end def check_power_state @@ -390,6 +362,37 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end end + enum COMMANDS + Status = 0x00 + Hard_off = 0x11 # Completely powers off + Panel_mute = 0xF9 # Screen blanking / visual mute + Volume = 0x12 + Contrast = 0x24 + Brightness = 0x25 + Sharpness = 0x26 + Colour = 0x27 + Tint = 0x28 + Red_gain = 0x29 + Green_gain = 0x2A + Blue_gain = 0x2B + Input = 0x14 + Mode = 0x18 + Size = 0x19 + Pip = 0x3C # picture in picture + Auto_adjust = 0x3D + Wall_mode = 0x5C # Video wall mode + Safety = 0x5D + Wall_on = 0x84 # Video wall enabled + Wall_user = 0x89 # Video wall user control + Speaker = 0x68 + Net_standby = 0xB5 # Keep NIC active in standby + Eco_solution = 0xE6 # Eco options (auto power off) + Auto_power = 0x33 + Screen_split = 0xB2 # Tri / quad split (larger panels only) + Software_version = 0x0E + Serial_number = 0x0B + end + private def do_send(command : String, data : Int32 | Array(Int32) = [] of Int32, **options) data = [data] if data.is_a?(Int32) diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr index dfaa5f4d019..b4c0bb8262f 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/md_series_spec.cr @@ -1,7 +1,17 @@ + # [header, command, id, size, data] + DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do + id = "\x00" + + # connected -> do_poll + # power? will take priority over status as status has priority = 0 + # power? -> panel_mute + should_send("\xAA\xF9#{id}\x00") + # status + should_send("\xAA\x00#{id}\x00") + exec(:volume, 24) - # header + command + id + size + value - should_send("\xAA\x12\x00\x01\x18") - #responds("\xAA\xFF\x00\x03\x41\x12Volume") - #status[:volume].should eq(24) + should_send("\xAA\x12#{id}\x01\x18") + # responds("\xAA\xFF\x00\x03\x41\x12Volume") + # status[:volume].should eq(24) end \ No newline at end of file From b91a62d334b839781688c68898a22f2971f84aca Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 17 Jun 2020 14:28:38 +0100 Subject: [PATCH 20/95] fix(samsung): fix abstract tokenizer and add responses in spec --- drivers/samsung/displays/md_series.cr | 22 +++++++++++++++------- drivers/samsung/displays/md_series_spec.cr | 14 ++++++++------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 1e5710679f4..d32c3f30eac 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -33,13 +33,13 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # TODO: figure out how to define indicator \xAA def init_tokenizer @buffer = Tokenizer.new do |io| - # bytes = io.peek # for demonstration purposes - string = io.gets_to_end + bytes = io.peek + logger.debug { "bytes: #{bytes}" } # (data length + header and checksum) # original ruby code with support for indicator and variable length message # string[2].to_i + 4 - string[3].to_i + 4 # move data length index + 1 as response will include indicator + bytes[3].to_i + 5 # move data length index + 1 as response will include indicator end end @@ -250,7 +250,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def unmute_audio if self[:audio_mute]? - self[:audio_mute] = false + self[:audio_mute] = false volume(self[:previous_volume].as_i? || 50) end end @@ -350,6 +350,15 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def received(data, task) logger.debug { "Samsung sent: #{data}" } + + # Calculate checksum of response + sum : Int32 = 0 + data[1..-2].each do |b| + sum += b + end + checksum = (sum & 0xFF).to_s(16) + + logger.debug { "Checksum: #{checksum}" } task.try &.success end @@ -400,9 +409,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver command = COMMANDS.parse(command).value data = [command, @id, data.size] + data # Build request - # TODO: write equivalent in crystal - # # data << (data.reduce(:+) & 0xFF) # Add checksum - data = [0xAA] + data # Add header + data << (data.sum & 0xFF) # Add checksum + data = [0xAA] + data # Add header data = byte_to_hex(data) logger.debug { "Sending to Samsung: #{data}}" } diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr index b4c0bb8262f..9b405339c22 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/md_series_spec.cr @@ -1,4 +1,4 @@ - # [header, command, id, size, data] + # [header, command, id, data.size, [data], checksum] DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do id = "\x00" @@ -6,12 +6,14 @@ DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do # connected -> do_poll # power? will take priority over status as status has priority = 0 # power? -> panel_mute - should_send("\xAA\xF9#{id}\x00") + should_send("\xAA\xF9#{id}\x00\xF9") + responds("\xAA\xFF#{id}\x03A\xF9\x00\x3C") # status - should_send("\xAA\x00#{id}\x00") + should_send("\xAA\x00#{id}\x00\x00") + responds("\xAA\xFF#{id}\x09A\x00\x00\x06\x00\x14\x00\x00\x00\x63") exec(:volume, 24) - should_send("\xAA\x12#{id}\x01\x18") - # responds("\xAA\xFF\x00\x03\x41\x12Volume") - # status[:volume].should eq(24) + should_send("\xAA\x12#{id}\x01\x18\x2B") + responds("\xAA\xFF\x00\x03A\x12\x18") + status[:volume].should eq(24) end \ No newline at end of file From fc951b0cf37f6dfbfe7629cf617ba2dd570939fe Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 17 Jun 2020 16:35:07 +0100 Subject: [PATCH 21/95] feat(samsung): add skeleton and checksum verification for received method --- drivers/samsung/displays/md_series.cr | 105 ++++++++++++++------- drivers/samsung/displays/md_series_spec.cr | 2 +- 2 files changed, 70 insertions(+), 37 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index d32c3f30eac..e0c942a9fef 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -34,7 +34,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def init_tokenizer @buffer = Tokenizer.new do |io| bytes = io.peek - logger.debug { "bytes: #{bytes}" } + logger.debug { "Received: #{bytes}" } # (data length + header and checksum) # original ruby code with support for indicator and variable length message @@ -65,7 +65,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver def connected do_poll - # do_device_config unless self[:hard_off]? + do_device_config unless self[:hard_off]? schedule.every(30.seconds) do logger.debug { "-- polling display" } @@ -305,29 +305,29 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # end # end -# DEVICE_SETTINGS = [ -# :network_standby, -# :auto_off_timer, -# :auto_power, -# :contrast, -# :brightness, -# :sharpness, -# :colour, -# :tint, -# :red_gain, -# :green_gain, -# :blue_gain, -# ] - -# def do_device_config -# logger.debug { "Syncronising device state with settings" } -# DEVICE_SETTINGS.each do |name| -# value = setting(Int32?, name) -# # TODO: find out if these are equivalent -# # __send__(name, value) unless value.nil? -# do_send(name.to_s, value.as(Int32)) unless value.nil? -# end -# end + DEVICE_SETTINGS = [ + :network_standby, + :auto_off_timer, + :auto_power, + :contrast, + :brightness, + :sharpness, + :colour, + :tint, + :red_gain, + :green_gain, + :blue_gain, +] + + def do_device_config + logger.debug { "Syncronising device state with settings" } + DEVICE_SETTINGS.each do |name| + value = setting(Int32, name) + # TODO: find out if these are equivalent + # __send__(name, value) unless value.nil? + do_send(name.to_s, value) unless value.nil? + end + end # TODO: check type for broadcast is correct def wake(broadcast : String? = nil) @@ -344,21 +344,52 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end enum RESPONSESTATUS - Ack = 0x41 - Nak = 0x4e + Ack = 0x41 # A + Nak = 0x4e # N end def received(data, task) - logger.debug { "Samsung sent: #{data}" } + data = data.map{ |b| b.to_i }.to_a + hex = byte_to_hex(data) + logger.debug { "Samsung sent: #{hex}" } # Calculate checksum of response - sum : Int32 = 0 - data[1..-2].each do |b| - sum += b + checksum = data[1..-2].sum & 0xFF + + # Pop also removes the checksum from the response here + if data.pop != checksum + logger.error { "invalid checksum" } + # TODO: + # task.retry + end + + status = data[4]? + command = data[5]? + value = data[6..-1] if data[6]? + + case status + when RESPONSESTATUS::Ack + case command + when COMMAND::Status + when COMMAND::Panel_mute + when COMMAND::Volume + when COMMAND::Brightness + when COMMAND::Input + when COMMAND::Speaker + when COMMAND::Hard_off + when COMMAND::Screen_split + when COMMAND::Software_version + when COMMAND::Serial_number + else + logger.debug { "Samsung responded with ACK: #{value}" } + end + when RESPONSESTATUS::Nak + logger.warn { "Samsung responded with NAK:" } + else + logger.warn { "Samsung aborted with:" } end - checksum = (sum & 0xFF).to_s(16) - logger.debug { "Checksum: #{checksum}" } + logger.debug { "Checksum: #{checksum.to_s(16)}" } task.try &.success end @@ -371,7 +402,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end end - enum COMMANDS + enum COMMAND Status = 0x00 Hard_off = 0x11 # Completely powers off Panel_mute = 0xF9 # Screen blanking / visual mute @@ -406,7 +437,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver data = [data] if data.is_a?(Int32) # # options[:name] = command if data.length > 0 # name unless status request - command = COMMANDS.parse(command).value + command = COMMAND.parse(command).value data = [command, @id, data.size] + data # Build request data << (data.sum & 0xFF) # Add checksum @@ -415,7 +446,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver data = byte_to_hex(data) logger.debug { "Sending to Samsung: #{data}}" } data = data.hexbytes - logger.debug { "hexbytes = #{data}" } + logger.debug { "hexbytes: #{data}" } send(data, **options) # send(array_to_str(data), options).catch do |reason| @@ -424,6 +455,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # end end + # TODO: find out if I can do this instead + # def byte_to_hex(bytes : Enumerable(Int)) : String def byte_to_hex(bytes : Array(Int32)) : String bytes.map { |n| "%02X" % (n & 0xFF) }.join end diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr index 9b405339c22..a9393334d97 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/md_series_spec.cr @@ -15,5 +15,5 @@ DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do exec(:volume, 24) should_send("\xAA\x12#{id}\x01\x18\x2B") responds("\xAA\xFF\x00\x03A\x12\x18") - status[:volume].should eq(24) + # status[:volume].should eq(24) end \ No newline at end of file From 9afb025ecb8a23137a4478da2e0eb899987ad028 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 17 Jun 2020 16:57:38 +0100 Subject: [PATCH 22/95] feat(samsung): complete most of the received method --- drivers/samsung/displays/md_series.cr | 43 +++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index e0c942a9fef..9534181936e 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -355,6 +355,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # Calculate checksum of response checksum = data[1..-2].sum & 0xFF + logger.debug { "Checksum: #{checksum.to_s(16)}" } # Pop also removes the checksum from the response here if data.pop != checksum @@ -363,33 +364,63 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # task.retry end - status = data[4]? - command = data[5]? - value = data[6..-1] if data[6]? + status = data[4] + command = data[5] + values = data[6..-1] + value = values.first case status when RESPONSESTATUS::Ack case command when COMMAND::Status + self[:hard_off] = values[0] == 0 + self[:power] = false if self[:hard_off] + self[:volume] = values[1] + self[:audio_mute] = false if values[2] > 0 + self[:input] = INPUTS.new(values[3]).to_s + check_power_state when COMMAND::Panel_mute + self[:power] = value == 0 + check_power_state when COMMAND::Volume + self[:volume] = value + self[:audio_mute] = false if value > 0 when COMMAND::Brightness + self[:brightness] = value when COMMAND::Input + self[:input] = INPUTS.new(value).to_s + # The input feedback behaviour seems to go a little odd when + # screen split is active. Ignore any input forcing when on. + unless self[:screen_split] + self[:input_stable] = self[:input] == self[:input_target] + switch_to(self[:input_target].as_s) unless self[:input_stable] + end when COMMAND::Speaker + self[:speaker] = SPEAKERMODES.new(value).to_s when COMMAND::Hard_off + self[:hard_off] = value == 0 + self[:power] = false if self[:hard_off] when COMMAND::Screen_split + # TODO: + # self[:screen_split] = value.positive? when COMMAND::Software_version + self[:software_version] = values.join when COMMAND::Serial_number + self[:serial_number] = values.join else logger.debug { "Samsung responded with ACK: #{value}" } end + + hex when RESPONSESTATUS::Nak - logger.warn { "Samsung responded with NAK:" } + logger.warn { "Samsung responded with NAK: #{byte_to_hex(values)}" } + # TODO + # :failed # Failed response else - logger.warn { "Samsung aborted with:" } + logger.warn { "Samsung aborted with: #{byte_to_hex(values)}" } + hex end - logger.debug { "Checksum: #{checksum.to_s(16)}" } task.try &.success end From df40e66dd02c005e0ddcd1403c4bc2b803ba06da Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 17 Jun 2020 17:08:57 +0100 Subject: [PATCH 23/95] fix(samsung): use task signals --- drivers/samsung/displays/md_series.cr | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 9534181936e..338aa833b0d 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -360,8 +360,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # Pop also removes the checksum from the response here if data.pop != checksum logger.error { "invalid checksum" } - # TODO: - # task.retry + task.try &.retry end status = data[4] @@ -411,17 +410,14 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver logger.debug { "Samsung responded with ACK: #{value}" } end - hex + task.try &.success when RESPONSESTATUS::Nak - logger.warn { "Samsung responded with NAK: #{byte_to_hex(values)}" } - # TODO - # :failed # Failed response + task.try &.abort("Samsung responded with NAK: #{byte_to_hex(values)}") else - logger.warn { "Samsung aborted with: #{byte_to_hex(values)}" } - hex + task.try &.abort("Samsung aborted with: #{byte_to_hex(values)}") end - task.try &.success + hex end def check_power_state From 4839b793d81a08ca9e4c04083a00e27dc10643ee Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 17 Jun 2020 17:22:18 +0100 Subject: [PATCH 24/95] feat(samsung): port over split function --- drivers/samsung/displays/md_series.cr | 54 +++++++++++++-------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 338aa833b0d..e267ae7a3b2 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -194,31 +194,28 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver do_send("input", INPUTS.parse(input).value, **options) end - # # TODO: check if used anywhere - # enum SCALE_MODE - # fill = 0x09 - # fit = 0x20 - # end + enum SCALEMODE + Fill = 0x09 + Fit = 0x20 + end + + # Activite the internal compositor. Can either split 3 or 4 ways. + def split(inputs : Array(String) = ["hdmi", "hdmi2", "hdmi3"], layout : Int32 = 0, scale : String = "fit", **options) + main_source = inputs.shift + + data = [ + 1, # enable + 0, # sound from screen section 1 + layout, # layout mode (1..6) + SCALEMODE.parse(scale).value, # scaling for main source + inputs.flat_map do |input| + [INPUTS.parse(input).value, SCALEMODE.parse(scale).value] + end + ].flatten - # # Activite the internal compositor. Can either split 3 or 4 ways. - # def split(inputs = [:hdmi, :hdmi2, :hdmi3], layout = 0, scale = :fit, **options) - # main_source = inputs.shift - - # data = [ - # 1, # enable - # 0, # sound from screen section 1 - # layout, # layout mode (1..6) - # SCALE_MODE[scale], # scaling for main source - # inputs.flat_map do |input| - # input = input.to_sym if input.is_a? String - # [INPUTS[input], SCALE_MODE[scale]] - # end - # ].flatten - - # switch_to(main_source, options).then do - # do_send("screen_split", data, options) - # end - # end + switch_to(main_source, **options) + do_send("screen_split", data, **options) + end def in_range(val : Int32, max : Int32) : Int32 min = 0 @@ -476,10 +473,11 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver logger.debug { "hexbytes: #{data}" } send(data, **options) - # send(array_to_str(data), options).catch do |reason| - # disconnect - # thread.reject(reason) - # end + # TODO: find out if this is necessary + # send(array_to_str(data), options).catch do |reason| + # disconnect + # thread.reject(reason) + # end end # TODO: find out if I can do this instead From 2d8cab73821d7f6657171e63cdfd64ce04a348b6 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 17 Jun 2020 18:31:47 +0100 Subject: [PATCH 25/95] feat(samsung): add status value checks to spec --- drivers/samsung/displays/md_series.cr | 13 +++++-------- drivers/samsung/displays/md_series_spec.cr | 13 ++++++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index e267ae7a3b2..1c3e0283608 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -352,16 +352,15 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # Calculate checksum of response checksum = data[1..-2].sum & 0xFF - logger.debug { "Checksum: #{checksum.to_s(16)}" } # Pop also removes the checksum from the response here if data.pop != checksum - logger.error { "invalid checksum" } - task.try &.retry + logger.error { "Invalid checksum\nChecksum should be: #{checksum.to_s(16)}" } + return task.try &.retry end - status = data[4] - command = data[5] + status = RESPONSESTATUS.new(data[4]) + command = COMMAND.new(data[5]) values = data[6..-1] value = values.first @@ -372,7 +371,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver self[:hard_off] = values[0] == 0 self[:power] = false if self[:hard_off] self[:volume] = values[1] - self[:audio_mute] = false if values[2] > 0 + self[:audio_mute] = values[2] == 1 self[:input] = INPUTS.new(values[3]).to_s check_power_state when COMMAND::Panel_mute @@ -413,8 +412,6 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver else task.try &.abort("Samsung aborted with: #{byte_to_hex(values)}") end - - hex end def check_power_state diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr index a9393334d97..9066139771a 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/md_series_spec.cr @@ -8,12 +8,19 @@ DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do # power? -> panel_mute should_send("\xAA\xF9#{id}\x00\xF9") responds("\xAA\xFF#{id}\x03A\xF9\x00\x3C") + status[:power].should eq(true) + # status should_send("\xAA\x00#{id}\x00\x00") - responds("\xAA\xFF#{id}\x09A\x00\x00\x06\x00\x14\x00\x00\x00\x63") + responds("\xAA\xFF#{id}\x09A\x00\x01\x06\x00\x14\x00\x00\x00\x64") + status[:hard_off].should eq(false) + status[:volume].should eq(6) + status[:audio_mute].should eq(false) + status[:input].should eq("Vga") exec(:volume, 24) should_send("\xAA\x12#{id}\x01\x18\x2B") - responds("\xAA\xFF\x00\x03A\x12\x18") - # status[:volume].should eq(24) + responds("\xAA\xFF\x00\x03A\x12\x18\x6D") + status[:volume].should eq(24) + status[:audio_mute].should eq(false) end \ No newline at end of file From aaab229e262a4f87855b950918b9e9d0fef3c1e8 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 17 Jun 2020 18:42:47 +0100 Subject: [PATCH 26/95] fix(samsung): save screen_split value --- drivers/samsung/displays/md_series.cr | 3 +-- drivers/samsung/displays/md_series_spec.cr | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 1c3e0283608..d22310dc8c8 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -396,8 +396,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver self[:hard_off] = value == 0 self[:power] = false if self[:hard_off] when COMMAND::Screen_split - # TODO: - # self[:screen_split] = value.positive? + self[:screen_split] = value >= 0 when COMMAND::Software_version self[:software_version] = values.join when COMMAND::Serial_number diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr index 9066139771a..a85aa8d6593 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/md_series_spec.cr @@ -9,11 +9,11 @@ DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do should_send("\xAA\xF9#{id}\x00\xF9") responds("\xAA\xFF#{id}\x03A\xF9\x00\x3C") status[:power].should eq(true) - # status should_send("\xAA\x00#{id}\x00\x00") responds("\xAA\xFF#{id}\x09A\x00\x01\x06\x00\x14\x00\x00\x00\x64") status[:hard_off].should eq(false) + # status[:power].should eq(true) status[:volume].should eq(6) status[:audio_mute].should eq(false) status[:input].should eq("Vga") From ef0f243aca2040e30d592cf0e91cd02091bee90b Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 19 Jun 2020 15:07:13 +0100 Subject: [PATCH 27/95] feat(samsung): add macro to define methods --- drivers/samsung/displays/md_series.cr | 42 +++++++++++++-------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index d22310dc8c8..e07eb6337b4 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -227,11 +227,6 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver val end - def volume(vol : Int32, **options) - vol = in_range(vol, 100) - do_send("volume", vol, **options) - end - # Emulate mute def mute_audio(val : Bool = true) if val @@ -284,23 +279,26 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver do_send("auto_power", state, **options) end - # TODO: figure out what this does and port to Crystal - # # Colour control - # [ - # :contrast, - # :brightness, - # :sharpness, - # :colour, - # :tint, - # :red_gain, - # :green_gain, - # :blue_gain - # ].each do |command| - # define_method command do |val, **options| - # val = in_range(val.to_i, 100) - # do_send(command, val, options) - # end - # end + # Display control + METHODS = [ + "volume", + "contrast", + "brightness", + "sharpness", + "colour", + "tint", + "red_gain", + "green_gain", + "blue_gain" + ] + + # Macro to define methods from the array above + {% for name in METHODS %} + def {{name.id}}(val : Int32, **options) + val = in_range(val, 100) + do_send({{name.id.stringify}}, val, **options) + end + {% end %} DEVICE_SETTINGS = [ :network_standby, From 71c7976c9673889f130c50b6ddbf6abd7fecdbd7 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 22 Jun 2020 13:39:31 +0100 Subject: [PATCH 28/95] refactor(samsung): use Crystal's Bytes --- drivers/samsung/displays/md_series.cr | 124 ++++++++++++--------- drivers/samsung/displays/md_series_spec.cr | 2 + 2 files changed, 73 insertions(+), 53 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index e07eb6337b4..d2166ef5353 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -110,7 +110,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # def power?(**options, &block) def power?(**options) # options[:emit] = block unless block.nil? - do_send("panel_mute", [] of Int32, **options) + do_send("panel_mute", [] of UInt8, **options) end # Adds mute states compatible with projectors @@ -131,42 +131,64 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver do_send("serial_number") end - # # ability to send custom mdc commands via backoffice - # def custom_mdc(command, value = "") - # do_send(hex_to_byte(command).bytes[0], hex_to_byte(value).bytes) - # end - - # def set_timer(enable = true, volume = 0) - # # set the time on the display - # time_cmd = 0xA7 - # time_request = [] of Int - # t = Time.now - # time_request << t.day - # hour = t.hour - # ampm = if hour > 12 - # hour = hour - 12 - # 0 # pm - # else - # 1 # am - # end - # time_request << hour - # time_request << t.min - # time_request << t.month - # year = t.year.to_s(16).rjust(4, "0") - # time_request << year[0..1].to_i(16) - # time_request << year[2..-1].to_i(16) - # time_request << ampm - - # do_send time_cmd, time_request - - # state = is_affirmative?(enable) ? "01" : "00" - # vol = volume.to_s(16).rjust(2, "0") - # # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply - # custom_mdc( - # "A4", - # "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" - # ) - # end + # Converts a hex encoded string into a binary string + def byte_to_hex(data : String) : String + # Removes invalid characters + data = data.gsub(/(0x|[^0-9A-Fa-f])*/, "") + + # Ensure we have an even number of characters + data = '0' + data if data.size % 2 > 0 + + # Breaks string into an array of characters + output = [] of Int32 + data.scan(/.{2}/) do |md| + output << md[0].to_i(16) + end + + String.build do |io| + output.each do |number| + io.write_byte number.to_u8 + end + end + end + + # ability to send custom mdc commands via backoffice + def custom_mdc(command : String, value : String) + command = byte_to_hex(command).bytes[0].to_i + data = byte_to_hex(value).bytes + do_send(command, data) + end + + def set_timer(enable : Bool = true, volume : Int32 = 0) + # set the time on the display + time_request = [] of Int32 + t = Time.local + time_request << t.day + hour = t.hour + ampm = if hour > 12 + hour = hour - 12 + 0 # pm + else + 1 # am + end + time_request << hour + time_request << t.minute + time_request << t.month + year = t.year.to_s(16).rjust(4, '0') + time_request << year[0..1].to_i(16) + time_request << year[2..-1].to_i(16) + time_request << ampm + + do_send("time", time_request) + + state = enable ? "01" : "00" + vol = volume.to_s(16).rjust(2, '0') + # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply + custom_mdc( + "A4", + "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" + ) + end enum INPUTS Vga = 0x14 # pc in manual @@ -257,7 +279,7 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end def do_poll - do_send("status", [] of Int32, priority: 0) + do_send("status", [] of UInt8, priority: 0) power? unless self[:hard_off]? end @@ -344,9 +366,9 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver end def received(data, task) - data = data.map{ |b| b.to_i }.to_a - hex = byte_to_hex(data) + hex = data.hexstring logger.debug { "Samsung sent: #{hex}" } + data = data.map{ |b| b.to_i }.to_a # Calculate checksum of response checksum = data[1..-2].sum & 0xFF @@ -405,9 +427,9 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver task.try &.success when RESPONSESTATUS::Nak - task.try &.abort("Samsung responded with NAK: #{byte_to_hex(values)}") + task.try &.abort("Samsung responded with NAK: #{hex}") else - task.try &.abort("Samsung aborted with: #{byte_to_hex(values)}") + task.try &.abort("Samsung aborted with: #{hex}") end end @@ -449,22 +471,24 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver Screen_split = 0xB2 # Tri / quad split (larger panels only) Software_version = 0x0E Serial_number = 0x0B + Time = 0xA7 end - private def do_send(command : String, data : Int32 | Array(Int32) = [] of Int32, **options) + private def do_send(command : String | Int32, data : Int32 | Array(Int) = [] of UInt8, **options) data = [data] if data.is_a?(Int32) # # options[:name] = command if data.length > 0 # name unless status request - command = COMMAND.parse(command).value + command = COMMAND.parse(command).value if command.is_a?(String) data = [command, @id, data.size] + data # Build request data << (data.sum & 0xFF) # Add checksum data = [0xAA] + data # Add header - data = byte_to_hex(data) + # Convert to Bytes + data = Slice.new(data.size) { |i| data[i].to_u8 } + logger.debug { "Sending to Samsung: #{data}}" } - data = data.hexbytes - logger.debug { "hexbytes: #{data}" } + logger.debug { "hexbytes: #{data.hexstring}" } send(data, **options) # TODO: find out if this is necessary @@ -473,10 +497,4 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver # thread.reject(reason) # end end - - # TODO: find out if I can do this instead - # def byte_to_hex(bytes : Enumerable(Int)) : String - def byte_to_hex(bytes : Array(Int32)) : String - bytes.map { |n| "%02X" % (n & 0xFF) }.join - end end diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/md_series_spec.cr index a85aa8d6593..e5adcc9fa84 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/md_series_spec.cr @@ -23,4 +23,6 @@ DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do responds("\xAA\xFF\x00\x03A\x12\x18\x6D") status[:volume].should eq(24) status[:audio_mute].should eq(false) + + exec(:set_timer, true, 15) end \ No newline at end of file From 754d41bcb32eaab9d8b4e17916440dc5d5606084 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 22 Jun 2020 13:54:29 +0100 Subject: [PATCH 29/95] chore(samsung): cleanup do_send param types --- drivers/samsung/displays/md_series.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index d2166ef5353..3aa3a9cc573 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -474,8 +474,8 @@ class Samsung::Displays::MdSeries < PlaceOS::Driver Time = 0xA7 end - private def do_send(command : String | Int32, data : Int32 | Array(Int) = [] of UInt8, **options) - data = [data] if data.is_a?(Int32) + private def do_send(command : String | Int, data : Int | Array(Int) = [] of UInt8, **options) + data = [data] if data.is_a?(Int) # # options[:name] = command if data.length > 0 # name unless status request command = COMMAND.parse(command).value if command.is_a?(String) From effadb0937e95311b7b517ca44c8fadcb83b8665 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 23 Jun 2020 10:24:09 +0100 Subject: [PATCH 30/95] chore(samsung): remove documentation links --- drivers/samsung/displays/md_series.cr | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/md_series.cr index 3aa3a9cc573..a4dac4df10e 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/md_series.cr @@ -1,8 +1,5 @@ module Samsung; end -# Documentation: https://drive.google.com/a/room.tools/file/d/135yRevYnI6BbZvRWjV51Ur0yKU5bQ_a-/view?usp=sharing -# Older Documentation: https://aca.im/driver_docs/Samsung/MDC%20Protocol%202015%20v13.7c.pdf - class Samsung::Displays::MdSeries < PlaceOS::Driver # Discovery Information tcp_port 1515 From 0f6ea0ce17a46c136b49903d0e1d02a70999ae80 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 23 Jun 2020 10:27:57 +0100 Subject: [PATCH 31/95] refactor(samsung): rename to MDCProtocol --- drivers/samsung/displays/{md_series.cr => mdc_protocol.cr} | 2 +- .../displays/{md_series_spec.cr => mdc_protocol_spec.cr} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename drivers/samsung/displays/{md_series.cr => mdc_protocol.cr} (99%) rename drivers/samsung/displays/{md_series_spec.cr => mdc_protocol_spec.cr} (93%) diff --git a/drivers/samsung/displays/md_series.cr b/drivers/samsung/displays/mdc_protocol.cr similarity index 99% rename from drivers/samsung/displays/md_series.cr rename to drivers/samsung/displays/mdc_protocol.cr index a4dac4df10e..ab8c71defe7 100644 --- a/drivers/samsung/displays/md_series.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -1,6 +1,6 @@ module Samsung; end -class Samsung::Displays::MdSeries < PlaceOS::Driver +class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Discovery Information tcp_port 1515 descriptive_name "Samsung MD, DM & QM Series LCD" diff --git a/drivers/samsung/displays/md_series_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr similarity index 93% rename from drivers/samsung/displays/md_series_spec.cr rename to drivers/samsung/displays/mdc_protocol_spec.cr index e5adcc9fa84..8fa5d642698 100644 --- a/drivers/samsung/displays/md_series_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -1,6 +1,6 @@ # [header, command, id, data.size, [data], checksum] -DriverSpecs.mock_driver "Samsung::Displays::MdSeries" do +DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do id = "\x00" # connected -> do_poll From 1d8677dbc89c8dd167a55e568b30b915372ee965 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 23 Jun 2020 10:41:44 +0100 Subject: [PATCH 32/95] refactor(samsung): use Enum's from_value instead of new --- drivers/samsung/displays/mdc_protocol.cr | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index ab8c71defe7..131266ffeb6 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -20,7 +20,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver default_settings({ display_id: 0, rs232_control: false, - blank: nil, + blanking_input: nil, }) @id : Int32 = 0 @@ -33,10 +33,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver bytes = io.peek logger.debug { "Received: #{bytes}" } - # (data length + header and checksum) - # original ruby code with support for indicator and variable length message - # string[2].to_i + 4 - bytes[3].to_i + 5 # move data length index + 1 as response will include indicator + # [header, command, id, data.size, [data], checksum] + bytes[3].to_i + 5 end end @@ -57,7 +55,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def on_update @id = setting(Int32, :display_id) @rs232 = setting(Bool, :rs232_control) - @blank = setting(String?, :blank) + @blank = setting(String?, :blanking_input) end def connected @@ -65,7 +63,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_device_config unless self[:hard_off]? schedule.every(30.seconds) do - logger.debug { "-- polling display" } do_poll end end @@ -376,8 +373,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver return task.try &.retry end - status = RESPONSESTATUS.new(data[4]) - command = COMMAND.new(data[5]) + status = RESPONSESTATUS.from_value(data[4]) + command = COMMAND.from_value(data[5]) values = data[6..-1] value = values.first @@ -389,7 +386,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:power] = false if self[:hard_off] self[:volume] = values[1] self[:audio_mute] = values[2] == 1 - self[:input] = INPUTS.new(values[3]).to_s + self[:input] = INPUTS.from_value(values[3]).to_s check_power_state when COMMAND::Panel_mute self[:power] = value == 0 @@ -400,7 +397,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when COMMAND::Brightness self[:brightness] = value when COMMAND::Input - self[:input] = INPUTS.new(value).to_s + self[:input] = INPUTS.from_value(value).to_s # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. unless self[:screen_split] @@ -408,7 +405,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver switch_to(self[:input_target].as_s) unless self[:input_stable] end when COMMAND::Speaker - self[:speaker] = SPEAKERMODES.new(value).to_s + self[:speaker] = SPEAKERMODES.from_value(value).to_s when COMMAND::Hard_off self[:hard_off] = value == 0 self[:power] = false if self[:hard_off] From 1066b952f6c8af68fa53de067b336c24d61006a3 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 23 Jun 2020 10:51:43 +0100 Subject: [PATCH 33/95] refactor(samsung): use question methods for Enums --- drivers/samsung/displays/mdc_protocol.cr | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 131266ffeb6..0fe43a28ce7 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -379,24 +379,24 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver value = values.first case status - when RESPONSESTATUS::Ack + when .ack? case command - when COMMAND::Status + when .status? self[:hard_off] = values[0] == 0 self[:power] = false if self[:hard_off] self[:volume] = values[1] self[:audio_mute] = values[2] == 1 self[:input] = INPUTS.from_value(values[3]).to_s check_power_state - when COMMAND::Panel_mute + when .panel_mute? self[:power] = value == 0 check_power_state - when COMMAND::Volume + when .volume? self[:volume] = value self[:audio_mute] = false if value > 0 - when COMMAND::Brightness + when .brightness? self[:brightness] = value - when COMMAND::Input + when .input? self[:input] = INPUTS.from_value(value).to_s # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. @@ -404,23 +404,23 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:input_stable] = self[:input] == self[:input_target] switch_to(self[:input_target].as_s) unless self[:input_stable] end - when COMMAND::Speaker + when .speaker? self[:speaker] = SPEAKERMODES.from_value(value).to_s - when COMMAND::Hard_off + when .hard_off? self[:hard_off] = value == 0 self[:power] = false if self[:hard_off] - when COMMAND::Screen_split + when .screen_split? self[:screen_split] = value >= 0 - when COMMAND::Software_version + when .software_version? self[:software_version] = values.join - when COMMAND::Serial_number + when .serial_number? self[:serial_number] = values.join else logger.debug { "Samsung responded with ACK: #{value}" } end task.try &.success - when RESPONSESTATUS::Nak + when .nak? task.try &.abort("Samsung responded with NAK: #{hex}") else task.try &.abort("Samsung aborted with: #{hex}") From 79b1babeb0e9ada19f79f93977b24bd503d23924 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 23 Jun 2020 11:03:03 +0100 Subject: [PATCH 34/95] refactor(samsung): update do_send to only take in COMMAND enums --- drivers/samsung/displays/mdc_protocol.cr | 60 ++++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 0fe43a28ce7..645cbe0306b 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -84,19 +84,19 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Blank the screen before turning off panel if required # required by some video walls where screens are chained switch_to(@blank.as(String)) if @blank && self[:power]? - do_send("panel_mute", 1) + do_send(COMMAND::Panel_mute, 1) elsif !@rs232 && !self[:connected]? wake(broadcast) else # Power on - do_send("hard_off", 1) - do_send("panel_mute", 0) + do_send(COMMAND::Hard_off, 1) + do_send(COMMAND::Panel_mute, 0) end end def hard_off - do_send("panel_mute", 0) if self[:power]? - do_send("hard_off", 0) + do_send(COMMAND::Panel_mute, 0) if self[:power]? + do_send(COMMAND::Hard_off, 0) do_poll end @@ -104,7 +104,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # def power?(**options, &block) def power?(**options) # options[:emit] = block unless block.nil? - do_send("panel_mute", [] of UInt8, **options) + do_send(COMMAND::Panel_mute, [] of UInt8, **options) end # Adds mute states compatible with projectors @@ -118,11 +118,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # check software version def software_version? - do_send("software_version") + do_send(COMMAND::Software_version) end def serial_number? - do_send("serial_number") + do_send(COMMAND::Serial_number) end # Converts a hex encoded string into a binary string @@ -173,7 +173,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver time_request << year[2..-1].to_i(16) time_request << ampm - do_send("time", time_request) + do_send(COMMAND::Time, time_request) state = enable ? "01" : "00" vol = volume.to_s(16).rjust(2, '0') @@ -207,7 +207,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def switch_to(input : String, **options) self[:input_stable] = false self[:input_target] = input - do_send("input", INPUTS.parse(input).value, **options) + do_send(COMMAND::Input, INPUTS.parse(input).value, **options) end enum SCALEMODE @@ -230,17 +230,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver ].flatten switch_to(main_source, **options) - do_send("screen_split", data, **options) - end - - def in_range(val : Int32, max : Int32) : Int32 - min = 0 - if val < min - val = min - elsif val > max - val = max - end - val + do_send(COMMAND::Screen_split, data, **options) end # Emulate mute @@ -269,30 +259,40 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def speaker_select(mode : String, **options) - do_send("speaker", SPEAKERMODES.parse(mode).value, **options) + do_send(COMMAND::Speaker, SPEAKERMODES.parse(mode).value, **options) end def do_poll - do_send("status", [] of UInt8, priority: 0) + do_send(COMMAND::Status, [] of UInt8, priority: 0) power? unless self[:hard_off]? end # Enable power on (without WOL) def network_standby(enable : Bool, **options) state = enable ? 1 : 0 - do_send("net_standby", state, **options) + do_send(COMMAND::Net_standby, state, **options) end # Eco auto power off timer def auto_off_timer(enable : Bool, **options) state = enable ? 1 : 0 - do_send("eco_solution", [0x81, state], **options) + do_send(COMMAND::Eco_solution, [0x81, state], **options) end # Device auto power control (presumably signal based?) def auto_power(enable : Bool, **options) state = enable ? 1 : 0 - do_send("auto_power", state, **options) + do_send(COMMAND::Auto_power, state, **options) + end + + def in_range(val : Int32, max : Int32) : Int32 + min = 0 + if val < min + val = min + elsif val > max + val = max + end + val end # Display control @@ -312,7 +312,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver {% for name in METHODS %} def {{name.id}}(val : Int32, **options) val = in_range(val, 100) - do_send({{name.id.stringify}}, val, **options) + do_send(COMMAND.parse({{name.id.stringify}}), val, **options) end {% end %} @@ -336,7 +336,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver value = setting(Int32, name) # TODO: find out if these are equivalent # __send__(name, value) unless value.nil? - do_send(name.to_s, value) unless value.nil? + do_send(COMMAND.parse(value.to_s), value) unless value.nil? end end @@ -468,11 +468,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver Time = 0xA7 end - private def do_send(command : String | Int, data : Int | Array(Int) = [] of UInt8, **options) + private def do_send(command : COMMAND | Int, data : Int | Array(Int) = [] of UInt8, **options) data = [data] if data.is_a?(Int) # # options[:name] = command if data.length > 0 # name unless status request - command = COMMAND.parse(command).value if command.is_a?(String) + command = command.value if command.is_a?(COMMAND) data = [command, @id, data.size] + data # Build request data << (data.sum & 0xFF) # Add checksum From a44f7d8c547716a7efd7781d25f20385dcccc7db Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 23 Jun 2020 12:17:34 +0100 Subject: [PATCH 35/95] refactor(samsung): use less intermediate arrays in do_send --- drivers/samsung/displays/mdc_protocol.cr | 35 +++++++++--------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 645cbe0306b..49fda801b0a 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -285,16 +285,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(COMMAND::Auto_power, state, **options) end - def in_range(val : Int32, max : Int32) : Int32 - min = 0 - if val < min - val = min - elsif val > max - val = max - end - val - end - # Display control METHODS = [ "volume", @@ -311,7 +301,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Macro to define methods from the array above {% for name in METHODS %} def {{name.id}}(val : Int32, **options) - val = in_range(val, 100) + val = val.clamp(0, 100) do_send(COMMAND.parse({{name.id.stringify}}), val, **options) end {% end %} @@ -362,7 +352,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def received(data, task) hex = data.hexstring logger.debug { "Samsung sent: #{hex}" } - data = data.map{ |b| b.to_i }.to_a + data = data.map(&.to_i).to_a # Calculate checksum of response checksum = data[1..-2].sum & 0xFF @@ -474,16 +464,17 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # # options[:name] = command if data.length > 0 # name unless status request command = command.value if command.is_a?(COMMAND) - data = [command, @id, data.size] + data # Build request - data << (data.sum & 0xFF) # Add checksum - data = [0xAA] + data # Add header - - # Convert to Bytes - data = Slice.new(data.size) { |i| data[i].to_u8 } - - logger.debug { "Sending to Samsung: #{data}}" } - logger.debug { "hexbytes: #{data.hexstring}" } - send(data, **options) + bytes = Slice(UInt8).new(data.size + 5) + bytes[0] = 0xAA.to_u8 # Header + bytes[1] = command.to_u8 + bytes[2] = @id.to_u8 + bytes[3] = data.size.to_u8 + # Copy data into bytes for index 4...(4 + data.size) + data.each_with_index(4) do |b, i| bytes[i] = b.to_u8 end + bytes[-1] = (bytes[1..-2].map(&.to_i).sum & 0xFF).to_u8 # Checksum + + logger.debug { "Sending to Samsung: #{bytes.hexstring}" } + send(bytes, **options) # TODO: find out if this is necessary # send(array_to_str(data), options).catch do |reason| From 9f08d9f4d252e417a175a8728acd1fef77b9e600 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 23 Jun 2020 12:29:38 +0100 Subject: [PATCH 36/95] chore(samsung): fix indentation --- drivers/samsung/displays/mdc_protocol.cr | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 49fda801b0a..2e1432f43b8 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -32,9 +32,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver @buffer = Tokenizer.new do |io| bytes = io.peek logger.debug { "Received: #{bytes}" } - - # [header, command, id, data.size, [data], checksum] - bytes[3].to_i + 5 + # [header, command, id, data.size, [data], checksum] + bytes[3].to_i + 5 end end From e5e7d335de9b85e74940cbfb5cc3d3433392dbea Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 25 Jun 2020 10:43:24 +0100 Subject: [PATCH 37/95] chore(samsung): remove custom command method --- drivers/samsung/displays/mdc_protocol.cr | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 2e1432f43b8..b9c6a28b072 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -145,13 +145,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end - # ability to send custom mdc commands via backoffice - def custom_mdc(command : String, value : String) - command = byte_to_hex(command).bytes[0].to_i - data = byte_to_hex(value).bytes - do_send(command, data) - end - def set_timer(enable : Bool = true, volume : Int32 = 0) # set the time on the display time_request = [] of Int32 @@ -176,11 +169,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver state = enable ? "01" : "00" vol = volume.to_s(16).rjust(2, '0') - # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply - custom_mdc( - "A4", - "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" - ) + # on 03:45am enabled off 03:30am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply + data = "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" + data = byte_to_hex(data).bytes + + do_send(COMMAND::Timer, data) end enum INPUTS @@ -455,9 +448,10 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver Software_version = 0x0E Serial_number = 0x0B Time = 0xA7 + Timer = 0xA4 end - private def do_send(command : COMMAND | Int, data : Int | Array(Int) = [] of UInt8, **options) + private def do_send(command : COMMAND, data : Int | Array(Int) = [] of UInt8, **options) data = [data] if data.is_a?(Int) # # options[:name] = command if data.length > 0 # name unless status request From 821ed9a68308fde8828b2b4d374273079218fb6c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 25 Jun 2020 12:21:49 +0100 Subject: [PATCH 38/95] refactor/fix(samsung): move command assembly into enum class and use correct method to calculate checksum --- drivers/samsung/displays/mdc_protocol.cr | 28 +++++++++---------- drivers/samsung/displays/mdc_protocol_spec.cr | 8 +++--- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index b9c6a28b072..1afd6a1326c 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -347,7 +347,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver data = data.map(&.to_i).to_a # Calculate checksum of response - checksum = data[1..-2].sum & 0xFF + checksum = data[1..-2].reduce(&.+) # Pop also removes the checksum from the response here if data.pop != checksum @@ -449,26 +449,24 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver Serial_number = 0x0B Time = 0xA7 Timer = 0xA4 + + def build(id : Int32, data : Array(Int)) : Bytes + Bytes.new(data.size + 5).tap do |bytes| + bytes[0] = 0xAA_u8 # Header + bytes[1] = self.to_u8 # Command + bytes[2] = id.to_u8 # Display ID + bytes[3] = data.size.to_u8 # Data size + data.each_with_index(4) { |b, i| bytes[i] = b.to_u8 } # Data + bytes[-1] = bytes[1..-2].reduce(&.+).to_u8! # Checksum + end + end end private def do_send(command : COMMAND, data : Int | Array(Int) = [] of UInt8, **options) data = [data] if data.is_a?(Int) - - # # options[:name] = command if data.length > 0 # name unless status request - command = command.value if command.is_a?(COMMAND) - - bytes = Slice(UInt8).new(data.size + 5) - bytes[0] = 0xAA.to_u8 # Header - bytes[1] = command.to_u8 - bytes[2] = @id.to_u8 - bytes[3] = data.size.to_u8 - # Copy data into bytes for index 4...(4 + data.size) - data.each_with_index(4) do |b, i| bytes[i] = b.to_u8 end - bytes[-1] = (bytes[1..-2].map(&.to_i).sum & 0xFF).to_u8 # Checksum - + bytes = command.build(@id, data) logger.debug { "Sending to Samsung: #{bytes.hexstring}" } send(bytes, **options) - # TODO: find out if this is necessary # send(array_to_str(data), options).catch do |reason| # disconnect diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index 8fa5d642698..7c48a494b56 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -7,11 +7,11 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do # power? will take priority over status as status has priority = 0 # power? -> panel_mute should_send("\xAA\xF9#{id}\x00\xF9") - responds("\xAA\xFF#{id}\x03A\xF9\x00\x3C") + responds("\xAA\xFF#{id}\x03A\xF9\x00\xFF") status[:power].should eq(true) # status should_send("\xAA\x00#{id}\x00\x00") - responds("\xAA\xFF#{id}\x09A\x00\x01\x06\x00\x14\x00\x00\x00\x64") + responds("\xAA\xFF#{id}\x09A\x00\x01\x06\x00\x14\x00\x00\x00\xFF") status[:hard_off].should eq(false) # status[:power].should eq(true) status[:volume].should eq(6) @@ -19,8 +19,8 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do status[:input].should eq("Vga") exec(:volume, 24) - should_send("\xAA\x12#{id}\x01\x18\x2B") - responds("\xAA\xFF\x00\x03A\x12\x18\x6D") + should_send("\xAA\x12#{id}\x01\x18\x12") + responds("\xAA\xFF\x00\x03A\x12\x18\xFF") status[:volume].should eq(24) status[:audio_mute].should eq(false) From 009e6aa5d4c64e2a9e277cdde65a4e246d62dd67 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 25 Jun 2020 13:39:25 +0100 Subject: [PATCH 39/95] refactor(samsung): more Bytes --- drivers/samsung/displays/mdc_protocol.cr | 54 ++++++++---------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 1afd6a1326c..f7d8089f0bf 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -1,6 +1,8 @@ module Samsung; end class Samsung::Displays::MDCProtocol < PlaceOS::Driver + include Utilities::Transcoder + # Discovery Information tcp_port 1515 descriptive_name "Samsung MD, DM & QM Series LCD" @@ -103,7 +105,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # def power?(**options, &block) def power?(**options) # options[:emit] = block unless block.nil? - do_send(COMMAND::Panel_mute, [] of UInt8, **options) + do_send(COMMAND::Panel_mute, Bytes.empty, **options) end # Adds mute states compatible with projectors @@ -124,27 +126,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(COMMAND::Serial_number) end - # Converts a hex encoded string into a binary string - def byte_to_hex(data : String) : String - # Removes invalid characters - data = data.gsub(/(0x|[^0-9A-Fa-f])*/, "") - - # Ensure we have an even number of characters - data = '0' + data if data.size % 2 > 0 - - # Breaks string into an array of characters - output = [] of Int32 - data.scan(/.{2}/) do |md| - output << md[0].to_i(16) - end - - String.build do |io| - output.each do |number| - io.write_byte number.to_u8 - end - end - end - def set_timer(enable : Bool = true, volume : Int32 = 0) # set the time on the display time_request = [] of Int32 @@ -165,15 +146,14 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver time_request << year[2..-1].to_i(16) time_request << ampm - do_send(COMMAND::Time, time_request) + do_send(COMMAND::Time, array_to_bytes(time_request)) state = enable ? "01" : "00" vol = volume.to_s(16).rjust(2, '0') # on 03:45am enabled off 03:30am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply data = "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" - data = byte_to_hex(data).bytes - do_send(COMMAND::Timer, data) + do_send(COMMAND::Timer, hex_to_bytes(data)) end enum INPUTS @@ -222,7 +202,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver ].flatten switch_to(main_source, **options) - do_send(COMMAND::Screen_split, data, **options) + do_send(COMMAND::Screen_split, array_to_bytes(data), **options) end # Emulate mute @@ -255,7 +235,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def do_poll - do_send(COMMAND::Status, [] of UInt8, priority: 0) + do_send(COMMAND::Status, Bytes.empty, priority: 0) power? unless self[:hard_off]? end @@ -268,7 +248,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Eco auto power off timer def auto_off_timer(enable : Bool, **options) state = enable ? 1 : 0 - do_send(COMMAND::Eco_solution, [0x81, state], **options) + do_send(COMMAND::Eco_solution, Bytes[0x81, state], **options) end # Device auto power control (presumably signal based?) @@ -450,20 +430,20 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver Time = 0xA7 Timer = 0xA4 - def build(id : Int32, data : Array(Int)) : Bytes + def build(id : Int32, data : Bytes) : Bytes Bytes.new(data.size + 5).tap do |bytes| - bytes[0] = 0xAA_u8 # Header - bytes[1] = self.to_u8 # Command - bytes[2] = id.to_u8 # Display ID - bytes[3] = data.size.to_u8 # Data size - data.each_with_index(4) { |b, i| bytes[i] = b.to_u8 } # Data - bytes[-1] = bytes[1..-2].reduce(&.+).to_u8! # Checksum + bytes[0] = 0xAA_u8 # Header + bytes[1] = self.to_u8 # Command + bytes[2] = id.to_u8 # Display ID + bytes[3] = data.size.to_u8 # Data size + data.each_with_index(4) { |b, i| bytes[i] = b } # Data + bytes[-1] = bytes[1..-2].reduce(&.+) # Checksum end end end - private def do_send(command : COMMAND, data : Int | Array(Int) = [] of UInt8, **options) - data = [data] if data.is_a?(Int) + private def do_send(command : COMMAND, data : Int | Bytes = Bytes.empty, **options) + data = Bytes[data] if data.is_a?(Int) bytes = command.build(@id, data) logger.debug { "Sending to Samsung: #{bytes.hexstring}" } send(bytes, **options) From e074df8a4d66a61071456b5e3f05d4775dba2fe3 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 25 Jun 2020 14:18:02 +0100 Subject: [PATCH 40/95] chore(samsung): remove set timer method --- drivers/samsung/displays/mdc_protocol.cr | 30 ------------------- drivers/samsung/displays/mdc_protocol_spec.cr | 4 +-- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index f7d8089f0bf..0178dbeb76a 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -126,36 +126,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(COMMAND::Serial_number) end - def set_timer(enable : Bool = true, volume : Int32 = 0) - # set the time on the display - time_request = [] of Int32 - t = Time.local - time_request << t.day - hour = t.hour - ampm = if hour > 12 - hour = hour - 12 - 0 # pm - else - 1 # am - end - time_request << hour - time_request << t.minute - time_request << t.month - year = t.year.to_s(16).rjust(4, '0') - time_request << year[0..1].to_i(16) - time_request << year[2..-1].to_i(16) - time_request << ampm - - do_send(COMMAND::Time, array_to_bytes(time_request)) - - state = enable ? "01" : "00" - vol = volume.to_s(16).rjust(2, '0') - # on 03:45am enabled off 03:30am enabled on-everyday ignore manual off-everyday ignore manual volume 15 input HDMI holiday apply - data = "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" - - do_send(COMMAND::Timer, hex_to_bytes(data)) - end - enum INPUTS Vga = 0x14 # pc in manual Dvi = 0x18 diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index 7c48a494b56..3186d344aa0 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -23,6 +23,4 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do responds("\xAA\xFF\x00\x03A\x12\x18\xFF") status[:volume].should eq(24) status[:audio_mute].should eq(false) - - exec(:set_timer, true, 15) -end \ No newline at end of file +end From ad1868b7ad2247ff216366eb45ceefbf11626677 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 25 Jun 2020 14:44:28 +0100 Subject: [PATCH 41/95] chore(samsung): use camelcase convention for type names --- drivers/samsung/displays/mdc_protocol.cr | 104 +++++++++++------------ 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 0178dbeb76a..314c71a4d54 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -85,19 +85,19 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Blank the screen before turning off panel if required # required by some video walls where screens are chained switch_to(@blank.as(String)) if @blank && self[:power]? - do_send(COMMAND::Panel_mute, 1) + do_send(Command::Panel_Mute, 1) elsif !@rs232 && !self[:connected]? wake(broadcast) else # Power on - do_send(COMMAND::Hard_off, 1) - do_send(COMMAND::Panel_mute, 0) + do_send(Command::Hard_Off, 1) + do_send(Command::Panel_Mute, 0) end end def hard_off - do_send(COMMAND::Panel_mute, 0) if self[:power]? - do_send(COMMAND::Hard_off, 0) + do_send(Command::Panel_Mute, 0) if self[:power]? + do_send(Command::Hard_Off, 0) do_poll end @@ -105,7 +105,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # def power?(**options, &block) def power?(**options) # options[:emit] = block unless block.nil? - do_send(COMMAND::Panel_mute, Bytes.empty, **options) + do_send(Command::Panel_Mute, Bytes.empty, **options) end # Adds mute states compatible with projectors @@ -119,40 +119,40 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # check software version def software_version? - do_send(COMMAND::Software_version) + do_send(Command::Software_Version) end def serial_number? - do_send(COMMAND::Serial_number) + do_send(Command::Serial_Number) end - enum INPUTS + enum Inputs Vga = 0x14 # pc in manual Dvi = 0x18 - Dvi_video = 0x1F + Dvi_Video = 0x1F Hdmi = 0x21 - Hdmi_pc = 0x22 + Hdmi_Pc = 0x22 Hdmi2 = 0x23 - Hdmi2_pc = 0x24 + Hdmi2_Pc = 0x24 Hdmi3 = 0x31 - Hdmi3_pc = 0x32 + Hdmi3_Pc = 0x32 Hdmi4 = 0x33 - Hdmi4_pc = 0x34 - Display_port = 0x25 + Hdmi4_Pc = 0x34 + Display_Port = 0x25 Dtv = 0x40 Media = 0x60 Widi = 0x61 - Magic_info = 0x20 + Magic_Info = 0x20 Whiteboard = 0x64 end def switch_to(input : String, **options) self[:input_stable] = false self[:input_target] = input - do_send(COMMAND::Input, INPUTS.parse(input).value, **options) + do_send(Command::Input, Inputs.parse(input).value, **options) end - enum SCALEMODE + enum ScaleModes Fill = 0x09 Fit = 0x20 end @@ -165,14 +165,14 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver 1, # enable 0, # sound from screen section 1 layout, # layout mode (1..6) - SCALEMODE.parse(scale).value, # scaling for main source + ScaleModes.parse(scale).value, # scaling for main source inputs.flat_map do |input| - [INPUTS.parse(input).value, SCALEMODE.parse(scale).value] + [Inputs.parse(input).value, ScaleModes.parse(scale).value] end ].flatten switch_to(main_source, **options) - do_send(COMMAND::Screen_split, array_to_bytes(data), **options) + do_send(Command::Screen_Split, array_to_bytes(data), **options) end # Emulate mute @@ -195,36 +195,36 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end - enum SPEAKERMODES + enum SpeakerModes Internal = 0 External = 1 end def speaker_select(mode : String, **options) - do_send(COMMAND::Speaker, SPEAKERMODES.parse(mode).value, **options) + do_send(Command::Speaker, SpeakerModes.parse(mode).value, **options) end def do_poll - do_send(COMMAND::Status, Bytes.empty, priority: 0) + do_send(Command::Status, Bytes.empty, priority: 0) power? unless self[:hard_off]? end # Enable power on (without WOL) def network_standby(enable : Bool, **options) state = enable ? 1 : 0 - do_send(COMMAND::Net_standby, state, **options) + do_send(Command::Net_Standby, state, **options) end # Eco auto power off timer def auto_off_timer(enable : Bool, **options) state = enable ? 1 : 0 - do_send(COMMAND::Eco_solution, Bytes[0x81, state], **options) + do_send(Command::Eco_Solution, Bytes[0x81, state], **options) end # Device auto power control (presumably signal based?) def auto_power(enable : Bool, **options) state = enable ? 1 : 0 - do_send(COMMAND::Auto_power, state, **options) + do_send(Command::Auto_Power, state, **options) end # Display control @@ -244,7 +244,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver {% for name in METHODS %} def {{name.id}}(val : Int32, **options) val = val.clamp(0, 100) - do_send(COMMAND.parse({{name.id.stringify}}), val, **options) + do_send(Command.parse({{name.id.stringify}}), val, **options) end {% end %} @@ -268,7 +268,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver value = setting(Int32, name) # TODO: find out if these are equivalent # __send__(name, value) unless value.nil? - do_send(COMMAND.parse(value.to_s), value) unless value.nil? + do_send(Command.parse(value.to_s), value) unless value.nil? end end @@ -286,7 +286,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end - enum RESPONSESTATUS + enum ResponseStatus Ack = 0x41 # A Nak = 0x4e # N end @@ -305,8 +305,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver return task.try &.retry end - status = RESPONSESTATUS.from_value(data[4]) - command = COMMAND.from_value(data[5]) + status = ResponseStatus.from_value(data[4]) + command = Command.from_value(data[5]) values = data[6..-1] value = values.first @@ -318,7 +318,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:power] = false if self[:hard_off] self[:volume] = values[1] self[:audio_mute] = values[2] == 1 - self[:input] = INPUTS.from_value(values[3]).to_s + self[:input] = Inputs.from_value(values[3]).to_s check_power_state when .panel_mute? self[:power] = value == 0 @@ -329,7 +329,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .brightness? self[:brightness] = value when .input? - self[:input] = INPUTS.from_value(value).to_s + self[:input] = Inputs.from_value(value).to_s # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. unless self[:screen_split] @@ -337,7 +337,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver switch_to(self[:input_target].as_s) unless self[:input_stable] end when .speaker? - self[:speaker] = SPEAKERMODES.from_value(value).to_s + self[:speaker] = SpeakerModes.from_value(value).to_s when .hard_off? self[:hard_off] = value == 0 self[:power] = false if self[:hard_off] @@ -368,35 +368,35 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end - enum COMMAND + enum Command Status = 0x00 - Hard_off = 0x11 # Completely powers off - Panel_mute = 0xF9 # Screen blanking / visual mute + Hard_Off = 0x11 # Completely powers off + Panel_Mute = 0xF9 # Screen blanking / visual mute Volume = 0x12 Contrast = 0x24 Brightness = 0x25 Sharpness = 0x26 Colour = 0x27 Tint = 0x28 - Red_gain = 0x29 - Green_gain = 0x2A - Blue_gain = 0x2B + Red_Gain = 0x29 + Green_Gain = 0x2A + Blue_Gain = 0x2B Input = 0x14 Mode = 0x18 Size = 0x19 Pip = 0x3C # picture in picture - Auto_adjust = 0x3D - Wall_mode = 0x5C # Video wall mode + Auto_Adjust = 0x3D + Wall_Mode = 0x5C # Video wall mode Safety = 0x5D - Wall_on = 0x84 # Video wall enabled - Wall_user = 0x89 # Video wall user control + Wall_On = 0x84 # Video wall enabled + Wall_User = 0x89 # Video wall user control Speaker = 0x68 - Net_standby = 0xB5 # Keep NIC active in standby - Eco_solution = 0xE6 # Eco options (auto power off) - Auto_power = 0x33 - Screen_split = 0xB2 # Tri / quad split (larger panels only) - Software_version = 0x0E - Serial_number = 0x0B + Net_Standby = 0xB5 # Keep NIC active in standby + Eco_Solution = 0xE6 # Eco options (auto power off) + Auto_Power = 0x33 + Screen_Split = 0xB2 # Tri / quad split (larger panels only) + Software_Version = 0x0E + Serial_Number = 0x0B Time = 0xA7 Timer = 0xA4 @@ -412,7 +412,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end - private def do_send(command : COMMAND, data : Int | Bytes = Bytes.empty, **options) + private def do_send(command : Command, data : Int | Bytes = Bytes.empty, **options) data = Bytes[data] if data.is_a?(Int) bytes = command.build(@id, data) logger.debug { "Sending to Samsung: #{bytes.hexstring}" } From 7b57367bc9e25ed90a3b7da791d90c1e12a98c50 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 25 Jun 2020 16:29:02 +0100 Subject: [PATCH 42/95] chore(samsung) remove wake method --- drivers/samsung/displays/mdc_protocol.cr | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 314c71a4d54..1ea5b2101a8 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -272,20 +272,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end - # TODO: check type for broadcast is correct - def wake(broadcast : String? = nil) - mac = setting(String, :mac_address) - if mac - # config is the database model representing this device - wake_device(mac, broadcast) - info = "Wake on Lan for MAC #{mac}" - info += " directed to VLAN #{broadcast}" if broadcast - logger.debug { info } - else - logger.debug { "No MAC address provided" } - end - end - enum ResponseStatus Ack = 0x41 # A Nak = 0x4e # N From 3b1a9a225920a5082a463c4500a7df70103386b1 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 25 Jun 2020 16:38:32 +0100 Subject: [PATCH 43/95] chore(samsung): remove ScaleModes enum and split method --- drivers/samsung/displays/mdc_protocol.cr | 27 +----------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 1ea5b2101a8..5c6cbc5783e 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -1,8 +1,6 @@ module Samsung; end class Samsung::Displays::MDCProtocol < PlaceOS::Driver - include Utilities::Transcoder - # Discovery Information tcp_port 1515 descriptive_name "Samsung MD, DM & QM Series LCD" @@ -87,7 +85,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver switch_to(@blank.as(String)) if @blank && self[:power]? do_send(Command::Panel_Mute, 1) elsif !@rs232 && !self[:connected]? - wake(broadcast) + wake_device(broadcast.as(String)) if broadcast else # Power on do_send(Command::Hard_Off, 1) @@ -152,29 +150,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(Command::Input, Inputs.parse(input).value, **options) end - enum ScaleModes - Fill = 0x09 - Fit = 0x20 - end - - # Activite the internal compositor. Can either split 3 or 4 ways. - def split(inputs : Array(String) = ["hdmi", "hdmi2", "hdmi3"], layout : Int32 = 0, scale : String = "fit", **options) - main_source = inputs.shift - - data = [ - 1, # enable - 0, # sound from screen section 1 - layout, # layout mode (1..6) - ScaleModes.parse(scale).value, # scaling for main source - inputs.flat_map do |input| - [Inputs.parse(input).value, ScaleModes.parse(scale).value] - end - ].flatten - - switch_to(main_source, **options) - do_send(Command::Screen_Split, array_to_bytes(data), **options) - end - # Emulate mute def mute_audio(val : Bool = true) if val From 4a2abddd531357148f9a9d9b8445a238a1ec74b1 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 25 Jun 2020 17:28:50 +0100 Subject: [PATCH 44/95] fix(samsung): use panel mute command for mute --- drivers/samsung/displays/mdc_protocol.cr | 10 +++++----- drivers/samsung/displays/mdc_protocol_spec.cr | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 5c6cbc5783e..560a42c99ec 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -84,8 +84,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # required by some video walls where screens are chained switch_to(@blank.as(String)) if @blank && self[:power]? do_send(Command::Panel_Mute, 1) - elsif !@rs232 && !self[:connected]? - wake_device(broadcast.as(String)) if broadcast + elsif !@rs232 && !self[:connected]? && broadcast + wake_device(broadcast.as(String)) else # Power on do_send(Command::Hard_Off, 1) @@ -96,7 +96,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def hard_off do_send(Command::Panel_Mute, 0) if self[:power]? do_send(Command::Hard_Off, 0) - do_poll end # TODO: figure out what block is for @@ -108,11 +107,12 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Adds mute states compatible with projectors def mute(state : Bool = true) - power(!state) + state = state ? 1 : 0 + do_send(Command::Panel_Mute, state) end def unmute - power(true) + mute(false) end # check software version diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index 3186d344aa0..9b19b316507 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -23,4 +23,12 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do responds("\xAA\xFF\x00\x03A\x12\x18\xFF") status[:volume].should eq(24) status[:audio_mute].should eq(false) + + exec(:mute, true) + responds("\xAA\xFF#{id}\x03A\xF9\x01\xFF") + status[:power].should eq(false) + + exec(:unmute) + responds("\xAA\xFF#{id}\x03A\xF9\x00\xFF") + status[:power].should eq(true) end From 0f07bc41e3f27edbfa22205a9f5fb4e10c494c9f Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 Jul 2020 15:14:49 +0100 Subject: [PATCH 45/95] fix(mdc_protocol): use Powerable interface --- drivers/samsung/displays/mdc_protocol.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 560a42c99ec..ab69f8fd026 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -1,6 +1,10 @@ +require "driver/interface/powerable" + module Samsung; end class Samsung::Displays::MDCProtocol < PlaceOS::Driver + include Interface::Powerable + # Discovery Information tcp_port 1515 descriptive_name "Samsung MD, DM & QM Series LCD" From 720f6232bec63a31c81096c2ab9495cb409dbbdd Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 Jul 2020 15:15:37 +0100 Subject: [PATCH 46/95] fix(mdc_protocol): use Muteable interface --- drivers/samsung/displays/mdc_protocol.cr | 59 +++++++++++++++--------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index ab69f8fd026..d4e19047f68 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -1,9 +1,11 @@ require "driver/interface/powerable" +require "driver/interface/muteable" module Samsung; end class Samsung::Displays::MDCProtocol < PlaceOS::Driver include Interface::Powerable + include Interface::Muteable # Discovery Information tcp_port 1515 @@ -109,14 +111,45 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(Command::Panel_Mute, Bytes.empty, **options) end + # Mutes audio + video + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + logger.debug { "requested mute state: #{state}" } + mute_video(state) + mute_audio(state) + end + # Adds mute states compatible with projectors - def mute(state : Bool = true) + def mute_video(state : Bool = true) state = state ? 1 : 0 do_send(Command::Panel_Mute, state) end - def unmute - mute(false) + def unmute_video + mute_video(false) + end + + # Emulate mute + def mute_audio(val : Bool = true) + if val + if !self[:audio_mute]? + self[:audio_mute] = true + self[:previous_volume] = self[:volume].as_i? || 50 + volume(0) + end + else + unmute_audio + end + end + + def unmute_audio + if self[:audio_mute]? + self[:audio_mute] = false + volume(self[:previous_volume].as_i? || 50) + end end # check software version @@ -154,26 +187,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(Command::Input, Inputs.parse(input).value, **options) end - # Emulate mute - def mute_audio(val : Bool = true) - if val - if !self[:audio_mute]? - self[:audio_mute] = true - self[:previous_volume] = self[:volume].as_i? || 50 - volume(0) - end - else - unmute_audio - end - end - - def unmute_audio - if self[:audio_mute]? - self[:audio_mute] = false - volume(self[:previous_volume].as_i? || 50) - end - end - enum SpeakerModes Internal = 0 External = 1 From 140b6094001b33c8cedbbd452a47c587c34cedd4 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 Jul 2020 15:44:45 +0100 Subject: [PATCH 47/95] fix(mdc_protocol): implement Switchable interface --- drivers/samsung/displays/mdc_protocol.cr | 50 ++++++++++++------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index d4e19047f68..b70c6326bf7 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -1,5 +1,6 @@ require "driver/interface/powerable" require "driver/interface/muteable" +require "driver/interface/switchable" module Samsung; end @@ -7,6 +8,27 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver include Interface::Powerable include Interface::Muteable + enum Inputs + Vga = 0x14 # pc in manual + Dvi = 0x18 + Dvi_Video = 0x1F + Hdmi = 0x21 + Hdmi_Pc = 0x22 + Hdmi2 = 0x23 + Hdmi2_Pc = 0x24 + Hdmi3 = 0x31 + Hdmi3_Pc = 0x32 + Hdmi4 = 0x33 + Hdmi4_Pc = 0x34 + Display_Port = 0x25 + Dtv = 0x40 + Media = 0x60 + Widi = 0x61 + Magic_Info = 0x20 + Whiteboard = 0x64 + end + include PlaceOS::Driver::Interface::InputSelection(Inputs) + # Discovery Information tcp_port 1515 descriptive_name "Samsung MD, DM & QM Series LCD" @@ -88,7 +110,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver if !power # Blank the screen before turning off panel if required # required by some video walls where screens are chained - switch_to(@blank.as(String)) if @blank && self[:power]? + switch_to(Inputs.parse(@blank.as(String))) if @blank && self[:power]? do_send(Command::Panel_Mute, 1) elsif !@rs232 && !self[:connected]? && broadcast wake_device(broadcast.as(String)) @@ -161,30 +183,10 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(Command::Serial_Number) end - enum Inputs - Vga = 0x14 # pc in manual - Dvi = 0x18 - Dvi_Video = 0x1F - Hdmi = 0x21 - Hdmi_Pc = 0x22 - Hdmi2 = 0x23 - Hdmi2_Pc = 0x24 - Hdmi3 = 0x31 - Hdmi3_Pc = 0x32 - Hdmi4 = 0x33 - Hdmi4_Pc = 0x34 - Display_Port = 0x25 - Dtv = 0x40 - Media = 0x60 - Widi = 0x61 - Magic_Info = 0x20 - Whiteboard = 0x64 - end - - def switch_to(input : String, **options) + def switch_to(input : Input, **options) self[:input_stable] = false self[:input_target] = input - do_send(Command::Input, Inputs.parse(input).value, **options) + do_send(Command::Input, input.value, **options) end enum SpeakerModes @@ -312,7 +314,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # screen split is active. Ignore any input forcing when on. unless self[:screen_split] self[:input_stable] = self[:input] == self[:input_target] - switch_to(self[:input_target].as_s) unless self[:input_stable] + switch_to(Inputs.parse(self[:input_target].as_s)) unless self[:input_stable] end when .speaker? self[:speaker] = SpeakerModes.from_value(value).to_s From fd77d80a33b251a24702acb6d53a910e99e689dd Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 Jul 2020 15:49:49 +0100 Subject: [PATCH 48/95] fix(mdc_protocol): initiliase previous volume --- drivers/samsung/displays/mdc_protocol.cr | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index b70c6326bf7..d1ee233ad2f 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -71,6 +71,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:volume_min] = 0 self[:volume_max] = 100 + self[:previous_volume] = 50 # Meta data for inquiring interfaces self[:type] = :lcd @@ -159,7 +160,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver if val if !self[:audio_mute]? self[:audio_mute] = true - self[:previous_volume] = self[:volume].as_i? || 50 + self[:previous_volume] = self[:volume].as_i volume(0) end else @@ -170,7 +171,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def unmute_audio if self[:audio_mute]? self[:audio_mute] = false - volume(self[:previous_volume].as_i? || 50) + volume(self[:previous_volume].as_i) end end From 45ae2c62ff0dfa22866f860b26c4021540601aa4 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 Jul 2020 16:01:17 +0100 Subject: [PATCH 49/95] fix(mdc_protocol): use setting? instead of setting --- drivers/samsung/displays/mdc_protocol.cr | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index d1ee233ad2f..dc488a68cc7 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -47,8 +47,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver default_settings({ display_id: 0, - rs232_control: false, - blanking_input: nil, + rs232_control: false }) @id : Int32 = 0 @@ -83,7 +82,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def on_update @id = setting(Int32, :display_id) @rs232 = setting(Bool, :rs232_control) - @blank = setting(String?, :blanking_input) + @blank = setting?(String, :blanking_input) end def connected @@ -260,7 +259,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def do_device_config logger.debug { "Syncronising device state with settings" } DEVICE_SETTINGS.each do |name| - value = setting(Int32, name) + value = setting?(Int32, name) # TODO: find out if these are equivalent # __send__(name, value) unless value.nil? do_send(Command.parse(value.to_s), value) unless value.nil? From b28d7340fcbdc7c39ac69ccd167123ab90e78a50 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 6 Aug 2020 10:31:26 +0100 Subject: [PATCH 50/95] feat(samsung): add Security annotation to methods --- drivers/samsung/displays/mdc_protocol.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index dc488a68cc7..25d1fdc6d89 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -236,6 +236,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Macro to define methods from the array above {% for name in METHODS %} + @[Security(Level::Administrator)] def {{name.id}}(val : Int32, **options) val = val.clamp(0, 100) do_send(Command.parse({{name.id.stringify}}), val, **options) From dfbbee7565707a488ff29246c92c40e4d087e783 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 6 Aug 2020 11:27:13 +0100 Subject: [PATCH 51/95] refactor(mdc_protocol): remove init_tokenizer method --- drivers/samsung/displays/mdc_protocol.cr | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 25d1fdc6d89..1cf0afb40cb 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -54,18 +54,15 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver @rs232 : Bool = false @blank : String? - # TODO: figure out how to define indicator \xAA - def init_tokenizer - @buffer = Tokenizer.new do |io| + def on_load + # TODO: figure out how to define indicator \xAA + transport.tokenizer = Tokenizer.new do |io| bytes = io.peek logger.debug { "Received: #{bytes}" } # [header, command, id, data.size, [data], checksum] bytes[3].to_i + 5 end - end - def on_load - transport.tokenizer = init_tokenizer on_update self[:volume_min] = 0 From 06182fcd9966e2e93d2423888d8ef4fe243d781d Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 6 Aug 2020 11:56:37 +0100 Subject: [PATCH 52/95] refactor(mdc_protocol): use instance variables --- drivers/samsung/displays/mdc_protocol.cr | 33 ++++++++++-------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 1cf0afb40cb..fdf170615e2 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -53,6 +53,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver @id : Int32 = 0 @rs232 : Bool = false @blank : String? + @previous_volume : Int32 = 50 + # Meta data for inquiring interfaces + @input_stable : Bool = true + @input_target : Input = Input::Hdmi + @power_stable : Bool = true def on_load # TODO: figure out how to define indicator \xAA @@ -64,16 +69,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end on_update - - self[:volume_min] = 0 - self[:volume_max] = 100 - self[:previous_volume] = 50 - - # Meta data for inquiring interfaces - self[:type] = :lcd - self[:input_stable] = true - self[:input_target] ||= :hdmi - self[:power_stable] = true end def on_update @@ -102,7 +97,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # TODO: check type for broadcast is correct def power(power : Bool, broadcast : String? = nil) self[:power_target] = power - self[:power_stable] = false + @power_stable = false if !power # Blank the screen before turning off panel if required @@ -156,7 +151,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver if val if !self[:audio_mute]? self[:audio_mute] = true - self[:previous_volume] = self[:volume].as_i + @previous_volume = self[:volume].as_i volume(0) end else @@ -167,7 +162,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def unmute_audio if self[:audio_mute]? self[:audio_mute] = false - volume(self[:previous_volume].as_i) + volume(@previous_volume) end end @@ -181,8 +176,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def switch_to(input : Input, **options) - self[:input_stable] = false - self[:input_target] = input + @input_stable = false + @input_target = input do_send(Command::Input, input.value, **options) end @@ -311,8 +306,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. unless self[:screen_split] - self[:input_stable] = self[:input] == self[:input_target] - switch_to(Inputs.parse(self[:input_target].as_s)) unless self[:input_stable] + @input_stable = self[:input] == @input_target + switch_to(@input_target) unless @input_stable end when .speaker? self[:speaker] = SpeakerModes.from_value(value).to_s @@ -338,9 +333,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def check_power_state - return if self[:power_stable]? + return if @power_stable if self[:power]? == self[:power_target]? - self[:power_stable] = true + @power_stable = true else power(self[:power_target].as_bool) end From 78805245d72af15df17168d81a40bfc8648b91f7 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 6 Aug 2020 12:18:16 +0100 Subject: [PATCH 53/95] fix(mdc_protocol): use immediate flag for scheduler --- drivers/samsung/displays/mdc_protocol.cr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index fdf170615e2..7d8426fc3d5 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -78,10 +78,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def connected - do_poll do_device_config unless self[:hard_off]? - schedule.every(30.seconds) do + schedule.every(30.seconds, true) do do_poll end end From a70f079c0092993c7ba2c7bf9d585f9c2bca4154 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 6 Aug 2020 17:33:30 +0100 Subject: [PATCH 54/95] fix(mdc_protocol): let compiler infer type --- drivers/samsung/displays/mdc_protocol.cr | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 7d8426fc3d5..3122aa3d7ac 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -52,7 +52,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver @id : Int32 = 0 @rs232 : Bool = false - @blank : String? + @blank : Input? @previous_volume : Int32 = 50 # Meta data for inquiring interfaces @input_stable : Bool = true @@ -74,7 +74,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def on_update @id = setting(Int32, :display_id) @rs232 = setting(Bool, :rs232_control) - @blank = setting?(String, :blanking_input) + if blanking_input = setting?(String, :blanking_input) + @blank = Input.parse?(blanking_input) + end end def connected @@ -101,7 +103,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver if !power # Blank the screen before turning off panel if required # required by some video walls where screens are chained - switch_to(Inputs.parse(@blank.as(String))) if @blank && self[:power]? + if (blanking_input = @blank) && self[:power]? + switch_to(blanking_input) + end do_send(Command::Panel_Mute, 1) elsif !@rs232 && !self[:connected]? && broadcast wake_device(broadcast.as(String)) From 1b3052d0dc194c641c40227990efd2cd57c9eedf Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 6 Aug 2020 17:37:25 +0100 Subject: [PATCH 55/95] fix(mdc_protocol): let compiler infer broadcast type --- drivers/samsung/displays/mdc_protocol.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 3122aa3d7ac..a30b4adfbbf 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -107,8 +107,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver switch_to(blanking_input) end do_send(Command::Panel_Mute, 1) - elsif !@rs232 && !self[:connected]? && broadcast - wake_device(broadcast.as(String)) + elsif !@rs232 && !self[:connected]? && (broadcast_string = broadcast) + wake_device(broadcast_string) else # Power on do_send(Command::Hard_Off, 1) From 7ac3454f0335d7b5adf3ce787860f02200302e86 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 6 Aug 2020 17:49:22 +0100 Subject: [PATCH 56/95] fix(mdc_protocol): define array for method names inline with macro --- drivers/samsung/displays/mdc_protocol.cr | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index a30b4adfbbf..192dcf7f1f7 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -217,20 +217,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end # Display control - METHODS = [ - "volume", - "contrast", - "brightness", - "sharpness", - "colour", - "tint", - "red_gain", - "green_gain", - "blue_gain" - ] - - # Macro to define methods from the array above - {% for name in METHODS %} + {% for name in ["volume", "contrast", "brightness", "sharpness", "colour", "tint", "red_gain", "green_gain", "blue_gain"] %} @[Security(Level::Administrator)] def {{name.id}}(val : Int32, **options) val = val.clamp(0, 100) From a5cecd2b567ccd8f7b15605a755925b4635f9b90 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Aug 2020 10:54:34 +0100 Subject: [PATCH 57/95] feat(mdc_protocol): add logic for message indicator --- drivers/samsung/displays/mdc_protocol.cr | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 192dcf7f1f7..b7f61b94511 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -63,9 +63,12 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # TODO: figure out how to define indicator \xAA transport.tokenizer = Tokenizer.new do |io| bytes = io.peek + # disconnect if the first byte is not 0xAA + disconnect if bytes[0] != 170 logger.debug { "Received: #{bytes}" } # [header, command, id, data.size, [data], checksum] - bytes[3].to_i + 5 + # return 0 if byte.size < 4 to flag that the message is incomplete + bytes.size < 4 ? 0 : bytes[3].to_i + 5 end on_update From feec321d31a1a6908b8456b6c9794c7995bbcd9f Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Aug 2020 10:58:31 +0100 Subject: [PATCH 58/95] chore(mdc): remove completed todo --- drivers/samsung/displays/mdc_protocol.cr | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index b7f61b94511..5eeef9b4a1c 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -60,7 +60,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver @power_stable : Bool = true def on_load - # TODO: figure out how to define indicator \xAA transport.tokenizer = Tokenizer.new do |io| bytes = io.peek # disconnect if the first byte is not 0xAA From 3fd04f5b5b8e714fbd2f8ec20f64743ef9a28e69 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Aug 2020 11:51:22 +0100 Subject: [PATCH 59/95] fix(mdc): define indicator as constant --- drivers/samsung/displays/mdc_protocol.cr | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 5eeef9b4a1c..b8b7a1cfe3a 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -8,6 +8,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver include Interface::Powerable include Interface::Muteable + INDICATOR = 0xAA_u8 + enum Inputs Vga = 0x14 # pc in manual Dvi = 0x18 @@ -62,11 +64,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def on_load transport.tokenizer = Tokenizer.new do |io| bytes = io.peek - # disconnect if the first byte is not 0xAA - disconnect if bytes[0] != 170 + # Ensure message indicator is well-formed + disconnect unless bytes.first == INDICATOR logger.debug { "Received: #{bytes}" } # [header, command, id, data.size, [data], checksum] - # return 0 if byte.size < 4 to flag that the message is incomplete + # return 0 if the message is incomplete bytes.size < 4 ? 0 : bytes[3].to_i + 5 end From a293bd0d83758aacfa1e3b5df3f7d28bdc71579c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Aug 2020 12:03:11 +0100 Subject: [PATCH 60/95] fix(mdc): use indicator constant when building command Co-authored-by: Caspian Baska --- drivers/samsung/displays/mdc_protocol.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index b8b7a1cfe3a..2312a3b2028 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -369,7 +369,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def build(id : Int32, data : Bytes) : Bytes Bytes.new(data.size + 5).tap do |bytes| - bytes[0] = 0xAA_u8 # Header + bytes[0] = INDICATOR # Header bytes[1] = self.to_u8 # Command bytes[2] = id.to_u8 # Display ID bytes[3] = data.size.to_u8 # Data size From 96e296e30c9aa1012c59c40957803cb81498b872 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 4 Sep 2020 18:21:47 +0100 Subject: [PATCH 61/95] chore(mdc): clean up do_device_config --- drivers/samsung/displays/mdc_protocol.cr | 45 ++++++++++++------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 2312a3b2028..f4cbbcfc6c7 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -220,8 +220,18 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(Command::Auto_Power, state, **options) end - # Display control - {% for name in ["volume", "contrast", "brightness", "sharpness", "colour", "tint", "red_gain", "green_gain", "blue_gain"] %} + INT_DEVICE_SETTINGS = [ + "volume", + "contrast", + "brightness", + "sharpness", + "colour", + "tint", + "red_gain", + "green_gain", + "blue_gain" + ] + {% for name in INT_DEVICE_SETTINGS %} @[Security(Level::Administrator)] def {{name.id}}(val : Int32, **options) val = val.clamp(0, 100) @@ -229,28 +239,19 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end {% end %} - DEVICE_SETTINGS = [ - :network_standby, - :auto_off_timer, - :auto_power, - :contrast, - :brightness, - :sharpness, - :colour, - :tint, - :red_gain, - :green_gain, - :blue_gain, -] - def do_device_config logger.debug { "Syncronising device state with settings" } - DEVICE_SETTINGS.each do |name| - value = setting?(Int32, name) - # TODO: find out if these are equivalent - # __send__(name, value) unless value.nil? - do_send(Command.parse(value.to_s), value) unless value.nil? - end + + # Boolean device settings + {% for name in ["network_standby", "auto_off_timer", "auto_power"] %} + %value = setting?(Bool, {{name.id}}) + {{name.id}}(%value) unless %value.nil? + {% end %} + + {% for name in INT_DEVICE_SETTINGS %} + %value = setting?(Int32, {{name.id}}) + {{name.id}}(%value) unless %value.nil? + {% end %} end enum ResponseStatus From bf41b5ac9b46a46de53e6f138d5e8c4087f48b2f Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 7 Sep 2020 12:04:09 +0100 Subject: [PATCH 62/95] fix(mdc): stringify setting names --- drivers/samsung/displays/mdc_protocol.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index f4cbbcfc6c7..4efea1b349f 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -244,12 +244,12 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Boolean device settings {% for name in ["network_standby", "auto_off_timer", "auto_power"] %} - %value = setting?(Bool, {{name.id}}) + %value = setting?(Bool, {{name.id.stringify}}) {{name.id}}(%value) unless %value.nil? {% end %} {% for name in INT_DEVICE_SETTINGS %} - %value = setting?(Int32, {{name.id}}) + %value = setting?(Int32, {{name.id.stringify}}) {{name.id}}(%value) unless %value.nil? {% end %} end From eaefef6e7ffa3ac8c730311e5e008a775adbf7e4 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 14 Sep 2020 17:34:28 +0100 Subject: [PATCH 63/95] fix(mdc): update require lines --- drivers/samsung/displays/mdc_protocol.cr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 4efea1b349f..3340c4b25a3 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -1,9 +1,9 @@ -require "driver/interface/powerable" -require "driver/interface/muteable" -require "driver/interface/switchable" - module Samsung; end +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/switchable" + class Samsung::Displays::MDCProtocol < PlaceOS::Driver include Interface::Powerable include Interface::Muteable From ad373785f29e399586a1d5613e9d387419fa3855 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 14 Sep 2020 17:44:01 +0100 Subject: [PATCH 64/95] fix(mdc): group device settings --- drivers/samsung/displays/mdc_protocol.cr | 72 +++++++++--------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 3340c4b25a3..43aa7b2263b 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -202,54 +202,38 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver power? unless self[:hard_off]? end - # Enable power on (without WOL) - def network_standby(enable : Bool, **options) - state = enable ? 1 : 0 - do_send(Command::Net_Standby, state, **options) - end - - # Eco auto power off timer - def auto_off_timer(enable : Bool, **options) - state = enable ? 1 : 0 - do_send(Command::Eco_Solution, Bytes[0x81, state], **options) - end - - # Device auto power control (presumably signal based?) - def auto_power(enable : Bool, **options) - state = enable ? 1 : 0 - do_send(Command::Auto_Power, state, **options) - end - - INT_DEVICE_SETTINGS = [ - "volume", - "contrast", - "brightness", - "sharpness", - "colour", - "tint", - "red_gain", - "green_gain", - "blue_gain" - ] - {% for name in INT_DEVICE_SETTINGS %} + DEVICE_SETTINGS = { + network_standby: Bool, + auto_off_timer: Bool, + auto_power: Bool, + volume: Int32, + contrast: Int32, + brightness: Int32, + sharpness: Int32, + colour: Int32, + tint: Int32, + red_gain: Int32, + green_gain: Int32, + blue_gain: Int32 +} + {% for name, kind in DEVICE_SETTINGS %} @[Security(Level::Administrator)] - def {{name.id}}(val : Int32, **options) - val = val.clamp(0, 100) - do_send(Command.parse({{name.id.stringify}}), val, **options) + def {{name.id}}(value : {{kind}}, **options) + {% if kind.resolve == Bool %} + state = value ? 1 : 0 + data = {{name.id.stringify}} == "auto_off_timer" ? Bytes[0x81, state] : state + {% elsif kind.resolve == Int32 %} + data = value.clamp(0, 100) + {% end %} + do_send(Command.parse({{name.id.stringify}}), data, **options) end {% end %} def do_device_config logger.debug { "Syncronising device state with settings" } - # Boolean device settings - {% for name in ["network_standby", "auto_off_timer", "auto_power"] %} - %value = setting?(Bool, {{name.id.stringify}}) - {{name.id}}(%value) unless %value.nil? - {% end %} - - {% for name in INT_DEVICE_SETTINGS %} - %value = setting?(Int32, {{name.id.stringify}}) + {% for name, kind in DEVICE_SETTINGS %} + %value = setting?({{kind}}, {{name.id.stringify}}) {{name.id}}(%value) unless %value.nil? {% end %} end @@ -359,9 +343,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver Wall_On = 0x84 # Video wall enabled Wall_User = 0x89 # Video wall user control Speaker = 0x68 - Net_Standby = 0xB5 # Keep NIC active in standby - Eco_Solution = 0xE6 # Eco options (auto power off) - Auto_Power = 0x33 + Network_Standby = 0xB5 # Keep NIC active in standby, enable power on (without WOL) + Auto_Off_Timer = 0xE6 # Eco options (auto power off) + Auto_Power = 0x33 # Device auto power control (presumably signal based?) Screen_Split = 0xB2 # Tri / quad split (larger panels only) Software_Version = 0x0E Serial_Number = 0x0B From eb65da9b6e52d05dd03988c6d580bac2294518cf Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 14 Sep 2020 17:45:40 +0100 Subject: [PATCH 65/95] chore(mdc): fix indentation --- drivers/samsung/displays/mdc_protocol.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 43aa7b2263b..c7612313942 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -215,7 +215,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver red_gain: Int32, green_gain: Int32, blue_gain: Int32 -} + } {% for name, kind in DEVICE_SETTINGS %} @[Security(Level::Administrator)] def {{name.id}}(value : {{kind}}, **options) From bc115c25fc63ecb1049517a7eadac59a4c260ea2 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 16 Sep 2020 14:00:48 +0100 Subject: [PATCH 66/95] fix(mdc): return power after power query --- drivers/samsung/displays/mdc_protocol.cr | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index c7612313942..3abab3ec8fe 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -125,11 +125,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(Command::Hard_Off, 0) end - # TODO: figure out what block is for - # def power?(**options, &block) def power?(**options) - # options[:emit] = block unless block.nil? - do_send(Command::Panel_Mute, Bytes.empty, **options) + do_send(Command::Panel_Mute, Bytes.empty, **options).get + self[:power] end # Mutes audio + video From 630d1213b538f2702af767969d2bd9a33d6597d7 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 09:06:01 +0100 Subject: [PATCH 67/95] chore(mdc): remove broadcast --- drivers/samsung/displays/mdc_protocol.cr | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 3abab3ec8fe..32df80f16d2 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -96,11 +96,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver schedule.clear end - # As true power off disconnects the server we only want to - # power off the panel. This doesn't work in video walls - # so if a nominal blank input is - # TODO: check type for broadcast is correct - def power(power : Bool, broadcast : String? = nil) + # As true power off disconnects the server we only want to power off the panel + def power(power : Bool) self[:power_target] = power @power_stable = false @@ -111,8 +108,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver switch_to(blanking_input) end do_send(Command::Panel_Mute, 1) - elsif !@rs232 && !self[:connected]? && (broadcast_string = broadcast) - wake_device(broadcast_string) else # Power on do_send(Command::Hard_Off, 1) From 0897720f354be2efd5602baf8fad79e8c02d5faa Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 09:06:38 +0100 Subject: [PATCH 68/95] fix(mdc): remove old ruby code --- drivers/samsung/displays/mdc_protocol.cr | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 32df80f16d2..0b2970a077f 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -362,10 +362,5 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver bytes = command.build(@id, data) logger.debug { "Sending to Samsung: #{bytes.hexstring}" } send(bytes, **options) - # TODO: find out if this is necessary - # send(array_to_str(data), options).catch do |reason| - # disconnect - # thread.reject(reason) - # end end end From 59fe9aa630abac79aa710ade043602e70706652c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 09:11:11 +0100 Subject: [PATCH 69/95] fix(mdc): set input target as nil initially --- drivers/samsung/displays/mdc_protocol.cr | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 0b2970a077f..42aa7513257 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -56,9 +56,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver @rs232 : Bool = false @blank : Input? @previous_volume : Int32 = 50 - # Meta data for inquiring interfaces @input_stable : Bool = true - @input_target : Input = Input::Hdmi + @input_target : Input? = nil @power_stable : Bool = true def on_load @@ -177,7 +176,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def switch_to(input : Input, **options) @input_stable = false - @input_target = input + input_target = @input_target + @input_target = input_target if input_target do_send(Command::Input, input.value, **options) end @@ -278,8 +278,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. unless self[:screen_split] - @input_stable = self[:input] == @input_target - switch_to(@input_target) unless @input_stable + input_target = @input_target + @input_stable = self[:input] == input_target + if input_target + switch_to(input_target) unless @input_stable + end end when .speaker? self[:speaker] = SpeakerModes.from_value(value).to_s From 3781786578535012b59047db762ceec920412bb5 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 11:17:06 +0100 Subject: [PATCH 70/95] fix(mdc): set power properly --- drivers/samsung/displays/mdc_protocol.cr | 6 ++++-- drivers/samsung/displays/mdc_protocol_spec.cr | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 42aa7513257..2f6a3acfaee 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -259,8 +259,10 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .ack? case command when .status? - self[:hard_off] = values[0] == 0 - self[:power] = false if self[:hard_off] + if values[0] == 0 + self[:hard_off] = true + self[:power] = false + end self[:volume] = values[1] self[:audio_mute] = values[2] == 1 self[:input] = Inputs.from_value(values[3]).to_s diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index 9b19b316507..27b4ec3a220 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -12,8 +12,7 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do # status should_send("\xAA\x00#{id}\x00\x00") responds("\xAA\xFF#{id}\x09A\x00\x01\x06\x00\x14\x00\x00\x00\xFF") - status[:hard_off].should eq(false) - # status[:power].should eq(true) + status[:power].should eq(true) status[:volume].should eq(6) status[:audio_mute].should eq(false) status[:input].should eq("Vga") From 95f64ba271b69c74ebc321d2e158dda0b42fcaf7 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 11:20:51 +0100 Subject: [PATCH 71/95] fix(mdc): create variable for hard_off --- drivers/samsung/displays/mdc_protocol.cr | 7 +++---- drivers/samsung/displays/mdc_protocol_spec.cr | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 2f6a3acfaee..f75f76c219f 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -259,10 +259,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .ack? case command when .status? - if values[0] == 0 - self[:hard_off] = true - self[:power] = false - end + hard_off = values[0] == 0 + self[:hard_off] = hard_off + self[:power] = false if hard_off self[:volume] = values[1] self[:audio_mute] = values[2] == 1 self[:input] = Inputs.from_value(values[3]).to_s diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index 27b4ec3a220..6a5adda7086 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -12,6 +12,7 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do # status should_send("\xAA\x00#{id}\x00\x00") responds("\xAA\xFF#{id}\x09A\x00\x01\x06\x00\x14\x00\x00\x00\xFF") + status[:hard_off].should eq(false) status[:power].should eq(true) status[:volume].should eq(6) status[:audio_mute].should eq(false) From a2f498f067e8d138988181e313662c4d3903f9a2 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 13:36:14 +0100 Subject: [PATCH 72/95] fix(mdc): assign variables when checking self values --- drivers/samsung/displays/mdc_protocol.cr | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index f75f76c219f..57718291ffa 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -115,7 +115,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def hard_off - do_send(Command::Panel_Mute, 0) if self[:power]? + do_send(Command::Panel_Mute, 0) if power = self[:power]? do_send(Command::Hard_Off, 0) end @@ -159,7 +159,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def unmute_audio - if self[:audio_mute]? + if audio_mute = self[:audio_mute]? self[:audio_mute] = false volume(@previous_volume) end @@ -191,8 +191,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def do_poll + hard_off = self[:hard_off]? do_send(Command::Status, Bytes.empty, priority: 0) - power? unless self[:hard_off]? + power? unless hard_off end DEVICE_SETTINGS = { @@ -278,7 +279,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:input] = Inputs.from_value(value).to_s # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. - unless self[:screen_split] + unless screen_split = self[:screen_split]? input_target = @input_target @input_stable = self[:input] == input_target if input_target @@ -288,8 +289,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .speaker? self[:speaker] = SpeakerModes.from_value(value).to_s when .hard_off? - self[:hard_off] = value == 0 - self[:power] = false if self[:hard_off] + hard_off = value == 0 + unless was_off = self[:hard_off]? + self[:hard_off] = hard_off + self[:power] = false if hard_off + end when .screen_split? self[:screen_split] = value >= 0 when .software_version? From e5386f5f62d498fb4249625c7ed90c1cd8ed6ca9 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 13:43:05 +0100 Subject: [PATCH 73/95] fix(mdc): assign more self values to variables --- drivers/samsung/displays/mdc_protocol.cr | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 57718291ffa..e63efc7cba7 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -83,7 +83,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def connected - do_device_config unless self[:hard_off]? + do_device_config unless hard_off = self[:hard_off]? schedule.every(30.seconds, true) do do_poll @@ -191,9 +191,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def do_poll - hard_off = self[:hard_off]? do_send(Command::Status, Bytes.empty, priority: 0) - power? unless hard_off + power? unless hard_off = self[:hard_off]? end DEVICE_SETTINGS = { @@ -260,8 +259,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .ack? case command when .status? - hard_off = values[0] == 0 - self[:hard_off] = hard_off + self[:hard_off] = hard_off = values[0] == 0 self[:power] = false if hard_off self[:volume] = values[1] self[:audio_mute] = values[2] == 1 @@ -289,9 +287,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .speaker? self[:speaker] = SpeakerModes.from_value(value).to_s when .hard_off? - hard_off = value == 0 unless was_off = self[:hard_off]? - self[:hard_off] = hard_off + self[:hard_off] = hard_off = value == 0 self[:power] = false if hard_off end when .screen_split? From 96923a9bd6ae588978f98d62284763c404843d96 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 13:49:43 +0100 Subject: [PATCH 74/95] chore(mdc): remove some logging --- drivers/samsung/displays/mdc_protocol.cr | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index e63efc7cba7..9202bc93c34 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -130,7 +130,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver index : Int32 | String = 0, layer : MuteLayer = MuteLayer::AudioVideo ) - logger.debug { "requested mute state: #{state}" } mute_video(state) mute_audio(state) end @@ -223,8 +222,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver {% end %} def do_device_config - logger.debug { "Syncronising device state with settings" } - {% for name, kind in DEVICE_SETTINGS %} %value = setting?({{kind}}, {{name.id.stringify}}) {{name.id}}(%value) unless %value.nil? @@ -313,8 +310,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver return if @power_stable if self[:power]? == self[:power_target]? @power_stable = true - else - power(self[:power_target].as_bool) + elsif power_target = self[:power_target] + power(power_target.as_bool) end end From 14468b4df0f578f7efe54ff4d0edd0ff45cf95c7 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 15:22:13 +0100 Subject: [PATCH 75/95] fix(mdc): use Muteable interface properly --- drivers/samsung/displays/mdc_protocol.cr | 36 +++++++++---------- drivers/samsung/displays/mdc_protocol_spec.cr | 25 ++++++++++--- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 9202bc93c34..3f212e60a16 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -83,7 +83,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def connected - do_device_config unless hard_off = self[:hard_off]? + do_device_config unless (hard_off = self[:hard_off]?) && hard_off.as_bool? schedule.every(30.seconds, true) do do_poll @@ -115,7 +115,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def hard_off - do_send(Command::Panel_Mute, 0) if power = self[:power]? + do_send(Command::Panel_Mute, 0) if (power = self[:power]?) && power.as_bool? do_send(Command::Hard_Off, 0) end @@ -124,30 +124,26 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:power] end - # Mutes audio + video + # Mutes both audio/video def mute( state : Bool = true, index : Int32 | String = 0, layer : MuteLayer = MuteLayer::AudioVideo ) - mute_video(state) - mute_audio(state) + mute_video(state) unless layer == MuteLayer::Audio + mute_audio(state) unless layer == MuteLayer::Video end - # Adds mute states compatible with projectors + # Adds video mute state compatible with projectors def mute_video(state : Bool = true) state = state ? 1 : 0 do_send(Command::Panel_Mute, state) end - def unmute_video - mute_video(false) - end - - # Emulate mute - def mute_audio(val : Bool = true) - if val - if !self[:audio_mute]? + # Emulate audio mute + def mute_audio(state : Bool = true) + if state + unless (audio_mute = self[:audio_mute]?) && audio_mute.as_bool? self[:audio_mute] = true @previous_volume = self[:volume].as_i volume(0) @@ -158,7 +154,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def unmute_audio - if audio_mute = self[:audio_mute]? + if (audio_mute = self[:audio_mute]?) && audio_mute.as_bool? self[:audio_mute] = false volume(@previous_volume) end @@ -191,7 +187,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def do_poll do_send(Command::Status, Bytes.empty, priority: 0) - power? unless hard_off = self[:hard_off]? + power? unless (hard_off = self[:hard_off]?) && hard_off.as_bool? end DEVICE_SETTINGS = { @@ -274,9 +270,9 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:input] = Inputs.from_value(value).to_s # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. - unless screen_split = self[:screen_split]? + unless (screen_split = self[:screen_split]?) && screen_split.as_bool? input_target = @input_target - @input_stable = self[:input] == input_target + @input_stable = self[:input]? == input_target if input_target switch_to(input_target) unless @input_stable end @@ -284,7 +280,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .speaker? self[:speaker] = SpeakerModes.from_value(value).to_s when .hard_off? - unless was_off = self[:hard_off]? + unless (was_off = self[:hard_off]?) && was_off.as_bool? self[:hard_off] = hard_off = value == 0 self[:power] = false if hard_off end @@ -310,7 +306,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver return if @power_stable if self[:power]? == self[:power_target]? @power_stable = true - elsif power_target = self[:power_target] + elsif power_target = self[:power_target]? power(power_target.as_bool) end end diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index 6a5adda7086..ffdea2407c8 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -1,4 +1,5 @@ # [header, command, id, data.size, [data], checksum] +require "placeos-driver/interface/muteable" DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do id = "\x00" @@ -18,17 +19,31 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do status[:audio_mute].should eq(false) status[:input].should eq("Vga") - exec(:volume, 24) - should_send("\xAA\x12#{id}\x01\x18\x12") - responds("\xAA\xFF\x00\x03A\x12\x18\xFF") - status[:volume].should eq(24) + exec(:volume, 6) + should_send("\xAA\x12#{id}\x01\x06\x12") + responds("\xAA\xFF\x00\x03A\x12\x06\xFF") + status[:volume].should eq(6) status[:audio_mute].should eq(false) - exec(:mute, true) + exec(:mute) + # Video mute + should_send("\xAA\xF9\x00\x01\x01\xF9") responds("\xAA\xFF#{id}\x03A\xF9\x01\xFF") status[:power].should eq(false) + # Audio mute + should_send("\xAA\x12#{id}\x01\x00\x12") + responds("\xAA\xFF\x00\x03A\x12\x00\xFF") + status[:audio_mute].should eq(true) + status[:volume].should eq(0) exec(:unmute) + # Video unmute + should_send("\xAA\xF9\x00\x01\x00\xF9") responds("\xAA\xFF#{id}\x03A\xF9\x00\xFF") status[:power].should eq(true) + # Audio unmute + should_send("\xAA\x12#{id}\x01\x06\x12") + responds("\xAA\xFF\x00\x03A\x12\x06\xFF") + status[:audio_mute].should eq(false) + status[:volume].should eq(6) end From e746aa295f067e5d87aefe4aaff498ec1a9ad10b Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 15:25:30 +0100 Subject: [PATCH 76/95] fix(mdc): add spec for changing volume --- drivers/samsung/displays/mdc_protocol_spec.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index ffdea2407c8..b5502c7a20a 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -19,6 +19,12 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do status[:audio_mute].should eq(false) status[:input].should eq("Vga") + exec(:volume, 24) + should_send("\xAA\x12#{id}\x01\x19\x12") + responds("\xAA\xFF\x00\x03A\x12\x18\xFF") + status[:volume].should eq(24) + status[:audio_mute].should eq(false) + exec(:volume, 6) should_send("\xAA\x12#{id}\x01\x06\x12") responds("\xAA\xFF\x00\x03A\x12\x06\xFF") From 1480d35493501f97efdfb58ceafb9fe4219ac9b0 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 22 Sep 2020 15:27:26 +0100 Subject: [PATCH 77/95] fix(mdc): use correct hex value for spec --- drivers/samsung/displays/mdc_protocol_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index b5502c7a20a..a7aa168e865 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -20,7 +20,7 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do status[:input].should eq("Vga") exec(:volume, 24) - should_send("\xAA\x12#{id}\x01\x19\x12") + should_send("\xAA\x12#{id}\x01\x18\x12") responds("\xAA\xFF\x00\x03A\x12\x18\xFF") status[:volume].should eq(24) status[:audio_mute].should eq(false) From 320ff1add78e47c125fa522d8a3c4d2346750c65 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 29 Sep 2020 12:25:16 +0100 Subject: [PATCH 78/95] fix(mdc): use shorter way of checking self values --- drivers/samsung/displays/mdc_protocol.cr | 20 +++++++++---------- drivers/samsung/displays/mdc_protocol_spec.cr | 3 +-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 3f212e60a16..efb6574eae7 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -83,7 +83,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def connected - do_device_config unless (hard_off = self[:hard_off]?) && hard_off.as_bool? + do_device_config unless self[:hard_off]?.try &.as_bool? schedule.every(30.seconds, true) do do_poll @@ -115,7 +115,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def hard_off - do_send(Command::Panel_Mute, 0) if (power = self[:power]?) && power.as_bool? + do_send(Command::Panel_Mute, 0) if self[:power]?.try &.as_bool? do_send(Command::Hard_Off, 0) end @@ -143,7 +143,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Emulate audio mute def mute_audio(state : Bool = true) if state - unless (audio_mute = self[:audio_mute]?) && audio_mute.as_bool? + unless self[:audio_mute]?.try &.as_bool? self[:audio_mute] = true @previous_volume = self[:volume].as_i volume(0) @@ -154,7 +154,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def unmute_audio - if (audio_mute = self[:audio_mute]?) && audio_mute.as_bool? + if self[:audio_mute]?.try &.as_bool? self[:audio_mute] = false volume(@previous_volume) end @@ -187,7 +187,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def do_poll do_send(Command::Status, Bytes.empty, priority: 0) - power? unless (hard_off = self[:hard_off]?) && hard_off.as_bool? + power? unless self[:hard_off]?.try &.as_bool? end DEVICE_SETTINGS = { @@ -270,7 +270,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:input] = Inputs.from_value(value).to_s # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. - unless (screen_split = self[:screen_split]?) && screen_split.as_bool? + unless self[:screen_split]?.try &.as_bool? input_target = @input_target @input_stable = self[:input]? == input_target if input_target @@ -280,7 +280,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .speaker? self[:speaker] = SpeakerModes.from_value(value).to_s when .hard_off? - unless (was_off = self[:hard_off]?) && was_off.as_bool? + unless self[:hard_off]?.try &.as_bool? self[:hard_off] = hard_off = value == 0 self[:power] = false if hard_off end @@ -298,7 +298,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .nak? task.try &.abort("Samsung responded with NAK: #{hex}") else - task.try &.abort("Samsung aborted with: #{hex}") + task.try &.retry end end @@ -306,8 +306,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver return if @power_stable if self[:power]? == self[:power_target]? @power_stable = true - elsif power_target = self[:power_target]? - power(power_target.as_bool) + else + power(self[:power_target].as_bool) end end diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index a7aa168e865..586da98b2a8 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -1,5 +1,4 @@ - # [header, command, id, data.size, [data], checksum] -require "placeos-driver/interface/muteable" +# [header, command, id, data.size, [data], checksum] DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do id = "\x00" From cd26260295f601004ee51fa1ea624287c9cd5e30 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 29 Sep 2020 12:34:37 +0100 Subject: [PATCH 79/95] fix(mdc): use as_bool instead of as_bool? --- drivers/samsung/displays/mdc_protocol.cr | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index efb6574eae7..e5df3974ef7 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -83,7 +83,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def connected - do_device_config unless self[:hard_off]?.try &.as_bool? + do_device_config unless self[:hard_off]?.try &.as_bool schedule.every(30.seconds, true) do do_poll @@ -115,7 +115,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def hard_off - do_send(Command::Panel_Mute, 0) if self[:power]?.try &.as_bool? + do_send(Command::Panel_Mute, 0) if self[:power]?.try &.as_bool do_send(Command::Hard_Off, 0) end @@ -143,7 +143,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Emulate audio mute def mute_audio(state : Bool = true) if state - unless self[:audio_mute]?.try &.as_bool? + unless self[:audio_mute]?.try &.as_bool self[:audio_mute] = true @previous_volume = self[:volume].as_i volume(0) @@ -154,7 +154,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def unmute_audio - if self[:audio_mute]?.try &.as_bool? + if self[:audio_mute]?.try &.as_bool self[:audio_mute] = false volume(@previous_volume) end @@ -187,7 +187,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def do_poll do_send(Command::Status, Bytes.empty, priority: 0) - power? unless self[:hard_off]?.try &.as_bool? + power? unless self[:hard_off]?.try &.as_bool end DEVICE_SETTINGS = { @@ -270,7 +270,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:input] = Inputs.from_value(value).to_s # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. - unless self[:screen_split]?.try &.as_bool? + unless self[:screen_split]?.try &.as_bool input_target = @input_target @input_stable = self[:input]? == input_target if input_target @@ -280,7 +280,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .speaker? self[:speaker] = SpeakerModes.from_value(value).to_s when .hard_off? - unless self[:hard_off]?.try &.as_bool? + unless self[:hard_off]?.try &.as_bool self[:hard_off] = hard_off = value == 0 self[:power] = false if hard_off end From 5bb7ceb978df3e50eaf883caaad75c297ba7c010 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 29 Sep 2020 12:38:46 +0100 Subject: [PATCH 80/95] fix(mdc): use strict parse instead of parse? Co-authored-by: Kim Burgess --- drivers/samsung/displays/mdc_protocol.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index e5df3974ef7..cfa675ef64f 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -77,7 +77,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def on_update @id = setting(Int32, :display_id) @rs232 = setting(Bool, :rs232_control) - if blanking_input = setting?(String, :blanking_input) + @blank = setting?(String, :blanking_input).try &->Input.parse(String) @blank = Input.parse?(blanking_input) end end From 0ccbe367c548ef462393efc6c1785b427e38ee7d Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 29 Sep 2020 12:42:31 +0100 Subject: [PATCH 81/95] fix(mdc): correctly use commit github suggestion --- drivers/samsung/displays/mdc_protocol.cr | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index cfa675ef64f..8a715238676 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -1,5 +1,3 @@ -module Samsung; end - require "placeos-driver/interface/powerable" require "placeos-driver/interface/muteable" require "placeos-driver/interface/switchable" @@ -78,8 +76,6 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver @id = setting(Int32, :display_id) @rs232 = setting(Bool, :rs232_control) @blank = setting?(String, :blanking_input).try &->Input.parse(String) - @blank = Input.parse?(blanking_input) - end end def connected From 7cb6ad532642dbabc5846469d85b863a0bfc1acc Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 29 Sep 2020 12:47:13 +0100 Subject: [PATCH 82/95] fix(mdc): use if statement for checking MuteLayer --- drivers/samsung/displays/mdc_protocol.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 8a715238676..73d93bd7cce 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -126,8 +126,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver index : Int32 | String = 0, layer : MuteLayer = MuteLayer::AudioVideo ) - mute_video(state) unless layer == MuteLayer::Audio - mute_audio(state) unless layer == MuteLayer::Video + mute_video(state) if layer == MuteLayer::Video || layer == MuteLayer::AudioVideo + mute_audio(state) if layer == MuteLayer::Audio || layer == MuteLayer::AudioVideo end # Adds video mute state compatible with projectors From dc618a5f41e18010880efdadb1b57dfed3afd563 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 29 Sep 2020 14:13:20 +0100 Subject: [PATCH 83/95] chore(mdc): use CamelCase --- drivers/samsung/displays/mdc_protocol.cr | 112 +++++++++++------------ 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 73d93bd7cce..4112b05feef 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -9,23 +9,23 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver INDICATOR = 0xAA_u8 enum Inputs - Vga = 0x14 # pc in manual - Dvi = 0x18 - Dvi_Video = 0x1F - Hdmi = 0x21 - Hdmi_Pc = 0x22 - Hdmi2 = 0x23 - Hdmi2_Pc = 0x24 - Hdmi3 = 0x31 - Hdmi3_Pc = 0x32 - Hdmi4 = 0x33 - Hdmi4_Pc = 0x34 - Display_Port = 0x25 - Dtv = 0x40 - Media = 0x60 - Widi = 0x61 - Magic_Info = 0x20 - Whiteboard = 0x64 + Vga = 0x14 # pc in manual + Dvi = 0x18 + DviVideo = 0x1F + Hdmi = 0x21 + HdmiPc = 0x22 + Hdmi2 = 0x23 + Hdmi2Pc = 0x24 + Hdmi3 = 0x31 + Hdmi3Pc = 0x32 + Hdmi4 = 0x33 + Hdmi4Pc = 0x34 + DisplayPort = 0x25 + Dtv = 0x40 + Media = 0x60 + Widi = 0x61 + MagicInfo = 0x20 + Whiteboard = 0x64 end include PlaceOS::Driver::Interface::InputSelection(Inputs) @@ -102,21 +102,21 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver if (blanking_input = @blank) && self[:power]? switch_to(blanking_input) end - do_send(Command::Panel_Mute, 1) + do_send(Command::PanelMute, 1) else # Power on - do_send(Command::Hard_Off, 1) - do_send(Command::Panel_Mute, 0) + do_send(Command::HardOff, 1) + do_send(Command::PanelMute, 0) end end def hard_off - do_send(Command::Panel_Mute, 0) if self[:power]?.try &.as_bool - do_send(Command::Hard_Off, 0) + do_send(Command::PanelMute, 0) if self[:power]?.try &.as_bool + do_send(Command::HardOff, 0) end def power?(**options) - do_send(Command::Panel_Mute, Bytes.empty, **options).get + do_send(Command::PanelMute, Bytes.empty, **options).get self[:power] end @@ -133,7 +133,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Adds video mute state compatible with projectors def mute_video(state : Bool = true) state = state ? 1 : 0 - do_send(Command::Panel_Mute, state) + do_send(Command::PanelMute, state) end # Emulate audio mute @@ -158,11 +158,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # check software version def software_version? - do_send(Command::Software_Version) + do_send(Command::SoftwareVersion) end def serial_number? - do_send(Command::Serial_Number) + do_send(Command::SerialNumber) end def switch_to(input : Input, **options) @@ -308,36 +308,36 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end enum Command - Status = 0x00 - Hard_Off = 0x11 # Completely powers off - Panel_Mute = 0xF9 # Screen blanking / visual mute - Volume = 0x12 - Contrast = 0x24 - Brightness = 0x25 - Sharpness = 0x26 - Colour = 0x27 - Tint = 0x28 - Red_Gain = 0x29 - Green_Gain = 0x2A - Blue_Gain = 0x2B - Input = 0x14 - Mode = 0x18 - Size = 0x19 - Pip = 0x3C # picture in picture - Auto_Adjust = 0x3D - Wall_Mode = 0x5C # Video wall mode - Safety = 0x5D - Wall_On = 0x84 # Video wall enabled - Wall_User = 0x89 # Video wall user control - Speaker = 0x68 - Network_Standby = 0xB5 # Keep NIC active in standby, enable power on (without WOL) - Auto_Off_Timer = 0xE6 # Eco options (auto power off) - Auto_Power = 0x33 # Device auto power control (presumably signal based?) - Screen_Split = 0xB2 # Tri / quad split (larger panels only) - Software_Version = 0x0E - Serial_Number = 0x0B - Time = 0xA7 - Timer = 0xA4 + Status = 0x00 + HardOff = 0x11 # Completely powers off + PanelMute = 0xF9 # Screen blanking / visual mute + Volume = 0x12 + Contrast = 0x24 + Brightness = 0x25 + Sharpness = 0x26 + Colour = 0x27 + Tint = 0x28 + RedGain = 0x29 + GreenGain = 0x2A + BlueGain = 0x2B + Input = 0x14 + Mode = 0x18 + Size = 0x19 + Pip = 0x3C # picture in picture + AutoAdjust = 0x3D + WallMode = 0x5C # Video wall mode + Safety = 0x5D + WallOn = 0x84 # Video wall enabled + WallUser = 0x89 # Video wall user control + Speaker = 0x68 + NetworkStandby = 0xB5 # Keep NIC active in standby, enable power on (without WOL) + AutoOffTimer = 0xE6 # Eco options (auto power off) + AutoPower = 0x33 # Device auto power control (presumably signal based?) + ScreenSplit = 0xB2 # Tri / quad split (larger panels only) + SoftwareVersion = 0x0E + SerialNumber = 0x0B + Time = 0xA7 + Timer = 0xA4 def build(id : Int32, data : Bytes) : Bytes Bytes.new(data.size + 5).tap do |bytes| From bdbc8ae15158a68f5cbeb89db1bb1f5834c1208c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 6 Oct 2020 10:53:35 +0100 Subject: [PATCH 84/95] fix(samsung): address feedback --- drivers/samsung/displays/mdc_protocol.cr | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 4112b05feef..9626dfa6bc4 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -157,11 +157,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end # check software version - def software_version? + def software_version do_send(Command::SoftwareVersion) end - def serial_number? + def serial_number do_send(Command::SerialNumber) end @@ -172,13 +172,13 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver do_send(Command::Input, input.value, **options) end - enum SpeakerModes + enum SpeakerMode Internal = 0 External = 1 end - def speaker_select(mode : String, **options) - do_send(Command::Speaker, SpeakerModes.parse(mode).value, **options) + def speaker_select(mode : SpeakerMode, **options) + do_send(Command::Speaker, mode.value, **options) end def do_poll @@ -252,7 +252,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:power] = false if hard_off self[:volume] = values[1] self[:audio_mute] = values[2] == 1 - self[:input] = Inputs.from_value(values[3]).to_s + self[:input] = Inputs.from_value(values[3]) check_power_state when .panel_mute? self[:power] = value == 0 @@ -263,7 +263,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .brightness? self[:brightness] = value when .input? - self[:input] = Inputs.from_value(value).to_s + self[:input] = Inputs.from_value(value) # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. unless self[:screen_split]?.try &.as_bool @@ -274,7 +274,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end when .speaker? - self[:speaker] = SpeakerModes.from_value(value).to_s + self[:speaker] = SpeakerMode.from_value(value) when .hard_off? unless self[:hard_off]?.try &.as_bool self[:hard_off] = hard_off = value == 0 From 4edeb5e813740ea35963694a426cf406cce34284 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 6 Oct 2020 12:37:26 +0100 Subject: [PATCH 85/95] chore(mdc): use Input instead of Inputs --- drivers/samsung/displays/mdc_protocol.cr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 9626dfa6bc4..7197c4b8b3c 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -8,7 +8,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver INDICATOR = 0xAA_u8 - enum Inputs + enum Input Vga = 0x14 # pc in manual Dvi = 0x18 DviVideo = 0x1F @@ -27,7 +27,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver MagicInfo = 0x20 Whiteboard = 0x64 end - include PlaceOS::Driver::Interface::InputSelection(Inputs) + include PlaceOS::Driver::Interface::InputSelection(Input) # Discovery Information tcp_port 1515 @@ -252,7 +252,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver self[:power] = false if hard_off self[:volume] = values[1] self[:audio_mute] = values[2] == 1 - self[:input] = Inputs.from_value(values[3]) + self[:input] = Input.from_value(values[3]) check_power_state when .panel_mute? self[:power] = value == 0 @@ -263,7 +263,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .brightness? self[:brightness] = value when .input? - self[:input] = Inputs.from_value(value) + self[:input] = Input.from_value(value) # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. unless self[:screen_split]?.try &.as_bool From 6bbb6ff69dd45f8e2c5bd54b16683b82de32ebee Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 6 Oct 2020 13:17:43 +0100 Subject: [PATCH 86/95] chore(mdc): use state instead of power --- drivers/samsung/displays/mdc_protocol.cr | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 7197c4b8b3c..1bced86a7f2 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -92,21 +92,21 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end # As true power off disconnects the server we only want to power off the panel - def power(power : Bool) + def power(state : Bool) self[:power_target] = power @power_stable = false - if !power + if state + # Power on + do_send(Command::HardOff, 1) + do_send(Command::PanelMute, 0) + else # Blank the screen before turning off panel if required # required by some video walls where screens are chained if (blanking_input = @blank) && self[:power]? switch_to(blanking_input) end do_send(Command::PanelMute, 1) - else - # Power on - do_send(Command::HardOff, 1) - do_send(Command::PanelMute, 0) end end From 0ee5b80719feeb76c0bdec8f8b85be618e42d463 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 6 Oct 2020 13:19:19 +0100 Subject: [PATCH 87/95] fix(mdc): change power_target to instance variable --- drivers/samsung/displays/mdc_protocol.cr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 1bced86a7f2..071c41ce8f4 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -57,6 +57,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver @input_stable : Bool = true @input_target : Input? = nil @power_stable : Bool = true + @power_target : Bool = true def on_load transport.tokenizer = Tokenizer.new do |io| @@ -93,7 +94,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # As true power off disconnects the server we only want to power off the panel def power(state : Bool) - self[:power_target] = power + @power_target = state @power_stable = false if state @@ -300,10 +301,10 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def check_power_state return if @power_stable - if self[:power]? == self[:power_target]? + if self[:power]? == @power_target @power_stable = true else - power(self[:power_target].as_bool) + power(@power_target) end end From 92ef5115851e036402c6174ead16347bb3ba44d1 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 6 Oct 2020 13:57:46 +0100 Subject: [PATCH 88/95] fix(mdc): use shorter method to check MuteLayer --- drivers/samsung/displays/mdc_protocol.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 071c41ce8f4..c631b540649 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -127,8 +127,8 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver index : Int32 | String = 0, layer : MuteLayer = MuteLayer::AudioVideo ) - mute_video(state) if layer == MuteLayer::Video || layer == MuteLayer::AudioVideo - mute_audio(state) if layer == MuteLayer::Audio || layer == MuteLayer::AudioVideo + mute_video(state) if layer.video? || layer.audio_video? + mute_audio(state) if layer.audio? || layer.audio_video? end # Adds video mute state compatible with projectors From 326044a282a2b72815c72fe644a34e739b4e4d34 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 6 Oct 2020 14:12:40 +0100 Subject: [PATCH 89/95] fix(mdc): remove unmute audio --- drivers/samsung/displays/mdc_protocol.cr | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index c631b540649..6c323557495 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -139,20 +139,13 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Emulate audio mute def mute_audio(state : Bool = true) + # Do nothing if already in desired state + return if self[:audio_mute]?.try &.as_bool == state + self[:audio_mute] = state if state - unless self[:audio_mute]?.try &.as_bool - self[:audio_mute] = true - @previous_volume = self[:volume].as_i - volume(0) - end + @previous_volume = self[:volume].as_i + volume(0) else - unmute_audio - end - end - - def unmute_audio - if self[:audio_mute]?.try &.as_bool - self[:audio_mute] = false volume(@previous_volume) end end From cda86f3a7a3c344c4c0071201f81e4e3d17b79a7 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 12 Oct 2020 13:05:41 +0100 Subject: [PATCH 90/95] fix(mdc): set input_target correctly --- drivers/samsung/displays/mdc_protocol.cr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 6c323557495..595e85c442a 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -161,8 +161,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def switch_to(input : Input, **options) @input_stable = false - input_target = @input_target - @input_target = input_target if input_target + @input_target = input do_send(Command::Input, input.value, **options) end From 9b464ea4dd02a4a125601c47d3470b930d2ae233 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 12 Oct 2020 13:06:29 +0100 Subject: [PATCH 91/95] fix(mdc): don't use linebreak in error logging --- drivers/samsung/displays/mdc_protocol.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 595e85c442a..cf856191380 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -228,7 +228,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver # Pop also removes the checksum from the response here if data.pop != checksum - logger.error { "Invalid checksum\nChecksum should be: #{checksum.to_s(16)}" } + logger.error { "Invalid checksum, checksum should be: #{checksum.to_s(16)}" } return task.try &.retry end From bf21a45f5faacdd06bb1c5d7834c31cf62bb998d Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 12 Oct 2020 13:07:55 +0100 Subject: [PATCH 92/95] fix(mdc): make check_power_state method private --- drivers/samsung/displays/mdc_protocol.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index cf856191380..5eb997c152c 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -291,7 +291,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end - def check_power_state + private def check_power_state return if @power_stable if self[:power]? == @power_target @power_stable = true From 886d433be586e9c4c28d9d049937dade3980107c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 12 Oct 2020 13:38:25 +0100 Subject: [PATCH 93/95] fix(mdc): don't map response --- drivers/samsung/displays/mdc_protocol.cr | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 5eb997c152c..be233be4a05 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -221,20 +221,18 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver def received(data, task) hex = data.hexstring logger.debug { "Samsung sent: #{hex}" } - data = data.map(&.to_i).to_a # Calculate checksum of response checksum = data[1..-2].reduce(&.+) - # Pop also removes the checksum from the response here - if data.pop != checksum + if data[-1] != checksum logger.error { "Invalid checksum, checksum should be: #{checksum.to_s(16)}" } return task.try &.retry end status = ResponseStatus.from_value(data[4]) command = Command.from_value(data[5]) - values = data[6..-1] + values = data[6..-2] value = values.first case status From 89f4747c30b29e95d9b7e5292fb48ecde06d1632 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 12 Oct 2020 13:51:08 +0100 Subject: [PATCH 94/95] fix(mdc): set type to UInt8 to reduce casting --- drivers/samsung/displays/mdc_protocol.cr | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index be233be4a05..6338011c3ad 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -50,7 +50,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver rs232_control: false }) - @id : Int32 = 0 + @id : UInt8 = 0 @rs232 : Bool = false @blank : Input? @previous_volume : Int32 = 50 @@ -74,7 +74,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end def on_update - @id = setting(Int32, :display_id) + @id = setting(UInt8, :display_id) @rs232 = setting(Bool, :rs232_control) @blank = setting?(String, :blanking_input).try &->Input.parse(String) end @@ -298,7 +298,7 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver end end - enum Command + enum Command : UInt8 Status = 0x00 HardOff = 0x11 # Completely powers off PanelMute = 0xF9 # Screen blanking / visual mute @@ -330,11 +330,11 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver Time = 0xA7 Timer = 0xA4 - def build(id : Int32, data : Bytes) : Bytes + def build(id : UInt8, data : Bytes) : Bytes Bytes.new(data.size + 5).tap do |bytes| bytes[0] = INDICATOR # Header - bytes[1] = self.to_u8 # Command - bytes[2] = id.to_u8 # Display ID + bytes[1] = self.value # Command + bytes[2] = id # Display ID bytes[3] = data.size.to_u8 # Data size data.each_with_index(4) { |b, i| bytes[i] = b } # Data bytes[-1] = bytes[1..-2].reduce(&.+) # Checksum From 4aec982dbd98736e8d16722646743de738e3afa0 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 13 Oct 2020 12:03:31 +0100 Subject: [PATCH 95/95] feat(mdc): add specs for switch_to and power --- drivers/samsung/displays/mdc_protocol.cr | 10 ++++----- drivers/samsung/displays/mdc_protocol_spec.cr | 21 ++++++++++++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr index 6338011c3ad..b428fb031b6 100644 --- a/drivers/samsung/displays/mdc_protocol.cr +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -254,15 +254,13 @@ class Samsung::Displays::MDCProtocol < PlaceOS::Driver when .brightness? self[:brightness] = value when .input? - self[:input] = Input.from_value(value) + current_input = Input.from_value(value) + self[:input] = current_input # The input feedback behaviour seems to go a little odd when # screen split is active. Ignore any input forcing when on. unless self[:screen_split]?.try &.as_bool - input_target = @input_target - @input_stable = self[:input]? == input_target - if input_target - switch_to(input_target) unless @input_stable - end + @input_stable = current_input == (input_target = @input_target) + switch_to(input_target) if input_target && !@input_stable end when .speaker? self[:speaker] = SpeakerMode.from_value(value) diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr index 586da98b2a8..5e94b0e49f7 100644 --- a/drivers/samsung/displays/mdc_protocol_spec.cr +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -20,19 +20,19 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do exec(:volume, 24) should_send("\xAA\x12#{id}\x01\x18\x12") - responds("\xAA\xFF\x00\x03A\x12\x18\xFF") + responds("\xAA\xFF#{id}\x03A\x12\x18\xFF") status[:volume].should eq(24) status[:audio_mute].should eq(false) exec(:volume, 6) should_send("\xAA\x12#{id}\x01\x06\x12") - responds("\xAA\xFF\x00\x03A\x12\x06\xFF") + responds("\xAA\xFF#{id}\x03A\x12\x06\xFF") status[:volume].should eq(6) status[:audio_mute].should eq(false) exec(:mute) # Video mute - should_send("\xAA\xF9\x00\x01\x01\xF9") + should_send("\xAA\xF9#{id}\x01\x01\xF9") responds("\xAA\xFF#{id}\x03A\xF9\x01\xFF") status[:power].should eq(false) # Audio mute @@ -43,12 +43,23 @@ DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do exec(:unmute) # Video unmute - should_send("\xAA\xF9\x00\x01\x00\xF9") + should_send("\xAA\xF9#{id}\x01\x00\xF9") responds("\xAA\xFF#{id}\x03A\xF9\x00\xFF") status[:power].should eq(true) # Audio unmute should_send("\xAA\x12#{id}\x01\x06\x12") - responds("\xAA\xFF\x00\x03A\x12\x06\xFF") + responds("\xAA\xFF#{id}\x03A\x12\x06\xFF") status[:audio_mute].should eq(false) status[:volume].should eq(6) + + exec(:switch_to, "hdmi") + should_send("\xAA\x14#{id}\x01\x21\x14") + responds("\xAA\xFF#{id}\x03A\x14\x21\xFF") + status[:input].should eq("Hdmi") + + # power(false) == video_mute(true) + exec(:power, false) + should_send("\xAA\xF9#{id}\x01\x01\xF9") + responds("\xAA\xFF#{id}\x03A\xF9\x01\xFF") + status[:power].should eq(false) end