From 415e0f14af572cf81f64d99a5310e49004f548b5 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 25 Oct 2017 18:00:17 +1100 Subject: [PATCH 0001/1752] initial testing against domino server --- modules/i_b_m/domino/bookings.rb | 5 +- modules/i_b_m/domino/notes_calendar.rb | 79 +++++++++++++++----------- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/modules/i_b_m/domino/bookings.rb b/modules/i_b_m/domino/bookings.rb index 7b3a34c1..da8a47eb 100644 --- a/modules/i_b_m/domino/bookings.rb +++ b/modules/i_b_m/domino/bookings.rb @@ -189,12 +189,11 @@ def start_meeting(meeting_ref) def cancel_meeting(start_time) calendar = system[:Calendar] - events = calendar.events.value - events.keep_if do |event| + events = calendar.events.value.keep_if do |event| event[:start].to_i == start_time end events.each do |event| - calendar.remove(event) + calendar.cancel_booking(event) end end diff --git a/modules/i_b_m/domino/notes_calendar.rb b/modules/i_b_m/domino/notes_calendar.rb index f5aaf643..50215333 100644 --- a/modules/i_b_m/domino/notes_calendar.rb +++ b/modules/i_b_m/domino/notes_calendar.rb @@ -28,10 +28,14 @@ def on_load end def on_update - @timezone = setting(:timezone) - @username = setting(:username) + self[:timezone] = @timezone = setting(:timezone) + self[:timezone] = @username = setting(:username) @password = setting(:password) - @database = setting(:database) + self[:timezone] = @database = setting(:database) + + @headers = { + authorization: [@username, @password] + } end DECODE_OPTIONS = { @@ -51,9 +55,10 @@ def free_rooms(building:, starting:, ending:, capacity: nil, timezone: @timezone } params[:capacity] = capacity.to_i if capacity - get('/api/freebusy/freerooms', query: params) do |data| - return :retry unless data.status == 200 - JSON.parse(data.body, DECODE_OPTIONS)[:rooms] + get('/api/freebusy/freerooms', query: params, headers: @headers) do |data| + if data.status == 200 + JSON.parse(data.body, DECODE_OPTIONS)[:rooms] + else; :retry; end end end @@ -69,31 +74,34 @@ def user_busy(email:, starting: nil, ending: nil, days: nil, timezone: @timezone params[:before] = before.utc.iso8601 end - get('/api/freebusy/busytime', query: params) do |data| - return :retry unless data.status == 200 - times = JSON.parse(data.body, DECODE_OPTIONS)[:busyTimes] - times.collect do |period| - s = period[:start] - e = period[:end] - { - starting: Time.iso8601("#{s[:date]}T#{s[:time]}Z"), - ending: Time.iso8601("#{e[:date]}T#{e[:time]}Z") - } - end + get('/api/freebusy/busytime', query: params, headers: @headers) do |data| + if data.status == 200 + times = JSON.parse(data.body, DECODE_OPTIONS)[:busyTimes] + times.collect do |period| + s = period[:start] + e = period[:end] + { + starting: Time.iso8601("#{s[:date]}T#{s[:time]}Z"), + ending: Time.iso8601("#{e[:date]}T#{e[:time]}Z") + } + end + else; :retry; end end end def directories - get('/api/freebusy/directories') do |data| - return :retry unless data.status == 200 - JSON.parse(data.body, DECODE_OPTIONS) + get('/api/freebusy/directories', headers: @headers) do |data| + if data.status == 200 + JSON.parse(data.body, DECODE_OPTIONS) + else; :retry; end end end def sites(directory) - get("/api/freebusy/sites/#{directory}") do |data| - return :retry unless data.status == 200 - JSON.parse(data.body, DECODE_OPTIONS) + get("/api/freebusy/sites/#{directory}", headers: @headers) do |data| + if data.status == 200 + JSON.parse(data.body, DECODE_OPTIONS) + else; :retry; end end end @@ -107,14 +115,11 @@ def bookings(starting: nil, ending: nil, timezone: @timezone, count: 100, start: count: count, since: since.utc.iso8601, before: before.utc.iso8601, - headers: { - authorization: [@username, @password] - } } query[:fields] = Array(fields).join(',') if fields.present? - get("/mail/#{@database}/api/calendar/events", query: query) do |data| + get("/mail/#{@database}/api/calendar/events", query: query, headers: @headers) do |data| return :retry unless data.status == 200 - parse(JSON.parse(data.body, DECODE_OPTIONS)) + parse(JSON.parse("[#{data.body}]", DECODE_OPTIONS)[0]) end end @@ -128,7 +133,7 @@ def cancel_booking(id:, recurrenceId: nil, email_attendees: false, **opts) "/mail/#{@database}/api/calendar/events/#{id}" end - delete(uri, query: query) do |data| + delete(uri, query: query, headers: @headers) do |data| logger.warn "unable to delete meeting #{id} as it could not be found" if data.status == 404 :success end @@ -175,11 +180,12 @@ def create_booking(starting:, ending:, summary:, location: nil, description: nil post("/mail/#{@database}/api/calendar/events", { query: query, - headers: headers, + headers: @headers.merge(headers), body: event.to_json }) do |data| - return parse(JSON.parse(data.body, DECODE_OPTIONS)) if data.status == 201 - :retry + if data.status == 201 + parse(JSON.parse(data.body, DECODE_OPTIONS)) + else; :retry; end end end @@ -197,6 +203,13 @@ def get_time_range(starting, ending, timezone) end def to_date(time) + if time.is_a?(String) + Time.zone = @timezone + time = Time.zone.parse(time) + elsif time.is_a?(Integer) + time = Time.at(time) + end + utctime = time.getutc { date: utctime.strftime("%Y-%m-%d"), @@ -210,6 +223,8 @@ def to_date(time) } def parse(response) + return [] if response.nil? + events = response[:events] Array(events).collect do |event| ev = { From 2462b149166a5aaa002aa8d6b10917025566ba94 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 26 Oct 2017 14:44:13 +1100 Subject: [PATCH 0002/1752] (ibm:domino) rename the folder underscores were not required --- modules/{i_b_m => ibm}/domino/bookings.rb | 0 modules/{i_b_m => ibm}/domino/notes_calendar.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename modules/{i_b_m => ibm}/domino/bookings.rb (100%) rename modules/{i_b_m => ibm}/domino/notes_calendar.rb (100%) diff --git a/modules/i_b_m/domino/bookings.rb b/modules/ibm/domino/bookings.rb similarity index 100% rename from modules/i_b_m/domino/bookings.rb rename to modules/ibm/domino/bookings.rb diff --git a/modules/i_b_m/domino/notes_calendar.rb b/modules/ibm/domino/notes_calendar.rb similarity index 100% rename from modules/i_b_m/domino/notes_calendar.rb rename to modules/ibm/domino/notes_calendar.rb From 4fa0e66ab0c5828b5ece23bdbe94a6bcf1fd5605 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 27 Oct 2017 18:13:10 +1100 Subject: [PATCH 0003/1752] (domino:calendar) fix calendar entry creation --- modules/ibm/domino/notes_calendar.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ibm/domino/notes_calendar.rb b/modules/ibm/domino/notes_calendar.rb index 50215333..6fb38294 100644 --- a/modules/ibm/domino/notes_calendar.rb +++ b/modules/ibm/domino/notes_calendar.rb @@ -181,7 +181,7 @@ def create_booking(starting:, ending:, summary:, location: nil, description: nil post("/mail/#{@database}/api/calendar/events", { query: query, headers: @headers.merge(headers), - body: event.to_json + body: { events: [event] }.to_json }) do |data| if data.status == 201 parse(JSON.parse(data.body, DECODE_OPTIONS)) From 7e6dfbf61bd763531bc6558826d5693fdae30a4d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 31 Oct 2017 16:04:16 +1100 Subject: [PATCH 0004/1752] Update gallagher set pdf method' --- lib/gallagher.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/gallagher.rb b/lib/gallagher.rb index 7f570e9b..2c2deb6c 100755 --- a/lib/gallagher.rb +++ b/lib/gallagher.rb @@ -199,5 +199,33 @@ def set_access(vivant_id, access_group, time_start, time_end) return e.message end end + + + def set_pdf(vivant_id, pdf_name, pdf_value) + # Add five minutes to time start + method_name = 'cif_update_cardholder_pdf' + begin + response = @client.call(method_name.to_sym, + message: { + 'wsdl:sessionToken': { 'web:Value': gallagher_token}, + 'wsdl:id': { + 'wsdl:PdfName': 'Unique ID', + 'wsdl:Value': "cardholder--#{vivant_id}" + }, + 'wsdl:pdfValue': { + :'@i:type' => 'web:PdfGeneralValue', + :'@xmlns:i' => 'http://www.w3.org/2001/XMLSchema-instance', + 'web:Name': pdf_name, + 'web:Type': 'StrEnum', + 'web:Value': pdf_value + } + }) + return response.body[(method_name + '_response').to_sym] + rescue Savon::SOAPFault => e + return e.message + end + end end + + From f668fc64b99f25e3dd6428adafc3ad35ebce19ff Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 1 Nov 2017 11:54:08 +1100 Subject: [PATCH 0005/1752] (sony:bravia) add device driver --- modules/sony/display/bravia.rb | 230 ++++++++++++++++++++++++++++ modules/sony/display/bravia_spec.rb | 56 +++++++ 2 files changed, 286 insertions(+) create mode 100755 modules/sony/display/bravia.rb create mode 100644 modules/sony/display/bravia_spec.rb diff --git a/modules/sony/display/bravia.rb b/modules/sony/display/bravia.rb new file mode 100755 index 00000000..5526842d --- /dev/null +++ b/modules/sony/display/bravia.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true, encoding: ASCII-8BIT + +module Sony; end +module Sony::Display; end + +# Documentation: https://aca.im/driver_docs/Sony/sony+bravia+simple+ip+control.pdf + +class Sony::Display::Bravia + include ::Orchestrator::Constants + + # Discovery Information + tcp_port 20060 + descriptive_name 'Sony Bravia LCD Display' + generic_name :Display + + # Communication settings + # 24bytes with header however we'll ignore the footer + tokenize indicator: "\x2A\x53", msg_length: 21 + + def on_load # :nodoc: + self[:volume_min] = 0 + self[:volume_max] = 100 + end + + def connected # :nodoc: + # Display disconnects after 30seconds of no comms + schedule.every('20s') { poll } + end + + def disconnected # :nodoc: + # Stop polling + schedule.clear + end + + # Power the display on or off + # + # @param [Boolean] the desired power state + def power(state, _ = nil) + if is_affirmative?(state) + request(:power, 1) + logger.debug "-- sony display requested to power on" + else + request(:power, 0) + logger.debug "-- sony display requested to power off" + end + + # Request status update + power? + end + + # query display power state + def power?(**options, &block) + options[:emit] = block if block_given? + options[:priority] ||= 0 + query(:power, options) + end + + INPUTS = { + tv: "00000", + hdmi: "10000", + mirror: "50000", + vga: "60000" + } + INPUTS.merge!(INPUTS.invert) + + # switch to input on display + # + # @param [Symbol, String] the desired power state. i.e. hdmi2 + def switch_to(input) + type, index = input.to_s.scan /[^0-9]+|\d+/ + index ||= '1' + + inp = type.to_sym + raise ArgumentError, "unknown input #{input}" unless INPUTS.has_key? inp + + request(:input, "#{INPUTS[inp]}#{index.rjust(4, '0')}") + logger.debug { "requesting to switch to: #{input}" } + + input? + end + + def input? + query(:input, priority: 0) + end + + + # Set the picture mute state + # + # @param [Boolean] the desired picture mute state + def mute(state = true) + val = is_affirmative?(state) ? 1 : 0 + request(:mute, val) + logger.debug "requested to mute #{state}" + mute? + end + + def unmute + mute false + end + + def mute? + query(:mute, priority: 0) + end + + # Set the audio mute state + # + # @param [Boolean] the desired mute state + def mute_audio(state = true) + val = is_affirmative?(state) ? 1 : 0 + request(:audio_mute, val) + logger.debug "requested to mute audio #{state}" + audio_mute? + end + + def unmute_audio + mute_audio false + end + + def audio_mute? + query(:audio_mute, priority: 0) + end + + # Set the volume to the desired level + # + # @param [Integer] the desired volume level + def volume(level) + request(:volume, level.to_i) + volume? + end + + def volume? + query(:volume, priority: 0) + end + + # Queries for power, input, mute, audio mute and volume state + def poll + power? do + if self[:power] + input? + mute? + audio_mute? + volume? + end + end + end + + def received(byte_str, resolve, command) # :nodoc: + logger.debug { "sent: #{byte_str}" } + + type = TYPES[byte_str[0]] + cmd = byte_str[1..4] + param = byte_str[5..-1] + + # Request failure + return :abort if param[0] == 'F' + + # If this is a response to a control request then it must have succeeded + return :success if type == :answer && command && TYPES[command[:data][2]] == :control + + # Data request response or notify + cmd_type = COMMANDS[cmd] + case cmd_type + when :power, :mute, :audio_mute, :pip + self[cmd_type] = param.to_i == 1 + when :volume + self[:volume] = param.to_i + when :mac_address + self[:mac_address] = param.split('#')[0] + when :input + input_num = param[7..11] + index_num = param[12..-1].to_i + if index_num == 1 + self[:input] = INPUTS[input_num] + else + self[:input] = :"#{INPUTS[input_num]}#{index_num}" + end + end + + # Ignore notify as we might be expecting a response and don't want to process + return :ignore if type == :notify + :success + end + + + protected + + + COMMANDS = { + ir_code: 'IRCC', + power: 'POWR', + volume: 'VOLU', + audio_mute: 'AMUT', + mute: 'PMUT', + channel: 'CHNN', + tv_input: 'ISRC', + input: 'INPT', + toggle_mute: 'TPMU', + pip: 'PIPI', + toggle_pip: 'TPIP', + position_pip: 'TPPP', + broadcast_address: 'BADR', + mac_address: 'MADR' + } + COMMANDS.merge!(COMMANDS.invert) + + TYPES = { + control: "\x43", + enquiry: "\x45", + answer: "\x41", + notify: "\x4E" + } + TYPES.merge! TYPES.invert + + def request(command, parameter = nil, **options) # :nodoc: + cmd = command.to_sym + options[:name] = cmd + do_send(:control, COMMANDS[cmd], parameter, options) + end + + def query(state, **options) # :nodoc: + options[:name] = :"#{state}_query" + do_send(:enquiry, COMMANDS[state.to_sym], nil, options) + end + + def do_send(type, command, parameter = nil, **options) # :nodoc: + param = parameter.nil? ? '################' : parameter.to_s.rjust(16, '0') + cmd = "\x2A\x53#{TYPES[type]}#{command}#{param}\n" + send(cmd, options) + end +end diff --git a/modules/sony/display/bravia_spec.rb b/modules/sony/display/bravia_spec.rb new file mode 100644 index 00000000..59c77dea --- /dev/null +++ b/modules/sony/display/bravia_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true, encoding: ASCII-8BIT + +Orchestrator::Testing.mock_device 'Sony::Display::Bravia' do + exec(:power, true) + .should_send("\x2A\x53\x43POWR0000000000000001\n") + .responds("\x2A\x53\x41POWR0000000000000000\n") + .should_send("\x2A\x53\x45POWR################\n") + .responds("\x2A\x53\x41POWR0000000000000001\n") + expect(status[:power]).to be(true) + + exec(:switch_to, :hdmi) + .should_send("\x2A\x53\x43INPT0000000100000001\n") + .responds("\x2A\x53\x41INPT0000000000000000\n") + .should_send("\x2A\x53\x45INPT################\n") + .responds("\x2A\x53\x41INPT0000000100000001\n") + expect(status[:input]).to be(:hdmi) + + exec(:switch_to, :vga34) + .should_send("\x2A\x53\x43INPT0000000600000034\n") + .responds("\x2A\x53\x41INPT0000000000000000\n") + .should_send("\x2A\x53\x45INPT################\n") + .responds("\x2A\x53\x41INPT0000000600000034\n") + expect(status[:input]).to be(:vga34) + + exec(:volume, 99) + .should_send("\x2A\x53\x43VOLU0000000000000099\n") + .responds("\x2A\x53\x41VOLU0000000000000000\n") + .should_send("\x2A\x53\x45VOLU################\n") + .responds("\x2A\x53\x41VOLU0000000000000099\n") + expect(status[:volume]).to be(99) + + exec(:mute) + .should_send("\x2A\x53\x43PMUT0000000000000001\n") + .responds("\x2A\x53\x41PMUT0000000000000000\n") + .should_send("\x2A\x53\x45PMUT################\n") + .responds("\x2A\x53\x41PMUT0000000000000001\n") + expect(status[:mute]).to be(true) + + # Test failure + exec(:unmute) + .should_send("\x2A\x53\x43PMUT0000000000000000\n") + .responds("\x2A\x53\x41PMUTFFFFFFFFFFFFFFFF\n") + .should_send("\x2A\x53\x45PMUT################\n") + .responds("\x2A\x53\x41PMUT0000000000000001\n") + expect(status[:mute]).to be(true) + + # Test notify + exec(:volume, 50) + .should_send("\x2A\x53\x43VOLU0000000000000050\n") + .responds("\x2A\x53\x4EPMUT0000000000000000\n") # mix in a notify + .responds("\x2A\x53\x41VOLU0000000000000000\n") + .should_send("\x2A\x53\x45VOLU################\n") + .responds("\x2A\x53\x41VOLU0000000000000050\n") + expect(status[:volume]).to be(50) + expect(status[:mute]).to be(false) +end From b30853bd75c71d0cf6d469c08a88956a7498066c Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 15 Nov 2017 11:28:45 +1100 Subject: [PATCH 0006/1752] (aca:skype) add polling and dev tools --- modules/aca/skype_logic.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/aca/skype_logic.rb b/modules/aca/skype_logic.rb index bede6582..efb06a9e 100644 --- a/modules/aca/skype_logic.rb +++ b/modules/aca/skype_logic.rb @@ -12,6 +12,7 @@ def on_load self[:accept_call] = 0 self[:hang_up] = 0 self[:call_uri] = 0 + self[:open_dev_tools] = 0 @mic_mutes = setting(:mics_mutes) end @@ -26,7 +27,7 @@ def set_uri(uri) end def call_uri(uri = nil) - set_uri(uri) + #set_uri(uri) return unless self[:uri].present? self[:call_uri] += 1 end @@ -77,4 +78,13 @@ def room_user(name) def state(status) self[:state] = status end + + # The interface will poll the server periodically, helping discover issues + def poll + self[:last_polled] = Time.now.to_s + end + + def open_dev_tools + self[:open_dev_tools] += 1 + end end From a724a59306e18a1912ba5ae1f0b5e8a788a10670 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 15 Nov 2017 11:35:01 +1100 Subject: [PATCH 0007/1752] (aca:skype) add clear local storage option --- modules/aca/skype_logic.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/aca/skype_logic.rb b/modules/aca/skype_logic.rb index efb06a9e..0f4c04a5 100644 --- a/modules/aca/skype_logic.rb +++ b/modules/aca/skype_logic.rb @@ -13,6 +13,7 @@ def on_load self[:hang_up] = 0 self[:call_uri] = 0 self[:open_dev_tools] = 0 + self[:clear_local_storage] = 0 @mic_mutes = setting(:mics_mutes) end @@ -87,4 +88,8 @@ def poll def open_dev_tools self[:open_dev_tools] += 1 end + + def clear_local_storage + self[:clear_local_storage] += 1 + end end From 2c5fbdc9fe5e58c3d9faf6294275d1f9a11ee011 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 20 Nov 2017 19:39:08 +1000 Subject: [PATCH 0008/1752] (cisco:spark) base module structure --- modules/cisco/spark/room_os.rb | 65 +++++++++++++++++++++++ modules/cisco/spark/sx20.rb | 35 +++++++++++++ modules/cisco/spark/xapi/action.rb | 49 ++++++++++++++++++ modules/cisco/spark/xapi/mapper.rb | 82 ++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+) create mode 100644 modules/cisco/spark/room_os.rb create mode 100644 modules/cisco/spark/sx20.rb create mode 100644 modules/cisco/spark/xapi/action.rb create mode 100644 modules/cisco/spark/xapi/mapper.rb diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb new file mode 100644 index 00000000..3e552a21 --- /dev/null +++ b/modules/cisco/spark/room_os.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +Dir[File.join(File.dirname(__FILE__), 'xapi', '*.rb')].each { |f| load f } + +module Cisco; end +module Cisco::Spark; end + +class Cisco::Spark::RoomOs + include ::Orchestrator::Constants + + Xapi = Cisco::Spark::Xapi + + implements :ssh + descriptive_name 'Cisco Spark Room Device' + generic_name :VidConf + + description <<~DESC + Low level driver for any Cisco Spark Room OS device. This may be used + if direct access is required to the device API, or a required feature + is not provided by the device specific implementation. + + Where possible use the implementation for room device in use + (i.e. SX80, Spark Room Kit etc). + DESC + + tokenize delimiter: "\n}\n", + wait_ready: "OK\n" # *r Login successful\n\nOK\n + clear_queue_on_disconnect! + + def on_load; end + + def on_unload; end + + def on_update; end + + def connected + do_send 'Echo off', wait: false, priority: 96 + do_send 'xPreferences OutputMode JSON', wait: false + end + + def disconnected; end + + def received(data, deferrable, command) + logger.debug { "<- #{data}" } + end + + # Execute an xCommand on the device. + # + # @param command [String] the command to execute + # @param args [Hash] the command arguments + def xcommand(command, args = {}) + do_send Xapi::Action.xcommand(command, args) + end + + protected + + # Execute and arbitary command on the device. + # + # @param command [String] the command to execute + # @param options [Hash] send options to be passed to the transport layer + def do_send(command, **options) + logger.debug { "-> #{command}" } + send "#{command}\n", **options + end +end diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb new file mode 100644 index 00000000..1385b275 --- /dev/null +++ b/modules/cisco/spark/sx20.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +load File.expand_path('./room_os.rb', File.dirname(__FILE__)) + +class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs + include ::Orchestrator::Security + include ::Cisco::Spark::Xapi::Mapper + + descriptive_name 'Cisco Spark SX20' + description <<~DESC + Device access requires an API user to be created on the endpoint. + DESC + + # Restrict access to the direct API methods to admins + protect_method :xcommand, :xstatus, :xfeedback + + + command 'Call Accept' => :accept, + _CallId: Integer + + CAMERA_MOVE_DIRECTION = [ + :Left, + :Right, + :Up, + :Down, + :ZoomIn, + :ZoomOut + ].freeze + command 'Call FarEndCameraControl Move' => :far_end_camera, + Value: CAMERA_MOVE_DIRECTION, + _CallId: Integer + + # configuration! 'Network/n/VLAN/Voice' => :set_voice_vlan, + # Mode: [:Auto, :Manual, :Off] +end diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb new file mode 100644 index 00000000..4e69f64a --- /dev/null +++ b/modules/cisco/spark/xapi/action.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'set' + +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Xapi; end + +# Pure utility methods for building Cisco xAPI actions. +module Cisco::Spark::Xapi::Action + ACTION_TYPE ||= Set.new [ + :xConfiguration, + :xCommand, + :xStatus, + :xFeedback, + :xPreferences + ] + + module_function + + # Serialize an xAPI action into transmittable command. + # + # @param type [ACTION_TYPE] the type of action to execute + # @param command [String, Array] action command path + # @param args [Hash] an optional hash of keyword arguments for the action + # @return [String] + def create_action(type, command, args = {}) + unless ACTION_TYPE.include? type + raise ArgumentError, + "Invalid action type. Must be one of #{ACTION_TYPE}." + end + + arg_str = args.map do |name, value| + value = "\"#{value}\"" if value.is_a? String + "#{name}: #{value}" + end + + [type, command, arg_str].flatten.join ' ' + end + + # Serialize an xCommand into transmittable command. + # + # @param command [String, Array] command path + # @param args [Hash] an optional hash of keyword arguments + # @return [String] + def xcommand(command, args = {}) + create_action :xCommand, command, args + end +end diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb new file mode 100644 index 00000000..028c526b --- /dev/null +++ b/modules/cisco/spark/xapi/mapper.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Xapi; end + +# Minimal DSL for mapping Cisco's xAPI to methods. +module Cisco::Spark::Xapi::Mapper + module ApiMapperMethods + # Bind an xCommand to a module method. + # + # This abuses ruby's ordered hashes and fat arrow hash to provide a + # neat, declarative syntax for building out Room OS device modules. + # + # Example: + # command 'Fake n Command' => :my_method, + # ParamA: [:enum, :of, :options], + # ParamB: String, + # _OptionalParam: Integer + # + # Will provide the method: + # def my_method(index, param_a, param_b, optional_param = nil) + # ... + # end + # + # @param mapping [Hash] + # - first k/v pair is a mapping from the xCommand => method_name + # - use 'n' within command path elements containing an index element + # (as per the device protocol guide), this will be lifted into an + # 'index' parameter + # - all other pairs are ParamName: + # - prefix optional params with an underscore + # @return [Symbol] the mapped method name + def command(mapping) + command_path, method_name = mapping.shift + + raise ArgumentError, 'mapped command must be a String' \ + unless command_path.is_a? String + + raise ArgumentError, 'method name must be a Symbol' \ + unless method_name.is_a? Symbol + + params = mapping.keys.map { |name| name.to_s.underscore } + opt_, req = params.partition { |name| name.starts_with? '_' } + opt = opt_.map { |name| "#{name[1..-1]} = nil" } + param_str = (req + opt).join ', ' + + # TODO: add support for index commands + command_str = command_path.split(' ').join(' ') + # use .tap to isolate + + # TODO: add argument type checks + + class_eval <<-METHOD + def #{method_name}(#{param_str}) + args = binding.local_variables + .map { |p| + [p.to_s.camelize, binding.local_variable_get(p)] + } + .to_h + .compact + do_send Xapi::Action.xcommand('#{command_str}', args) + logger.debug { args } + end + METHOD + + method_name + end + + # Bind an xCommand to a protected method (admin execution only). + # + # @return [Symbol] + # @see #command + def command!(mapping) + protect_method command(mapping) + end + end + + def self.included(klass) + klass.extend ApiMapperMethods + end +end From 079e6235690ed500a2c0198dc48b23d47f8c2c48 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 22 Nov 2017 14:06:39 +1000 Subject: [PATCH 0009/1752] (reek) allow model number to be used as module names --- .reek | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.reek b/.reek index 36b7e762..6e03ef2d 100644 --- a/.reek +++ b/.reek @@ -39,8 +39,12 @@ TooManyConstants: # Suppress warning about parameter length within reason. LongParameterList: max_params: 4 - + # Prevent from flagging multiple calls to utility methods # (e.g. is_affirmative?). RepeatedConditional: enabled: false + +# Allow for device model numbers to be used as module names. +UncommunicativeModuleName: + enabled: false From 1c98978a75232309b064752e4ab7256f0f6b6303 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 22 Nov 2017 20:52:40 +1000 Subject: [PATCH 0010/1752] (cisco:spark) fix response tokenization --- modules/cisco/spark/room_os.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 3e552a21..343e0190 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -23,8 +23,8 @@ class Cisco::Spark::RoomOs (i.e. SX80, Spark Room Kit etc). DESC - tokenize delimiter: "\n}\n", - wait_ready: "OK\n" # *r Login successful\n\nOK\n + tokenize delimiter: /(?<=\n})|(?<=\n{})\n/, + wait_ready: "*r Login successful\n\nOK\n\n" clear_queue_on_disconnect! def on_load; end From 38f23856537f1e8e2c0e51c9846a1e6aeb369237 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 22 Nov 2017 20:55:20 +1000 Subject: [PATCH 0011/1752] (cisco:spark) let xapi module handle all packet packing --- modules/cisco/spark/room_os.rb | 17 ++++------------- modules/cisco/spark/xapi/action.rb | 2 +- modules/cisco/spark/xapi/mapper.rb | 3 +-- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 343e0190..4a211e37 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -34,8 +34,8 @@ def on_unload; end def on_update; end def connected - do_send 'Echo off', wait: false, priority: 96 - do_send 'xPreferences OutputMode JSON', wait: false + send "Echo off\n", wait: false, priority: 96 + send "xPreferences OutputMode JSON\n", wait: false end def disconnected; end @@ -49,17 +49,8 @@ def received(data, deferrable, command) # @param command [String] the command to execute # @param args [Hash] the command arguments def xcommand(command, args = {}) - do_send Xapi::Action.xcommand(command, args) - end - - protected + request = Xapi::Action.xcommand command, args - # Execute and arbitary command on the device. - # - # @param command [String] the command to execute - # @param options [Hash] send options to be passed to the transport layer - def do_send(command, **options) - logger.debug { "-> #{command}" } - send "#{command}\n", **options + send request, name: command end end diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index 4e69f64a..68ae3e25 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -35,7 +35,7 @@ def create_action(type, command, args = {}) "#{name}: #{value}" end - [type, command, arg_str].flatten.join ' ' + [type, command, arg_str].flatten.join(' ') + "\n" end # Serialize an xCommand into transmittable command. diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 028c526b..8a2fe3fe 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -59,8 +59,7 @@ def #{method_name}(#{param_str}) } .to_h .compact - do_send Xapi::Action.xcommand('#{command_str}', args) - logger.debug { args } + send Xapi::Action.xcommand('#{command_str}', args) end METHOD From b28541b844ba3a60ae8bc02913191cc7ed1e9efa Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 22 Nov 2017 21:57:52 +1000 Subject: [PATCH 0012/1752] (cisco:spark) implement response parsing for xCommands --- modules/cisco/spark/room_os.rb | 67 +++++++++++++++++++++++++++++- modules/cisco/spark/xapi/action.rb | 4 +- modules/cisco/spark/xapi/mapper.rb | 2 +- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 4a211e37..c9da17ea 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require 'json' +require 'securerandom' + Dir[File.join(File.dirname(__FILE__), 'xapi', '*.rb')].each { |f| load f } module Cisco; end @@ -42,6 +45,10 @@ def disconnected; end def received(data, deferrable, command) logger.debug { "<- #{data}" } + + # Async events only + + :success end # Execute an xCommand on the device. @@ -49,8 +56,66 @@ def received(data, deferrable, command) # @param command [String] the command to execute # @param args [Hash] the command arguments def xcommand(command, args = {}) + send_xcommand command, args + end + + protected + + # Perform the actual command execution - this allows device implementations + # to protect access to #xcommand and still refer the gruntwork here. + def send_xcommand(command, args = {}) request = Xapi::Action.xcommand command, args - send request, name: command + do_send request, name: command do |response| + result = response.first.last + + logger.debug response + + if result['status'] == 'OK' + :success + else + reason = result.dig 'Reason', 'Value' + logger.error reason if reason + :abort + end + end + end + + def do_send(command, **options) + request_id = SecureRandom.uuid + + # FIXME: find a neater way of sharing id's with the test framework. + self[:__last_uuid] = request_id + + send "#{command} | resultId=\"#{request_id}\"\n", **options do |rx| + begin + response = JSON.parse rx, object_class: CaseInsensitiveHash + + if response['ResultId'] == request_id + if block_given? + yield response['CommandResponse'] + else + :success + end + else + :ignore + end + rescue JSON::ParserError => error + logger.warn "Malformed device response: #{error}" + :fail + end + end + end +end + +class CaseInsensitiveHash < ActiveSupport::HashWithIndifferentAccess + def [](key) + super convert_key(key) + end + + protected + + def convert_key(key) + key.respond_to?(:downcase) ? key.downcase : key end end diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index 68ae3e25..c25486bc 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -30,12 +30,12 @@ def create_action(type, command, args = {}) "Invalid action type. Must be one of #{ACTION_TYPE}." end - arg_str = args.map do |name, value| + args = args.map do |name, value| value = "\"#{value}\"" if value.is_a? String "#{name}: #{value}" end - [type, command, arg_str].flatten.join(' ') + "\n" + [type, command, args].flatten.join ' ' end # Serialize an xCommand into transmittable command. diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 8a2fe3fe..4d9c8fa0 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -59,7 +59,7 @@ def #{method_name}(#{param_str}) } .to_h .compact - send Xapi::Action.xcommand('#{command_str}', args) + send_xcommand '#{command_str}', args end METHOD From abfd27ac1ed65579fc837d60c3699b42959feb4d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 23 Nov 2017 03:32:51 +1000 Subject: [PATCH 0013/1752] (cisco:spark) add RoomOS spec --- modules/cisco/spark/room_os_spec.rb | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 modules/cisco/spark/room_os_spec.rb diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb new file mode 100644 index 00000000..b08f1cde --- /dev/null +++ b/modules/cisco/spark/room_os_spec.rb @@ -0,0 +1,68 @@ +Orchestrator::Testing.mock_device 'Cisco::Spark::RoomOs' do + transmit <<~BANNER + Welcome to + Cisco Codec Release Spark Room OS 2017-10-31 192c369 + SW Release Date: 2017-10-31 + *r Login successful + + OK + + BANNER + + expect(status[:connected]).to be true + + # Comms setup + should_send "Echo off\n" + should_send "xPreferences OutputMode JSON\n" + + # Basic command + exec(:xcommand, 'Standby Deactivate') + .should_send("xCommand Standby Deactivate | resultId=\"#{status[:__last_uuid]}\"\n") + .responds( + <<~JSON + { + "CommandResponse":{ + "StandbyDeactivateResult":{ + "status":"OK" + } + }, + "ResultId": \"#{status[:__last_uuid]}\" + } + JSON + ) + + # Command with arguments + exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, Layout: :PIP) + .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 Layout: PIP | resultId=\"#{status[:__last_uuid]}\n") + .responds( + <<~JSON + { + "CommandResponse":{ + "InputSetMainVideoSourceResult":{ + "status":"OK" + } + }, + "ResultId": \"#{status[:__last_uuid]}\" + } + JSON + ) + + # Return device argument errors + exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, SourceId: 1) + .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 SourceId: 1 | resultId=\"#{status[:__last_uuid]}\n") + .responds( + <<~JSON + { + "CommandResponse":{ + "InputSetMainVideoSourceResult":{ + "status":"Error", + "Reason":{ + "Value":"Must supply either SourceId or ConnectorId (but not both.)" + } + } + }, + "ResultId": \"#{status[:__last_uuid]}\" + } + JSON + ) +end From 12c3be2676ef4368ef3135d2dd6359919dabd0dd Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 23 Nov 2017 14:07:52 +1000 Subject: [PATCH 0014/1752] (cisco:spark) fix device response parsing --- modules/cisco/spark/room_os.rb | 28 +++++++++++++++++++--------- modules/cisco/spark/room_os_spec.rb | 4 ++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index c9da17ea..3d775ab8 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -67,15 +67,25 @@ def send_xcommand(command, args = {}) request = Xapi::Action.xcommand command, args do_send request, name: command do |response| - result = response.first.last - - logger.debug response - - if result['status'] == 'OK' - :success + # The result keys are a little odd - they're a concatenation of the + # last two command elements and 'Result'. + # For example: + # `xCommand Video Input SetMainVideoSource...` + # becomes: + # `InputSetMainVideoSourceResult` + result_key = command.split(' ').last(2).join('') + 'Result' + result = response.dig 'CommandResponse', result_key + + if result + if result['status'] == 'OK' + :success + else + reason = result.dig 'Reason', 'Value' + logger.error reason if reason + :abort + end else - reason = result.dig 'Reason', 'Value' - logger.error reason if reason + logger.warn 'Unexpected response format' :abort end end @@ -93,7 +103,7 @@ def do_send(command, **options) if response['ResultId'] == request_id if block_given? - yield response['CommandResponse'] + yield response else :success end diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index b08f1cde..c26aa105 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -33,7 +33,7 @@ # Command with arguments exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, Layout: :PIP) - .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 Layout: PIP | resultId=\"#{status[:__last_uuid]}\n") + .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 Layout: PIP | resultId=\"#{status[:__last_uuid]}\"\n") .responds( <<~JSON { @@ -49,7 +49,7 @@ # Return device argument errors exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, SourceId: 1) - .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 SourceId: 1 | resultId=\"#{status[:__last_uuid]}\n") + .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 SourceId: 1 | resultId=\"#{status[:__last_uuid]}\"\n") .responds( <<~JSON { From 926fd52cb9e7215bc829bd3b4028293c66c94995 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 23 Nov 2017 16:13:25 +1000 Subject: [PATCH 0015/1752] (cisco:spark) intercept calls to uuid generation from test runner --- modules/cisco/spark/room_os.rb | 17 +++++++++++++---- modules/cisco/spark/room_os_spec.rb | 27 +++++++++++++++++++++------ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 3d775ab8..453f2a2d 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -91,11 +91,16 @@ def send_xcommand(command, args = {}) end end + # Execute raw command on the device. + # + # Automatically appends a result tag and handles routing of response + # handling for async interactions. If a block is passed a pre-parsed + # response object will be yielded to it. + # + # @param command [String] the raw command to execute + # @yield [response] a pre-parsed response object for the command def do_send(command, **options) - request_id = SecureRandom.uuid - - # FIXME: find a neater way of sharing id's with the test framework. - self[:__last_uuid] = request_id + request_id = generate_request_uuid send "#{command} | resultId=\"#{request_id}\"\n", **options do |rx| begin @@ -116,6 +121,10 @@ def do_send(command, **options) end end end + + def generate_request_uuid + SecureRandom.uuid + end end class CaseInsensitiveHash < ActiveSupport::HashWithIndifferentAccess diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index c26aa105..a496008b 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -1,4 +1,19 @@ Orchestrator::Testing.mock_device 'Cisco::Spark::RoomOs' do + # Intercept calls to the request id generation so we can run tests. + @manager.instance.class_eval do + generate_uuid = instance_method(:generate_request_uuid) + + define_method(:generate_request_uuid) do + generate_uuid.bind(self).call.tap do |id| + instance_variable_set :@__last_uuid, id + end + end + end + + def last_uuid + @manager.instance.instance_variable_get :@__last_uuid + end + transmit <<~BANNER Welcome to Cisco Codec Release Spark Room OS 2017-10-31 192c369 @@ -17,7 +32,7 @@ # Basic command exec(:xcommand, 'Standby Deactivate') - .should_send("xCommand Standby Deactivate | resultId=\"#{status[:__last_uuid]}\"\n") + .should_send("xCommand Standby Deactivate | resultId=\"#{last_uuid}\"\n") .responds( <<~JSON { @@ -26,14 +41,14 @@ "status":"OK" } }, - "ResultId": \"#{status[:__last_uuid]}\" + "ResultId": \"#{last_uuid}\" } JSON ) # Command with arguments exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, Layout: :PIP) - .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 Layout: PIP | resultId=\"#{status[:__last_uuid]}\"\n") + .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 Layout: PIP | resultId=\"#{last_uuid}\"\n") .responds( <<~JSON { @@ -42,14 +57,14 @@ "status":"OK" } }, - "ResultId": \"#{status[:__last_uuid]}\" + "ResultId": \"#{last_uuid}\" } JSON ) # Return device argument errors exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, SourceId: 1) - .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 SourceId: 1 | resultId=\"#{status[:__last_uuid]}\"\n") + .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 SourceId: 1 | resultId=\"#{last_uuid}\"\n") .responds( <<~JSON { @@ -61,7 +76,7 @@ } } }, - "ResultId": \"#{status[:__last_uuid]}\" + "ResultId": \"#{last_uuid}\" } JSON ) From 639da072427f025991cb2ecf14b9585d7a3ec84a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 23 Nov 2017 19:08:50 +1000 Subject: [PATCH 0016/1752] (cisco:spark) add support for pushing device configuration --- modules/cisco/spark/room_os.rb | 39 +++++++++++++++++++++++++++-- modules/cisco/spark/room_os_spec.rb | 11 ++++++++ modules/cisco/spark/xapi/action.rb | 22 +++++++++++----- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 453f2a2d..d9f16e37 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -59,6 +59,14 @@ def xcommand(command, args = {}) send_xcommand command, args end + # Push a configuration settings to the device. + # + # @param path [String] the configuration path + # @param settings [Hash] the configuration values to apply + def xconfiguration(path, settings) + send_xconfiguration path, settings + end + protected # Perform the actual command execution - this allows device implementations @@ -91,6 +99,28 @@ def send_xcommand(command, args = {}) end end + def send_xconfiguration(path, settings = {}) + setting, value = settings.shift + + request = Xapi::Action.xconfiguration path, setting, value + + apply = do_send request, name: "#{path} #{setting}" do |response| + result = response.dig 'CommandResponse', 'Configuration' + + if result&.[] 'status' == 'Error' + reason = result.dig 'Reason', 'Value' + logger.error reason if reason + :abort + else + :sucess + end + end + + # Values can only be applied one at a time - recurse and return the + # full promise chain. + apply.then send_xconfiguration(path, settings) unless settings.empty? + end + # Execute raw command on the device. # # Automatically appends a result tag and handles routing of response @@ -98,11 +128,16 @@ def send_xcommand(command, args = {}) # response object will be yielded to it. # # @param command [String] the raw command to execute - # @yield [response] a pre-parsed response object for the command + # @param options [Hash] options for the transport layer + # @yield [response] + # a pre-parsed response object for the command, if used this block + # should return the response result def do_send(command, **options) request_id = generate_request_uuid - send "#{command} | resultId=\"#{request_id}\"\n", **options do |rx| + request = "#{command} | resultId=\"#{request_id}\"\n" + + send request, **options do |rx| begin response = JSON.parse rx, object_class: CaseInsensitiveHash diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index a496008b..811494d4 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -80,4 +80,15 @@ def last_uuid } JSON ) + + # Basic configuration + exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera) + .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{last_uuid}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{last_uuid}\" + } + JSON + ) end diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index c25486bc..8b9b298f 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -21,10 +21,10 @@ module Cisco::Spark::Xapi::Action # Serialize an xAPI action into transmittable command. # # @param type [ACTION_TYPE] the type of action to execute - # @param command [String, Array] action command path + # @param path [String, Array] the action path # @param args [Hash] an optional hash of keyword arguments for the action # @return [String] - def create_action(type, command, args = {}) + def create_action(type, path, args = {}) unless ACTION_TYPE.include? type raise ArgumentError, "Invalid action type. Must be one of #{ACTION_TYPE}." @@ -35,15 +35,25 @@ def create_action(type, command, args = {}) "#{name}: #{value}" end - [type, command, args].flatten.join ' ' + [type, path, args].flatten.join ' ' end # Serialize an xCommand into transmittable command. # - # @param command [String, Array] command path + # @param path [String, Array] command path # @param args [Hash] an optional hash of keyword arguments # @return [String] - def xcommand(command, args = {}) - create_action :xCommand, command, args + def xcommand(path, args = {}) + create_action :xCommand, path, args + end + + # Serialize an xConfiguration action into a transmittable command. + # + # @param path [String, Array] the configuration path + # @param setting [String] the setting key + # @param value the configuration value to apply + # @return [String] + def xconfiguration(path, setting, value) + create_action :xConfiguration, path, setting => value end end From 27056368cdca19fdd9ba1a0f6f7bd95cc87c4d3d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 23 Nov 2017 19:09:55 +1000 Subject: [PATCH 0017/1752] (cisco:spark) support commands that generate multiple device interactions from the test runner --- modules/cisco/spark/room_os_spec.rb | 67 +++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 811494d4..5c9d7c5b 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -1,17 +1,33 @@ +require 'thread' + Orchestrator::Testing.mock_device 'Cisco::Spark::RoomOs' do - # Intercept calls to the request id generation so we can run tests. + # Patch in some tracking of request UUID's so we can form and validate + # device comms. @manager.instance.class_eval do generate_uuid = instance_method(:generate_request_uuid) define_method(:generate_request_uuid) do generate_uuid.bind(self).call.tap do |id| - instance_variable_set :@__last_uuid, id + @__request_ids ||= Queue.new + @__request_ids << id end end end - def last_uuid - @manager.instance.instance_variable_get :@__last_uuid + def request_ids + @manager.instance.instance_variable_get :@__request_ids + end + + def id_peek + @last_id || id_pop + end + + def id_pop + if @last_id + @last_id.tap { @last_id = nil } + else + request_ids.pop(true).tap { |id| @last_id = id } + end end transmit <<~BANNER @@ -32,7 +48,7 @@ def last_uuid # Basic command exec(:xcommand, 'Standby Deactivate') - .should_send("xCommand Standby Deactivate | resultId=\"#{last_uuid}\"\n") + .should_send("xCommand Standby Deactivate | resultId=\"#{id_peek}\"\n") .responds( <<~JSON { @@ -41,14 +57,14 @@ def last_uuid "status":"OK" } }, - "ResultId": \"#{last_uuid}\" + "ResultId": \"#{id_pop}\" } JSON ) # Command with arguments exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, Layout: :PIP) - .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 Layout: PIP | resultId=\"#{last_uuid}\"\n") + .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 Layout: PIP | resultId=\"#{id_peek}\"\n") .responds( <<~JSON { @@ -57,14 +73,14 @@ def last_uuid "status":"OK" } }, - "ResultId": \"#{last_uuid}\" + "ResultId": \"#{id_pop}\" } JSON ) # Return device argument errors exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, SourceId: 1) - .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 SourceId: 1 | resultId=\"#{last_uuid}\"\n") + .should_send("xCommand Video Input SetMainVideoSource ConnectorId: 1 SourceId: 1 | resultId=\"#{id_peek}\"\n") .responds( <<~JSON { @@ -76,18 +92,45 @@ def last_uuid } } }, - "ResultId": \"#{last_uuid}\" + "ResultId": \"#{id_pop}\" } JSON ) # Basic configuration exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera) - .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{last_uuid}\"\n") + .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + + # Multuple settings + exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Name: "Borris", Quality: :Motion) + .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + .should_send("xConfiguration Video Input Connector 1 Name: \"Borris\" | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + .should_send("xConfiguration Video Input Connector 1 Quality: Motion | resultId=\"#{id_peek}\"\n") .responds( <<~JSON { - "ResultId": \"#{last_uuid}\" + "ResultId": \"#{id_pop}\" } JSON ) From 9dcb3cc74f0bc7320576731212a1b4c9d168bf30 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 11:08:23 +1000 Subject: [PATCH 0018/1752] (cisco:spark) fix poorly formed output from xconfiguration when applying multiple settings --- modules/cisco/spark/room_os.rb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index d9f16e37..c3e87ecf 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -99,26 +99,26 @@ def send_xcommand(command, args = {}) end end - def send_xconfiguration(path, settings = {}) - setting, value = settings.shift + def send_xconfiguration(path, settings) + # The device API only allows a single setting to be applied with each + # request. + apply_setting = lambda do |(setting, value)| + request = Xapi::Action.xconfiguration path, setting, value - request = Xapi::Action.xconfiguration path, setting, value + do_send request, name: "#{path} #{setting}" do |response| + result = response.dig 'CommandResponse', 'Configuration' - apply = do_send request, name: "#{path} #{setting}" do |response| - result = response.dig 'CommandResponse', 'Configuration' - - if result&.[] 'status' == 'Error' - reason = result.dig 'Reason', 'Value' - logger.error reason if reason - :abort - else - :sucess + if result&.[] 'status' == 'Error' + reason = result.dig 'Reason', 'Value' + logger.error reason if reason + :abort + else + :sucess + end end end - # Values can only be applied one at a time - recurse and return the - # full promise chain. - apply.then send_xconfiguration(path, settings) unless settings.empty? + thread.all settings.to_a.map(&apply_setting) end # Execute raw command on the device. From 11430429d28774eda58f9a951a57e00a9f53ea77 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 11:08:56 +1000 Subject: [PATCH 0019/1752] (cisco:spark) simply access to request id tracking --- modules/cisco/spark/room_os_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 5c9d7c5b..d9bc3c54 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -6,6 +6,8 @@ @manager.instance.class_eval do generate_uuid = instance_method(:generate_request_uuid) + attr_accessor :__request_ids + define_method(:generate_request_uuid) do generate_uuid.bind(self).call.tap do |id| @__request_ids ||= Queue.new @@ -15,7 +17,7 @@ end def request_ids - @manager.instance.instance_variable_get :@__request_ids + @manager.instance.__request_ids end def id_peek From 905f36aa6e17934b3ffd07ad76c3c0b9182260aa Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 13:56:56 +1000 Subject: [PATCH 0020/1752] (rubocop) allow slashes in regex --- .rubocop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 924ada19..1335b409 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -70,3 +70,6 @@ Style/Documentation: Style/NumericLiterals: Enabled: false + +Style/RegexpLiteral: + AllowInnerSlashes: true From 72a71355aedc4461b3a0a9e34d6426695ecc6a78 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 14:22:11 +1000 Subject: [PATCH 0021/1752] (cisco:spark) fix issue in spec where first call to id_pop would fail --- modules/cisco/spark/room_os_spec.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index d9bc3c54..9d6a0110 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -21,15 +21,11 @@ def request_ids end def id_peek - @last_id || id_pop + @last_id || request_ids.pop(true).tap { |id| @last_id = id } end def id_pop - if @last_id - @last_id.tap { @last_id = nil } - else - request_ids.pop(true).tap { |id| @last_id = id } - end + @last_id.tap { @last_id = nil } || request_ids.pop(true) end transmit <<~BANNER From 4352bab4e26dae3c5d7e18268b2e04ea77bcd314 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 15:39:55 +1000 Subject: [PATCH 0022/1752] (cisco:spark) add tests for command results --- modules/cisco/spark/room_os_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 9d6a0110..da0ea522 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -59,6 +59,7 @@ def id_pop } JSON ) + expect(result).to be :success # Command with arguments exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, Layout: :PIP) @@ -75,6 +76,7 @@ def id_pop } JSON ) + expect(result).to be :success # Return device argument errors exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, SourceId: 1) @@ -94,6 +96,7 @@ def id_pop } JSON ) + expect { result }.to raise_error(Orchestrator::Error::CommandFailure) # Basic configuration exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera) @@ -105,6 +108,7 @@ def id_pop } JSON ) + expect(result).to match_array [:success] # Multuple settings exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Name: "Borris", Quality: :Motion) @@ -132,4 +136,5 @@ def id_pop } JSON ) + expect(result).to match_array [:success, :success, :success] end From 83eadccce09f80328c98af4c243a38a9e62f64fa Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 15:40:38 +1000 Subject: [PATCH 0023/1752] (cisco:spark) fix incorrect response from xconfiguration rx handler --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index c3e87ecf..9ef05abc 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -113,7 +113,7 @@ def send_xconfiguration(path, settings) logger.error reason if reason :abort else - :sucess + :success end end end From 49e85d4290ad026fb9535b9acc00b3306365bd5e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 15:41:57 +1000 Subject: [PATCH 0024/1752] (cisco:spark) handle invalid device commands --- modules/cisco/spark/room_os.rb | 11 ++++++++--- modules/cisco/spark/room_os_spec.rb | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 9ef05abc..505b1786 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -26,7 +26,7 @@ class Cisco::Spark::RoomOs (i.e. SX80, Spark Room Kit etc). DESC - tokenize delimiter: /(?<=\n})|(?<=\n{})\n/, + tokenize delimiter: /(?<=\n})|(?<=\n{})|(?<=Command not recognized.)\n/, wait_ready: "*r Login successful\n\nOK\n\n" clear_queue_on_disconnect! @@ -151,8 +151,13 @@ def do_send(command, **options) :ignore end rescue JSON::ParserError => error - logger.warn "Malformed device response: #{error}" - :fail + if rx == 'Command not recognized.' + logger.error { "Invalid command: `#{command}`" } + :abort + else + logger.warn { "Malformed device response: #{error}" } + :fail + end end end end diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index da0ea522..1a2cfd64 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -44,6 +44,12 @@ def id_pop should_send "Echo off\n" should_send "xPreferences OutputMode JSON\n" + # Handle invalid device commands + exec(:do_send, 'Not a real command') + .should_send("Not a real command | resultId=\"#{id_pop}\"\n") + .responds("Command not recognized.\n") + expect { result }.to raise_error(Orchestrator::Error::CommandFailure) + # Basic command exec(:xcommand, 'Standby Deactivate') .should_send("xCommand Standby Deactivate | resultId=\"#{id_peek}\"\n") From 1dfb97438dcb9dc111e80fa3e68663ebc2431df2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 16:00:36 +1000 Subject: [PATCH 0025/1752] (cisco:spark) handle invalid / inaccessible xCommands --- modules/cisco/spark/room_os.rb | 14 +++++++++----- modules/cisco/spark/room_os_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 505b1786..2a542922 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -75,14 +75,18 @@ def send_xcommand(command, args = {}) request = Xapi::Action.xcommand command, args do_send request, name: command do |response| - # The result keys are a little odd - they're a concatenation of the - # last two command elements and 'Result'. + # The result keys are a little odd: they're a concatenation of the + # last two command elements and 'Result', unless the command + # failed in which case it's just 'Result'. # For example: - # `xCommand Video Input SetMainVideoSource...` + # xCommand Video Input SetMainVideoSource ... # becomes: - # `InputSetMainVideoSourceResult` + # InputSetMainVideoSourceResult result_key = command.split(' ').last(2).join('') + 'Result' - result = response.dig 'CommandResponse', result_key + command_result = response.dig 'CommandResponse', result_key + failure_result = response.dig 'CommandResponse', 'Result' + + result = command_result || failure_result if result if result['status'] == 'OK' diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 1a2cfd64..079f6fd9 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -104,6 +104,29 @@ def id_pop ) expect { result }.to raise_error(Orchestrator::Error::CommandFailure) + # Return error from invalid / inaccessable xCommands + exec(:xcommand, 'Not A Real Command') + .should_send("xCommand Not A Real Command | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "CommandResponse":{ + "Result":{ + "status":"Error", + "Reason":{ + "Value":"Unknown command" + } + }, + "XPath":{ + "Value":"/Not/A/Real/Command" + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + expect { result }.to raise_error(Orchestrator::Error::CommandFailure) + # Basic configuration exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera) .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") From 0b8a19d0ed0a67c0ee8af9d89bed71cf381ec996 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 16:13:34 +1000 Subject: [PATCH 0026/1752] (cisco:spark) neaten up spec layout --- modules/cisco/spark/room_os_spec.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 079f6fd9..710a720b 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -28,6 +28,15 @@ def id_pop @last_id.tap { @last_id = nil } || request_ids.pop(true) end + def section(message) + puts "\n\n#{'-' * 80}" + puts message + puts "\n" + end + + # ------------------------------------------------------------------------- + section 'Connection setup' + transmit <<~BANNER Welcome to Cisco Codec Release Spark Room OS 2017-10-31 192c369 @@ -40,16 +49,23 @@ def id_pop expect(status[:connected]).to be true - # Comms setup should_send "Echo off\n" should_send "xPreferences OutputMode JSON\n" + + # ------------------------------------------------------------------------- + section 'Protected base methods - ignore the access warnings' + # Handle invalid device commands exec(:do_send, 'Not a real command') .should_send("Not a real command | resultId=\"#{id_pop}\"\n") .responds("Command not recognized.\n") expect { result }.to raise_error(Orchestrator::Error::CommandFailure) + + # ------------------------------------------------------------------------- + section 'Commands' + # Basic command exec(:xcommand, 'Standby Deactivate') .should_send("xCommand Standby Deactivate | resultId=\"#{id_peek}\"\n") @@ -127,6 +143,10 @@ def id_pop ) expect { result }.to raise_error(Orchestrator::Error::CommandFailure) + + # ------------------------------------------------------------------------- + section 'Configuration' + # Basic configuration exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera) .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") From 536fb0df3bca3ee6f7ce3686145674290b6c7a58 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 17:30:13 +1000 Subject: [PATCH 0027/1752] (cisco:spark) add test for basic command assembly and reponse parsing --- modules/cisco/spark/room_os_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 710a720b..9bbcce5a 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -56,6 +56,23 @@ def section(message) # ------------------------------------------------------------------------- section 'Protected base methods - ignore the access warnings' + # Append a request id and handle generic response parsing + exec(:do_send, 'xCommand Standby Deactivate') + .should_send("xCommand Standby Deactivate | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "CommandResponse":{ + "StandbyDeactivateResult":{ + "status":"OK" + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + expect(result).to be :success + # Handle invalid device commands exec(:do_send, 'Not a real command') .should_send("Not a real command | resultId=\"#{id_pop}\"\n") From 644a16f0cf812ca10320bc3c2eb37af9adfa72f7 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 18:11:49 +1000 Subject: [PATCH 0028/1752] (cisco:spark) fix issue when failed response contains additional whitespace --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 2a542922..3aaca9d2 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -155,7 +155,7 @@ def do_send(command, **options) :ignore end rescue JSON::ParserError => error - if rx == 'Command not recognized.' + if rx.strip == 'Command not recognized.' logger.error { "Invalid command: `#{command}`" } :abort else From 350b1c74b726abe924ada49c10fac570c736b531 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 24 Nov 2017 18:12:49 +1000 Subject: [PATCH 0029/1752] (cisco:spark) add support for device state and event subscription --- modules/cisco/spark/room_os.rb | 10 ++++++++++ modules/cisco/spark/room_os_spec.rb | 12 ++++++++++++ modules/cisco/spark/xapi/action.rb | 10 ++++++++++ 3 files changed, 32 insertions(+) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 3aaca9d2..8c8f01a9 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -125,6 +125,16 @@ def send_xconfiguration(path, settings) thread.all settings.to_a.map(&apply_setting) end + # Subscribe to feedback from the device + def subscribe(path) + request = Xapi::Action.xfeedback path + + do_send request do |response| + # Always returns empty JSON object, regardless of if xPath exists + :success + end + end + # Execute raw command on the device. # # Automatically appends a result tag and handles routing of response diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 9bbcce5a..6e4db843 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -79,6 +79,18 @@ def section(message) .responds("Command not recognized.\n") expect { result }.to raise_error(Orchestrator::Error::CommandFailure) + # Device event subscription + exec(:subscribe, '/Status/Audio/Microphones/Mute') + .should_send("xFeedback register /Status/Audio/Microphones/Mute | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + expect(result).to be :success + # ------------------------------------------------------------------------- section 'Commands' diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index 8b9b298f..7149863f 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -56,4 +56,14 @@ def xcommand(path, args = {}) def xconfiguration(path, setting, value) create_action :xConfiguration, path, setting => value end + + # Serialize a xFeedback subscription request. + # + # @param path [String, Array] the feedback document path + # @return [String] + def xfeedback(path) + # Allow space or slash seperated paths + path = path.split(/[\s\/\\]/).reject(&:empty?) if path.is_a? String + create_action :xFeedback, "register /#{path.join '/'}" + end end From cd25fb369c585d383b78c05881fa1fa7719b086d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 25 Nov 2017 22:03:50 +1000 Subject: [PATCH 0030/1752] (cisco:spark) handle processing of interleaved, async responses --- modules/cisco/spark/room_os.rb | 63 +++++++++++++++++++---------- modules/cisco/spark/room_os_spec.rb | 26 +++++++++++- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 8c8f01a9..86b6964c 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -43,12 +43,34 @@ def connected def disconnected; end + # Handle all incoming data from the device. + # + # In addition to acting an the normal Orchestrator callback, on_receive + # blocks also pipe through here for initial JSON decoding. See #do_send. def received(data, deferrable, command) logger.debug { "<- #{data}" } - # Async events only + response = JSON.parse data, object_class: CaseInsensitiveHash - :success + if block_given? + # Let any pending command response handlers have first pass... + yield(response).tap do |command_result| + # Otherwise support interleaved async events + unprocessed = command_result == :ignore || command_result.nil? + handle_async response if unprocessed + end + else + handle_async response + :ignore + end + rescue JSON::ParserError => error + if data.strip == 'Command not recognized.' + logger.error { "Command not recognized: `#{command[:data]}`" } + :abort + else + logger.warn { "Malformed device response: #{error}" } + :fail + end end # Execute an xCommand on the device. @@ -151,36 +173,35 @@ def do_send(command, **options) request = "#{command} | resultId=\"#{request_id}\"\n" - send request, **options do |rx| - begin - response = JSON.parse rx, object_class: CaseInsensitiveHash - - if response['ResultId'] == request_id - if block_given? - yield response - else - :success - end - else - :ignore - end - rescue JSON::ParserError => error - if rx.strip == 'Command not recognized.' - logger.error { "Invalid command: `#{command}`" } - :abort + handle_response = lambda do |response| + if response['ResultId'] == request_id + if block_given? + yield response else - logger.warn { "Malformed device response: #{error}" } - :fail + :success end + else + :ignore end end + + logger.debug { "-> #{request}" } + + send request, **options do |response, defer, cmd| + received response, defer, cmd, &handle_response + end end def generate_request_uuid SecureRandom.uuid end + + def handle_async(response) + logger.debug "Handling async rx for #{response}" + end end + class CaseInsensitiveHash < ActiveSupport::HashWithIndifferentAccess def [](key) super convert_key(key) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 6e4db843..f3b8c7bb 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -54,7 +54,7 @@ def section(message) # ------------------------------------------------------------------------- - section 'Protected base methods - ignore the access warnings' + section 'Base comms (protected methods - ignore the access warnings)' # Append a request id and handle generic response parsing exec(:do_send, 'xCommand Standby Deactivate') @@ -79,6 +79,30 @@ def section(message) .responds("Command not recognized.\n") expect { result }.to raise_error(Orchestrator::Error::CommandFailure) + # Handle async response data + exec(:do_send, 'xCommand Standby Deactivate') + .should_send("xCommand Standby Deactivate | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "RandomAsyncData": "Foo" + } + JSON + ) + .responds( + <<~JSON + { + "CommandResponse":{ + "StandbyDeactivateResult":{ + "status":"OK" + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + expect(result).to be :success + # Device event subscription exec(:subscribe, '/Status/Audio/Microphones/Mute') .should_send("xFeedback register /Status/Audio/Microphones/Mute | resultId=\"#{id_peek}\"\n") From e30ddfa4a7fcaca62fe8ac98502854f2d4d13576 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 25 Nov 2017 22:24:34 +1000 Subject: [PATCH 0031/1752] (cisco:spark) update xconfiguration to return either a unit :success or an array or all responses --- modules/cisco/spark/room_os.rb | 6 ++++-- modules/cisco/spark/room_os_spec.rb | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 86b6964c..5701bf9d 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -128,7 +128,7 @@ def send_xcommand(command, args = {}) def send_xconfiguration(path, settings) # The device API only allows a single setting to be applied with each # request. - apply_setting = lambda do |(setting, value)| + interactions = settings.to_a.map do |(setting, value)| request = Xapi::Action.xconfiguration path, setting, value do_send request, name: "#{path} #{setting}" do |response| @@ -144,7 +144,9 @@ def send_xconfiguration(path, settings) end end - thread.all settings.to_a.map(&apply_setting) + thread.all(interactions).then do |results| + results.all? { |result| result == :success } ? :success : results + end end # Subscribe to feedback from the device diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index f3b8c7bb..4bcfa2d1 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -210,7 +210,7 @@ def section(message) } JSON ) - expect(result).to match_array [:success] + expect(result).to be :success # Multuple settings exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Name: "Borris", Quality: :Motion) @@ -238,5 +238,5 @@ def section(message) } JSON ) - expect(result).to match_array [:success, :success, :success] + expect(result).to be :success end From 7687a61be65ab66f897a7924117be2b62d5ca585 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 26 Nov 2017 01:57:49 +1000 Subject: [PATCH 0032/1752] (cisco:spark) provide subscription mechanism for device feedback --- modules/cisco/spark/room_os.rb | 89 +++++++++++++++++++++++++++--- modules/cisco/spark/xapi/action.rb | 16 +++++- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 5701bf9d..a2b4d020 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -41,7 +41,9 @@ def connected send "xPreferences OutputMode JSON\n", wait: false end - def disconnected; end + def disconnected + clear_subscriptions! + end # Handle all incoming data from the device. # @@ -149,13 +151,73 @@ def send_xconfiguration(path, settings) end end - # Subscribe to feedback from the device - def subscribe(path) - request = Xapi::Action.xfeedback path + # Subscribe to feedback from the device. + # + # @param path [String, Array] the xPath to subscribe to updates for + # @param update_handler [Proc] a callback to receive updates for the path + def subscribe(path, &update_handler) + logger.debug { "Subscribing to device feedback for #{path}" } + + request = Xapi::Action.xfeedback :register, path + + # Build up a Trie based on the path components + @subscriptions ||= {} + path_components = xpath_split path + path_components.reduce(@subscriptions) do |node, component| + node[component] ||= {} + end + + # And insert the handler at it's appropriate node + node = @subscriptions.dig(*path_components) + node[:_handlers] ||= [] + node[:_handlers] << update_handler + + # Always returns an empty JSON object, no special response to handle + do_send request + end + + # Clear all subscribers from a path and deregister device feedback. + def unsubscribe(path) + logger.debug { "Unsubscribing feedback for #{path}" } + + request = Xapi::Action.xfeedback :deregister, path + + # Nuke the subtree from the subscription tracker + path_components = xpath_split path + if path_components.empty? + @subscriptions = {} + else + node = @subscriptions.dig(*path_components) + if node.nil? + logger.warn { "No subscriptions registered for #{path}" } + else + *parent_path, node_key = path_components + parent = if parent_path.empty? + @subscriptions + else + @subscriptions.dig(*parent_path) + end + parent.delete node_key + end + end + + do_send request + end + + # Clears any previously registered device feedback subscriptions + def clear_subscriptions! + unsubscribe '/' + end - do_send request do |response| - # Always returns empty JSON object, regardless of if xPath exists - :success + # Split a space or slash seperated path into it's components. + def xpath_split(path) + if path.is_a? Array + path + else + path.split(/[\s\/\\]/) + .reject(&:empty?) + .map(&:downcase) + .map(&:to_sym) end end @@ -200,6 +262,19 @@ def generate_request_uuid def handle_async(response) logger.debug "Handling async rx for #{response}" + + # Traverse the response where there are any subscriptions registered + notify = lambda do |subscriptions, resp| + resp.each do |key, subtree| + subpath = key.downcase.to_sym + next unless subscriptions.key? subpath + handlers = subscriptions.dig subpath, :_handlers + [*handlers].each { |handler| handler.call subtree } + notify.call subscriptions[subpath], subtree + end + end + + notify.call @subscriptions, response end end diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index 7149863f..22cb8dfa 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -16,6 +16,11 @@ module Cisco::Spark::Xapi::Action :xPreferences ] + FEEDBACK_ACTION ||= Set.new [ + :register, + :deregister + ] + module_function # Serialize an xAPI action into transmittable command. @@ -59,11 +64,18 @@ def xconfiguration(path, setting, value) # Serialize a xFeedback subscription request. # + # @param action [:register, :deregister] # @param path [String, Array] the feedback document path # @return [String] - def xfeedback(path) + def xfeedback(action, path) + unless FEEDBACK_ACTION.include? action + raise ArgumentError, + "Invalid feedback action. Must be one of #{FEEDBACK_ACTION}." + end + # Allow space or slash seperated paths path = path.split(/[\s\/\\]/).reject(&:empty?) if path.is_a? String - create_action :xFeedback, "register /#{path.join '/'}" + + create_action :xFeedback, "#{action} /#{path.join '/'}" end end From 17084ef15eafbf68b1a8e2ea959d6500a780eeca Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 26 Nov 2017 20:56:31 +1000 Subject: [PATCH 0033/1752] (cisco:spark) move subscription manager out into it's own class --- modules/cisco/spark/room_os.rb | 146 ++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 64 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index a2b4d020..8e9dd793 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -42,7 +42,7 @@ def connected end def disconnected - clear_subscriptions! + clear_subscriptions end # Handle all incoming data from the device. @@ -59,10 +59,10 @@ def received(data, deferrable, command) yield(response).tap do |command_result| # Otherwise support interleaved async events unprocessed = command_result == :ignore || command_result.nil? - handle_async response if unprocessed + @subscriptions&.notify response if unprocessed end else - handle_async response + @subscriptions&.notify response :ignore end rescue JSON::ParserError => error @@ -158,69 +158,34 @@ def send_xconfiguration(path, settings) def subscribe(path, &update_handler) logger.debug { "Subscribing to device feedback for #{path}" } - request = Xapi::Action.xfeedback :register, path + @subscriptions ||= FeedbackTrie.new - # Build up a Trie based on the path components - @subscriptions ||= {} - path_components = xpath_split path - path_components.reduce(@subscriptions) do |node, component| - node[component] ||= {} + unless @subscriptions.contains? path + request = Xapi::Action.xfeedback :register, path + # Always returns an empty JSON object, no special response to handle + result = do_send request end - # And insert the handler at it's appropriate node - node = @subscriptions.dig(*path_components) - node[:_handlers] ||= [] - node[:_handlers] << update_handler + @subscriptions.insert path, &update_handler - # Always returns an empty JSON object, no special response to handle - do_send request + result || ::Libuv::Q::ResolvedPromise.new(thread, :success) end # Clear all subscribers from a path and deregister device feedback. def unsubscribe(path) logger.debug { "Unsubscribing feedback for #{path}" } - request = Xapi::Action.xfeedback :deregister, path - - # Nuke the subtree from the subscription tracker - path_components = xpath_split path - if path_components.empty? - @subscriptions = {} - else - node = @subscriptions.dig(*path_components) - if node.nil? - logger.warn { "No subscriptions registered for #{path}" } - else - *parent_path, node_key = path_components - parent = if parent_path.empty? - @subscriptions - else - @subscriptions.dig(*parent_path) - end - parent.delete node_key - end - end + @subscriptions&.remove path + request = Xapi::Action.xfeedback :deregister, path do_send request end # Clears any previously registered device feedback subscriptions - def clear_subscriptions! + def clear_subscriptions unsubscribe '/' end - # Split a space or slash seperated path into it's components. - def xpath_split(path) - if path.is_a? Array - path - else - path.split(/[\s\/\\]/) - .reject(&:empty?) - .map(&:downcase) - .map(&:to_sym) - end - end - # Execute raw command on the device. # # Automatically appends a result tag and handles routing of response @@ -260,22 +225,6 @@ def generate_request_uuid SecureRandom.uuid end - def handle_async(response) - logger.debug "Handling async rx for #{response}" - - # Traverse the response where there are any subscriptions registered - notify = lambda do |subscriptions, resp| - resp.each do |key, subtree| - subpath = key.downcase.to_sym - next unless subscriptions.key? subpath - handlers = subscriptions.dig subpath, :_handlers - [*handlers].each { |handler| handler.call subtree } - notify.call subscriptions[subpath], subtree - end - end - - notify.call @subscriptions, response - end end @@ -290,3 +239,72 @@ def convert_key(key) key.respond_to?(:downcase) ? key.downcase : key end end + + +class Cisco::Spark::RoomOs::FeedbackTrie < CaseInsensitiveHash + # Insert a response handler block to be notified of updates effecting the + # specified feedback path. + def insert(path, &handler) + node = tokenize(path).reduce(self) do |trie, token| + trie[token] ||= self.class.new + end + + node << handler + + self + end + + # Nuke a subtree below the path + def remove(path) + path_components = tokenize path + + if path_components.empty? + clear + @handlers&.clear + else + *parent_path, node_key = path_components + parent = parent_path.empty? ? self : dig(*parent_path) + parent&.delete node_key + end + + self + end + + def contains?(path) + !dig(*tokenize(path)).nil? + end + + # Propogate a response throughout the trie + def notify(response) + response.each do |key, value| + node = self[key] + next unless node + node.dispatch value + node.notify value + end + end + + protected + + # Append a rx handler block to this node. + def <<(blk) + @handlers ||= [] + @handlers << blk + end + + # Dispatch to all handlers registered on this node. + def dispatch(value) + [*@handlers].each { |handler| handler.call value } + end + + def tokenize(path) + if path.is_a? Array + path + else + path.split(/[\s\/\\]/) + .reject(&:empty?) + .map(&:downcase) + .map(&:to_sym) + end + end +end From c0f04c739496cd8d0861a0d9edb61ccc80dcf588 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 26 Nov 2017 21:50:27 +1000 Subject: [PATCH 0034/1752] (cisco:spark) minor neaten up --- modules/cisco/spark/room_os.rb | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 8e9dd793..e654f606 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -48,7 +48,7 @@ def disconnected # Handle all incoming data from the device. # # In addition to acting an the normal Orchestrator callback, on_receive - # blocks also pipe through here for initial JSON decoding. See #do_send. + # procs also pipe through here for initial JSON decoding. See #do_send. def received(data, deferrable, command) logger.debug { "<- #{data}" } @@ -58,8 +58,8 @@ def received(data, deferrable, command) # Let any pending command response handlers have first pass... yield(response).tap do |command_result| # Otherwise support interleaved async events - unprocessed = command_result == :ignore || command_result.nil? - @subscriptions&.notify response if unprocessed + unhandled = command_result == :ignore || command_result.nil? + @subscriptions&.notify response if unhandled end else @subscriptions&.notify response @@ -128,8 +128,7 @@ def send_xcommand(command, args = {}) end def send_xconfiguration(path, settings) - # The device API only allows a single setting to be applied with each - # request. + # The API only allows a single setting to be applied with each request. interactions = settings.to_a.map do |(setting, value)| request = Xapi::Action.xconfiguration path, setting, value @@ -162,7 +161,7 @@ def subscribe(path, &update_handler) unless @subscriptions.contains? path request = Xapi::Action.xfeedback :register, path - # Always returns an empty JSON object, no special response to handle + # Always returns an empty response, nothing special to handle result = do_send request end @@ -171,7 +170,6 @@ def subscribe(path, &update_handler) result || ::Libuv::Q::ResolvedPromise.new(thread, :success) end - # Clear all subscribers from a path and deregister device feedback. def unsubscribe(path) logger.debug { "Unsubscribing feedback for #{path}" } @@ -181,17 +179,12 @@ def unsubscribe(path) do_send request end - # Clears any previously registered device feedback subscriptions def clear_subscriptions unsubscribe '/' end # Execute raw command on the device. # - # Automatically appends a result tag and handles routing of response - # handling for async interactions. If a block is passed a pre-parsed - # response object will be yielded to it. - # # @param command [String] the raw command to execute # @param options [Hash] options for the transport layer # @yield [response] From 3977f13b66c27dd227c35557c1c0c5bba9e1fb83 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 26 Nov 2017 21:51:52 +1000 Subject: [PATCH 0035/1752] (cisco:spark) fix issue with CaseInsensitiveHash not allowing indifferent access --- modules/cisco/spark/room_os.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index e654f606..5fbd4da3 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -229,7 +229,7 @@ def [](key) protected def convert_key(key) - key.respond_to?(:downcase) ? key.downcase : key + super(key.try(:downcase) || key) end end @@ -294,10 +294,7 @@ def tokenize(path) if path.is_a? Array path else - path.split(/[\s\/\\]/) - .reject(&:empty?) - .map(&:downcase) - .map(&:to_sym) + path.split(/[\s\/\\]/).reject(&:empty?) end end end From 481d7f262484dcd30e4be5e64656229c54daff0d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 26 Nov 2017 21:53:47 +1000 Subject: [PATCH 0036/1752] (cisco:spark) fix error when bottoming out on walking a response --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 5fbd4da3..75b9aae2 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -269,7 +269,7 @@ def contains?(path) # Propogate a response throughout the trie def notify(response) - response.each do |key, value| + response.try(:each) do |key, value| node = self[key] next unless node node.dispatch value From a3909dc59ada44c6e98f5c05e1b5dc2cac922df7 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 26 Nov 2017 22:15:32 +1000 Subject: [PATCH 0037/1752] (cisco:spark) sync state on connect, subscribe to configuration updates --- modules/cisco/spark/room_os.rb | 11 ++- modules/cisco/spark/room_os_spec.rb | 107 +++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 75b9aae2..cc4001ee 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -39,6 +39,8 @@ def on_update; end def connected send "Echo off\n", wait: false, priority: 96 send "xPreferences OutputMode JSON\n", wait: false + + subscribe_to_configuration end def disconnected @@ -183,6 +185,14 @@ def clear_subscriptions unsubscribe '/' end + def subscribe_to_configuration + subscribe '/Configuration' do |configuration| + self[:configuration] = configuration + end + + send "xConfiguration *\n", wait: false + end + # Execute raw command on the device. # # @param command [String] the raw command to execute @@ -217,7 +227,6 @@ def do_send(command, **options) def generate_request_uuid SecureRandom.uuid end - end diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 4bcfa2d1..0e9257b1 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -52,6 +52,112 @@ def section(message) should_send "Echo off\n" should_send "xPreferences OutputMode JSON\n" + # ------------------------------------------------------------------------- + section 'Initial state sync' + + should_send "xFeedback register /Configuration | resultId=\"#{id_peek}\"\n" + responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + + should_send "xConfiguration *\n" + + responds( + <<~JSON + { + "Configuration":{ + "Audio":{ + "DefaultVolume":{ + "valueSpaceRef":"/Valuespace/INT_0_100", + "Value":"50" + }, + "Input":{ + "Line":[ + { + "id":"1", + "VideoAssociation":{ + "MuteOnInactiveVideo":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"On" + }, + "VideoInputSource":{ + "valueSpaceRef":"/Valuespace/TTPAR_PresentationSources_2", + "Value":"2" + } + } + } + ], + "Microphone":[ + { + "id":"1", + "EchoControl":{ + "Dereverberation":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"Off" + }, + "Mode":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"On" + }, + "NoiseReduction":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"On" + } + }, + "Level":{ + "valueSpaceRef":"/Valuespace/INT_0_24", + "Value":"14" + }, + "Mode":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"On" + } + }, + { + "id":"2", + "EchoControl":{ + "Dereverberation":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"Off" + }, + "Mode":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"On" + }, + "NoiseReduction":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"On" + } + }, + "Level":{ + "valueSpaceRef":"/Valuespace/INT_0_24", + "Value":"14" + }, + "Mode":{ + "valueSpaceRef":"/Valuespace/TTPAR_OnOff", + "Value":"On" + } + } + ] + }, + "Microphones":{ + "Mute":{ + "Enabled":{ + "valueSpaceRef":"/Valuespace/TTPAR_MuteEnabled", + "Value":"True" + } + } + } + } + } + } + JSON + ) + expect(status[:configuration].dig(:audio, :input, :microphone, 1, :mode, :value)).to eq "On" # ------------------------------------------------------------------------- section 'Base comms (protected methods - ignore the access warnings)' @@ -115,7 +221,6 @@ def section(message) ) expect(result).to be :success - # ------------------------------------------------------------------------- section 'Commands' From 4f8af85a090ba0092a394ed5e2f8aee1f7116e1c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 00:59:02 +1100 Subject: [PATCH 0038/1752] Add domino library and remove device files --- lib/ibm/domino.rb | 91 +++++++++ modules/ibm/domino/bookings.rb | 229 ----------------------- modules/ibm/domino/notes_calendar.rb | 269 --------------------------- 3 files changed, 91 insertions(+), 498 deletions(-) create mode 100644 lib/ibm/domino.rb delete mode 100644 modules/ibm/domino/bookings.rb delete mode 100644 modules/ibm/domino/notes_calendar.rb diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb new file mode 100644 index 00000000..40694790 --- /dev/null +++ b/lib/ibm/domino.rb @@ -0,0 +1,91 @@ +# domino = IBM::Domino.new({ +# username: nil, +# password: nil, +# auth_hash: 'T1RTIFRlc3QxIFByb2plY3QgU0c6UEBzc3cwcmQxMjM=', +# domain: 'http://sg-mbxpwv001.cn.asia.ad.pwcinternal.com', +# timezone: 'Singapore' +# }) +# res = nil +# reactor.run { +# res = domino.get_bookings('mail/generic/otstest1projectsg.nsf', (Time.now - 1.day).midnight, (Time.now - 1.day).midnight + 3.days) +# } +require 'active_support/time' +module IBM; end + +class IBM::Domino + def initialize( + username:, + password:, + auth_hash:, + domain:, + timezone: + ) + @domain = domain + @headers = { + 'Authorization' => "Basic #{auth_hash}", + 'Content-Type' => 'application/json' + } + @domino_api = UV::HttpEndpoint.new(@domain, {inactivity_timeout: 25000}) + end + + def to_ibm_date(time) + time.strftime("%Y-%m-%dT%H:%M:%SZ") + end + + + def get_bookings(database, starting, ending, days=nil) + + # Set our unchanging path + path = "#{@domain}/#{database}/api/calendar/events" + + # Sent count to max + query = { + count: 100 + } + + # If we have a range use it + if starting + query[:since] = to_ibm_date(starting) + query[:before] = to_ibm_date(ending) + end + + response = @domino_api.get(path: path, headers: @headers, body: nil, query: query).value + domino_bookings = JSON.parse(response.body)['events'] + + bookings = [] + + domino_bookings.each{ |booking| + # booking = domino_bookings.sample + bookings.push(get_attendees(booking, database)) + } + bookings + end + + def get_attendees(booking, database) + path = "#{@domain}/#{database}/api/calendar/events/#{booking['id']}" + # puts "Attendee path is #{path}" + booking_request = @domino_api.get(path: path, headers: @headers).value + booking_response = JSON.parse(booking_request.body)['events'][0] + if booking_response['attendees'] + attendees = booking_response['attendees'].dup + attendees.map!{ |b| + { + name: b['displayName'], + email: b['email'] + } + } + booking_response['attendees'] = attendees + end + if booking_response['organizer'] + organizer = booking_response['organizer'].dup + organizer = + { + name: organizer['displayName'], + email: organizer['email'] + } + booking_response['organizer'] = organizer + end + booking_response + end +end + diff --git a/modules/ibm/domino/bookings.rb b/modules/ibm/domino/bookings.rb deleted file mode 100644 index da8a47eb..00000000 --- a/modules/ibm/domino/bookings.rb +++ /dev/null @@ -1,229 +0,0 @@ -# encoding: ASCII-8BIT - - -# For rounding up to the nearest 15min -# See: http://stackoverflow.com/questions/449271/how-to-round-a-time-down-to-the-nearest-15-minutes-in-ruby -class ActiveSupport::TimeWithZone - def ceil(seconds = 60) - return self if seconds.zero? - Time.at(((self - self.utc_offset).to_f / seconds).ceil * seconds).in_time_zone + self.utc_offset - end -end - - -module IBM; end -module IBM::Domino; end - - -class IBM::Domino::Bookings - include ::Orchestrator::Constants - - descriptive_name 'IBM Domino Room Bookings' - generic_name :Bookings - implements :logic - - # The room we are interested in - default_settings({ - update_every: '2m' - }) - - - def on_load - on_update - end - - def on_update - self[:hide_all] = setting(:hide_all) || false - self[:touch_enabled] = setting(:touch_enabled) || false - self[:name] = self[:room_name] = setting(:room_name) || system.name - - self[:control_url] = setting(:booking_control_url) || system.config.support_url - self[:booking_controls] = setting(:booking_controls) - self[:booking_catering] = setting(:booking_catering) - self[:booking_hide_details] = setting(:booking_hide_details) - self[:booking_hide_availability] = setting(:booking_hide_availability) - self[:booking_hide_user] = setting(:booking_hide_user) - self[:booking_hide_description] = setting(:booking_hide_description) - self[:booking_hide_timeline] = setting(:booking_hide_timeline) - - # Is there catering available for this room? - self[:catering] = setting(:catering_system_id) - if self[:catering] - self[:menu] = setting(:menu) - end - - # Load the last known values (persisted to the DB) - self[:waiter_status] = (setting(:waiter_status) || :idle).to_sym - self[:waiter_call] = self[:waiter_status] != :idle - - self[:catering_status] = setting(:last_catering_status) || {} - self[:order_status] = :idle - - self[:last_meeting_started] = setting(:last_meeting_started) - self[:cancel_meeting_after] = setting(:cancel_meeting_after) - - fetch_bookings - schedule.clear - schedule.every(setting(:update_every) || '5m') { fetch_bookings } - end - - - def set_light_status(status) - lightbar = system[:StatusLight] - return if lightbar.nil? - - case status.to_sym - when :unavailable - lightbar.colour(:red) - when :available - lightbar.colour(:green) - when :pending - lightbar.colour(:orange) - else - lightbar.colour(:off) - end - end - - - # ====================================== - # Waiter call information - # ====================================== - def waiter_call(state) - status = is_affirmative?(state) - - self[:waiter_call] = status - - # Used to highlight the service button - if status - self[:waiter_status] = :pending - else - self[:waiter_status] = :idle - end - - define_setting(:waiter_status, self[:waiter_status]) - end - - def call_acknowledged - self[:waiter_status] = :accepted - define_setting(:waiter_status, self[:waiter_status]) - end - - - # ====================================== - # Catering Management - # ====================================== - def catering_status(details) - self[:catering_status] = details - - # We'll turn off the green light on the waiter call button - if self[:waiter_status] != :idle && details[:progress] == 'visited' - self[:waiter_call] = false - self[:waiter_status] = :idle - define_setting(:waiter_status, self[:waiter_status]) - end - - define_setting(:last_catering_status, details) - end - - def commit_order(order_details) - self[:order_status] = :pending - status = self[:catering_status] - - if status && status[:progress] == 'visited' - status = status.dup - status[:progress] = 'cleaned' - self[:catering_status] = status - end - - if self[:catering] - sys = system - @oid ||= 1 - systems(self[:catering])[:Orders].add_order({ - id: "#{sys.id}_#{@oid}", - created_at: Time.now.to_i, - room_id: sys.id, - room_name: sys.name, - order: order_details - }) - end - end - - def order_accepted - self[:order_status] = :accepted - end - - def order_complete - self[:order_status] = :idle - end - - - - # ====================================== - # ROOM BOOKINGS: - # ====================================== - def fetch_bookings - self[:today] = system[:Calendar].events.value.collect do |event| - { - :Start => event[:starting].utc.iso8601[0..18], - :End => event[:ending].utc.iso8601[0..18], - :Subject => event[:summary], - :owner => event[:organizer] || '' - # :setup => 0, - # :breakdown => 0 - } - end - end - - - # ====================================== - # Meeting Helper Functions - # ====================================== - - def start_meeting(meeting_ref) - self[:last_meeting_started] = meeting_ref - self[:meeting_pending] = meeting_ref - self[:meeting_ending] = false - self[:meeting_pending_notice] = false - define_setting(:last_meeting_started, meeting_ref) - end - - def cancel_meeting(start_time) - calendar = system[:Calendar] - events = calendar.events.value.keep_if do |event| - event[:start].to_i == start_time - end - events.each do |event| - calendar.cancel_booking(event) - end - end - - # If last meeting started !== meeting pending then - # we'll show a warning on the in room touch panel - def set_meeting_pending(meeting_ref) - self[:meeting_ending] = false - self[:meeting_pending] = meeting_ref - self[:meeting_pending_notice] = true - end - - # Meeting ending warning indicator - # (When meeting_ending !== last_meeting_started then the warning hasn't been cleared) - # The warning is only displayed when meeting_ending === true - def set_end_meeting_warning(meeting_ref = nil, extendable = false) - if self[:last_meeting_started].nil? || self[:meeting_ending] != (meeting_ref || self[:last_meeting_started]) - self[:meeting_ending] = true - - # Allows meeting ending warnings in all rooms - self[:last_meeting_started] = meeting_ref if meeting_ref - self[:meeting_canbe_extended] = extendable - end - end - - def clear_end_meeting_warning - self[:meeting_ending] = self[:last_meeting_started] - end - # --------- - - def create_meeting(options) - - end -end diff --git a/modules/ibm/domino/notes_calendar.rb b/modules/ibm/domino/notes_calendar.rb deleted file mode 100644 index 6fb38294..00000000 --- a/modules/ibm/domino/notes_calendar.rb +++ /dev/null @@ -1,269 +0,0 @@ - - -module IBM; end -module IBM::Domino; end - -# Documentation: https://aca.im/driver_docs/IBM/Domino+Freebusy+Service.pdf -# also https://www-10.lotus.com/ldd/ddwiki.nsf/xpAPIViewer.xsp?lookupName=IBM+Domino+Access+Services+9.0.1#action=openDocument&res_title=Calendar_events_GET&content=apicontent -# also https://www-10.lotus.com/ldd/ddwiki.nsf/xpAPIViewer.xsp?lookupName=IBM+Domino+Access+Services+9.0.1#action=openDocument&res_title=JSON_representation_of_an_event_das901&content=apicontent - -class IBM::Domino::NotesCalendar - include ::Orchestrator::Constants - include ::Orchestrator::Transcoder - - # Discovery Information - implements :service - descriptive_name 'IBM Notes Calendar' - generic_name :Calendar - - default_settings({ - 'timezone' => 'Singapore', - 'database' => 'room-name.nsf', - 'username' => 'username', - '$password' => 'password' - }) - - def on_load - on_update - end - - def on_update - self[:timezone] = @timezone = setting(:timezone) - self[:timezone] = @username = setting(:username) - @password = setting(:password) - self[:timezone] = @database = setting(:database) - - @headers = { - authorization: [@username, @password] - } - end - - DECODE_OPTIONS = { - symbolize_names: true - }.freeze - - - # ================= - # Free Busy Service - # ================= - def free_rooms(building:, starting:, ending:, capacity: nil, timezone: @timezone) - starting, ending = get_time_range(starting, ending, timezone) - params = { - :site => building, - :start => starting.utc.iso8601, - :end => ending.utc.iso8601 - } - params[:capacity] = capacity.to_i if capacity - - get('/api/freebusy/freerooms', query: params, headers: @headers) do |data| - if data.status == 200 - JSON.parse(data.body, DECODE_OPTIONS)[:rooms] - else; :retry; end - end - end - - def user_busy(email:, starting: nil, ending: nil, days: nil, timezone: @timezone) - since, before = get_time_range(starting, ending, timezone) - params = { - name: email, - since: since.utc.iso8601 - } - if days - params[:days] = days - else - params[:before] = before.utc.iso8601 - end - - get('/api/freebusy/busytime', query: params, headers: @headers) do |data| - if data.status == 200 - times = JSON.parse(data.body, DECODE_OPTIONS)[:busyTimes] - times.collect do |period| - s = period[:start] - e = period[:end] - { - starting: Time.iso8601("#{s[:date]}T#{s[:time]}Z"), - ending: Time.iso8601("#{e[:date]}T#{e[:time]}Z") - } - end - else; :retry; end - end - end - - def directories - get('/api/freebusy/directories', headers: @headers) do |data| - if data.status == 200 - JSON.parse(data.body, DECODE_OPTIONS) - else; :retry; end - end - end - - def sites(directory) - get("/api/freebusy/sites/#{directory}", headers: @headers) do |data| - if data.status == 200 - JSON.parse(data.body, DECODE_OPTIONS) - else; :retry; end - end - end - - # ===================== - # Domino Access Service - # ===================== - def bookings(starting: nil, ending: nil, timezone: @timezone, count: 100, start: 0, fields: nil, **opts) - since, before = get_time_range(starting, ending, timezone) - query = { - start: start, - count: count, - since: since.utc.iso8601, - before: before.utc.iso8601, - } - query[:fields] = Array(fields).join(',') if fields.present? - get("/mail/#{@database}/api/calendar/events", query: query, headers: @headers) do |data| - return :retry unless data.status == 200 - parse(JSON.parse("[#{data.body}]", DECODE_OPTIONS)[0]) - end - end - - def cancel_booking(id:, recurrenceId: nil, email_attendees: false, **opts) - query = {} - query[:workflow] = false unless email_attendees - - uri = if recurrenceId - "/mail/#{@database}/api/calendar/events/#{id}/#{recurrenceId}" - else - "/mail/#{@database}/api/calendar/events/#{id}" - end - - delete(uri, query: query, headers: @headers) do |data| - logger.warn "unable to delete meeting #{id} as it could not be found" if data.status == 404 - :success - end - end - - def create_booking(starting:, ending:, summary:, location: nil, description: nil, organizer: nil, email_attendees: false, attendees: [], timezone: @timezone, **opts) - event = { - :summary => summary, - :class => :public, - :start => to_date(starting), - :end => to_date(ending) - } - - event[:location] = location if location - event[:description] = description if description - - # The value can be either "application/json" or "text/calendar" - headers = { - 'content-type' => 'application/json' - } - - # If workflow=false, the service doesn't send out any invitations - query = {} - query[:workflow] = false unless email_attendees - - event[:attendees] = Array(attendees).collect do |attendee| - { - role: "req-participant", - email: attendee - } - end - - if organizer - event[:organizer] = { - email: organizer - } - - # Chair has permissions to edit the event - event[:attendees].push({ - role: "chair", - email: organizer - }) - end - - post("/mail/#{@database}/api/calendar/events", { - query: query, - headers: @headers.merge(headers), - body: { events: [event] }.to_json - }) do |data| - if data.status == 201 - parse(JSON.parse(data.body, DECODE_OPTIONS)) - else; :retry; end - end - end - - - protected - - - def get_time_range(starting, ending, timezone) - return [starting, ending] if starting.is_a?(Time) - - Time.zone = timezone - start = starting.nil? ? Time.zone.today.to_time : Time.zone.parse(starting) - fin = ending.nil? ? Time.zone.tomorrow.to_time : Time.zone.parse(ending) - [start, fin] - end - - def to_date(time) - if time.is_a?(String) - Time.zone = @timezone - time = Time.zone.parse(time) - elsif time.is_a?(Integer) - time = Time.at(time) - end - - utctime = time.getutc - { - date: utctime.strftime("%Y-%m-%d"), - time: utctime.strftime("%H-%M-%S"), - utc: true - } - end - - TIMEZONE_MAP = { - 'eastern' => 'Eastern Time (US & Canada)' - } - - def parse(response) - return [] if response.nil? - - events = response[:events] - Array(events).collect do |event| - ev = { - resource: event[:href], - id: event[:id], - summary: event[:summary], - location: event[:location] - } - - ev[:recurrenceId] = event[:recurrenceId] if event[:recurrenceId] - - # Generate ruby time objects for easy manipulation - time = event[:start] - if time[:utc] - ev[:starting] = Time.iso8601("#{time[:date]}T#{time[:time]}Z") - time = event[:end] - ev[:ending] = Time.iso8601("#{time[:date]}T#{time[:time]}Z") - else - tz = TIMEZONE_MAP[time[:tzid].downcase] - if tz - Time.zone = tz - else - Time.zone = "UTC" - logger.warn "Could not find timezone #{time[:tzid]}" - end - ev[:starting] = Time.zone.iso8601("#{time[:date]}T#{time[:time]}") - time = event[:end] - ev[:ending] = Time.zone.iso8601("#{time[:date]}T#{time[:time]}") - end - - ev[:organizer] = if event[:organizer] - # Domino returns: "Darius Servino Oco\/SG\/GTS\/PwC" - event[:organizer][:displayName].split('/')[0] - elsif event[:"x-lotus-organizer"] - # Domino returns: "CN=Darius Servino Oco\/OU=SG\/OU=GTS\/O=PwC" - event[:"x-lotus-organizer"][:data].split("CN=")[1]&.split('/')[0] - end - - ev - end - end -end From 63fa8d1f680d33476800cbe9355ce4a7e3d381bb Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Nov 2017 12:08:52 +1000 Subject: [PATCH 0039/1752] (cisco:spark) fix issue with configuration errors not being detected --- modules/cisco/spark/room_os.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index cc4001ee..8003f16b 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -137,9 +137,10 @@ def send_xconfiguration(path, settings) do_send request, name: "#{path} #{setting}" do |response| result = response.dig 'CommandResponse', 'Configuration' - if result&.[] 'status' == 'Error' + if result&.[]('status') == 'Error' reason = result.dig 'Reason', 'Value' - logger.error reason if reason + xpath = result.dig 'XPath', 'Value' + logger.error "#{reason} (#{xpath})" if reason :abort else :success From 50e42dcfc6ad817cca866707b70c01620baddc4f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 13:51:45 +1100 Subject: [PATCH 0040/1752] Update domino library to do CRUD --- lib/ibm/domino.rb | 169 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 144 insertions(+), 25 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 40694790..9aa86579 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -1,14 +1,3 @@ -# domino = IBM::Domino.new({ -# username: nil, -# password: nil, -# auth_hash: 'T1RTIFRlc3QxIFByb2plY3QgU0c6UEBzc3cwcmQxMjM=', -# domain: 'http://sg-mbxpwv001.cn.asia.ad.pwcinternal.com', -# timezone: 'Singapore' -# }) -# res = nil -# reactor.run { -# res = domino.get_bookings('mail/generic/otstest1projectsg.nsf', (Time.now - 1.day).midnight, (Time.now - 1.day).midnight + 3.days) -# } require 'active_support/time' module IBM; end @@ -21,6 +10,7 @@ def initialize( timezone: ) @domain = domain + @timeone = timezone @headers = { 'Authorization' => "Basic #{auth_hash}", 'Content-Type' => 'application/json' @@ -28,17 +18,34 @@ def initialize( @domino_api = UV::HttpEndpoint.new(@domain, {inactivity_timeout: 25000}) end - def to_ibm_date(time) - time.strftime("%Y-%m-%dT%H:%M:%SZ") + def domino_request(request_method, endpoint, data = nil, query = {}, headers = {}) + # Convert our request method to a symbol and our data to a JSON string + request_method = request_method.to_sym + data = data.to_json if !data.nil? && data.class != String + + @headers.merge(headers) if headers + + domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" + response = @domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) end + def get_free_rooms(starting, ending) + starting, ending = get_time_range(starting, ending, @timezone) + + req_params = { + :site => ENV["DOMINO_SITE"], + :start => to_ibm_date(starting), + :end => to_ibm_date(ending) + } + + res = domino_request('get','/api/freebusy/freerooms', nil, req_params).value + domino_emails = JSON.parse(res.body)['rooms'] + end - def get_bookings(database, starting, ending, days=nil) - # Set our unchanging path - path = "#{@domain}/#{database}/api/calendar/events" - # Sent count to max + def get_bookings(database, starting, ending, days=nil) + # Set count to max query = { count: 100 } @@ -49,29 +56,118 @@ def get_bookings(database, starting, ending, days=nil) query[:before] = to_ibm_date(ending) end - response = @domino_api.get(path: path, headers: @headers, body: nil, query: query).value + # Get our bookings + response = domino_request('get', "/#{database}/api/calendar/events", nil, query).value domino_bookings = JSON.parse(response.body)['events'] + # Grab the attendee for each booking bookings = [] - domino_bookings.each{ |booking| - # booking = domino_bookings.sample bookings.push(get_attendees(booking, database)) } bookings end + + def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + event = { + :summary => summary, + :class => :public, + :start => to_utc_date(starting), + :end => to_utc_date(ending) + } + + event[:description] = description if description + + + event[:attendees] = Array(attendees).collect do |attendee| + { + role: "req-participant", + status: "needs-action", + rsvp: true, + displayName: attendee[:name], + email: attendee[:email] + } + end + + event[:organizer] = { + email: organizer[:email], + displayName: organizer[:name] + } + + # If there are attendees add the service account + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "displayName":"OTS Test1 Project SG/SG/R&R/PwC", + "email":"ots.test1.project.sg@sg.pwc.com" + }) if attendees + + request = domino_request('post', "/#{room}/api/calendar/events", {events: [event]}).value + JSON.parse(request.body)['events'][0] + end + + def delete_booking(room, id) + request = domino_request('delete', "/#{room}/api/calendar/events/#{id}").value.status + end + + + def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + event = { + :summary => summary, + :class => :public, + :start => to_utc_date(starting), + :end => to_utc_date(ending) + } + + query = {} + + if description + event[:description] = description + else + query[:literally] = true + end + + + event[:attendees] = Array(attendees).collect do |attendee| + { + role: "req-participant", + status: "needs-action", + rsvp: true, + displayName: attendee[:name], + email: attendee[:email] + } + end + + event[:organizer] = { + email: organizer[:email], + displayName: organizer[:name] + } + + # If there are attendees add the service account + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "displayName":"OTS Test1 Project SG/SG/R&R/PwC", + "email":"ots.test1.project.sg@sg.pwc.com" + }) if attendees + + request = domino_request('put', "/#{room}/api/calendar/events/#{id}", {events: [event]}, query).value + request.status + end + def get_attendees(booking, database) path = "#{@domain}/#{database}/api/calendar/events/#{booking['id']}" - # puts "Attendee path is #{path}" booking_request = @domino_api.get(path: path, headers: @headers).value booking_response = JSON.parse(booking_request.body)['events'][0] if booking_response['attendees'] attendees = booking_response['attendees'].dup - attendees.map!{ |b| + attendees.map!{ |attendee| { - name: b['displayName'], - email: b['email'] + name: attendee['displayName'], + email: attendee['email'] } } booking_response['attendees'] = attendees @@ -87,5 +183,28 @@ def get_attendees(booking, database) end booking_response end -end + def to_ibm_date(time) + time.strftime("%Y-%m-%dT%H:%M:%SZ") + end + + + def to_utc_date(time) + utctime = time.getutc + { + date: utctime.strftime("%Y-%m-%d"), + time: utctime.strftime("%H:%M:%S"), + utc: true + } + end + + def get_time_range(starting, ending, timezone) + return [starting, ending] if starting.is_a?(Time) + + Time.zone = timezone + start = starting.nil? ? Time.zone.today.to_time : Time.zone.parse(starting) + fin = ending.nil? ? Time.zone.tomorrow.to_time : Time.zone.parse(ending) + [start, fin] + end + +end From 147e6fdb9b3a42ca46775c09a2098e0874f54ace Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Nov 2017 12:39:10 +1000 Subject: [PATCH 0041/1752] (cisco:spark) fix issue when handling xconfiguration pushes that result in a partial failure --- modules/cisco/spark/room_os.rb | 8 ++++-- modules/cisco/spark/room_os_spec.rb | 44 ++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 8003f16b..ef12cf97 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -148,8 +148,12 @@ def send_xconfiguration(path, settings) end end - thread.all(interactions).then do |results| - results.all? { |result| result == :success } ? :success : results + thread.finally(interactions).then do |results| + if results.all? { |(_, resolved)| resolved == true } + :success + else + thread.defer.reject 'Could not apply all settings' + end end end diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 0e9257b1..b768650e 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -317,7 +317,7 @@ def section(message) ) expect(result).to be :success - # Multuple settings + # Multuple settings return a unit :success when all ok exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Name: "Borris", Quality: :Motion) .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") .responds( @@ -344,4 +344,46 @@ def section(message) JSON ) expect(result).to be :success + + # Multuple settings with failure with return a promise that rejects + exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Foo: "Bar", Quality: :Motion) + .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + .should_send("xConfiguration Video Input Connector 1 Foo: \"Bar\" | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "CommandResponse":{ + "Configuration":{ + "status":"Error", + "Reason":{ + "Value":"No match on address expression." + }, + "XPath":{ + "Value":"Configuration/Video/Input/Connector[1]/Foo" + } + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + .should_send("xConfiguration Video Input Connector 1 Quality: Motion | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + result.tap do |last_result| + expect(last_result.resolved?).to be true + expect { last_result.value }.to raise_error(CoroutineRejection) + end end From 23120cb2cf882fcabe40cc8e7c7afb409d71234e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Nov 2017 13:43:26 +1000 Subject: [PATCH 0042/1752] (cisco:spark) use thread helper to create resolved promise --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index ef12cf97..b972e331 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -174,7 +174,7 @@ def subscribe(path, &update_handler) @subscriptions.insert path, &update_handler - result || ::Libuv::Q::ResolvedPromise.new(thread, :success) + result || thread.defer.resolve(:success) end def unsubscribe(path) From b8642e3fc886db8d3971d983500bcfa226a2d3dc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:21:54 +1100 Subject: [PATCH 0043/1752] Fix minor date issues --- lib/ibm/domino.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 9aa86579..f1395935 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -30,6 +30,7 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { end def get_free_rooms(starting, ending) + starting, ending = convert_to_datetime(starting, ending) starting, ending = get_time_range(starting, ending, @timezone) req_params = { @@ -45,6 +46,7 @@ def get_free_rooms(starting, ending) def get_bookings(database, starting, ending, days=nil) + starting, ending = convert_to_datetime(starting, ending) # Set count to max query = { count: 100 @@ -70,6 +72,7 @@ def get_bookings(database, starting, ending, days=nil) def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -114,6 +117,7 @@ def delete_booking(room, id) def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -188,6 +192,23 @@ def to_ibm_date(time) time.strftime("%Y-%m-%dT%H:%M:%SZ") end + def convert_to_datetime(starting, ending) + if !(starting.class == Time) + if string_is_digits(starting) + starting = Time.at(starting) + ending = Time.at(ending) + else + starting = Time.parse(starting) + ending = Time.parse(ending) + end + end + starting, ending + end + + + def string_is_digits(string) + string.scan(/\D/).empty? + end def to_utc_date(time) utctime = time.getutc From 638ffd9a6357258453ab97aaba05143c9b924d5c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:25:41 +1100 Subject: [PATCH 0044/1752] Fix date issues --- lib/ibm/domino.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index f1395935..582884e9 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -195,6 +195,18 @@ def to_ibm_date(time) def convert_to_datetime(starting, ending) if !(starting.class == Time) if string_is_digits(starting) + + # Convert to an integer + starting = starting.to_i + ending = ending.to_i + + # If JavaScript epoch remove milliseconds + if starting.length == 13 + starting /= 1000 + ending /= 1000 + end + + # Convert to datetimes starting = Time.at(starting) ending = Time.at(ending) else From 7b9826115099e7bef82fef4b4e107e91dc719f4b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:29:18 +1100 Subject: [PATCH 0045/1752] Test syntax fix --- lib/ibm/domino.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 582884e9..df72a1ed 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -30,7 +30,7 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { end def get_free_rooms(starting, ending) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_datetime(starting, ending) starting, ending = get_time_range(starting, ending, @timezone) req_params = { @@ -46,7 +46,7 @@ def get_free_rooms(starting, ending) def get_bookings(database, starting, ending, days=nil) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_datetime(starting, ending) # Set count to max query = { count: 100 @@ -72,7 +72,7 @@ def get_bookings(database, starting, ending, days=nil) def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -117,7 +117,7 @@ def delete_booking(room, id) def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -214,7 +214,7 @@ def convert_to_datetime(starting, ending) ending = Time.parse(ending) end end - starting, ending + return starting end From ff439df06954d5831bc6d166f83a296f03fd4ea7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:31:25 +1100 Subject: [PATCH 0046/1752] Try yet another syntax --- lib/ibm/domino.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index df72a1ed..d494e45e 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -30,7 +30,7 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { end def get_free_rooms(starting, ending) - starting = convert_to_datetime(starting, ending) + starting, ending = convert_to_datetime(starting, ending) starting, ending = get_time_range(starting, ending, @timezone) req_params = { @@ -46,7 +46,7 @@ def get_free_rooms(starting, ending) def get_bookings(database, starting, ending, days=nil) - starting = convert_to_datetime(starting, ending) + starting, ending = convert_to_datetime(starting, ending) # Set count to max query = { count: 100 @@ -72,7 +72,7 @@ def get_bookings(database, starting, ending, days=nil) def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting = convert_to_datetime(starting, ending) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -117,7 +117,7 @@ def delete_booking(room, id) def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting = convert_to_datetime(starting, ending) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -214,7 +214,7 @@ def convert_to_datetime(starting, ending) ending = Time.parse(ending) end end - return starting + return starting, ending end From 615e18fe83f083151dc7fe6c97510c5791b1cf1f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:34:05 +1100 Subject: [PATCH 0047/1752] Fix another date issue --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d494e45e..82aa9750 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -201,7 +201,7 @@ def convert_to_datetime(starting, ending) ending = ending.to_i # If JavaScript epoch remove milliseconds - if starting.length == 13 + if starting.to_s.length == 13 starting /= 1000 ending /= 1000 end From 6cb7d3a62e4afba4b3ef61fa96f0b051c1003c06 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Nov 2017 14:36:37 +1000 Subject: [PATCH 0048/1752] (cisco:spark) provide friendly error message for partial failures --- modules/cisco/spark/room_os.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index b972e331..6169969d 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -149,10 +149,16 @@ def send_xconfiguration(path, settings) end thread.finally(interactions).then do |results| - if results.all? { |(_, resolved)| resolved == true } + resolved = results.map(&:last) + if resolved.all? :success else - thread.defer.reject 'Could not apply all settings' + failures = settings.keys + .reject + .with_index { |_, index| resolved[index] } + + thread.defer.reject 'Could not apply all settings. ' \ + "Failed on #{failures.join ', '}." end end end From dcb1c5a5017093597705785a73931a5fbf42983f Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 27 Nov 2017 13:09:39 +0800 Subject: [PATCH 0049/1752] Fix room CRUD --- lib/ibm/domino.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 82aa9750..5e2943c2 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -36,7 +36,8 @@ def get_free_rooms(starting, ending) req_params = { :site => ENV["DOMINO_SITE"], :start => to_ibm_date(starting), - :end => to_ibm_date(ending) + :end => to_ibm_date(ending), + :capacity => 1 } res = domino_request('get','/api/freebusy/freerooms', nil, req_params).value @@ -58,12 +59,19 @@ def get_bookings(database, starting, ending, days=nil) query[:before] = to_ibm_date(ending) end + Rails.logger.info "Getting bookings for" + Rails.logger.info "/#{database}/api/calendar/events" + # Get our bookings response = domino_request('get', "/#{database}/api/calendar/events", nil, query).value domino_bookings = JSON.parse(response.body)['events'] # Grab the attendee for each booking bookings = [] + if response.status == 200 && response.body.nil? + Rails.logger.info "Got empty response" + domino_bookings = [] + end domino_bookings.each{ |booking| bookings.push(get_attendees(booking, database)) } From 2004b5c5eacc8fd0f302906a45dbb03772fbcdd1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 16:20:38 +1100 Subject: [PATCH 0050/1752] Fix room database check --- lib/ibm/domino.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 5e2943c2..5d5df146 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -46,7 +46,10 @@ def get_free_rooms(starting, ending) - def get_bookings(database, starting, ending, days=nil) + def get_bookings(room_id, starting, ending, days=nil) + room = Orchestrator::ControlSystem.find(room_id) + Rails.logger.info "Getting bookings for #{room.name}" + database = room.settings['database'] starting, ending = convert_to_datetime(starting, ending) # Set count to max query = { From dc3a08be5d832ad495d3396ad99b0775832129b8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 16:57:50 +1100 Subject: [PATCH 0051/1752] Fix error response logic --- lib/ibm/domino.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 5d5df146..152632ff 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -48,7 +48,6 @@ def get_free_rooms(starting, ending) def get_bookings(room_id, starting, ending, days=nil) room = Orchestrator::ControlSystem.find(room_id) - Rails.logger.info "Getting bookings for #{room.name}" database = room.settings['database'] starting, ending = convert_to_datetime(starting, ending) # Set count to max @@ -62,19 +61,20 @@ def get_bookings(room_id, starting, ending, days=nil) query[:before] = to_ibm_date(ending) end - Rails.logger.info "Getting bookings for" - Rails.logger.info "/#{database}/api/calendar/events" # Get our bookings response = domino_request('get', "/#{database}/api/calendar/events", nil, query).value - domino_bookings = JSON.parse(response.body)['events'] - + case response.status + when 204 + domino_bookings = [] + when 200 + domino_bookings = JSON.parse(response.body)['events'] + else + raise "Unexpected Response: #{response.status} \n #{response.body}" + end # Grab the attendee for each booking bookings = [] - if response.status == 200 && response.body.nil? - Rails.logger.info "Got empty response" - domino_bookings = [] - end + domino_bookings.each{ |booking| bookings.push(get_attendees(booking, database)) } From b391c5b98fd68798dcbb8e329ab66882f03ec4c7 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Nov 2017 23:48:01 +1000 Subject: [PATCH 0052/1752] (cisco:spark) support both slash and space seperated paths --- modules/cisco/spark/xapi/action.rb | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index 22cb8dfa..eb8d54f1 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -26,21 +26,21 @@ module Cisco::Spark::Xapi::Action # Serialize an xAPI action into transmittable command. # # @param type [ACTION_TYPE] the type of action to execute - # @param path [String, Array] the action path - # @param args [Hash] an optional hash of keyword arguments for the action + # @param args [String, Array] the action args + # @param kwargs [Hash] an optional hash of keyword arguments for the action # @return [String] - def create_action(type, path, args = {}) + def create_action(type, *args, **kwargs) unless ACTION_TYPE.include? type raise ArgumentError, "Invalid action type. Must be one of #{ACTION_TYPE}." end - args = args.map do |name, value| + kwargs = kwargs.map do |name, value| value = "\"#{value}\"" if value.is_a? String "#{name}: #{value}" end - [type, path, args].flatten.join ' ' + [type, args, kwargs].flatten.join ' ' end # Serialize an xCommand into transmittable command. @@ -48,8 +48,8 @@ def create_action(type, path, args = {}) # @param path [String, Array] command path # @param args [Hash] an optional hash of keyword arguments # @return [String] - def xcommand(path, args = {}) - create_action :xCommand, path, args + def xcommand(path, **args) + create_action :xCommand, tokenize(path), args end # Serialize an xConfiguration action into a transmittable command. @@ -59,7 +59,7 @@ def xcommand(path, args = {}) # @param value the configuration value to apply # @return [String] def xconfiguration(path, setting, value) - create_action :xConfiguration, path, setting => value + create_action :xConfiguration, tokenize(path), setting => value end # Serialize a xFeedback subscription request. @@ -73,9 +73,11 @@ def xfeedback(action, path) "Invalid feedback action. Must be one of #{FEEDBACK_ACTION}." end - # Allow space or slash seperated paths - path = path.split(/[\s\/\\]/).reject(&:empty?) if path.is_a? String + create_action :xFeedback, action, "/#{tokenize(path).join '/'}" + end - create_action :xFeedback, "#{action} /#{path.join '/'}" + def tokenize(path) + # Allow space or slash seperated paths + path&.split(/[\s\/\\]/)&.reject(&:empty?) || path end end From e2709007fed2d8741949f3f07feecf6d0ed0fcc8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Nov 2017 23:52:09 +1000 Subject: [PATCH 0053/1752] (cisco:spark) neaten up receive handler --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 6169969d..db50d676 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -60,7 +60,7 @@ def received(data, deferrable, command) # Let any pending command response handlers have first pass... yield(response).tap do |command_result| # Otherwise support interleaved async events - unhandled = command_result == :ignore || command_result.nil? + unhandled = [:ignore, nil].include? command_result @subscriptions&.notify response if unhandled end else From a3c5688c76ce48680ece7bdeece6feec069f9ed8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 Nov 2017 00:29:17 +1000 Subject: [PATCH 0054/1752] (cisco:spark) add support for device status queries --- modules/cisco/spark/room_os.rb | 43 ++++++++++++++++ modules/cisco/spark/room_os_spec.rb | 76 +++++++++++++++++++++++++++++ modules/cisco/spark/xapi/action.rb | 8 +++ 3 files changed, 127 insertions(+) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index db50d676..b9f227c2 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -81,6 +81,7 @@ def received(data, deferrable, command) # # @param command [String] the command to execute # @param args [Hash] the command arguments + # @return [::Libuv::Q::Promise] resolves when the command completes def xcommand(command, args = {}) send_xcommand command, args end @@ -89,10 +90,19 @@ def xcommand(command, args = {}) # # @param path [String] the configuration path # @param settings [Hash] the configuration values to apply + # @param [:Libuv::Q::Promise] resolves when the commands complete def xconfiguration(path, settings) send_xconfiguration path, settings end + # Trigger a status update for the specified path. + # + # @param path [String] the status path + # @param [:Libuv::Q::Promise] resolves with the status response as a Hash + def xstatus(path) + send_xstatus path + end + protected # Perform the actual command execution - this allows device implementations @@ -163,6 +173,38 @@ def send_xconfiguration(path, settings) end end + # Query the device's current status. + # + # @param path [String] + # @yield [response] + # a pre-parsed response object for the command, if used this block + # should return the response result + def send_xstatus(path) + request = Xapi::Action.xstatus path + + defer = thread.defer + + do_send request, name: "? #{path}" do |response| + path_components = Xapi::Action.tokenize path + status_response = response.dig 'Status', *path_components + error_result = response.dig 'CommandResponse', 'Status' + + if status_response + yield status_response if block_given? + defer.resolve status_response + :success + else + reason = error_result&.dig 'Reason', 'Value' + xpath = error_result&.dig 'XPath', 'Value' + logger.error "#{reason} (#{xpath})" if reason + defer.reject + :abort + end + end + + defer.promise + end + # Subscribe to feedback from the device. # # @param path [String, Array] the xPath to subscribe to updates for @@ -211,6 +253,7 @@ def subscribe_to_configuration # @yield [response] # a pre-parsed response object for the command, if used this block # should return the response result + # @return [::Libuv::Q::Promise] def do_send(command, **options) request_id = generate_request_uuid diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index b768650e..8453cc18 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -386,4 +386,80 @@ def section(message) expect(last_result.resolved?).to be true expect { last_result.value }.to raise_error(CoroutineRejection) end + + + # ------------------------------------------------------------------------- + section 'Status' + + # Status query + exec(:xstatus, 'Audio') + .should_send("xStatus Audio | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "Status":{ + "Audio":{ + "Input":{ + "Connectors":{ + "Microphone":[ + { + "id":"1", + "ConnectionStatus":{ + "Value":"Connected" + } + }, + { + "id":"2", + "ConnectionStatus":{ + "Value":"NotConnected" + } + } + ] + } + }, + "Microphones":{ + "Mute":{ + "Value":"On" + } + }, + "Output":{ + "Connectors":{ + "Line":[ + { + "id":"1", + "DelayMs":{ + "Value":"0" + } + } + ] + } + }, + "Volume":{ + "Value":"50" + } + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + + # Status results are provided in the return + exec(:xstatus, 'Time') + .should_send("xStatus Time | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "Status":{ + "Time":{ + "SystemTime":{ + "Value":"2017-11-27T15:14:25+1000" + } + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + expect(result.dig('SystemTime', 'Value')).to eq '2017-11-27T15:14:25+1000' end diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index eb8d54f1..a897ad33 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -62,6 +62,14 @@ def xconfiguration(path, setting, value) create_action :xConfiguration, tokenize(path), setting => value end + # Serialize an xStatus request into transmittable command. + # + # @param path [String, Array] status path + # @return [String] + def xstatus(path) + create_action :xStatus, tokenize(path) + end + # Serialize a xFeedback subscription request. # # @param action [:register, :deregister] From 21579263d845734d60e3b315a4d4e01ce113e23e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:21:54 +1100 Subject: [PATCH 0055/1752] Fix minor date issues --- lib/ibm/domino.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 9aa86579..f1395935 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -30,6 +30,7 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { end def get_free_rooms(starting, ending) + starting, ending = convert_to_datetime(starting, ending) starting, ending = get_time_range(starting, ending, @timezone) req_params = { @@ -45,6 +46,7 @@ def get_free_rooms(starting, ending) def get_bookings(database, starting, ending, days=nil) + starting, ending = convert_to_datetime(starting, ending) # Set count to max query = { count: 100 @@ -70,6 +72,7 @@ def get_bookings(database, starting, ending, days=nil) def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -114,6 +117,7 @@ def delete_booking(room, id) def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -188,6 +192,23 @@ def to_ibm_date(time) time.strftime("%Y-%m-%dT%H:%M:%SZ") end + def convert_to_datetime(starting, ending) + if !(starting.class == Time) + if string_is_digits(starting) + starting = Time.at(starting) + ending = Time.at(ending) + else + starting = Time.parse(starting) + ending = Time.parse(ending) + end + end + starting, ending + end + + + def string_is_digits(string) + string.scan(/\D/).empty? + end def to_utc_date(time) utctime = time.getutc From 4498593248b49bc28e34d7f20f85c8f9abd7be33 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:25:41 +1100 Subject: [PATCH 0056/1752] Fix date issues --- lib/ibm/domino.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index f1395935..582884e9 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -195,6 +195,18 @@ def to_ibm_date(time) def convert_to_datetime(starting, ending) if !(starting.class == Time) if string_is_digits(starting) + + # Convert to an integer + starting = starting.to_i + ending = ending.to_i + + # If JavaScript epoch remove milliseconds + if starting.length == 13 + starting /= 1000 + ending /= 1000 + end + + # Convert to datetimes starting = Time.at(starting) ending = Time.at(ending) else From f0d6b14af8e46821882319069b3ad2dac36dc468 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:29:18 +1100 Subject: [PATCH 0057/1752] Test syntax fix --- lib/ibm/domino.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 582884e9..df72a1ed 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -30,7 +30,7 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { end def get_free_rooms(starting, ending) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_datetime(starting, ending) starting, ending = get_time_range(starting, ending, @timezone) req_params = { @@ -46,7 +46,7 @@ def get_free_rooms(starting, ending) def get_bookings(database, starting, ending, days=nil) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_datetime(starting, ending) # Set count to max query = { count: 100 @@ -72,7 +72,7 @@ def get_bookings(database, starting, ending, days=nil) def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -117,7 +117,7 @@ def delete_booking(room, id) def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -214,7 +214,7 @@ def convert_to_datetime(starting, ending) ending = Time.parse(ending) end end - starting, ending + return starting end From 04d7493a9915f91ab6fb3d0d1588c6564ecab349 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:31:25 +1100 Subject: [PATCH 0058/1752] Try yet another syntax --- lib/ibm/domino.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index df72a1ed..d494e45e 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -30,7 +30,7 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { end def get_free_rooms(starting, ending) - starting = convert_to_datetime(starting, ending) + starting, ending = convert_to_datetime(starting, ending) starting, ending = get_time_range(starting, ending, @timezone) req_params = { @@ -46,7 +46,7 @@ def get_free_rooms(starting, ending) def get_bookings(database, starting, ending, days=nil) - starting = convert_to_datetime(starting, ending) + starting, ending = convert_to_datetime(starting, ending) # Set count to max query = { count: 100 @@ -72,7 +72,7 @@ def get_bookings(database, starting, ending, days=nil) def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting = convert_to_datetime(starting, ending) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -117,7 +117,7 @@ def delete_booking(room, id) def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting = convert_to_datetime(starting, ending) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -214,7 +214,7 @@ def convert_to_datetime(starting, ending) ending = Time.parse(ending) end end - return starting + return starting, ending end From 218fcfafeab7d31f96c6e01126dbd1084ddbdeea Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 15:34:05 +1100 Subject: [PATCH 0059/1752] Fix another date issue --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d494e45e..82aa9750 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -201,7 +201,7 @@ def convert_to_datetime(starting, ending) ending = ending.to_i # If JavaScript epoch remove milliseconds - if starting.length == 13 + if starting.to_s.length == 13 starting /= 1000 ending /= 1000 end From 4a8cc97af8eb050eee26bd692f7aaf0bc57dd276 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 27 Nov 2017 13:09:39 +0800 Subject: [PATCH 0060/1752] Fix room CRUD --- lib/ibm/domino.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 82aa9750..5e2943c2 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -36,7 +36,8 @@ def get_free_rooms(starting, ending) req_params = { :site => ENV["DOMINO_SITE"], :start => to_ibm_date(starting), - :end => to_ibm_date(ending) + :end => to_ibm_date(ending), + :capacity => 1 } res = domino_request('get','/api/freebusy/freerooms', nil, req_params).value @@ -58,12 +59,19 @@ def get_bookings(database, starting, ending, days=nil) query[:before] = to_ibm_date(ending) end + Rails.logger.info "Getting bookings for" + Rails.logger.info "/#{database}/api/calendar/events" + # Get our bookings response = domino_request('get', "/#{database}/api/calendar/events", nil, query).value domino_bookings = JSON.parse(response.body)['events'] # Grab the attendee for each booking bookings = [] + if response.status == 200 && response.body.nil? + Rails.logger.info "Got empty response" + domino_bookings = [] + end domino_bookings.each{ |booking| bookings.push(get_attendees(booking, database)) } From 41bbd7ed29c6862302a7ddd68585f48b58bc6627 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 16:20:38 +1100 Subject: [PATCH 0061/1752] Fix room database check --- lib/ibm/domino.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 5e2943c2..5d5df146 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -46,7 +46,10 @@ def get_free_rooms(starting, ending) - def get_bookings(database, starting, ending, days=nil) + def get_bookings(room_id, starting, ending, days=nil) + room = Orchestrator::ControlSystem.find(room_id) + Rails.logger.info "Getting bookings for #{room.name}" + database = room.settings['database'] starting, ending = convert_to_datetime(starting, ending) # Set count to max query = { From e6075916b2e899e56506a7c5d2f11c4d988a6490 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Nov 2017 16:57:50 +1100 Subject: [PATCH 0062/1752] Fix error response logic --- lib/ibm/domino.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 5d5df146..152632ff 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -48,7 +48,6 @@ def get_free_rooms(starting, ending) def get_bookings(room_id, starting, ending, days=nil) room = Orchestrator::ControlSystem.find(room_id) - Rails.logger.info "Getting bookings for #{room.name}" database = room.settings['database'] starting, ending = convert_to_datetime(starting, ending) # Set count to max @@ -62,19 +61,20 @@ def get_bookings(room_id, starting, ending, days=nil) query[:before] = to_ibm_date(ending) end - Rails.logger.info "Getting bookings for" - Rails.logger.info "/#{database}/api/calendar/events" # Get our bookings response = domino_request('get', "/#{database}/api/calendar/events", nil, query).value - domino_bookings = JSON.parse(response.body)['events'] - + case response.status + when 204 + domino_bookings = [] + when 200 + domino_bookings = JSON.parse(response.body)['events'] + else + raise "Unexpected Response: #{response.status} \n #{response.body}" + end # Grab the attendee for each booking bookings = [] - if response.status == 200 && response.body.nil? - Rails.logger.info "Got empty response" - domino_bookings = [] - end + domino_bookings.each{ |booking| bookings.push(get_attendees(booking, database)) } From 5dd7f2119084b084fb075567733016673809bfd5 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 Nov 2017 01:24:02 +1000 Subject: [PATCH 0063/1752] (cisco:spark) memoize subscriptions object to ensure safe access --- modules/cisco/spark/room_os.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index b9f227c2..aa0932bb 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -61,10 +61,10 @@ def received(data, deferrable, command) yield(response).tap do |command_result| # Otherwise support interleaved async events unhandled = [:ignore, nil].include? command_result - @subscriptions&.notify response if unhandled + subscriptions.notify response if unhandled end else - @subscriptions&.notify response + subscriptions.notify response :ignore end rescue JSON::ParserError => error @@ -212,15 +212,13 @@ def send_xstatus(path) def subscribe(path, &update_handler) logger.debug { "Subscribing to device feedback for #{path}" } - @subscriptions ||= FeedbackTrie.new - - unless @subscriptions.contains? path + unless subscriptions.contains? path request = Xapi::Action.xfeedback :register, path # Always returns an empty response, nothing special to handle result = do_send request end - @subscriptions.insert path, &update_handler + subscriptions.insert path, &update_handler result || thread.defer.resolve(:success) end @@ -228,7 +226,7 @@ def subscribe(path, &update_handler) def unsubscribe(path) logger.debug { "Unsubscribing feedback for #{path}" } - @subscriptions&.remove path + subscriptions.remove path request = Xapi::Action.xfeedback :deregister, path do_send request @@ -281,6 +279,10 @@ def do_send(command, **options) def generate_request_uuid SecureRandom.uuid end + + def subscriptions + @subscriptions ||= FeedbackTrie.new + end end From 2d8c7bdb1acf8e372cad2377802748f28039ac7d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 Nov 2017 01:29:09 +1000 Subject: [PATCH 0064/1752] (cisco:spark) memoize handlers object --- modules/cisco/spark/room_os.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index aa0932bb..0321b0fb 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -318,7 +318,7 @@ def remove(path) if path_components.empty? clear - @handlers&.clear + handlers.clear else *parent_path, node_key = path_components parent = parent_path.empty? ? self : dig(*parent_path) @@ -346,13 +346,12 @@ def notify(response) # Append a rx handler block to this node. def <<(blk) - @handlers ||= [] - @handlers << blk + handlers << blk end # Dispatch to all handlers registered on this node. def dispatch(value) - [*@handlers].each { |handler| handler.call value } + handlers.each { |handler| handler.call value } end def tokenize(path) @@ -362,4 +361,8 @@ def tokenize(path) path.split(/[\s\/\\]/).reject(&:empty?) end end + + def handlers + @handlers ||= [] + end end From 06cd0ae64120e09f4f82c2ebbd2cb330e88fa8b8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 Nov 2017 02:00:53 +1000 Subject: [PATCH 0065/1752] (cisco:spark) refector deeply nested iterators --- modules/cisco/spark/room_os.rb | 42 +++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 0321b0fb..0de24237 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -92,7 +92,7 @@ def xcommand(command, args = {}) # @param settings [Hash] the configuration values to apply # @param [:Libuv::Q::Promise] resolves when the commands complete def xconfiguration(path, settings) - send_xconfiguration path, settings + send_xconfigurations path, settings end # Trigger a status update for the specified path. @@ -139,33 +139,39 @@ def send_xcommand(command, args = {}) end end - def send_xconfiguration(path, settings) - # The API only allows a single setting to be applied with each request. - interactions = settings.to_a.map do |(setting, value)| - request = Xapi::Action.xconfiguration path, setting, value + # Apply a single configuration on the device. + def send_xconfiguration(path, setting, value) + request = Xapi::Action.xconfiguration path, setting, value - do_send request, name: "#{path} #{setting}" do |response| - result = response.dig 'CommandResponse', 'Configuration' + do_send request, name: "#{path} #{setting}" do |response| + result = response.dig 'CommandResponse', 'Configuration' - if result&.[]('status') == 'Error' - reason = result.dig 'Reason', 'Value' - xpath = result.dig 'XPath', 'Value' - logger.error "#{reason} (#{xpath})" if reason - :abort - else - :success - end + if result&.[]('status') == 'Error' + reason = result.dig 'Reason', 'Value' + xpath = result.dig 'XPath', 'Value' + logger.error "#{reason} (#{xpath})" if reason + :abort + else + :success end end + end + + # Apply a set of configurations. + def send_xconfigurations(path, settings) + # The API only allows a single setting to be applied with each request. + interactions = settings.to_a.map do |(setting, value)| + send_xconfiguration(path, setting, value) + end thread.finally(interactions).then do |results| resolved = results.map(&:last) if resolved.all? :success else - failures = settings.keys - .reject - .with_index { |_, index| resolved[index] } + failures = resolved.zip(settings.keys) + .reject(&:first) + .map(&:last) thread.defer.reject 'Could not apply all settings. ' \ "Failed on #{failures.join ', '}." From 2edbec60d41d579f5d1954829f4f55ded2dfda91 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 Nov 2017 02:16:07 +1000 Subject: [PATCH 0066/1752] (reek) allow private utility functions --- .reek | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.reek b/.reek index 6e03ef2d..1945332c 100644 --- a/.reek +++ b/.reek @@ -48,3 +48,7 @@ RepeatedConditional: # Allow for device model numbers to be used as module names. UncommunicativeModuleName: enabled: false + +# Support private, pure functions +UtilityFunction: + public_methods_only: true From 94287f542799326f5ea6c4c80b77a57d11e76e6b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 Nov 2017 02:23:56 +1000 Subject: [PATCH 0067/1752] (rubocop) allow long lines in specs --- .rubocop.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 1335b409..4129bb2d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -27,6 +27,10 @@ Metrics/ClassLength: Metrics/ModuleLength: Max: 300 +Metrics/LineLength: + Exclude: + - "**/**/*_spec.rb" + Metrics/ParameterLists: Max: 5 From 770bfb105b02529db841cef716a39f5468101e10 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 Nov 2017 02:25:16 +1000 Subject: [PATCH 0068/1752] (cisco:spark) minor linter issue fixup --- modules/cisco/spark/room_os_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 8453cc18..a60534c4 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -157,7 +157,7 @@ def section(message) } JSON ) - expect(status[:configuration].dig(:audio, :input, :microphone, 1, :mode, :value)).to eq "On" + expect(status[:configuration].dig(:audio, :input, :microphone, 1, :mode, :value)).to eq 'On' # ------------------------------------------------------------------------- section 'Base comms (protected methods - ignore the access warnings)' @@ -318,7 +318,7 @@ def section(message) expect(result).to be :success # Multuple settings return a unit :success when all ok - exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Name: "Borris", Quality: :Motion) + exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Name: 'Borris', Quality: :Motion) .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") .responds( <<~JSON @@ -346,7 +346,7 @@ def section(message) expect(result).to be :success # Multuple settings with failure with return a promise that rejects - exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Foo: "Bar", Quality: :Motion) + exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Foo: 'Bar', Quality: :Motion) .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") .responds( <<~JSON From cd51f7aa502719df97920a5f13028ed74d6ba060 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 13:49:49 +1100 Subject: [PATCH 0069/1752] Update to beta branch version --- lib/ibm/domino.rb | 52 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 9aa86579..152632ff 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -30,12 +30,14 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { end def get_free_rooms(starting, ending) + starting, ending = convert_to_datetime(starting, ending) starting, ending = get_time_range(starting, ending, @timezone) req_params = { :site => ENV["DOMINO_SITE"], :start => to_ibm_date(starting), - :end => to_ibm_date(ending) + :end => to_ibm_date(ending), + :capacity => 1 } res = domino_request('get','/api/freebusy/freerooms', nil, req_params).value @@ -44,7 +46,10 @@ def get_free_rooms(starting, ending) - def get_bookings(database, starting, ending, days=nil) + def get_bookings(room_id, starting, ending, days=nil) + room = Orchestrator::ControlSystem.find(room_id) + database = room.settings['database'] + starting, ending = convert_to_datetime(starting, ending) # Set count to max query = { count: 100 @@ -56,12 +61,20 @@ def get_bookings(database, starting, ending, days=nil) query[:before] = to_ibm_date(ending) end + # Get our bookings response = domino_request('get', "/#{database}/api/calendar/events", nil, query).value - domino_bookings = JSON.parse(response.body)['events'] - + case response.status + when 204 + domino_bookings = [] + when 200 + domino_bookings = JSON.parse(response.body)['events'] + else + raise "Unexpected Response: #{response.status} \n #{response.body}" + end # Grab the attendee for each booking bookings = [] + domino_bookings.each{ |booking| bookings.push(get_attendees(booking, database)) } @@ -70,6 +83,7 @@ def get_bookings(database, starting, ending, days=nil) def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -114,6 +128,7 @@ def delete_booking(room, id) def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -188,6 +203,35 @@ def to_ibm_date(time) time.strftime("%Y-%m-%dT%H:%M:%SZ") end + def convert_to_datetime(starting, ending) + if !(starting.class == Time) + if string_is_digits(starting) + + # Convert to an integer + starting = starting.to_i + ending = ending.to_i + + # If JavaScript epoch remove milliseconds + if starting.to_s.length == 13 + starting /= 1000 + ending /= 1000 + end + + # Convert to datetimes + starting = Time.at(starting) + ending = Time.at(ending) + else + starting = Time.parse(starting) + ending = Time.parse(ending) + end + end + return starting, ending + end + + + def string_is_digits(string) + string.scan(/\D/).empty? + end def to_utc_date(time) utctime = time.getutc From bda9580dd339335ec9eb16a07fe0a99f84a01b1f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 13:54:45 +1100 Subject: [PATCH 0070/1752] Fix response check --- lib/ibm/domino.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 152632ff..d87a2680 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -65,10 +65,12 @@ def get_bookings(room_id, starting, ending, days=nil) # Get our bookings response = domino_request('get', "/#{database}/api/calendar/events", nil, query).value case response.status - when 204 - domino_bookings = [] when 200 - domino_bookings = JSON.parse(response.body)['events'] + if response.body.nil? || response.body == "" + domino_bookings = [] + else + domino_bookings = JSON.parse(response.body)['events'] + end else raise "Unexpected Response: #{response.status} \n #{response.body}" end From 1a4b73f44deb890c181c28e712efe5af35b95cb7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 14:09:09 +1100 Subject: [PATCH 0071/1752] Return whole request from booking creation --- lib/ibm/domino.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d87a2680..4ba76627 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -121,7 +121,6 @@ def create_booking(starting:, ending:, room:, summary:, description: nil, organi }) if attendees request = domino_request('post', "/#{room}/api/calendar/events", {events: [event]}).value - JSON.parse(request.body)['events'][0] end def delete_booking(room, id) From dfbf9a7ffed76720849be650af946260cb7c2945 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 14:11:08 +1100 Subject: [PATCH 0072/1752] Return request correctly --- lib/ibm/domino.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 4ba76627..c2129dd1 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -121,6 +121,7 @@ def create_booking(starting:, ending:, room:, summary:, description: nil, organi }) if attendees request = domino_request('post', "/#{room}/api/calendar/events", {events: [event]}).value + request end def delete_booking(room, id) From 4d24989e81f9a9a8cf14cc3da8a3bfbd4e9cef96 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 14:17:59 +1100 Subject: [PATCH 0073/1752] Remove the need for displayName field --- lib/ibm/domino.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index c2129dd1..bb396573 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -97,28 +97,28 @@ def create_booking(starting:, ending:, room:, summary:, description: nil, organi event[:attendees] = Array(attendees).collect do |attendee| - { + out_attendee = { role: "req-participant", status: "needs-action", rsvp: true, - displayName: attendee[:name], email: attendee[:email] } + out_attendee[:displayName] = attendee[:name] if attendee[:name] + out_attendee end event[:organizer] = { - email: organizer[:email], - displayName: organizer[:name] + email: organizer[:email] } - # If there are attendees add the service account + event[:organizer][:displayName] = organizer[:name] if organizer[:name] + event[:attendees].push({ "role":"chair", "status":"accepted", "rsvp":false, - "displayName":"OTS Test1 Project SG/SG/R&R/PwC", - "email":"ots.test1.project.sg@sg.pwc.com" - }) if attendees + "email": current_user.email + }) request = domino_request('post', "/#{room}/api/calendar/events", {events: [event]}).value request From 095bf1f870e017f1925d385971842eec0cbe43c9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 14:19:27 +1100 Subject: [PATCH 0074/1752] Add current user as param --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index bb396573..497a7fb5 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -84,7 +84,7 @@ def get_bookings(room_id, starting, ending, days=nil) end - def create_booking(starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + def create_booking(current_user:, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, From 753bfa5bd1b8f38a97d9bf771c7d03b1188acf62 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 14:33:57 +1100 Subject: [PATCH 0075/1752] Make organizer optional and add room as attendee --- lib/ibm/domino.rb | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 497a7fb5..d7a64296 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -84,7 +84,7 @@ def get_bookings(room_id, starting, ending, days=nil) end - def create_booking(current_user:, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + def create_booking(current_user:, starting:, ending:, room_database:, room_email:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, @@ -107,20 +107,41 @@ def create_booking(current_user:, starting:, ending:, room:, summary:, descripti out_attendee end - event[:organizer] = { - email: organizer[:email] - } - - event[:organizer][:displayName] = organizer[:name] if organizer[:name] + # Set the current user as orgaqnizer and chair if no organizer passed in + if organizer + event[:organizer] = { + email: organizer[:email] + } + event[:organizer][:displayName] = organizer[:name] if organizer[:name] + + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "email": organizer[:email] + }) + else + event[:organizer] = { + email: current_user.email + } + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "email": current_user.email + }) + end + # Add the room as an attendee event[:attendees].push({ "role":"chair", "status":"accepted", "rsvp":false, - "email": current_user.email + "email": room_email }) - request = domino_request('post', "/#{room}/api/calendar/events", {events: [event]}).value + + request = domino_request('post', "/#{room_database}/api/calendar/events", {events: [event]}).value request end From 8da3dca138d76342eb5e982e7c82fd6cf91182eb Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 14:43:38 +1100 Subject: [PATCH 0076/1752] Set room userType --- lib/ibm/domino.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d7a64296..09796ea1 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -137,6 +137,7 @@ def create_booking(current_user:, starting:, ending:, room_database:, room_email "role":"chair", "status":"accepted", "rsvp":false, + "userType":"room", "email": room_email }) From f4459f8ae5b8d0051403bebe7fa380c23f182a09 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 28 Nov 2017 16:17:40 +1100 Subject: [PATCH 0077/1752] Update params to use user database and room ID --- lib/ibm/domino.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 09796ea1..1110af94 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -84,7 +84,8 @@ def get_bookings(room_id, starting, ending, days=nil) end - def create_booking(current_user:, starting:, ending:, room_database:, room_email:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + def create_booking(current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + room = Orchestrator::ControlSystem.find(room_id) starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, @@ -138,11 +139,11 @@ def create_booking(current_user:, starting:, ending:, room_database:, room_email "status":"accepted", "rsvp":false, "userType":"room", - "email": room_email + "email": room.email }) - request = domino_request('post', "/#{room_database}/api/calendar/events", {events: [event]}).value + request = domino_request('post', "/#{database}/api/calendar/events", {events: [event]}).value request end From 002c94f27b0b9221bd3dad626036430d9403a9af Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 28 Nov 2017 17:09:28 +1100 Subject: [PATCH 0078/1752] CiscoSX:Camera Update preset recall for CE8 --- modules/cisco/tele_presence/sx_camera_common.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/cisco/tele_presence/sx_camera_common.rb b/modules/cisco/tele_presence/sx_camera_common.rb index cba9a2fe..30666158 100644 --- a/modules/cisco/tele_presence/sx_camera_common.rb +++ b/modules/cisco/tele_presence/sx_camera_common.rb @@ -68,12 +68,7 @@ def power?(options = nil, &block) def home - # command("Camera PositionReset CameraId:#{@index}", name: :preset).then do - # Preset1 is a better home as it will usually pointed to a default position wheras PositionReset may not be a userfull view - recall_position(1).then do - autofocus - do_poll - end + command("Camera Preset ActivateDefaultPosition", name: :preset) end def autofocus @@ -246,8 +241,7 @@ def preset(name) def recall_position(number) number = in_range(number, 15, 1) - command('Camera PositionActivateFromPreset', params({ - :CameraId => @index, + command('Camera Preset Activate Preset', params({ :PresetId => number }), name: :preset).then do autofocus From a4d944fe098423bd7736434f28b4051fd9482758 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Nov 2017 01:26:11 +1000 Subject: [PATCH 0079/1752] (cisco:spark) show hidden characters in rx data --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 0de24237..52ac89b7 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -52,7 +52,7 @@ def disconnected # In addition to acting an the normal Orchestrator callback, on_receive # procs also pipe through here for initial JSON decoding. See #do_send. def received(data, deferrable, command) - logger.debug { "<- #{data}" } + logger.debug { "<- #{data.inspect}" } response = JSON.parse data, object_class: CaseInsensitiveHash From 3416f8e38c6e37a64ba65528b5739f28d4769bc2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Nov 2017 02:45:58 +1000 Subject: [PATCH 0080/1752] (cisco:spark) fix issues with response tokenization --- modules/cisco/spark/room_os.rb | 16 ++++++++++++---- modules/cisco/spark/room_os_spec.rb | 5 +++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 52ac89b7..bd54d18e 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -26,8 +26,10 @@ class Cisco::Spark::RoomOs (i.e. SX80, Spark Room Kit etc). DESC - tokenize delimiter: /(?<=\n})|(?<=\n{})|(?<=Command not recognized.)\n/, - wait_ready: "*r Login successful\n\nOK\n\n" + tokenize \ + delimiter: /(?<=\n})|(?<=\n{})|(?<=Command not recognized.)|(?<=OK)[\r\n]+/, + wait_ready: /\*r Login successful[\r\n]+/ + clear_queue_on_disconnect! def on_load; end @@ -37,7 +39,10 @@ def on_unload; end def on_update; end def connected - send "Echo off\n", wait: false, priority: 96 + send "Echo off\n", priority: 96 do |response| + :success if response.starts_with? "\e[?1034h" + end + send "xPreferences OutputMode JSON\n", wait: false subscribe_to_configuration @@ -68,7 +73,10 @@ def received(data, deferrable, command) :ignore end rescue JSON::ParserError => error - if data.strip == 'Command not recognized.' + case data.strip + when 'OK' + :success + when 'Command not recognized.' logger.error { "Command not recognized: `#{command[:data]}`" } :abort else diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index a60534c4..928e89a6 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -50,6 +50,8 @@ def section(message) expect(status[:connected]).to be true should_send "Echo off\n" + responds "\e[?1034h\r\nOK\r\n" + should_send "xPreferences OutputMode JSON\n" # ------------------------------------------------------------------------- @@ -65,7 +67,6 @@ def section(message) ) should_send "xConfiguration *\n" - responds( <<~JSON { @@ -182,7 +183,7 @@ def section(message) # Handle invalid device commands exec(:do_send, 'Not a real command') .should_send("Not a real command | resultId=\"#{id_pop}\"\n") - .responds("Command not recognized.\n") + .responds("Command not recognized.\r\n") expect { result }.to raise_error(Orchestrator::Error::CommandFailure) # Handle async response data From da15629a4517dc252d50a9e6fe759e89ad891bdd Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Nov 2017 02:46:41 +1000 Subject: [PATCH 0081/1752] (cisco:spark) force device reconnect on reload --- modules/cisco/spark/room_os.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index bd54d18e..972e905b 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -36,7 +36,10 @@ def on_load; end def on_unload; end - def on_update; end + def on_update + # Force a reconnect and event resubscribe following module updates. + disconnect + end def connected send "Echo off\n", priority: 96 do |response| From be041a50e2ed6357fa1b7441b98653cb4c4750ab Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Nov 2017 03:38:10 +1000 Subject: [PATCH 0082/1752] (cisco:spark) register as a control system against the device --- modules/cisco/spark/room_os.rb | 44 +++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 972e905b..a695b7b9 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -48,11 +48,17 @@ def connected send "xPreferences OutputMode JSON\n", wait: false + register_control_system.then do + schedule.every('30s') { heartbeat timeout: 35 } + end + subscribe_to_configuration end def disconnected clear_subscriptions + + schedule.clear end # Handle all incoming data from the device. @@ -60,7 +66,7 @@ def disconnected # In addition to acting an the normal Orchestrator callback, on_receive # procs also pipe through here for initial JSON decoding. See #do_send. def received(data, deferrable, command) - logger.debug { "<- #{data.inspect}" } + logger.debug { "<- #{data}" } response = JSON.parse data, object_class: CaseInsensitiveHash @@ -101,7 +107,7 @@ def xcommand(command, args = {}) # # @param path [String] the configuration path # @param settings [Hash] the configuration values to apply - # @param [:Libuv::Q::Promise] resolves when the commands complete + # @param [::Libuv::Q::Promise] resolves when the commands complete def xconfiguration(path, settings) send_xconfigurations path, settings end @@ -109,7 +115,7 @@ def xconfiguration(path, settings) # Trigger a status update for the specified path. # # @param path [String] the status path - # @param [:Libuv::Q::Promise] resolves with the status response as a Hash + # @param [::Libuv::Q::Promise] resolves with the status response as a Hash def xstatus(path) send_xstatus path end @@ -300,6 +306,38 @@ def generate_request_uuid def subscriptions @subscriptions ||= FeedbackTrie.new end + + def register_control_system + info = "#{self.class.name}-#{version}" + + send_xcommand 'Peripherals Connect', + ID: peripheral_id, + Name: 'ACAEngine', + SoftwareInfo: info, + Type: :ControlSystem + end + + def heartbeat(timeout:) + send_xcommand 'Peripherals HeartBeat', + ID: peripheral_id, + Timeout: timeout + end + + def edge_id + @edge_id ||= @__config__.settings.edge_id + end + + def peripheral_id + "ACA-#{edge_id}" + end + + def version + if system 'git --version' + Dir.chdir(__dir__) { `git rev-parse --short HEAD`.strip } + else + 'Unknown' + end + end end From 19c6cffc3f094d3ead7e433b7b048e1f19a8d8cc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 29 Nov 2017 12:55:11 +1100 Subject: [PATCH 0083/1752] Use Khalis' domain to create bookings (temporary) --- lib/ibm/domino.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 1110af94..bd50a758 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -24,8 +24,13 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { data = data.to_json if !data.nil? && data.class != String @headers.merge(headers) if headers + + if request_method == :post + domino_path = "#{ENV['DOMINO_CREATE_DOMAIN']}#{endpoint}" + else + domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" + end - domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" response = @domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) end From 9012190ca3f08d415d11ce3d0cbf17ed290166bd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 29 Nov 2017 13:07:13 +1100 Subject: [PATCH 0084/1752] Hacky temporarily solution to different domains --- lib/ibm/domino.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index bd50a758..84a33fad 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -26,12 +26,15 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { @headers.merge(headers) if headers if request_method == :post + + domino_api = UV::HttpEndpoint.new(ENV['DOMINO_CREATE_DOMAIN'], {inactivity_timeout: 25000}) domino_path = "#{ENV['DOMINO_CREATE_DOMAIN']}#{endpoint}" else + domino_api = @domino_api domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" end - response = @domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) + response = domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) end def get_free_rooms(starting, ending) From c96ce4840fea046a4237ace49e0716704ff8db49 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 29 Nov 2017 17:04:37 +1100 Subject: [PATCH 0085/1752] Update get_bookings to use --- lib/ibm/domino.rb | 89 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 84a33fad..29bfdb20 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -1,3 +1,5 @@ +# Reference: https://www.ibm.com/developerworks/lotus/library/ls-Domino_URL_cheat_sheet/ + require 'active_support/time' module IBM; end @@ -23,7 +25,7 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { request_method = request_method.to_sym data = data.to_json if !data.nil? && data.class != String - @headers.merge(headers) if headers + @headers.merge(headers) if headers if request_method == :post @@ -54,41 +56,50 @@ def get_free_rooms(starting, ending) - def get_bookings(room_id, starting, ending, days=nil) + def get_bookings(room_id, date=Time.now.tomorrow.midnight) room = Orchestrator::ControlSystem.find(room_id) - database = room.settings['database'] - starting, ending = convert_to_datetime(starting, ending) + room_name = room.settings['name'] + + # The domino API takes a StartKey and UntilKey + # We will only ever need one days worth of bookings + # If startkey = 2017-11-29 and untilkey = 2017-11-30 + # Then all bookings on the 30th (the day of the untilkey) are returned + + # Make date a date object from epoch or parsed text + date = convert_to_simpledate(date) + + starting = date.yesterday.strftime("%Y%m%d") + ending = date.strftime("%Y%m%d") + # Set count to max query = { - count: 100 + StartKey: starting, + UntilKey: ending, + KeyType: 'time', + ReadViewEntries: nil, + OutputFormat: 'JSON' } - # If we have a range use it - if starting - query[:since] = to_ibm_date(starting) - query[:before] = to_ibm_date(ending) - end - - # Get our bookings - response = domino_request('get', "/#{database}/api/calendar/events", nil, query).value - case response.status - when 200 - if response.body.nil? || response.body == "" - domino_bookings = [] - else - domino_bookings = JSON.parse(response.body)['events'] + request = domino_request('get', "/RRDB.nsf/93FDE1776546DEEB482581E7000B27FF", nil, query) + response = request.value + + # Go through the returned bookings and add to output array + rooms_bookings = [] + bookings = JSON.parse(response.body)['viewentry'] + bookings.each{ |booking| + domino_room_name = booking['entrydata'][2]['text']['0'].split('/')[0] + if room_name == domino_room_name + new_booking = { + start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i, + end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i, + summary: booking['entrydata'][5]['text']['0'], + organizer: booking['entrydata'][3]['text']['0'] + } + rooms_bookings.push(new_booking) end - else - raise "Unexpected Response: #{response.status} \n #{response.body}" - end - # Grab the attendee for each booking - bookings = [] - - domino_bookings.each{ |booking| - bookings.push(get_attendees(booking, database)) } - bookings + rooms_bookings end @@ -236,6 +247,28 @@ def to_ibm_date(time) time.strftime("%Y-%m-%dT%H:%M:%SZ") end + def convert_to_simpledate(date) + if !(date.class == Time) + if string_is_digits(date) + + # Convert to an integer + date = date.to_i + + # If JavaScript epoch remove milliseconds + if starting.to_s.length == 13 + starting /= 1000 + ending /= 1000 + end + + # Convert to datetimes + date = Time.at(date) + else + date = Time.parse(date) + end + end + return date + end + def convert_to_datetime(starting, ending) if !(starting.class == Time) if string_is_digits(starting) From a1f6330e04a11a57d573c7058fb29c01fbead638 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Nov 2017 19:46:03 +1000 Subject: [PATCH 0086/1752] (cisco:spark) only force a disconnect on load when connected --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index a695b7b9..3eea6dbc 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -38,7 +38,7 @@ def on_unload; end def on_update # Force a reconnect and event resubscribe following module updates. - disconnect + disconnect if self[:connected] end def connected From a4320395a9d151433dca798d1199c68e9ad2c5b6 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Nov 2017 19:47:56 +1000 Subject: [PATCH 0087/1752] (cisco:spark) add support for static peripheral id / software info --- modules/cisco/spark/room_os.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 3eea6dbc..c8bed68d 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -32,11 +32,16 @@ class Cisco::Spark::RoomOs clear_queue_on_disconnect! - def on_load; end + def on_load + on_update + end def on_unload; end def on_update + self[:peripheral_id] = setting(:peripheral_id) || "aca-#{edge_id}" + self[:version] = setting(:version) || "#{self.class.name}-#{version}" + # Force a reconnect and event resubscribe following module updates. disconnect if self[:connected] end @@ -308,18 +313,16 @@ def subscriptions end def register_control_system - info = "#{self.class.name}-#{version}" - send_xcommand 'Peripherals Connect', - ID: peripheral_id, + ID: self[:peripheral_id], Name: 'ACAEngine', - SoftwareInfo: info, + SoftwareInfo: self[:version], Type: :ControlSystem end def heartbeat(timeout:) send_xcommand 'Peripherals HeartBeat', - ID: peripheral_id, + ID: self[:peripheral_id], Timeout: timeout end @@ -327,10 +330,6 @@ def edge_id @edge_id ||= @__config__.settings.edge_id end - def peripheral_id - "ACA-#{edge_id}" - end - def version if system 'git --version' Dir.chdir(__dir__) { `git rev-parse --short HEAD`.strip } From 6d237a804665b8eed561e92f83c886e9f3bcc1a3 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Nov 2017 19:48:25 +1000 Subject: [PATCH 0088/1752] (cisco:spark) add test case for registration --- modules/cisco/spark/room_os_spec.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 928e89a6..57454357 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -1,6 +1,10 @@ require 'thread' -Orchestrator::Testing.mock_device 'Cisco::Spark::RoomOs' do +Orchestrator::Testing.mock_device 'Cisco::Spark::RoomOs', + settings: { + peripheral_id: 'MOCKED_ID', + version: 'MOCKED_VERSION' + } do # Patch in some tracking of request UUID's so we can form and validate # device comms. @manager.instance.class_eval do @@ -54,6 +58,23 @@ def section(message) should_send "xPreferences OutputMode JSON\n" + # ------------------------------------------------------------------------- + section 'System registration' + + should_send "xCommand Peripherals Connect ID: \"MOCKED_ID\" Name: \"ACAEngine\" SoftwareInfo: \"MOCKED_VERSION\" Type: ControlSystem | resultId=\"#{id_peek}\"\n" + responds( + <<~JSON + { + "CommandResponse":{ + "PeripheralsConnectResult":{ + "status":"OK" + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + # ------------------------------------------------------------------------- section 'Initial state sync' From 47e697e305839d0d291195f9bfe092cdb704adf4 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Nov 2017 20:47:47 +1000 Subject: [PATCH 0089/1752] (cisco:spark) neaten up delimiter regex --- modules/cisco/spark/room_os.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index c8bed68d..31aa9b0b 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -27,7 +27,12 @@ class Cisco::Spark::RoomOs DESC tokenize \ - delimiter: /(?<=\n})|(?<=\n{})|(?<=Command not recognized.)|(?<=OK)[\r\n]+/, + delimiter: Regexp.union([ # Zero-width groups (included in response) + '}', + '{}', + 'Command not recognized.', + 'OK' + ].map { |delim| /(?<=^#{delim})[\r\n]+/ }), wait_ready: /\*r Login successful[\r\n]+/ clear_queue_on_disconnect! From 8cd44fdfabfed8523a6fdaa1563ae5efb54ce251 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 30 Nov 2017 12:03:00 +1000 Subject: [PATCH 0090/1752] (cisco:spark) only tokenize paths when required --- modules/cisco/spark/xapi/action.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index a897ad33..a623dead 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -49,7 +49,7 @@ def create_action(type, *args, **kwargs) # @param args [Hash] an optional hash of keyword arguments # @return [String] def xcommand(path, **args) - create_action :xCommand, tokenize(path), args + create_action :xCommand, path, args end # Serialize an xConfiguration action into a transmittable command. @@ -59,7 +59,7 @@ def xcommand(path, **args) # @param value the configuration value to apply # @return [String] def xconfiguration(path, setting, value) - create_action :xConfiguration, tokenize(path), setting => value + create_action :xConfiguration, path, setting => value end # Serialize an xStatus request into transmittable command. @@ -67,7 +67,7 @@ def xconfiguration(path, setting, value) # @param path [String, Array] status path # @return [String] def xstatus(path) - create_action :xStatus, tokenize(path) + create_action :xStatus, path end # Serialize a xFeedback subscription request. @@ -81,11 +81,13 @@ def xfeedback(action, path) "Invalid feedback action. Must be one of #{FEEDBACK_ACTION}." end - create_action :xFeedback, action, "/#{tokenize(path).join '/'}" + xpath = tokenize path if path.is_a? String + + create_action :xFeedback, action, "/#{xpath.join '/'}" end def tokenize(path) # Allow space or slash seperated paths - path&.split(/[\s\/\\]/)&.reject(&:empty?) || path + path.split(/[\s\/\\]/).reject(&:empty?) end end From 12c8da28c57549940433242a426ec9df09cb52be Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 30 Nov 2017 13:01:52 +1000 Subject: [PATCH 0091/1752] (cisco:spark) move utility data classes out to standalone files --- modules/cisco/spark/room_os.rb | 95 ++----------------- .../cisco/spark/util/case_insensitive_hash.rb | 16 ++++ modules/cisco/spark/util/feedback_trie.rb | 71 ++++++++++++++ modules/cisco/spark/xapi/action.rb | 4 - modules/cisco/spark/xapi/tokenize.rb | 21 ++++ 5 files changed, 116 insertions(+), 91 deletions(-) create mode 100644 modules/cisco/spark/util/case_insensitive_hash.rb create mode 100644 modules/cisco/spark/util/feedback_trie.rb create mode 100644 modules/cisco/spark/xapi/tokenize.rb diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 31aa9b0b..f796a7f3 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -3,15 +3,18 @@ require 'json' require 'securerandom' -Dir[File.join(File.dirname(__FILE__), 'xapi', '*.rb')].each { |f| load f } - module Cisco; end module Cisco::Spark; end +module Cisco::Spark::Xapi; end +module Cisco::Spark::Util; end +Dir[File.join(__dir__, '{xapi,util}', '*.rb')].each { |lib| load lib } + class Cisco::Spark::RoomOs include ::Orchestrator::Constants - Xapi = Cisco::Spark::Xapi + Xapi = ::Cisco::Spark::Xapi + Util = ::Cisco::Spark::Util implements :ssh descriptive_name 'Cisco Spark Room Device' @@ -78,7 +81,7 @@ def disconnected def received(data, deferrable, command) logger.debug { "<- #{data}" } - response = JSON.parse data, object_class: CaseInsensitiveHash + response = JSON.parse data, object_class: Util::CaseInsensitiveHash if block_given? # Let any pending command response handlers have first pass... @@ -314,7 +317,7 @@ def generate_request_uuid end def subscriptions - @subscriptions ||= FeedbackTrie.new + @subscriptions ||= Util::FeedbackTrie.new end def register_control_system @@ -343,85 +346,3 @@ def version end end end - - -class CaseInsensitiveHash < ActiveSupport::HashWithIndifferentAccess - def [](key) - super convert_key(key) - end - - protected - - def convert_key(key) - super(key.try(:downcase) || key) - end -end - - -class Cisco::Spark::RoomOs::FeedbackTrie < CaseInsensitiveHash - # Insert a response handler block to be notified of updates effecting the - # specified feedback path. - def insert(path, &handler) - node = tokenize(path).reduce(self) do |trie, token| - trie[token] ||= self.class.new - end - - node << handler - - self - end - - # Nuke a subtree below the path - def remove(path) - path_components = tokenize path - - if path_components.empty? - clear - handlers.clear - else - *parent_path, node_key = path_components - parent = parent_path.empty? ? self : dig(*parent_path) - parent&.delete node_key - end - - self - end - - def contains?(path) - !dig(*tokenize(path)).nil? - end - - # Propogate a response throughout the trie - def notify(response) - response.try(:each) do |key, value| - node = self[key] - next unless node - node.dispatch value - node.notify value - end - end - - protected - - # Append a rx handler block to this node. - def <<(blk) - handlers << blk - end - - # Dispatch to all handlers registered on this node. - def dispatch(value) - handlers.each { |handler| handler.call value } - end - - def tokenize(path) - if path.is_a? Array - path - else - path.split(/[\s\/\\]/).reject(&:empty?) - end - end - - def handlers - @handlers ||= [] - end -end diff --git a/modules/cisco/spark/util/case_insensitive_hash.rb b/modules/cisco/spark/util/case_insensitive_hash.rb new file mode 100644 index 00000000..5737ecaf --- /dev/null +++ b/modules/cisco/spark/util/case_insensitive_hash.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'active_support/core_ext/hash/indifferent_access' + +class Cisco::Spark::Util::CaseInsensitiveHash < \ + ActiveSupport::HashWithIndifferentAccess + def [](key) + super convert_key(key) + end + + protected + + def convert_key(key) + super(key.try(:downcase) || key) + end +end diff --git a/modules/cisco/spark/util/feedback_trie.rb b/modules/cisco/spark/util/feedback_trie.rb new file mode 100644 index 00000000..5a8fd383 --- /dev/null +++ b/modules/cisco/spark/util/feedback_trie.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require_relative 'case_insensitive_hash' + +class Cisco::Spark::Util::FeedbackTrie < Cisco::Spark::Util::CaseInsensitiveHash + # Insert a response handler block to be notified of updates effecting the + # specified feedback path. + def insert(path, &handler) + node = tokenize(path).reduce(self) do |trie, token| + trie[token] ||= self.class.new + end + + node << handler + + self + end + + # Nuke a subtree below the path + def remove(path) + path_components = tokenize path + + if path_components.empty? + clear + handlers.clear + else + *parent_path, node_key = path_components + parent = parent_path.empty? ? self : dig(*parent_path) + parent&.delete node_key + end + + self + end + + def contains?(path) + !dig(*tokenize(path)).nil? + end + + # Propogate a response throughout the trie + def notify(response) + response.try(:each) do |key, value| + node = self[key] + next unless node + node.dispatch value + node.notify value + end + end + + protected + + # Append a rx handler block to this node. + def <<(blk) + handlers << blk + end + + # Dispatch to all handlers registered on this node. + def dispatch(value) + handlers.each { |handler| handler.call value } + end + + def tokenize(path) + if path.is_a? Array + path + else + path.split(/[\s\/\\]/).reject(&:empty?) + end + end + + def handlers + @handlers ||= [] + end +end diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index a623dead..0a2f3c93 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -2,10 +2,6 @@ require 'set' -module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Xapi; end - # Pure utility methods for building Cisco xAPI actions. module Cisco::Spark::Xapi::Action ACTION_TYPE ||= Set.new [ diff --git a/modules/cisco/spark/xapi/tokenize.rb b/modules/cisco/spark/xapi/tokenize.rb new file mode 100644 index 00000000..5ca16201 --- /dev/null +++ b/modules/cisco/spark/xapi/tokenize.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Xapi; end + +# Regexp's for tokenizing the xAPI command and response structure. +module Cisco::Spark::Xapi::Tokenize + + + module_function + + # Split a space or slash seperated path into it's components. + def path(xpath) + if xpath.respond_to? :split + xpath.split(/[\s\/\\]/).reject(&:empty?) + else + xpath + end + end +end From 0dae2afd86994a4966fb25d22d260b4a7d474745 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 30 Nov 2017 13:52:34 +1000 Subject: [PATCH 0092/1752] (cisco:spark) move git methods into a utility module --- modules/cisco/spark/room_os.rb | 16 ++++++++-------- modules/cisco/spark/util/git.rb | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 modules/cisco/spark/util/git.rb diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index f796a7f3..49e9cf85 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -47,8 +47,11 @@ def on_load def on_unload; end def on_update - self[:peripheral_id] = setting(:peripheral_id) || "aca-#{edge_id}" - self[:version] = setting(:version) || "#{self.class.name}-#{version}" + load_setting :peripheral_id, + default: "aca-#{edge_id}" + + load_setting :version, + default: "#{self.class.name}-#{Util::Git.hash __dir__}" # Force a reconnect and event resubscribe following module updates. disconnect if self[:connected] @@ -338,11 +341,8 @@ def edge_id @edge_id ||= @__config__.settings.edge_id end - def version - if system 'git --version' - Dir.chdir(__dir__) { `git rev-parse --short HEAD`.strip } - else - 'Unknown' - end + # Load a setting from the module config into a status variable. + def load_setting(name, default:) + self[name] = setting(name) || default end end diff --git a/modules/cisco/spark/util/git.rb b/modules/cisco/spark/util/git.rb new file mode 100644 index 00000000..8b07fc8c --- /dev/null +++ b/modules/cisco/spark/util/git.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Cisco::Spark::Util::Git + module_function + + # Get the commit hash for the passed path. + # + # @param path [String] the path to the repo + # @return [String, nil] the short commit hash + def hash(path) + Dir.chdir(path) { `git rev-parse --short HEAD`.strip } if installed? + end + + # Check if git is installed and accessible to the curent process. + # + # @return [Boolean] + def installed? + system 'git --version' + end +end From 0d40ec6fd096b4808d8db90689f9cc116c2dcc7f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 30 Nov 2017 16:25:38 +1100 Subject: [PATCH 0093/1752] Make sure epoch is string to begin with --- lib/ibm/domino.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 29bfdb20..0462c74f 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -296,6 +296,7 @@ def convert_to_datetime(starting, ending) def string_is_digits(string) + string = string.to_s string.scan(/\D/).empty? end From 5936cdf43deabcb36343542487652c9462df98c9 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 30 Nov 2017 19:40:23 +1000 Subject: [PATCH 0094/1752] (cisco:spark) change peripheral ID to be unique to module instance --- modules/cisco/spark/room_os.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 49e9cf85..5bbf5d88 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -47,11 +47,8 @@ def on_load def on_unload; end def on_update - load_setting :peripheral_id, - default: "aca-#{edge_id}" - - load_setting :version, - default: "#{self.class.name}-#{Util::Git.hash __dir__}" + load_setting :peripheral_id, default: SecureRandom.uuid, persist: true + load_setting :version, default: version # Force a reconnect and event resubscribe following module updates. disconnect if self[:connected] @@ -337,12 +334,14 @@ def heartbeat(timeout:) Timeout: timeout end - def edge_id - @edge_id ||= @__config__.settings.edge_id + def version + "#{self.class.name}-#{Util::Git.hash __dir__}" end - # Load a setting from the module config into a status variable. - def load_setting(name, default:) - self[name] = setting(name) || default + # Load a setting into a status variable of the same name. + def load_setting(name, default:, persist: false) + value = setting(name) + define_setting(name, default) if value.nil? && persist + self[name] = value || default end end From 955910b1ae69b7a801ec5290fadd169f221240f0 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 30 Nov 2017 19:49:15 +1000 Subject: [PATCH 0095/1752] (reek) disable both instances of control-couple --- .reek | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.reek b/.reek index 1945332c..47b9358c 100644 --- a/.reek +++ b/.reek @@ -31,6 +31,8 @@ NilCheck: # Boolean switches on state based methods (such as power and mute) are useful. BooleanParameter: enabled: false +ControlParameter: + enabled: false # Allow for a larger number of constants for protocol definitions. TooManyConstants: From 8a6389fbb37f748a81926d13789147e455c73191 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 30 Nov 2017 20:12:58 +1000 Subject: [PATCH 0096/1752] (cisco:spark) fix issue with tokenizer not applying to child classes --- modules/cisco/spark/room_os.rb | 11 ++--------- modules/cisco/spark/sx20.rb | 4 ++++ modules/cisco/spark/xapi/tokenize.rb | 21 --------------------- modules/cisco/spark/xapi/tokens.rb | 18 ++++++++++++++++++ 4 files changed, 24 insertions(+), 30 deletions(-) delete mode 100644 modules/cisco/spark/xapi/tokenize.rb create mode 100644 modules/cisco/spark/xapi/tokens.rb diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 5bbf5d88..eecf7108 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -29,15 +29,8 @@ class Cisco::Spark::RoomOs (i.e. SX80, Spark Room Kit etc). DESC - tokenize \ - delimiter: Regexp.union([ # Zero-width groups (included in response) - '}', - '{}', - 'Command not recognized.', - 'OK' - ].map { |delim| /(?<=^#{delim})[\r\n]+/ }), - wait_ready: /\*r Login successful[\r\n]+/ - + tokenize delimiter: Xapi::Tokens::COMMAND_RESPONSE, + wait_ready: Xapi::Tokens::LOGIN_COMPLETE clear_queue_on_disconnect! def on_load diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 1385b275..72a4960d 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -11,6 +11,10 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs Device access requires an API user to be created on the endpoint. DESC + tokenize delimiter: Xapi::Tokens::COMMAND_RESPONSE, + wait_ready: Xapi::Tokens::LOGIN_COMPLETE + clear_queue_on_disconnect! + # Restrict access to the direct API methods to admins protect_method :xcommand, :xstatus, :xfeedback diff --git a/modules/cisco/spark/xapi/tokenize.rb b/modules/cisco/spark/xapi/tokenize.rb deleted file mode 100644 index 5ca16201..00000000 --- a/modules/cisco/spark/xapi/tokenize.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Xapi; end - -# Regexp's for tokenizing the xAPI command and response structure. -module Cisco::Spark::Xapi::Tokenize - - - module_function - - # Split a space or slash seperated path into it's components. - def path(xpath) - if xpath.respond_to? :split - xpath.split(/[\s\/\\]/).reject(&:empty?) - else - xpath - end - end -end diff --git a/modules/cisco/spark/xapi/tokens.rb b/modules/cisco/spark/xapi/tokens.rb new file mode 100644 index 00000000..89ecaa82 --- /dev/null +++ b/modules/cisco/spark/xapi/tokens.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Xapi; end + +# Regexp's for tokenizing the xAPI command and response structure. +module Cisco::Spark::Xapi::Tokens + JSON = /(?<=^})|(?<=^{})[\r\n]+/ + + INVALID_COMMAND = /(?<=^Command not recognized\.)[\r\n]+/ + + SUCCESS = /(?<=^OK)[\r\n]+/ + + COMMAND_RESPONSE = Regexp.union([JSON, INVALID_COMMAND, SUCCESS]) + + LOGIN_COMPLETE = /\*r Login successful[\r\n]+/ +end From 2791259b2e1b9abd460ce83dcff80f1a1c7379fa Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 30 Nov 2017 21:20:46 +1000 Subject: [PATCH 0097/1752] (cisco: spark) Move generation of module version out it's own class --- modules/cisco/spark/room_os.rb | 16 ++++++++-------- modules/cisco/spark/util/meta.rb | 12 ++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 modules/cisco/spark/util/meta.rb diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index eecf7108..d88f3697 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -34,17 +34,16 @@ class Cisco::Spark::RoomOs clear_queue_on_disconnect! def on_load - on_update + load_settings end def on_unload; end def on_update - load_setting :peripheral_id, default: SecureRandom.uuid, persist: true - load_setting :version, default: version + load_settings # Force a reconnect and event resubscribe following module updates. - disconnect if self[:connected] + disconnect end def connected @@ -327,14 +326,15 @@ def heartbeat(timeout:) Timeout: timeout end - def version - "#{self.class.name}-#{Util::Git.hash __dir__}" - end - # Load a setting into a status variable of the same name. def load_setting(name, default:, persist: false) value = setting(name) define_setting(name, default) if value.nil? && persist self[name] = value || default end + + def load_settings + load_setting :peripheral_id, default: SecureRandom.uuid, persist: true + load_setting :version, default: Util::Meta.version(self) + end end diff --git a/modules/cisco/spark/util/meta.rb b/modules/cisco/spark/util/meta.rb new file mode 100644 index 00000000..e3e9e3a6 --- /dev/null +++ b/modules/cisco/spark/util/meta.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require_relative 'git' + +module Cisco::Spark::Util::Meta + module_function + + def version(instance) + hash = Cisco::Spark::Util::Git.hash __dir__ + "#{instance.class.name}-#{hash}" + end +end From 94a25413bb14c98a3ba9e4fb975235b86d82e765 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 30 Nov 2017 21:37:27 +1000 Subject: [PATCH 0098/1752] (cisco:spark) neaten up file layout --- modules/cisco/spark/room_os.rb | 81 ++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index d88f3697..558a7e77 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -33,6 +33,10 @@ class Cisco::Spark::RoomOs wait_ready: Xapi::Tokens::LOGIN_COMPLETE clear_queue_on_disconnect! + + # ------------------------------ + # Callbacks + def on_load load_settings end @@ -47,17 +51,13 @@ def on_update end def connected - send "Echo off\n", priority: 96 do |response| - :success if response.starts_with? "\e[?1034h" - end - - send "xPreferences OutputMode JSON\n", wait: false + init_connection register_control_system.then do schedule.every('30s') { heartbeat timeout: 35 } end - subscribe_to_configuration + sync_config end def disconnected @@ -99,6 +99,10 @@ def received(data, deferrable, command) end end + + # ------------------------------ + # Exec methods + # Execute an xCommand on the device. # # @param command [String] the command to execute @@ -125,8 +129,13 @@ def xstatus(path) send_xstatus path end + protected + + # ------------------------------ + # xAPI interactions + # Perform the actual command execution - this allows device implementations # to protect access to #xcommand and still refer the gruntwork here. def send_xcommand(command, args = {}) @@ -233,6 +242,10 @@ def send_xstatus(path) defer.promise end + + # ------------------------------ + # Event subscription + # Subscribe to feedback from the device. # # @param path [String, Array] the xPath to subscribe to updates for @@ -264,12 +277,20 @@ def clear_subscriptions unsubscribe '/' end - def subscribe_to_configuration - subscribe '/Configuration' do |configuration| - self[:configuration] = configuration + def subscriptions + @subscriptions ||= Util::FeedbackTrie.new + end + + + # ------------------------------ + # Base comms + + def init_connection + send "Echo off\n", priority: 96 do |response| + :success if response.starts_with? "\e[?1034h" end - send "xConfiguration *\n", wait: false + send "xPreferences OutputMode JSON\n", wait: false end # Execute raw command on the device. @@ -308,10 +329,34 @@ def generate_request_uuid SecureRandom.uuid end - def subscriptions - @subscriptions ||= Util::FeedbackTrie.new + + # ------------------------------ + # Module status + + # Load a setting into a status variable of the same name. + def load_setting(name, default:, persist: false) + value = setting(name) + define_setting(name, default) if value.nil? && persist + self[name] = value || default + end + + def load_settings + load_setting :peripheral_id, default: SecureRandom.uuid, persist: true + load_setting :version, default: Util::Meta.version(self) + end + + def sync_config + subscribe '/Configuration' do |configuration| + self[:configuration] = configuration + end + + send "xConfiguration *\n", wait: false end + + # ------------------------------ + # Connectivity management + def register_control_system send_xcommand 'Peripherals Connect', ID: self[:peripheral_id], @@ -325,16 +370,4 @@ def heartbeat(timeout:) ID: self[:peripheral_id], Timeout: timeout end - - # Load a setting into a status variable of the same name. - def load_setting(name, default:, persist: false) - value = setting(name) - define_setting(name, default) if value.nil? && persist - self[name] = value || default - end - - def load_settings - load_setting :peripheral_id, default: SecureRandom.uuid, persist: true - load_setting :version, default: Util::Meta.version(self) - end end From 397bad41bc82a38af3333c06a3dfd11056a1e7c8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 1 Dec 2017 14:48:15 +1100 Subject: [PATCH 0099/1752] Update to use user's database path --- lib/ibm/domino.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 0462c74f..71e32093 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -20,15 +20,18 @@ def initialize( @domino_api = UV::HttpEndpoint.new(@domain, {inactivity_timeout: 25000}) end - def domino_request(request_method, endpoint, data = nil, query = {}, headers = {}) + def domino_request(request_method, endpoint, data = nil, query = {}, headers = {}, full_path = nil) # Convert our request method to a symbol and our data to a JSON string request_method = request_method.to_sym data = data.to_json if !data.nil? && data.class != String @headers.merge(headers) if headers - if request_method == :post - + if full_path + uri = URI.parse(full_path) + domino_api = UV::HttpEndpoint.new("http://#{uri.host}", {inactivity_timeout: 25000}) + domino_path = uri.to_s + elsif request_method == :post domino_api = UV::HttpEndpoint.new(ENV['DOMINO_CREATE_DOMAIN'], {inactivity_timeout: 25000}) domino_path = "#{ENV['DOMINO_CREATE_DOMAIN']}#{endpoint}" else @@ -162,7 +165,7 @@ def create_booking(current_user:, starting:, ending:, database:, room_id:, summa }) - request = domino_request('post', "/#{database}/api/calendar/events", {events: [event]}).value + request = domino_request('post', nil, {events: [event]}, nil, nil, database).value request end From 488cf1b39a1c3d1d937a9408a3fd0cf790bd2eb6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 4 Dec 2017 13:07:17 +1100 Subject: [PATCH 0100/1752] Set HTTPS --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 71e32093..81d5e420 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -28,8 +28,8 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { @headers.merge(headers) if headers if full_path - uri = URI.parse(full_path) - domino_api = UV::HttpEndpoint.new("http://#{uri.host}", {inactivity_timeout: 25000}) + uri = URI.parse(full_path + '/api/calendar/events') + domino_api = UV::HttpEndpoint.new("https://#{uri.host}", {inactivity_timeout: 25000}) domino_path = uri.to_s elsif request_method == :post domino_api = UV::HttpEndpoint.new(ENV['DOMINO_CREATE_DOMAIN'], {inactivity_timeout: 25000}) From 5c19f090106e099fd26b3051a76fe5f3af1a4cd0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 4 Dec 2017 13:07:17 +1100 Subject: [PATCH 0101/1752] Set HTTPS --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 71e32093..81d5e420 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -28,8 +28,8 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { @headers.merge(headers) if headers if full_path - uri = URI.parse(full_path) - domino_api = UV::HttpEndpoint.new("http://#{uri.host}", {inactivity_timeout: 25000}) + uri = URI.parse(full_path + '/api/calendar/events') + domino_api = UV::HttpEndpoint.new("https://#{uri.host}", {inactivity_timeout: 25000}) domino_path = uri.to_s elsif request_method == :post domino_api = UV::HttpEndpoint.new(ENV['DOMINO_CREATE_DOMAIN'], {inactivity_timeout: 25000}) From 28430274015fecf14972dd5b31a20a10966d330a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 5 Dec 2017 17:19:08 +1100 Subject: [PATCH 0102/1752] Set empty array if body empty --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 81d5e420..96afac06 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -89,7 +89,7 @@ def get_bookings(room_id, date=Time.now.tomorrow.midnight) # Go through the returned bookings and add to output array rooms_bookings = [] - bookings = JSON.parse(response.body)['viewentry'] + bookings = JSON.parse(response.body)['viewentry'] || [] bookings.each{ |booking| domino_room_name = booking['entrydata'][2]['text']['0'].split('/')[0] if room_name == domino_room_name From 7a7bfe29436961b88e7337515dbb1821191d3bf8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Dec 2017 11:49:09 +1000 Subject: [PATCH 0103/1752] (cisco:spark) ensure parent modules exist --- modules/cisco/spark/util/case_insensitive_hash.rb | 4 ++++ modules/cisco/spark/util/feedback_trie.rb | 4 ++++ modules/cisco/spark/util/git.rb | 4 ++++ modules/cisco/spark/util/meta.rb | 4 ++++ modules/cisco/spark/xapi/action.rb | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/modules/cisco/spark/util/case_insensitive_hash.rb b/modules/cisco/spark/util/case_insensitive_hash.rb index 5737ecaf..0624ad6a 100644 --- a/modules/cisco/spark/util/case_insensitive_hash.rb +++ b/modules/cisco/spark/util/case_insensitive_hash.rb @@ -2,6 +2,10 @@ require 'active_support/core_ext/hash/indifferent_access' +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Util; end + class Cisco::Spark::Util::CaseInsensitiveHash < \ ActiveSupport::HashWithIndifferentAccess def [](key) diff --git a/modules/cisco/spark/util/feedback_trie.rb b/modules/cisco/spark/util/feedback_trie.rb index 5a8fd383..567420cd 100644 --- a/modules/cisco/spark/util/feedback_trie.rb +++ b/modules/cisco/spark/util/feedback_trie.rb @@ -2,6 +2,10 @@ require_relative 'case_insensitive_hash' +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Util; end + class Cisco::Spark::Util::FeedbackTrie < Cisco::Spark::Util::CaseInsensitiveHash # Insert a response handler block to be notified of updates effecting the # specified feedback path. diff --git a/modules/cisco/spark/util/git.rb b/modules/cisco/spark/util/git.rb index 8b07fc8c..d00a92dc 100644 --- a/modules/cisco/spark/util/git.rb +++ b/modules/cisco/spark/util/git.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Util; end + module Cisco::Spark::Util::Git module_function diff --git a/modules/cisco/spark/util/meta.rb b/modules/cisco/spark/util/meta.rb index e3e9e3a6..2b83b39f 100644 --- a/modules/cisco/spark/util/meta.rb +++ b/modules/cisco/spark/util/meta.rb @@ -2,6 +2,10 @@ require_relative 'git' +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Util; end + module Cisco::Spark::Util::Meta module_function diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index 0a2f3c93..a623dead 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -2,6 +2,10 @@ require 'set' +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Xapi; end + # Pure utility methods for building Cisco xAPI actions. module Cisco::Spark::Xapi::Action ACTION_TYPE ||= Set.new [ From f22a056bf3bfde36fa483f8a8fa7256e5172fa1c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Dec 2017 11:56:38 +1000 Subject: [PATCH 0104/1752] (cisco:spark) mixin util/api modules --- modules/cisco/spark/room_os.rb | 33 +++++++++++++++------------------ modules/cisco/spark/sx20.rb | 4 ++-- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 558a7e77..d085f4be 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -3,18 +3,15 @@ require 'json' require 'securerandom' +Dir[File.join(__dir__, '{xapi,util}', '*.rb')].each { |lib| load lib } + module Cisco; end module Cisco::Spark; end -module Cisco::Spark::Xapi; end -module Cisco::Spark::Util; end -Dir[File.join(__dir__, '{xapi,util}', '*.rb')].each { |lib| load lib } - class Cisco::Spark::RoomOs include ::Orchestrator::Constants - - Xapi = ::Cisco::Spark::Xapi - Util = ::Cisco::Spark::Util + include ::Cisco::Spark::Xapi + include ::Cisco::Spark::Util implements :ssh descriptive_name 'Cisco Spark Room Device' @@ -29,8 +26,8 @@ class Cisco::Spark::RoomOs (i.e. SX80, Spark Room Kit etc). DESC - tokenize delimiter: Xapi::Tokens::COMMAND_RESPONSE, - wait_ready: Xapi::Tokens::LOGIN_COMPLETE + tokenize delimiter: Tokens::COMMAND_RESPONSE, + wait_ready: Tokens::LOGIN_COMPLETE clear_queue_on_disconnect! @@ -73,7 +70,7 @@ def disconnected def received(data, deferrable, command) logger.debug { "<- #{data}" } - response = JSON.parse data, object_class: Util::CaseInsensitiveHash + response = JSON.parse data, object_class: CaseInsensitiveHash if block_given? # Let any pending command response handlers have first pass... @@ -139,7 +136,7 @@ def xstatus(path) # Perform the actual command execution - this allows device implementations # to protect access to #xcommand and still refer the gruntwork here. def send_xcommand(command, args = {}) - request = Xapi::Action.xcommand command, args + request = Action.xcommand command, args do_send request, name: command do |response| # The result keys are a little odd: they're a concatenation of the @@ -172,7 +169,7 @@ def send_xcommand(command, args = {}) # Apply a single configuration on the device. def send_xconfiguration(path, setting, value) - request = Xapi::Action.xconfiguration path, setting, value + request = Action.xconfiguration path, setting, value do_send request, name: "#{path} #{setting}" do |response| result = response.dig 'CommandResponse', 'Configuration' @@ -217,12 +214,12 @@ def send_xconfigurations(path, settings) # a pre-parsed response object for the command, if used this block # should return the response result def send_xstatus(path) - request = Xapi::Action.xstatus path + request = Action.xstatus path defer = thread.defer do_send request, name: "? #{path}" do |response| - path_components = Xapi::Action.tokenize path + path_components = Action.tokenize path status_response = response.dig 'Status', *path_components error_result = response.dig 'CommandResponse', 'Status' @@ -254,7 +251,7 @@ def subscribe(path, &update_handler) logger.debug { "Subscribing to device feedback for #{path}" } unless subscriptions.contains? path - request = Xapi::Action.xfeedback :register, path + request = Action.xfeedback :register, path # Always returns an empty response, nothing special to handle result = do_send request end @@ -269,7 +266,7 @@ def unsubscribe(path) subscriptions.remove path - request = Xapi::Action.xfeedback :deregister, path + request = Action.xfeedback :deregister, path do_send request end @@ -278,7 +275,7 @@ def clear_subscriptions end def subscriptions - @subscriptions ||= Util::FeedbackTrie.new + @subscriptions ||= FeedbackTrie.new end @@ -342,7 +339,7 @@ def load_setting(name, default:, persist: false) def load_settings load_setting :peripheral_id, default: SecureRandom.uuid, persist: true - load_setting :version, default: Util::Meta.version(self) + load_setting :version, default: Meta.version(self) end def sync_config diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 72a4960d..a1333df4 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -11,8 +11,8 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs Device access requires an API user to be created on the endpoint. DESC - tokenize delimiter: Xapi::Tokens::COMMAND_RESPONSE, - wait_ready: Xapi::Tokens::LOGIN_COMPLETE + tokenize delimiter: Tokens::COMMAND_RESPONSE, + wait_ready: Tokens::LOGIN_COMPLETE clear_queue_on_disconnect! # Restrict access to the direct API methods to admins From 8cf7044cd8d616ed5135bab90f448f3aed52126e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Dec 2017 00:52:03 +1000 Subject: [PATCH 0105/1752] (cisco:spark) map all response data to appropriate types --- modules/cisco/spark/room_os.rb | 17 +++--- modules/cisco/spark/room_os_spec.rb | 4 +- modules/cisco/spark/xapi/response.rb | 80 ++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 modules/cisco/spark/xapi/response.rb diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index d085f4be..e7857503 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -70,7 +70,7 @@ def disconnected def received(data, deferrable, command) logger.debug { "<- #{data}" } - response = JSON.parse data, object_class: CaseInsensitiveHash + response = Response.parse data, into: CaseInsensitiveHash if block_given? # Let any pending command response handlers have first pass... @@ -83,7 +83,7 @@ def received(data, deferrable, command) subscriptions.notify response :ignore end - rescue JSON::ParserError => error + rescue Response::ParserError => error case data.strip when 'OK' :success @@ -156,8 +156,7 @@ def send_xcommand(command, args = {}) if result['status'] == 'OK' :success else - reason = result.dig 'Reason', 'Value' - logger.error reason if reason + logger.error result['Reason'] :abort end else @@ -175,9 +174,7 @@ def send_xconfiguration(path, setting, value) result = response.dig 'CommandResponse', 'Configuration' if result&.[]('status') == 'Error' - reason = result.dig 'Reason', 'Value' - xpath = result.dig 'XPath', 'Value' - logger.error "#{reason} (#{xpath})" if reason + logger.error "#{result['Reason']} (#{result['XPath']})" :abort else :success @@ -221,16 +218,14 @@ def send_xstatus(path) do_send request, name: "? #{path}" do |response| path_components = Action.tokenize path status_response = response.dig 'Status', *path_components - error_result = response.dig 'CommandResponse', 'Status' if status_response yield status_response if block_given? defer.resolve status_response :success else - reason = error_result&.dig 'Reason', 'Value' - xpath = error_result&.dig 'XPath', 'Value' - logger.error "#{reason} (#{xpath})" if reason + error = response.dig 'CommandResponse', 'Status' + logger.error "#{error['Reason']} (#{error['XPath']})" defer.reject :abort end diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 57454357..8d0fc781 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -179,7 +179,7 @@ def section(message) } JSON ) - expect(status[:configuration].dig(:audio, :input, :microphone, 1, :mode, :value)).to eq 'On' + expect(status[:configuration].dig(:audio, :input, :microphone, 1, :mode)).to be true # ------------------------------------------------------------------------- section 'Base comms (protected methods - ignore the access warnings)' @@ -483,5 +483,5 @@ def section(message) } JSON ) - expect(result.dig('SystemTime', 'Value')).to eq '2017-11-27T15:14:25+1000' + expect(result['SystemTime']).to eq '2017-11-27T15:14:25+1000' end diff --git a/modules/cisco/spark/xapi/response.rb b/modules/cisco/spark/xapi/response.rb new file mode 100644 index 00000000..06cb3775 --- /dev/null +++ b/modules/cisco/spark/xapi/response.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'json' + +module Cisco; end +module Cisco::Spark; end +module Cisco::Spark::Xapi; end + +module Cisco::Spark::Xapi::Response + class ParserError < StandardError; end + + module_function + + # Parse a raw device response. + # + # @param data [String] the raw device response to parse + # @param into [Class] the object class to parser into (subclass of Hash) + # @return a nested structure containing the fully parsed response + # @raise [ParserError] if data is invalid + def parse(data, into: Hash) + response = JSON.parse data, object_class: into + compress response + rescue JSON::ParserError => error + raise ParserError, error + end + + # Lift the 'Value' keys returned from raw response so their parent contains + # a direct value object rather than a hash of the value and type. + def compress(fragment) + case fragment + when Hash + value, valuespaceref = fragment.values_at(:value, :valuespaceref) + if value + valuespace = valuespaceref&.split('/')&.last&.to_sym + convert value, valuespace + else + fragment.transform_values { |item| compress item } + end + when Array + fragment.map { |item| compress item } + else + fragment + end + end + + BOOLEAN = ->(val) { ['On', 'True'].include? val } + BOOL_OR = lambda do |term| + sym = term.to_sym + ->(val) { val == term ? sym : BOOLEAN[val] } + end + + PARSERS = { + TTPAR_OnOff: BOOLEAN, + TTPAR_OnOffAuto: BOOL_OR['Auto'], + TTPAR_OnOffCurrent: BOOL_OR['Current'], + TTPAR_MuteEnabled: BOOLEAN + }.freeze + + # Map a raw response value to an appropriate datatype. + # + # @param value [String] the value to convert + # @param valuespace [Symbol] the Cisco value space reference + # @return the value as an appropriate core datatype + def convert(value, valuespace) + if valuespace + parser = PARSERS[valuespace] + if parser + parser.call(value) + else + begin + Integer(value) + rescue ArgumentError + value.to_sym + end + end + else + value + end + end +end From aaa691f8d73f0fa6be141c9554044f3c7e31834d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Dec 2017 11:45:15 +1000 Subject: [PATCH 0106/1752] (cisco:spark) vaidate arguments in outgoing device commands --- modules/cisco/spark/sx20.rb | 4 +-- modules/cisco/spark/xapi/action.rb | 4 +-- modules/cisco/spark/xapi/mapper.rb | 42 +++++++++++++++++------------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index a1333df4..48f62a47 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -20,7 +20,7 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs command 'Call Accept' => :accept, - _CallId: Integer + CallId_: Integer CAMERA_MOVE_DIRECTION = [ :Left, @@ -32,7 +32,7 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs ].freeze command 'Call FarEndCameraControl Move' => :far_end_camera, Value: CAMERA_MOVE_DIRECTION, - _CallId: Integer + CallId_: Integer # configuration! 'Network/n/VLAN/Voice' => :set_voice_vlan, # Mode: [:Auto, :Manual, :Off] diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index a623dead..1784e240 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -48,8 +48,8 @@ def create_action(type, *args, **kwargs) # @param path [String, Array] command path # @param args [Hash] an optional hash of keyword arguments # @return [String] - def xcommand(path, **args) - create_action :xCommand, path, args + def xcommand(path, args) + create_action :xCommand, path, **args end # Serialize an xConfiguration action into a transmittable command. diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 4d9c8fa0..04a98031 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -16,7 +16,7 @@ module ApiMapperMethods # command 'Fake n Command' => :my_method, # ParamA: [:enum, :of, :options], # ParamB: String, - # _OptionalParam: Integer + # OptionalParam_: Integer # # Will provide the method: # def my_method(index, param_a, param_b, optional_param = nil) @@ -29,34 +29,38 @@ module ApiMapperMethods # (as per the device protocol guide), this will be lifted into an # 'index' parameter # - all other pairs are ParamName: - # - prefix optional params with an underscore + # - suffix optional params with an underscore # @return [Symbol] the mapped method name def command(mapping) command_path, method_name = mapping.shift - raise ArgumentError, 'mapped command must be a String' \ - unless command_path.is_a? String - - raise ArgumentError, 'method name must be a Symbol' \ - unless method_name.is_a? Symbol - params = mapping.keys.map { |name| name.to_s.underscore } - opt_, req = params.partition { |name| name.starts_with? '_' } - opt = opt_.map { |name| "#{name[1..-1]} = nil" } - param_str = (req + opt).join ', ' + opt_, req = params.partition { |name| name.ends_with? '_' } + opt = opt_.map { |name| name.chomp '_' } + + param_str = (req + opt.map { |name| "#{name} = nil" }).join ', ' # TODO: add support for index commands command_str = command_path.split(' ').join(' ') - # use .tap to isolate - # TODO: add argument type checks + types = Hash[(req + opt).zip(mapping.values)] + type_checks = types.map do |param, type| + if type.is_a? Class + msg = "#{param} must be a #{type}" + cond = "#{param}.is_a?(#{type})" + else + msg = "#{param} must be one of #{type}" + cond = "#{type}.any? { |t| t.to_s.casecmp(#{param}) == 0 }" + end + "raise ArgumentError, '#{msg}' unless #{param}.nil? || #{cond}" + end + type_check_str = type_checks.join "\n" - class_eval <<-METHOD + class_eval <<~METHOD def #{method_name}(#{param_str}) + #{type_check_str} args = binding.local_variables - .map { |p| - [p.to_s.camelize, binding.local_variable_get(p)] - } + .map { |p| [p.to_s.camelize.to_sym, binding.local_variable_get(p)] } .to_h .compact send_xcommand '#{command_str}', args @@ -75,7 +79,9 @@ def command!(mapping) end end - def self.included(klass) + module_function + + def included(klass) klass.extend ApiMapperMethods end end From 98195839ebef2dad554fa68818d3a1975f34ef71 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Dec 2017 17:39:41 +1000 Subject: [PATCH 0107/1752] (cisco:spark) fix warnings about constant redefinition during hot reload --- modules/cisco/spark/xapi/response.rb | 6 +++--- modules/cisco/spark/xapi/tokens.rb | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/cisco/spark/xapi/response.rb b/modules/cisco/spark/xapi/response.rb index 06cb3775..c44c88cf 100644 --- a/modules/cisco/spark/xapi/response.rb +++ b/modules/cisco/spark/xapi/response.rb @@ -43,13 +43,13 @@ def compress(fragment) end end - BOOLEAN = ->(val) { ['On', 'True'].include? val } - BOOL_OR = lambda do |term| + BOOLEAN ||= ->(val) { ['On', 'True'].include? val } + BOOL_OR ||= lambda do |term| sym = term.to_sym ->(val) { val == term ? sym : BOOLEAN[val] } end - PARSERS = { + PARSERS ||= { TTPAR_OnOff: BOOLEAN, TTPAR_OnOffAuto: BOOL_OR['Auto'], TTPAR_OnOffCurrent: BOOL_OR['Current'], diff --git a/modules/cisco/spark/xapi/tokens.rb b/modules/cisco/spark/xapi/tokens.rb index 89ecaa82..08e58cf7 100644 --- a/modules/cisco/spark/xapi/tokens.rb +++ b/modules/cisco/spark/xapi/tokens.rb @@ -6,13 +6,13 @@ module Cisco::Spark::Xapi; end # Regexp's for tokenizing the xAPI command and response structure. module Cisco::Spark::Xapi::Tokens - JSON = /(?<=^})|(?<=^{})[\r\n]+/ + JSON_RESPONSE ||= /(?<=^})|(?<=^{})[\r\n]+/ - INVALID_COMMAND = /(?<=^Command not recognized\.)[\r\n]+/ + INVALID_COMMAND ||= /(?<=^Command not recognized\.)[\r\n]+/ - SUCCESS = /(?<=^OK)[\r\n]+/ + SUCCESS ||= /(?<=^OK)[\r\n]+/ - COMMAND_RESPONSE = Regexp.union([JSON, INVALID_COMMAND, SUCCESS]) + COMMAND_RESPONSE ||= Regexp.union([JSON_RESPONSE, INVALID_COMMAND, SUCCESS]) - LOGIN_COMPLETE = /\*r Login successful[\r\n]+/ + LOGIN_COMPLETE ||= /\*r Login successful[\r\n]+/ end From 4826ffae696b7d182cfcc2ca9a4be1dcba8a418c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Dec 2017 17:44:07 +1000 Subject: [PATCH 0108/1752] (cisco:spark) protect direct API methods from non-admin access --- modules/cisco/spark/sx20.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 48f62a47..17871d0b 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -16,7 +16,7 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs clear_queue_on_disconnect! # Restrict access to the direct API methods to admins - protect_method :xcommand, :xstatus, :xfeedback + protect_method :xcommand, :xconfigruation, :xstatus command 'Call Accept' => :accept, From 604d078593c644d978f4ab33bbfb0b12ca15a86f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Dec 2017 18:16:39 +1000 Subject: [PATCH 0109/1752] (cisco:spark) fix naming conflict with device feedback and module state subscription --- modules/cisco/spark/room_os.rb | 26 +++++++++++++------------- modules/cisco/spark/room_os_spec.rb | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index e7857503..17e2b708 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -58,7 +58,7 @@ def connected end def disconnected - clear_subscriptions + clear_device_subscriptions schedule.clear end @@ -77,10 +77,10 @@ def received(data, deferrable, command) yield(response).tap do |command_result| # Otherwise support interleaved async events unhandled = [:ignore, nil].include? command_result - subscriptions.notify response if unhandled + device_subscriptions.notify response if unhandled end else - subscriptions.notify response + device_subscriptions.notify response :ignore end rescue Response::ParserError => error @@ -242,35 +242,35 @@ def send_xstatus(path) # # @param path [String, Array] the xPath to subscribe to updates for # @param update_handler [Proc] a callback to receive updates for the path - def subscribe(path, &update_handler) + def register_feedback(path, &update_handler) logger.debug { "Subscribing to device feedback for #{path}" } - unless subscriptions.contains? path + unless device_subscriptions.contains? path request = Action.xfeedback :register, path # Always returns an empty response, nothing special to handle result = do_send request end - subscriptions.insert path, &update_handler + device_subscriptions.insert path, &update_handler result || thread.defer.resolve(:success) end - def unsubscribe(path) + def unregister_feedback(path) logger.debug { "Unsubscribing feedback for #{path}" } - subscriptions.remove path + device_subscriptions.remove path request = Action.xfeedback :deregister, path do_send request end - def clear_subscriptions - unsubscribe '/' + def clear_device_subscriptions + unregister_feedback '/' end - def subscriptions - @subscriptions ||= FeedbackTrie.new + def device_subscriptions + @device_subscriptions ||= FeedbackTrie.new end @@ -338,7 +338,7 @@ def load_settings end def sync_config - subscribe '/Configuration' do |configuration| + register_feedback '/Configuration' do |configuration| self[:configuration] = configuration end diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 8d0fc781..20f0dd52 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -232,7 +232,7 @@ def section(message) expect(result).to be :success # Device event subscription - exec(:subscribe, '/Status/Audio/Microphones/Mute') + exec(:register_feedback, '/Status/Audio/Microphones/Mute') .should_send("xFeedback register /Status/Audio/Microphones/Mute | resultId=\"#{id_peek}\"\n") .responds( <<~JSON From d8c3cd91f62e413d093830b768c5e24ad613cadb Mon Sep 17 00:00:00 2001 From: Creeves Date: Thu, 7 Dec 2017 20:23:39 +0800 Subject: [PATCH 0110/1752] Add user's booking request --- lib/ibm/domino.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 96afac06..c8ec1c95 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -57,7 +57,21 @@ def get_free_rooms(starting, ending) domino_emails = JSON.parse(res.body)['rooms'] end + def get_users_bookings(database, date=Time.now.tomorrow.midnight) + # Make date a date object from epoch or parsed text + date = convert_to_simpledate(date) + + starting = to_ibm_date(date.yesterday) + ending = to_ibm_date(date) + + query = { + before: ending, + since: starting + } + request = domino_request('get', nil, nil, query, nil, database).value + request + end def get_bookings(room_id, date=Time.now.tomorrow.midnight) room = Orchestrator::ControlSystem.find(room_id) @@ -174,7 +188,7 @@ def delete_booking(room, id) end - def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + def (id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, From 88b0b74456c8b42019faae7e8f82b494d0ab5d8a Mon Sep 17 00:00:00 2001 From: Creeves Date: Thu, 7 Dec 2017 20:38:06 +0800 Subject: [PATCH 0111/1752] Fucking typo --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index c8ec1c95..9d6272f6 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -188,7 +188,7 @@ def delete_booking(room, id) end - def (id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, From 224cda1760d1f7093e25295dbd971fcb7d20ea45 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Dec 2017 22:42:56 +1000 Subject: [PATCH 0112/1752] (cisco:spark) add ability to bind device state to status variables --- modules/cisco/spark/room_os.rb | 10 +++++++--- modules/cisco/spark/sx20.rb | 4 ++++ modules/cisco/spark/xapi/mapper.rb | 32 ++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 17e2b708..9ea89175 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -337,11 +337,15 @@ def load_settings load_setting :version, default: Meta.version(self) end - def sync_config - register_feedback '/Configuration' do |configuration| - self[:configuration] = configuration + # Bind arbitary device state to a status variable. + def bind_state(path, status_key) + register_feedback path do |value| + self[status_key] = value end + end + def sync_config + bind_state '/Configuration', :configuration send "xConfiguration *\n", wait: false end diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 17871d0b..e273fe0b 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -19,6 +19,10 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs protect_method :xcommand, :xconfigruation, :xstatus + state '/Status/Standby/State' => :standby + + state '/Status/Audio/Microphones/Mute' => :mic_mute + command 'Call Accept' => :accept, CallId_: Integer diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 04a98031..6716e3e8 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -77,11 +77,39 @@ def #{method_name}(#{param_str}) def command!(mapping) protect_method command(mapping) end + + # Define a binding between device state and module status variables. + # + # Similar to command bindings, this provides a declarative mapping + # from a device xpath to an exposed state variable. Subscriptions will + # be automatically setup as part of connection initialisation. + # + # Example: + # state '/Status/Standby/State' => :standby + # + # Will track the device standby state and push it to self[:standby] + # + # @param mapping [Hash] a set of xpath => status variable bindings + def state(mapping) + state_mappings.merge! mapping + end + + def state_mappings + @mappings ||= {} + end + end + + module ApiMapperHooks + def connected + super + self.class.state_mappings.each(&method(:bind_state)) + end end module_function - def included(klass) - klass.extend ApiMapperMethods + def included(base) + base.extend ApiMapperMethods + base.prepend ApiMapperHooks end end From dfdafa2817925e836f69932e0f76ac8b2005b465 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 8 Dec 2017 00:15:02 +1000 Subject: [PATCH 0113/1752] (cisco:spark) add support for specifiying value ranges for command args --- modules/cisco/spark/xapi/mapper.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 6716e3e8..5c77250c 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -45,9 +45,13 @@ def command(mapping) types = Hash[(req + opt).zip(mapping.values)] type_checks = types.map do |param, type| - if type.is_a? Class + case type + when Class msg = "#{param} must be a #{type}" cond = "#{param}.is_a?(#{type})" + when Range + msg = "#{param} must be within #{type}" + cond = "(#{type}).include?(#{param})" else msg = "#{param} must be one of #{type}" cond = "#{type}.any? { |t| t.to_s.casecmp(#{param}) == 0 }" From 31f1d1b0c7bfaade19f7a807058ad9a6144c71d0 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 8 Dec 2017 00:35:55 +1000 Subject: [PATCH 0114/1752] (cisco:spark) add mapping for available commands on the SX20 --- modules/cisco/spark/sx20.rb | 38 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index e273fe0b..4a228fb5 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -load File.expand_path('./room_os.rb', File.dirname(__FILE__)) +load File.join(__dir__, 'room_os.rb') class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs include ::Orchestrator::Security @@ -15,29 +15,25 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs wait_ready: Tokens::LOGIN_COMPLETE clear_queue_on_disconnect! - # Restrict access to the direct API methods to admins protect_method :xcommand, :xconfigruation, :xstatus + command 'Audio Microphones Mute' => :mic_mute_on + command 'Audio Microphones Unmute' => :mic_mute_off + command 'Audio Microphones ToggleMute' => :mic_mute_toggle + state '/Status/Audio/Microphones/Mute' => :mic_mute + command 'Audio Sound Play' => :play_sound, + Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, :CallWaiting, + :Dial, :KeyInput, :KeyInputDelete, :KeyTone, :Nav, :NavBack, + :Notification, :OK, :PresentationConnect, :Ringing, :SignIn, + :SpecialInfo, :TelephoneCall, :VideoCall, :VolumeAdjust, :WakeUp], + Loop_: [:Off, :On] + command 'Audio Sound Stop' => :stop_sound + + command 'Standby Deactivate' => :wake_up + command 'Standby Activate' => :standby + command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) state '/Status/Standby/State' => :standby - state '/Status/Audio/Microphones/Mute' => :mic_mute - - command 'Call Accept' => :accept, - CallId_: Integer - - CAMERA_MOVE_DIRECTION = [ - :Left, - :Right, - :Up, - :Down, - :ZoomIn, - :ZoomOut - ].freeze - command 'Call FarEndCameraControl Move' => :far_end_camera, - Value: CAMERA_MOVE_DIRECTION, - CallId_: Integer - - # configuration! 'Network/n/VLAN/Voice' => :set_voice_vlan, - # Mode: [:Auto, :Manual, :Off] + command! 'SystemUnit Boot' => :reboot, Action_: [:Restart, :Shutdown] end From 03fdb81c9a0d6d8bfc749f2c0458d59a753bc0dc Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 8 Dec 2017 12:09:36 +1100 Subject: [PATCH 0115/1752] (aca:tracking) desk management to provide usage summary --- modules/aca/tracking/desk_man_virtual.ts | 3 ++ modules/aca/tracking/desk_management.rb | 37 ++++++++++++------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/modules/aca/tracking/desk_man_virtual.ts b/modules/aca/tracking/desk_man_virtual.ts index d37f53ad..e1ae7d7e 100644 --- a/modules/aca/tracking/desk_man_virtual.ts +++ b/modules/aca/tracking/desk_man_virtual.ts @@ -25,6 +25,9 @@ win.control.systems['sys-desk-tracking'] = { // Reserved desks that are not in use but otherwise reserved "building:level1:reserved": ["desk3", "desk8"], + // Number of free desks on the level + "building:level1:occupied_count": 6, + // If connected==false, reserved_by==your/current user, user.reserve_time < global.reserve_time and user.reserve_time + unplug_time < time.now // then prompt user to reserve or release desk. // If conflict=true then notify that your sitting at someone elses desk diff --git a/modules/aca/tracking/desk_management.rb b/modules/aca/tracking/desk_management.rb index e8f92441..461d79da 100644 --- a/modules/aca/tracking/desk_management.rb +++ b/modules/aca/tracking/desk_management.rb @@ -8,6 +8,8 @@ module Aca::Tracking; end class Aca::Tracking::DeskManagement include ::Orchestrator::Constants + + descriptive_name 'ACA Desk Management' generic_name :DeskManagement implements :logic @@ -118,16 +120,17 @@ def get_usage buildings.each do |building, levels| levels.each do |level, desks| key = "#{building}:#{level}" - self[key] = desks[:inuse] - self["#{key}:clashes"] = desks[:clash] - self["#{key}:reserved"] = desks[:reserved] + self[key] = desks.inuse + self["#{key}:clashes"] = desks.clash + self["#{key}:reserved"] = desks.reserved + self["#{key}:occupied_count"] = desks.inuse.length - desks.clash.length + desks.reserved.length - desks[:users].each do |user| + desks.users.each do |user| self[user.username] = user self[user.reserved_by] = user if user.clash end - desks[:reserved_users].each do |user| + desks.reserved_users.each do |user| self[user.reserved_by] = user end end @@ -137,6 +140,8 @@ def get_usage } end + PortUsage = Struct.new(:inuse, :clash, :reserved, :users, :reserved_users) + def apply_mappings(buildings, switch, mappings) switch_ip = switch[:ip_address] map = mappings[switch_ip] @@ -155,20 +160,14 @@ def apply_mappings(buildings, switch, mappings) # Build lookup structures b = buildings[building] ||= {} - port_usage = b[level] ||= { - inuse: [], - clash: [], - reserved: [], - users: [], - reserved_users: [] - } - - # Prevent needless hash lookups - inuse = port_usage[:inuse] - clash = port_usage[:clash] - reserved = port_usage[:reserved] - users = port_usage[:users] - reserved_users = port_usage[:reserved_users] + port_usage = b[level] ||= PortUsage.new([], [], [], [], []) + + # Prevent needless lookups + inuse = port_usage.inuse + clash = port_usage.clash + reserved = port_usage.reserved + users = port_usage.users + reserved_users = port_usage.reserved_users # Map the ports to desk IDs interfaces.each do |port| From 6e9b689d0f0c35839e23e3a03dbfe976e74951f5 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 09:10:10 +0800 Subject: [PATCH 0116/1752] Get users full booking details --- lib/ibm/domino.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 9d6272f6..5ba77a28 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -70,7 +70,17 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) } request = domino_request('get', nil, nil, query, nil, database).value - request + if [200,201,204].include?(request.status) + events = JSON.parse(booking_response.body)['events'] || [] + else + return nil + end + full_events = [] + events.each{|event| + full_event = get_attendees(database + '/' + event['id']) + full_events.push(full_event) + } + full_events end def get_bookings(room_id, date=Time.now.tomorrow.midnight) @@ -234,9 +244,8 @@ def edit_booking(id, starting:, ending:, room:, summary:, description: nil, orga request.status end - def get_attendees(booking, database) - path = "#{@domain}/#{database}/api/calendar/events/#{booking['id']}" - booking_request = @domino_api.get(path: path, headers: @headers).value + def get_attendees(path) + booking_request = domino_request('get',nil,nil,nil,nil,path).value booking_response = JSON.parse(booking_request.body)['events'][0] if booking_response['attendees'] attendees = booking_response['attendees'].dup From 790787b7004c4d901d770c019539ec871544d2da Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 8 Dec 2017 11:12:30 +1000 Subject: [PATCH 0117/1752] (cisco:spark) fix issue with repsonse compression when 'value' keys did not occur on an leaf --- modules/cisco/spark/xapi/response.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/xapi/response.rb b/modules/cisco/spark/xapi/response.rb index c44c88cf..0b30db1e 100644 --- a/modules/cisco/spark/xapi/response.rb +++ b/modules/cisco/spark/xapi/response.rb @@ -30,7 +30,7 @@ def compress(fragment) case fragment when Hash value, valuespaceref = fragment.values_at(:value, :valuespaceref) - if value + if value&.is_a? String valuespace = valuespaceref&.split('/')&.last&.to_sym convert value, valuespace else From 9b58eca16cb8eb6515726c3731442412a4556b60 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 09:30:01 +0800 Subject: [PATCH 0118/1752] Use full path if available --- lib/ibm/domino.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 5ba77a28..e1ce6fe5 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -25,10 +25,14 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { request_method = request_method.to_sym data = data.to_json if !data.nil? && data.class != String - @headers.merge(headers) if headers + @headers.merge(headers) if headers if full_path - uri = URI.parse(full_path + '/api/calendar/events') + if full_path.include?('/api/calendar/events') + uri = URI.parse(full_path) + else + uri = URI.parse(full_path + '/api/calendar/events') + end domino_api = UV::HttpEndpoint.new("https://#{uri.host}", {inactivity_timeout: 25000}) domino_path = uri.to_s elsif request_method == :post From c751c900dfd4d4508402effa710d515f4b31a727 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 09:32:05 +0800 Subject: [PATCH 0119/1752] Fix syntax issue --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index e1ce6fe5..4b4f4b43 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -75,7 +75,7 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) request = domino_request('get', nil, nil, query, nil, database).value if [200,201,204].include?(request.status) - events = JSON.parse(booking_response.body)['events'] || [] + events = JSON.parse(request.body)['events'] || [] else return nil end From cac61e2aed918c9c1618e66c2d9f889eb5746994 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 09:34:27 +0800 Subject: [PATCH 0120/1752] Fix path logic --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 4b4f4b43..bc0b5c04 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -81,7 +81,7 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) end full_events = [] events.each{|event| - full_event = get_attendees(database + '/' + event['id']) + full_event = get_attendees(database + '/api/calendar/events/' + event['id']) full_events.push(full_event) } full_events From 746b7ca46e0f7c20bcda8165bc093de50c7d33d3 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 11:37:13 +0800 Subject: [PATCH 0121/1752] Fix empty response --- lib/ibm/domino.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index bc0b5c04..efae86b5 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -75,7 +75,11 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) request = domino_request('get', nil, nil, query, nil, database).value if [200,201,204].include?(request.status) - events = JSON.parse(request.body)['events'] || [] + if request.body != '' + events = JSON.parse(request.body)['events'] + else + events = [] + end else return nil end From 6796abbd8e08716b5d1f001d15c6970f8566e59d Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 12:38:04 +0800 Subject: [PATCH 0122/1752] Search freebusy with utc --- lib/ibm/domino.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index efae86b5..845e5e6b 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -48,7 +48,10 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { def get_free_rooms(starting, ending) starting, ending = convert_to_datetime(starting, ending) - starting, ending = get_time_range(starting, ending, @timezone) + # starting, ending = get_time_range(starting, ending, @timezone) + + starting = starting.utc + ending = ending.utc req_params = { :site => ENV["DOMINO_SITE"], From 5d652064ef181087fb2abadf9d4ea2e15c8d7df5 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 12:50:16 +0800 Subject: [PATCH 0123/1752] Return UTC epoch in bookings --- lib/ibm/domino.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 845e5e6b..8c662cd3 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -277,6 +277,8 @@ def get_attendees(path) } booking_response['organizer'] = organizer end + bookings_response['start'] = Time.parse(bookings_response['start']['date']+'T'+bookings_response['start']['time']+'+0800').utc.to_i + bookings_response['end'] = Time.parse(bookings_response['end']['date']+'T'+bookings_response['end']['time']+'+0800').utc.to_i booking_response end From 787ca00e09009588cd5f1e3a830138a254b7a5a0 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 12:55:24 +0800 Subject: [PATCH 0124/1752] Accidentally pluralised --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 8c662cd3..54137dff 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -277,8 +277,8 @@ def get_attendees(path) } booking_response['organizer'] = organizer end - bookings_response['start'] = Time.parse(bookings_response['start']['date']+'T'+bookings_response['start']['time']+'+0800').utc.to_i - bookings_response['end'] = Time.parse(bookings_response['end']['date']+'T'+bookings_response['end']['time']+'+0800').utc.to_i + booking_response['start'] = Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0800').utc.to_i + booking_response['end'] = Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0800').utc.to_i booking_response end From 014f968d9aa87cf26ce2cedb267460882a4c0401 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 14:45:12 +0800 Subject: [PATCH 0125/1752] Add support URL line to description --- lib/ibm/domino.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 54137dff..6ef7ee1a 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -151,7 +151,15 @@ def create_booking(current_user:, starting:, ending:, database:, room_id:, summa :end => to_utc_date(ending) } - event[:description] = description if description + if description.nil? + description = "" + end + + if room.support_url + description = description + "\nTo control this meeting room, click here: #{room.support_url}" + event[:description] = description + end + event[:attendees] = Array(attendees).collect do |attendee| From 92da952cd72c4a023d6f4e4e57afc3097ade0618 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 15:37:44 +0800 Subject: [PATCH 0126/1752] Use href due to recurring bookings --- lib/ibm/domino.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 6ef7ee1a..24225c7e 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -88,7 +88,10 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) end full_events = [] events.each{|event| - full_event = get_attendees(database + '/api/calendar/events/' + event['id']) + db_uri = URI.parse(database) + base_domain = db_uri.scheme + "://" + uri.host + Rails.logger.info "Requesting to #{base_domain + event['href']}" + full_event = get_attendees(base_domain + event['href']) full_events.push(full_event) } full_events @@ -160,8 +163,6 @@ def create_booking(current_user:, starting:, ending:, database:, room_id:, summa event[:description] = description end - - event[:attendees] = Array(attendees).collect do |attendee| out_attendee = { role: "req-participant", From c8511176b065953fbd5c5b1cbd8f5049bd7d1630 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 15:54:00 +0800 Subject: [PATCH 0127/1752] If 404 is returned, use base event --- lib/ibm/domino.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 24225c7e..8880c91a 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -92,6 +92,9 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) base_domain = db_uri.scheme + "://" + uri.host Rails.logger.info "Requesting to #{base_domain + event['href']}" full_event = get_attendees(base_domain + event['href']) + if full_event == false + full_event = event + end full_events.push(full_event) } full_events @@ -266,6 +269,9 @@ def edit_booking(id, starting:, ending:, room:, summary:, description: nil, orga def get_attendees(path) booking_request = domino_request('get',nil,nil,nil,nil,path).value + if ![200,201,204].include?(booking_response.status) + return false + end booking_response = JSON.parse(booking_request.body)['events'][0] if booking_response['attendees'] attendees = booking_response['attendees'].dup From 162d6825ceaf9f480eb24f1c31fd02171180a83b Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 15:57:02 +0800 Subject: [PATCH 0128/1752] Fix syntax --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 8880c91a..025ce069 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -89,7 +89,7 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) full_events = [] events.each{|event| db_uri = URI.parse(database) - base_domain = db_uri.scheme + "://" + uri.host + base_domain = db_uri.scheme + "://" + db_uri.host Rails.logger.info "Requesting to #{base_domain + event['href']}" full_event = get_attendees(base_domain + event['href']) if full_event == false From b61173494da4ba511495b2e75373805b5c0ebed5 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 15:59:21 +0800 Subject: [PATCH 0129/1752] Moooore syntax issues --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 025ce069..0b500805 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -269,7 +269,7 @@ def edit_booking(id, starting:, ending:, room:, summary:, description: nil, orga def get_attendees(path) booking_request = domino_request('get',nil,nil,nil,nil,path).value - if ![200,201,204].include?(booking_response.status) + if ![200,201,204].include?(booking_request.status) return false end booking_response = JSON.parse(booking_request.body)['events'][0] From 7c422043e53d9734a5fee254edb1837f098a2774 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 16:02:05 +0800 Subject: [PATCH 0130/1752] Convert start and end to epoch --- lib/ibm/domino.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 0b500805..3239d703 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -94,6 +94,8 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) full_event = get_attendees(base_domain + event['href']) if full_event == false full_event = event + full_event['start'] = Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i + full_event['end'] = Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i end full_events.push(full_event) } From 249b8714b64863bd4bf2144fb167a4d7d4c6245f Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 16:30:01 +0800 Subject: [PATCH 0131/1752] Put temp fields in to stop frontend error --- lib/ibm/domino.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 3239d703..7b098664 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -94,6 +94,9 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) full_event = get_attendees(base_domain + event['href']) if full_event == false full_event = event + full_event['organizer'] = {email: 'N/A'} + full_event['description'] = '' + full_event['attendees'] = [] full_event['start'] = Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i full_event['end'] = Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i end From 3a0065ab3688877b8e7f35ced0739533120becf9 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 16:34:24 +0800 Subject: [PATCH 0132/1752] Move dates forward one day --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 7b098664..204ac314 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -68,8 +68,8 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) # Make date a date object from epoch or parsed text date = convert_to_simpledate(date) - starting = to_ibm_date(date.yesterday) - ending = to_ibm_date(date) + starting = to_ibm_date(date) + ending = to_ibm_date(date.tomorrow) query = { before: ending, From e1dc00aa1a49e7beb6e1b2d042b75eccb791a0d9 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 16:43:06 +0800 Subject: [PATCH 0133/1752] Use milisecond epochs --- lib/ibm/domino.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 204ac314..0fd08095 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -87,7 +87,7 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) return nil end full_events = [] - events.each{|event| + events.each{ |event| db_uri = URI.parse(database) base_domain = db_uri.scheme + "://" + db_uri.host Rails.logger.info "Requesting to #{base_domain + event['href']}" @@ -97,8 +97,8 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) full_event['organizer'] = {email: 'N/A'} full_event['description'] = '' full_event['attendees'] = [] - full_event['start'] = Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i - full_event['end'] = Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i + full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i.to_s + "000").to_i + full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i.to_s + "000").to_i end full_events.push(full_event) } From bea6e081a8aa352ece21f96f6bc5275a7b87df07 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 16:45:01 +0800 Subject: [PATCH 0134/1752] Update other epoch --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 0fd08095..56eab118 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -297,8 +297,8 @@ def get_attendees(path) } booking_response['organizer'] = organizer end - booking_response['start'] = Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0800').utc.to_i - booking_response['end'] = Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0800').utc.to_i + booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0800').utc.to_i.to_s + "000").to_i + booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0800').utc.to_i.to_s + "000").to_i booking_response end From f4fe7835c9e668e6b6e14959deeea00effe8632b Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 17:54:54 +0800 Subject: [PATCH 0135/1752] Read in as UTC --- lib/ibm/domino.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 56eab118..bddcc700 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -97,8 +97,8 @@ def get_users_bookings(database, date=Time.now.tomorrow.midnight) full_event['organizer'] = {email: 'N/A'} full_event['description'] = '' full_event['attendees'] = [] - full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i.to_s + "000").to_i - full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i.to_s + "000").to_i + full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i + full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i end full_events.push(full_event) } @@ -297,8 +297,8 @@ def get_attendees(path) } booking_response['organizer'] = organizer end - booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0800').utc.to_i.to_s + "000").to_i - booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0800').utc.to_i.to_s + "000").to_i + booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0000').utc.to_i.to_s + "000").to_i + booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0000').utc.to_i.to_s + "000").to_i booking_response end From d2836f581c71972d62a66e1f967e6b53fb7b2251 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 21:32:34 +0800 Subject: [PATCH 0136/1752] Update edit function --- lib/ibm/domino.rb | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index bddcc700..c8c7247f 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -226,8 +226,9 @@ def delete_booking(room, id) end - def edit_booking(id, starting:, ending:, room:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - starting, ending = convert_to_datetime(starting, ending) + def edit_booking(id, current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + room = Orchestrator::ControlSystem.find(room_id) + starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, :class => :public, @@ -235,41 +236,49 @@ def edit_booking(id, starting:, ending:, room:, summary:, description: nil, orga :end => to_utc_date(ending) } - query = {} + if description.nil? + description = "" + end - if description + if room.support_url + description = description + "\nTo control this meeting room, click here: #{room.support_url}" event[:description] = description - else - query[:literally] = true end - event[:attendees] = Array(attendees).collect do |attendee| - { + out_attendee = { role: "req-participant", status: "needs-action", rsvp: true, - displayName: attendee[:name], email: attendee[:email] } + out_attendee[:displayName] = attendee[:name] if attendee[:name] + out_attendee end + # Organizer will not change event[:organizer] = { - email: organizer[:email], - displayName: organizer[:name] + email: current_user.email } + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "email": current_user.email + }) - # If there are attendees add the service account + # Add the room as an attendee event[:attendees].push({ "role":"chair", "status":"accepted", "rsvp":false, - "displayName":"OTS Test1 Project SG/SG/R&R/PwC", - "email":"ots.test1.project.sg@sg.pwc.com" - }) if attendees + "userType":"room", + "email": room.email + }) + - request = domino_request('put', "/#{room}/api/calendar/events/#{id}", {events: [event]}, query).value - request.status + request = domino_request('put', nil, {events: [event]}, nil, nil, database + "/api/calendar/events/#{id}").value + request end def get_attendees(path) From 49f38f80cba607c17a651c33c777593fdb7c2eb8 Mon Sep 17 00:00:00 2001 From: Creeves Date: Fri, 8 Dec 2017 21:40:11 +0800 Subject: [PATCH 0137/1752] Fix edit function declaration --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index c8c7247f..39c6e6c9 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -226,7 +226,7 @@ def delete_booking(room, id) end - def edit_booking(id, current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + def edit_booking(id:, current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) room = Orchestrator::ControlSystem.find(room_id) starting, ending = convert_to_datetime(starting, ending) event = { From c0d1430c121e87d1e65529a210361673ad02d8c7 Mon Sep 17 00:00:00 2001 From: Creeves Date: Sat, 9 Dec 2017 09:19:23 +0800 Subject: [PATCH 0138/1752] Add href and ID to request --- lib/ibm/domino.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 39c6e6c9..17a71999 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -233,7 +233,9 @@ def edit_booking(id:, current_user:, starting:, ending:, database:, room_id:, su :summary => summary, :class => :public, :start => to_utc_date(starting), - :end => to_utc_date(ending) + :end => to_utc_date(ending), + :href => "/#{database}/api/calendar/events/#{id}", + :id => id } if description.nil? From 7b29040bc16fd56b6f4675eea32b72854aa73cfe Mon Sep 17 00:00:00 2001 From: Creeves Date: Sat, 9 Dec 2017 13:53:53 +0800 Subject: [PATCH 0139/1752] Default to one week if no date passed --- lib/ibm/domino.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 17a71999..1e5b841e 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -64,12 +64,18 @@ def get_free_rooms(starting, ending) domino_emails = JSON.parse(res.body)['rooms'] end - def get_users_bookings(database, date=Time.now.tomorrow.midnight) - # Make date a date object from epoch or parsed text - date = convert_to_simpledate(date) + def get_users_bookings(database, date=nil) + + if !date.nil? + # Make date a date object from epoch or parsed text + date = convert_to_simpledate(date) - starting = to_ibm_date(date) - ending = to_ibm_date(date.tomorrow) + starting = to_ibm_date(date) + ending = to_ibm_date(date.tomorrow) + else + starting = to_ibm_date(Time.now.midnight) + ending = to_ibm_date((Time.now.midnight + 1.week)) + end query = { before: ending, From d5d2e9b991d209266e22198d3d627b12a285f77e Mon Sep 17 00:00:00 2001 From: Creeves Date: Sat, 9 Dec 2017 14:00:25 +0800 Subject: [PATCH 0140/1752] Add debugging --- lib/ibm/domino.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 1e5b841e..acdbf2f7 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -43,6 +43,13 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" end + Rails.logger.info "------------NEW DOMINO REQUEST--------------" + Rails.logger.info domino_path + Rails.logger.info query + Rails.logger.info data + Rails.logger.info @headers + Rails.logger.info "--------------------------------------------" + response = domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) end From edd62121d59b1fe415c6f12e1f189f6b251c4fcd Mon Sep 17 00:00:00 2001 From: Creeves Date: Sat, 9 Dec 2017 15:38:37 +0800 Subject: [PATCH 0141/1752] Add boolean change fields and room email --- lib/ibm/domino.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index acdbf2f7..84484c22 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -239,7 +239,7 @@ def delete_booking(room, id) end - def edit_booking(id:, current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) room = Orchestrator::ControlSystem.find(room_id) starting, ending = convert_to_datetime(starting, ending) event = { @@ -303,6 +303,11 @@ def get_attendees(path) end booking_response = JSON.parse(booking_request.body)['events'][0] if booking_response['attendees'] + booking_response['attendees'].each{|attendee| + if attendee.key?('userType') && attendee['userType'] == 'room' + booking_response['room_email'] = attendee['email'] + end + } attendees = booking_response['attendees'].dup attendees.map!{ |attendee| { From f8fb3f8b3f9f37a5959c1abf8042d37e3ff4bb4b Mon Sep 17 00:00:00 2001 From: Creeves Date: Sat, 9 Dec 2017 16:23:26 +0800 Subject: [PATCH 0142/1752] Use room email not ID --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 84484c22..cc1e6ddd 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -239,8 +239,8 @@ def delete_booking(room, id) end - def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - room = Orchestrator::ControlSystem.find(room_id) + def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, ending:, database:, room_email:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + room = Orchestrator::ControlSystem.find_by_email(room_email) starting, ending = convert_to_datetime(starting, ending) event = { :summary => summary, From ac8432a6d88f9e8f6e3380b4327d7e29056ca18a Mon Sep 17 00:00:00 2001 From: Creeves Date: Sat, 9 Dec 2017 17:53:49 +0800 Subject: [PATCH 0143/1752] Return whole delete method response --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index cc1e6ddd..458b287c 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -235,7 +235,7 @@ def create_booking(current_user:, starting:, ending:, database:, room_id:, summa end def delete_booking(room, id) - request = domino_request('delete', "/#{room}/api/calendar/events/#{id}").value.status + request = domino_request('delete', "/#{room}/api/calendar/events/#{id}").value end From cc3d8808fba434557a74e3643e028778d32058b1 Mon Sep 17 00:00:00 2001 From: Creeves Date: Sat, 9 Dec 2017 23:19:34 +0800 Subject: [PATCH 0144/1752] Use database in DELETE request --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 458b287c..0f04f4ad 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -234,8 +234,8 @@ def create_booking(current_user:, starting:, ending:, database:, room_id:, summa request end - def delete_booking(room, id) - request = domino_request('delete', "/#{room}/api/calendar/events/#{id}").value + def delete_booking(database, id) + request = domino_request('delete', nil, nil, nil, nil, "#{database}/api/calendar/events/#{id}").value end From deabe08849f01de5456c8f575171ade03e1354cd Mon Sep 17 00:00:00 2001 From: Creeves Date: Sun, 10 Dec 2017 11:21:36 +0800 Subject: [PATCH 0145/1752] Add sip driver to modules until Steve advises --- modules/ibm/sip.rb | 245 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 modules/ibm/sip.rb diff --git a/modules/ibm/sip.rb b/modules/ibm/sip.rb new file mode 100644 index 00000000..7be35040 --- /dev/null +++ b/modules/ibm/sip.rb @@ -0,0 +1,245 @@ +# encoding: ASCII-8BIT + + +# For rounding up to the nearest 15min +# See: http://stackoverflow.com/questions/449271/how-to-round-a-time-down-to-the-nearest-15-minutes-in-ruby +class ActiveSupport::TimeWithZone + def ceil(seconds = 60) + return self if seconds.zero? + Time.at(((self - self.utc_offset).to_f / seconds).ceil * seconds).in_time_zone + self.utc_offset + end +end + + +module IBM; end +module IBM::Domino; end + + +class IBM::Domino::Bookings + include ::Orchestrator::Constants + + descriptive_name 'IBM Domino SIPs' + generic_name :Bookings + implements :logic + + # The room we are interested in + default_settings({ + update_every: '2m' + }) + + + def on_load + on_update + end + + def on_update + self[:hide_all] = setting(:hide_all) || false + self[:touch_enabled] = setting(:touch_enabled) || false + self[:name] = self[:room_name] = setting(:room_name) || system.name + + self[:control_url] = setting(:booking_control_url) || system.config.support_url + self[:booking_controls] = setting(:booking_controls) + self[:booking_catering] = setting(:booking_catering) + self[:booking_hide_details] = setting(:booking_hide_details) + self[:booking_hide_availability] = setting(:booking_hide_availability) + self[:booking_hide_user] = setting(:booking_hide_user) + self[:booking_hide_description] = setting(:booking_hide_description) + self[:booking_hide_timeline] = setting(:booking_hide_timeline) + + # Is there catering available for this room? + self[:catering] = setting(:catering_system_id) + if self[:catering] + self[:menu] = setting(:menu) + end + + # Load the last known values (persisted to the DB) + self[:waiter_status] = (setting(:waiter_status) || :idle).to_sym + self[:waiter_call] = self[:waiter_status] != :idle + + self[:catering_status] = setting(:last_catering_status) || {} + self[:order_status] = :idle + + self[:last_meeting_started] = setting(:last_meeting_started) + self[:cancel_meeting_after] = setting(:cancel_meeting_after) + + @domino = ::IBM::Domino.new({ + username: ENV['DOMINO_USERNAME'], + password: ENV['DOMINO_PASSWORD'], + auth_hash: ENV['DOMINO_HASH'], + domain: ENV['DOMINO_DOMAIN'], + timezone: 'Singapore' + }) + + fetch_bookings + schedule.clear + schedule.every(setting(:update_every) || '5m') { fetch_bookings } + end + + + def set_light_status(status) + lightbar = system[:StatusLight] + return if lightbar.nil? + + case status.to_sym + when :unavailable + lightbar.colour(:red) + when :available + lightbar.colour(:green) + when :pending + lightbar.colour(:orange) + else + lightbar.colour(:off) + end + end + + + # ====================================== + # Waiter call information + # ====================================== + def waiter_call(state) + status = is_affirmative?(state) + + self[:waiter_call] = status + + # Used to highlight the service button + if status + self[:waiter_status] = :pending + else + self[:waiter_status] = :idle + end + + define_setting(:waiter_status, self[:waiter_status]) + end + + def call_acknowledged + self[:waiter_status] = :accepted + define_setting(:waiter_status, self[:waiter_status]) + end + + + # ====================================== + # Catering Management + # ====================================== + def catering_status(details) + self[:catering_status] = details + + # We'll turn off the green light on the waiter call button + if self[:waiter_status] != :idle && details[:progress] == 'visited' + self[:waiter_call] = false + self[:waiter_status] = :idle + define_setting(:waiter_status, self[:waiter_status]) + end + + define_setting(:last_catering_status, details) + end + + def commit_order(order_details) + self[:order_status] = :pending + status = self[:catering_status] + + if status && status[:progress] == 'visited' + status = status.dup + status[:progress] = 'cleaned' + self[:catering_status] = status + end + + if self[:catering] + sys = system + @oid ||= 1 + systems(self[:catering])[:Orders].add_order({ + id: "#{sys.id}_#{@oid}", + created_at: Time.now.to_i, + room_id: sys.id, + room_name: sys.name, + order: order_details + }) + end + end + + def order_accepted + self[:order_status] = :accepted + end + + def order_complete + self[:order_status] = :idle + end + + + + # ====================================== + # ROOM BOOKINGS: + # ====================================== + def fetch_bookings + + # :Start => event[:starting].utc.iso8601[0..18], + # :End => event[:ending].utc.iso8601[0..18], + # :Subject => event[:summary], + # :owner => event[:organizer] || '' + # :setup => 0, + # :breakdown => 0 + bookings = @domino.get_bookings(system.id) + bookings.map!{|booking| + { + :Start => Time.at(booking[:start]).utc.iso8601[0..18], + :End => Time.at(booking[:end]).utc.iso8601[0..18], + :Subject => booking[:summary], + :owner => booking[:organizer] + + } + } + end + + + # ====================================== + # Meeting Helper Functions + # ====================================== + + def start_meeting(meeting_ref) + self[:last_meeting_started] = meeting_ref + self[:meeting_pending] = meeting_ref + self[:meeting_ending] = false + self[:meeting_pending_notice] = false + define_setting(:last_meeting_started, meeting_ref) + end + + def cancel_meeting(start_time) + calendar = system[:Calendar] + events = calendar.events.value + events.keep_if do |event| + event[:start].to_i == start_time + end + events.each do |event| + calendar.remove(event) + end + end + + # If last meeting started !== meeting pending then + # we'll show a warning on the in room touch panel + def set_meeting_pending(meeting_ref) + self[:meeting_ending] = false + self[:meeting_pending] = meeting_ref + self[:meeting_pending_notice] = true + end + + # Meeting ending warning indicator + # (When meeting_ending !== last_meeting_started then the warning hasn't been cleared) + # The warning is only displayed when meeting_ending === true + def set_end_meeting_warning(meeting_ref = nil, extendable = false) + if self[:last_meeting_started].nil? || self[:meeting_ending] != (meeting_ref || self[:last_meeting_started]) + self[:meeting_ending] = true + + # Allows meeting ending warnings in all rooms + self[:last_meeting_started] = meeting_ref if meeting_ref + self[:meeting_canbe_extended] = extendable + end + end + + def clear_end_meeting_warning + self[:meeting_ending] = self[:last_meeting_started] + end + # --------- + + def create_meeting(options) + + end +end \ No newline at end of file From 5de1358be058193a2aa26ac0dc413443afa8bf95 Mon Sep 17 00:00:00 2001 From: Creeves Date: Sun, 10 Dec 2017 11:25:30 +0800 Subject: [PATCH 0146/1752] Rename folder to caps --- modules/{ibm => i_b_m}/sip.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/{ibm => i_b_m}/sip.rb (100%) diff --git a/modules/ibm/sip.rb b/modules/i_b_m/sip.rb similarity index 100% rename from modules/ibm/sip.rb rename to modules/i_b_m/sip.rb From 54502d45db3037b3707e69b31d1a7838eb8c9355 Mon Sep 17 00:00:00 2001 From: Creeves Date: Sun, 10 Dec 2017 11:26:50 +0800 Subject: [PATCH 0147/1752] Rename once again --- modules/i_b_m/sip.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/i_b_m/sip.rb b/modules/i_b_m/sip.rb index 7be35040..8ff428f5 100644 --- a/modules/i_b_m/sip.rb +++ b/modules/i_b_m/sip.rb @@ -12,10 +12,8 @@ def ceil(seconds = 60) module IBM; end -module IBM::Domino; end - -class IBM::Domino::Bookings +class IBM::Sip include ::Orchestrator::Constants descriptive_name 'IBM Domino SIPs' From 2214379028b47ea82411b6801ea95cbf3aadb0d3 Mon Sep 17 00:00:00 2001 From: Creeves Date: Sun, 10 Dec 2017 11:39:03 +0800 Subject: [PATCH 0148/1752] Add domino requirement --- lib/ibm/domino.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 0f04f4ad..a73d90ca 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -1,3 +1,4 @@ +require 'ibm/domino' # Reference: https://www.ibm.com/developerworks/lotus/library/ls-Domino_URL_cheat_sheet/ require 'active_support/time' From 7afafae9db4b98e4337627ad9731c65680ae2b3c Mon Sep 17 00:00:00 2001 From: Creeves Date: Sun, 10 Dec 2017 11:45:52 +0800 Subject: [PATCH 0149/1752] Remove domino stuff from aca-d-m --- lib/ibm/domino.rb | 410 ------------------------------------------- modules/i_b_m/sip.rb | 243 ------------------------- 2 files changed, 653 deletions(-) delete mode 100644 lib/ibm/domino.rb delete mode 100644 modules/i_b_m/sip.rb diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb deleted file mode 100644 index a73d90ca..00000000 --- a/lib/ibm/domino.rb +++ /dev/null @@ -1,410 +0,0 @@ -require 'ibm/domino' -# Reference: https://www.ibm.com/developerworks/lotus/library/ls-Domino_URL_cheat_sheet/ - -require 'active_support/time' -module IBM; end - -class IBM::Domino - def initialize( - username:, - password:, - auth_hash:, - domain:, - timezone: - ) - @domain = domain - @timeone = timezone - @headers = { - 'Authorization' => "Basic #{auth_hash}", - 'Content-Type' => 'application/json' - } - @domino_api = UV::HttpEndpoint.new(@domain, {inactivity_timeout: 25000}) - end - - def domino_request(request_method, endpoint, data = nil, query = {}, headers = {}, full_path = nil) - # Convert our request method to a symbol and our data to a JSON string - request_method = request_method.to_sym - data = data.to_json if !data.nil? && data.class != String - - @headers.merge(headers) if headers - - if full_path - if full_path.include?('/api/calendar/events') - uri = URI.parse(full_path) - else - uri = URI.parse(full_path + '/api/calendar/events') - end - domino_api = UV::HttpEndpoint.new("https://#{uri.host}", {inactivity_timeout: 25000}) - domino_path = uri.to_s - elsif request_method == :post - domino_api = UV::HttpEndpoint.new(ENV['DOMINO_CREATE_DOMAIN'], {inactivity_timeout: 25000}) - domino_path = "#{ENV['DOMINO_CREATE_DOMAIN']}#{endpoint}" - else - domino_api = @domino_api - domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" - end - - Rails.logger.info "------------NEW DOMINO REQUEST--------------" - Rails.logger.info domino_path - Rails.logger.info query - Rails.logger.info data - Rails.logger.info @headers - Rails.logger.info "--------------------------------------------" - - response = domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) - end - - def get_free_rooms(starting, ending) - starting, ending = convert_to_datetime(starting, ending) - # starting, ending = get_time_range(starting, ending, @timezone) - - starting = starting.utc - ending = ending.utc - - req_params = { - :site => ENV["DOMINO_SITE"], - :start => to_ibm_date(starting), - :end => to_ibm_date(ending), - :capacity => 1 - } - - res = domino_request('get','/api/freebusy/freerooms', nil, req_params).value - domino_emails = JSON.parse(res.body)['rooms'] - end - - def get_users_bookings(database, date=nil) - - if !date.nil? - # Make date a date object from epoch or parsed text - date = convert_to_simpledate(date) - - starting = to_ibm_date(date) - ending = to_ibm_date(date.tomorrow) - else - starting = to_ibm_date(Time.now.midnight) - ending = to_ibm_date((Time.now.midnight + 1.week)) - end - - query = { - before: ending, - since: starting - } - - request = domino_request('get', nil, nil, query, nil, database).value - if [200,201,204].include?(request.status) - if request.body != '' - events = JSON.parse(request.body)['events'] - else - events = [] - end - else - return nil - end - full_events = [] - events.each{ |event| - db_uri = URI.parse(database) - base_domain = db_uri.scheme + "://" + db_uri.host - Rails.logger.info "Requesting to #{base_domain + event['href']}" - full_event = get_attendees(base_domain + event['href']) - if full_event == false - full_event = event - full_event['organizer'] = {email: 'N/A'} - full_event['description'] = '' - full_event['attendees'] = [] - full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i - full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i - end - full_events.push(full_event) - } - full_events - end - - def get_bookings(room_id, date=Time.now.tomorrow.midnight) - room = Orchestrator::ControlSystem.find(room_id) - room_name = room.settings['name'] - - # The domino API takes a StartKey and UntilKey - # We will only ever need one days worth of bookings - # If startkey = 2017-11-29 and untilkey = 2017-11-30 - # Then all bookings on the 30th (the day of the untilkey) are returned - - # Make date a date object from epoch or parsed text - date = convert_to_simpledate(date) - - starting = date.yesterday.strftime("%Y%m%d") - ending = date.strftime("%Y%m%d") - - # Set count to max - query = { - StartKey: starting, - UntilKey: ending, - KeyType: 'time', - ReadViewEntries: nil, - OutputFormat: 'JSON' - } - - # Get our bookings - request = domino_request('get', "/RRDB.nsf/93FDE1776546DEEB482581E7000B27FF", nil, query) - response = request.value - - # Go through the returned bookings and add to output array - rooms_bookings = [] - bookings = JSON.parse(response.body)['viewentry'] || [] - bookings.each{ |booking| - domino_room_name = booking['entrydata'][2]['text']['0'].split('/')[0] - if room_name == domino_room_name - new_booking = { - start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i, - end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i, - summary: booking['entrydata'][5]['text']['0'], - organizer: booking['entrydata'][3]['text']['0'] - } - rooms_bookings.push(new_booking) - end - } - rooms_bookings - end - - - def create_booking(current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - room = Orchestrator::ControlSystem.find(room_id) - starting, ending = convert_to_datetime(starting, ending) - event = { - :summary => summary, - :class => :public, - :start => to_utc_date(starting), - :end => to_utc_date(ending) - } - - if description.nil? - description = "" - end - - if room.support_url - description = description + "\nTo control this meeting room, click here: #{room.support_url}" - event[:description] = description - end - - event[:attendees] = Array(attendees).collect do |attendee| - out_attendee = { - role: "req-participant", - status: "needs-action", - rsvp: true, - email: attendee[:email] - } - out_attendee[:displayName] = attendee[:name] if attendee[:name] - out_attendee - end - - # Set the current user as orgaqnizer and chair if no organizer passed in - if organizer - event[:organizer] = { - email: organizer[:email] - } - event[:organizer][:displayName] = organizer[:name] if organizer[:name] - - event[:attendees].push({ - "role":"chair", - "status":"accepted", - "rsvp":false, - "email": organizer[:email] - }) - else - event[:organizer] = { - email: current_user.email - } - event[:attendees].push({ - "role":"chair", - "status":"accepted", - "rsvp":false, - "email": current_user.email - }) - end - - # Add the room as an attendee - event[:attendees].push({ - "role":"chair", - "status":"accepted", - "rsvp":false, - "userType":"room", - "email": room.email - }) - - - request = domino_request('post', nil, {events: [event]}, nil, nil, database).value - request - end - - def delete_booking(database, id) - request = domino_request('delete', nil, nil, nil, nil, "#{database}/api/calendar/events/#{id}").value - end - - - def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, ending:, database:, room_email:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) - room = Orchestrator::ControlSystem.find_by_email(room_email) - starting, ending = convert_to_datetime(starting, ending) - event = { - :summary => summary, - :class => :public, - :start => to_utc_date(starting), - :end => to_utc_date(ending), - :href => "/#{database}/api/calendar/events/#{id}", - :id => id - } - - if description.nil? - description = "" - end - - if room.support_url - description = description + "\nTo control this meeting room, click here: #{room.support_url}" - event[:description] = description - end - - event[:attendees] = Array(attendees).collect do |attendee| - out_attendee = { - role: "req-participant", - status: "needs-action", - rsvp: true, - email: attendee[:email] - } - out_attendee[:displayName] = attendee[:name] if attendee[:name] - out_attendee - end - - # Organizer will not change - event[:organizer] = { - email: current_user.email - } - event[:attendees].push({ - "role":"chair", - "status":"accepted", - "rsvp":false, - "email": current_user.email - }) - - # Add the room as an attendee - event[:attendees].push({ - "role":"chair", - "status":"accepted", - "rsvp":false, - "userType":"room", - "email": room.email - }) - - - request = domino_request('put', nil, {events: [event]}, nil, nil, database + "/api/calendar/events/#{id}").value - request - end - - def get_attendees(path) - booking_request = domino_request('get',nil,nil,nil,nil,path).value - if ![200,201,204].include?(booking_request.status) - return false - end - booking_response = JSON.parse(booking_request.body)['events'][0] - if booking_response['attendees'] - booking_response['attendees'].each{|attendee| - if attendee.key?('userType') && attendee['userType'] == 'room' - booking_response['room_email'] = attendee['email'] - end - } - attendees = booking_response['attendees'].dup - attendees.map!{ |attendee| - { - name: attendee['displayName'], - email: attendee['email'] - } - } - booking_response['attendees'] = attendees - end - if booking_response['organizer'] - organizer = booking_response['organizer'].dup - organizer = - { - name: organizer['displayName'], - email: organizer['email'] - } - booking_response['organizer'] = organizer - end - booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0000').utc.to_i.to_s + "000").to_i - booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0000').utc.to_i.to_s + "000").to_i - booking_response - end - - def to_ibm_date(time) - time.strftime("%Y-%m-%dT%H:%M:%SZ") - end - - def convert_to_simpledate(date) - if !(date.class == Time) - if string_is_digits(date) - - # Convert to an integer - date = date.to_i - - # If JavaScript epoch remove milliseconds - if starting.to_s.length == 13 - starting /= 1000 - ending /= 1000 - end - - # Convert to datetimes - date = Time.at(date) - else - date = Time.parse(date) - end - end - return date - end - - def convert_to_datetime(starting, ending) - if !(starting.class == Time) - if string_is_digits(starting) - - # Convert to an integer - starting = starting.to_i - ending = ending.to_i - - # If JavaScript epoch remove milliseconds - if starting.to_s.length == 13 - starting /= 1000 - ending /= 1000 - end - - # Convert to datetimes - starting = Time.at(starting) - ending = Time.at(ending) - else - starting = Time.parse(starting) - ending = Time.parse(ending) - end - end - return starting, ending - end - - - def string_is_digits(string) - string = string.to_s - string.scan(/\D/).empty? - end - - def to_utc_date(time) - utctime = time.getutc - { - date: utctime.strftime("%Y-%m-%d"), - time: utctime.strftime("%H:%M:%S"), - utc: true - } - end - - def get_time_range(starting, ending, timezone) - return [starting, ending] if starting.is_a?(Time) - - Time.zone = timezone - start = starting.nil? ? Time.zone.today.to_time : Time.zone.parse(starting) - fin = ending.nil? ? Time.zone.tomorrow.to_time : Time.zone.parse(ending) - [start, fin] - end - -end diff --git a/modules/i_b_m/sip.rb b/modules/i_b_m/sip.rb deleted file mode 100644 index 8ff428f5..00000000 --- a/modules/i_b_m/sip.rb +++ /dev/null @@ -1,243 +0,0 @@ -# encoding: ASCII-8BIT - - -# For rounding up to the nearest 15min -# See: http://stackoverflow.com/questions/449271/how-to-round-a-time-down-to-the-nearest-15-minutes-in-ruby -class ActiveSupport::TimeWithZone - def ceil(seconds = 60) - return self if seconds.zero? - Time.at(((self - self.utc_offset).to_f / seconds).ceil * seconds).in_time_zone + self.utc_offset - end -end - - -module IBM; end - -class IBM::Sip - include ::Orchestrator::Constants - - descriptive_name 'IBM Domino SIPs' - generic_name :Bookings - implements :logic - - # The room we are interested in - default_settings({ - update_every: '2m' - }) - - - def on_load - on_update - end - - def on_update - self[:hide_all] = setting(:hide_all) || false - self[:touch_enabled] = setting(:touch_enabled) || false - self[:name] = self[:room_name] = setting(:room_name) || system.name - - self[:control_url] = setting(:booking_control_url) || system.config.support_url - self[:booking_controls] = setting(:booking_controls) - self[:booking_catering] = setting(:booking_catering) - self[:booking_hide_details] = setting(:booking_hide_details) - self[:booking_hide_availability] = setting(:booking_hide_availability) - self[:booking_hide_user] = setting(:booking_hide_user) - self[:booking_hide_description] = setting(:booking_hide_description) - self[:booking_hide_timeline] = setting(:booking_hide_timeline) - - # Is there catering available for this room? - self[:catering] = setting(:catering_system_id) - if self[:catering] - self[:menu] = setting(:menu) - end - - # Load the last known values (persisted to the DB) - self[:waiter_status] = (setting(:waiter_status) || :idle).to_sym - self[:waiter_call] = self[:waiter_status] != :idle - - self[:catering_status] = setting(:last_catering_status) || {} - self[:order_status] = :idle - - self[:last_meeting_started] = setting(:last_meeting_started) - self[:cancel_meeting_after] = setting(:cancel_meeting_after) - - @domino = ::IBM::Domino.new({ - username: ENV['DOMINO_USERNAME'], - password: ENV['DOMINO_PASSWORD'], - auth_hash: ENV['DOMINO_HASH'], - domain: ENV['DOMINO_DOMAIN'], - timezone: 'Singapore' - }) - - fetch_bookings - schedule.clear - schedule.every(setting(:update_every) || '5m') { fetch_bookings } - end - - - def set_light_status(status) - lightbar = system[:StatusLight] - return if lightbar.nil? - - case status.to_sym - when :unavailable - lightbar.colour(:red) - when :available - lightbar.colour(:green) - when :pending - lightbar.colour(:orange) - else - lightbar.colour(:off) - end - end - - - # ====================================== - # Waiter call information - # ====================================== - def waiter_call(state) - status = is_affirmative?(state) - - self[:waiter_call] = status - - # Used to highlight the service button - if status - self[:waiter_status] = :pending - else - self[:waiter_status] = :idle - end - - define_setting(:waiter_status, self[:waiter_status]) - end - - def call_acknowledged - self[:waiter_status] = :accepted - define_setting(:waiter_status, self[:waiter_status]) - end - - - # ====================================== - # Catering Management - # ====================================== - def catering_status(details) - self[:catering_status] = details - - # We'll turn off the green light on the waiter call button - if self[:waiter_status] != :idle && details[:progress] == 'visited' - self[:waiter_call] = false - self[:waiter_status] = :idle - define_setting(:waiter_status, self[:waiter_status]) - end - - define_setting(:last_catering_status, details) - end - - def commit_order(order_details) - self[:order_status] = :pending - status = self[:catering_status] - - if status && status[:progress] == 'visited' - status = status.dup - status[:progress] = 'cleaned' - self[:catering_status] = status - end - - if self[:catering] - sys = system - @oid ||= 1 - systems(self[:catering])[:Orders].add_order({ - id: "#{sys.id}_#{@oid}", - created_at: Time.now.to_i, - room_id: sys.id, - room_name: sys.name, - order: order_details - }) - end - end - - def order_accepted - self[:order_status] = :accepted - end - - def order_complete - self[:order_status] = :idle - end - - - - # ====================================== - # ROOM BOOKINGS: - # ====================================== - def fetch_bookings - - # :Start => event[:starting].utc.iso8601[0..18], - # :End => event[:ending].utc.iso8601[0..18], - # :Subject => event[:summary], - # :owner => event[:organizer] || '' - # :setup => 0, - # :breakdown => 0 - bookings = @domino.get_bookings(system.id) - bookings.map!{|booking| - { - :Start => Time.at(booking[:start]).utc.iso8601[0..18], - :End => Time.at(booking[:end]).utc.iso8601[0..18], - :Subject => booking[:summary], - :owner => booking[:organizer] - - } - } - end - - - # ====================================== - # Meeting Helper Functions - # ====================================== - - def start_meeting(meeting_ref) - self[:last_meeting_started] = meeting_ref - self[:meeting_pending] = meeting_ref - self[:meeting_ending] = false - self[:meeting_pending_notice] = false - define_setting(:last_meeting_started, meeting_ref) - end - - def cancel_meeting(start_time) - calendar = system[:Calendar] - events = calendar.events.value - events.keep_if do |event| - event[:start].to_i == start_time - end - events.each do |event| - calendar.remove(event) - end - end - - # If last meeting started !== meeting pending then - # we'll show a warning on the in room touch panel - def set_meeting_pending(meeting_ref) - self[:meeting_ending] = false - self[:meeting_pending] = meeting_ref - self[:meeting_pending_notice] = true - end - - # Meeting ending warning indicator - # (When meeting_ending !== last_meeting_started then the warning hasn't been cleared) - # The warning is only displayed when meeting_ending === true - def set_end_meeting_warning(meeting_ref = nil, extendable = false) - if self[:last_meeting_started].nil? || self[:meeting_ending] != (meeting_ref || self[:last_meeting_started]) - self[:meeting_ending] = true - - # Allows meeting ending warnings in all rooms - self[:last_meeting_started] = meeting_ref if meeting_ref - self[:meeting_canbe_extended] = extendable - end - end - - def clear_end_meeting_warning - self[:meeting_ending] = self[:last_meeting_started] - end - # --------- - - def create_meeting(options) - - end -end \ No newline at end of file From 7b64dafb87d1bfcd57ae6170ac83f8e32ee4c0fd Mon Sep 17 00:00:00 2001 From: Creeves Date: Sun, 10 Dec 2017 12:01:17 +0800 Subject: [PATCH 0150/1752] Add library back --- lib/ibm/domino.rb | 409 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 lib/ibm/domino.rb diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb new file mode 100644 index 00000000..0f04f4ad --- /dev/null +++ b/lib/ibm/domino.rb @@ -0,0 +1,409 @@ +# Reference: https://www.ibm.com/developerworks/lotus/library/ls-Domino_URL_cheat_sheet/ + +require 'active_support/time' +module IBM; end + +class IBM::Domino + def initialize( + username:, + password:, + auth_hash:, + domain:, + timezone: + ) + @domain = domain + @timeone = timezone + @headers = { + 'Authorization' => "Basic #{auth_hash}", + 'Content-Type' => 'application/json' + } + @domino_api = UV::HttpEndpoint.new(@domain, {inactivity_timeout: 25000}) + end + + def domino_request(request_method, endpoint, data = nil, query = {}, headers = {}, full_path = nil) + # Convert our request method to a symbol and our data to a JSON string + request_method = request_method.to_sym + data = data.to_json if !data.nil? && data.class != String + + @headers.merge(headers) if headers + + if full_path + if full_path.include?('/api/calendar/events') + uri = URI.parse(full_path) + else + uri = URI.parse(full_path + '/api/calendar/events') + end + domino_api = UV::HttpEndpoint.new("https://#{uri.host}", {inactivity_timeout: 25000}) + domino_path = uri.to_s + elsif request_method == :post + domino_api = UV::HttpEndpoint.new(ENV['DOMINO_CREATE_DOMAIN'], {inactivity_timeout: 25000}) + domino_path = "#{ENV['DOMINO_CREATE_DOMAIN']}#{endpoint}" + else + domino_api = @domino_api + domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" + end + + Rails.logger.info "------------NEW DOMINO REQUEST--------------" + Rails.logger.info domino_path + Rails.logger.info query + Rails.logger.info data + Rails.logger.info @headers + Rails.logger.info "--------------------------------------------" + + response = domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) + end + + def get_free_rooms(starting, ending) + starting, ending = convert_to_datetime(starting, ending) + # starting, ending = get_time_range(starting, ending, @timezone) + + starting = starting.utc + ending = ending.utc + + req_params = { + :site => ENV["DOMINO_SITE"], + :start => to_ibm_date(starting), + :end => to_ibm_date(ending), + :capacity => 1 + } + + res = domino_request('get','/api/freebusy/freerooms', nil, req_params).value + domino_emails = JSON.parse(res.body)['rooms'] + end + + def get_users_bookings(database, date=nil) + + if !date.nil? + # Make date a date object from epoch or parsed text + date = convert_to_simpledate(date) + + starting = to_ibm_date(date) + ending = to_ibm_date(date.tomorrow) + else + starting = to_ibm_date(Time.now.midnight) + ending = to_ibm_date((Time.now.midnight + 1.week)) + end + + query = { + before: ending, + since: starting + } + + request = domino_request('get', nil, nil, query, nil, database).value + if [200,201,204].include?(request.status) + if request.body != '' + events = JSON.parse(request.body)['events'] + else + events = [] + end + else + return nil + end + full_events = [] + events.each{ |event| + db_uri = URI.parse(database) + base_domain = db_uri.scheme + "://" + db_uri.host + Rails.logger.info "Requesting to #{base_domain + event['href']}" + full_event = get_attendees(base_domain + event['href']) + if full_event == false + full_event = event + full_event['organizer'] = {email: 'N/A'} + full_event['description'] = '' + full_event['attendees'] = [] + full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i + full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i + end + full_events.push(full_event) + } + full_events + end + + def get_bookings(room_id, date=Time.now.tomorrow.midnight) + room = Orchestrator::ControlSystem.find(room_id) + room_name = room.settings['name'] + + # The domino API takes a StartKey and UntilKey + # We will only ever need one days worth of bookings + # If startkey = 2017-11-29 and untilkey = 2017-11-30 + # Then all bookings on the 30th (the day of the untilkey) are returned + + # Make date a date object from epoch or parsed text + date = convert_to_simpledate(date) + + starting = date.yesterday.strftime("%Y%m%d") + ending = date.strftime("%Y%m%d") + + # Set count to max + query = { + StartKey: starting, + UntilKey: ending, + KeyType: 'time', + ReadViewEntries: nil, + OutputFormat: 'JSON' + } + + # Get our bookings + request = domino_request('get', "/RRDB.nsf/93FDE1776546DEEB482581E7000B27FF", nil, query) + response = request.value + + # Go through the returned bookings and add to output array + rooms_bookings = [] + bookings = JSON.parse(response.body)['viewentry'] || [] + bookings.each{ |booking| + domino_room_name = booking['entrydata'][2]['text']['0'].split('/')[0] + if room_name == domino_room_name + new_booking = { + start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i, + end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i, + summary: booking['entrydata'][5]['text']['0'], + organizer: booking['entrydata'][3]['text']['0'] + } + rooms_bookings.push(new_booking) + end + } + rooms_bookings + end + + + def create_booking(current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + room = Orchestrator::ControlSystem.find(room_id) + starting, ending = convert_to_datetime(starting, ending) + event = { + :summary => summary, + :class => :public, + :start => to_utc_date(starting), + :end => to_utc_date(ending) + } + + if description.nil? + description = "" + end + + if room.support_url + description = description + "\nTo control this meeting room, click here: #{room.support_url}" + event[:description] = description + end + + event[:attendees] = Array(attendees).collect do |attendee| + out_attendee = { + role: "req-participant", + status: "needs-action", + rsvp: true, + email: attendee[:email] + } + out_attendee[:displayName] = attendee[:name] if attendee[:name] + out_attendee + end + + # Set the current user as orgaqnizer and chair if no organizer passed in + if organizer + event[:organizer] = { + email: organizer[:email] + } + event[:organizer][:displayName] = organizer[:name] if organizer[:name] + + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "email": organizer[:email] + }) + else + event[:organizer] = { + email: current_user.email + } + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "email": current_user.email + }) + end + + # Add the room as an attendee + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "userType":"room", + "email": room.email + }) + + + request = domino_request('post', nil, {events: [event]}, nil, nil, database).value + request + end + + def delete_booking(database, id) + request = domino_request('delete', nil, nil, nil, nil, "#{database}/api/calendar/events/#{id}").value + end + + + def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, ending:, database:, room_email:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + room = Orchestrator::ControlSystem.find_by_email(room_email) + starting, ending = convert_to_datetime(starting, ending) + event = { + :summary => summary, + :class => :public, + :start => to_utc_date(starting), + :end => to_utc_date(ending), + :href => "/#{database}/api/calendar/events/#{id}", + :id => id + } + + if description.nil? + description = "" + end + + if room.support_url + description = description + "\nTo control this meeting room, click here: #{room.support_url}" + event[:description] = description + end + + event[:attendees] = Array(attendees).collect do |attendee| + out_attendee = { + role: "req-participant", + status: "needs-action", + rsvp: true, + email: attendee[:email] + } + out_attendee[:displayName] = attendee[:name] if attendee[:name] + out_attendee + end + + # Organizer will not change + event[:organizer] = { + email: current_user.email + } + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "email": current_user.email + }) + + # Add the room as an attendee + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "userType":"room", + "email": room.email + }) + + + request = domino_request('put', nil, {events: [event]}, nil, nil, database + "/api/calendar/events/#{id}").value + request + end + + def get_attendees(path) + booking_request = domino_request('get',nil,nil,nil,nil,path).value + if ![200,201,204].include?(booking_request.status) + return false + end + booking_response = JSON.parse(booking_request.body)['events'][0] + if booking_response['attendees'] + booking_response['attendees'].each{|attendee| + if attendee.key?('userType') && attendee['userType'] == 'room' + booking_response['room_email'] = attendee['email'] + end + } + attendees = booking_response['attendees'].dup + attendees.map!{ |attendee| + { + name: attendee['displayName'], + email: attendee['email'] + } + } + booking_response['attendees'] = attendees + end + if booking_response['organizer'] + organizer = booking_response['organizer'].dup + organizer = + { + name: organizer['displayName'], + email: organizer['email'] + } + booking_response['organizer'] = organizer + end + booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0000').utc.to_i.to_s + "000").to_i + booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0000').utc.to_i.to_s + "000").to_i + booking_response + end + + def to_ibm_date(time) + time.strftime("%Y-%m-%dT%H:%M:%SZ") + end + + def convert_to_simpledate(date) + if !(date.class == Time) + if string_is_digits(date) + + # Convert to an integer + date = date.to_i + + # If JavaScript epoch remove milliseconds + if starting.to_s.length == 13 + starting /= 1000 + ending /= 1000 + end + + # Convert to datetimes + date = Time.at(date) + else + date = Time.parse(date) + end + end + return date + end + + def convert_to_datetime(starting, ending) + if !(starting.class == Time) + if string_is_digits(starting) + + # Convert to an integer + starting = starting.to_i + ending = ending.to_i + + # If JavaScript epoch remove milliseconds + if starting.to_s.length == 13 + starting /= 1000 + ending /= 1000 + end + + # Convert to datetimes + starting = Time.at(starting) + ending = Time.at(ending) + else + starting = Time.parse(starting) + ending = Time.parse(ending) + end + end + return starting, ending + end + + + def string_is_digits(string) + string = string.to_s + string.scan(/\D/).empty? + end + + def to_utc_date(time) + utctime = time.getutc + { + date: utctime.strftime("%Y-%m-%d"), + time: utctime.strftime("%H:%M:%S"), + utc: true + } + end + + def get_time_range(starting, ending, timezone) + return [starting, ending] if starting.is_a?(Time) + + Time.zone = timezone + start = starting.nil? ? Time.zone.today.to_time : Time.zone.parse(starting) + fin = ending.nil? ? Time.zone.tomorrow.to_time : Time.zone.parse(ending) + [start, fin] + end + +end From d3b2d00e8d9ac7a52ece28d875d66513765d4081 Mon Sep 17 00:00:00 2001 From: Creeves Date: Sun, 10 Dec 2017 12:18:50 +0800 Subject: [PATCH 0151/1752] Change IBM to Ibm --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 0f04f4ad..7815a9b2 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -1,9 +1,9 @@ # Reference: https://www.ibm.com/developerworks/lotus/library/ls-Domino_URL_cheat_sheet/ require 'active_support/time' -module IBM; end +module Ibm; end -class IBM::Domino +class Ibm::Domino def initialize( username:, password:, From f03353f51adb0655c9a2244c14a3d5bdf851e748 Mon Sep 17 00:00:00 2001 From: Creeves Date: Mon, 11 Dec 2017 09:32:02 +0800 Subject: [PATCH 0152/1752] If singapore time then dont' convert to utc before hand --- lib/ibm/domino.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 7815a9b2..00949e7d 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -326,8 +326,14 @@ def get_attendees(path) } booking_response['organizer'] = organizer end - booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0000').utc.to_i.to_s + "000").to_i - booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0000').utc.to_i.to_s + "000").to_i + if booking_response['start'].key?('tzid') && booking_response['start']['tzid'] == "Singapore Standard Time" + booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0800').utc.to_i.to_s + "000").to_i + booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0800').utc.to_i.to_s + "000").to_i + else + booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0000').utc.to_i.to_s + "000").to_i + booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0000').utc.to_i.to_s + "000").to_i + end + booking_response end From c1d2a617a71e56e5947493cd7bafb5aff037e5db Mon Sep 17 00:00:00 2001 From: Creeves Date: Mon, 11 Dec 2017 09:33:20 +0800 Subject: [PATCH 0153/1752] Fix default date for get_bookings --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 00949e7d..7681c1aa 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -118,7 +118,7 @@ def get_users_bookings(database, date=nil) full_events end - def get_bookings(room_id, date=Time.now.tomorrow.midnight) + def get_bookings(room_id, date=Time.now.midnight) room = Orchestrator::ControlSystem.find(room_id) room_name = room.settings['name'] From 99b93626b58b9d29e7af0e0cae98f734fc9f40b7 Mon Sep 17 00:00:00 2001 From: Creeves Date: Mon, 11 Dec 2017 09:58:10 +0800 Subject: [PATCH 0154/1752] Update attendee logic and fields --- lib/ibm/domino.rb | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 7681c1aa..d468da8a 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -106,6 +106,9 @@ def get_users_bookings(database, date=nil) Rails.logger.info "Requesting to #{base_domain + event['href']}" full_event = get_attendees(base_domain + event['href']) if full_event == false + Rails.logger.info "##############################" + Rails.logger.info "FAILED TO GET DETAILED BOOKING" + Rails.logger.info "##############################" full_event = event full_event['organizer'] = {email: 'N/A'} full_event['description'] = '' @@ -310,9 +313,21 @@ def get_attendees(path) } attendees = booking_response['attendees'].dup attendees.map!{ |attendee| + if attendee['status'] == 'accepted' + accepted = true + else + accepted = false + end + if attendee.key?('displayName') + attendee_name = attendee['displayName'] + else + attendee_name = attendee['email'] + end + { name: attendee['displayName'], - email: attendee['email'] + email: attendee['email'], + state: attendee['status'].gsub(/-/,' ') } } booking_response['attendees'] = attendees @@ -322,7 +337,8 @@ def get_attendees(path) organizer = { name: organizer['displayName'], - email: organizer['email'] + email: organizer['email'], + accepted: true } booking_response['organizer'] = organizer end From 24b2829e11637c32dbe770f438b93622103394e8 Mon Sep 17 00:00:00 2001 From: Creeves Date: Mon, 11 Dec 2017 10:10:30 +0800 Subject: [PATCH 0155/1752] Fix sumdat syntax boi --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d468da8a..28e3eeb1 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -325,7 +325,7 @@ def get_attendees(path) end { - name: attendee['displayName'], + name: attendee_name, email: attendee['email'], state: attendee['status'].gsub(/-/,' ') } From 8762a22144ca6c2e330c57041593c01fc68969a3 Mon Sep 17 00:00:00 2001 From: Creeves Date: Mon, 11 Dec 2017 15:09:31 +0800 Subject: [PATCH 0156/1752] Always put room_email field and set to null --- lib/ibm/domino.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 28e3eeb1..96c9c943 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -309,6 +309,8 @@ def get_attendees(path) booking_response['attendees'].each{|attendee| if attendee.key?('userType') && attendee['userType'] == 'room' booking_response['room_email'] = attendee['email'] + else + booking_response['room_email'] = nil end } attendees = booking_response['attendees'].dup From 07585264b1ef92db5eff0988f09b0700eedcf386 Mon Sep 17 00:00:00 2001 From: Creeves Date: Mon, 11 Dec 2017 17:50:14 +0800 Subject: [PATCH 0157/1752] Add method to get bookings created today --- lib/ibm/domino.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 96c9c943..98b97018 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -71,7 +71,15 @@ def get_free_rooms(starting, ending) domino_emails = JSON.parse(res.body)['rooms'] end - def get_users_bookings(database, date=nil) + def get_users_bookings_created_today(database) + user_bookings = get_users_bookings(database, nil, 2) + user_bookings.select!{ |booking| + Time.now.midnight < booking['last-modified'] && Time.now.tomorrow.midnight > booking['last-modified'] + } + user_bookings + end + + def get_users_bookings(database, date=nil, weeks=1) if !date.nil? # Make date a date object from epoch or parsed text @@ -81,7 +89,7 @@ def get_users_bookings(database, date=nil) ending = to_ibm_date(date.tomorrow) else starting = to_ibm_date(Time.now.midnight) - ending = to_ibm_date((Time.now.midnight + 1.week)) + ending = to_ibm_date((Time.now.midnight + weeks.week)) end query = { From 1ad01ea8adf0c5be6716d1a226c5135ed6bd562a Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 12 Dec 2017 16:03:21 +1100 Subject: [PATCH 0158/1752] created_today: cater for events without last_modified --- lib/ibm/domino.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 98b97018..04d23d0d 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -72,11 +72,15 @@ def get_free_rooms(starting, ending) end def get_users_bookings_created_today(database) - user_bookings = get_users_bookings(database, nil, 2) + user_bookings = get_users_bookings(database, nil, 1) user_bookings.select!{ |booking| - Time.now.midnight < booking['last-modified'] && Time.now.tomorrow.midnight > booking['last-modified'] + booking['last-modified'] && Time.now.midnight < booking['last-modified'] && Time.now.tomorrow.midnight > booking['last-modified'] } user_bookings + + rescue => e + STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" + raise e end def get_users_bookings(database, date=nil, weeks=1) From 07b265c9d6d29121bedbc09edbf03216a01d93b7 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 12 Dec 2017 19:31:16 +1000 Subject: [PATCH 0159/1752] (cisco:spark) add device implementation for SX80 --- modules/cisco/spark/sx80.rb | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 modules/cisco/spark/sx80.rb diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb new file mode 100644 index 00000000..75387c3e --- /dev/null +++ b/modules/cisco/spark/sx80.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +load File.join(__dir__, 'room_os.rb') + +class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs + include ::Orchestrator::Security + include ::Cisco::Spark::Xapi::Mapper + + descriptive_name 'Cisco Spark SX80' + description <<~DESC + Device access requires an API user to be created on the endpoint. + DESC + + tokenize delimiter: Tokens::COMMAND_RESPONSE, + wait_ready: Tokens::LOGIN_COMPLETE + clear_queue_on_disconnect! + + protect_method :xcommand, :xconfigruation, :xstatus + + command 'Audio Microphones Mute' => :mic_mute_on + command 'Audio Microphones Unmute' => :mic_mute_off + command 'Audio Microphones ToggleMute' => :mic_mute_toggle + state '/Status/Audio/Microphones/Mute' => :mic_mute + + command 'Audio Sound Play' => :play_sound, + Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, + :CallWaiting, :Dial, :KeyInput, :KeyInputDelete, :KeyTone, + :Nav, :NavBack, :Notification, :OK, :PresentationConnect, + :Ringing, :SignIn, :SpecialInfo, :TelephoneCall, + :VideoCall, :VolumeAdjust, :WakeUp], + Loop_: [:Off, :On] + command 'Audio Sound Stop' => :stop_sound + + command 'Call Disconnect' => :disconnect, CallId_: Integer + command 'Dial' => :dial, + Number: String, + Protocol_: [:H320, :H323, :Sip, :Spark], + CallRate_: (64..6000), + CallType_: [:Audio, :Video] + + command 'Camera PositionReset' => :camera_position_reset, + CameraId: (1..7), + Axis_: [:All, :Focus, :PanTilt, :Zoom] + command 'Camera Ramp' => :camera_move, + CameraId: (1..7), + Pan_: [:Left, :Right, :Stop], + PanSpeed_: (1..15), + Tilt_: [:Down, :Up, :Stop], + TiltSpeed_: (1..15), + Zoom_: [:In, :Out, :Stop], + ZoomSpeed_: (1..15), + Focus_: [:Far, :Near, :Stop] + + command! 'Cameras AutoFocus Diagnostics Start' => \ + :autofocus_diagnostics_start, + CameraId_: (1..7) + command! 'Cameras AutoFocus Diagnostics Stop' => \ + :autofocus_diagnostics_stop, + CameraId_: (1..7) + state '/Status/Cameras/Camera' => :camera + + command! 'Cameras PresenterTrack ClearPosition' => :presenter_track_clear + command! 'Cameras PresenterTrack StorePosition' => :presenter_track_store + command! 'Cameras PresenterTrack Set' => :presenter_track, + Mode: [:Off, :Follow, :Diagnostic, :Background, :Setup, + :Persistant] + state '/Status/Cameras/PresenterTrack' => :presenter_track + + command! 'Cameras SpeakerTrack Diagnostics Start' => \ + :speaker_track_diagnostics_start + command! 'Cameras SpeakerTrack Diagnostics Stop' => \ + :speaker_track_diagnostics_stop + state '/Status/Cameras/SpeakerTrack' => :speaker_track + + state '/Status/Conference/DoNotDisturb' => :do_not_disturb + + state '/Status/Conference/Presentation/Mode' => :presentation + + state '/Status/Peripherals/ConnectedDevice' => :peripherals + + state '/Status/Video/Input' => :video_input + + state '/Status/Video/Output' => :video_output + + command 'Standby Deactivate' => :wake_up + command 'Standby Activate' => :standby + command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) + state '/Status/Standby/State' => :standby + + command! 'SystemUnit Boot' => :reboot, Action_: [:Restart, :Shutdown] +end From d07a28f75dc984fa62e23cebf7083fc9c6e4da39 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 12 Dec 2017 22:27:28 +1000 Subject: [PATCH 0160/1752] (cisco:spark) change optional parameters to kwargs --- modules/cisco/spark/xapi/mapper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 5c77250c..3141b671 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -38,7 +38,7 @@ def command(mapping) opt_, req = params.partition { |name| name.ends_with? '_' } opt = opt_.map { |name| name.chomp '_' } - param_str = (req + opt.map { |name| "#{name} = nil" }).join ', ' + param_str = (req + opt.map { |name| "#{name}: nil" }).join ', ' # TODO: add support for index commands command_str = command_path.split(' ').join(' ') From abcfbf8062080cdcc7e516a4e6a359be4d14db3c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 12:18:05 +1000 Subject: [PATCH 0161/1752] (cisco:spark) add volume status to SX80 --- modules/cisco/spark/sx80.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 75387c3e..93e72a6d 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -31,6 +31,8 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound + state '/Status/Audio/Volume' => :volume + command 'Call Disconnect' => :disconnect, CallId_: Integer command 'Dial' => :dial, Number: String, From 1ad4c803d2e5daa8f4a4b29ab6ceaa195e708b65 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 12:23:34 +1000 Subject: [PATCH 0162/1752] (cisco:spark) wrap long lines --- modules/cisco/spark/sx20.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 4a228fb5..73662ea7 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -23,10 +23,11 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs state '/Status/Audio/Microphones/Mute' => :mic_mute command 'Audio Sound Play' => :play_sound, - Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, :CallWaiting, - :Dial, :KeyInput, :KeyInputDelete, :KeyTone, :Nav, :NavBack, - :Notification, :OK, :PresentationConnect, :Ringing, :SignIn, - :SpecialInfo, :TelephoneCall, :VideoCall, :VolumeAdjust, :WakeUp], + Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, + :CallWaiting, :Dial, :KeyInput, :KeyInputDelete, :KeyTone, + :Nav, :NavBack, :Notification, :OK, :PresentationConnect, + :Ringing, :SignIn, :SpecialInfo, :TelephoneCall, + :VideoCall, :VolumeAdjust, :WakeUp], Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound From 16ab646cd8ad9fe09853326e27674ad2f5fb211a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 12:23:54 +1000 Subject: [PATCH 0163/1752] (cisco:spark) group all status subscriptions together --- modules/cisco/spark/sx80.rb | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 93e72a6d..f5fdc1f0 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -17,10 +17,21 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs protect_method :xcommand, :xconfigruation, :xstatus + state '/Status/Audio/Microphones/Mute' => :mic_mute + state '/Status/Audio/Volume' => :volume + state '/Status/Cameras/Camera' => :camera + state '/Status/Cameras/PresenterTrack' => :presenter_track + state '/Status/Cameras/SpeakerTrack' => :speaker_track + state '/Status/Conference/DoNotDisturb' => :do_not_disturb + state '/Status/Conference/Presentation/Mode' => :presentation + state '/Status/Peripherals/ConnectedDevice' => :peripherals + state '/Status/Video/Input' => :video_input + state '/Status/Video/Output' => :video_output + state '/Status/Standby/State' => :standby + command 'Audio Microphones Mute' => :mic_mute_on command 'Audio Microphones Unmute' => :mic_mute_off command 'Audio Microphones ToggleMute' => :mic_mute_toggle - state '/Status/Audio/Microphones/Mute' => :mic_mute command 'Audio Sound Play' => :play_sound, Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, @@ -31,8 +42,6 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound - state '/Status/Audio/Volume' => :volume - command 'Call Disconnect' => :disconnect, CallId_: Integer command 'Dial' => :dial, Number: String, @@ -59,35 +68,21 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs command! 'Cameras AutoFocus Diagnostics Stop' => \ :autofocus_diagnostics_stop, CameraId_: (1..7) - state '/Status/Cameras/Camera' => :camera command! 'Cameras PresenterTrack ClearPosition' => :presenter_track_clear command! 'Cameras PresenterTrack StorePosition' => :presenter_track_store command! 'Cameras PresenterTrack Set' => :presenter_track, Mode: [:Off, :Follow, :Diagnostic, :Background, :Setup, :Persistant] - state '/Status/Cameras/PresenterTrack' => :presenter_track command! 'Cameras SpeakerTrack Diagnostics Start' => \ :speaker_track_diagnostics_start command! 'Cameras SpeakerTrack Diagnostics Stop' => \ :speaker_track_diagnostics_stop - state '/Status/Cameras/SpeakerTrack' => :speaker_track - - state '/Status/Conference/DoNotDisturb' => :do_not_disturb - - state '/Status/Conference/Presentation/Mode' => :presentation - - state '/Status/Peripherals/ConnectedDevice' => :peripherals - - state '/Status/Video/Input' => :video_input - - state '/Status/Video/Output' => :video_output command 'Standby Deactivate' => :wake_up command 'Standby Activate' => :standby command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) - state '/Status/Standby/State' => :standby command! 'SystemUnit Boot' => :reboot, Action_: [:Restart, :Shutdown] end From 555d10a4bcf3ba6cf42f7cfa98e6fb2e740bdf7a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 17:42:25 +1000 Subject: [PATCH 0164/1752] (cisco:spark) simplify binding to device status --- modules/cisco/spark/room_os.rb | 12 +++++++++--- modules/cisco/spark/sx80.rb | 22 +++++++++++----------- modules/cisco/spark/xapi/mapper.rb | 12 ++++++------ 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 9ea89175..5c48747c 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -337,15 +337,21 @@ def load_settings load_setting :version, default: Meta.version(self) end - # Bind arbitary device state to a status variable. - def bind_state(path, status_key) + # Bind arbitary device feedback to a status variable. + def bind_feedback(path, status_key) register_feedback path do |value| self[status_key] = value end end + # Bind device status to a module status variable. + def bind_status(path, status_key) + bind_feedback "/Status/#{Action.tokenize(path).join('/')}", status_key + send_xstatus path + end + def sync_config - bind_state '/Configuration', :configuration + bind_feedback '/Configuration', :configuration send "xConfiguration *\n", wait: false end diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index f5fdc1f0..31dbd2d2 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -17,17 +17,17 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs protect_method :xcommand, :xconfigruation, :xstatus - state '/Status/Audio/Microphones/Mute' => :mic_mute - state '/Status/Audio/Volume' => :volume - state '/Status/Cameras/Camera' => :camera - state '/Status/Cameras/PresenterTrack' => :presenter_track - state '/Status/Cameras/SpeakerTrack' => :speaker_track - state '/Status/Conference/DoNotDisturb' => :do_not_disturb - state '/Status/Conference/Presentation/Mode' => :presentation - state '/Status/Peripherals/ConnectedDevice' => :peripherals - state '/Status/Video/Input' => :video_input - state '/Status/Video/Output' => :video_output - state '/Status/Standby/State' => :standby + status 'Audio Microphones Mute' => :mic_mute + status 'Audio Volume' => :volume + status 'Cameras Camera' => :camera + status 'Cameras PresenterTrack' => :presenter_track + status 'Cameras SpeakerTrack' => :speaker_track + status 'Conference DoNotDisturb' => :do_not_disturb + status 'Conference Presentation Mode' => :presentation + status 'Peripherals ConnectedDevice' => :peripherals + status 'Video Input' => :video_input + status 'Video Output' => :video_output + status 'Standby State' => :standby command 'Audio Microphones Mute' => :mic_mute_on command 'Audio Microphones Unmute' => :mic_mute_off diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 3141b671..9212d03e 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -85,20 +85,20 @@ def command!(mapping) # Define a binding between device state and module status variables. # # Similar to command bindings, this provides a declarative mapping - # from a device xpath to an exposed state variable. Subscriptions will + # from a device xpath to an exposed status variable. Subscriptions will # be automatically setup as part of connection initialisation. # # Example: - # state '/Status/Standby/State' => :standby + # status 'Standby State' => :standby # # Will track the device standby state and push it to self[:standby] # # @param mapping [Hash] a set of xpath => status variable bindings - def state(mapping) - state_mappings.merge! mapping + def status(mapping) + status_mappings.merge! mapping end - def state_mappings + def status_mappings @mappings ||= {} end end @@ -106,7 +106,7 @@ def state_mappings module ApiMapperHooks def connected super - self.class.state_mappings.each(&method(:bind_state)) + self.class.status_mappings.each(&method(:bind_status)) end end From 1ffa57ed3a83ce7f1f9641fd8230dbf46063e7a9 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 17:57:50 +1000 Subject: [PATCH 0165/1752] (cisco:spark) fix method naming conflict --- modules/cisco/spark/sx80.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 31dbd2d2..48d4556d 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -42,7 +42,7 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound - command 'Call Disconnect' => :disconnect, CallId_: Integer + command 'Call Disconnect' => :hangup, CallId_: Integer command 'Dial' => :dial, Number: String, Protocol_: [:H320, :H323, :Sip, :Spark], From b4630e43a939d5c1391fbfa475f6d92b5d0399e6 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 18:29:25 +1000 Subject: [PATCH 0166/1752] (cisco:spark) fixup feedback subscription for status bindings --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 5c48747c..acd34c79 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -346,7 +346,7 @@ def bind_feedback(path, status_key) # Bind device status to a module status variable. def bind_status(path, status_key) - bind_feedback "/Status/#{Action.tokenize(path).join('/')}", status_key + bind_feedback "/Status/#{path.tr ' ', '/'}", status_key send_xstatus path end From 83c74b75974cd29ff6c640e157868fc788903cfd Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 18:34:23 +1000 Subject: [PATCH 0167/1752] (cisco:spark) don't wait for echo off response --- modules/cisco/spark/room_os.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index acd34c79..46cf98b5 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -278,10 +278,7 @@ def device_subscriptions # Base comms def init_connection - send "Echo off\n", priority: 96 do |response| - :success if response.starts_with? "\e[?1034h" - end - + send "Echo off\n", priority: 96, wait: false send "xPreferences OutputMode JSON\n", wait: false end From 84eff2bcb17130e933c3971a8bc93f019faf43d1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 19:53:17 +1000 Subject: [PATCH 0168/1752] (cisco:spark) convert all device feedback to appropriate data types --- modules/cisco/spark/xapi/response.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/cisco/spark/xapi/response.rb b/modules/cisco/spark/xapi/response.rb index 0b30db1e..53ac281b 100644 --- a/modules/cisco/spark/xapi/response.rb +++ b/modules/cisco/spark/xapi/response.rb @@ -62,19 +62,19 @@ def compress(fragment) # @param valuespace [Symbol] the Cisco value space reference # @return the value as an appropriate core datatype def convert(value, valuespace) - if valuespace - parser = PARSERS[valuespace] - if parser - parser.call(value) - else - begin - Integer(value) - rescue ArgumentError + parser = PARSERS[valuespace] + if parser + parser.call(value) + else + begin + Integer(value) + rescue ArgumentError + if value =~ /\A[[:alpha:]]+\z/ value.to_sym + else + value end end - else - value end end end From 7026f2044e24d17a8bd8bd341e74ba513fb966f2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 20:03:57 +1000 Subject: [PATCH 0169/1752] (cisco:spark) add tests for feedback and status subscription --- modules/cisco/spark/room_os_spec.rb | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/spark/room_os_spec.rb index 20f0dd52..4a0fa70c 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/spark/room_os_spec.rb @@ -243,6 +243,71 @@ def section(message) ) expect(result).to be :success + # Bind module status to device state + exec(:bind_feedback, '/Status/Audio/Volume', :volume) + .should_send("xFeedback register /Status/Audio/Volume | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + .responds( + <<~JSON + { + "Status":{ + "Audio":{ + "Volume":{ + "Value":"50" + } + } + } + } + JSON + ) + expect(status[:volume]).to be 50 + + # Bind xStatus to module state + exec(:bind_status, 'Standby State', :standby_state) + .should_send("xFeedback register /Status/Standby/State | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + .should_send("xStatus Standby State | resultId=\"#{id_peek}\"\n") + .responds( + <<~JSON + { + "Status":{ + "Standby":{ + "State":{ + "Value":"HalfWake" + } + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + .responds( + <<~JSON + { + "Status":{ + "Standby":{ + "State":{ + "Value":"Standby" + } + } + } + } + JSON + ) + expect(status[:standby_state]).to be :Standby + # ------------------------------------------------------------------------- section 'Commands' From 4a8be849ffcc16dd37dba7a8f3082d61a01adeec Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 20:34:39 +1000 Subject: [PATCH 0170/1752] (cisco:spark) fix issue where subscribed status would not sync on start --- modules/cisco/spark/room_os.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 46cf98b5..a6d91047 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -207,15 +207,13 @@ def send_xconfigurations(path, settings) # Query the device's current status. # # @param path [String] - # @yield [response] - # a pre-parsed response object for the command, if used this block - # should return the response result + # @yield [response] a pre-parsed response object for the status query def send_xstatus(path) request = Action.xstatus path defer = thread.defer - do_send request, name: "? #{path}" do |response| + do_send request do |response| path_components = Action.tokenize path status_response = response.dig 'Status', *path_components @@ -344,7 +342,9 @@ def bind_feedback(path, status_key) # Bind device status to a module status variable. def bind_status(path, status_key) bind_feedback "/Status/#{path.tr ' ', '/'}", status_key - send_xstatus path + send_xstatus path do |value| + self[status_key] = value + end end def sync_config From 698e00f4d5dd875b1769276f211554948589c791 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 21:06:05 +1000 Subject: [PATCH 0171/1752] (cisco:spark) remove subscription to cameras to reduce status traffic --- modules/cisco/spark/sx80.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 48d4556d..9b73ab0f 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -19,7 +19,6 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume - status 'Cameras Camera' => :camera status 'Cameras PresenterTrack' => :presenter_track status 'Cameras SpeakerTrack' => :speaker_track status 'Conference DoNotDisturb' => :do_not_disturb From 0c8ac2970a356c2194acc36338944ddc9ea3224a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 21:09:52 +1000 Subject: [PATCH 0172/1752] (cisco:spark) subscribe to other pieces of useful device state --- modules/cisco/spark/sx80.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 9b73ab0f..bcebd8d7 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -21,9 +21,12 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs status 'Audio Volume' => :volume status 'Cameras PresenterTrack' => :presenter_track status 'Cameras SpeakerTrack' => :speaker_track + status 'RoomAnalytics PeoplePresence' => :presence_detected status 'Conference DoNotDisturb' => :do_not_disturb status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals + status 'SystemUnit State NumberOfActiveCalls' => :active_calls + status 'Video SelfView Mode' => :selfview status 'Video Input' => :video_input status 'Video Output' => :video_output status 'Standby State' => :standby From ec8caedc7f888a322408f07f8390ce8fb5b0d224 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 21:44:30 +1000 Subject: [PATCH 0173/1752] (cisco:spark) convert all boolean-ish values to bool --- modules/cisco/spark/xapi/response.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/xapi/response.rb b/modules/cisco/spark/xapi/response.rb index 53ac281b..b925eb3e 100644 --- a/modules/cisco/spark/xapi/response.rb +++ b/modules/cisco/spark/xapi/response.rb @@ -43,7 +43,7 @@ def compress(fragment) end end - BOOLEAN ||= ->(val) { ['On', 'True'].include? val } + BOOLEAN ||= ->(val) { is_affirmative? val } BOOL_OR ||= lambda do |term| sym = term.to_sym ->(val) { val == term ? sym : BOOLEAN[val] } @@ -69,7 +69,11 @@ def convert(value, valuespace) begin Integer(value) rescue ArgumentError - if value =~ /\A[[:alpha:]]+\z/ + if is_affirmative? value + true + elsif is_negatory? value + false + elsif value =~ /\A[[:alpha:]]+\z/ value.to_sym else value @@ -77,4 +81,12 @@ def convert(value, valuespace) end end end + + def is_affirmative?(value) + ::Orchestrator::Constants::On_vars.include? value + end + + def is_negatory?(value) + ::Orchestrator::Constants::Off_vars.include? value + end end From 752d2a7f4bf6f758221a70691db79de39c52e01e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 21:53:33 +1000 Subject: [PATCH 0174/1752] (cisco:spark) fix issue where 'false' status values would not parse correctly --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index a6d91047..1ba6d9b2 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -217,7 +217,7 @@ def send_xstatus(path) path_components = Action.tokenize path status_response = response.dig 'Status', *path_components - if status_response + if !status_response.nil? yield status_response if block_given? defer.resolve status_response :success From b4221d0a2108e55195a9a8803f6d10d5f921a78b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 22:01:48 +1000 Subject: [PATCH 0175/1752] (cisco:spark) update SX20 instance to use new status bindings --- modules/cisco/spark/sx20.rb | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 73662ea7..2dda7e62 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -17,10 +17,21 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs protect_method :xcommand, :xconfigruation, :xstatus + status 'Audio Microphones Mute' => :mic_mute + status 'Audio Volume' => :volume + status 'RoomAnalytics PeoplePresence' => :presence_detected + status 'Conference DoNotDisturb' => :do_not_disturb + status 'Conference Presentation Mode' => :presentation + status 'Peripherals ConnectedDevice' => :peripherals + status 'SystemUnit State NumberOfActiveCalls' => :active_calls + status 'Video SelfView Mode' => :selfview + status 'Video Input' => :video_input + status 'Video Output' => :video_output + status 'Standby State' => :standby + command 'Audio Microphones Mute' => :mic_mute_on command 'Audio Microphones Unmute' => :mic_mute_off command 'Audio Microphones ToggleMute' => :mic_mute_toggle - state '/Status/Audio/Microphones/Mute' => :mic_mute command 'Audio Sound Play' => :play_sound, Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, @@ -31,10 +42,29 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound + command 'Call Disconnect' => :hangup, CallId_: Integer + command 'Dial' => :dial, + Number: String, + Protocol_: [:H320, :H323, :Sip, :Spark], + CallRate_: (64..6000), + CallType_: [:Audio, :Video] + + command 'Camera PositionReset' => :camera_position_reset, + CameraId: (1..2), + Axis_: [:All, :Focus, :PanTilt, :Zoom] + command 'Camera Ramp' => :camera_move, + CameraId: (1..2), + Pan_: [:Left, :Right, :Stop], + PanSpeed_: (1..15), + Tilt_: [:Down, :Up, :Stop], + TiltSpeed_: (1..15), + Zoom_: [:In, :Out, :Stop], + ZoomSpeed_: (1..15), + Focus_: [:Far, :Near, :Stop] + command 'Standby Deactivate' => :wake_up command 'Standby Activate' => :standby command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) - state '/Status/Standby/State' => :standby command! 'SystemUnit Boot' => :reboot, Action_: [:Restart, :Shutdown] end From 9a11f0858ff9cb0682f0049e800361b95b4674ad Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 22:38:30 +1000 Subject: [PATCH 0176/1752] (cisco:spark) auto-drop empty args --- modules/cisco/spark/xapi/action.rb | 2 +- modules/cisco/spark/xapi/mapper.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/spark/xapi/action.rb index 1784e240..54ed8c65 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/spark/xapi/action.rb @@ -35,7 +35,7 @@ def create_action(type, *args, **kwargs) "Invalid action type. Must be one of #{ACTION_TYPE}." end - kwargs = kwargs.map do |name, value| + kwargs = kwargs.compact.map do |name, value| value = "\"#{value}\"" if value.is_a? String "#{name}: #{value}" end diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 9212d03e..75f765ac 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -66,7 +66,6 @@ def #{method_name}(#{param_str}) args = binding.local_variables .map { |p| [p.to_s.camelize.to_sym, binding.local_variable_get(p)] } .to_h - .compact send_xcommand '#{command_str}', args end METHOD From 93969762eb521b7ab5e7417be55fbdf9e395e361 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Dec 2017 23:57:46 +1000 Subject: [PATCH 0177/1752] (cisco:spark) move tokenizer config and method protection into parent module --- modules/cisco/spark/room_os.rb | 12 ++++++++++++ modules/cisco/spark/sx20.rb | 7 ------- modules/cisco/spark/sx80.rb | 7 ------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 1ba6d9b2..497c3701 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -10,6 +10,7 @@ module Cisco::Spark; end class Cisco::Spark::RoomOs include ::Orchestrator::Constants + include ::Orchestrator::Security include ::Cisco::Spark::Xapi include ::Cisco::Spark::Util @@ -126,6 +127,17 @@ def xstatus(path) send_xstatus path end + def self.extended(child) + child.class_eval do + protect_method :xcommand, :xconfigruation, :xstatus + + # Workaround for issues in Orchestrator where these are not applied + tokenize delimiter: Tokens::COMMAND_RESPONSE, + wait_ready: Tokens::LOGIN_COMPLETE + clear_queue_on_disconnect! + end + end + protected diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 2dda7e62..87eaff52 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -3,7 +3,6 @@ load File.join(__dir__, 'room_os.rb') class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs - include ::Orchestrator::Security include ::Cisco::Spark::Xapi::Mapper descriptive_name 'Cisco Spark SX20' @@ -11,12 +10,6 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs Device access requires an API user to be created on the endpoint. DESC - tokenize delimiter: Tokens::COMMAND_RESPONSE, - wait_ready: Tokens::LOGIN_COMPLETE - clear_queue_on_disconnect! - - protect_method :xcommand, :xconfigruation, :xstatus - status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume status 'RoomAnalytics PeoplePresence' => :presence_detected diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index bcebd8d7..dd4632e8 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -3,7 +3,6 @@ load File.join(__dir__, 'room_os.rb') class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs - include ::Orchestrator::Security include ::Cisco::Spark::Xapi::Mapper descriptive_name 'Cisco Spark SX80' @@ -11,12 +10,6 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs Device access requires an API user to be created on the endpoint. DESC - tokenize delimiter: Tokens::COMMAND_RESPONSE, - wait_ready: Tokens::LOGIN_COMPLETE - clear_queue_on_disconnect! - - protect_method :xcommand, :xconfigruation, :xstatus - status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume status 'Cameras PresenterTrack' => :presenter_track From 8deaa8716af631a04766066a26f183a8aba99dd3 Mon Sep 17 00:00:00 2001 From: Creeves Date: Thu, 14 Dec 2017 09:04:13 +0800 Subject: [PATCH 0178/1752] Add simple param to only pass start and end --- lib/ibm/domino.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 04d23d0d..4f398c11 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -83,7 +83,7 @@ def get_users_bookings_created_today(database) raise e end - def get_users_bookings(database, date=nil, weeks=1) + def get_users_bookings(database, date=nil, weeks=1, simple=nil) if !date.nil? # Make date a date object from epoch or parsed text @@ -113,6 +113,13 @@ def get_users_bookings(database, date=nil, weeks=1) end full_events = [] events.each{ |event| + if simple + full_events.push({ + start: (Time.parse(event['start']['date']+'T'+event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i + end: (Time.parse(event['end']['date']+'T'+event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i + }) + next + end db_uri = URI.parse(database) base_domain = db_uri.scheme + "://" + db_uri.host Rails.logger.info "Requesting to #{base_domain + event['href']}" From 891e21b8dc93c74771a6d544f58aa1127ab39aba Mon Sep 17 00:00:00 2001 From: Creeves Date: Thu, 14 Dec 2017 09:08:15 +0800 Subject: [PATCH 0179/1752] Hurrdurr i code gud --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 4f398c11..f794cb49 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -115,7 +115,7 @@ def get_users_bookings(database, date=nil, weeks=1, simple=nil) events.each{ |event| if simple full_events.push({ - start: (Time.parse(event['start']['date']+'T'+event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i + start: (Time.parse(event['start']['date']+'T'+event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i, end: (Time.parse(event['end']['date']+'T'+event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i }) next From e99df814f29e9159fd9145635df7ce5ff9ebb8fb Mon Sep 17 00:00:00 2001 From: Creeves Date: Thu, 14 Dec 2017 09:13:57 +0800 Subject: [PATCH 0180/1752] Reverse arg order so default works --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index f794cb49..836459f2 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -72,7 +72,7 @@ def get_free_rooms(starting, ending) end def get_users_bookings_created_today(database) - user_bookings = get_users_bookings(database, nil, 1) + user_bookings = get_users_bookings(database, nil, nil, 1) user_bookings.select!{ |booking| booking['last-modified'] && Time.now.midnight < booking['last-modified'] && Time.now.tomorrow.midnight > booking['last-modified'] } @@ -83,7 +83,7 @@ def get_users_bookings_created_today(database) raise e end - def get_users_bookings(database, date=nil, weeks=1, simple=nil) + def get_users_bookings(database, date=nil, simple=nil, weeks=1) if !date.nil? # Make date a date object from epoch or parsed text From c733d746c7fe3652c57db45f47d4eae3276db497 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 11:58:35 +1000 Subject: [PATCH 0181/1752] (cisco:spark) rename boolean parsers --- modules/cisco/spark/xapi/response.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/cisco/spark/xapi/response.rb b/modules/cisco/spark/xapi/response.rb index b925eb3e..f448d132 100644 --- a/modules/cisco/spark/xapi/response.rb +++ b/modules/cisco/spark/xapi/response.rb @@ -43,7 +43,7 @@ def compress(fragment) end end - BOOLEAN ||= ->(val) { is_affirmative? val } + BOOLEAN ||= ->(val) { truthy? val } BOOL_OR ||= lambda do |term| sym = term.to_sym ->(val) { val == term ? sym : BOOLEAN[val] } @@ -69,9 +69,9 @@ def convert(value, valuespace) begin Integer(value) rescue ArgumentError - if is_affirmative? value + if truthy? value true - elsif is_negatory? value + elsif falsey? value false elsif value =~ /\A[[:alpha:]]+\z/ value.to_sym @@ -82,11 +82,11 @@ def convert(value, valuespace) end end - def is_affirmative?(value) + def truthy?(value) ::Orchestrator::Constants::On_vars.include? value end - def is_negatory?(value) + def falsey?(value) ::Orchestrator::Constants::Off_vars.include? value end end From 9de509ff5e1b05305f378856172a36bbbb3ae162 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 12:27:45 +1000 Subject: [PATCH 0182/1752] (cisco:spark) add base UI extensions --- modules/cisco/spark/sx20.rb | 2 + modules/cisco/spark/sx80.rb | 2 + modules/cisco/spark/ui_extensions.rb | 70 ++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 modules/cisco/spark/ui_extensions.rb diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 87eaff52..378a0921 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true load File.join(__dir__, 'room_os.rb') +load File.join(__dir__, 'ui_extensions.rb') class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs include ::Cisco::Spark::Xapi::Mapper + include ::Cisco::Spark::UiExtensions descriptive_name 'Cisco Spark SX20' description <<~DESC diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index dd4632e8..5152867a 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true load File.join(__dir__, 'room_os.rb') +load File.join(__dir__, 'ui_extensions.rb') class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs include ::Cisco::Spark::Xapi::Mapper + include ::Cisco::Spark::UiExtensions descriptive_name 'Cisco Spark SX80' description <<~DESC diff --git a/modules/cisco/spark/ui_extensions.rb b/modules/cisco/spark/ui_extensions.rb new file mode 100644 index 00000000..54925bb3 --- /dev/null +++ b/modules/cisco/spark/ui_extensions.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Cisco; end +module Cisco::Spark; end + +module Cisco::Spark::UiExtensions + include ::Cisco::Spark::Xapi::Mapper + + module Hooks + def connected + super + register_feedback '/Event/UserInterface/Extensions/Widget/Action' do |action| + logger.debug action + end + end + end + + def self.included(base) + base.prepend Hooks + end + + command 'UserInterface Message Alert Clear' => :msg_alert_clear + command 'UserInterface Message Alert Display' => :msg_alert, + Text: String, + Title_: String, + Duration_: (0..3600) + + command 'UserInterface Message Prompt Clear' => :msg_prompt_clear + command 'UserInterface Message Prompt Display' => :msg_prompt, + Text: String, + Title_: String, + FeedbackId_: String, + Duration_: (0..3600), + 'Option.1' => String, + 'Option.2' => String, + 'Option.3' => String, + 'Option.4' => String, + 'Option.5' => String + + command 'UserInterface Message TextInput Clear' => :msg_text_clear + command 'UserInterface Message TextInput Display' => :msg_text, + Text: String, + Title_: String, + FeedbackId_: String, + Duration_: (0..3600), + InputType_: [:SingleLine, :Numeric, :Password, :PIN], + KeyboardState_: [:Open, :Closed], + PlaceHolder_: String, + SubmitText_: String + + def ui_set_value(widget, value) + if value.nil? + send_xcommand 'UserInterface Extensions Widget UnsetValue', + WidgetId: widget + else + send_xcommand 'UserInterface Extensions Widget SetValue', + Value: value, WidgetId: widget + end + end + + protected + + def ui_extensions_list + send_xcommand 'UserInterface Extensions List' + end + + def ui_extensions_clear + send_xcommand 'UserInterface Extensions Clear' + end +end From c5fa1bad6f8c404ce3de667337a276b89bffa072 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 12:41:08 +1000 Subject: [PATCH 0183/1752] (cisco:spark) fix issue with auto-generated variable names --- modules/cisco/spark/ui_extensions.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/cisco/spark/ui_extensions.rb b/modules/cisco/spark/ui_extensions.rb index 54925bb3..3c8448e9 100644 --- a/modules/cisco/spark/ui_extensions.rb +++ b/modules/cisco/spark/ui_extensions.rb @@ -26,16 +26,16 @@ def self.included(base) Duration_: (0..3600) command 'UserInterface Message Prompt Clear' => :msg_prompt_clear - command 'UserInterface Message Prompt Display' => :msg_prompt, - Text: String, - Title_: String, - FeedbackId_: String, - Duration_: (0..3600), - 'Option.1' => String, - 'Option.2' => String, - 'Option.3' => String, - 'Option.4' => String, - 'Option.5' => String + def msg_prompt(text, options, title: nil, feedback_id: nil, duration: nil) + send_xcommand \ + 'UserInterface Message Prompt Display', + { + Text: text, + Title: title, + FeedbackId: feedback_id, + Duration: duration + }.merge(Hash[('Option.1'..'Option.5').zip options]) + end command 'UserInterface Message TextInput Clear' => :msg_text_clear command 'UserInterface Message TextInput Display' => :msg_text, From ededae05ac26b6eb94a7ee06fd87ebadd21b8b37 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 12:45:36 +1000 Subject: [PATCH 0184/1752] (cisco:spark) fix issue with tokenizer settings not applying --- modules/cisco/spark/room_os.rb | 5 ----- modules/cisco/spark/sx20.rb | 4 ++++ modules/cisco/spark/sx80.rb | 4 ++++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index 497c3701..e22f47ea 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -130,11 +130,6 @@ def xstatus(path) def self.extended(child) child.class_eval do protect_method :xcommand, :xconfigruation, :xstatus - - # Workaround for issues in Orchestrator where these are not applied - tokenize delimiter: Tokens::COMMAND_RESPONSE, - wait_ready: Tokens::LOGIN_COMPLETE - clear_queue_on_disconnect! end end diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 378a0921..b1871bb7 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -12,6 +12,10 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs Device access requires an API user to be created on the endpoint. DESC + tokenize delimiter: Tokens::COMMAND_RESPONSE, + wait_ready: Tokens::LOGIN_COMPLETE + clear_queue_on_disconnect! + status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume status 'RoomAnalytics PeoplePresence' => :presence_detected diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 5152867a..3b98ac0a 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -12,6 +12,10 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs Device access requires an API user to be created on the endpoint. DESC + tokenize delimiter: Tokens::COMMAND_RESPONSE, + wait_ready: Tokens::LOGIN_COMPLETE + clear_queue_on_disconnect! + status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume status 'Cameras PresenterTrack' => :presenter_track From 39b4abc9a5e43028d45ce151cd4ca08fc761cd35 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 12:53:39 +1000 Subject: [PATCH 0185/1752] (cisco:spark) fix issue with option prompt arguments --- modules/cisco/spark/ui_extensions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/ui_extensions.rb b/modules/cisco/spark/ui_extensions.rb index 3c8448e9..cbd3d9cf 100644 --- a/modules/cisco/spark/ui_extensions.rb +++ b/modules/cisco/spark/ui_extensions.rb @@ -34,7 +34,7 @@ def msg_prompt(text, options, title: nil, feedback_id: nil, duration: nil) Title: title, FeedbackId: feedback_id, Duration: duration - }.merge(Hash[('Option.1'..'Option.5').zip options]) + }.merge(Hash[('Option.1'..'Option.5').map(&:to_sym).zip options]) end command 'UserInterface Message TextInput Clear' => :msg_text_clear From dcef19b358141fc2f741a710759723fcf48e90f1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 13:22:01 +1000 Subject: [PATCH 0186/1752] (cisco:spark) make feedback_id a required param --- modules/cisco/spark/ui_extensions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/ui_extensions.rb b/modules/cisco/spark/ui_extensions.rb index cbd3d9cf..78ff9ba3 100644 --- a/modules/cisco/spark/ui_extensions.rb +++ b/modules/cisco/spark/ui_extensions.rb @@ -40,8 +40,8 @@ def msg_prompt(text, options, title: nil, feedback_id: nil, duration: nil) command 'UserInterface Message TextInput Clear' => :msg_text_clear command 'UserInterface Message TextInput Display' => :msg_text, Text: String, + FeedbackId: String, Title_: String, - FeedbackId_: String, Duration_: (0..3600), InputType_: [:SingleLine, :Numeric, :Password, :PIN], KeyboardState_: [:Open, :Closed], From 58e4588a0560564f40ce8cba6f48f32f64e619cf Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 18:45:46 +1000 Subject: [PATCH 0187/1752] (cisco:spark) add ability to place device into halfwake state --- modules/cisco/spark/sx20.rb | 1 + modules/cisco/spark/sx80.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index b1871bb7..430a5a93 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -62,6 +62,7 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs Focus_: [:Far, :Near, :Stop] command 'Standby Deactivate' => :wake_up + command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 3b98ac0a..5046e484 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -82,6 +82,7 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs :speaker_track_diagnostics_stop command 'Standby Deactivate' => :wake_up + command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) From 96b5f612230baf3be2c105e55d8ae5be7a130466 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 19:39:09 +1000 Subject: [PATCH 0188/1752] (cisco:spark) update power methods --- modules/cisco/spark/sx20.rb | 13 ++++++++++++- modules/cisco/spark/sx80.rb | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index 430a5a93..f8886e95 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -61,10 +61,21 @@ class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs ZoomSpeed_: (1..15), Focus_: [:Far, :Near, :Stop] - command 'Standby Deactivate' => :wake_up + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) + def power(state = false) + if is_affirmative? state + powerup + elsif is_negatory? state + standby + elsif state.to_s =~ /wake/i + half_wake + else + logger.error "Invalid power state: #{state}" + end + end command! 'SystemUnit Boot' => :reboot, Action_: [:Restart, :Shutdown] end diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 5046e484..6db7b09c 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -81,10 +81,21 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs command! 'Cameras SpeakerTrack Diagnostics Stop' => \ :speaker_track_diagnostics_stop - command 'Standby Deactivate' => :wake_up + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) + def power(state = false) + if is_affirmative? state + powerup + elsif is_negatory? state + standby + elsif state.to_s =~ /wake/i + half_wake + else + logger.error "Invalid power state: #{state}" + end + end command! 'SystemUnit Boot' => :reboot, Action_: [:Restart, :Shutdown] end From 3a3c15e3fde6f14c1d88ce772f6be9d47e266f41 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 20:06:37 +1000 Subject: [PATCH 0189/1752] (cisco:spark) make autofocus camera selection compulsory --- modules/cisco/spark/sx80.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 6db7b09c..c7aa94c5 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -65,10 +65,10 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs command! 'Cameras AutoFocus Diagnostics Start' => \ :autofocus_diagnostics_start, - CameraId_: (1..7) + CameraId: (1..7) command! 'Cameras AutoFocus Diagnostics Stop' => \ :autofocus_diagnostics_stop, - CameraId_: (1..7) + CameraId: (1..7) command! 'Cameras PresenterTrack ClearPosition' => :presenter_track_clear command! 'Cameras PresenterTrack StorePosition' => :presenter_track_store From aa494a3ab3c838c917af4fbc7f3ce7f272a6e337 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 14 Dec 2017 22:35:35 +1000 Subject: [PATCH 0190/1752] (cisco:spark) add ability to enable/disable speaker track --- modules/cisco/spark/sx80.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index c7aa94c5..84b3f13e 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -81,6 +81,16 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs command! 'Cameras SpeakerTrack Diagnostics Stop' => \ :speaker_track_diagnostics_stop + # The 'integrator' account can't active/deactive SpeakerTrack, but we can + # cut off access via a configuration setting. + def speaker_track(state = On) + if is_affirmative? state + send_xconfiguration 'Cameras SpeakerTrack', :Mode, :Auto + else + send_xconfiguration 'Cameras SpeakerTrack', :Mode, :Off + end + end + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby From 366c9ead66d67a4cdea2577435915440cd59c95a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 02:10:02 +1000 Subject: [PATCH 0191/1752] (cisco:spark) add command mappings for external sources --- modules/cisco/spark/external_source.rb | 44 ++++++++++++++++++++++++++ modules/cisco/spark/sx20.rb | 2 ++ modules/cisco/spark/sx80.rb | 2 ++ 3 files changed, 48 insertions(+) create mode 100644 modules/cisco/spark/external_source.rb diff --git a/modules/cisco/spark/external_source.rb b/modules/cisco/spark/external_source.rb new file mode 100644 index 00000000..be259eab --- /dev/null +++ b/modules/cisco/spark/external_source.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Cisco; end +module Cisco::Spark; end + +module Cisco::Spark::ExternalSource + include ::Cisco::Spark::Xapi::Mapper + + module Hooks + def connected + super + register_feedback '/Event/UserInterface/Presentation/ExternalSource' do |action| + logger.debug action + # TODO update module status with active source so our modules can subscribe + end + end + end + + def self.included(base) + base.prepend Hooks + end + + command! 'UserInterface ExternalSource Add' => :add_source, + ConnectorId: (1..7), + Name: String, + SourceIdentifier: String, + Type: [:pc, :camera, :desktop, :document_camera, :mediaplayer, + :other, :whiteboard] + + command! 'UserInterface ExternalsSource Remove' => :remove_source, + SourceIdentifier: String + + command! 'UserInterface ExternalsSource RemoveAll' => :clear_sources + + command 'UserInterface ExternalsSource Select' => :select_source, + SourceIdentifier: String + + command! 'UserInterface ExternalsSource State Set' => :source_state, + SourceIdentifier: String, + State: [:Error, :Hidden, :NotReady, :Ready], + ErrorReason_: String + + command 'UserInterface ExternalSource List' => :list_sources +end diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/spark/sx20.rb index f8886e95..cce0597e 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/spark/sx20.rb @@ -2,10 +2,12 @@ load File.join(__dir__, 'room_os.rb') load File.join(__dir__, 'ui_extensions.rb') +load File.join(__dir__, 'external_source.rb') class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs include ::Cisco::Spark::Xapi::Mapper include ::Cisco::Spark::UiExtensions + include ::Cisco::Spark::ExternalSource descriptive_name 'Cisco Spark SX20' description <<~DESC diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 84b3f13e..9be44654 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -2,10 +2,12 @@ load File.join(__dir__, 'room_os.rb') load File.join(__dir__, 'ui_extensions.rb') +load File.join(__dir__, 'external_source.rb') class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs include ::Cisco::Spark::Xapi::Mapper include ::Cisco::Spark::UiExtensions + include ::Cisco::Spark::ExternalSource descriptive_name 'Cisco Spark SX80' description <<~DESC From 321705f25825bab6fc9d4363995ac3c7ef05ce28 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 10:11:45 +1100 Subject: [PATCH 0192/1752] Add error catching to users booking function --- lib/ibm/domino.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 836459f2..e211ce89 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -138,6 +138,10 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) full_events.push(full_event) } full_events + + rescue => e + STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" + raise e end def get_bookings(room_id, date=Time.now.midnight) From 1c77d22d03a300bb9146c399d20472f0e873a17a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 10:11:45 +1100 Subject: [PATCH 0193/1752] Add error catching to users booking function --- lib/ibm/domino.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 836459f2..e211ce89 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -138,6 +138,10 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) full_events.push(full_event) } full_events + + rescue => e + STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" + raise e end def get_bookings(room_id, date=Time.now.midnight) From a0557c3453d5a1d3319a9e0ed91cbcbeb6925874 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 10:54:43 +1100 Subject: [PATCH 0194/1752] Remove rooms from response --- lib/ibm/domino.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index e211ce89..793b47b4 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -325,10 +325,12 @@ def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, en def get_attendees(path) booking_request = domino_request('get',nil,nil,nil,nil,path).value if ![200,201,204].include?(booking_request.status) + Rails.logger.info "Didn't get a 20X response from meeting detail requst." return false end booking_response = JSON.parse(booking_request.body)['events'][0] if booking_response['attendees'] + Rails.logger.info "Booking has attendees" booking_response['attendees'].each{|attendee| if attendee.key?('userType') && attendee['userType'] == 'room' booking_response['room_email'] = attendee['email'] @@ -348,6 +350,9 @@ def get_attendees(path) else attendee_name = attendee['email'] end + if attendee.key?('userType') && attendee['userType'] == 'room' + next + end { name: attendee_name, From 0a6b78cee6a9db51fc41a110ccd008499222f41c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 11:02:32 +1100 Subject: [PATCH 0195/1752] Add logic for no start time present --- lib/ibm/domino.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 793b47b4..d839b838 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -372,7 +372,10 @@ def get_attendees(path) } booking_response['organizer'] = organizer end - if booking_response['start'].key?('tzid') && booking_response['start']['tzid'] == "Singapore Standard Time" + if !booking_response['start'].key?('time') + booking_response['start'] = (Time.parse(booking_response['start']['date']+'T00:00:00+0800').utc.to_i.to_s + "000").to_i + booking_response['end'] = (Time.parse(booking_response['end']['date']+'T00:00:00+0800').utc.to_i.to_s + "000").to_i + elsif booking_response['start'].key?('tzid') && booking_response['start']['tzid'] == "Singapore Standard Time" booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0800').utc.to_i.to_s + "000").to_i booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0800').utc.to_i.to_s + "000").to_i else From 0aaa9173e5f070d92a534d63b81b76813f7f73f0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 11:03:41 +1100 Subject: [PATCH 0196/1752] Remove debugging for now --- lib/ibm/domino.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d839b838..f6153f11 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -139,9 +139,9 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) } full_events - rescue => e - STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" - raise e + # rescue => e + # STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" + # raise e end def get_bookings(room_id, date=Time.now.midnight) From cdf8423af269053397d2836ebf9c987e1df59727 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 11:17:24 +1100 Subject: [PATCH 0197/1752] Add readable time --- lib/ibm/domino.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index f6153f11..032c5bd5 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -382,6 +382,8 @@ def get_attendees(path) booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0000').utc.to_i.to_s + "000").to_i booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0000').utc.to_i.to_s + "000").to_i end + booking_response['start_readable'] = Time.at(booking_response['start']).to_s + booking_response['end_readable'] = Time.at(booking_response['end']).to_s booking_response end From 9a0465c43808e45875054b7fd39dac3cf0c4c2a3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 11:20:22 +1100 Subject: [PATCH 0198/1752] Set organizer as empty object if not exsting --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 032c5bd5..29838a9f 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -129,7 +129,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) Rails.logger.info "FAILED TO GET DETAILED BOOKING" Rails.logger.info "##############################" full_event = event - full_event['organizer'] = {email: 'N/A'} + full_event['organizer'] = {} full_event['description'] = '' full_event['attendees'] = [] full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i From 9700aa38f2b1060fc8951bbb5653a9bce6282725 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 11:22:55 +1100 Subject: [PATCH 0199/1752] Stop using JS epochs --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 29838a9f..1dce0c73 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -382,8 +382,8 @@ def get_attendees(path) booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0000').utc.to_i.to_s + "000").to_i booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0000').utc.to_i.to_s + "000").to_i end - booking_response['start_readable'] = Time.at(booking_response['start']).to_s - booking_response['end_readable'] = Time.at(booking_response['end']).to_s + booking_response['start_readable'] = Time.at(booking_response['start'].to_i / 1000).to_s + booking_response['end_readable'] = Time.at(booking_response['end'].to_i / 1000).to_s booking_response end From 5037d059d1cc84df61b2afde6007c0fa0209a0f9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 11:29:34 +1100 Subject: [PATCH 0200/1752] Remove the room after being set to nil --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 1dce0c73..a04a9f27 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -359,7 +359,7 @@ def get_attendees(path) email: attendee['email'], state: attendee['status'].gsub(/-/,' ') } - } + }.compact booking_response['attendees'] = attendees end if booking_response['organizer'] From 0d9bf05f24128b40a7d3a458309c1df11142e361 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 11:33:02 +1100 Subject: [PATCH 0201/1752] OMG ADD EXCLAMANTION POINT --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index a04a9f27..e8515eda 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -359,7 +359,7 @@ def get_attendees(path) email: attendee['email'], state: attendee['status'].gsub(/-/,' ') } - }.compact + }.compact! booking_response['attendees'] = attendees end if booking_response['organizer'] From d4cb3f01442aefaec7c7ef43f4b3facb3f6e41cd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 12:31:25 +1100 Subject: [PATCH 0202/1752] Catch errors --- lib/ibm/domino.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index e8515eda..d5e5b0e0 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -139,9 +139,9 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) } full_events - # rescue => e - # STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" - # raise e + rescue => e + STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" + raise e end def get_bookings(room_id, date=Time.now.midnight) From 43082f969ee7c0387c8d840f8802f1834dfcfb95 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 12:35:28 +1100 Subject: [PATCH 0203/1752] Fix special time case --- lib/ibm/domino.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d5e5b0e0..b0c92314 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -132,8 +132,16 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) full_event['organizer'] = {} full_event['description'] = '' full_event['attendees'] = [] - full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i - full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i + if !full_event['start'].key('time') + full_event['start'] = (Time.parse(full_event['start']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i + full_event['end'] = (Time.parse(full_event['end']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i + elsif booking_response['start'].key?('tzid') && booking_response['start']['tzid'] == "Singapore Standard Time" + full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i.to_s + "000").to_i + full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i.to_s + "000").to_i + else + full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i + full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i + end end full_events.push(full_event) } From a196e92376b97b5951059ea54f52176f78d9e792 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 12:37:25 +1100 Subject: [PATCH 0204/1752] Fix syntax --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index b0c92314..58847aa9 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -132,7 +132,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) full_event['organizer'] = {} full_event['description'] = '' full_event['attendees'] = [] - if !full_event['start'].key('time') + if !full_event['start'].key?('time') full_event['start'] = (Time.parse(full_event['start']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i full_event['end'] = (Time.parse(full_event['end']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i elsif booking_response['start'].key?('tzid') && booking_response['start']['tzid'] == "Singapore Standard Time" From 62aad80ffbb424388d55a0f3a1eb1a23b0d63001 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 12:46:04 +1100 Subject: [PATCH 0205/1752] Add better debugging --- lib/ibm/domino.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 58847aa9..a8e447d8 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -147,9 +147,9 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) } full_events - rescue => e - STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" - raise e + rescue => e + STDERR.puts "\n\n#{e.message}\n#{e.backtrace.join("\n")}\n\n" + raise "\n\n#{e.message}\n#{e.backtrace.join("\n")}\n\n" end def get_bookings(room_id, date=Time.now.midnight) From f52b3fe3fdaf0a5366a4358077d083c903d7b305 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 18 Dec 2017 13:09:52 +1100 Subject: [PATCH 0206/1752] Fix syntax --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index a8e447d8..a02addba 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -135,7 +135,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) if !full_event['start'].key?('time') full_event['start'] = (Time.parse(full_event['start']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i full_event['end'] = (Time.parse(full_event['end']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i - elsif booking_response['start'].key?('tzid') && booking_response['start']['tzid'] == "Singapore Standard Time" + elsif full_event['start'].key?('tzid') && full_event['start']['tzid'] == "Singapore Standard Time" full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i.to_s + "000").to_i full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i.to_s + "000").to_i else From 37367d0de3fc1468554dc04359a94641580c6af1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 14:38:28 +1000 Subject: [PATCH 0207/1752] (cisco:spark) remove access protections for source methods --- modules/cisco/spark/external_source.rb | 31 ++++++++++++++------------ 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/modules/cisco/spark/external_source.rb b/modules/cisco/spark/external_source.rb index be259eab..1a5faec8 100644 --- a/modules/cisco/spark/external_source.rb +++ b/modules/cisco/spark/external_source.rb @@ -11,7 +11,8 @@ def connected super register_feedback '/Event/UserInterface/Presentation/ExternalSource' do |action| logger.debug action - # TODO update module status with active source so our modules can subscribe + # TODO update module status with active source so our modules + # can subscribe end end end @@ -20,25 +21,27 @@ def self.included(base) base.prepend Hooks end - command! 'UserInterface ExternalSource Add' => :add_source, - ConnectorId: (1..7), - Name: String, - SourceIdentifier: String, - Type: [:pc, :camera, :desktop, :document_camera, :mediaplayer, - :other, :whiteboard] + # TODO: protect methods (via ::Orchestrator::Security) that manipulate + # sources. Currently mapper does not support this from within a module. + command 'UserInterface ExternalSource Add' => :add_source, + ConnectorId: (1..7), + Name: String, + SourceIdentifier: String, + Type: [:pc, :camera, :desktop, :document_camera, :mediaplayer, + :other, :whiteboard] - command! 'UserInterface ExternalsSource Remove' => :remove_source, - SourceIdentifier: String + command 'UserInterface ExternalsSource Remove' => :remove_source, + SourceIdentifier: String - command! 'UserInterface ExternalsSource RemoveAll' => :clear_sources + command 'UserInterface ExternalsSource RemoveAll' => :clear_sources command 'UserInterface ExternalsSource Select' => :select_source, SourceIdentifier: String - command! 'UserInterface ExternalsSource State Set' => :source_state, - SourceIdentifier: String, - State: [:Error, :Hidden, :NotReady, :Ready], - ErrorReason_: String + command 'UserInterface ExternalsSource State Set' => :source_state, + State: [:Error, :Hidden, :NotReady, :Ready], + SourceIdentifier: String, + ErrorReason_: String command 'UserInterface ExternalSource List' => :list_sources end From f45a30a9de9f54e1b3b917f6a29881681c9d56ff Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 14:38:47 +1000 Subject: [PATCH 0208/1752] (cisco:spark) add TODO for message prompt behaviour --- modules/cisco/spark/ui_extensions.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/cisco/spark/ui_extensions.rb b/modules/cisco/spark/ui_extensions.rb index 78ff9ba3..1254e151 100644 --- a/modules/cisco/spark/ui_extensions.rb +++ b/modules/cisco/spark/ui_extensions.rb @@ -27,6 +27,8 @@ def self.included(base) command 'UserInterface Message Prompt Clear' => :msg_prompt_clear def msg_prompt(text, options, title: nil, feedback_id: nil, duration: nil) + # TODO: return a promise, then prepend a async traffic monitor so it + # can be resolved with the response, or rejected after the timeout. send_xcommand \ 'UserInterface Message Prompt Display', { From a096dfd8d5672942c095181c98fcddb2266102d7 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 14:48:16 +1000 Subject: [PATCH 0209/1752] (cisco:spark) fix parser warning when initing This reverts commit 83c74b75974cd29ff6c640e157868fc788903cfd. --- modules/cisco/spark/room_os.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index e22f47ea..a7e94099 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -283,7 +283,10 @@ def device_subscriptions # Base comms def init_connection - send "Echo off\n", priority: 96, wait: false + send "Echo off\n", priority: 96 do |response| + :success if response.starts_with? "\e[?1034h" + end + send "xPreferences OutputMode JSON\n", wait: false end From 877cc01d1e75149358878bfbb3a63d76aff35f1a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 15:01:05 +1000 Subject: [PATCH 0210/1752] (cisco:spark) fix issue with echo off response parsing --- modules/cisco/spark/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/spark/room_os.rb index a7e94099..8b6c9fd8 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/spark/room_os.rb @@ -284,7 +284,7 @@ def device_subscriptions def init_connection send "Echo off\n", priority: 96 do |response| - :success if response.starts_with? "\e[?1034h" + :success if response.include? "\e[?1034h" end send "xPreferences OutputMode JSON\n", wait: false From 20d9555504a41e75ca7fcac38618edcfc7b0e148 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 15:10:43 +1000 Subject: [PATCH 0211/1752] (cisco:spark) fix incorrect command mappings --- modules/cisco/spark/external_source.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/cisco/spark/external_source.rb b/modules/cisco/spark/external_source.rb index 1a5faec8..2273c1f3 100644 --- a/modules/cisco/spark/external_source.rb +++ b/modules/cisco/spark/external_source.rb @@ -23,25 +23,25 @@ def self.included(base) # TODO: protect methods (via ::Orchestrator::Security) that manipulate # sources. Currently mapper does not support this from within a module. - command 'UserInterface ExternalSource Add' => :add_source, + command 'UserInterface Presentation ExternalSource Add' => :add_source, ConnectorId: (1..7), Name: String, SourceIdentifier: String, Type: [:pc, :camera, :desktop, :document_camera, :mediaplayer, :other, :whiteboard] - command 'UserInterface ExternalsSource Remove' => :remove_source, + command 'UserInterface Presentation ExternalSource Remove' => :remove_source, SourceIdentifier: String - command 'UserInterface ExternalsSource RemoveAll' => :clear_sources + command 'UserInterface Presentation ExternalSource RemoveAll' => :clear_sources - command 'UserInterface ExternalsSource Select' => :select_source, + command 'UserInterface Presentation ExternalSource Select' => :select_source, SourceIdentifier: String - command 'UserInterface ExternalsSource State Set' => :source_state, + command 'UserInterface Presentation ExternalSource State Set' => :source_state, State: [:Error, :Hidden, :NotReady, :Ready], SourceIdentifier: String, ErrorReason_: String - command 'UserInterface ExternalSource List' => :list_sources + command 'UserInterface Presentation ExternalSource List' => :list_sources end From 61aa06b50a9348fd4cbde0f7a37a0d9ec49f23c0 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 17:39:39 +1000 Subject: [PATCH 0212/1752] (cisco:spark) push external source selection to status variable --- modules/cisco/spark/external_source.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/cisco/spark/external_source.rb b/modules/cisco/spark/external_source.rb index 2273c1f3..35537566 100644 --- a/modules/cisco/spark/external_source.rb +++ b/modules/cisco/spark/external_source.rb @@ -9,10 +9,10 @@ module Cisco::Spark::ExternalSource module Hooks def connected super - register_feedback '/Event/UserInterface/Presentation/ExternalSource' do |action| - logger.debug action - # TODO update module status with active source so our modules - # can subscribe + register_feedback \ + '/Event/UserInterface/Presentation/ExternalSource' do |action| + source = action.dig 'Selected', 'SourceIdentifier' + self[:external_source] = source unless source.nil? end end end From a347a51db7fe59065c4bc96880ac08c3560d9cec Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 17:47:07 +1000 Subject: [PATCH 0213/1752] (cisco:spark) reorder args so source identifier is always at first --- modules/cisco/spark/external_source.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/spark/external_source.rb b/modules/cisco/spark/external_source.rb index 35537566..12a855e2 100644 --- a/modules/cisco/spark/external_source.rb +++ b/modules/cisco/spark/external_source.rb @@ -24,9 +24,9 @@ def self.included(base) # TODO: protect methods (via ::Orchestrator::Security) that manipulate # sources. Currently mapper does not support this from within a module. command 'UserInterface Presentation ExternalSource Add' => :add_source, + SourceIdentifier: String, ConnectorId: (1..7), Name: String, - SourceIdentifier: String, Type: [:pc, :camera, :desktop, :document_camera, :mediaplayer, :other, :whiteboard] @@ -39,8 +39,8 @@ def self.included(base) SourceIdentifier: String command 'UserInterface Presentation ExternalSource State Set' => :source_state, - State: [:Error, :Hidden, :NotReady, :Ready], SourceIdentifier: String, + State: [:Error, :Hidden, :NotReady, :Ready], ErrorReason_: String command 'UserInterface Presentation ExternalSource List' => :list_sources From 0fd1d0f4c09d8cec6504b407d0f68e5dde703279 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Dec 2017 20:18:57 +1000 Subject: [PATCH 0214/1752] (cisco:spark) fix argument validation for lists of symbols --- modules/cisco/spark/xapi/mapper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/spark/xapi/mapper.rb index 75f765ac..f77f159c 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/spark/xapi/mapper.rb @@ -54,7 +54,7 @@ def command(mapping) cond = "(#{type}).include?(#{param})" else msg = "#{param} must be one of #{type}" - cond = "#{type}.any? { |t| t.to_s.casecmp(#{param}) == 0 }" + cond = "#{type}.any? { |t| t.to_s.casecmp(#{param}.to_s) == 0 }" end "raise ArgumentError, '#{msg}' unless #{param}.nil? || #{cond}" end From aa630c35eaec862b8f6be59e297e2f86044646b3 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 19 Dec 2017 15:31:43 +1000 Subject: [PATCH 0215/1752] (cisco:spark) track out of call presentation state --- modules/cisco/spark/sx80.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/spark/sx80.rb index 9be44654..081ff252 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/spark/sx80.rb @@ -18,6 +18,17 @@ class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs wait_ready: Tokens::LOGIN_COMPLETE clear_queue_on_disconnect! + def connected + super + + register_feedback '/Event/PresentationPreviewStarted' do + self[:local_presentation] = true + end + register_feedback '/Event/PresentationPreviewStopped' do + self[:local_presentation] = false + end + end + status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume status 'Cameras PresenterTrack' => :presenter_track From f89abdadf0ca8c7b2798c6284c4b0101dd80c38e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 19 Dec 2017 18:49:43 +1000 Subject: [PATCH 0216/1752] (cisco:spark) ensure external source select events always propogate --- modules/cisco/spark/external_source.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cisco/spark/external_source.rb b/modules/cisco/spark/external_source.rb index 12a855e2..c5456c66 100644 --- a/modules/cisco/spark/external_source.rb +++ b/modules/cisco/spark/external_source.rb @@ -12,7 +12,10 @@ def connected register_feedback \ '/Event/UserInterface/Presentation/ExternalSource' do |action| source = action.dig 'Selected', 'SourceIdentifier' - self[:external_source] = source unless source.nil? + unless source.nil? + self[:external_source] = source + signal_status(:external_source) + end end end end From b5725d1e39c6769c2d7cddaecd06f15401d22906 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 19 Dec 2017 18:55:00 +1000 Subject: [PATCH 0217/1752] (cisco:spark) add ability to enable / disable content audio --- modules/cisco/spark/external_source.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/cisco/spark/external_source.rb b/modules/cisco/spark/external_source.rb index c5456c66..1ad3b6d7 100644 --- a/modules/cisco/spark/external_source.rb +++ b/modules/cisco/spark/external_source.rb @@ -24,6 +24,11 @@ def self.included(base) base.prepend Hooks end + def source_audio(state) + mode = is_affirmative?(state) ? :On : :Off + send_xconfiguration 'Audio Input HDMI 3', :Mode, mode + end + # TODO: protect methods (via ::Orchestrator::Security) that manipulate # sources. Currently mapper does not support this from within a module. command 'UserInterface Presentation ExternalSource Add' => :add_source, From ddd356787779e6746c01ed9dbd5a636a67b54015 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Dec 2017 11:09:16 +1100 Subject: [PATCH 0218/1752] Accomodate other time one name --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index a02addba..d68006f4 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -135,7 +135,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) if !full_event['start'].key?('time') full_event['start'] = (Time.parse(full_event['start']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i full_event['end'] = (Time.parse(full_event['end']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i - elsif full_event['start'].key?('tzid') && full_event['start']['tzid'] == "Singapore Standard Time" + elsif full_event['start'].key?('tzid') && ["Singapore Standard Time", "GMT+8 Standard Time"].include?(full_event['start']['tzid']) full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i.to_s + "000").to_i full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i.to_s + "000").to_i else From 0cbc2f6384539fde5efbac6dcaad41a3ee89f6e2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Dec 2017 17:40:59 +1100 Subject: [PATCH 0219/1752] Match the whole room name including "/PwC SG R&R" --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d68006f4..ec16f087 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -184,7 +184,7 @@ def get_bookings(room_id, date=Time.now.midnight) rooms_bookings = [] bookings = JSON.parse(response.body)['viewentry'] || [] bookings.each{ |booking| - domino_room_name = booking['entrydata'][2]['text']['0'].split('/')[0] + domino_room_name = booking['entrydata'][2]['text']['0'] if room_name == domino_room_name new_booking = { start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i, From 3508319a6771a8e9c2526eaa9ecfe91faeb5b08d Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 28 Dec 2017 19:07:23 +0800 Subject: [PATCH 0220/1752] enable local button lock by default prevents displays going network offline when users press the power button on the display --- modules/lg/lcd/model_ls5.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/lg/lcd/model_ls5.rb b/modules/lg/lcd/model_ls5.rb index 4b59caf0..f3a4bca2 100644 --- a/modules/lg/lcd/model_ls5.rb +++ b/modules/lg/lcd/model_ls5.rb @@ -55,6 +55,7 @@ def connected wake_on_lan(true) no_signal_off(false) auto_off(false) + local_button_lock(true) do_poll end @@ -79,7 +80,8 @@ def disconnected wol: 'w', no_signal_off: 'g', auto_off: 'n', - dpm: 'j' + dpm: 'j', + local_button_lock: 'o' } Lookup = Command.invert @@ -223,6 +225,12 @@ def configure_dpm(time_out = 4) # The action DPM takes needs to be configured using a remote # The action should be set to: screen off always end + + def local_button_lock(enable = true) + #0=off, 1=lock all except Power buttons, 2=lock all buttons. Default to 2 as power off from local button results in network offline + val = is_affirmative?(enable) ? 2 : 0 + do_send(Command[:local_button_lock], val, :f, name: :local_button_lock) + end def no_signal_off(enable = false) val = is_affirmative?(enable) ? 1 : 0 From 6be54193e4d418f3d73164c627938c86756ba542 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 28 Dec 2017 19:23:47 +0800 Subject: [PATCH 0221/1752] detect local button lock ack --- modules/lg/lcd/model_ls5.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/lg/lcd/model_ls5.rb b/modules/lg/lcd/model_ls5.rb index f3a4bca2..311f3602 100644 --- a/modules/lg/lcd/model_ls5.rb +++ b/modules/lg/lcd/model_ls5.rb @@ -331,6 +331,8 @@ def received(data, resolve, command) logger.debug { "No Signal Auto Off changed!" } when :auto_off logger.debug { "Auto Off changed!" } + when :local_button_lock + logger.debug { "Local Button Lock changed!" } else return :ignore end From cdf34af54682c221f530df9f415438c258161e2a Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 28 Dec 2017 19:32:03 +0800 Subject: [PATCH 0222/1752] fix local button lock command --- modules/lg/lcd/model_ls5.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/lg/lcd/model_ls5.rb b/modules/lg/lcd/model_ls5.rb index 311f3602..39f55376 100644 --- a/modules/lg/lcd/model_ls5.rb +++ b/modules/lg/lcd/model_ls5.rb @@ -229,7 +229,7 @@ def configure_dpm(time_out = 4) def local_button_lock(enable = true) #0=off, 1=lock all except Power buttons, 2=lock all buttons. Default to 2 as power off from local button results in network offline val = is_affirmative?(enable) ? 2 : 0 - do_send(Command[:local_button_lock], val, :f, name: :local_button_lock) + do_send(Command[:local_button_lock], val, :t, name: :local_button_lock) end def no_signal_off(enable = false) From 7844c843282e3ff265089999575bd8fe52c2b54a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 3 Jan 2018 15:15:47 +1100 Subject: [PATCH 0223/1752] Make get_bookings get all at once --- lib/ibm/domino.rb | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index ec16f087..08e0498a 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -152,9 +152,17 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) raise "\n\n#{e.message}\n#{e.backtrace.join("\n")}\n\n" end - def get_bookings(room_id, date=Time.now.midnight) - room = Orchestrator::ControlSystem.find(room_id) - room_name = room.settings['name'] + def get_bookings(room_ids, date=Time.now.midnight) + if !(room_ids.class == Array) + room_ids = [room_ids] + end + room_names = room_ids.map{|id| Orchestrator::ControlSystem.find(id).settings['name']} + room_mapping = {} + room_ids.each{|id| + room_mapping[Orchestrator::ControlSystem.find(id).settings['name']] = id + } + # room = Orchestrator::ControlSystem.find(room_id) + # room_name = room.settings['name'] # The domino API takes a StartKey and UntilKey # We will only ever need one days worth of bookings @@ -181,18 +189,23 @@ def get_bookings(room_id, date=Time.now.midnight) response = request.value # Go through the returned bookings and add to output array - rooms_bookings = [] + rooms_bookings = {} bookings = JSON.parse(response.body)['viewentry'] || [] bookings.each{ |booking| + + # Get the room name domino_room_name = booking['entrydata'][2]['text']['0'] - if room_name == domino_room_name + + # Check if room is in our list + if room_names.include?(domino_room_name) + rooms_bookings[room_mapping[domino_room_name]] ||= [] new_booking = { start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i, end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i, summary: booking['entrydata'][5]['text']['0'], organizer: booking['entrydata'][3]['text']['0'] } - rooms_bookings.push(new_booking) + rooms_bookings[room_mapping[domino_room_name]].push(new_booking) end } rooms_bookings From 6e0078d5190c5fa1df017d7bdf7677353fe7447d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 4 Jan 2018 23:05:22 +1100 Subject: [PATCH 0224/1752] Clean up timezone code --- lib/ibm/domino.rb | 67 +++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 08e0498a..a3e6f17f 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -104,7 +104,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) request = domino_request('get', nil, nil, query, nil, database).value if [200,201,204].include?(request.status) if request.body != '' - events = JSON.parse(request.body)['events'] + events = add_event_utc(JSON.parse(request.body)['events']) else events = [] end @@ -125,23 +125,10 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) Rails.logger.info "Requesting to #{base_domain + event['href']}" full_event = get_attendees(base_domain + event['href']) if full_event == false - Rails.logger.info "##############################" - Rails.logger.info "FAILED TO GET DETAILED BOOKING" - Rails.logger.info "##############################" full_event = event full_event['organizer'] = {} full_event['description'] = '' full_event['attendees'] = [] - if !full_event['start'].key?('time') - full_event['start'] = (Time.parse(full_event['start']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i - full_event['end'] = (Time.parse(full_event['end']['date']+'T00:00:00+0000').utc.to_i.to_s + "000").to_i - elsif full_event['start'].key?('tzid') && ["Singapore Standard Time", "GMT+8 Standard Time"].include?(full_event['start']['tzid']) - full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0800').utc.to_i.to_s + "000").to_i - full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0800').utc.to_i.to_s + "000").to_i - else - full_event['start'] = (Time.parse(full_event['start']['date']+'T'+full_event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i - full_event['end'] = (Time.parse(full_event['end']['date']+'T'+full_event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i - end end full_events.push(full_event) } @@ -349,7 +336,9 @@ def get_attendees(path) Rails.logger.info "Didn't get a 20X response from meeting detail requst." return false end - booking_response = JSON.parse(booking_request.body)['events'][0] + + booking_response = add_event_utc(JSON.parse(booking_request.body))[0] + if booking_response['attendees'] Rails.logger.info "Booking has attendees" booking_response['attendees'].each{|attendee| @@ -383,6 +372,7 @@ def get_attendees(path) }.compact! booking_response['attendees'] = attendees end + if booking_response['organizer'] organizer = booking_response['organizer'].dup organizer = @@ -393,22 +383,49 @@ def get_attendees(path) } booking_response['organizer'] = organizer end - if !booking_response['start'].key?('time') - booking_response['start'] = (Time.parse(booking_response['start']['date']+'T00:00:00+0800').utc.to_i.to_s + "000").to_i - booking_response['end'] = (Time.parse(booking_response['end']['date']+'T00:00:00+0800').utc.to_i.to_s + "000").to_i - elsif booking_response['start'].key?('tzid') && booking_response['start']['tzid'] == "Singapore Standard Time" - booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0800').utc.to_i.to_s + "000").to_i - booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0800').utc.to_i.to_s + "000").to_i - else - booking_response['start'] = (Time.parse(booking_response['start']['date']+'T'+booking_response['start']['time']+'+0000').utc.to_i.to_s + "000").to_i - booking_response['end'] = (Time.parse(booking_response['end']['date']+'T'+booking_response['end']['time']+'+0000').utc.to_i.to_s + "000").to_i - end + booking_response['start_readable'] = Time.at(booking_response['start'].to_i / 1000).to_s booking_response['end_readable'] = Time.at(booking_response['end'].to_i / 1000).to_s booking_response end + def add_event_utc(response) + + events = response['events'] + response.key?('timezones') ? timezones = response['timezones'] : timezones = nil + + events.each{ |event| + # If the event has no time, set time to "00:00:00" + if !event['start'].key?('time') + start_time = "00:00:00" + end_time = "00:00:00" + else + start_time = event['start']['time'] + end_time = event['end']['time'] + end + + # If the event start has a tzid field, use the timezones hash + if event['start'].key?('tzid') + offset = timezones.find{|t| t['tzid'] == event['start']['tzid']}['standard']['offsetFrom'] + + # If the event has a utc field set to true, use utc + elsif event['start'].key?('utc') && event['start']['utc'] + offset = "+0000" + end + + start_timestring = "#{event['start']['date']}T#{start_time}#{offset}" + start_utc = (Time.parse(start_timestring).utc.to_i.to_s + "000").to_i + + end_timestring = "#{event['end']['date']}T#{end_time}#{offset}" + end_utc = (Time.parse(end_timestring).utc.to_i.to_s + "000").to_i + + event['start'] = start_utc + event['end'] = end_utc + } + events + end + def to_ibm_date(time) time.strftime("%Y-%m-%dT%H:%M:%SZ") end From e11c254442c9f3c45b0cb52460e3407a7b2a8598 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 4 Jan 2018 23:26:28 +1100 Subject: [PATCH 0225/1752] Fix logic issue --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index a3e6f17f..3b8af5b6 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -104,7 +104,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) request = domino_request('get', nil, nil, query, nil, database).value if [200,201,204].include?(request.status) if request.body != '' - events = add_event_utc(JSON.parse(request.body)['events']) + events = add_event_utc(JSON.parse(request.body)) else events = [] end From 3fee12fef23cd333729a2bde1364b556b71685ae Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 5 Jan 2018 15:38:53 +1100 Subject: [PATCH 0226/1752] Ensure empty array not nil if no bookings --- lib/ibm/domino.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 3b8af5b6..7654047b 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -177,6 +177,9 @@ def get_bookings(room_ids, date=Time.now.midnight) # Go through the returned bookings and add to output array rooms_bookings = {} + room_ids.each{|id| + rooms_bookings[id] = [] + } bookings = JSON.parse(response.body)['viewentry'] || [] bookings.each{ |booking| @@ -185,7 +188,6 @@ def get_bookings(room_ids, date=Time.now.midnight) # Check if room is in our list if room_names.include?(domino_room_name) - rooms_bookings[room_mapping[domino_room_name]] ||= [] new_booking = { start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i, end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i, From bccae9197271a66a8a8194fd957ca2c18d897409 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 5 Jan 2018 16:04:23 +1100 Subject: [PATCH 0227/1752] Add count to room booking list request --- lib/ibm/domino.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 7654047b..ed4e1e19 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -164,6 +164,7 @@ def get_bookings(room_ids, date=Time.now.midnight) # Set count to max query = { + Count: '500', StartKey: starting, UntilKey: ending, KeyType: 'time', From dd74c35258218a28d3b4bfd639c3aa3e5bb4a4cf Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 5 Jan 2018 17:14:54 +1100 Subject: [PATCH 0228/1752] (cisco:mixer) for Cisco SX series codec for replacing VidConf devices in stead of Mixer devices --- modules/cisco/tele_presence/sx_mixer.rb | 15 +++ .../cisco/tele_presence/sx_mixer_common.rb | 91 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 modules/cisco/tele_presence/sx_mixer.rb create mode 100644 modules/cisco/tele_presence/sx_mixer_common.rb diff --git a/modules/cisco/tele_presence/sx_mixer.rb b/modules/cisco/tele_presence/sx_mixer.rb new file mode 100644 index 00000000..3918d539 --- /dev/null +++ b/modules/cisco/tele_presence/sx_mixer.rb @@ -0,0 +1,15 @@ +load File.expand_path('./sx_telnet.rb', File.dirname(__FILE__)) +load File.expand_path('./sx_mixer_common.rb', File.dirname(__FILE__)) + + +class Cisco::TelePresence::SxMixer < Cisco::TelePresence::SxTelnet + include Cisco::TelePresence::SxMixerCommon + + # Communication settings + tokenize delimiter: "\r", + wait_ready: "login:" + clear_queue_on_disconnect! + + descriptive_name 'Cisco TelePresence Mixer' + generic_name :Mixer +end diff --git a/modules/cisco/tele_presence/sx_mixer_common.rb b/modules/cisco/tele_presence/sx_mixer_common.rb new file mode 100644 index 00000000..5400850e --- /dev/null +++ b/modules/cisco/tele_presence/sx_mixer_common.rb @@ -0,0 +1,91 @@ +# encoding: ASCII-8BIT +module Cisco::TelePresence::SxMixerCommon + def on_load + super + on_update + end + + def on_update + end + + def connected + self[:power] = true + super + do_poll + schedule.every('30s') do + logger.debug "-- Polling VC Volume" + do_poll + end + end + + def disconnected + self[:power] = false + super + schedule.clear + end + + def power(state) + self[:power] # Here for compatibility with other camera modules + end + + def power?(options = nil, &block) + block.call unless block.nil? + self[:power] + end + + def fader(_, value) + vol = in_range(value.to_i, 100, 0) + command('Audio Volume Set', params({ + level: vol + }), name: :volume).then do + self[:faderOutput] = vol + end + end + + def mute(_, state) + value = is_affirmative?(state) ? 'Mute' : 'Unmute' + command("Audio Volume #{value}"), name: :mute).then do + self[:faderOutput_mute] = value + end + end + # --------------- + # STATUS REQUESTS + # --------------- + def volume? + status "Audio Volume", priority: 0, name: :volume? + end + + def muted? + status "Audio VolumeMute", priority: 0, name: :muted? + end + + def do_poll + volume? + muted? + end + + IsResponse = '*s'.freeze + IsComplete = '**'.freeze + def received(data, resolve, command) + logger.debug { "Cisco SX Mixer sent #{data}" } + result = Shellwords.split data + if command + if result[0] == IsComplete + return :success + elsif result[0] != IsResponse + return :ignore + end + end + if result[0] == IsResponse + type = result[2].downcase.gsub(':', '').to_sym + case type + when :volume + self[:faderOutput] = result[-1].to_i + when :volumemute + self[:faderOutput_mute] = result[-1].downcase != 'off' + end + return :ignore + end + return :success + end +end \ No newline at end of file From 7b04f3e80493dcff6cea71f85e1088bdcc538aa3 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 5 Jan 2018 17:42:21 +1100 Subject: [PATCH 0229/1752] (cisco:mixer) fix syntax, add mutes(), faders(), --- modules/cisco/tele_presence/sx_mixer_common.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_mixer_common.rb b/modules/cisco/tele_presence/sx_mixer_common.rb index 5400850e..8b394070 100644 --- a/modules/cisco/tele_presence/sx_mixer_common.rb +++ b/modules/cisco/tele_presence/sx_mixer_common.rb @@ -41,13 +41,23 @@ def fader(_, value) self[:faderOutput] = vol end end + + def faders(ids:, level:, **_) + fader(nil, level) + end def mute(_, state) value = is_affirmative?(state) ? 'Mute' : 'Unmute' - command("Audio Volume #{value}"), name: :mute).then do + command("Audio Volume #{value}", name: :mute).then do self[:faderOutput_mute] = value end end + + def mutes(ids:, muted:, **_) + mute(muted) + end + + # --------------- # STATUS REQUESTS # --------------- From 76afcb7a0bd1383e67022951d3cd56595ac80d14 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 8 Jan 2018 13:12:10 +1100 Subject: [PATCH 0230/1752] (ibm:domino) New bookings, don't append AV Control URL to booking description --- lib/ibm/domino.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index ed4e1e19..d8e05895 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -216,10 +216,10 @@ def create_booking(current_user:, starting:, ending:, database:, room_id:, summa description = "" end - if room.support_url - description = description + "\nTo control this meeting room, click here: #{room.support_url}" - event[:description] = description - end + #if room.support_url + # description = description + "\nTo control this meeting room, click here: #{room.support_url}" + # event[:description] = description + #end event[:attendees] = Array(attendees).collect do |attendee| out_attendee = { From 436021c52b096553ffdc17e481584259452ea6da Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 8 Jan 2018 15:08:45 +1100 Subject: [PATCH 0231/1752] (cisco:tele_presence) Camera PTZ: New min/max values for CE8 --- modules/cisco/tele_presence/sx_camera_common.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/cisco/tele_presence/sx_camera_common.rb b/modules/cisco/tele_presence/sx_camera_common.rb index 30666158..bc27998f 100644 --- a/modules/cisco/tele_presence/sx_camera_common.rb +++ b/modules/cisco/tele_presence/sx_camera_common.rb @@ -13,14 +13,14 @@ def on_load self[:joy_right] = 3 self[:joy_center] = 0 - self[:pan_max] = 65535 # Right - self[:pan_min] = -65535 # Left + self[:pan_max] = 10000 # Right + self[:pan_min] = -10000 # Left self[:pan_center] = 0 - self[:tilt_max] = 65535 # UP - self[:tilt_min] = -65535 # Down + self[:tilt_max] = 2500 # UP + self[:tilt_min] = -2500 # Down self[:tilt_center] = 0 - self[:zoom_max] = 17284 # 65535 + self[:zoom_max] = 8500 # 65535 self[:zoom_min] = 0 super From fad575887dca9a3df0df82f54d2aeb6143bbcbfa Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 8 Jan 2018 16:37:25 +1100 Subject: [PATCH 0232/1752] Code cleanup --- lib/ibm/domino.rb | 53 ++++++++++------------------------------------- 1 file changed, 11 insertions(+), 42 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d8e05895..0c6ff183 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -54,8 +54,8 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { end def get_free_rooms(starting, ending) - starting, ending = convert_to_datetime(starting, ending) - # starting, ending = get_time_range(starting, ending, @timezone) + starting = convert_to_simpledate(starting) + ending = convert_to_simpledate(ending) starting = starting.utc ending = ending.utc @@ -140,9 +140,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) end def get_bookings(room_ids, date=Time.now.midnight) - if !(room_ids.class == Array) - room_ids = [room_ids] - end + room_ids = Array(room_ids) room_names = room_ids.map{|id| Orchestrator::ControlSystem.find(id).settings['name']} room_mapping = {} room_ids.each{|id| @@ -204,7 +202,8 @@ def get_bookings(room_ids, date=Time.now.midnight) def create_booking(current_user:, starting:, ending:, database:, room_id:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) room = Orchestrator::ControlSystem.find(room_id) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_simpledate(starting) + ending = convert_to_simpledate(ending) event = { :summary => summary, :class => :public, @@ -278,7 +277,8 @@ def delete_booking(database, id) def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, ending:, database:, room_email:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) room = Orchestrator::ControlSystem.find_by_email(room_email) - starting, ending = convert_to_datetime(starting, ending) + starting = convert_to_simpledate(starting) + ending = convert_to_simpledate(ending) event = { :summary => summary, :class => :public, @@ -429,10 +429,12 @@ def add_event_utc(response) events end + # Take a time object and convert to a string in the format IBM uses def to_ibm_date(time) time.strftime("%Y-%m-%dT%H:%M:%SZ") end + # Takes a date of any kind (epoch, string, time object) and returns a time object def convert_to_simpledate(date) if !(date.class == Time) if string_is_digits(date) @@ -455,37 +457,13 @@ def convert_to_simpledate(date) return date end - def convert_to_datetime(starting, ending) - if !(starting.class == Time) - if string_is_digits(starting) - - # Convert to an integer - starting = starting.to_i - ending = ending.to_i - - # If JavaScript epoch remove milliseconds - if starting.to_s.length == 13 - starting /= 1000 - ending /= 1000 - end - - # Convert to datetimes - starting = Time.at(starting) - ending = Time.at(ending) - else - starting = Time.parse(starting) - ending = Time.parse(ending) - end - end - return starting, ending - end - - + # Returns true if a string is all digits (used to check for an epoch) def string_is_digits(string) string = string.to_s string.scan(/\D/).empty? end + # Take a time object and return a hash in the format LN uses def to_utc_date(time) utctime = time.getutc { @@ -495,13 +473,4 @@ def to_utc_date(time) } end - def get_time_range(starting, ending, timezone) - return [starting, ending] if starting.is_a?(Time) - - Time.zone = timezone - start = starting.nil? ? Time.zone.today.to_time : Time.zone.parse(starting) - fin = ending.nil? ? Time.zone.tomorrow.to_time : Time.zone.parse(ending) - [start, fin] - end - end From 3c11e5e61c3d19cbb2a544ccb59849af0713ed8f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 8 Jan 2018 17:41:38 +1100 Subject: [PATCH 0233/1752] Add support URL to all bookings --- lib/ibm/domino.rb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 0c6ff183..833ea368 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -341,7 +341,8 @@ def get_attendees(path) end booking_response = add_event_utc(JSON.parse(booking_request.body))[0] - + room = get_system(booking) + support_url = room.support_url if booking_response['attendees'] Rails.logger.info "Booking has attendees" booking_response['attendees'].each{|attendee| @@ -389,10 +390,26 @@ def get_attendees(path) booking_response['start_readable'] = Time.at(booking_response['start'].to_i / 1000).to_s booking_response['end_readable'] = Time.at(booking_response['end'].to_i / 1000).to_s - + booking_response.support_url = support_url if support_url booking_response end + def get_system(booking) + @@elastic ||= Elastic.new(Orchestrator::ControlSystem) + + # Deal with a date range query + params[:q] = "\"#{booking['location']}\"" + params[:limit] = 500 + + + # Find the room with the email ID passed in + filters = {} + query = @@elastic.query(params, filters) + matching_rooms = @@elastic.search(query)[:results] + return matching_rooms[0] + + end + def add_event_utc(response) events = response['events'] From 519d03d8d0736df86cb216f10e6fc085c0b20957 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 8 Jan 2018 17:44:31 +1100 Subject: [PATCH 0234/1752] I type type good good --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 833ea368..44d9da7d 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -341,7 +341,7 @@ def get_attendees(path) end booking_response = add_event_utc(JSON.parse(booking_request.body))[0] - room = get_system(booking) + room = get_system(booking_response) support_url = room.support_url if booking_response['attendees'] Rails.logger.info "Booking has attendees" From 332843ec84c5ccc2cee8408debb768841c5befa0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 8 Jan 2018 17:49:40 +1100 Subject: [PATCH 0235/1752] Don't use params in get_system function --- lib/ibm/domino.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 44d9da7d..f343e96e 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -398,13 +398,14 @@ def get_system(booking) @@elastic ||= Elastic.new(Orchestrator::ControlSystem) # Deal with a date range query - params[:q] = "\"#{booking['location']}\"" - params[:limit] = 500 + elastic_params = {} + elastic_params[:q] = "\"#{booking['location']}\"" + elastic_params[:limit] = 500 # Find the room with the email ID passed in filters = {} - query = @@elastic.query(params, filters) + query = @@elastic.query(elastic_params, filters) matching_rooms = @@elastic.search(query)[:results] return matching_rooms[0] From b284de725912e7b1c95f574de6bb7c0922618aaf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 8 Jan 2018 17:51:32 +1100 Subject: [PATCH 0236/1752] Elastic params need to be real params not hash --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index f343e96e..150dc42f 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -398,7 +398,7 @@ def get_system(booking) @@elastic ||= Elastic.new(Orchestrator::ControlSystem) # Deal with a date range query - elastic_params = {} + elastic_params = ActionController::Parameters.new({}) elastic_params[:q] = "\"#{booking['location']}\"" elastic_params[:limit] = 500 From 04c9c5b1f2ed8504001967f25deaa3eeee020dd8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 8 Jan 2018 17:53:54 +1100 Subject: [PATCH 0237/1752] Use ruby code, not javascript --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 150dc42f..e1957019 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -390,7 +390,7 @@ def get_attendees(path) booking_response['start_readable'] = Time.at(booking_response['start'].to_i / 1000).to_s booking_response['end_readable'] = Time.at(booking_response['end'].to_i / 1000).to_s - booking_response.support_url = support_url if support_url + booking_response['support_url'] = support_url if support_url booking_response end From 450f1f6d09fed318191107ac859f1bd82418f06c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 8 Jan 2018 17:56:49 +1100 Subject: [PATCH 0238/1752] If room is nil then no support URL --- lib/ibm/domino.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index e1957019..9e4e6aa5 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -342,7 +342,13 @@ def get_attendees(path) booking_response = add_event_utc(JSON.parse(booking_request.body))[0] room = get_system(booking_response) - support_url = room.support_url + + if room + support_url = room.support_url + else + support_url = nil + end + if booking_response['attendees'] Rails.logger.info "Booking has attendees" booking_response['attendees'].each{|attendee| From 5646a49d273827f9a12635cf3b4c55c461129bde Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 9 Jan 2018 10:47:18 +1100 Subject: [PATCH 0239/1752] Fix date parsing issue --- lib/ibm/domino.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 9e4e6aa5..0d59cbaa 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -467,9 +467,8 @@ def convert_to_simpledate(date) date = date.to_i # If JavaScript epoch remove milliseconds - if starting.to_s.length == 13 - starting /= 1000 - ending /= 1000 + if date.to_s.length == 13 + date /= 1000 end # Convert to datetimes From fa5e9e7d36ee19da72fddc3132b2baec501ea933 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 9 Jan 2018 10:53:04 +1100 Subject: [PATCH 0240/1752] Minor code cleanup --- lib/ibm/domino.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 0d59cbaa..f2ce23d6 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -88,7 +88,6 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) if !date.nil? # Make date a date object from epoch or parsed text date = convert_to_simpledate(date) - starting = to_ibm_date(date) ending = to_ibm_date(date.tomorrow) else @@ -122,7 +121,6 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) end db_uri = URI.parse(database) base_domain = db_uri.scheme + "://" + db_uri.host - Rails.logger.info "Requesting to #{base_domain + event['href']}" full_event = get_attendees(base_domain + event['href']) if full_event == false full_event = event @@ -146,8 +144,6 @@ def get_bookings(room_ids, date=Time.now.midnight) room_ids.each{|id| room_mapping[Orchestrator::ControlSystem.find(id).settings['name']] = id } - # room = Orchestrator::ControlSystem.find(room_id) - # room_name = room.settings['name'] # The domino API takes a StartKey and UntilKey # We will only ever need one days worth of bookings @@ -350,7 +346,6 @@ def get_attendees(path) end if booking_response['attendees'] - Rails.logger.info "Booking has attendees" booking_response['attendees'].each{|attendee| if attendee.key?('userType') && attendee['userType'] == 'room' booking_response['room_email'] = attendee['email'] From 040151ffb0dd3a7e3d4d63433567e0ceeb7c19a3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 10 Jan 2018 11:07:42 +1100 Subject: [PATCH 0241/1752] Change get_bookings to use a range if needed --- lib/ibm/domino.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index f2ce23d6..0b4cda13 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -137,7 +137,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) raise "\n\n#{e.message}\n#{e.backtrace.join("\n")}\n\n" end - def get_bookings(room_ids, date=Time.now.midnight) + def get_bookings(room_ids, date=Time.now.midnight, ending=nil) room_ids = Array(room_ids) room_names = room_ids.map{|id| Orchestrator::ControlSystem.find(id).settings['name']} room_mapping = {} @@ -152,9 +152,14 @@ def get_bookings(room_ids, date=Time.now.midnight) # Make date a date object from epoch or parsed text date = convert_to_simpledate(date) - starting = date.yesterday.strftime("%Y%m%d") - ending = date.strftime("%Y%m%d") + + if ending + ending = convert_to_simpledate(ending).strftime("%Y%m%d") + else + ending = date.strftime("%Y%m%d") + end + # Set count to max query = { From d2d4f59a92406dadbf1dd4d0a5d755fd032f1ecd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 10 Jan 2018 12:04:09 +1100 Subject: [PATCH 0242/1752] Fix simple users_bookings request --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 0b4cda13..c2d63262 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -114,8 +114,8 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) events.each{ |event| if simple full_events.push({ - start: (Time.parse(event['start']['date']+'T'+event['start']['time']+'+0000').utc.to_i.to_s + "000").to_i, - end: (Time.parse(event['end']['date']+'T'+event['end']['time']+'+0000').utc.to_i.to_s + "000").to_i + start: event['start'], + end: event['start'] }) next end From aed8f0b7689e189638afc925c95735302e45655b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 11 Jan 2018 16:26:33 +1100 Subject: [PATCH 0243/1752] Deal with inviatations in get_users_bookings --- lib/ibm/domino.rb | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index c2d63262..9c0cc255 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -28,7 +28,7 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { @headers.merge(headers) if headers if full_path - if full_path.include?('/api/calendar/events') + if full_path.include?('/api/calendar/') uri = URI.parse(full_path) else uri = URI.parse(full_path + '/api/calendar/events') @@ -100,28 +100,56 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) since: starting } + events = [] + # First request is to the user's database request = domino_request('get', nil, nil, query, nil, database).value if [200,201,204].include?(request.status) if request.body != '' events = add_event_utc(JSON.parse(request.body)) - else - events = [] end else return nil end + + query = { + since: starting + } + + invite_db = database + '/api/calendar/invitations' + request = domino_request('get', nil, nil, query, nil, invite_db).value + if [200,201,204].include?(request.status) + if request.body != '' + events += JSON.parse(request.body)['events'] + end + else + return nil + end + full_events = [] events.each{ |event| + db_uri = URI.parse(database) + base_domain = db_uri.scheme + "://" + db_uri.host + if simple - full_events.push({ - start: event['start'], - end: event['start'] - }) + # If we're dealing with an invite we must try and resolve the href + if !event.key?('start') + invite = get_attendees(base_domain + event['href']) + if invite + full_events.push({ + start: invite['start'], + end: invite['end'] + }) + end + else + full_events.push({ + start: event['start'], + end: event['end'] + }) + end next end - db_uri = URI.parse(database) - base_domain = db_uri.scheme + "://" + db_uri.host full_event = get_attendees(base_domain + event['href']) + if full_event == false full_event = event full_event['organizer'] = {} From eda932c4756bcb3c49f9de1102929cc42a4e97fe Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 11 Jan 2018 16:47:57 +1100 Subject: [PATCH 0244/1752] Ensure returned invites is not nil --- lib/ibm/domino.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 9c0cc255..90ad19b5 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -119,7 +119,8 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) request = domino_request('get', nil, nil, query, nil, invite_db).value if [200,201,204].include?(request.status) if request.body != '' - events += JSON.parse(request.body)['events'] + invites = JSON.parse(request.body)['events'] + events += invites if !invites.nil? end else return nil From 34cbe6036c8fba85a5ba66516d4b24ab273c78c9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 12 Jan 2018 14:35:47 +1100 Subject: [PATCH 0245/1752] Only check last_modified if that key exists --- lib/ibm/domino.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 90ad19b5..e7245df1 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -74,7 +74,11 @@ def get_free_rooms(starting, ending) def get_users_bookings_created_today(database) user_bookings = get_users_bookings(database, nil, nil, 1) user_bookings.select!{ |booking| - booking['last-modified'] && Time.now.midnight < booking['last-modified'] && Time.now.tomorrow.midnight > booking['last-modified'] + if booking.key?('last-modified') + booking['last-modified'] && Time.now.midnight < booking['last-modified'] && Time.now.tomorrow.midnight > booking['last-modified'] + else + false + end } user_bookings From bba05233d6d248062d9c270dd5f7f01344980e0a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 13 Jan 2018 01:14:37 +1100 Subject: [PATCH 0246/1752] Send milliseconds in start and end --- lib/ibm/domino.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index e7245df1..849b9254 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -222,8 +222,7 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil) # Check if room is in our list if room_names.include?(domino_room_name) new_booking = { - start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i, - end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i, + end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i * 1000, summary: booking['entrydata'][5]['text']['0'], organizer: booking['entrydata'][3]['text']['0'] } From 40fba7aedd28a83bdbdc1d054e1ea04025ed2361 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 13 Jan 2018 01:14:49 +1100 Subject: [PATCH 0247/1752] Fix description in create --- lib/ibm/domino.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 849b9254..691322cf 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -248,10 +248,10 @@ def create_booking(current_user:, starting:, ending:, database:, room_id:, summa description = "" end - #if room.support_url - # description = description + "\nTo control this meeting room, click here: #{room.support_url}" - # event[:description] = description - #end + # if room.support_url + # description = description + "\nTo control this meeting room, click here: #{room.support_url}" + event[:description] = description + # end event[:attendees] = Array(attendees).collect do |attendee| out_attendee = { From 6b51c14cebfc0c664afbb80b0a771711931f34cb Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 13 Jan 2018 01:15:40 +1100 Subject: [PATCH 0248/1752] Fix start param --- lib/ibm/domino.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 691322cf..c0419385 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -222,6 +222,7 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil) # Check if room is in our list if room_names.include?(domino_room_name) new_booking = { + start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i * 1000, end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i * 1000, summary: booking['entrydata'][5]['text']['0'], organizer: booking['entrydata'][3]['text']['0'] From 1f24b98f928e1e0b9db8ebd0a028b4041b8f0623 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 13 Jan 2018 02:13:34 +1100 Subject: [PATCH 0249/1752] Change description lines in edit booking --- lib/ibm/domino.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index c0419385..3555843f 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -326,10 +326,10 @@ def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, en description = "" end - if room.support_url - description = description + "\nTo control this meeting room, click here: #{room.support_url}" + # if room.support_url + # description = description + "\nTo control this meeting room, click here: #{room.support_url}" event[:description] = description - end + # end event[:attendees] = Array(attendees).collect do |attendee| out_attendee = { From dc8b70a34f8cbdf840f915cab3f07f66fc888eea Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 17 Jan 2018 11:55:35 +1100 Subject: [PATCH 0250/1752] Add method to get a single booking --- lib/ibm/domino.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 3555843f..84ce9154 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -170,6 +170,15 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) raise "\n\n#{e.message}\n#{e.backtrace.join("\n")}\n\n" end + def get_booking(path) + booking_request = domino_request('get',nil,nil,nil,nil,path).value + if ![200,201,204].include?(booking_request.status) + Rails.logger.info "Didn't get a 20X response from meeting detail requst." + return false + end + return JSON.parse(booking_request.body)['events'][0] + end + def get_bookings(room_ids, date=Time.now.midnight, ending=nil) room_ids = Array(room_ids) room_names = room_ids.map{|id| Orchestrator::ControlSystem.find(id).settings['name']} @@ -304,6 +313,8 @@ def create_booking(current_user:, starting:, ending:, database:, room_id:, summa request end + + def delete_booking(database, id) request = domino_request('delete', nil, nil, nil, nil, "#{database}/api/calendar/events/#{id}").value end From 642df2062a8377d046a220a5ddc8717c9e8c5892 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 17 Jan 2018 12:05:46 +1100 Subject: [PATCH 0251/1752] Add room's ID into user's bookings --- lib/ibm/domino.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 84ce9154..95363aa3 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -389,8 +389,10 @@ def get_attendees(path) room = get_system(booking_response) if room + room_id = room.id support_url = room.support_url else + room_id = nil support_url = nil end @@ -441,6 +443,7 @@ def get_attendees(path) booking_response['start_readable'] = Time.at(booking_response['start'].to_i / 1000).to_s booking_response['end_readable'] = Time.at(booking_response['end'].to_i / 1000).to_s booking_response['support_url'] = support_url if support_url + booking_response['room_id'] = room_id if room_id booking_response end From 543430b54ad5d684d6dfd9834a79b46778ada1a5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 17 Jan 2018 12:25:42 +1100 Subject: [PATCH 0252/1752] Send full FQDN to request, not just endpoint --- lib/ibm/domino.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 95363aa3..c3a5649c 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -170,8 +170,11 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) raise "\n\n#{e.message}\n#{e.backtrace.join("\n")}\n\n" end - def get_booking(path) - booking_request = domino_request('get',nil,nil,nil,nil,path).value + def get_booking(path, database) + db_uri = URI.parse(database) + base_domain = db_uri.scheme + "://" + db_uri.host + event_path = base_domain + path + booking_request = domino_request('get',nil,nil,nil,nil,event_path).value if ![200,201,204].include?(booking_request.status) Rails.logger.info "Didn't get a 20X response from meeting detail requst." return false From cbcb6926ba93d651d09b5e66d070e6dfbe6f26b7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 17 Jan 2018 12:47:37 +1100 Subject: [PATCH 0253/1752] Only return users events if not declined --- lib/ibm/domino.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index c3a5649c..d46f5e10 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -161,7 +161,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) full_event['description'] = '' full_event['attendees'] = [] end - full_events.push(full_event) + full_events.push(full_event) if !full_event['declined'] } full_events @@ -443,13 +443,29 @@ def get_attendees(path) booking_response['organizer'] = organizer end + declined = !(is_accepted(booking_response)) + booking_response['start_readable'] = Time.at(booking_response['start'].to_i / 1000).to_s booking_response['end_readable'] = Time.at(booking_response['end'].to_i / 1000).to_s booking_response['support_url'] = support_url if support_url booking_response['room_id'] = room_id if room_id + booking_response['declined'] = true if declined booking_response end + def is_accepted(event) + accepted = true + event['attendees'].each{|attendee| + if attendee.key?('userType') && attendee['userType'] == 'room' + if attendee.key?('status') && attendee['status'] == 'declined' + accepted = false + end + end + } + return accepted + end + + def get_system(booking) @@elastic ||= Elastic.new(Orchestrator::ControlSystem) From bd0b562011060270844b00843aecd23c4de89cb7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 17 Jan 2018 13:00:16 +1100 Subject: [PATCH 0254/1752] Put check for room attendee before we remove that attendee --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d46f5e10..b35b2f27 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -400,6 +400,8 @@ def get_attendees(path) end if booking_response['attendees'] + + declined = !(is_accepted(booking_response)) booking_response['attendees'].each{|attendee| if attendee.key?('userType') && attendee['userType'] == 'room' booking_response['room_email'] = attendee['email'] @@ -443,8 +445,6 @@ def get_attendees(path) booking_response['organizer'] = organizer end - declined = !(is_accepted(booking_response)) - booking_response['start_readable'] = Time.at(booking_response['start'].to_i / 1000).to_s booking_response['end_readable'] = Time.at(booking_response['end'].to_i / 1000).to_s booking_response['support_url'] = support_url if support_url From 149b2bb291755131045b47e2dc31567ca7f0da71 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 17 Jan 2018 14:39:56 +1100 Subject: [PATCH 0255/1752] Do not remote declined bookings --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index b35b2f27..2515bd8b 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -161,7 +161,7 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) full_event['description'] = '' full_event['attendees'] = [] end - full_events.push(full_event) if !full_event['declined'] + full_events.push(full_event) } full_events From 75ccbb10da2e5a5432ac3a386757c534d7b2a099 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 19 Jan 2018 12:30:30 +1100 Subject: [PATCH 0256/1752] Query in UTC time --- lib/ibm/domino.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 2515bd8b..331b28b4 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -91,12 +91,12 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) if !date.nil? # Make date a date object from epoch or parsed text - date = convert_to_simpledate(date) + date = convert_to_simpledate(date).utc starting = to_ibm_date(date) ending = to_ibm_date(date.tomorrow) else - starting = to_ibm_date(Time.now.midnight) - ending = to_ibm_date((Time.now.midnight + weeks.week)) + starting = to_ibm_date(Time.now.midnight.utc) + ending = to_ibm_date((Time.now.midnight.utc + weeks.week)) end query = { From 10ff4ffabd40d798f49426415f685912fdfe1d57 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 19 Jan 2018 12:31:14 +1100 Subject: [PATCH 0257/1752] Query in UTC time --- lib/ibm/domino.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 2515bd8b..331b28b4 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -91,12 +91,12 @@ def get_users_bookings(database, date=nil, simple=nil, weeks=1) if !date.nil? # Make date a date object from epoch or parsed text - date = convert_to_simpledate(date) + date = convert_to_simpledate(date).utc starting = to_ibm_date(date) ending = to_ibm_date(date.tomorrow) else - starting = to_ibm_date(Time.now.midnight) - ending = to_ibm_date((Time.now.midnight + weeks.week)) + starting = to_ibm_date(Time.now.midnight.utc) + ending = to_ibm_date((Time.now.midnight.utc + weeks.week)) end query = { From 9da88d5ac4515fe7705e07fceecf570292c24185 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 19 Jan 2018 13:32:29 +1100 Subject: [PATCH 0258/1752] Remove Invitations from events --- lib/ibm/domino.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 331b28b4..e3d54f6c 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -488,6 +488,9 @@ def add_event_utc(response) events = response['events'] response.key?('timezones') ? timezones = response['timezones'] : timezones = nil + events.reject!{|event| + event['summary'][0..10] == "Invitation:" + } events.each{ |event| # If the event has no time, set time to "00:00:00" if !event['start'].key?('time') From 761b9c3b51128f1aa64b7b5cff660f017aec5f12 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 22 Jan 2018 12:22:55 +1100 Subject: [PATCH 0259/1752] Check for summary key before removing invites --- lib/ibm/domino.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index e3d54f6c..4231ab11 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -489,7 +489,9 @@ def add_event_utc(response) response.key?('timezones') ? timezones = response['timezones'] : timezones = nil events.reject!{|event| - event['summary'][0..10] == "Invitation:" + if event.key?('summary') + event['summary'][0..10] == "Invitation:" + end } events.each{ |event| # If the event has no time, set time to "00:00:00" From 090f861742ae64cf49a414f5c8aaefadf2f773c9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 24 Jan 2018 13:06:09 +1100 Subject: [PATCH 0260/1752] Add check for no end time in my bookings --- lib/ibm/domino.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 4231ab11..404a88b5 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -498,6 +498,9 @@ def add_event_utc(response) if !event['start'].key?('time') start_time = "00:00:00" end_time = "00:00:00" + elsif !event['end'].key?('time') + start_time = event['start']['time'] + end_time = event['start']['time'] else start_time = event['start']['time'] end_time = event['end']['time'] From 6f3636433cb2e7a5b2bc6b1ecbe443ba5064a164 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 24 Jan 2018 13:16:30 +1100 Subject: [PATCH 0261/1752] Check for end key, not just time in end key --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 404a88b5..9a3873db 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -498,7 +498,7 @@ def add_event_utc(response) if !event['start'].key?('time') start_time = "00:00:00" end_time = "00:00:00" - elsif !event['end'].key?('time') + elsif !event.key?('end') start_time = event['start']['time'] end_time = event['start']['time'] else From 8ae7b0faddb6db700a5adb189c91b0ff1508acdd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 24 Jan 2018 13:20:31 +1100 Subject: [PATCH 0262/1752] Fix more logic dependant on end key --- lib/ibm/domino.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 9a3873db..d684679d 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -498,12 +498,18 @@ def add_event_utc(response) if !event['start'].key?('time') start_time = "00:00:00" end_time = "00:00:00" + start_date = event['start']['date'] + end_date = event['end']['date'] elsif !event.key?('end') start_time = event['start']['time'] end_time = event['start']['time'] + start_date = event['start']['date'] + end_date = event['start']['date'] else start_time = event['start']['time'] end_time = event['end']['time'] + start_date = event['start']['date'] + end_date = event['end']['date'] end # If the event start has a tzid field, use the timezones hash @@ -515,10 +521,10 @@ def add_event_utc(response) offset = "+0000" end - start_timestring = "#{event['start']['date']}T#{start_time}#{offset}" + start_timestring = "#{start_date}T#{start_time}#{offset}" start_utc = (Time.parse(start_timestring).utc.to_i.to_s + "000").to_i - end_timestring = "#{event['end']['date']}T#{end_time}#{offset}" + end_timestring = "#{end_date}T#{end_time}#{offset}" end_utc = (Time.parse(end_timestring).utc.to_i.to_s + "000").to_i event['start'] = start_utc From 5cea39b80b97706143df84955b6a5f9577fe9a6a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 25 Jan 2018 10:57:09 +1100 Subject: [PATCH 0263/1752] Set location to unassigned if booking declined --- lib/ibm/domino.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index d684679d..7ae6b4b8 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -450,6 +450,7 @@ def get_attendees(path) booking_response['support_url'] = support_url if support_url booking_response['room_id'] = room_id if room_id booking_response['declined'] = true if declined + booking_response['location'] = "unassigned" if declined booking_response end From 9a92f210232d636927d052d9b113302a14e89fa5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 25 Jan 2018 12:04:06 +1100 Subject: [PATCH 0264/1752] Remove room ID and email if declined --- lib/ibm/domino.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 7ae6b4b8..601fd0e1 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -451,6 +451,8 @@ def get_attendees(path) booking_response['room_id'] = room_id if room_id booking_response['declined'] = true if declined booking_response['location'] = "unassigned" if declined + booking_response['room_email'] = nil if declined + booking_response['room_id'] = nil if declined booking_response end From ddc1b429caba83734f433fdf3dd345e107bef2a4 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 25 Jan 2018 17:23:24 +1100 Subject: [PATCH 0265/1752] Use capital "U" for unassigned rooms --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 601fd0e1..4bf862f7 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -450,7 +450,7 @@ def get_attendees(path) booking_response['support_url'] = support_url if support_url booking_response['room_id'] = room_id if room_id booking_response['declined'] = true if declined - booking_response['location'] = "unassigned" if declined + booking_response['location'] = "Unassigned" if declined booking_response['room_email'] = nil if declined booking_response['room_id'] = nil if declined booking_response From 988487e41769ff5c1177e647c841415084207b0e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 2 Feb 2018 11:38:57 +1100 Subject: [PATCH 0266/1752] Send booking ID and database if requested to cancel bookings --- lib/ibm/domino.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 4bf862f7..6b2af333 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -182,7 +182,7 @@ def get_booking(path, database) return JSON.parse(booking_request.body)['events'][0] end - def get_bookings(room_ids, date=Time.now.midnight, ending=nil) + def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) room_ids = Array(room_ids) room_names = room_ids.map{|id| Orchestrator::ControlSystem.find(id).settings['name']} room_mapping = {} @@ -233,12 +233,27 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil) # Check if room is in our list if room_names.include?(domino_room_name) + organizer = booking['entrydata'][3]['text']['0'] new_booking = { start: Time.parse(booking['entrydata'][0]['datetime']['0']).to_i * 1000, end: Time.parse(booking['entrydata'][1]['datetime']['0']).to_i * 1000, summary: booking['entrydata'][5]['text']['0'], - organizer: booking['entrydata'][3]['text']['0'] + organizer: organizer } + if full_data + new_booking[:booking_id] = booking['entrydata'][9]['text']['0'] + uat_server = UV::HttpEndpoint.new(ENV['ALL_USERS_DOMAIN']) + all_users = uat_server.get(path: ENV['ALL_USERS_PATH']).value + all_users = JSON.parse(all_users.body)['staff'] + staff_db = nil + all_users.each{|u| + if u['StaffLNMail'] == organizer + staff_db = "https://#{u['ServerName']}/#{u['MailboxPath']}.nsf" + break + end + } + new_booking[:database] = staff_db + end rooms_bookings[room_mapping[domino_room_name]].push(new_booking) end } From 52abd98741fbaac2eac298436d991545a8b7768e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Feb 2018 18:26:56 +1100 Subject: [PATCH 0267/1752] Return the path not booking ID --- lib/ibm/domino.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 6b2af333..a973cc01 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -241,7 +241,7 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) organizer: organizer } if full_data - new_booking[:booking_id] = booking['entrydata'][9]['text']['0'] + booking_id = booking['entrydata'][9]['text']['0'] uat_server = UV::HttpEndpoint.new(ENV['ALL_USERS_DOMAIN']) all_users = uat_server.get(path: ENV['ALL_USERS_PATH']).value all_users = JSON.parse(all_users.body)['staff'] @@ -252,7 +252,10 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) break end } + staff_db_uri = URI.parse(staff_db) + path = "#{staff_db_uri.path}/api/calendar/events#{booking_id}" new_booking[:database] = staff_db + new_booking[:path] = path end rooms_bookings[room_mapping[domino_room_name]].push(new_booking) end From dde85c05d950eeb7664b4d0a70e6c09fa0cf624c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Feb 2018 18:42:29 +1100 Subject: [PATCH 0268/1752] Forgot a little slashie boi --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index a973cc01..b212c79e 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -253,7 +253,7 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) end } staff_db_uri = URI.parse(staff_db) - path = "#{staff_db_uri.path}/api/calendar/events#{booking_id}" + path = "#{staff_db_uri.path}/api/calendar/events/#{booking_id}" new_booking[:database] = staff_db new_booking[:path] = path end From 38e7c74ce93d0c0941328229cba20c9afe6ba1a7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Feb 2018 18:49:16 +1100 Subject: [PATCH 0269/1752] Remove changed fields in edit --- lib/ibm/domino.rb | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index b212c79e..804a347c 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -341,7 +341,7 @@ def delete_booking(database, id) end - def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, ending:, database:, room_email:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) + def edit_booking(id:, current_user: nil, starting:, ending:, database:, room_email:, summary:, description: nil, organizer:, attendees: [], timezone: @timezone, **opts) room = Orchestrator::ControlSystem.find_by_email(room_email) starting = convert_to_simpledate(starting) ending = convert_to_simpledate(ending) @@ -374,16 +374,28 @@ def edit_booking(time_changed:, room_changed:, id:, current_user:, starting:, en out_attendee end - # Organizer will not change - event[:organizer] = { - email: current_user.email - } - event[:attendees].push({ - "role":"chair", - "status":"accepted", - "rsvp":false, - "email": current_user.email - }) + if current_user.nil? + event[:organizer] = { + email: organizer + } + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "email": organizer + }) + else + # Organizer will not change + event[:organizer] = { + email: current_user.email + } + event[:attendees].push({ + "role":"chair", + "status":"accepted", + "rsvp":false, + "email": current_user.email + }) + end # Add the room as an attendee event[:attendees].push({ From eaa7c27aebbad9cd7963ac5d45aad2a0fe737865 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Feb 2018 19:19:00 +1100 Subject: [PATCH 0270/1752] Fuck off the href field whern editing --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 804a347c..c5755600 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -350,7 +350,7 @@ def edit_booking(id:, current_user: nil, starting:, ending:, database:, room_ema :class => :public, :start => to_utc_date(starting), :end => to_utc_date(ending), - :href => "/#{database}/api/calendar/events/#{id}", + # :href => "/#{database}/api/calendar/events/#{id}", :id => id } From 289b351ba28188bee556b8789575da24f6beb67b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 6 Feb 2018 13:50:44 +1100 Subject: [PATCH 0271/1752] Only make one request to all-users.json --- lib/ibm/domino.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index c5755600..713542b8 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -220,6 +220,12 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) request = domino_request('get', "/RRDB.nsf/93FDE1776546DEEB482581E7000B27FF", nil, query) response = request.value + if full_data + uat_server = UV::HttpEndpoint.new(ENV['ALL_USERS_DOMAIN']) + all_users = uat_server.get(path: ENV['ALL_USERS_PATH']).value + all_users = JSON.parse(all_users.body)['staff'] + end + # Go through the returned bookings and add to output array rooms_bookings = {} room_ids.each{|id| @@ -242,9 +248,6 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) } if full_data booking_id = booking['entrydata'][9]['text']['0'] - uat_server = UV::HttpEndpoint.new(ENV['ALL_USERS_DOMAIN']) - all_users = uat_server.get(path: ENV['ALL_USERS_PATH']).value - all_users = JSON.parse(all_users.body)['staff'] staff_db = nil all_users.each{|u| if u['StaffLNMail'] == organizer From 35671386997026cc234e962b9d2f6aa150ec0225 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 6 Feb 2018 14:32:17 +1100 Subject: [PATCH 0272/1752] Pass in a logger --- lib/ibm/domino.rb | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 713542b8..634a2259 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -9,10 +9,12 @@ def initialize( password:, auth_hash:, domain:, - timezone: + timezone:, + logger: logger ) @domain = domain @timeone = timezone + @logger = logger @headers = { 'Authorization' => "Basic #{auth_hash}", 'Content-Type' => 'application/json' @@ -43,12 +45,12 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" end - Rails.logger.info "------------NEW DOMINO REQUEST--------------" - Rails.logger.info domino_path - Rails.logger.info query - Rails.logger.info data - Rails.logger.info @headers - Rails.logger.info "--------------------------------------------" + logger.info "------------NEW DOMINO REQUEST--------------" + logger.info domino_path + logger.info query + logger.info data + logger.info @headers + logger.info "--------------------------------------------" response = domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) end @@ -176,7 +178,7 @@ def get_booking(path, database) event_path = base_domain + path booking_request = domino_request('get',nil,nil,nil,nil,event_path).value if ![200,201,204].include?(booking_request.status) - Rails.logger.info "Didn't get a 20X response from meeting detail requst." + logger.info "Didn't get a 20X response from meeting detail requst." return false end return JSON.parse(booking_request.body)['events'][0] @@ -232,6 +234,9 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) rooms_bookings[id] = [] } bookings = JSON.parse(response.body)['viewentry'] || [] + @logger.info "Checking room names:" + @logger.info room_names + start_timer = Time.now bookings.each{ |booking| # Get the room name @@ -263,6 +268,8 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) rooms_bookings[room_mapping[domino_room_name]].push(new_booking) end } + end_timer = Time.now + @logger.info "Total time #{end_timer - start_timer}" rooms_bookings end @@ -417,7 +424,7 @@ def edit_booking(id:, current_user: nil, starting:, ending:, database:, room_ema def get_attendees(path) booking_request = domino_request('get',nil,nil,nil,nil,path).value if ![200,201,204].include?(booking_request.status) - Rails.logger.info "Didn't get a 20X response from meeting detail requst." + logger.info "Didn't get a 20X response from meeting detail requst." return false end From 3be688ac5cee713e2bca5e97b8d584de8e8a0df1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 6 Feb 2018 14:38:36 +1100 Subject: [PATCH 0273/1752] Fix syntax for logging --- lib/ibm/domino.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 634a2259..ef5144f4 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -10,7 +10,7 @@ def initialize( auth_hash:, domain:, timezone:, - logger: logger + logger: Rails.logger ) @domain = domain @timeone = timezone @@ -45,12 +45,12 @@ def domino_request(request_method, endpoint, data = nil, query = {}, headers = { domino_path = "#{ENV['DOMINO_DOMAIN']}#{endpoint}" end - logger.info "------------NEW DOMINO REQUEST--------------" - logger.info domino_path - logger.info query - logger.info data - logger.info @headers - logger.info "--------------------------------------------" + @logger.info "------------NEW DOMINO REQUEST--------------" + @logger.info domino_path + @logger.info query + @logger.info data + @logger.info @headers + @logger.info "--------------------------------------------" response = domino_api.__send__(request_method, path: domino_path, headers: @headers, body: data, query: query) end @@ -178,7 +178,7 @@ def get_booking(path, database) event_path = base_domain + path booking_request = domino_request('get',nil,nil,nil,nil,event_path).value if ![200,201,204].include?(booking_request.status) - logger.info "Didn't get a 20X response from meeting detail requst." + @logger.info "Didn't get a 20X response from meeting detail requst." return false end return JSON.parse(booking_request.body)['events'][0] @@ -424,7 +424,7 @@ def edit_booking(id:, current_user: nil, starting:, ending:, database:, room_ema def get_attendees(path) booking_request = domino_request('get',nil,nil,nil,nil,path).value if ![200,201,204].include?(booking_request.status) - logger.info "Didn't get a 20X response from meeting detail requst." + @logger.info "Didn't get a 20X response from meeting detail requst." return false end From e86073e5b906280b965451621f1dcbadf91d28ac Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 6 Feb 2018 14:46:12 +1100 Subject: [PATCH 0274/1752] Don't use Rails logger, use ruby logger --- lib/ibm/domino.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index ef5144f4..3ea178a0 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -1,6 +1,7 @@ # Reference: https://www.ibm.com/developerworks/lotus/library/ls-Domino_URL_cheat_sheet/ require 'active_support/time' +require 'logger' module Ibm; end class Ibm::Domino @@ -10,7 +11,7 @@ def initialize( auth_hash:, domain:, timezone:, - logger: Rails.logger + logger: Logger.new(STDOUT) ) @domain = domain @timeone = timezone From 3086d2eef59d023b4b9b96032c66c17a7794c2e9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 6 Feb 2018 15:49:32 +1100 Subject: [PATCH 0275/1752] Add keepalive and ensure workflow notices are sent --- lib/ibm/domino.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 3ea178a0..91932c18 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -20,7 +20,7 @@ def initialize( 'Authorization' => "Basic #{auth_hash}", 'Content-Type' => 'application/json' } - @domino_api = UV::HttpEndpoint.new(@domain, {inactivity_timeout: 25000}) + @domino_api = UV::HttpEndpoint.new(@domain, {inactivity_timeout: 25000, keepalive: false}) end def domino_request(request_method, endpoint, data = nil, query = {}, headers = {}, full_path = nil) @@ -418,7 +418,7 @@ def edit_booking(id:, current_user: nil, starting:, ending:, database:, room_ema }) - request = domino_request('put', nil, {events: [event]}, nil, nil, database + "/api/calendar/events/#{id}").value + request = domino_request('put', nil, {events: [event]}, {workflow: true}, nil, database + "/api/calendar/events/#{id}").value request end From 3ea63174c2d17830923e620abbc1ac013970b41f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 12 Feb 2018 16:29:34 +1100 Subject: [PATCH 0276/1752] Add microsoft office365 library --- lib/microsoft/office.rb | 271 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 lib/microsoft/office.rb diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb new file mode 100644 index 00000000..db1211ef --- /dev/null +++ b/lib/microsoft/office.rb @@ -0,0 +1,271 @@ +require 'active_support/time' +require 'logger' +module Microsoft; end + +class Microsoft::Office + TIMEZONE_MAPPING = { + "Sydney": "AUS Eastern Standard Time" + } + def initialize( + client_id:, + client_secret:, + app_site:, + app_token_url:, + app_scope:, + graph_domain:, + service_account_email:, + logger: Rails.logger + ) + @client_id = client_id + @client_secret = client_secret + @app_site = app_site + @app_token_url = app_token_url + @app_scope = app_scope + @graph_domain = graph_domain + @service_account_email = service_account_email + @graph_client ||= OAuth2::Client.new( + @client_id, + @client_secret, + {:site => @app_site, :token_url => @app_token_url} + ) + end + + def graph_token + @graph_token ||= @graph_client.client_credentials.get_token({ + :scope => @app_scope + }).token + end + + def graph_request(request_method, endpoint, data = nil, query = {}, headers = {}) + + # Convert our request method to a symbol and our data to a JSON string + request_method = request_method.to_sym + data = data.to_json if !data.nil? && data.class != String + + # Set our unchanging headers + headers['Authorization'] = "Bearer #{graph_token}" + headers['Content-Type'] = ENV['GRAPH_CONTENT_TYPE'] + headers['Prefer'] = ENV['GRAPH_PREFER'] + + graph_path = "#{@graph_domain}#{endpoint}" + + log_graph_request(request_method, data, query, headers, graph_path) + + graph_api = UV::HttpEndpoint.new(@graph_domain, {inactivity_timeout: 25000}) + response = graph_api.__send__(request_method, path: graph_path, headers: headers, body: data, query: query) + end + + def log_graph_request(request_method, data, query, headers, graph_path) + STDERR.puts "--------------NEW GRAPH REQUEST------------" + STDERR.puts "#{request_method} to #{graph_path}" + STDERR.puts data if data + STDERR.puts query if query + STDERR.puts headers + STDERR.puts '--------------------------------------------' + STDERR.flush + end + + + def get_users + endpoint = "/v1.0/users" + user_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] + end + + def get_user(user_id) + endpoint = "/v1.0/users/#{user_id}" + user_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] + end + + def get_rooms + endpoint = "/beta/users/#{@service_account_email}/findRooms" + room_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] + end + + def get_room(room_id) + endpoint = "/beta/users/#{@service_account_email}/findRooms" + room_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] + room_response.select! { |room| room['email'] == room_id } + end + + def get_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) + # Allow passing in epoch, time string or ruby Time class + start_param = ensure_ruby_date(start_param).iso8601 + end_param = ensure_ruby_date(end_param).iso8601 + + # Array of all bookings within our period + recurring_bookings = get_recurring_bookings_by_user(user_id, start_param, end_param) + + endpoint = "/v1.0/users/#{user_id}/events" + + query_hash = {} + query_hash['$top'] = "200" + + # Build our query to only get bookings within our datetimes + if not start_param.nil? + query_hash['$filter'.to_sym] = "(Start/DateTime le '#{start_param}' and End/DateTime ge '#{start_param}') or (Start/DateTime ge '#{start_param}' and Start/DateTime le '#{end_param}')" + end + + bookings_response = graph_request('get', endpoint, nil, query_hash).value + bookings = JSON.parse(bookings_response.body)['value'] + bookings.concat recurring_bookings + end + + def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) + # Allow passing in epoch, time string or ruby Time class + start_param = ensure_ruby_date(start_param).iso8601 + end_param = ensure_ruby_date(end_param).iso8601 + + recurring_endpoint = "/v1.0/users/#{user_id}/calendarView" + + # Build our query to only get bookings within our datetimes + query_hash = {} + query_hash['$top'] = "200" + + if not start_param.nil? + query_hash['startDateTime'] = start_param + query_hash['endDateTime'] = end_param + end + + recurring_response = graph_request('get', recurring_endpoint, nil, query_hash).value + recurring_bookings = JSON.parse(recurring_response.body)['value'] + end + + def get_bookings_by_room(room_id, start_param=Time.now, end_param=(Time.now + 1.week)) + return get_bookings_by_user(room_id, start_param, end_param) + end + + + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney') + description = String(description) + attendees = Array(attendees) + + # Get our room + room = Orchestrator::ControlSystem.find(room_id) + + # Set our endpoint with the email + endpoint = "/v1.0/users/#{room.email}/events" + + # Ensure our start and end params are Ruby dates and format them in Graph format + start_param = ensure_ruby_date(start_param).in_time_zone(timezone).iso8601.split("+")[0] + end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] + + + # Add the attendees + attendees.map!{|a| + { emailAddress: { + address: a[:email], + name: a[:name] + } } + } + + # Add the room as an attendee + attendees.push({ + type: "resource", + emailAddress: { + address: room.email, + name: room.name + } + }) + + # Add the current user as an attendee + attendees.push({ + emailAddress: { + address: current_user.email, + name: current_user.name + } + }) + + # Create our event which will eventually be stringified + event = { + subject: subject, + body: { + contentType: 'html', + content: description + }, + start: { + dateTime: start_param, + timeZone: TIMEZONE_MAPPING[timezone.to_sym] + }, + end: { + dateTime: end_param, + timeZone: TIMEZONE_MAPPING[timezone.to_sym] + }, + location: { + displayName: room.name, + locationEmailAddress: room.email + }, + isOrganizer: false, + organizer: { + emailAddress: { + address: current_user.email, + name: current_user.name + } + }, + attendees: attendees + }.to_json + + response = JSON.parse(graph_request('post', endpoint, event).value.body)['value'] + end + + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, timezone:'Sydney') + # We will always need a room and endpoint passed in + room = Orchestrator::ControlSystem.find(room_id) + endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" + event = {} + event[:subject] = subject if subject + + event[:start] = { + dateTime: start_param, + timeZone: TIMEZONE_MAPPING[timezone.to_sym] + } if start_param + + event[:end] = { + dateTime: end_param, + timeZone: TIMEZONE_MAPPING[timezone.to_sym] + } if end_param + + event[:body] = { + contentType: 'html', + content: description + } if description + + # Let's assume that the request has the current user and room as an attendee already + event[:attendees] = attendees.map{|a| + { emailAddress: { + address: a[:email], + name: a[:name] + } } + } if attendees + + response = JSON.parse(graph_request('patch', endpoint, event).to_json.value.body)['value'] + end + + # Takes a date of any kind (epoch, string, time object) and returns a time object + def ensure_ruby_date(date) + if !(date.class == Time || date.class == DateTime) + if string_is_digits(date) + + # Convert to an integer + date = date.to_i + + # If JavaScript epoch remove milliseconds + if date.to_s.length == 13 + date /= 1000 + end + + # Convert to datetimes + date = Time.at(date) + else + date = Time.parse(date) + end + end + return date + end + + # Returns true if a string is all digits (used to check for an epoch) + def string_is_digits(string) + string = string.to_s + string.scan(/\D/).empty? + end + +end From 8235e315127d3882cb3ffa0fc192d8cdbc2490dc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 13 Feb 2018 13:23:19 +1100 Subject: [PATCH 0277/1752] Remove timezone data from date params --- lib/microsoft/office.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index db1211ef..cd3036f3 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -89,8 +89,8 @@ def get_room(room_id) def get_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) # Allow passing in epoch, time string or ruby Time class - start_param = ensure_ruby_date(start_param).iso8601 - end_param = ensure_ruby_date(end_param).iso8601 + start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] + end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] # Array of all bookings within our period recurring_bookings = get_recurring_bookings_by_user(user_id, start_param, end_param) @@ -112,8 +112,8 @@ def get_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1. def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) # Allow passing in epoch, time string or ruby Time class - start_param = ensure_ruby_date(start_param).iso8601 - end_param = ensure_ruby_date(end_param).iso8601 + start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] + end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] recurring_endpoint = "/v1.0/users/#{user_id}/calendarView" From 14b01186572a77b2f50a28dd0768a063b0070c72 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 13 Feb 2018 15:24:35 +1100 Subject: [PATCH 0278/1752] Add default env variable values --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index cd3036f3..4255b941 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -44,8 +44,8 @@ def graph_request(request_method, endpoint, data = nil, query = {}, headers = {} # Set our unchanging headers headers['Authorization'] = "Bearer #{graph_token}" - headers['Content-Type'] = ENV['GRAPH_CONTENT_TYPE'] - headers['Prefer'] = ENV['GRAPH_PREFER'] + headers['Content-Type'] = ENV['GRAPH_CONTENT_TYPE'] || "application/json" + headers['Prefer'] = ENV['GRAPH_PREFER'] || 'outlook.timezone="Australia/Sydney"' graph_path = "#{@graph_domain}#{endpoint}" From 457696a7389743c4cbfed97ca021032a3e4f6fba Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Feb 2018 14:52:39 +1100 Subject: [PATCH 0279/1752] Add named params and user filtering --- lib/microsoft/office.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4255b941..e355ed0e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -65,13 +65,18 @@ def log_graph_request(request_method, data, query, headers, graph_path) STDERR.flush end - - def get_users + def get_users(q: nil, limit: nil) + filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q + query_params = { + count: true + filter: filter_param, + limit: limit + }.compact! endpoint = "/v1.0/users" - user_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] + user_response = JSON.parse(graph_request('get', endpoint, nil, query_params).value.body)['value'] end - def get_user(user_id) + def get_user(user_id:) endpoint = "/v1.0/users/#{user_id}" user_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] end @@ -81,13 +86,13 @@ def get_rooms room_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] end - def get_room(room_id) + def get_room(room_id:) endpoint = "/beta/users/#{@service_account_email}/findRooms" room_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] room_response.select! { |room| room['email'] == room_id } end - def get_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] @@ -130,7 +135,7 @@ def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Tim recurring_bookings = JSON.parse(recurring_response.body)['value'] end - def get_bookings_by_room(room_id, start_param=Time.now, end_param=(Time.now + 1.week)) + def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1.week)) return get_bookings_by_user(room_id, start_param, end_param) end From 50246bb2688b11810195f98cf22d4da6fc7f6982 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Feb 2018 15:01:58 +1100 Subject: [PATCH 0280/1752] Add a little comma boi --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e355ed0e..d92cf633 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -68,7 +68,7 @@ def log_graph_request(request_method, data, query, headers, graph_path) def get_users(q: nil, limit: nil) filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q query_params = { - count: true + count: true, filter: filter_param, limit: limit }.compact! From 7350a27459b9b198f8bebaa928b5d3c68a3f4bff Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Feb 2018 16:52:12 +1100 Subject: [PATCH 0281/1752] Add findMeetingTimes method --- lib/microsoft/office.rb | 66 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index d92cf633..9872e0b8 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -68,22 +68,26 @@ def log_graph_request(request_method, data, query, headers, graph_path) def get_users(q: nil, limit: nil) filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q query_params = { - count: true, - filter: filter_param, - limit: limit - }.compact! + '$filter': filter_param, + '$top': limit + }.compact endpoint = "/v1.0/users" - user_response = JSON.parse(graph_request('get', endpoint, nil, query_params).value.body)['value'] + JSON.parse(graph_request('get', endpoint, nil, query_params).value.body)['value'] end def get_user(user_id:) endpoint = "/v1.0/users/#{user_id}" - user_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] + JSON.parse(graph_request('get', endpoint).value.body) end - def get_rooms + def get_rooms(q: nil, limit: nil) + filter_param = "startswith(name,'#{q}') or startswith(address,'#{q}')" if q + query_params = { + '$filter': filter_param, + '$top': limit + }.compact endpoint = "/beta/users/#{@service_account_email}/findRooms" - room_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] + room_response = JSON.parse(graph_request('get', endpoint, nil, query_params).value.body)['value'] end def get_room(room_id:) @@ -92,6 +96,52 @@ def get_room(room_id:) room_response.select! { |room| room['email'] == room_id } end + def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) + endpoint = "/beta/users/#{@service_account_email}/findMeetingTimes" + start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] + end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] + + # Add the attendees + attendees.map!{|a| + { + type: 'required', + emailAddress: { + address: a[:email], + name: a[:name] + } } + } + + locations = { + isRequired: true, + locations: room_ids.map{ |email| + { locationEmailAddress: email } + }, + suggestLocation: true + } + + time_constraint = { + timeslots: [{ + start: { + DateTime: start_param, + TimeZone: 'UTC' + }, + end: { + DateTime: end_param, + TimeZone: 'UTC' + } + }] + } + + post_data = { + attendees: attendees, + locations: locations, + timeConstraint: time_constraint + }.to_json + + + JSON.parse(graph_request('post', endpoint, post_data).value.body) + end + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] From d663d97fcaa59b71d60d9f6feb712f912b05165a Mon Sep 17 00:00:00 2001 From: vagrant Date: Thu, 15 Feb 2018 11:26:29 +1100 Subject: [PATCH 0282/1752] Update to use client password auth --- lib/microsoft/office.rb | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 9872e0b8..ff27e478 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -14,6 +14,7 @@ def initialize( app_scope:, graph_domain:, service_account_email:, + service_account_password:, logger: Rails.logger ) @client_id = client_id @@ -23,6 +24,7 @@ def initialize( @app_scope = app_scope @graph_domain = graph_domain @service_account_email = service_account_email + @service_account_password = service_account_password @graph_client ||= OAuth2::Client.new( @client_id, @client_secret, @@ -31,7 +33,10 @@ def initialize( end def graph_token - @graph_token ||= @graph_client.client_credentials.get_token({ + @graph_token ||= @graph_client.password.get_token( + @service_account_email, + @service_account_password, + { :scope => @app_scope }).token end @@ -97,9 +102,9 @@ def get_room(room_id:) end def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) - endpoint = "/beta/users/#{@service_account_email}/findMeetingTimes" - start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] - end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] + endpoint = "/v1.0/users/#{@service_account_email}/findMeetingTimes" + start_param = ensure_ruby_date((start_param || Time.now)).utc.iso8601.split("+")[0] + end_param = ensure_ruby_date((end_param || (Time.now + 1.hour))).utc.iso8601.split("+")[0] # Add the attendees attendees.map!{|a| @@ -111,15 +116,19 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) } } } - locations = { + location_constraint = { isRequired: true, locations: room_ids.map{ |email| - { locationEmailAddress: email } + { + locationEmailAddress: email, + resolveAvailability: false + } }, - suggestLocation: true + suggestLocation: false } time_constraint = { + activityDomain: 'unrestricted', timeslots: [{ start: { DateTime: start_param, @@ -134,12 +143,16 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) post_data = { attendees: attendees, - locations: locations, - timeConstraint: time_constraint + locationConstraint: location_constraint, + timeConstraint: time_constraint, + maxCandidates: 1000, + returnSuggestionReasons: true }.to_json - - JSON.parse(graph_request('post', endpoint, post_data).value.body) + response = graph_request('post', endpoint, post_data).value.body + # JSON.parse(response)['meetingTimeSuggestions'][0]['locations'].map{ |room| + # room['locationEmailAddress'] if room['locationEmailAddress'] != "" + # }.compact end def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) From 94a45f752d650576435530500826f1e3833cc7de Mon Sep 17 00:00:00 2001 From: vagrant Date: Thu, 15 Feb 2018 12:30:54 +1100 Subject: [PATCH 0283/1752] Update to use findMeetings correctly --- lib/microsoft/office.rb | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index ff27e478..0a5e084c 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -102,9 +102,13 @@ def get_room(room_id:) end def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) - endpoint = "/v1.0/users/#{@service_account_email}/findMeetingTimes" - start_param = ensure_ruby_date((start_param || Time.now)).utc.iso8601.split("+")[0] - end_param = ensure_ruby_date((end_param || (Time.now + 1.hour))).utc.iso8601.split("+")[0] + endpoint = "/v1.0/users/#{@service_account_email}/findMeetingTimes" + now = Time.now + start_ruby_param = ensure_ruby_date((start_param || now)) + end_ruby_param = ensure_ruby_date((end_param || (now + 1.hour))) + duration_string = "PT#{end_ruby_param.to_i-start_ruby_param.to_i}S" + start_param = start_ruby_param.utc.iso8601.split("+")[0] + end_param = (end_ruby_param + 30.minutes).utc.iso8601.split("+")[0] # Add the attendees attendees.map!{|a| @@ -121,7 +125,7 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) locations: room_ids.map{ |email| { locationEmailAddress: email, - resolveAvailability: false + resolveAvailability: true } }, suggestLocation: false @@ -146,13 +150,13 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) locationConstraint: location_constraint, timeConstraint: time_constraint, maxCandidates: 1000, - returnSuggestionReasons: true + returnSuggestionReasons: true, + meetingDuration: duration_string + + }.to_json - response = graph_request('post', endpoint, post_data).value.body - # JSON.parse(response)['meetingTimeSuggestions'][0]['locations'].map{ |room| - # room['locationEmailAddress'] if room['locationEmailAddress'] != "" - # }.compact + JSON.parse(graph_request('post', endpoint, post_data).value.body) end def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) From 6260a903791ac6678c470e06f70714198baaa2a3 Mon Sep 17 00:00:00 2001 From: vagrant Date: Thu, 15 Feb 2018 12:38:45 +1100 Subject: [PATCH 0284/1752] Have separated auth for availability and the rest --- lib/microsoft/office.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 0a5e084c..b9503d3a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -33,7 +33,13 @@ def initialize( end def graph_token - @graph_token ||= @graph_client.password.get_token( + @graph_token ||= @graph_client.client_credentials.get_token({ + :scope => @app_scope + }).token + end + + def password_graph_token + @graph_token ||= @graph_client.password.get_token( @service_account_email, @service_account_password, { @@ -41,14 +47,18 @@ def graph_token }).token end - def graph_request(request_method, endpoint, data = nil, query = {}, headers = {}) + def graph_request(request_method, endpoint, data = nil, query = {}, headers = {}, password=false) # Convert our request method to a symbol and our data to a JSON string request_method = request_method.to_sym data = data.to_json if !data.nil? && data.class != String # Set our unchanging headers - headers['Authorization'] = "Bearer #{graph_token}" + if password + headers['Authorization'] = "Bearer #{password_graph_token}" + else + headers['Authorization'] = "Bearer #{graph_token}" + end headers['Content-Type'] = ENV['GRAPH_CONTENT_TYPE'] || "application/json" headers['Prefer'] = ENV['GRAPH_PREFER'] || 'outlook.timezone="Australia/Sydney"' @@ -156,7 +166,7 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) }.to_json - JSON.parse(graph_request('post', endpoint, post_data).value.body) + JSON.parse(graph_request('post', endpoint, post_data, nil, nil, true).value.body) end def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) From 6e099593d1568e95cdd8fbd9d4c2c95285d53321 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Feb 2018 16:11:34 +1100 Subject: [PATCH 0285/1752] Add proxy option to office365 library --- lib/microsoft/office.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b9503d3a..035ce03d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -15,6 +15,7 @@ def initialize( graph_domain:, service_account_email:, service_account_password:, + internet_proxy:nil, logger: Rails.logger ) @client_id = client_id @@ -25,10 +26,13 @@ def initialize( @graph_domain = graph_domain @service_account_email = service_account_email @service_account_password = service_account_password + @internet_proxy = internet_proxy + oauth_options = { site: @app_site, token_url: @app_token_url } + oauth_options[:connection_opts] = { proxy: @internet_proxy } if @internet_proxy @graph_client ||= OAuth2::Client.new( @client_id, @client_secret, - {:site => @app_site, :token_url => @app_token_url} + oauth_options ) end From 9b827243a388c4292b587d6c74fbcb251cd68e82 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Feb 2018 16:14:18 +1100 Subject: [PATCH 0286/1752] Also add proxy to the actual requests to o365 --- lib/microsoft/office.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 035ce03d..4c5c9c31 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -70,7 +70,9 @@ def graph_request(request_method, endpoint, data = nil, query = {}, headers = {} log_graph_request(request_method, data, query, headers, graph_path) - graph_api = UV::HttpEndpoint.new(@graph_domain, {inactivity_timeout: 25000}) + proxy = URI.parse(@internet_proxy) + + graph_api = UV::HttpEndpoint.new(@graph_domain, {inactivity_timeout: 25000, proxy: { host: proxy.host, port: proxy.port }}) response = graph_api.__send__(request_method, path: graph_path, headers: headers, body: data, query: query) end From 506a96876b80dc9fa9c4d074e0bfc19d98197567 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 23 Feb 2018 12:05:53 +1100 Subject: [PATCH 0287/1752] (aca:meeting_room) modes switch: optionally clobber outputs available tested OK --- modules/aca/meeting_room.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index 7dcc6d79..c84ada0c 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -492,8 +492,11 @@ def switch_mode(mode_name, from_join = false, booting: false) mode_outs = mode[:outputs] || {} difference = mode_outs.keys - default_outs.keys - self[:outputs] = ActiveSupport::HashWithIndifferentAccess.new.deep_merge(mode_outs.merge(default_outs)) - @original_outputs = self[:outputs].deep_dup + if mode[:outputs_clobber] + self[:outputs] = ActiveSupport::HashWithIndifferentAccess.new.deep_merge(mode_outs) + else + self[:outputs] = ActiveSupport::HashWithIndifferentAccess.new.deep_merge(mode_outs.merge(default_outs)) + end if respond_to? :switch_mode_custom begin From 2191b21a9c66045a8a24eebcb2c6522147ef2254 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 25 Feb 2018 22:42:08 +1100 Subject: [PATCH 0288/1752] Update to use password auth and add errors --- lib/microsoft/office.rb | 107 +++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4c5c9c31..26e0d1b2 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -1,6 +1,14 @@ require 'active_support/time' require 'logger' -module Microsoft; end +module Microsoft + class Error < StandardError + class ResourceNotFound < Error; end + class InvalidAuthenticationToken < Error; end + class BadRequest < Error; end + class ErrorInvalidIdMalformed < Error; end + class ErrorAccessDenied < Error; end + end +end class Microsoft::Office TIMEZONE_MAPPING = { @@ -51,8 +59,9 @@ def password_graph_token }).token end - def graph_request(request_method, endpoint, data = nil, query = {}, headers = {}, password=false) - + def graph_request(request_method, endpoint, data = nil, query = {}, headers = nil, password=false) + headers = Hash(headers) + query = Hash(query) # Convert our request method to a symbol and our data to a JSON string request_method = request_method.to_sym data = data.to_json if !data.nil? && data.class != String @@ -68,24 +77,52 @@ def graph_request(request_method, endpoint, data = nil, query = {}, headers = {} graph_path = "#{@graph_domain}#{endpoint}" - log_graph_request(request_method, data, query, headers, graph_path) + log_graph_request(request_method, data, query, headers, graph_path, password) - proxy = URI.parse(@internet_proxy) - graph_api = UV::HttpEndpoint.new(@graph_domain, {inactivity_timeout: 25000, proxy: { host: proxy.host, port: proxy.port }}) + graph_api_options = {inactivity_timeout: 25000} + if @internet_proxy + proxy = URI.parse(@internet_proxy) + graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } + end + + graph_api = UV::HttpEndpoint.new(@graph_domain, graph_api_options) response = graph_api.__send__(request_method, path: graph_path, headers: headers, body: data, query: query) end - def log_graph_request(request_method, data, query, headers, graph_path) + def log_graph_request(request_method, data, query, headers, graph_path, password) STDERR.puts "--------------NEW GRAPH REQUEST------------" STDERR.puts "#{request_method} to #{graph_path}" + STDERR.puts "Data:" STDERR.puts data if data + STDERR.puts "Query:" STDERR.puts query if query - STDERR.puts headers + STDERR.puts "Headers:" + STDERR.puts headers if headers + STDERR.puts "Password auth is: #{password}" STDERR.puts '--------------------------------------------' STDERR.flush end + def check_response(response) + case response.status + when 200, 201, 204 + return + when 400 + if response['error']['code'] == 'ErrorInvalidIdMalformed' + raise Microsoft::Error::ErrorInvalidIdMalformed.new(response.body) + else + raise Microsoft::Error::BadRequest.new(response.body) + end + when 401 + raise Microsoft::Error::InvalidAuthenticationToken.new(response.body) + when 403 + raise Microsoft::Error::ErrorAccessDenied.new(response.body) + when 404 + raise Microsoft::Error::ResourceNotFound.new(response.body) + end + end + def get_users(q: nil, limit: nil) filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q query_params = { @@ -93,12 +130,16 @@ def get_users(q: nil, limit: nil) '$top': limit }.compact endpoint = "/v1.0/users" - JSON.parse(graph_request('get', endpoint, nil, query_params).value.body)['value'] + request = graph_request('get', endpoint, nil, query_params).value + check_response(request) + JSON.parse(request.body)['value'] end def get_user(user_id:) endpoint = "/v1.0/users/#{user_id}" - JSON.parse(graph_request('get', endpoint).value.body) + request = graph_request('get', endpoint).value + check_response(request) + JSON.parse(request.body) end def get_rooms(q: nil, limit: nil) @@ -108,12 +149,16 @@ def get_rooms(q: nil, limit: nil) '$top': limit }.compact endpoint = "/beta/users/#{@service_account_email}/findRooms" - room_response = JSON.parse(graph_request('get', endpoint, nil, query_params).value.body)['value'] + request = graph_request('get', endpoint, nil, query_params).value + check_response(request) + room_response = JSON.parse(request.body)['value'] end def get_room(room_id:) endpoint = "/beta/users/#{@service_account_email}/findRooms" - room_response = JSON.parse(graph_request('get', endpoint).value.body)['value'] + request = graph_request('get', endpoint).value + check_response(request) + room_response = JSON.parse(request.body)['value'] room_response.select! { |room| room['email'] == room_id } end @@ -172,7 +217,17 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) }.to_json - JSON.parse(graph_request('post', endpoint, post_data, nil, nil, true).value.body) + request = graph_request('post', endpoint, post_data, nil, nil, true).value + check_response(request) + JSON.parse(request.body) + end + + def delete_booking(room_id:, booking_id:) + room = Orchestrator::ControlSystem.find(room_id) + endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" + request = graph_request('delete', endpoint, nil, nil, nil, true).value + check_response(request) + response = JSON.parse(request.body) end def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) @@ -193,9 +248,14 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 query_hash['$filter'.to_sym] = "(Start/DateTime le '#{start_param}' and End/DateTime ge '#{start_param}') or (Start/DateTime ge '#{start_param}' and Start/DateTime le '#{end_param}')" end - bookings_response = graph_request('get', endpoint, nil, query_hash).value + bookings_response = graph_request('get', endpoint, nil, query_hash, nil, true).value + check_response(bookings_response) bookings = JSON.parse(bookings_response.body)['value'] - bookings.concat recurring_bookings + if bookings.nil? + return recurring_bookings + else + bookings.concat recurring_bookings + end end def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) @@ -214,7 +274,8 @@ def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Tim query_hash['endDateTime'] = end_param end - recurring_response = graph_request('get', recurring_endpoint, nil, query_hash).value + recurring_response = graph_request('get', recurring_endpoint, nil, query_hash, nil, true).value + check_response(recurring_response) recurring_bookings = JSON.parse(recurring_response.body)['value'] end @@ -292,13 +353,20 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil attendees: attendees }.to_json - response = JSON.parse(graph_request('post', endpoint, event).value.body)['value'] + request = graph_request('post', endpoint, event, nil, nil, true).value + + check_response(request) + + response = JSON.parse(request.body)['value'] end def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, timezone:'Sydney') # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find(room_id) endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" + STDERR.puts "ENDPOINT IS" + STDERR.puts endpoint + STDERR.flush event = {} event[:subject] = subject if subject @@ -325,9 +393,12 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec } } } if attendees - response = JSON.parse(graph_request('patch', endpoint, event).to_json.value.body)['value'] + request = graph_request('patch', endpoint, event.to_json, nil, nil, true).value + check_response(request) + response = JSON.parse(request.body)['value'] end + # Takes a date of any kind (epoch, string, time object) and returns a time object def ensure_ruby_date(date) if !(date.class == Time || date.class == DateTime) From bdcf6e8997330aa568db547fab46da851243eb8f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 26 Feb 2018 10:01:15 +1100 Subject: [PATCH 0289/1752] Use named params for graph request --- lib/microsoft/office.rb | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 26e0d1b2..5066fdac 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -59,19 +59,20 @@ def password_graph_token }).token end - def graph_request(request_method, endpoint, data = nil, query = {}, headers = nil, password=false) + def graph_request(request_method:, endpoint:, data:nil, query:{}, headers:nil, password:false) headers = Hash(headers) query = Hash(query) # Convert our request method to a symbol and our data to a JSON string request_method = request_method.to_sym data = data.to_json if !data.nil? && data.class != String - # Set our unchanging headers if password headers['Authorization'] = "Bearer #{password_graph_token}" else headers['Authorization'] = "Bearer #{graph_token}" end + + # Set our unchanging headers headers['Content-Type'] = ENV['GRAPH_CONTENT_TYPE'] || "application/json" headers['Prefer'] = ENV['GRAPH_PREFER'] || 'outlook.timezone="Australia/Sydney"' @@ -130,7 +131,7 @@ def get_users(q: nil, limit: nil) '$top': limit }.compact endpoint = "/v1.0/users" - request = graph_request('get', endpoint, nil, query_params).value + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params).value check_response(request) JSON.parse(request.body)['value'] end @@ -149,14 +150,14 @@ def get_rooms(q: nil, limit: nil) '$top': limit }.compact endpoint = "/beta/users/#{@service_account_email}/findRooms" - request = graph_request('get', endpoint, nil, query_params).value + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params).value check_response(request) room_response = JSON.parse(request.body)['value'] end def get_room(room_id:) endpoint = "/beta/users/#{@service_account_email}/findRooms" - request = graph_request('get', endpoint).value + request = graph_request(request_method: 'get', endpoint: endpoint).value check_response(request) room_response = JSON.parse(request.body)['value'] room_response.select! { |room| room['email'] == room_id } @@ -217,7 +218,7 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) }.to_json - request = graph_request('post', endpoint, post_data, nil, nil, true).value + request = graph_request(request_method: 'post', endpoint: endpoint, data: post_data, password: true).value check_response(request) JSON.parse(request.body) end @@ -225,7 +226,7 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) def delete_booking(room_id:, booking_id:) room = Orchestrator::ControlSystem.find(room_id) endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" - request = graph_request('delete', endpoint, nil, nil, nil, true).value + request = graph_request(request_method: 'delete', endpoint: endpoint, password: true).value check_response(request) response = JSON.parse(request.body) end @@ -248,7 +249,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 query_hash['$filter'.to_sym] = "(Start/DateTime le '#{start_param}' and End/DateTime ge '#{start_param}') or (Start/DateTime ge '#{start_param}' and Start/DateTime le '#{end_param}')" end - bookings_response = graph_request('get', endpoint, nil, query_hash, nil, true).value + bookings_response = graph_request(request_method: 'get', endpoint: endpoint, query: query_hash, password: true).value check_response(bookings_response) bookings = JSON.parse(bookings_response.body)['value'] if bookings.nil? @@ -274,7 +275,7 @@ def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Tim query_hash['endDateTime'] = end_param end - recurring_response = graph_request('get', recurring_endpoint, nil, query_hash, nil, true).value + recurring_response = graph_request(request_method: 'get', endpoint: recurring_endpoint, query: query_hash, password: true).value check_response(recurring_response) recurring_bookings = JSON.parse(recurring_response.body)['value'] end @@ -353,7 +354,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil attendees: attendees }.to_json - request = graph_request('post', endpoint, event, nil, nil, true).value + request = graph_request(request_method: 'post', endpoint: endpoint, data: event, password: true).value check_response(request) @@ -393,7 +394,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec } } } if attendees - request = graph_request('patch', endpoint, event.to_json, nil, nil, true).value + request = graph_request(request_method: 'patch', endpoint: endpoint, data: event.to_json, password: true).value check_response(request) response = JSON.parse(request.body)['value'] end From fdf4725933a4acbd227bd1629c4621e01ac5f959 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 28 Feb 2018 14:53:02 +1100 Subject: [PATCH 0290/1752] Use a delegated flag if the client has delegated permissoins --- lib/microsoft/office.rb | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 5066fdac..88f6ffd0 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -24,6 +24,7 @@ def initialize( service_account_email:, service_account_password:, internet_proxy:nil, + delegated:false, logger: Rails.logger ) @client_id = client_id @@ -35,6 +36,7 @@ def initialize( @service_account_email = service_account_email @service_account_password = service_account_password @internet_proxy = internet_proxy + @delegated = delegated oauth_options = { site: @app_site, token_url: @app_token_url } oauth_options[:connection_opts] = { proxy: @internet_proxy } if @internet_proxy @graph_client ||= OAuth2::Client.new( @@ -89,6 +91,13 @@ def graph_request(request_method:, endpoint:, data:nil, query:{}, headers:nil, p graph_api = UV::HttpEndpoint.new(@graph_domain, graph_api_options) response = graph_api.__send__(request_method, path: graph_path, headers: headers, body: data, query: query) + + start_timing = Time.now.to_i + response_value = response.value + end_timing = Time.now.to_i + STDERR.puts "Graph request took #{end_timing - start_timing} seconds" + STDERR.flush + return response_value end def log_graph_request(request_method, data, query, headers, graph_path, password) @@ -131,14 +140,14 @@ def get_users(q: nil, limit: nil) '$top': limit }.compact endpoint = "/v1.0/users" - request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params).value + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) JSON.parse(request.body)['value'] end def get_user(user_id:) endpoint = "/v1.0/users/#{user_id}" - request = graph_request('get', endpoint).value + request = graph_request('get', endpoint) check_response(request) JSON.parse(request.body) end @@ -150,14 +159,14 @@ def get_rooms(q: nil, limit: nil) '$top': limit }.compact endpoint = "/beta/users/#{@service_account_email}/findRooms" - request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params).value + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) room_response = JSON.parse(request.body)['value'] end def get_room(room_id:) endpoint = "/beta/users/#{@service_account_email}/findRooms" - request = graph_request(request_method: 'get', endpoint: endpoint).value + request = graph_request(request_method: 'get', endpoint: endpoint, password: true) check_response(request) room_response = JSON.parse(request.body)['value'] room_response.select! { |room| room['email'] == room_id } @@ -218,7 +227,7 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) }.to_json - request = graph_request(request_method: 'post', endpoint: endpoint, data: post_data, password: true).value + request = graph_request(request_method: 'post', endpoint: endpoint, data: post_data, password: @delegated) check_response(request) JSON.parse(request.body) end @@ -226,7 +235,7 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) def delete_booking(room_id:, booking_id:) room = Orchestrator::ControlSystem.find(room_id) endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" - request = graph_request(request_method: 'delete', endpoint: endpoint, password: true).value + request = graph_request(request_method: 'delete', endpoint: endpoint, password: @delegated) check_response(request) response = JSON.parse(request.body) end @@ -249,7 +258,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 query_hash['$filter'.to_sym] = "(Start/DateTime le '#{start_param}' and End/DateTime ge '#{start_param}') or (Start/DateTime ge '#{start_param}' and Start/DateTime le '#{end_param}')" end - bookings_response = graph_request(request_method: 'get', endpoint: endpoint, query: query_hash, password: true).value + bookings_response = graph_request(request_method: 'get', endpoint: endpoint, query: query_hash, password: @delegated) check_response(bookings_response) bookings = JSON.parse(bookings_response.body)['value'] if bookings.nil? @@ -275,7 +284,7 @@ def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Tim query_hash['endDateTime'] = end_param end - recurring_response = graph_request(request_method: 'get', endpoint: recurring_endpoint, query: query_hash, password: true).value + recurring_response = graph_request(request_method: 'get', endpoint: recurring_endpoint, query: query_hash, password: @delegated) check_response(recurring_response) recurring_bookings = JSON.parse(recurring_response.body)['value'] end @@ -354,7 +363,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil attendees: attendees }.to_json - request = graph_request(request_method: 'post', endpoint: endpoint, data: event, password: true).value + request = graph_request(request_method: 'post', endpoint: endpoint, data: event, password: @delegated) check_response(request) @@ -394,7 +403,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec } } } if attendees - request = graph_request(request_method: 'patch', endpoint: endpoint, data: event.to_json, password: true).value + request = graph_request(request_method: 'patch', endpoint: endpoint, data: event.to_json, password: @delegated) check_response(request) response = JSON.parse(request.body)['value'] end From 89275fbecf801ca3471648fbdb56171bb3698d7c Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 28 Feb 2018 16:11:36 +1100 Subject: [PATCH 0291/1752] Make office driver use library --- modules/aca/office_booking.rb | 79 ++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 719c885b..d6d144ee 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -2,6 +2,7 @@ require 'faraday' require 'uv-rays' +require 'microsoft/office' Faraday.default_adapter = :libuv # For rounding up to the nearest 15min @@ -156,6 +157,9 @@ def on_update @office_site = setting(:office_site) @office_token_url = setting(:office_token_url) @office_options = setting(:office_options) + @office_user_email = setting(:office_user_email) + @office_user_password = setting(:office_user_password) + @office_delegated = setting(:office_delegated) @office_room = (setting(:office_room) || system.email) # supports: SMTP, PSMTP, SID, UPN (user principle name) # NOTE:: Using UPN we might be able to remove the LDAP requirement @@ -316,36 +320,57 @@ def fetch_bookings(*args) # } # @office_room = 'testroom@internationaltowers.com' - client = OAuth2::Client.new(@office_client_id, @office_secret, {site: @office_site, token_url: @office_token_url}) + # @office_organiser_location = setting(:office_organiser_location) + # @office_client_id = setting(:office_client_id) + # @office_secret = setting(:office_secret) + # @office_scope = setting(:office_scope) + # @office_site = setting(:office_site) + # @office_token_url = setting(:office_token_url) + # @office_options = setting(:office_options) + # @office_room = (setting(:office_room) || system.email) + # client = OAuth2::Client.new(@office_client_id, @office_secret, {site: @office_site, token_url: @office_token_url}) + client = ::Microsoft::Office.new({ + client_id: @office_client_id || ENV['OFFICE_CLIENT_ID'], + client_secret: @office_secret || ENV["OFFICE_CLIENT_SECRET"], + app_site: @office_site || ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: @office_token_url || ENV["OFFICE_TOKEN_URL"], + app_scope: @office_scope || ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + service_account_email: @office_user_password || ENV['OFFICE_ACCOUNT_EMAIL'], + service_account_password: @office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], + internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'], + delegated: @office_delegated || false - begin - access_token = client.client_credentials.get_token({ - :scope => @office_scope - # :client_secret => ENV["OFFICE_APP_CLIENT_SECRET"], - # :client_id => ENV["OFFICE_APP_CLIENT_ID"] - }).token - rescue Exception => e - logger.debug e.message - logger.debug e.backtrace.inspect - raise e - end + }) + # begin + # access_token = client.client_credentials.get_token({ + # :scope => @office_scope + # # :client_secret => ENV["OFFICE_APP_CLIENT_SECRET"], + # # :client_id => ENV["OFFICE_APP_CLIENT_ID"] + # }).token + # rescue Exception => e + # logger.debug e.message + # logger.debug e.backtrace.inspect + # raise e + # end - # Set out domain, endpoint and content type - domain = 'https://graph.microsoft.com' - host = 'graph.microsoft.com' - endpoint = "/v1.0/users/#{@office_room}/events" - content_type = 'application/json;odata.metadata=minimal;odata.streaming=true' - # Create the request URI and config - office_api = UV::HttpEndpoint.new(domain, tls_options: {host_name: host}) - headers = { - 'Authorization' => "Bearer #{access_token}", - 'Content-Type' => content_type - } + # Set out domain, endpoint and content type + # domain = 'https://graph.microsoft.com' + # host = 'graph.microsoft.com' + # endpoint = "/v1.0/users/#{@office_room}/events" + # content_type = 'application/json;odata.metadata=minimal;odata.streaming=true' + + # # Create the request URI and config + # office_api = UV::HttpEndpoint.new(domain, tls_options: {host_name: host}) + # headers = { + # 'Authorization' => "Bearer #{access_token}", + # 'Content-Type' => content_type + # } # Make the request - response = office_api.get(path: "#{domain}#{endpoint}", headers: headers).value + response = client.get_bookings_by_room(@office_room, Time.now.midnight, Time.now.tomorrow.midnight) @@ -631,11 +656,7 @@ def delete_ews_booking(delete_at) def todays_bookings(response, office_organiser_location) - meeting_response = JSON.parse(response.body)['value'] - - results = [] - - meeting_response.each{|booking| + response.each{|booking| # start_time = Time.parse(booking['start']['dateTime']).utc.iso8601[0..18] + 'Z' # end_time = Time.parse(booking['end']['dateTime']).utc.iso8601[0..18] + 'Z' From 0d47b30fbc95fdf249c8d010ac1a3e626cde5da9 Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 28 Feb 2018 16:24:52 +1100 Subject: [PATCH 0292/1752] Update driver to create booking with library --- lib/microsoft/office.rb | 6 +-- modules/aca/office_booking.rb | 86 ++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 88f6ffd0..552d8cd5 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -329,8 +329,8 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil # Add the current user as an attendee attendees.push({ emailAddress: { - address: current_user.email, - name: current_user.name + address: current_user[:email], + name: current_user[:name] } }) @@ -367,7 +367,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil check_response(request) - response = JSON.parse(request.body)['value'] + response = JSON.parse(request.body) end def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, timezone:'Sydney') diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index d6d144ee..6f4981d5 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -165,6 +165,19 @@ def on_update # NOTE:: Using UPN we might be able to remove the LDAP requirement @office_connect_type = (setting(:office_connect_type) || :SMTP).to_sym @timezone = setting(:room_timezone) + + @client = ::Microsoft::Office.new({ + client_id: @office_client_id || ENV['OFFICE_CLIENT_ID'], + client_secret: @office_secret || ENV["OFFICE_CLIENT_SECRET"], + app_site: @office_site || ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: @office_token_url || ENV["OFFICE_TOKEN_URL"], + app_scope: @office_scope || ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + service_account_email: @office_user_password || ENV['OFFICE_ACCOUNT_EMAIL'], + service_account_password: @office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], + internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'], + delegated: @office_delegated || false + }) else logger.warn "oauth2 gem not available" if setting(:office_creds) end @@ -329,19 +342,7 @@ def fetch_bookings(*args) # @office_options = setting(:office_options) # @office_room = (setting(:office_room) || system.email) # client = OAuth2::Client.new(@office_client_id, @office_secret, {site: @office_site, token_url: @office_token_url}) - client = ::Microsoft::Office.new({ - client_id: @office_client_id || ENV['OFFICE_CLIENT_ID'], - client_secret: @office_secret || ENV["OFFICE_CLIENT_SECRET"], - app_site: @office_site || ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", - app_token_url: @office_token_url || ENV["OFFICE_TOKEN_URL"], - app_scope: @office_scope || ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", - graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", - service_account_email: @office_user_password || ENV['OFFICE_ACCOUNT_EMAIL'], - service_account_password: @office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], - internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'], - delegated: @office_delegated || false - }) # begin # access_token = client.client_credentials.get_token({ @@ -370,7 +371,7 @@ def fetch_bookings(*args) # } # Make the request - response = client.get_bookings_by_room(@office_room, Time.now.midnight, Time.now.tomorrow.midnight) + response = @client.get_bookings_by_room(@office_room, Time.now.midnight, Time.now.tomorrow.midnight) @@ -453,8 +454,8 @@ def create_meeting(options) req_params[:room_email] = @ews_room req_params[:organizer] = options[:user_email] req_params[:subject] = options[:title] - req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.iso8601.chop - req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.iso8601.chop + req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.to_i + req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.to_i # TODO:: Catch error for booking failure @@ -571,42 +572,43 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em logger.debug "Creating booking:" logger.debug booking_data - client = OAuth2::Client.new(@office_client_id, @office_secret, {site: @office_site, token_url: @office_token_url}) + # client = OAuth2::Client.new(@office_client_id, @office_secret, {site: @office_site, token_url: @office_token_url}) - begin - access_token = client.client_credentials.get_token({ - :scope => @office_scope - # :client_secret => ENV["OFFICE_APP_CLIENT_SECRET"], - # :client_id => ENV["OFFICE_APP_CLIENT_ID"] - }).token - rescue Exception => e - logger.debug e.message - logger.debug e.backtrace.inspect - raise e - end + # begin + # access_token = client.client_credentials.get_token({ + # :scope => @office_scope + # # :client_secret => ENV["OFFICE_APP_CLIENT_SECRET"], + # # :client_id => ENV["OFFICE_APP_CLIENT_ID"] + # }).token + # rescue Exception => e + # logger.debug e.message + # logger.debug e.backtrace.inspect + # raise e + # end - # Set out domain, endpoint and content type - domain = 'https://graph.microsoft.com' - host = 'graph.microsoft.com' - endpoint = "/v1.0/users/#{@office_room}/events" - content_type = 'application/json;odata.metadata=minimal;odata.streaming=true' - - # Create the request URI and config - office_api = UV::HttpEndpoint.new(domain, tls_options: {host_name: host}) - headers = { - 'Authorization' => "Bearer #{access_token}", - 'Content-Type' => content_type - } + # # Set out domain, endpoint and content type + # domain = 'https://graph.microsoft.com' + # host = 'graph.microsoft.com' + # endpoint = "/v1.0/users/#{@office_room}/events" + # content_type = 'application/json;odata.metadata=minimal;odata.streaming=true' + + # # Create the request URI and config + # office_api = UV::HttpEndpoint.new(domain, tls_options: {host_name: host}) + # headers = { + # 'Authorization' => "Bearer #{access_token}", + # 'Content-Type' => content_type + # } # Make the request - response = office_api.post(path: "#{domain}#{endpoint}", body: booking_data, headers: headers).value + # response = office_api.post(path: "#{domain}#{endpoint}", body: booking_data, headers: headers).value + response = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: {email: organizer, name: "User"}) logger.debug response.body logger.debug response.to_json - logger.debug JSON.parse(response.body)['id'] + logger.debug response['id'] - id = JSON.parse(response.body)['id'] + id = response['id'] # Return the booking IDs id From 741c5efb1ccfc810765300ecd2d16fb86637d258 Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 28 Feb 2018 16:31:58 +1100 Subject: [PATCH 0293/1752] Update method call to use named params --- modules/aca/office_booking.rb | 48 +---------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 6f4981d5..a4013926 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -324,54 +324,8 @@ def order_complete # ====================================== def fetch_bookings(*args) - # @office_client_id = ENV["OFFICE_APP_CLIENT_ID"] - # @office_secret = ENV["OFFICE_APP_CLIENT_SECRET"] - # @office_scope = ENV['OFFICE_APP_SCOPE'] - # @office_options = { - # site: ENV["OFFICE_APP_SITE"], - # token_url: ENV["OFFICE_APP_TOKEN_URL"] - # } - # @office_room = 'testroom@internationaltowers.com' - - # @office_organiser_location = setting(:office_organiser_location) - # @office_client_id = setting(:office_client_id) - # @office_secret = setting(:office_secret) - # @office_scope = setting(:office_scope) - # @office_site = setting(:office_site) - # @office_token_url = setting(:office_token_url) - # @office_options = setting(:office_options) - # @office_room = (setting(:office_room) || system.email) - # client = OAuth2::Client.new(@office_client_id, @office_secret, {site: @office_site, token_url: @office_token_url}) - - - # begin - # access_token = client.client_credentials.get_token({ - # :scope => @office_scope - # # :client_secret => ENV["OFFICE_APP_CLIENT_SECRET"], - # # :client_id => ENV["OFFICE_APP_CLIENT_ID"] - # }).token - # rescue Exception => e - # logger.debug e.message - # logger.debug e.backtrace.inspect - # raise e - # end - - - # Set out domain, endpoint and content type - # domain = 'https://graph.microsoft.com' - # host = 'graph.microsoft.com' - # endpoint = "/v1.0/users/#{@office_room}/events" - # content_type = 'application/json;odata.metadata=minimal;odata.streaming=true' - - # # Create the request URI and config - # office_api = UV::HttpEndpoint.new(domain, tls_options: {host_name: host}) - # headers = { - # 'Authorization' => "Bearer #{access_token}", - # 'Content-Type' => content_type - # } - # Make the request - response = @client.get_bookings_by_room(@office_room, Time.now.midnight, Time.now.tomorrow.midnight) + response = @client.get_bookings_by_room(room_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight) From fc9a1938c027d123c88e2c4e6a2e05d88a49c16c Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 28 Feb 2018 16:42:43 +1100 Subject: [PATCH 0294/1752] Add more named params --- lib/microsoft/office.rb | 2 +- modules/aca/office_booking.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 552d8cd5..625427c4 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -290,7 +290,7 @@ def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Tim end def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1.week)) - return get_bookings_by_user(room_id, start_param, end_param) + return get_bookings_by_user(room_id: room_id, start_param: start_param, end_param: end_param) end diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index a4013926..19e3dac5 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -325,7 +325,7 @@ def order_complete def fetch_bookings(*args) # Make the request - response = @client.get_bookings_by_room(room_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight) + response = @client.get_bookings_by_user(room_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight) From 0bb34efb8f670f04498bb6add9c65ca4b62257c9 Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 28 Feb 2018 16:57:00 +1100 Subject: [PATCH 0295/1752] Changed named param to use user_id not room_id --- modules/aca/office_booking.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 19e3dac5..ac558dde 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -323,11 +323,8 @@ def order_complete # ROOM BOOKINGS: # ====================================== def fetch_bookings(*args) - # Make the request - response = @client.get_bookings_by_user(room_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight) - - + response = @client.get_bookings_by_user(user_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight) task { todays_bookings(response, @office_organiser_location) From bd00cf6f3524fcf8e2e2fbd126577b235fc7b9ca Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 28 Feb 2018 16:59:42 +1100 Subject: [PATCH 0296/1752] Fix syntax --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index ac558dde..a1625040 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -608,7 +608,7 @@ def delete_ews_booking(delete_at) end def todays_bookings(response, office_organiser_location) - + results = [] response.each{|booking| # start_time = Time.parse(booking['start']['dateTime']).utc.iso8601[0..18] + 'Z' From 0acf3f6914cbfaefd6af1053df60429136505cb0 Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 28 Feb 2018 17:03:20 +1100 Subject: [PATCH 0297/1752] Fix organizer logic --- modules/aca/office_booking.rb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index a1625040..68e0aa37 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -150,7 +150,7 @@ def on_update # Do we want to use exchange web services to manage bookings if CAN_OFFICE logger.debug "Setting OFFICE" - @office_organiser_location = setting(:office_organiser_location) + @office_organiser_location = setting(:office_organiser_location) @office_client_id = setting(:office_client_id) @office_secret = setting(:office_secret) @office_scope = setting(:office_scope) @@ -611,15 +611,13 @@ def todays_bookings(response, office_organiser_location) results = [] response.each{|booking| - # start_time = Time.parse(booking['start']['dateTime']).utc.iso8601[0..18] + 'Z' - # end_time = Time.parse(booking['end']['dateTime']).utc.iso8601[0..18] + 'Z' - start_time = ActiveSupport::TimeZone.new('UTC').parse(booking['start']['dateTime']).iso8601[0..18] - end_time = ActiveSupport::TimeZone.new('UTC').parse(booking['end']['dateTime']).iso8601[0..18] + start_time = ActiveSupport::TimeZone.new('UTC').parse(booking['start']['dateTime']).iso8601 + end_time = ActiveSupport::TimeZone.new('UTC').parse(booking['end']['dateTime']).iso8601 if office_organiser_location == 'attendees' # Grab the first attendee organizer = booking['attendees'][0]['emailAddress']['name'] - elsif office_organiser_location == 'organizer' + else # Grab the organiser organizer = booking['organizer']['emailAddress']['name'] end @@ -629,14 +627,9 @@ def todays_bookings(response, office_organiser_location) :End => end_time, :Subject => booking['subject'], :owner => organizer - # :setup => 0, - # :breakdown => 0 }) } - logger.info "Got #{results.length} results!" - logger.info results.to_json - results end # ======================================= From 4286aca3af0402d4d25a7655a06282cae5c7536c Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 5 Mar 2018 17:33:28 +1100 Subject: [PATCH 0298/1752] Merge branch 'master' into beta --- .codeclimate.yml | 27 +- lib/hid/algorithms.rb | 20 +- modules/aca/office_booking.rb | 4 + modules/aca/slack_concierge.rb | 2 +- modules/aca/testing/websockets.rb | 84 +++++ modules/aca/tracking/desk_management.rb | 27 +- modules/aca/tracking/locate_user_spec.rb | 4 +- modules/amx/acendo_vibe.rb | 296 ++++++++++++++++++ modules/cisco/switch/snooping_catalyst.rb | 2 +- .../cisco/switch/snooping_catalyst_spec.rb | 10 +- modules/cisco/switch/snooping_sg.rb | 31 +- modules/cisco/switch/snooping_sg_spec.rb | 8 +- modules/foxtel/iq2.rb | 85 +++++ modules/foxtel/iq3.rb | 86 +++++ modules/helvar/helvar_net_protocol.md | 38 ++- modules/helvar/net.rb | 248 +++++++++++++++ modules/helvar/net_spec.rb | 20 ++ modules/sony/projector/serial_control.rb | 38 +-- 18 files changed, 961 insertions(+), 69 deletions(-) create mode 100644 modules/aca/testing/websockets.rb create mode 100755 modules/amx/acendo_vibe.rb create mode 100644 modules/foxtel/iq2.rb create mode 100644 modules/foxtel/iq3.rb create mode 100755 modules/helvar/net.rb create mode 100644 modules/helvar/net_spec.rb diff --git a/.codeclimate.yml b/.codeclimate.yml index cdd46eb6..f6780bb4 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -9,27 +9,44 @@ engines: enabled: true rubocop: enabled: true + checks: + Rubocop/Naming/ConstantName: + enabled: false + Rubocop/Style/MutableConstant: + enabled: false + FeatureEnvy: + enabled: false + InstanceVariableAssumption: + enabled: false + Rubocop/Performance/UnfreezeString: + enabled: false + Rubocop/Layout/CaseIndentation: + enabled: false + Rubocop/Metrics/LineLength: + enabled: false + UncommunicativeModuleName: + enabled: false reek: enabled: true checks: argument-count: config: - threshold: 4 + threshold: 12 complex-logic: config: - threshold: 4 + threshold: 10 file-lines: config: - threshold: 300 + threshold: 500 method-complexity: config: - threshold: 5 + threshold: 25 method-count: config: threshold: 40 method-lines: config: - threshold: 30 + threshold: 100 ratings: paths: - "**.rb" diff --git a/lib/hid/algorithms.rb b/lib/hid/algorithms.rb index 9369589f..a7491404 100755 --- a/lib/hid/algorithms.rb +++ b/lib/hid/algorithms.rb @@ -1,3 +1,5 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true module HID module Algorithms @@ -35,8 +37,8 @@ def self.from_wiegand(card_data) facility = (card_data & FACILITY_MASK) >> 17 facility_1s = count_1s(card_data & FAC_PAR_MASK) - parity_passed = card_1s % 2 == 1 && facility_1s % 2 == 0 - raise "parity check error" unless parity_passed + parity_passed = card_1s.odd? && facility_1s.even? + raise 'parity check error' unless parity_passed Wiegand26.new(card_data, facility, card) end @@ -47,11 +49,11 @@ def self.from_components(facility, card) card_data += card << 1 # Build the card parity bit (should be an odd number of ones) - card_data += (FAC_PAR_MASK ^ FACILITY_MASK) if count_1s(card) % 2 == 1 + card_data += (FAC_PAR_MASK ^ FACILITY_MASK) if count_1s(card).odd? card_data += facility << 17 # Build facility parity bit (should be an even number of ones) - card_data += 1 if count_1s(facility) % 2 == 0 + card_data += 1 if count_1s(facility).even? Wiegand26.new(card_data, facility, card) end @@ -74,10 +76,10 @@ def from_components(facility, card) odd_count = count_1s(card_data & PAR_ODD_MASK) # Even Parity - card_data += (1 << 34) if (even_count % 2 == 1) + card_data += (1 << 34) if even_count.odd? # Odd Parity - card_data += 2 if (odd_count % 2 == 0) + card_data += 2 if odd_count.even? card_data = card_data.to_s(2).rjust(35, '0').reverse.to_i(2) Wiegand35.new(card_data, facility, card) @@ -90,11 +92,11 @@ def from_components(facility, card) def self.from_wiegand(card_data) str = card_data.to_s(2).rjust(35, '0').reverse data = str.to_i(2) - even_count = count_1s(data & PAR_EVEN_MASK) + (str[0] == "1" ? 1 : 0) + even_count = count_1s(data & PAR_EVEN_MASK) + (str[0] == '1' ? 1 : 0) odd_count = count_1s(data & PAR_ODD_MASK) - parity_passed = odd_count % 2 == 1 && even_count % 2 == 0 - raise "parity check error" unless parity_passed + parity_passed = odd_count.odd? && even_count.even? + raise 'parity check error' unless parity_passed facility = (data & FACILITY_MASK) >> 22 card = (data & CARD_MASK) >> 2 diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 68e0aa37..99d75f6d 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -611,6 +611,10 @@ def todays_bookings(response, office_organiser_location) results = [] response.each{|booking| + meeting_response.each{|booking| + + # start_time = Time.parse(booking['start']['dateTime']).utc.iso8601[0..18] + 'Z' + # end_time = Time.parse(booking['end']['dateTime']).utc.iso8601[0..18] + 'Z' start_time = ActiveSupport::TimeZone.new('UTC').parse(booking['start']['dateTime']).iso8601 end_time = ActiveSupport::TimeZone.new('UTC').parse(booking['end']['dateTime']).iso8601 diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 6bd17ee4..2c6e285d 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -33,7 +33,7 @@ def send_message(message_text, thread_id) end def get_threads - messages = @client.web_client.channels_history({channel: setting(:channel), oldest: (Time.now - 1.month).to_i, count: 1000})['messages'] + messages = @client.web_client.channels_history({channel: setting(:channel), oldest: (Time.now - 12.months).to_i, count: 1000})['messages'] messages.delete_if{ |message| !((!message.key?('thread_ts') || message['thread_ts'] == message['ts']) && message['subtype'] == 'bot_message') } diff --git a/modules/aca/testing/websockets.rb b/modules/aca/testing/websockets.rb new file mode 100644 index 00000000..1291796e --- /dev/null +++ b/modules/aca/testing/websockets.rb @@ -0,0 +1,84 @@ +require 'protocols/websocket' + +module Aca; end +module Aca::Testing; end + +class Aca::Testing::Websockets + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # wss://echo.websocket.org + + tcp_port 80 + wait_response false + + def connected + new_websocket_client + end + + def disconnected + # clear the keepalive ping + schedule.clear + end + + # Send a text message + def send_text(message) + @ws.text message + end + + def close_connection + @ws.close + end + + protected + + def new_websocket_client + @ws = Protocols::Websocket.new(self, "#{setting(:protocol)}#{remote_address}") + @ws.start + end + + def received(data, resolve, command) + @ws.parse(data) + :success + end + + # ==================== + # Websocket callbacks: + # ==================== + + # websocket ready + def on_open + logger.debug { "Websocket connected" } + schedule.every('30s') do + @ws.ping('keepalive') + end + end + + def on_message(raw_string) + # Process request here + logger.debug { "received: #{raw_string}" } + end + + def on_ping(payload) + logger.debug { "received ping: #{payload}" } + end + + def on_pong(payload) + logger.debug { "received pong: #{payload}" } + end + + # connection is closing + def on_close(event) + # event.code + # event.reason + logger.debug { "closing... #{event.reason} (#{event.code})" } + end + + # connection is closing + def on_error(error) + # error.message + logger.debug { "ERROR! #{error.message}" } + end + + # ==================== +end diff --git a/modules/aca/tracking/desk_management.rb b/modules/aca/tracking/desk_management.rb index d220155d..40fe9943 100644 --- a/modules/aca/tracking/desk_management.rb +++ b/modules/aca/tracking/desk_management.rb @@ -3,7 +3,7 @@ module Aca; end module Aca::Tracking; end -# Manual desk tracking will use this DB structure for persistance +# Manual desk tracking will use this DB structure for persistance require 'aca/tracking/switch_port' require 'set' @@ -30,7 +30,7 @@ def on_load on_update # Load any manual check-in data - @manual_checkin.each do |level, _| + @manual_checkin.each_key do |level| logger.debug { "Loading manual desk check-in details for level #{level}" } query = ::Aca::Tracking::SwitchPort.find_by_switch_ip(level) @@ -89,8 +89,7 @@ def on_update # # @param level [String] the level id of the floor def desk_usage(level) - (self[level] || []) + - (self["#{level}:reserved"] || []) + (self[level] || []) + (self["#{level}:reserved"] || []) end # Grab the ownership details of the desk @@ -136,8 +135,8 @@ def reserve_desk(time = nil) level = desk_details[:level] desk_id = desk_details[:desk_id] - #reserved_by = @manual_usage[desk_id] - #return 4 unless reserved_by == username + # reserved_by = @manual_usage[desk_id] + # return 4 unless reserved_by == username reservation = ::Aca::Tracking::SwitchPort.find_by_id("swport-#{level}-#{desk_id}") raise "Mapping error. Reservation for #{desk_id} can't be found in the database" unless reservation @@ -265,7 +264,7 @@ def manual_checkout(details, user_initiated = true) level = details[:level] desk_id = details[:desk_id] username = details[:username] || desk_id - + tracker = ::Aca::Tracking::SwitchPort.find_by_id("swport-#{level}-#{desk_id}") tracker&.destroy @@ -283,7 +282,7 @@ def manual_checkout(details, user_initiated = true) def cleanup_manual_checkins remove = [] - @manual_usage.each do |desk_id, username| + @manual_usage.each_value do |username| details = self[username] remove << details unless details.reserved? end @@ -307,6 +306,8 @@ def subscribe_disconnect # Build the list of desk ids for each level desk_ids = {} hardware.each do |switch| + next if switch.nil? + ip = switch[:ip_address] mappings = @switch_mappings[ip] next unless mappings @@ -330,19 +331,17 @@ def subscribe_disconnect end # Apply the level details - desk_ids.each { |level, desks| + desk_ids.each do |level, desks| self["#{level}:desk_ids"] = desks self["#{level}:desk_count"] = desks.length - } + end # Watch for users unplugging laptops sys = system (1..hardware.length).each do |index| @subscriptions << sys.subscribe(:Snooping, index, :disconnected) do |notify| details = notify.value - if details.reserved_by - self[details.reserved_by] = details - end + self[details.reserved_by] = details if details.reserved_by end end end @@ -416,7 +415,7 @@ def apply_mappings(level_data, switch, mappings) return end - # Grab port information + # Grab port information interfaces = switch[:interfaces] reservations = switch[:reserved] diff --git a/modules/aca/tracking/locate_user_spec.rb b/modules/aca/tracking/locate_user_spec.rb index c781fb1f..ab6bb731 100644 --- a/modules/aca/tracking/locate_user_spec.rb +++ b/modules/aca/tracking/locate_user_spec.rb @@ -11,7 +11,7 @@ }) # Check MAC address lookup works - exec(:lookup, '192.168.1.16', 'stakach') + exec(:lookup, ['192.168.1.16', 'stakach']) expect(status['192.168.1.16'.to_sym]).to eq('stakach') expect(status['c4544438e158'.to_sym]).to eq('stakach') @@ -35,7 +35,7 @@ expect(user.class.bucket.get("macuser-c4544438e158", quiet: true)).to be(nil) # Test Cleanup - exec(:lookup, '192.168.1.16', 'stakach') + exec(:lookup, ['192.168.1.16', 'stakach']) expect(status['192.168.1.16'.to_sym]).to eq('stakach') expect(status['c4544438e158'.to_sym]).to eq('stakach') expect(user.class.bucket.get("macuser-c4544438e158")).to eq('stakach') diff --git a/modules/amx/acendo_vibe.rb b/modules/amx/acendo_vibe.rb new file mode 100755 index 00000000..9b6b94c3 --- /dev/null +++ b/modules/amx/acendo_vibe.rb @@ -0,0 +1,296 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'shellwords' + +module Amx; end + +# Documentation: https://aca.im/driver_docs/AMX/AMX+Acendo+Vibe.pdf + +class Amx::AcendoVibe + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + include ::Orchestrator::Security + + descriptive_name 'AMX Acendo Vibe' + generic_name :Mixer + + tokenize delimiter: /\r\n|\r|\n/ + tcp_port 4999 + + def on_load + on_update + end + + def on_update + end + + def connected + schedule.every('20s') do + logger.debug 'Maintaining connection..' + battery? priority: 0 + end + end + + def disconnected + schedule.clear + end + + CMDS = { + mic_mute: '/audmic/state', + auto_switch: '/audio/autoswitch', + default_volume: '/audio/defvolume', + gain: '/audio/gain/level', + gain_mode: '/audio/gain/mode', + source: '/audio/source', + mute: '/audio/state', + volume: '/audio/volume', + battery: '/battery/state', + bluetooth_enabled: '/bluetooth/state', + bluetooth_status: '/bluetooth/connstate', + camera_state: '/camera/state', + model: '/system/model', + system_name: '/system/name', + version: '/system/version', + usb_status: '/usbup/status', + occupancy_sensitivity: 'occupancy/sensitivity', + occupancy: '/occupancy/internal/state' + } + + CMDS.each do |name, cmd| + define_method "#{name}?" do |**options| + get(cmd, **options) + end + end + + def mic_mute(muted = true) + state = is_affirmative?(muted) ? 'muted' : 'normal' + set CMDS[:mic_mute], state + end + + def mic_unmute + mic_mute false + end + + def mutes(ids:, muted: true, **_) + mute(ids, muted) + end + + def mute(id, muted = true) + state = is_affirmative?(muted) ? 'muted' : 'normal' + if id == 'mic' + mic_mute(muted) + else + set CMDS[:mute], state + end + end + + def unmute + mute false + end + + def auto_switch(enable) + state = is_affirmative?(enable) ? 'on' : 'off' + set CMDS[:auto_switch], state + end + + def default_volume(level) + level = in_range(level.to_i, 100) + set CMDS[:default_volume], level + end + + def gain(level) + level = in_range(level.to_i, 100) + set CMDS[:gain], level + end + + def gain_mode(fixed) + state = is_affirmative?(fixed) ? 'fixed' : 'var' + set CMDS[:gain_mode], state + end + + # aux, bluetooth, hdmi, optical or usb + def source(input) + set CMDS[:source], input + end + + def fader(id, level) + volume(level) if id != 'mic' + end + + def volume(level) + level = in_range(level.to_i, 100) + set CMDS[:volume], level + end + + def bluetooth_enabled(state) + state = is_affirmative?(state) ? 'on' : 'off' + set CMDS[:bluetooth_enabled], state + end + + def pair + exec '/bluetooth/pairing', 'pair' + end + + def unpair + exec '/bluetooth/pairing', 'unpair' + end + + def system_name(name) + set CMDS[:system_name], name + end + + protect_method :firmware_update, :bluetooth_update, :reboot + + def firmware_update + exec '/system/firmware/update' + end + + def bluetooth_update + exec '/system/firmware/update/bt' + end + + def reboot + exec '/system/reboot' + end + + # AVC-5100 Only: + + def display(state) + state = is_affirmative?(state) ? 'on' : 'off' + set '/display/state', state + end + + def occupancy_sensitivity(setting) + # Valid values: off, low, medium or high + set CMDS[:occupancy_sensitivity], setting + end + + def ring_led_colour(red, green, blue) + # 0..255 + set '/ringleds/color', "#{red}:#{green}:#{blue}" + end + + def ring_led(state) + # state = off, on or pulsing + set '/ringleds/state', state + end + + def received(response, resolve, command) + logger.debug { "Soundbar sent #{response}" } + + data = response.downcase.shellsplit + + # Process events + if data[0] == 'event' + case data[1] + when 'mic_mute' + self[:mic_mute] = true + when 'mic_unmute' + self[:mic_mute] = false + when 'spkr_mute' + self[:mute] = true + when 'spkr_unmute' + self[:mute] = false + when 'src_change_audio' + self[:source] = data[2].to_sym + when 'vol_change' + self[:volume] = data[2].to_i + when 'usb_conn' + self[:usb_connected] = true + when 'usb_dis' + self[:usb_connected] = false + when 'hdmi_conn' + self[:hdmi_connected] = true + when 'hdmi_dis' + self[:hdmi_connected] = false + when 'vacancy_det' + self[:presence_detected] = false + when 'occupancy_det' + self[:presence_detected] = true + end + return :ignore + end + + # Remove the @ + type = data[0][1..-1] + + # Check for errors + if type == 'unsupported' + logger.warn response + return :abort + end + + if type == 'error' + logger.error "Error #{data[-1]} for command: #{data[1..-2].join(' ')}" + return :abort + end + + # Process responses + path = data[1].split('/') + arg = data[-1] + + # ignore echos + return unless data.length > 2 + + case path[1] + when 'audmic' + self[:mic_mute] = self[:fadermic_mute] = arg == 'muted' + when 'audio' + case path[2] + when 'autoswitch' + self[:auto_switch] = arg == 'on' + when 'defvolume' + self[:default_volume] = arg.to_i + when 'gain' + if path[3] == 'level' + self[:gain] = arg.to_i + else + self[:gain_mode] = arg + end + when 'source' + self[:source] = arg.to_sym + when 'state' + self[:muted] = self[:faderoutput_mute] = arg == 'muted' + when 'volume' + self[:volume] = self[:faderoutput] = arg.to_i + end + when 'battery' + self[:battery] = arg + when 'bluetooth' + if path[2] == 'connstate' + self[:bluetooth] = arg + else + self[:bluetooth_enabled] = arg == 'on' + end + when 'camera' + self[:camera] = arg + when 'system' + self[path[2]] = arg + when 'usbup' + self[:usb_status] = (arg == 'connected') + end + + :success + end + + protected + + def get(path, **options) + cmd = ['get', path].shelljoin + logger.debug { "sending: #{cmd}" } + send "#{cmd}\r", options + end + + def set(path, *args, **options) + cmd = ['set', path, *args].shelljoin + logger.debug { "sending: #{cmd}" } + send "#{cmd}\r", options + end + + def exec(path, *args, **options) + cmd = ['exec', path, *args].shelljoin + logger.debug { "sending: #{cmd}" } + send "#{cmd}\r", options + end +end diff --git a/modules/cisco/switch/snooping_catalyst.rb b/modules/cisco/switch/snooping_catalyst.rb index 3c45c070..af58e7b0 100644 --- a/modules/cisco/switch/snooping_catalyst.rb +++ b/modules/cisco/switch/snooping_catalyst.rb @@ -127,7 +127,7 @@ def received(data, resolve, command) # Detect more data available # ==> --More-- if data =~ /More/ - send(' ', priority: 99) + send(' ', priority: 99, retries: 0) return :success end diff --git a/modules/cisco/switch/snooping_catalyst_spec.rb b/modules/cisco/switch/snooping_catalyst_spec.rb index b7b92d3a..bc102ebc 100644 --- a/modules/cisco/switch/snooping_catalyst_spec.rb +++ b/modules/cisco/switch/snooping_catalyst_spec.rb @@ -18,7 +18,9 @@ # Should have configured the tracking data above mock = Aca::Tracking::SwitchPort.find(start_id) expect(mock.nil?).to eq false - expect(status[:"Gi4/1/2"]).to eq({ + details = status[:"Gi4/1/2"] + expect(details.delete(:connected_at).is_a?(Integer)).to be(true) + expect(details).to eq({ ip: "192.168.1.16", mac: "c4544438e158", connected: true, clash: false, reserved: false, username: nil, desk_id: nil }) @@ -54,8 +56,10 @@ # Should have updated this to be offline mock = Aca::Tracking::SwitchPort.find(start_id) - expect(mock.nil?).to eq false - expect(status[:"gi4/1/2"]).to eq({ + expect(mock.nil?).to be false + details = status[:"gi4/1/2"] + expect(details.delete(:connected_at).is_a?(Integer)).to be(true) + expect(details).to eq({ ip: nil, mac: nil, connected: false, clash: false, reserved: false, username: nil, desk_id: nil }) diff --git a/modules/cisco/switch/snooping_sg.rb b/modules/cisco/switch/snooping_sg.rb index 5504f221..fb777b9e 100644 --- a/modules/cisco/switch/snooping_sg.rb +++ b/modules/cisco/switch/snooping_sg.rb @@ -100,6 +100,10 @@ def query_connected_devices schedule.in(3000) { query_snooping_bindings } end + def update_reservations + check_reservations + end + protected @@ -128,7 +132,7 @@ def received(data, resolve, command) # Detect more data available # ==> More: , Quit: q or CTRL+Z, One line: if data =~ /More:/ - send(' ', priority: 99) + send(' ', priority: 99, retries: 0) return :success end @@ -225,6 +229,25 @@ def received(data, resolve, command) # ip, mac, reserved?, clash? self[interface] = details.details + elsif iface.username.nil? + username = ::Aca::Tracking::SwitchPort.bucket.get("macuser-#{mac}", quiet: true) + if username + logger.debug { "Found #{username} at #{ip}: #{mac}" } + + # NOTE:: Same as new connection + details = ::Aca::Tracking::SwitchPort.find_by_id("swport-#{remote_address}-#{interface}") || ::Aca::Tracking::SwitchPort.new + reserved = details.connected(mac, @reserve_time, { + device_ip: ip, + switch_ip: remote_address, + hostname: @hostname, + switch_name: @switch_name, + interface: interface + }) + + # ip, mac, reserved?, clash? + self[interface] = details.details + end + elsif not iface.reserved # We don't know the user who is at this desk... details = ::Aca::Tracking::SwitchPort.find_by_id("swport-#{remote_address}-#{interface}") @@ -290,10 +313,8 @@ def check_reservations # Check if the interfaces are still reserved @reserved_interface.each do |interface| details = ::Aca::Tracking::SwitchPort.find_by_id("swport-#{remote_address}-#{interface}") - if not details.reserved? - remove << interface - self[interface] = details.details - end + remove << interface unless details.reserved? + self[interface] = details.details end # Remove them from the reserved list if not diff --git a/modules/cisco/switch/snooping_sg_spec.rb b/modules/cisco/switch/snooping_sg_spec.rb index e0873a8f..e8727943 100644 --- a/modules/cisco/switch/snooping_sg_spec.rb +++ b/modules/cisco/switch/snooping_sg_spec.rb @@ -27,7 +27,9 @@ # Should have configured the tracking data above mock = Aca::Tracking::SwitchPort.find(start_id) expect(mock.nil?).to eq false - expect(status[:gi2]).to eq({ + details = status[:gi2] + expect(details.delete(:connected_at).is_a?(Integer)).to be(true) + expect(details).to eq({ ip: "192.168.1.16", mac: "c4544438e158", connected: true, clash: false, reserved: false, username: nil, desk_id: nil }) @@ -73,7 +75,9 @@ # Should have updated this to be offline mock = Aca::Tracking::SwitchPort.find(start_id) expect(mock.nil?).to eq false - expect(status[:gi2]).to eq({ + details = status[:gi2] + expect(details.delete(:connected_at).is_a?(Integer)).to be(true) + expect(details).to eq({ ip: nil, mac: nil, connected: false, clash: false, reserved: false, username: nil, desk_id: nil }) diff --git a/modules/foxtel/iq2.rb b/modules/foxtel/iq2.rb new file mode 100644 index 00000000..19e28b28 --- /dev/null +++ b/modules/foxtel/iq2.rb @@ -0,0 +1,85 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Foxtel; end + +class Foxtel::Iq2 + include ::Orchestrator::Constants + + # Discovery Information + descriptive_name 'FoxtelSet Top Box IQ2' + generic_name :Receiver + implements :logic + + def on_load + on_update + end + + def on_update + self[:ir_driver] = @ir_driver = setting(:ir_driver)&.to_sym || :DigitalIO + self[:ir_index] = @ir_index = setting(:ir_index) || 1 + end + + # Here for compatibility + def power(state = true, **options) + do_send('1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,28,6,10,6,3237') + true + end + + DIRECTIONS = { + left: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,22,6,3218', + right: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,28,6,3212', + up: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,10,6,3230', + down: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,16,6,322' + } + def cursor(direction, **options) + val = DIRECTIONS[direction.to_sym] + raise "invalid direction #{direction}" unless val + do_send(val) + end + + def num(number, **options) + val = case number.to_i + when 0 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,10,6,10,6,325' + when 1 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,10,6,16,6,324' + when 2 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,10,6,22,6,324' + when 3 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,10,6,28,6,323' + when 4 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,16,6,10,6,324' + when 5 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,16,6,16,6,324' + when 6 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,16,6,22,6,323' + when 7 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,16,6,28,6,323' + when 8 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,22,6,10,6,324' + when 9 then '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,22,6,16,6,323' + end + do_send(val) + end + + # Make compatible with IPTV systems + def channel(number) + number.to_s.each_char do |char| + num(char) + end + end + + COMMANDS = { + menu: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,22,6,10,6,28,6,22,6,3212', + setup: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,16,6,10,6,3237', + enter: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,28,6,10,6,3224', + channel_up: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,22,6,10,6,10,6,3243', + channel_down:'1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,16,6,3224', + guide: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,28,6,10,6,28,6,10,6,3218' + } + + # Automatically creates a callable function for each command + COMMANDS.each do |command, value| + define_method command do |**options| + do_send(value) + end + end + + protected + + def do_send(cmd) + system[@ir_driver].ir(@ir_index, cmd) + end +end \ No newline at end of file diff --git a/modules/foxtel/iq3.rb b/modules/foxtel/iq3.rb new file mode 100644 index 00000000..2d7186ab --- /dev/null +++ b/modules/foxtel/iq3.rb @@ -0,0 +1,86 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Foxtel; end + +class Foxtel::Iq3 + include ::Orchestrator::Constants + + # Discovery Information + descriptive_name 'FoxtelSet Top Box IQ3' + generic_name :Receiver + implements :logic + + def on_load + on_update + end + + def on_update + self[:ir_driver] = @ir_driver = setting(:ir_driver)&.to_sym || :DigitalIO + self[:ir_index] = @ir_index = setting(:ir_index) || 1 + end + + # Here for compatibility + def power(state = true, **options) + do_send('1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,28,6,10,6,3225') + do_send('1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,22,6,22,6,16,6,22,6,10,6,10,6,28,6,10,6,3212') + true + end + + DIRECTIONS = { + left: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,22,6,3206', + right: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,28,6,3200', + up: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,10,6,3219', + down: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,16,6,3212' + } + def cursor(direction, **options) + val = DIRECTIONS[direction.to_sym] + raise "invalid direction #{direction}" unless val + do_send(val) + end + + def num(number, **options) + val = case number.to_i + when 0 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,10,6,10,6,3243' + when 1 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,10,6,16,6,3237' + when 2 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,10,6,22,6,3231' + when 3 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,10,6,28,6,3225' + when 4 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,16,6,10,6,3237' + when 5 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,16,6,16,6,3231' + when 6 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,16,6,22,6,3225' + when 7 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,16,6,28,6,3219' + when 8 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,22,6,10,6,3231' + when 9 then '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,10,6,22,6,16,6,3225' + end + do_send(val) + end + + # Make compatible with IPTV systems + def channel(number) + number.to_s.each_char do |char| + num(char) + end + end + + COMMANDS = { + menu: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,28,6,16,6,16,6,28,6,3194', + exit: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,22,6,10,6,10,6,28,6,3212', + enter: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,28,6,10,6,3212', + channel_up: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,22,6,10,6,10,6,3231', + channel_down: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,22,6,10,6,16,6,3225', + guide: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,28,6,10,6,28,6,10,6,3206' + } + + # Automatically creates a callable function for each command + COMMANDS.each do |command, value| + define_method command do |**options| + do_send(value) + end + end + + protected + + def do_send(cmd) + system[@ir_driver].ir(@ir_index, cmd) + end +end \ No newline at end of file diff --git a/modules/helvar/helvar_net_protocol.md b/modules/helvar/helvar_net_protocol.md index ba84ffbb..6ec61f6a 100644 --- a/modules/helvar/helvar_net_protocol.md +++ b/modules/helvar/helvar_net_protocol.md @@ -1,7 +1,7 @@ # Helvar.net Protocol -Reference: http://www.lightmoves.com.au/downloads/Documentation/Helvar/HLC-IPDRV%20Installation%20and%20User%20Manual%20-%20Issue%203.pdf +Reference: https://aca.im/driver_docs/Helvar/HelvarNet-Overview.pdf For use with Helvar to DALI routers @@ -35,6 +35,7 @@ input device for example a control panel (device) would have a number of buttons So a full address would be written:- +* Cluster (1..253), Router (1..254), Subnet (1..4), Device (1..255), Subdevice (1..16) * cluster.router.subnet.address for output devices * cluster.router.subnet.address for input devices * cluster.router.subnet.address.sub-address for input sub-devices @@ -45,14 +46,27 @@ So a full address would be written:- * Router = the 4th octet of the IP address of that particular router * Subnet = the data bus on which devices are connected (Dali 1 = 1, Dali 2 = 2, S-Dim = 3, DMX = 4) * Address = the device address, dependant on the data bus (Dali = 1-64, S-Dim = 1-252, DMX = 1-512) -* Sub-address = the sub-device of the device (button, sensor, input etc.) +* Sub-address = the sub-device of the device (button, sensor, input etc.) ## Commands -* Fade time is in 1/100th of a second. So a fade of 900 is 9 seconds. -* Level is between 1 and 100 -* Address looks like: 1.2.1.1 +* `>V:` is the command prefix (`>V:2` represents the protocol version 2) +* `C:` is the command type + * `11` == select scene + * `13` == direct level group address + * `14` == direct level short address + * `109` == query selected scene +* `G:` specifies the lighting group +* `S:` specifices the lighting scene +* `F:` specifies the fade time (in 1/100ths of a second. So a fade of 900 is 9 seconds) +* `L:` specifies the level (between 1 and 100) +* `@` specifies the short address (looks like: 1.2.1.1) +* all commands end with a `#` + + +### Example Commands + * Direct level, short address: `>V:1,C:14,L:{0},F:{1},@{2}#` * {0} == level, {1} == fade_time, {2} == address * Direct level, group address: `>V:1,C:13,G:{0},L:{1},F:{2}#` @@ -60,6 +74,20 @@ So a full address would be written:- * Keep socket alive: `>V:1,C:14,L:0,F:9000,@65#` * Write to dummy address to keep socket alive + +### Example Query + +* `>V:2,C:109,G:17#` query Group 17 as to which scene it is currently in + * responds with: `?V:2,C:109,G:17=14#` + * i.e. Group 17 is in scene 14 + +### Example Error + +* `>V:1,C:104,@:2.2.1.1#` query device type + * responds with: `!V:1,C:104,@:2.2.1.1=11#` + * i.e. error 11, device does not exist + + References: * https://github.com/tkln/HelvarNet/blob/master/helvar.py diff --git a/modules/helvar/net.rb b/modules/helvar/net.rb new file mode 100755 index 00000000..117a638e --- /dev/null +++ b/modules/helvar/net.rb @@ -0,0 +1,248 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Helvar; end + +# Documentation: https://aca.im/driver_docs/Helvar/HelvarNet-Overview.pdf + +class Helvar::Net + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 50000 + descriptive_name 'Helvar Net Lighting Gateway' + generic_name :Lighting + + # Communication settings + tokenize delimiter: '#' + default_settings version: 2 + + def on_load + on_update + end + + def on_unload; end + + def on_update + @version = setting(:version) + end + + def connected + schedule.every('40s') do + logger.debug '-- Polling Helvar' + query_software_version + end + end + + def disconnected + schedule.clear + end + + def lighting(group, state) + level = is_affirmative?(state) ? 100 : 0 + light_level(group, level) + end + + def light_level(group, level, fade = 1000) + fade = (fade / 10).to_i + self[:"area#{group}_level"] = level + group_level(group: group, level: level, fade: fade, name: "group_level#{group}") + end + + def trigger(group, scene, fade = 1000) + fade = (fade / 10).to_i + self[:"area#{group}"] = scene + group_scene(group: group, scene: scene, fade: fade, name: "group_scene#{group}") + end + + def get_current_preset(group) + query_last_scene(group: group) + end + + Commands = { + '11' => :group_scene, + '12' => :device_scene, + '13' => :group_level, + '14' => :device_level, + '15' => :group_proportion, + '16' => :device_proportion, + '17' => :group_modify_proportion, + '18' => :device_modify_proportion, + '19' => :group_emergency_test, + '20' => :device_emergency_test, + '21' => :group_emergency_duration_test, + '22' => :device_emergency_duration_test, + '23' => :group_emergency_stop, + '24' => :device_emergency_stop, + + # Query commands + '70' => :query_lamp_hours, + '71' => :query_ballast_hours, + '72' => :query_max_voltage, + '73' => :query_min_voltage, + '74' => :query_max_temp, + '75' => :query_min_temp, + '100' => :query_device_types_with_addresses, + '101' => :query_clusters, + '102' => :query_routers, + '103' => :query_LSIB, + '104' => :query_device_type, + '105' => :query_description_group, + '106' => :query_description_device, + '107' => :query_workgroup_name, # must use UDP + '108' => :query_workgroup_membership, + '109' => :query_last_scene, + '110' => :query_device_state, + '111' => :query_device_disabled, + '112' => :query_lamp_failure, + '113' => :query_device_faulty, + '114' => :query_missing, + '129' => :query_emergency_battery_failure, + '150' => :query_measurement, + '151' => :query_inputs, + '152' => :query_load, + '160' => :query_power_consumption, + '161' => :query_group_power_consumption, + '164' => :query_group, + '165' => :query_groups, + '166' => :query_scene_names, + '167' => :query_scene_info, + '170' => :query_emergency_func_test_time, + '171' => :query_emergency_func_test_state, + '172' => :query_emergency_duration_time, + '173' => :query_emergency_duration_state, + '174' => :query_emergency_battery_charge, + '175' => :query_emergency_battery_time, + '176' => :query_emergency_total_lamp_time, + '185' => :query_time, + '186' => :query_longitude, + '187' => :query_latitude, + '188' => :query_time_zone, + '189' => :query_daylight_savings, + '190' => :query_software_version, + '191' => :query_helvar_net + } + Commands.merge!(Commands.invert) + Commands.each do |name, cmd| + next unless name.is_a?(Symbol) + define_method name do |**options| + do_send(cmd, **options) + end + end + + Params = { + 'V' => :ver, + 'Q' => :seq, + 'C' => :cmd, + 'A' => :ack, + '@' => :addr, + 'F' => :fade, + 'T' => :time, + 'L' => :level, + 'G' => :group, + 'S' => :scene, + 'B' => :block, + 'N' => :latitude, + 'E' => :longitude, + 'Z' => :time_zone, + # brighter or dimmer than the current level by a % of the difference + 'P' => :proportion, + 'D' => :display_screen, + 'Y' => :daylight_savings, + 'O' => :force_store_scene, + 'K' => :constant_light_scene + } + + def received(data, resolve, command) + logger.debug { "Helvar sent #{data}" } + + # remove connectors from multi-part responses + data.delete!('$') + + indicator = data[0] + case indicator + when '?', '>' + data, value = data.split('=') + params = {} + data.split(',').each do |param| + parts = param.split(':') + if parts.length > 1 + params[Params[parts[0]]] = parts[1] + elsif parts[0][0] == '@' + params[:addr] == parts[0][1..-1] + else + logger.debug { "unknown param type #{param}" } + end + end + + # Check for :ack + ack = params[:ack] + if ack + return :abort if ack != '1' + return :success + end + + cmd = Commands[params[:cmd]] + case cmd + when :query_last_scene + blk = params[:block] + if blk + self[:"area#{params[:group]}_block#{blk}"] = value.to_i + else + self[:"area#{params[:group]}"] = value.to_i + end + else + logger.debug { "unknown response value\n#{cmd} = #{value}" } + end + when '!' + error = Errors[data.split('=')[1]] + self[:last_error] = "error #{error} for #{data}" + logger.warn self[:last_error] + return :abort + else + logger.info "unknown request #{data}" + end + + :success + end + + + protected + + + Errors = { + '0' => 'success', + '1' => 'Invalid group index parameter', + '2' => 'Invalid cluster parameter', + '3' => 'Invalid router', + '4' => 'Invalid router subnet', + '5' => 'Invalid device parameter', + '6' => 'Invalid sub device parameter', + '7' => 'Invalid block parameter', + '8' => 'Invalid scene', + '9' => 'Cluster does not exist', + '10' => 'Router does not exist', + '11' => 'Device does not exist', + '12' => 'Property does not exist', + '13' => 'Invalid RAW message size', + '14' => 'Invalid messages type', + '15' => 'Invalid message command', + '16' => 'Missing ASCII terminator', + '17' => 'Missing ASCII parameter', + '18' => 'Incompatible version' + } + + def do_send(cmd, ver: @version, group: nil, block: nil, level: nil, scene: nil, fade: nil, addr: nil, **options) + req = String.new(">V:#{ver},C:#{cmd}") + req << ",G:#{group}" if group + req << ",B:#{block}" if block + req << ",L:#{level}" if level + req << ",S:#{scene}" if scene + req << ",F:#{fade}" if fade + req << ",@:#{addr}" if addr + req << '#' + logger.debug { "Requesting helvar: #{req}" } + send(req, options) + end +end diff --git a/modules/helvar/net_spec.rb b/modules/helvar/net_spec.rb new file mode 100644 index 00000000..6f24f0cb --- /dev/null +++ b/modules/helvar/net_spec.rb @@ -0,0 +1,20 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +Orchestrator::Testing.mock_device 'Helvar::Net' do + # Perform actions + exec(:trigger, 1, 2, 1100) + .should_send('>V:2,C:11,G:1,S:2,F:110#') + .responds('>V:2,C:11,G:1,S:2,F:110,A:1#') + expect(status[:area1]).to be(2) + + exec(:get_current_preset, 17) + .should_send('>V:2,C:109,G:17#') + .responds('?V:2,C:109,G:17=14#') + expect(status[:area17]).to be(14) + + exec(:get_current_preset, 20) + .should_send('>V:2,C:109,G:20#') + .responds('!V:2,C:109,G:20=1#') + expect(status[:last_error]).to eq('error Invalid group index parameter for !V:2,C:109,G:20=1') +end diff --git a/modules/sony/projector/serial_control.rb b/modules/sony/projector/serial_control.rb index 1c44ebbc..26a70666 100755 --- a/modules/sony/projector/serial_control.rb +++ b/modules/sony/projector/serial_control.rb @@ -33,8 +33,7 @@ def connected def disconnected schedule.clear end - - + # # Power commands # @@ -58,9 +57,7 @@ def power?(**options, &block) options[:priority] ||= 0 do_send(:get, :power_status, options) end - - - + # # Input selection # @@ -69,27 +66,25 @@ def power?(**options, &block) hdmi2: [0x00, 0x05] } INPUTS.merge!(INPUTS.invert) - - + def switch_to(input) input = input.to_sym raise 'unknown input' unless INPUTS.has_key? input do_send(:set, :input, INPUTS[input], delay_on_receive: 500) logger.debug { "requested to switch to: #{input}" } - + input? end def input? - do_send(:get, :input, {:priority => 0}) + do_send(:get, :input, priority: 0) end def lamp_time? - do_send(:get, :lamp_timer, {:priority => 0}) + do_send(:get, :lamp_timer, priority: 0) end - - + # # Mute Audio and Video # @@ -106,7 +101,7 @@ def unmute end def mute? - do_send(:get, :mute, {:priority => 0}) + do_send(:get, :mute, priority: 0) end @@ -118,7 +113,7 @@ def mute? [:contrast, :brightness, :color, :hue, :sharpness].each do |command| # Query command define_method :"#{command}?" do - do_send(:get, command, {:priority => 0}) + do_send(:get, command, priority: 0) end # Set value command @@ -143,7 +138,7 @@ def mute? } - def received(byte_str, resolve, command) # Data is default received as a string + def received(byte_str, resolve, command) # Remove community string (useless) logger.debug { "sony proj sent: 0xA9#{byte_to_hex(byte_str)}" } @@ -153,7 +148,7 @@ def received(byte_str, resolve, command) # Data is default received as a resp = data[3..4] # Check if an ACK/NAK - if type == 0x03 + if type == 0x03 if cmd == [0, 0] return :success else @@ -196,8 +191,7 @@ def received(byte_str, resolve, command) # Data is default received as a self[:input] = INPUTS[resp] when :contrast, :brightness, :color, :hue, :sharpness self[COMMANDS[cmd]] = resp[-1] - when :error_status - + # when :error_status end end @@ -224,15 +218,15 @@ def check_complete(byte_str) end # Still waiting on data - return false + false end def do_poll(*args) - power?({:priority => 0}).finally do + power?(priority: 0).finally do if self[:power] input? mute? - do_send(:get, :error_status, {:priority => 0}) + do_send(:get, :error_status, priority: 0) lamp_time? end end @@ -259,7 +253,7 @@ def do_poll(*args) def checksum(cmd) check = 0 - cmd.each { |byte| check = check | byte } + cmd.each { |byte| check |= byte } check end From 5066cb6d67cb4bd067380089a19d85a277f54b56 Mon Sep 17 00:00:00 2001 From: vagrant Date: Mon, 5 Mar 2018 22:24:29 +1100 Subject: [PATCH 0299/1752] Update ews library --- lib/microsoft/exchange.rb | 258 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 lib/microsoft/exchange.rb diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb new file mode 100644 index 00000000..451a74c6 --- /dev/null +++ b/lib/microsoft/exchange.rb @@ -0,0 +1,258 @@ +require 'active_support/time' +require 'logger' +require 'viewpoint2' + +module Microsoft; end + +class Microsoft::Exchange + TIMEZONE_MAPPING = { + "Sydney": "AUS Eastern Standard Time" + } + def initialize( + ews_url:, + service_account_email:, + service_account_password:, + internet_proxy:nil, + logger: Rails.logger + ) + @ews_url = ews_url + @service_account_email = service_account_email + @service_account_password = service_account_password + @internet_proxy = internet_proxy + ews_opts = { http_opts: { ssl_verify_mode: 0 } } + ews_opts[:http_opts][:http_client] = @internet_proxy if @internet_proxy + @ews_client ||= Viewpoint::EWSClient.new @ews_url, @service_account_email, @service_account_password, ews_opts + end + + def basic_text(field, name) + field[name][:text] + end + + def email_list(field, name=nil) + field[:email_addresses][:elems][-1][:entry][:text].gsub(/SMTP:|SIP:/,'') + end + + def phone_list(field, name=nil) + puts field + field[:phone_numbers][:elems][4][:entry][:text] || field[:phone_numbers][:elems][2][:entry][:text] + end + + + def get_users(q: nil, limit: nil) + ews_users = @ews_client.search_contacts(q) + users = [] + fields = { + display_name: 'name:basic_text', + email_addresses: 'email:email_list', + phone_numbers: 'phone:phone_list', + culture: 'locale:basic_text', + department: 'department:basic_text' + } + + ews_users.each do |user| + output = {} + user[:resolution][:elems][1][:contact][:elems].each do |field| + if fields.keys.include?(field.keys[0]) + + output[fields[field.keys[0]].split(':')[0]] = self.__send__(fields[field.keys[0]].split(':')[1], field, field.keys[0]) + end + end + users.push(output) + end + STDERR.puts users + STDERR.puts limit + STDERR.puts users[0..2] + STDERR.flush + limit ||= users.length + limit = limit.to_i - 1 + return users[0..limit.to_i] + end + + def get_user(user_id:) + get_users(q: user_id, limit: 1)[0] + end + + def find_time(cal_event, time) + elems = cal_event[:calendar_event][:elems] + start_time = nil + elems.each do |item| + if item[time] + Time.use_zone 'Sydney' do + start_time = Time.parse(item[time][:text]) + end + break + end + end + start_time + end + + def get_available_rooms(rooms:, start_time:, end_time:) + free_rooms = [] + + + # Get booking data for all rooms between time range bounds + user_free_busy = @ews_client.get_user_availability(rooms, + start_time: start_time, + end_time: end_time, + requested_view: :detailed, + time_zone: { + bias: -600, + standard_time: { + bias: -60, + time: "03:00:00", + day_order: 1, + month: 10, + day_of_week: 'Sunday' + }, + daylight_time: { + bias: 0, + time: "02:00:00", + day_order: 1, + month: 4, + day_of_week: 'Sunday' + } + } + ) + + user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| + # Remove meta data (business hours and response type) + resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| item[:free_busy_view_type] || item[:working_hours] } + + # Back to back meetings still show up so we need to remove these from the results + if resp.length == 1 + resp = resp[0][:calendar_event_array][:elems] + + if resp.length == 0 + free_rooms.push(rooms[index]) + elsif resp.length == 1 + free_rooms.push(rooms[index]) if find_time(resp[0], :start_time) == end_time + free_rooms.push(rooms[index]) if find_time(resp[0], :end_time) == start_time + end + elsif resp.length == 0 + # If response length is 0 then the room is free + free_rooms.push(rooms[index]) + end + } + + free_rooms + end + + def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.week)) + bookings = [] + calendar_id = @ews_client.get_folder(:calendar, opts = {act_as: email }).id + events = @ews_client.find_items(folder_id: calendar_id, calendar_view: {start_date: start_param, end_date: end_param}) + # events = @ews_client.get_item(:calendar, opts = {act_as: email}).items + events.each{|event| + event.get_all_properties! + booking = {} + booking[:subject] = event.subject + booking[:start] = event.start.to_i + booking[:end] = event.end.to_i + booking[:body] = event.body + booking[:organizer] = { + name: event.organizer.name, + email: event.organizer.email + } + booking[:attendees] = event.required_attendees.map {|attendee| + { + name: attendee.name, + email: attendee.email + } + } if event.required_attendees + bookings.push(booking) + } + bookings + end + + def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney') + description = String(description) + attendees = Array(attendees) + + + booking = {} + booking[:subject] = subject + booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop + # booking[:body] = description + booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop + booking[:required_attendees] = [{ + attendee: { mailbox: { email_address: current_user.email } } + }] + attendees.each do |attendee| + booking[:required_attendees].push({ + attendee: { mailbox: { email_address: attendee}} + }) + end + + folder = @ews_client.get_folder(:calendar, { act_as: room_email }) + appointment = folder.create_item(booking) + { + id: appointment.id, + start: start_param, + end: end_param, + attendees: attendees, + subject: subject + } + end + + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, timezone:'Sydney') + # We will always need a room and endpoint passed in + room = Orchestrator::ControlSystem.find(room_id) + endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" + event = {} + event[:subject] = subject if subject + + event[:start] = { + dateTime: start_param, + timeZone: TIMEZONE_MAPPING[timezone.to_sym] + } if start_param + + event[:end] = { + dateTime: end_param, + timeZone: TIMEZONE_MAPPING[timezone.to_sym] + } if end_param + + event[:body] = { + contentType: 'html', + content: description + } if description + + # Let's assume that the request has the current user and room as an attendee already + event[:attendees] = attendees.map{|a| + { emailAddress: { + address: a[:email], + name: a[:name] + } } + } if attendees + + response = JSON.parse(graph_request('patch', endpoint, event).to_json.value.body)['value'] + end + + # Takes a date of any kind (epoch, string, time object) and returns a time object + def ensure_ruby_date(date) + if !(date.class == Time || date.class == DateTime) + if string_is_digits(date) + + # Convert to an integer + date = date.to_i + + # If JavaScript epoch remove milliseconds + if date.to_s.length == 13 + date /= 1000 + end + + # Convert to datetimes + date = Time.at(date) + else + date = Time.parse(date) + end + end + return date + end + + # Returns true if a string is all digits (used to check for an epoch) + def string_is_digits(string) + string = string.to_s + string.scan(/\D/).empty? + end + +end From aed9ed1756321f981fbab21f1d29bbc9e8eb73f9 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 6 Mar 2018 10:22:17 +1100 Subject: [PATCH 0300/1752] Push debugging due to ro filesystem --- lib/microsoft/exchange.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 451a74c6..a3ee27e7 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -89,7 +89,11 @@ def find_time(cal_event, time) def get_available_rooms(rooms:, start_time:, end_time:) free_rooms = [] - + STDERR.puts "Getting available rooms with" + STDERR.puts start_time + STDERR.puts end_time + STDERR.flush + # Get booking data for all rooms between time range bounds user_free_busy = @ews_client.get_user_availability(rooms, start_time: start_time, From a312e12e7bfd47af364541576244101903252a07 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 6 Mar 2018 15:21:39 +1100 Subject: [PATCH 0301/1752] Remove lowercase email prefixes --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index a3ee27e7..ecfb8a67 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -29,7 +29,7 @@ def basic_text(field, name) end def email_list(field, name=nil) - field[:email_addresses][:elems][-1][:entry][:text].gsub(/SMTP:|SIP:/,'') + field[:email_addresses][:elems][-1][:entry][:text].gsub(/SMTP:|SIP:|sip:|smtp:/,'') end def phone_list(field, name=nil) @@ -93,7 +93,7 @@ def get_available_rooms(rooms:, start_time:, end_time:) STDERR.puts start_time STDERR.puts end_time STDERR.flush - + # Get booking data for all rooms between time range bounds user_free_busy = @ews_client.get_user_availability(rooms, start_time: start_time, From 0a4dd1e8a5304a1eef52f3d0b152ca68fec2f6cc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 6 Mar 2018 16:10:12 +1100 Subject: [PATCH 0302/1752] Get email from different location --- lib/microsoft/exchange.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index ecfb8a67..53028fcc 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -43,7 +43,7 @@ def get_users(q: nil, limit: nil) users = [] fields = { display_name: 'name:basic_text', - email_addresses: 'email:email_list', + # email_addresses: 'email:email_list', phone_numbers: 'phone:phone_list', culture: 'locale:basic_text', department: 'department:basic_text' @@ -57,6 +57,7 @@ def get_users(q: nil, limit: nil) output[fields[field.keys[0]].split(':')[0]] = self.__send__(fields[field.keys[0]].split(':')[1], field, field.keys[0]) end end + output[:email] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] users.push(output) end STDERR.puts users From 2c94893c0d6b3ef695c4ebf1c3a09eaa0e07fe85 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 12 Mar 2018 15:43:19 +1100 Subject: [PATCH 0303/1752] Add lockers library --- lib/loqit/lockers.rb | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 lib/loqit/lockers.rb diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb new file mode 100644 index 00000000..f36ff0be --- /dev/null +++ b/lib/loqit/lockers.rb @@ -0,0 +1,56 @@ +require 'savon' +require 'active_support/time' +require 'digest/md5' +module Loqit; end + + +# lockers_client = Loqit::Lockers.new( +# username: 'system', +# password: 'system', +# endpoint: 'https://DESKTOP-ABH46ML:8082/Cardholder/', +# namespace: 'http://www.gallagher.co/security/commandcentre/webservice', +# namespaces: {"xmlns:wsdl" => "http://www.gallagher.co/security/commandcentre/cifws", "xmlns:web" => 'http://www.gallagher.co/security/commandcentre/webservice'}, +# wsdl: 'http://192.168.1.200/soap/server_wsdl.php?wsdl', +# log: false, +# log_level: nil +# ) + +class Loqit::Lockers + def initialize( + username:, + password:, + serial:, + wsdl:, + log: false, + log_level: :debug + ) + savon_config = { + :wsdl => wsdl + } + + @client = Savon.client savon_config + @username = username + @password = password + @serial = serial + @header = { + header: { + username: @username, + password: Digest::MD5.hexdigest(@password), + serialnumber: @serial + } + } + end + + + + def list_lockers + response = client.call(:list_lockers, + message: { + unitSerial: @serial + }, + soap_header: @header + ) + end + +end + From 338fae7566d8587c2cee7fd3c139ca703d14abfc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 12 Mar 2018 16:04:46 +1100 Subject: [PATCH 0304/1752] Update syntax --- lib/loqit/lockers.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index f36ff0be..35ce8fbb 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -40,11 +40,8 @@ def initialize( } } end - - - def list_lockers - response = client.call(:list_lockers, + response = @client.call(:list_lockers, message: { unitSerial: @serial }, From e91d69bef09594e3bccf87eaccbdb902b7a864af Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 13 Mar 2018 10:49:51 +1100 Subject: [PATCH 0305/1752] Add index to request --- lib/microsoft/office.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 625427c4..a9420e46 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -133,11 +133,12 @@ def check_response(response) end end - def get_users(q: nil, limit: nil) + def get_users(q: nil, limit: nil, index: 0) filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q query_params = { '$filter': filter_param, - '$top': limit + '$top': limit, + '$skip': index }.compact endpoint = "/v1.0/users" request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) From 0fb93b770cf646771f2de96b193ac6279966503a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 13 Mar 2018 11:22:44 +1100 Subject: [PATCH 0306/1752] Fix skip_token logic --- lib/microsoft/office.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a9420e46..08a1a27c 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -133,17 +133,23 @@ def check_response(response) end end - def get_users(q: nil, limit: nil, index: 0) + def get_users(q: nil, limit: nil, skip_token: nil) filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q query_params = { '$filter': filter_param, '$top': limit, - '$skip': index + '$skipToken': skip_token }.compact endpoint = "/v1.0/users" request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) - JSON.parse(request.body)['value'] + body = JSON.parse(request.body) + if JSON.parse(request.body).key?('@odata.nextLink') + return { skip_token: body['@odata.nextLink'].split("skiptoken=")[-1].split("skipToken=")[-1], users: JSON.parse(request.body)['value'] } + else + + return { skip_token: nil, users: JSON.parse(request.body)['value'] } + end end def get_user(user_id:) From f95839289b145e2f1c8edb17383037cbbc4d1fd5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 13 Mar 2018 12:27:15 +1100 Subject: [PATCH 0307/1752] Fix RRDB time params logic --- lib/ibm/domino.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 91932c18..6aa1f778 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -200,12 +200,12 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) # Make date a date object from epoch or parsed text date = convert_to_simpledate(date) - starting = date.yesterday.strftime("%Y%m%d") + starting = date.utc.strftime("%Y%m%dT%H%M%S,00Z") if ending - ending = convert_to_simpledate(ending).strftime("%Y%m%d") + ending = convert_to_simpledate(ending).utc.strftime("%Y%m%dT%H%M%S,00Z") else - ending = date.strftime("%Y%m%d") + ending = date.utc.strftime("%Y%m%dT%H%M%S,00Z") end From 5f4dcbe29fb3573515511656ddadcfdeb2eb4ece Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 13 Mar 2018 12:32:18 +1100 Subject: [PATCH 0308/1752] Set end date to tomorrow lol --- lib/ibm/domino.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ibm/domino.rb b/lib/ibm/domino.rb index 6aa1f778..c4840aec 100644 --- a/lib/ibm/domino.rb +++ b/lib/ibm/domino.rb @@ -205,7 +205,7 @@ def get_bookings(room_ids, date=Time.now.midnight, ending=nil, full_data=false) if ending ending = convert_to_simpledate(ending).utc.strftime("%Y%m%dT%H%M%S,00Z") else - ending = date.utc.strftime("%Y%m%dT%H%M%S,00Z") + ending = date.tomorrow.utc.strftime("%Y%m%dT%H%M%S,00Z") end From 72d169d879c641094c3d52b334fefd8763f66a24 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 13 Mar 2018 12:57:08 +1100 Subject: [PATCH 0309/1752] Remove skip token stuff --- lib/microsoft/office.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 08a1a27c..625427c4 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -133,23 +133,16 @@ def check_response(response) end end - def get_users(q: nil, limit: nil, skip_token: nil) + def get_users(q: nil, limit: nil) filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q query_params = { '$filter': filter_param, - '$top': limit, - '$skipToken': skip_token + '$top': limit }.compact endpoint = "/v1.0/users" request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) - body = JSON.parse(request.body) - if JSON.parse(request.body).key?('@odata.nextLink') - return { skip_token: body['@odata.nextLink'].split("skiptoken=")[-1].split("skipToken=")[-1], users: JSON.parse(request.body)['value'] } - else - - return { skip_token: nil, users: JSON.parse(request.body)['value'] } - end + JSON.parse(request.body)['value'] end def get_user(user_id:) From 995bbb62a25ee8153eee40d5e7ea5b71f70c96f7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 13 Mar 2018 15:34:06 +1100 Subject: [PATCH 0310/1752] Update lockers api with examples --- lib/loqit/lockers.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 35ce8fbb..21c79dc2 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -3,17 +3,18 @@ require 'digest/md5' module Loqit; end - -# lockers_client = Loqit::Lockers.new( -# username: 'system', -# password: 'system', -# endpoint: 'https://DESKTOP-ABH46ML:8082/Cardholder/', -# namespace: 'http://www.gallagher.co/security/commandcentre/webservice', -# namespaces: {"xmlns:wsdl" => "http://www.gallagher.co/security/commandcentre/cifws", "xmlns:web" => 'http://www.gallagher.co/security/commandcentre/webservice'}, -# wsdl: 'http://192.168.1.200/soap/server_wsdl.php?wsdl', -# log: false, -# log_level: nil +# require 'loqit/lockers' +# lockers = Loqit::Lockers.new( +# username: 'xmltester', +# password: 'xmlPassword', +# wsdl: 'http://loqit.acgin.com.au/soap/server_wsdl.php?wsdl', +# serial: 'BE434080-7277-11E3-BC4D-94C69111930A' # ) +# random_locker = lockers.list_lockers.sample['number'] +# random_status = lockers.show_status(random_locker) +# random_open = lockers.open_locker(random_locker) + + class Loqit::Lockers def initialize( From 993ddec06f43e2ab495d32bcaa287c922832e88c Mon Sep 17 00:00:00 2001 From: aca-apps Date: Tue, 20 Mar 2018 18:12:33 +1100 Subject: [PATCH 0311/1752] Fix response format for bookings --- lib/microsoft/exchange.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 53028fcc..7300933e 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -151,8 +151,10 @@ def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.w event.get_all_properties! booking = {} booking[:subject] = event.subject - booking[:start] = event.start.to_i - booking[:end] = event.end.to_i + # booking[:start_date] = event.start.utc.iso8601 + # booking[:end_date] = event.end.utc.iso8601 + booking[:start_date] = event.start.to_i * 1000 + booking[:end_date] = event.end.to_i * 1000 booking[:body] = event.body booking[:organizer] = { name: event.organizer.name, From 55f82fbad7895ce96a3035de37a6dcace639635a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 26 Mar 2018 10:46:02 +1100 Subject: [PATCH 0312/1752] Take in epochs --- lib/microsoft/exchange.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 53028fcc..13172321 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -143,6 +143,10 @@ def get_available_rooms(rooms:, start_time:, end_time:) end def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.week)) + if [Integer, String].include?(start_param.class) + start_param = DateTime.at(start_param / 1000) + end_param = DateTime.at(end_param / 1000) + end bookings = [] calendar_id = @ews_client.get_folder(:calendar, opts = {act_as: email }).id events = @ews_client.find_items(folder_id: calendar_id, calendar_view: {start_date: start_param, end_date: end_param}) From a5dccb844c4e9a5f1d3ac94108e2b16f9fe7d332 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 26 Mar 2018 10:47:15 +1100 Subject: [PATCH 0313/1752] Take in epochs --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 62e20a79..f4452ef9 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -144,8 +144,8 @@ def get_available_rooms(rooms:, start_time:, end_time:) def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.week)) if [Integer, String].include?(start_param.class) - start_param = DateTime.at(start_param / 1000) - end_param = DateTime.at(end_param / 1000) + start_param = DateTime.at(start_param.to_i / 1000) + end_param = DateTime.at(end_param.to_i / 1000) end bookings = [] calendar_id = @ews_client.get_folder(:calendar, opts = {act_as: email }).id From 464e577cc2657bd481f59b03cccbc1be990d9b60 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 26 Mar 2018 10:50:15 +1100 Subject: [PATCH 0314/1752] Take in epochs --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index f4452ef9..ddb460e4 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -144,8 +144,8 @@ def get_available_rooms(rooms:, start_time:, end_time:) def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.week)) if [Integer, String].include?(start_param.class) - start_param = DateTime.at(start_param.to_i / 1000) - end_param = DateTime.at(end_param.to_i / 1000) + start_param = DateTime.parse(Time.at(start_param.to_i / 1000).to_s) + end_param = DateTime.parse(Time.at(end_param.to_i / 1000).to_s) end bookings = [] calendar_id = @ews_client.get_folder(:calendar, opts = {act_as: email }).id From 644d646aebc7d41689cb0937b94143f63fda06a0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 26 Mar 2018 11:14:38 +1100 Subject: [PATCH 0315/1752] Add title field to bookings returned --- lib/microsoft/exchange.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index ddb460e4..c28428d3 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -155,6 +155,7 @@ def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.w event.get_all_properties! booking = {} booking[:subject] = event.subject + booking[:title] = event.subject # booking[:start_date] = event.start.utc.iso8601 # booking[:end_date] = event.end.utc.iso8601 booking[:start_date] = event.start.to_i * 1000 From 316609f264bbc87b3d984ee95b0046c9da2de235 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 27 Mar 2018 11:45:10 +1100 Subject: [PATCH 0316/1752] Add debugging --- lib/microsoft/exchange.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index c28428d3..c0bc99db 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -21,6 +21,9 @@ def initialize( @internet_proxy = internet_proxy ews_opts = { http_opts: { ssl_verify_mode: 0 } } ews_opts[:http_opts][:http_client] = @internet_proxy if @internet_proxy + STDERR.puts '--------------- NEW CLIENT CREATED --------------' + STDERR.puts "At URL: #{@ews_url} with email: #{@service_account_email} and password #{@service_account_password}" + STDERR.puts '-------------------------------------------------' @ews_client ||= Viewpoint::EWSClient.new @ews_url, @service_account_email, @service_account_password, ews_opts end @@ -147,6 +150,9 @@ def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.w start_param = DateTime.parse(Time.at(start_param.to_i / 1000).to_s) end_param = DateTime.parse(Time.at(end_param.to_i / 1000).to_s) end + STDERR.puts '---------------- GETTING BOOKINGS ---------------' + STDERR.puts "At URL: #{email} with email: #{start_param} and password #{end_param}" + STDERR.puts '-------------------------------------------------' bookings = [] calendar_id = @ews_client.get_folder(:calendar, opts = {act_as: email }).id events = @ews_client.find_items(folder_id: calendar_id, calendar_view: {start_date: start_param, end_date: end_param}) From c16b9b881bcf9cfb5735663c4448a2a140f939a5 Mon Sep 17 00:00:00 2001 From: aca-apps Date: Wed, 28 Mar 2018 11:19:28 +1100 Subject: [PATCH 0317/1752] Error catching for rooms --- lib/microsoft/exchange.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index c0bc99db..970a32ad 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -146,6 +146,7 @@ def get_available_rooms(rooms:, start_time:, end_time:) end def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.week)) + begin if [Integer, String].include?(start_param.class) start_param = DateTime.parse(Time.at(start_param.to_i / 1000).to_s) end_param = DateTime.parse(Time.at(end_param.to_i / 1000).to_s) @@ -180,6 +181,11 @@ def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.w bookings.push(booking) } bookings + rescue Exception => msg + STDERR.puts msg + STDERR.flush + return [] + end end def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney') From 35263ba4438f8075c0c4e80f7671ea02efeed207 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Mar 2018 12:15:57 +1100 Subject: [PATCH 0318/1752] Add debugging and fix time params --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 970a32ad..cdc4e71f 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -145,14 +145,14 @@ def get_available_rooms(rooms:, start_time:, end_time:) free_rooms end - def get_bookings(email:, start_param:DateTime.now, end_param:(DateTime.now + 1.week)) + def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime.now.midnight + 1.week)) begin if [Integer, String].include?(start_param.class) start_param = DateTime.parse(Time.at(start_param.to_i / 1000).to_s) end_param = DateTime.parse(Time.at(end_param.to_i / 1000).to_s) end STDERR.puts '---------------- GETTING BOOKINGS ---------------' - STDERR.puts "At URL: #{email} with email: #{start_param} and password #{end_param}" + STDERR.puts "At email: #{email} with start: #{start_param} and end: #{end_param}" STDERR.puts '-------------------------------------------------' bookings = [] calendar_id = @ews_client.get_folder(:calendar, opts = {act_as: email }).id From 5e47bef8db1c2bd203678d1a127eca8518e3b92c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 5 Apr 2018 13:45:14 +1000 Subject: [PATCH 0319/1752] Use aussie organiser spelling --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index cdc4e71f..8d4cf6c7 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -168,7 +168,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:start_date] = event.start.to_i * 1000 booking[:end_date] = event.end.to_i * 1000 booking[:body] = event.body - booking[:organizer] = { + booking[:organiser] = { name: event.organizer.name, email: event.organizer.email } From 92e9e8c6d3ca5dd51e340d161de29009f7a97fc8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 6 Apr 2018 12:23:03 +1000 Subject: [PATCH 0320/1752] Add support for recurring bookings in o365 lib --- lib/microsoft/office.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 625427c4..d34f8a9b 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -294,7 +294,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney') + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, timezone:'Sydney') description = String(description) attendees = Array(attendees) @@ -305,6 +305,8 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil endpoint = "/v1.0/users/#{room.email}/events" # Ensure our start and end params are Ruby dates and format them in Graph format + start_object = ensure_ruby_date(start_param).in_time_zone(timezone) + end_object = ensure_ruby_date(end_param).in_time_zone(timezone) start_param = ensure_ruby_date(start_param).in_time_zone(timezone).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] @@ -361,7 +363,23 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil } }, attendees: attendees - }.to_json + } + + if recurrence + event[:recurrence] = { + pattern: { + type: recurrence, + interval: 1, + daysOfWeek: [start_object.strftime("%A")] + }, + range: { + type: 'noEnd', + startDate: start_object.strftime("%F") + } + } + end + + event = event.to_json request = graph_request(request_method: 'post', endpoint: endpoint, data: event, password: @delegated) From 0b3c5fdfce515a8a37fe8aec4d7abc51e088fc4f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 6 Apr 2018 15:34:14 +1000 Subject: [PATCH 0321/1752] Always use password for o365 freebusy --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index d34f8a9b..fed0d88d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -227,7 +227,7 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) }.to_json - request = graph_request(request_method: 'post', endpoint: endpoint, data: post_data, password: @delegated) + request = graph_request(request_method: 'post', endpoint: endpoint, data: post_data, password: true) check_response(request) JSON.parse(request.body) end From 468c21cd363e243fcf1a3b535bb92546e9488f17 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 10 Apr 2018 10:28:08 +1000 Subject: [PATCH 0322/1752] Split user name queries that contain space --- lib/microsoft/office.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index fed0d88d..91ec4b3f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -134,7 +134,16 @@ def check_response(response) end def get_users(q: nil, limit: nil) - filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q + if q.include?(" ") + queries = q.split(" ") + filter_params = [] + queries.each do |q| + filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))") + end + filter_param = filter_params.join(" or ") + else + filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q + end query_params = { '$filter': filter_param, '$top': limit From 9788408cdef77ee33add40b15d58b3834cb2f1f2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 12 Apr 2018 15:48:52 +1000 Subject: [PATCH 0323/1752] Add timeout functionality to exchange SIP --- modules/aca/exchange_booking.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 398d8c19..7aabef58 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -95,6 +95,7 @@ def on_update self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false self[:name] = self[:room_name] = setting(:room_name) || system.name + self[:timeout] = setting(:timeout) || false self[:control_url] = setting(:booking_control_url) || system.config.support_url self[:booking_controls] = setting(:booking_controls) @@ -104,6 +105,9 @@ def on_update self[:booking_hide_user] = setting(:booking_hide_user) self[:booking_hide_description] = setting(:booking_hide_description) self[:booking_hide_timeline] = setting(:booking_hide_timeline) + self[:last_meeting_started] = setting(:last_meeting_started) + self[:cancel_meeting_after] = setting(:cancel_meeting_after) + @check_meeting_ending = setting(:check_meeting_ending) # seconds before meeting ending @extend_meeting_by = setting(:extend_meeting_by) || 15.minutes.to_i From 49457a90aefa038f5fecd49f84285f87a9af34fd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Apr 2018 12:11:49 +1000 Subject: [PATCH 0324/1752] Update ews user grab to accomodate other formats --- lib/microsoft/exchange.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 8d4cf6c7..8b56920e 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -54,11 +54,14 @@ def get_users(q: nil, limit: nil) ews_users.each do |user| output = {} - user[:resolution][:elems][1][:contact][:elems].each do |field| - if fields.keys.include?(field.keys[0]) - - output[fields[field.keys[0]].split(':')[0]] = self.__send__(fields[field.keys[0]].split(':')[1], field, field.keys[0]) + if user[:resolution][:elems][1] && user[:resolution][:elems][1][:contact] + user[:resolution][:elems][1][:contact][:elems].each do |field| + if fields.keys.include?(field.keys[0]) + output[fields[field.keys[0]].split(':')[0]] = self.__send__(fields[field.keys[0]].split(':')[1], field, field.keys[0]) + end end + else + output[:name] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] end output[:email] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] users.push(output) From 417f99e8b956edb6fb35a0548fb1026bf94126bf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Apr 2018 12:23:26 +1000 Subject: [PATCH 0325/1752] Fix phone retreival logic --- lib/microsoft/exchange.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 8b56920e..b83ebec0 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -36,8 +36,20 @@ def email_list(field, name=nil) end def phone_list(field, name=nil) - puts field - field[:phone_numbers][:elems][4][:entry][:text] || field[:phone_numbers][:elems][2][:entry][:text] + phone = nil + field[:phone_numbers][:elems].each do |entry| + type = entry[:entry][:attribs][:key] + text = entry[:entry][:text] + + next unless text.present? + + if type == "MobilePhone" + return text + elsif type == "BusinessPhone" || phone.present? + phone = text + end + end + phone end From dfd91646f2ba19d0aa9345fa132b8306befb26a0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Apr 2018 12:29:19 +1000 Subject: [PATCH 0326/1752] Fix user retreival logic --- lib/microsoft/exchange.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index b83ebec0..8ff31e9b 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -66,16 +66,20 @@ def get_users(q: nil, limit: nil) ews_users.each do |user| output = {} - if user[:resolution][:elems][1] && user[:resolution][:elems][1][:contact] - user[:resolution][:elems][1][:contact][:elems].each do |field| - if fields.keys.include?(field.keys[0]) - output[fields[field.keys[0]].split(':')[0]] = self.__send__(fields[field.keys[0]].split(':')[1], field, field.keys[0]) - end + + user[:resolution][:elems][1][:contact][:elems].each do |field| + if fields.keys.include?(field.keys[0]) + key = field.keys[0].split(':') + val = self.__send__(fields[key[1]], field, field.keys[0]) + output[fields[key[0]]] = val if val end - else + end + + if output[:name].nil? output[:name] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] end output[:email] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] + users.push(output) end STDERR.puts users From 6d24a79743c0f62d50902270df2e336f7499c03d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Apr 2018 13:32:55 +1000 Subject: [PATCH 0327/1752] Fix logic in get-users --- lib/microsoft/exchange.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 8ff31e9b..9c51e09e 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -63,23 +63,20 @@ def get_users(q: nil, limit: nil) culture: 'locale:basic_text', department: 'department:basic_text' } - + keys = fields.keys ews_users.each do |user| output = {} - user[:resolution][:elems][1][:contact][:elems].each do |field| - if fields.keys.include?(field.keys[0]) - key = field.keys[0].split(':') - val = self.__send__(fields[key[1]], field, field.keys[0]) - output[fields[key[0]]] = val if val + key = field.keys[0] + if keys.include?(key) + splits = fields[key].split(':') + output[splits[0]] = self.__send__(splits[1], field, key) end end - - if output[:name].nil? - output[:name] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] + if output['name'].nil? + output['name'] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] end - output[:email] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] - + output['email'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] users.push(output) end STDERR.puts users From ace17a21cd707eb8530de8415b1d35abe3228c9c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Apr 2018 13:45:34 +1000 Subject: [PATCH 0328/1752] Temporary revert to test --- lib/microsoft/exchange.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 9c51e09e..8ff31e9b 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -63,20 +63,23 @@ def get_users(q: nil, limit: nil) culture: 'locale:basic_text', department: 'department:basic_text' } - keys = fields.keys + ews_users.each do |user| output = {} + user[:resolution][:elems][1][:contact][:elems].each do |field| - key = field.keys[0] - if keys.include?(key) - splits = fields[key].split(':') - output[splits[0]] = self.__send__(splits[1], field, key) + if fields.keys.include?(field.keys[0]) + key = field.keys[0].split(':') + val = self.__send__(fields[key[1]], field, field.keys[0]) + output[fields[key[0]]] = val if val end end - if output['name'].nil? - output['name'] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] + + if output[:name].nil? + output[:name] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] end - output['email'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] + output[:email] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] + users.push(output) end STDERR.puts users From 90cf03dd23ee19ada907413a3462d853702c1853 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Apr 2018 13:47:43 +1000 Subject: [PATCH 0329/1752] Undo revert --- lib/microsoft/exchange.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 8ff31e9b..9c51e09e 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -63,23 +63,20 @@ def get_users(q: nil, limit: nil) culture: 'locale:basic_text', department: 'department:basic_text' } - + keys = fields.keys ews_users.each do |user| output = {} - user[:resolution][:elems][1][:contact][:elems].each do |field| - if fields.keys.include?(field.keys[0]) - key = field.keys[0].split(':') - val = self.__send__(fields[key[1]], field, field.keys[0]) - output[fields[key[0]]] = val if val + key = field.keys[0] + if keys.include?(key) + splits = fields[key].split(':') + output[splits[0]] = self.__send__(splits[1], field, key) end end - - if output[:name].nil? - output[:name] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] + if output['name'].nil? + output['name'] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] end - output[:email] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] - + output['email'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] users.push(output) end STDERR.puts users From 7c73be9bb711cf5fa39e0d3156c4449b0723438b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Apr 2018 13:54:07 +1000 Subject: [PATCH 0330/1752] Catch users without an email --- lib/microsoft/exchange.rb | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 9c51e09e..8a83f7f4 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -65,19 +65,25 @@ def get_users(q: nil, limit: nil) } keys = fields.keys ews_users.each do |user| - output = {} - user[:resolution][:elems][1][:contact][:elems].each do |field| - key = field.keys[0] - if keys.include?(key) - splits = fields[key].split(':') - output[splits[0]] = self.__send__(splits[1], field, key) + begin + output = {} + user[:resolution][:elems][1][:contact][:elems].each do |field| + key = field.keys[0] + if keys.include?(key) + splits = fields[key].split(':') + output[splits[0]] = self.__send__(splits[1], field, key) + end end + if output['name'].nil? + output['name'] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] + end + output['email'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] + users.push(output) + rescue => e + STDERR.puts "GOT USER WITHOUT EMAIL" + STDERR.puts user + STDERR.flush end - if output['name'].nil? - output['name'] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] - end - output['email'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] - users.push(output) end STDERR.puts users STDERR.puts limit From 5a4579527a6a815e571206b8da9dc358a70689fd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Apr 2018 13:56:00 +1000 Subject: [PATCH 0331/1752] Catch users without an email --- lib/microsoft/exchange.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 8a83f7f4..545d667b 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -85,10 +85,6 @@ def get_users(q: nil, limit: nil) STDERR.flush end end - STDERR.puts users - STDERR.puts limit - STDERR.puts users[0..2] - STDERR.flush limit ||= users.length limit = limit.to_i - 1 return users[0..limit.to_i] From 7e8b85e9eff1b81f581eb23d846839f2c60629b1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 15 Apr 2018 22:22:27 +1000 Subject: [PATCH 0332/1752] (QSC:remote) add response processing test against hardware --- modules/qsc/QSC Remote Examples.txt | 82 +++++++++++++++++++++++++++++ modules/qsc/q_sys_remote.rb | 78 +++++++++++++++++++++++---- 2 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 modules/qsc/QSC Remote Examples.txt diff --git a/modules/qsc/QSC Remote Examples.txt b/modules/qsc/QSC Remote Examples.txt new file mode 100644 index 00000000..ad8eb640 --- /dev/null +++ b/modules/qsc/QSC Remote Examples.txt @@ -0,0 +1,82 @@ +QSC Remote Examples + +Fader Query +component_get "B-BC8-102-CS", "PGM:Level" + +Response: +{ + "jsonrpc": "2.0", + "result": { + "Name": "B-BC8-102-CS", + "Controls": [ + { + "Name": "PGM:Level", + "String": "-10.0dB", + "Value": -10.0, + "Position": 0.66666668, + "Choices": [ + + ], + "Color": "", + "Indeterminate": false, + "Invisible": false, + "Disabled": false, + "Legend": "" + } + ] + }, + "id": 1 +} + +"Value": -30.0, +"Position": 0.0, + + + +Query Mute +component_get "B-BC8-102-CS", "PGM:Mute" + +Response: +{ + "jsonrpc": "2.0", + "result": { + "Name": "B-BC8-102-CS", + "Controls": [ + { + "Name": "PGM:Mute", + "String": "false", + "Value": 0.0, + "Position": 0.0, + "Choices": [ + + ], + "Color": "", + "Indeterminate": false, + "Invisible": false, + "Disabled": false, + "Legend": "" + } + ] + } + "id": 1 +} + + + +Mute Mic + component_set "B-BC8-102-CS", {"Name":"PGM:Mute", "Value": true} + +Response: +{ + "jsonrpc": "2.0", + "result": true, + "id": 1 +} + + + +Change Volume + + component_set "B-BC8-102-CS", {"Name":"PGM:Level", "Value": -5} + + diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index e6089217..89a59c90 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -1,3 +1,6 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + module Qsc; end class Qsc::QSysRemote @@ -39,7 +42,7 @@ def on_load end def connected - schedule.every('1m') do + schedule.every('20s') do logger.debug "Maintaining Connection" no_op end @@ -100,7 +103,7 @@ def control_get(*names, **options) # ------------------ def component_get(name, *controls, **options) # Example usage: - # component_get 'My APM', 'ent.xfade.gain', 'ent.xfade.gain2' + # component_get 'My AMP', 'ent.xfade.gain', 'ent.xfade.gain2' controls.collect! do |ctrl| { @@ -114,9 +117,10 @@ def component_get(name, *controls, **options) }, **options) end - def component_set(name, *values, **options) + def component_set(name, value, **options) # Example usage: # component_set 'My APM', { :Name => 'ent.xfade.gain', :Value => -100 }, {...} + values = Array(value) do_send(next_id, cmd: :"Component.Set", params: { :Name => name, @@ -226,7 +230,7 @@ def mixer(name, inouts, mute = false, *_, **options) sec: :Outputs } } - def fader(name, level, index, type = :matrix_out, **options) + def matrix_fader(name, level, index, type = :matrix_out, **options) info = Faders[type] params = { @@ -245,7 +249,7 @@ def fader(name, level, index, type = :matrix_out, **options) end # Named params version - def faders(ids:, level:, index:, type: :matrix_out, **options) + def matrix_faders(ids:, level:, index:, type: :matrix_out, **options) fader(ids, level, index, type, **options) end @@ -260,7 +264,7 @@ def faders(ids:, level:, index:, type: :matrix_out, **options) pri: :Outputs } } - def mute(name, value, index, type = :matrix_out, **options) + def matrix_mute(name, value, index, type = :matrix_out, **options) info = Mutes[type] params = { @@ -279,7 +283,7 @@ def mute(name, value, index, type = :matrix_out, **options) end # Named params version - def mutes(ids:, muted: true, index:, type: :matrix_out, **options) + def matrix_mutes(ids:, muted: true, index:, type: :matrix_out, **options) mute(ids, muted, index, type, **options) end @@ -288,12 +292,42 @@ def mutes(ids:, muted: true, index:, type: :matrix_out, **options) # ------------------- # RESPONSE PROCESSING # ------------------- + DECODE_OPTIONS = { + symbolize_names: true + }.freeze + def received(data, resolve, command) logger.debug { "QSys sent: #{data}" } - response = JSON.parse(data) + response = JSON.parse(data, DECODE_OPTIONS) logger.debug { JSON.pretty_generate(response) } + result = response[:result] + case result + when Hash + controls = result[:Controls] + + if controls + # Probably Component.Get + process(controls, name: result[:Name]) + elsif result[:Platform] + # StatusGet + self[:platform] = result[:Platform] + self[:state] = result[:State] + self[:design_name] = result[:DesignName] + self[:design_code] = result[:DesignCode] + self[:is_redundant] = result[:IsRedundant] + self[:is_emulator] = result[:IsEmulator] + self[:status] = result[:Status] + end + when Array + # Control.Get + process(result) + when false + # Response failure + return :abort + end + return :success end @@ -301,13 +335,37 @@ def received(data, resolve, command) protected + BoolVals = ['true', 'false'] + def process(values, name: nil) + component = name.present? ? "#{name}_" : '' + values.each do |value| + name = value[:Name] + val = value[:Value] + + next unless val + + pos = value[:Position] + str = value[:String] + + if BoolVals.include?(str) + self["fader#{component}#{name}_mute"] = str == 'true' + elsif pos + # 0 - 1000 + self["fader#{component}#{name}"] = (pos * 1000).to_i + elsif val.is_a?(String) + self["#{component}#{name}"] = val + else + self["fader#{component}#{name}"] = val + end + end + end + def next_id @id += 1 @id end def do_send(id = nil, cmd:, params: {}, **options) - # Build the request req = { jsonrpc: JsonRpcVer, @@ -316,6 +374,8 @@ def do_send(id = nil, cmd:, params: {}, **options) } req[:id] = id if id + logger.debug { "requesting: #{req}" } + # Append the null terminator cmd = req.to_json cmd << "\0" From ad91491d30d8321b16ebbe20279c64e8efac69d6 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 16 Apr 2018 10:15:32 +1000 Subject: [PATCH 0333/1752] (QSC:remote) add compatibility methods --- modules/qsc/QSC Remote Examples.txt | 11 ++++- modules/qsc/q_sys_remote.rb | 75 +++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/modules/qsc/QSC Remote Examples.txt b/modules/qsc/QSC Remote Examples.txt index ad8eb640..6c3e257b 100644 --- a/modules/qsc/QSC Remote Examples.txt +++ b/modules/qsc/QSC Remote Examples.txt @@ -1,5 +1,9 @@ QSC Remote Examples + +"B-BC8-102-CS", "PGM:Select" + + Fader Query component_get "B-BC8-102-CS", "PGM:Level" @@ -74,9 +78,14 @@ Response: } - Change Volume component_set "B-BC8-102-CS", {"Name":"PGM:Level", "Value": -5} +OR + component_set "B-BC8-102-CS", {"Name":"PGM:Level", "Position": 0.6} + + +Select Source + component_set "B-BC8-102-CS", {"Name":"PGM:Select", "Value": "1"} diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index 89a59c90..eeec025c 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -288,6 +288,61 @@ def matrix_mutes(ids:, muted: true, index:, type: :matrix_out, **options) end + # --------------------- + # COMPATIBILITY METHODS + # --------------------- + + def fader(fader_id, level, component = nil, type = :fader) + faders = Array(fader_id) + if component + faders.map! do |fad| + {Name: fad, Value: level} + end + component_set(component, faders) + else + faders.each { |fad| control_set(fad, level) } + end + end + + def faders(ids:, level:, component: nil, type: :fader, **_) + fader(ids, level, component, type) + end + + def mute(fader_id, value = true, component = nil, type = :fader) + val = is_affirmative?(value) + fader(fader_id, val, component, type) + end + + def mutes(ids:, muted: true, component: nil, type: :fader, **_) + mute(ids, muted, component, type) + end + + def unmute(fader_id, component = nil, type = :fader) + mute(fader_id, false, component, type) + end + + def query_fader(fader_id, component = nil, type = :fader) + faders = Array(fader_id) + + if component + component_get(component, faders) + else + control_get(faders) + end + end + + def query_faders(ids:, component: nil, type: :fader, **_) + query_fader(ids, component, type) + end + + def query_mute(fader_id, component = nil, type = :fader) + query_fader(fader_id, component, type) + end + + def query_mutes(ids:, component: nil, type: :fader, **_) + query_fader(ids, component, type) + end + # ------------------- # RESPONSE PROCESSING @@ -302,6 +357,12 @@ def received(data, resolve, command) response = JSON.parse(data, DECODE_OPTIONS) logger.debug { JSON.pretty_generate(response) } + err = response[:error] + if err + logger.warn "Error code #{err[:code]} - #{Errors[err[:code]]}\n#{err[:message]}" + return :abort + end + result = response[:result] case result when Hash @@ -323,9 +384,6 @@ def received(data, resolve, command) when Array # Control.Get process(result) - when false - # Response failure - return :abort end return :success @@ -347,11 +405,18 @@ def process(values, name: nil) pos = value[:Position] str = value[:String] + # Seems like string values can be independant of the other values + # This should mostly work to detect a string value + if val == 0.0 && pos == 0.0 && str[0] != '0' + self["#{component}#{name}"] = str + next + end + if BoolVals.include?(str) self["fader#{component}#{name}_mute"] = str == 'true' elsif pos - # 0 - 1000 - self["fader#{component}#{name}"] = (pos * 1000).to_i + # Float between 0 and 1 + self["fader#{component}#{name}"] = pos elsif val.is_a?(String) self["#{component}#{name}"] = val else From f253bd0a68d6b524c0893465e163381592bb821e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 16 Apr 2018 10:22:58 +1000 Subject: [PATCH 0334/1752] (QSC:remote) name compatible component fader command This should improve performance when using faders --- modules/qsc/q_sys_remote.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index eeec025c..ad25fae3 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -298,7 +298,7 @@ def fader(fader_id, level, component = nil, type = :fader) faders.map! do |fad| {Name: fad, Value: level} end - component_set(component, faders) + component_set(component, faders, name: "level_#{faders[0]}") else faders.each { |fad| control_set(fad, level) } end From a6770d1ef214438e3a7dca932ec1b5a2461fa3de Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 16 Apr 2018 12:34:39 +1000 Subject: [PATCH 0335/1752] (QSC:remote) support abstracting fader values So we can use integer ranges vs float ranges if required --- modules/qsc/QSC Remote Examples.txt | 7 ------ modules/qsc/q_sys_remote.rb | 37 ++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/modules/qsc/QSC Remote Examples.txt b/modules/qsc/QSC Remote Examples.txt index 6c3e257b..1c58ff12 100644 --- a/modules/qsc/QSC Remote Examples.txt +++ b/modules/qsc/QSC Remote Examples.txt @@ -1,9 +1,6 @@ QSC Remote Examples -"B-BC8-102-CS", "PGM:Select" - - Fader Query component_get "B-BC8-102-CS", "PGM:Level" @@ -32,10 +29,6 @@ Response: "id": 1 } -"Value": -30.0, -"Position": 0.0, - - Query Mute component_get "B-BC8-102-CS", "PGM:Mute" diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index ad25fae3..f0edae35 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -39,6 +39,12 @@ class Qsc::QSysRemote def on_load + on_update + end + + def on_update + @db_based_faders = setting(:db_based_faders) + @integer_faders = setting(:integer_faders) end def connected @@ -295,12 +301,23 @@ def matrix_mutes(ids:, muted: true, index:, type: :matrix_out, **options) def fader(fader_id, level, component = nil, type = :fader) faders = Array(fader_id) if component - faders.map! do |fad| - {Name: fad, Value: level} + if @db_based_faders + level = level.to_f / 10.0 if @integer_faders + faders.map! do |fad| + {Name: fad, Value: level} + end + else + level = level.to_f / 1000.0 if @integer_faders + faders.map! do |fad| + {Name: fad, Position: level} + end + end + component_set(component, faders, name: "level_#{faders[0]}").then do + component_get(component, faders) end - component_set(component, faders, name: "level_#{faders[0]}") else - faders.each { |fad| control_set(fad, level) } + reqs = faders.collect { |fad| control_set(fad, level) } + reqs.first.then { control_get(faders) } end end @@ -416,11 +433,19 @@ def process(values, name: nil) self["fader#{component}#{name}_mute"] = str == 'true' elsif pos # Float between 0 and 1 - self["fader#{component}#{name}"] = pos + if @integer_faders + self["fader#{component}#{name}"] = (pos * 1000).to_i + else + self["fader#{component}#{name}"] = pos + end elsif val.is_a?(String) self["#{component}#{name}"] = val else - self["fader#{component}#{name}"] = val + if @integer_faders + self["fader#{component}#{name}"] = (val * 10).to_i + else + self["fader#{component}#{name}"] = val + end end end end From 5de3c9d95fd53a71893501a27dfe23e96d536a50 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 16 Apr 2018 15:22:52 +1000 Subject: [PATCH 0336/1752] (QSC:remote) improve feedback processing also improves request generation when there are multiple faders or mutes --- modules/qsc/q_sys_remote.rb | 67 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index f0edae35..d69e3de5 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -100,7 +100,7 @@ def control_set(name, value, ramp = nil, **options) end def control_get(*names, **options) - do_send(next_id, cmd: :"Control.Get", params: names, **options) + do_send(next_id, cmd: :"Control.Get", params: names.flatten, **options) end @@ -110,12 +110,8 @@ def control_get(*names, **options) def component_get(name, *controls, **options) # Example usage: # component_get 'My AMP', 'ent.xfade.gain', 'ent.xfade.gain2' - - controls.collect! do |ctrl| - { - :Name => ctrl - } - end + controls = controls.flatten + controls.collect! { |ctrl| { :Name => ctrl } } do_send(next_id, cmd: :"Component.Get", params: { :Name => name, @@ -126,7 +122,8 @@ def component_get(name, *controls, **options) def component_set(name, value, **options) # Example usage: # component_set 'My APM', { :Name => 'ent.xfade.gain', :Value => -100 }, {...} - values = Array(value) + values = value.is_a?(Array) ? value : [value] + # NOTE:: Can't use Array() helper on hashes as they are converted to arrays. do_send(next_id, cmd: :"Component.Set", params: { :Name => name, @@ -298,26 +295,26 @@ def matrix_mutes(ids:, muted: true, index:, type: :matrix_out, **options) # COMPATIBILITY METHODS # --------------------- - def fader(fader_id, level, component = nil, type = :fader) + def fader(fader_id, level, component = nil, type = :fader, use_value: false) faders = Array(fader_id) if component - if @db_based_faders + if @db_based_faders || use_value level = level.to_f / 10.0 if @integer_faders - faders.map! do |fad| + fads = faders.map do |fad| {Name: fad, Value: level} end else level = level.to_f / 1000.0 if @integer_faders - faders.map! do |fad| + fads = faders.map do |fad| {Name: fad, Position: level} end end - component_set(component, faders, name: "level_#{faders[0]}").then do + component_set(component, fads, name: "level_#{faders[0]}").then do component_get(component, faders) end else reqs = faders.collect { |fad| control_set(fad, level) } - reqs.first.then { control_get(faders) } + reqs.last.then { control_get(faders) } end end @@ -327,7 +324,7 @@ def faders(ids:, level:, component: nil, type: :fader, **_) def mute(fader_id, value = true, component = nil, type = :fader) val = is_affirmative?(value) - fader(fader_id, val, component, type) + fader(fader_id, val, component, type, use_value: true) end def mutes(ids:, muted: true, component: nil, type: :fader, **_) @@ -422,29 +419,31 @@ def process(values, name: nil) pos = value[:Position] str = value[:String] - # Seems like string values can be independant of the other values - # This should mostly work to detect a string value - if val == 0.0 && pos == 0.0 && str[0] != '0' - self["#{component}#{name}"] = str - next - end - if BoolVals.include?(str) self["fader#{component}#{name}_mute"] = str == 'true' - elsif pos - # Float between 0 and 1 - if @integer_faders - self["fader#{component}#{name}"] = (pos * 1000).to_i - else - self["fader#{component}#{name}"] = pos - end - elsif val.is_a?(String) - self["#{component}#{name}"] = val else - if @integer_faders - self["fader#{component}#{name}"] = (val * 10).to_i + # Seems like string values can be independant of the other values + # This should mostly work to detect a string value + if val == 0.0 && pos == 0.0 && str[0] != '0' + self["#{component}#{name}"] = str + next + end + + if pos + # Float between 0 and 1 + if @integer_faders + self["fader#{component}#{name}"] = (pos * 1000).to_i + else + self["fader#{component}#{name}"] = pos + end + elsif val.is_a?(String) + self["#{component}#{name}"] = val else - self["fader#{component}#{name}"] = val + if @integer_faders + self["fader#{component}#{name}"] = (val * 10).to_i + else + self["fader#{component}#{name}"] = val + end end end end From d6c803b83b1319640538f101f02d238528999473 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 17 Apr 2018 15:18:11 +1000 Subject: [PATCH 0337/1752] fix syntax in exchange booking directory search --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 23931127..d9f5e746 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -241,7 +241,7 @@ def directory_search(q, limit: 30) entries << entry entries << ({ - name: entry[:name] + name: entry[:name], phone: phone.gsub(/\D+/, '') }) if phone end From a088a0352591b6e5b97dcc6c63b90fe1efa91a6e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 17 Apr 2018 18:23:18 +1000 Subject: [PATCH 0338/1752] (microsoft:exchange) add close function ensures all connections are closed --- lib/microsoft/exchange.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 545d667b..7bc1a48e 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -31,6 +31,10 @@ def basic_text(field, name) field[name][:text] end + def close + @ews_client.ews.connection.httpcli.reset_all + end + def email_list(field, name=nil) field[:email_addresses][:elems][-1][:entry][:text].gsub(/SMTP:|SIP:|sip:|smtp:/,'') end From 44b5ca95c54c4bbc47f6495199e2654fb2b52e92 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 18 Apr 2018 12:34:38 +1000 Subject: [PATCH 0339/1752] Fix meeting cancellation request --- modules/aca/exchange_booking.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 23931127..11e53d87 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -365,7 +365,10 @@ def start_meeting(meeting_ref) def cancel_meeting(start_time) task { - delete_ews_booking (start_time / 1000).to_i + if start_time.class == Integer + delete_ews_booking (start_time / 1000).to_i + else + delete_ews_booking start_time.to_i }.then(proc { |count| logger.debug { "successfully removed #{count} bookings" } From 147256e6c9d779fe3264241e6f1e04a0e813b7d4 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 18 Apr 2018 12:36:32 +1000 Subject: [PATCH 0340/1752] Fix meeting cancellation request --- modules/aca/exchange_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index f8decf34..c1194f63 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -370,6 +370,7 @@ def cancel_meeting(start_time) delete_ews_booking (start_time / 1000).to_i else delete_ews_booking start_time.to_i + end }.then(proc { |count| logger.debug { "successfully removed #{count} bookings" } From be1279e1c768b49f1a78e720bd4acb1d83cd006c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 18 Apr 2018 12:38:07 +1000 Subject: [PATCH 0341/1752] Fix meeting cancellation request --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index c1194f63..2bfeabbc 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -631,7 +631,7 @@ def delete_ews_booking(delete_at) if @use_act_as # TODO:: think this line can be removed?? - delete_at = Time.parse(delete_at.to_s).to_i + # delete_at = Time.parse(delete_at.to_s).to_i opts = {} opts[:act_as] = @ews_room if @ews_room From 9242e7e8ebbb5bb8162b75db07fcb23cedceb687 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 18 Apr 2018 12:55:25 +1000 Subject: [PATCH 0342/1752] Fix meeting cancellation request --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 2bfeabbc..0031d00c 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -728,7 +728,7 @@ def todays_bookings logger.debug { item.inspect } # Prevent connections handing with TIME_WAIT - cli.ews.connection.httpcli.reset_all + # cli.ews.connection.httpcli.reset_all subject = item[:subject] From 8b238a7880badd343d80a69bf511fae694ceb561 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 18 Apr 2018 14:26:32 +1000 Subject: [PATCH 0343/1752] Change range to 2 days and add title --- lib/microsoft/exchange.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 7bc1a48e..32a21ffd 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -167,7 +167,7 @@ def get_available_rooms(rooms:, start_time:, end_time:) free_rooms end - def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime.now.midnight + 1.week)) + def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime.now.midnight + 2.days)) begin if [Integer, String].include?(start_param.class) start_param = DateTime.parse(Time.at(start_param.to_i / 1000).to_s) @@ -217,6 +217,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: booking = {} booking[:subject] = subject + booking[:title] = subject booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop # booking[:body] = description booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop From 6c1dba6644a8ebf36617de015d77fb9ef410a7b4 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 18 Apr 2018 15:29:50 +1000 Subject: [PATCH 0344/1752] fix syntax: office365 booking driver --- modules/aca/office_booking.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 99d75f6d..57e9d2ed 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -610,9 +610,6 @@ def delete_ews_booking(delete_at) def todays_bookings(response, office_organiser_location) results = [] response.each{|booking| - - meeting_response.each{|booking| - # start_time = Time.parse(booking['start']['dateTime']).utc.iso8601[0..18] + 'Z' # end_time = Time.parse(booking['end']['dateTime']).utc.iso8601[0..18] + 'Z' start_time = ActiveSupport::TimeZone.new('UTC').parse(booking['start']['dateTime']).iso8601 From 4b63d6aa95d8ddc77835c5f76bde4e4eabd63ab8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 16 Apr 2018 19:01:10 +1000 Subject: [PATCH 0345/1752] (aca:router) define initial data structure and parsing from settings --- modules/aca/router.rb | 244 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 modules/aca/router.rb diff --git a/modules/aca/router.rb b/modules/aca/router.rb new file mode 100644 index 00000000..cb9c1d75 --- /dev/null +++ b/modules/aca/router.rb @@ -0,0 +1,244 @@ +# frozen_string_literal: true + +require 'algorithms' +require 'set' + +module Aca; end + +class Aca::Router + include ::Orchestrator::Constants + + descriptive_name 'ACA Signal Router' + generic_name :Router + implements :logic + description <<~DESC + Signal distribution management for handling routing across multiple + devices and complex/layered switching infrastructure. + DESC + + def on_load + on_update + end + + def on_update + build_graph(setting(:connections) || {}) + end + + # Route a set of signals to arbitrary destinations. + # + # `map` is a hashmap of the structure `{ source: target | [targets] }` + # + # Multiple sources can be specified simultaneously, or if connecting a + # single source to a single destination, Ruby's implicit hash syntax can be + # exploited to let you express it neatly as `connect source => target`. + def connect(map) + # 1. Turn map -> list of paths (list of lists of nodes) + # 2. Check for intersections of node lists for different sources + # - get unique nodes for each source + # - intersect lists + # - confirm empty + # 3. Perform device interactions + # - step through paths and pair device interactions into tuples representing switch events + # - flatten and uniq + # - remove singular nodes (passthrough) + # - execute interactions + # 4. Consolidate each path into a success / fail + # 5. Raise exceptions / log errors for failures + # 6. Return consolidated promise + + map.each_pair do |source, targets| + Array(targets).each { |target| route source, target } + end + end + + protected + + def signal_graph + @signal_graph ||= SignalGraph.new + end + + def build_graph(map) + logger.debug 'building graph from signal map' + + @signal_graph = SignalGraph.from_map map + + nodes = signal_graph.map(&:id) + self[:nodes] = nodes + self[:inputs] = nodes.select { |x| signal_graph.outdegree(x).zero? } + self[:outputs] = nodes.select { |x| signal_graph.indegree(x).zero? } + end + + # Given a list of nodes that form a path, execute the device level + # interactions to switch a signal across across them. + def switch(path) + + end + + # Find the shortest path between a source and target node and return the + # list of nodes which form it. + def route(source, target) + + end +end + +# Graph data structure for respresentating abstract signal networks. +# +# All signal sinks and sources are represented as nodes, with directed edges +# holding connectivity information and a lambda that can be executed to +# 'activate' the edge, performing any device level interaction for signal +# switching. +# +# Directivity of the graph is inverted from the signal flow - edges use signal +# sinks as source and signal sources as their terminus so that a nodes may +# be efficiently added or removed taking all incoming signal connections with +# them. +class Aca::Router::SignalGraph + Edge = Struct.new :source, :target, :selector do + def activate + selector&.call + end + end + + class Node + attr_reader :id, :edges + + def initialize(id) + @id = id.to_sym + @edges = Set.new + end + + def join(other, selector = nil) + edges << Edge.new(self, other, selector) + self + end + + def neighbours + edges.map(&:target) + end + + def inspect + "#{id} --> [#{neighbours.join ' '}]" + end + + def to_s + id.to_s + end + + def eql?(other) + id == other.id + end + + def hash + id.hash + end + end + + include Enumerable + + attr_reader :nodes + + def initialize + @nodes = ActiveSupport::HashWithIndifferentAccess.new + end + + def <<(id) + nodes[id] ||= Node.new id + self + end + + def join(source, target, &selector) + nodes[source].join nodes[target], selector + self + end + + def [](id) + nodes[id] + end + + def each(&block) + nodes.values.each(&block) + end + + def indegree(id) + reduce(0) do |degree, node| + degree + node.neighbours.select { |x| x.id == id }.size + end + end + + def outdegree(id) + nodes[id].edges.size + end + + def inspect + object_identifier = "#{self.class.name}:0x#{format('%02x', object_id)}" + nodes = map(&:inspect).join ', ' + "#<#{object_identifier} @nodes={ #{nodes} }>" + end + + def to_s + "{ #{to_a.join ', '} }" + end + + # Build a signal map from a nested hash of input connectivity. + # + # `map` should be of the structure + # { device: { input_name: source } } + # or + # { device: [source] } + # + # When inputs are specified as an array, 1-based indicies will be used. + # + # Sources which exist on matrix switchers are defined as "device__output". + # + # For example, a map containing two displays and 2 laptop inputs, all + # connected via 2x2 matrix switcher would be: + # { + # Display_1: { + # hdmi: :Switcher_1__1 + # }, + # Display_2: { + # hdmi: :Switcher_1__2 + # }, + # Switcher_1: [:Laptop_1, :Laptop_2] + # } + # + def self.from_map(map) + graph = new + + matrix_nodes = [] + + to_hash = proc { |x| x.is_a?(Array) ? Hash[(1..x.size).zip x] : x } + + map.transform_values!(&to_hash).each_pair do |device, inputs| + # Create the node for the signal sink + graph << device + + inputs.each_pair do |input, source| + # Create a node and edge to each input source + graph << source + graph.join(device, source) do + system[device].switch_to input + end + + # Check is the input is a matrix switcher + upstream_device, output = source.split '__' + next if output.nil? + + matrix_nodes |= [upstream_device] + + # Push in nodes and edges to each matrix input + matrix_inputs = map[upstream_device] + matrix_inputs.each_pair do |matrix_input, upstream_source| + graph << upstream_source + graph.join(source, upstream_source) do + system[upstream_device].switch matrix_input => output + end + end + end + end + + # Remove any temp 'matrix device nodes' as we now how fully connected + # nodes for each input and output. + graph.tap { |g| g.nodes.except!(*matrix_nodes) } + end +end From f27bded7f42642e5d79d07bed8e76fa045a092c3 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 18 Apr 2018 21:41:37 +1000 Subject: [PATCH 0346/1752] (aca:router) update naming to make edge directivity clear --- modules/aca/router.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index cb9c1d75..b591c594 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -112,12 +112,12 @@ def join(other, selector = nil) self end - def neighbours + def successors edges.map(&:target) end def inspect - "#{id} --> [#{neighbours.join ' '}]" + "#{id} --> [#{successors.join ' '}]" end def to_s @@ -161,7 +161,7 @@ def each(&block) def indegree(id) reduce(0) do |degree, node| - degree + node.neighbours.select { |x| x.id == id }.size + degree + node.successors.select { |x| x.id == id }.size end end From b5ae7d03217fb78e9b01a6ee057d6002d2003ec6 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 18 Apr 2018 22:13:31 +1000 Subject: [PATCH 0347/1752] (aca:router) move all calculation of source/sink nodes into graph --- modules/aca/router.rb | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index b591c594..d8fcfd26 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -62,10 +62,9 @@ def build_graph(map) @signal_graph = SignalGraph.from_map map - nodes = signal_graph.map(&:id) - self[:nodes] = nodes - self[:inputs] = nodes.select { |x| signal_graph.outdegree(x).zero? } - self[:outputs] = nodes.select { |x| signal_graph.indegree(x).zero? } + self[:nodes] = signal_graph.map(&:id) + self[:inputs] = signal_graph.sinks.map(&:id) + self[:outputs] = signal_graph.sources.map(&:id) end # Given a list of nodes that form a path, execute the device level @@ -159,14 +158,30 @@ def each(&block) nodes.values.each(&block) end - def indegree(id) - reduce(0) do |degree, node| - degree + node.successors.select { |x| x.id == id }.size + def sources + select { |node| indegree(node.id).zero? } + end + + def sinks + select { |node| outdegree(node.id).zero? } + end + + def incoming_edges(id) + reduce(Set.new) do |edges, node| + edges | node.successors.select { |x| x.id == id } end end + def outgoing_edges(id) + nodes[id].edges + end + + def indegree(id) + incoming_edges(id).size + end + def outdegree(id) - nodes[id].edges.size + outgoing_edges(id).size end def inspect From 664c5a03204b107269427f9a71f68ac9a7de71df Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 18 Apr 2018 22:24:19 +1000 Subject: [PATCH 0348/1752] (aca:router) shift graph creation directly into update method --- modules/aca/router.rb | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index d8fcfd26..6803acfc 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -21,7 +21,13 @@ def on_load end def on_update - build_graph(setting(:connections) || {}) + logger.debug 'building graph from signal map' + + @signal_graph = SignalGraph.from_map(setting(:connections) || {}) + + self[:nodes] = signal_graph.map(&:id) + self[:inputs] = signal_graph.sinks.map(&:id) + self[:outputs] = signal_graph.sources.map(&:id) end # Route a set of signals to arbitrary destinations. @@ -30,7 +36,7 @@ def on_update # # Multiple sources can be specified simultaneously, or if connecting a # single source to a single destination, Ruby's implicit hash syntax can be - # exploited to let you express it neatly as `connect source => target`. + # used to let you express it neatly as `connect source => target`. def connect(map) # 1. Turn map -> list of paths (list of lists of nodes) # 2. Check for intersections of node lists for different sources @@ -57,16 +63,6 @@ def signal_graph @signal_graph ||= SignalGraph.new end - def build_graph(map) - logger.debug 'building graph from signal map' - - @signal_graph = SignalGraph.from_map map - - self[:nodes] = signal_graph.map(&:id) - self[:inputs] = signal_graph.sinks.map(&:id) - self[:outputs] = signal_graph.sources.map(&:id) - end - # Given a list of nodes that form a path, execute the device level # interactions to switch a signal across across them. def switch(path) From 7ca7a3967908984868e6ff7d11543d6597469b5b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 18 Apr 2018 23:21:43 +1000 Subject: [PATCH 0349/1752] (aca:router) return array rather than set for edge lists --- modules/aca/router.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 6803acfc..ba9ba6a2 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -163,13 +163,13 @@ def sinks end def incoming_edges(id) - reduce(Set.new) do |edges, node| + reduce([]) do |edges, node| edges | node.successors.select { |x| x.id == id } end end def outgoing_edges(id) - nodes[id].edges + nodes[id].edges.to_a end def indegree(id) From 355ea9886af1b2b4b2aef39d3e27fe7d660a5e53 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 18 Apr 2018 23:22:44 +1000 Subject: [PATCH 0350/1752] (aca:router) provide a named insert method --- modules/aca/router.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index ba9ba6a2..ea816c5a 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -136,13 +136,15 @@ def initialize @nodes = ActiveSupport::HashWithIndifferentAccess.new end - def <<(id) + def insert(id) nodes[id] ||= Node.new id self end def join(source, target, &selector) nodes[source].join nodes[target], selector + alias << insert + self end From d6df236b69bf4864c98c51044709b0cc25c01e42 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 18 Apr 2018 23:26:02 +1000 Subject: [PATCH 0351/1752] (aca:router) implement node removal --- modules/aca/router.rb | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index ea816c5a..ee01f36c 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -136,20 +136,35 @@ def initialize @nodes = ActiveSupport::HashWithIndifferentAccess.new end + def [](id) + nodes[id] + end + def insert(id) nodes[id] ||= Node.new id self end - def join(source, target, &selector) - nodes[source].join nodes[target], selector alias << insert + # If there is *certainty* the node has no incoming edges (i.e. it was a temp + # node used during graph construction), `check_incoming_edges` can be set + # to false to keep this O(1) rather than O(n). Using this at any other time + # will result in a memory leak and general bad things. Don't do it. + def delete(id, check_incoming_edges: true) + nodes.except! id + + if check_incoming_edges + incoming = proc { |edge| edge.target.id == id } + each { |node| nodes.edges.reject!(&incoming) } + end + self end - def [](id) - nodes[id] + def join(source, target, &selector) + nodes[source].join nodes[target], selector + self end def each(&block) @@ -252,6 +267,8 @@ def self.from_map(map) # Remove any temp 'matrix device nodes' as we now how fully connected # nodes for each input and output. - graph.tap { |g| g.nodes.except!(*matrix_nodes) } + matrix_nodes.reduce(graph) do |g, node| + g.delete node, check_incoming_edges: false + end end end From 7829e1b942a16490debcf1f69be4386920c6f311 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 19 Apr 2018 01:03:10 +1000 Subject: [PATCH 0352/1752] (aca:router) fix issue with node equality check --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index ee01f36c..486ad690 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -120,7 +120,7 @@ def to_s end def eql?(other) - id == other.id + id == other end def hash From c69b99a67135b97c785ee43a99e2431e35d01f07 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 19 Apr 2018 01:03:35 +1000 Subject: [PATCH 0353/1752] (aca:router) update comments --- modules/aca/router.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 486ad690..9acff8d1 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -84,9 +84,9 @@ def route(source, target) # switching. # # Directivity of the graph is inverted from the signal flow - edges use signal -# sinks as source and signal sources as their terminus so that a nodes may -# be efficiently added or removed taking all incoming signal connections with -# them. +# sinks as source and signal sources as their terminus. This optimises for +# cheap removal of signal sinks and better path finding (as most environments +# will have a small number of displays and a large number of sources). class Aca::Router::SignalGraph Edge = Struct.new :source, :target, :selector do def activate @@ -248,7 +248,8 @@ def self.from_map(map) system[device].switch_to input end - # Check is the input is a matrix switcher + # Check is the input is a matrix switcher or multi-output + # device (such as a USB switch). upstream_device, output = source.split '__' next if output.nil? From 9788174924c7ba5287b542da0e7d92b3a55e9c13 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 19 Apr 2018 01:04:35 +1000 Subject: [PATCH 0354/1752] (aca:router) implement dijkstra based on min heap --- modules/aca/router.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 9acff8d1..9a8c3b2b 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -197,6 +197,28 @@ def outdegree(id) outgoing_edges(id).size end + def dijkstra(id) + active = Containers::PriorityQueue.new { |x, y| (x <=> y) == -1 } + distance = Hash.new { 1.0 / 0.0 } + predecessor = {} + + distance[id] = 0 + active.push nodes[id], distance[id] + + until active.empty? + u = active.pop + u.successors.each do |v| + alt = distance[u.id] + 1 + next unless alt < distance[v.id] + distance[v.id] = alt + predecessor[v.id] = u + active.push v, distance[v.id] + end + end + + [distance, predecessor] + end + def inspect object_identifier = "#{self.class.name}:0x#{format('%02x', object_id)}" nodes = map(&:inspect).join ', ' From 1f79f99f6b028cce5f1f36479cc0b9ad7db1baa3 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 19 Apr 2018 09:32:53 +1000 Subject: [PATCH 0355/1752] (aca:router) improve readability of `inspect`ed nodes --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 9a8c3b2b..becbe09f 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -112,7 +112,7 @@ def successors end def inspect - "#{id} --> [#{successors.join ' '}]" + "#{id} --> { #{successors.join ', '} }" end def to_s From 9c36f5c94c879dd1f8cdfd266c1cf6f1cf065952 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 19 Apr 2018 09:42:38 +1000 Subject: [PATCH 0356/1752] (aca:router) implement `route` --- modules/aca/router.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index becbe09f..763ab70d 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -72,7 +72,14 @@ def switch(path) # Find the shortest path between a source and target node and return the # list of nodes which form it. def route(source, target) - + _, predecessor = signal_graph.dijkstra target + nodes = [] + next_node = signal_graph[source] + until next_node.nil? + nodes << next_node + next_node = predecessor[next_node.id] + end + nodes end end From e74edf897e71367212b1c98195373103f2c38e8f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 23 Apr 2018 13:23:09 +1000 Subject: [PATCH 0357/1752] (aca:router) momoise shortest paths --- modules/aca/router.rb | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 763ab70d..9384c66d 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -23,7 +23,9 @@ def on_load def on_update logger.debug 'building graph from signal map' - @signal_graph = SignalGraph.from_map(setting(:connections) || {}) + @route_cache = nil + + @signal_graph = SignalGraph.from_map(setting(:connections) || {}).freeze self[:nodes] = signal_graph.map(&:id) self[:inputs] = signal_graph.sinks.map(&:id) @@ -63,6 +65,12 @@ def signal_graph @signal_graph ||= SignalGraph.new end + def paths + @route_cache ||= Hash.new do |hash, node| + hash[node] = signal_graph.dijkstra node + end + end + # Given a list of nodes that form a path, execute the device level # interactions to switch a signal across across them. def switch(path) @@ -72,12 +80,20 @@ def switch(path) # Find the shortest path between a source and target node and return the # list of nodes which form it. def route(source, target) - _, predecessor = signal_graph.dijkstra target + path = paths[target] + + distance = path.distance_to[source] + raise "no route from #{source} to #{target}" if distance.infinite? + + logger.debug do + "found route from #{source} to #{target} in #{distance} hops" + end + nodes = [] next_node = signal_graph[source] until next_node.nil? nodes << next_node - next_node = predecessor[next_node.id] + next_node = path.predecessor[next_node.id] end nodes end @@ -95,6 +111,8 @@ def route(source, target) # cheap removal of signal sinks and better path finding (as most environments # will have a small number of displays and a large number of sources). class Aca::Router::SignalGraph + Paths = Struct.new :distance_to, :predecessor + Edge = Struct.new :source, :target, :selector do def activate selector&.call @@ -206,24 +224,24 @@ def outdegree(id) def dijkstra(id) active = Containers::PriorityQueue.new { |x, y| (x <=> y) == -1 } - distance = Hash.new { 1.0 / 0.0 } + distance_to = Hash.new { 1.0 / 0.0 } predecessor = {} - distance[id] = 0 - active.push nodes[id], distance[id] + distance_to[id] = 0 + active.push nodes[id], distance_to[id] until active.empty? u = active.pop u.successors.each do |v| - alt = distance[u.id] + 1 - next unless alt < distance[v.id] - distance[v.id] = alt + alt = distance_to[u.id] + 1 + next unless alt < distance_to[v.id] + distance_to[v.id] = alt predecessor[v.id] = u - active.push v, distance[v.id] + active.push v, distance_to[v.id] end end - [distance, predecessor] + Paths.new distance_to, predecessor end def inspect From de751ecf2d8c616828a3bcee0273f5d4923c1e15 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 23 Apr 2018 13:24:07 +1000 Subject: [PATCH 0358/1752] (aca:router) protect against interacting with nodes that don't exist --- modules/aca/router.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 9384c66d..05494b71 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -158,7 +158,9 @@ def hash attr_reader :nodes def initialize - @nodes = ActiveSupport::HashWithIndifferentAccess.new + @nodes = ActiveSupport::HashWithIndifferentAccess.new do |_, id| + raise ArgumentError, "\"#{id}\" does not exist" + end end def [](id) @@ -166,7 +168,7 @@ def [](id) end def insert(id) - nodes[id] ||= Node.new id + nodes[id] = Node.new id unless nodes.key? id self end From ba6c5ee4091022124d6c296974b0af0e62038d91 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 23 Apr 2018 19:26:29 +1000 Subject: [PATCH 0359/1752] (biamp:tesira) Telnet: add connection timeout as tested OK pwcsg --- modules/biamp/tesira.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/biamp/tesira.rb b/modules/biamp/tesira.rb index 26e8825d..2a3d693b 100755 --- a/modules/biamp/tesira.rb +++ b/modules/biamp/tesira.rb @@ -33,6 +33,7 @@ class Biamp::Tesira def on_load # Implement the Telnet protocol + defaults timeout: 15000 new_telnet_client config before_buffering: proc { |data| @telnet.buffer data From 21f8ac22294c0d81bdde4aca3b5afbb40e328394 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 23 Apr 2018 19:29:40 +1000 Subject: [PATCH 0360/1752] (sony:projector) Serial control: update inputs inputs a-c tested OK pwcsg (hdmi1 and hdmi2 are invalid for the tested model, may work for other models) --- modules/sony/projector/serial_control.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/sony/projector/serial_control.rb b/modules/sony/projector/serial_control.rb index 26a70666..9b7ac019 100755 --- a/modules/sony/projector/serial_control.rb +++ b/modules/sony/projector/serial_control.rb @@ -62,8 +62,11 @@ def power?(**options, &block) # Input selection # INPUTS = { - hdmi: [0x00, 0x04], - hdmi2: [0x00, 0x05] + hdmi: [0x00, 0x03], + hdmi2: [0x00, 0x03], + inputa: [0x00, 0x02], + inputb: [0x00, 0x03], + inputc: [0x00, 0x04] } INPUTS.merge!(INPUTS.invert) From e3db48e94e2ec5010e8377ee16de96d14308065b Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 23 Apr 2018 19:30:47 +1000 Subject: [PATCH 0361/1752] (sony:projector) serial control: flip mute true/false tested OK pwcsg --- modules/sony/projector/serial_control.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sony/projector/serial_control.rb b/modules/sony/projector/serial_control.rb index 9b7ac019..8ed39118 100755 --- a/modules/sony/projector/serial_control.rb +++ b/modules/sony/projector/serial_control.rb @@ -94,7 +94,7 @@ def lamp_time? def mute(val = true) logger.debug 'requested to mute' - actual = is_affirmative?(val) ? [0x00, 0x01] : [0x00, 0x00] + actual = is_affirmative?(val) ? [0x00, 0x00] : [0x00, 0x01] do_send(:set, :mute, actual, delay_on_receive: 500) end From 7babc488f09a947850e79d4f17722dbf125f30e9 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 24 Apr 2018 17:51:34 +1000 Subject: [PATCH 0362/1752] (aca:router) store edges as a hash for more efficient lookup --- modules/aca/router.rb | 45 ++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 05494b71..f9f4c4cc 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -113,33 +113,21 @@ def route(source, target) class Aca::Router::SignalGraph Paths = Struct.new :distance_to, :predecessor - Edge = Struct.new :source, :target, :selector do - def activate - selector&.call - end - end - class Node attr_reader :id, :edges def initialize(id) @id = id.to_sym - @edges = Set.new + @edges = HashWithIndifferentAccess.new do |_, other_id| + raise ArgumentError, "No edge from \"#{id}\" to \"#{other_id}\"" + end end - def join(other, selector = nil) - edges << Edge.new(self, other, selector) + def join(other_id, selector = nil) + edges[other_id] = selector self end - def successors - edges.map(&:target) - end - - def inspect - "#{id} --> { #{successors.join ', '} }" - end - def to_s id.to_s end @@ -176,21 +164,18 @@ def insert(id) # If there is *certainty* the node has no incoming edges (i.e. it was a temp # node used during graph construction), `check_incoming_edges` can be set - # to false to keep this O(1) rather than O(n). Using this at any other time - # will result in a memory leak and general bad things. Don't do it. + # to false to keep this O(1) rather than O(n). Using this flag at any other + # time will result a corrupt structure. def delete(id, check_incoming_edges: true) nodes.except! id - if check_incoming_edges - incoming = proc { |edge| edge.target.id == id } - each { |node| nodes.edges.reject!(&incoming) } - end + each { |node| node.edges.delete id } if check_incoming_edges self end def join(source, target, &selector) - nodes[source].join nodes[target], selector + nodes[source].join target, selector self end @@ -198,6 +183,10 @@ def each(&block) nodes.values.each(&block) end + def successors(id) + nodes[id].edges.keys.map { |x| nodes[x] } + end + def sources select { |node| indegree(node.id).zero? } end @@ -207,13 +196,13 @@ def sinks end def incoming_edges(id) - reduce([]) do |edges, node| - edges | node.successors.select { |x| x.id == id } + reduce(HashWithIndifferentAccess.new) do |edges, node| + edges.tap { |e| e[node.id] = node.edges[id] if node.edges.key? id } end end def outgoing_edges(id) - nodes[id].edges.to_a + nodes[id].edges end def indegree(id) @@ -234,7 +223,7 @@ def dijkstra(id) until active.empty? u = active.pop - u.successors.each do |v| + successors(u.id).each do |v| alt = distance_to[u.id] + 1 next unless alt < distance_to[v.id] distance_to[v.id] = alt From 705fbadbec4a430c7838fc36a63bf2ec634e42fc Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 24 Apr 2018 18:42:44 +1000 Subject: [PATCH 0363/1752] (aca:router) minor renaming --- modules/aca/router.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index f9f4c4cc..52a8f1a4 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -23,7 +23,7 @@ def on_load def on_update logger.debug 'building graph from signal map' - @route_cache = nil + @path_cache = nil @signal_graph = SignalGraph.from_map(setting(:connections) || {}).freeze @@ -66,7 +66,7 @@ def signal_graph end def paths - @route_cache ||= Hash.new do |hash, node| + @path_cache ||= Hash.new do |hash, node| hash[node] = signal_graph.dijkstra node end end @@ -79,21 +79,22 @@ def switch(path) # Find the shortest path between a source and target node and return the # list of nodes which form it. - def route(source, target) - path = paths[target] + def route(source, sink) + path = paths[sink] distance = path.distance_to[source] - raise "no route from #{source} to #{target}" if distance.infinite? + raise "no route from #{source} to #{sink}" if distance.infinite? logger.debug do - "found route from #{source} to #{target} in #{distance} hops" + "found route from #{source} to #{sink} in #{distance} hops" end nodes = [] - next_node = signal_graph[source] - until next_node.nil? - nodes << next_node - next_node = path.predecessor[next_node.id] + node = signal_graph[source] + until node.nil? + nodes << node + predecessor = path.predecessor[node.id] + node = predecessor end nodes end From a64ed8240cf8e3c6df12b9e75418f2616bb594db Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 12:03:11 +1000 Subject: [PATCH 0364/1752] Make ews a class variable --- lib/microsoft/exchange.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 32a21ffd..c6dee3fb 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -24,7 +24,7 @@ def initialize( STDERR.puts '--------------- NEW CLIENT CREATED --------------' STDERR.puts "At URL: #{@ews_url} with email: #{@service_account_email} and password #{@service_account_password}" STDERR.puts '-------------------------------------------------' - @ews_client ||= Viewpoint::EWSClient.new @ews_url, @service_account_email, @service_account_password, ews_opts + @@ews_client ||= Viewpoint::EWSClient.new @ews_url, @service_account_email, @service_account_password, ews_opts end def basic_text(field, name) @@ -32,7 +32,7 @@ def basic_text(field, name) end def close - @ews_client.ews.connection.httpcli.reset_all + @@ews_client.ews.connection.httpcli.reset_all end def email_list(field, name=nil) @@ -58,7 +58,7 @@ def phone_list(field, name=nil) def get_users(q: nil, limit: nil) - ews_users = @ews_client.search_contacts(q) + ews_users = @@ews_client.search_contacts(q) users = [] fields = { display_name: 'name:basic_text', @@ -121,7 +121,7 @@ def get_available_rooms(rooms:, start_time:, end_time:) STDERR.flush # Get booking data for all rooms between time range bounds - user_free_busy = @ews_client.get_user_availability(rooms, + user_free_busy = @@ews_client.get_user_availability(rooms, start_time: start_time, end_time: end_time, requested_view: :detailed, @@ -177,9 +177,9 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. STDERR.puts "At email: #{email} with start: #{start_param} and end: #{end_param}" STDERR.puts '-------------------------------------------------' bookings = [] - calendar_id = @ews_client.get_folder(:calendar, opts = {act_as: email }).id - events = @ews_client.find_items(folder_id: calendar_id, calendar_view: {start_date: start_param, end_date: end_param}) - # events = @ews_client.get_item(:calendar, opts = {act_as: email}).items + calendar_id = @@ews_client.get_folder(:calendar, opts = {act_as: email }).id + events = @@ews_client.find_items(folder_id: calendar_id, calendar_view: {start_date: start_param, end_date: end_param}) + # events = @@ews_client.get_item(:calendar, opts = {act_as: email}).items events.each{|event| event.get_all_properties! booking = {} @@ -230,7 +230,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: }) end - folder = @ews_client.get_folder(:calendar, { act_as: room_email }) + folder = @@ews_client.get_folder(:calendar, { act_as: room_email }) appointment = folder.create_item(booking) { id: appointment.id, From 9ebcca87651a91aef670f935b1619dcdd9776b14 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 14:51:08 +1000 Subject: [PATCH 0365/1752] Add fucntionality to available rooms --- lib/microsoft/exchange.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index c6dee3fb..93d0ee6c 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -146,21 +146,26 @@ def get_available_rooms(rooms:, start_time:, end_time:) user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| # Remove meta data (business hours and response type) - resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| item[:free_busy_view_type] || item[:working_hours] } + resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| + if item[:free_busy_view_type] || item[:working_hours] + free_rooms.push({free: false, room: rooms[index], end_time: find_time(resp[0], :end_time)}) + end + item[:free_busy_view_type] || item[:working_hours] + } # Back to back meetings still show up so we need to remove these from the results if resp.length == 1 resp = resp[0][:calendar_event_array][:elems] if resp.length == 0 - free_rooms.push(rooms[index]) + free_rooms.push({free: true, room: rooms[index], end_time: find_time(resp[0], :end_time)}) elsif resp.length == 1 - free_rooms.push(rooms[index]) if find_time(resp[0], :start_time) == end_time - free_rooms.push(rooms[index]) if find_time(resp[0], :end_time) == start_time + free_rooms.push({free: true, room: rooms[index], end_time: find_time(resp[0], :end_time)}) if find_time(resp[0], :start_time) == end_time + free_rooms.push({free: true, room: rooms[index], end_time: find_time(resp[0], :end_time)}) if find_time(resp[0], :end_time) == start_time end elsif resp.length == 0 # If response length is 0 then the room is free - free_rooms.push(rooms[index]) + free_rooms.push({free: true, room: rooms[index], end_time: find_time(resp[0], :end_time)}) end } From ec71b75d06bbf8e7359e86ea5f3da020c326d8be Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 14:54:04 +1000 Subject: [PATCH 0366/1752] Fix update_every bug --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 9dbb10ca..f3c9c72b 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -198,7 +198,7 @@ def on_update schedule.clear schedule.in(rand(10000)) { fetch_bookings } - schedule.every((setting(:update_every) || 120000) + rand(10000)) { fetch_bookings } + schedule.every((setting(:update_every) || 120000).to_i + rand(10000)) { fetch_bookings } end From e5433683c084a56d22d0072309b0869352488f9b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 14:59:55 +1000 Subject: [PATCH 0367/1752] Update availability logic --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 93d0ee6c..16a986e8 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -148,7 +148,7 @@ def get_available_rooms(rooms:, start_time:, end_time:) # Remove meta data (business hours and response type) resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| if item[:free_busy_view_type] || item[:working_hours] - free_rooms.push({free: false, room: rooms[index], end_time: find_time(resp[0], :end_time)}) + free_rooms.push({free: false, room: rooms[index], end_time: find_time(item, :end_time)}) end item[:free_busy_view_type] || item[:working_hours] } From 87db4d6cc1b85586797bc6fb29eee1fb8c38b650 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 15:17:34 +1000 Subject: [PATCH 0368/1752] Update availability logic --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 16a986e8..304b1264 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -147,8 +147,8 @@ def get_available_rooms(rooms:, start_time:, end_time:) user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| # Remove meta data (business hours and response type) resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| - if item[:free_busy_view_type] || item[:working_hours] - free_rooms.push({free: false, room: rooms[index], end_time: find_time(item, :end_time)}) + if item[:calendar_event_array] + free_rooms.push({free: false, room: rooms[index], end_time: find_time(item[:calendar_event_array][:elems], :end_time)}) end item[:free_busy_view_type] || item[:working_hours] } From b5a4c0b2891d7dea57ff97a49710b6173dbe4be2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 15:21:55 +1000 Subject: [PATCH 0369/1752] Update availability logic --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 304b1264..bbab0d9f 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -150,7 +150,7 @@ def get_available_rooms(rooms:, start_time:, end_time:) if item[:calendar_event_array] free_rooms.push({free: false, room: rooms[index], end_time: find_time(item[:calendar_event_array][:elems], :end_time)}) end - item[:free_busy_view_type] || item[:working_hours] + item[:calendar_event_array] } # Back to back meetings still show up so we need to remove these from the results From c032650549cf58f1d10b1bf4f3f76de14def9068 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 15:34:23 +1000 Subject: [PATCH 0370/1752] Update availability logic --- lib/microsoft/exchange.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index bbab0d9f..9c996597 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -146,13 +146,15 @@ def get_available_rooms(rooms:, start_time:, end_time:) user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| # Remove meta data (business hours and response type) - resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| + r[:free_busy_response][:elems][1][:free_busy_view][:elems].each { |item| if item[:calendar_event_array] - free_rooms.push({free: false, room: rooms[index], end_time: find_time(item[:calendar_event_array][:elems], :end_time)}) + free_rooms.push({free: false, room: rooms[index], end_time: find_time(item[:calendar_event_array][:elems][0], :end_time)}) if !free_rooms.map{|room| room[:room] }.include?(rooms[index]) end item[:calendar_event_array] } + resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| item[:free_busy_view_type] || item[:working_hours] } + # Back to back meetings still show up so we need to remove these from the results if resp.length == 1 resp = resp[0][:calendar_event_array][:elems] From 5df110e541cc08499113e4653afee5bcce2b4e4f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 16:31:37 +1000 Subject: [PATCH 0371/1752] revert freebusy changes --- lib/microsoft/exchange.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 9c996597..48974542 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -146,12 +146,12 @@ def get_available_rooms(rooms:, start_time:, end_time:) user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| # Remove meta data (business hours and response type) - r[:free_busy_response][:elems][1][:free_busy_view][:elems].each { |item| - if item[:calendar_event_array] - free_rooms.push({free: false, room: rooms[index], end_time: find_time(item[:calendar_event_array][:elems][0], :end_time)}) if !free_rooms.map{|room| room[:room] }.include?(rooms[index]) - end - item[:calendar_event_array] - } + # r[:free_busy_response][:elems][1][:free_busy_view][:elems].each { |item| + # if item[:calendar_event_array] + # free_rooms.push({free: false, room: rooms[index], end_time: find_time(item[:calendar_event_array][:elems][0], :end_time)}) if !free_rooms.map{|room| room[:room] }.include?(rooms[index]) + # end + # item[:calendar_event_array] + # } resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| item[:free_busy_view_type] || item[:working_hours] } From 96f56fefafbe3ce3260ec3935b3fba5960c1619c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Apr 2018 16:37:18 +1000 Subject: [PATCH 0372/1752] revert freebusy changes --- lib/microsoft/exchange.rb | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 48974542..d4a10414 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -144,15 +144,8 @@ def get_available_rooms(rooms:, start_time:, end_time:) } ) - user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| + user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| # Remove meta data (business hours and response type) - # r[:free_busy_response][:elems][1][:free_busy_view][:elems].each { |item| - # if item[:calendar_event_array] - # free_rooms.push({free: false, room: rooms[index], end_time: find_time(item[:calendar_event_array][:elems][0], :end_time)}) if !free_rooms.map{|room| room[:room] }.include?(rooms[index]) - # end - # item[:calendar_event_array] - # } - resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| item[:free_busy_view_type] || item[:working_hours] } # Back to back meetings still show up so we need to remove these from the results @@ -160,17 +153,16 @@ def get_available_rooms(rooms:, start_time:, end_time:) resp = resp[0][:calendar_event_array][:elems] if resp.length == 0 - free_rooms.push({free: true, room: rooms[index], end_time: find_time(resp[0], :end_time)}) + free_rooms.push(rooms[index]) elsif resp.length == 1 - free_rooms.push({free: true, room: rooms[index], end_time: find_time(resp[0], :end_time)}) if find_time(resp[0], :start_time) == end_time - free_rooms.push({free: true, room: rooms[index], end_time: find_time(resp[0], :end_time)}) if find_time(resp[0], :end_time) == start_time + free_rooms.push(rooms[index]) if find_time(resp[0], :start_time) == end_time + free_rooms.push(rooms[index]) if find_time(resp[0], :end_time) == start_time end elsif resp.length == 0 # If response length is 0 then the room is free - free_rooms.push({free: true, room: rooms[index], end_time: find_time(resp[0], :end_time)}) + free_rooms.push(rooms[index]) end } - free_rooms end From 6228539a625475c7ce15772bc6e3dbc8d1afa085 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 27 Apr 2018 19:16:01 +1000 Subject: [PATCH 0373/1752] (aca:router) store raw IO information on edges rather than lambda --- modules/aca/router.rb | 52 ++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 52a8f1a4..f58652cd 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -71,14 +71,8 @@ def paths end end - # Given a list of nodes that form a path, execute the device level - # interactions to switch a signal across across them. - def switch(path) - - end - - # Find the shortest path between a source and target node and return the - # list of nodes which form it. + # Find the shortest path between between two nodes and return a list of the + # nodes which this passes through and their connecting edges. def route(source, sink) path = paths[sink] @@ -90,22 +84,26 @@ def route(source, sink) end nodes = [] + edges = [] node = signal_graph[source] until node.nil? nodes << node predecessor = path.predecessor[node.id] + edges << predecessor.edges[node.id] unless predecessor.nil? node = predecessor end - nodes + + logger.debug { nodes.join ' ---> ' } + + [nodes, edges] end end # Graph data structure for respresentating abstract signal networks. # # All signal sinks and sources are represented as nodes, with directed edges -# holding connectivity information and a lambda that can be executed to -# 'activate' the edge, performing any device level interaction for signal -# switching. +# holding connectivity information needed to execute device level interaction +# to 'activate' the edge. # # Directivity of the graph is inverted from the signal flow - edges use signal # sinks as source and signal sources as their terminus. This optimises for @@ -114,6 +112,16 @@ def route(source, sink) class Aca::Router::SignalGraph Paths = Struct.new :distance_to, :predecessor + Edge = Struct.new :source, :target, :device, :input, :output do + def activate + if output.nil? + system[device].switch_to input + else + system[device].switch input => output + end + end + end + class Node attr_reader :id, :edges @@ -124,8 +132,8 @@ def initialize(id) end end - def join(other_id, selector = nil) - edges[other_id] = selector + def join(other_id, datum) + edges[other_id] = datum self end @@ -175,8 +183,9 @@ def delete(id, check_incoming_edges: true) self end - def join(source, target, &selector) - nodes[source].join target, selector + def join(source, target, &block) + datum = Edge.new(source, target).tap(&block) + nodes[source].join target, datum self end @@ -283,8 +292,9 @@ def self.from_map(map) inputs.each_pair do |input, source| # Create a node and edge to each input source graph << source - graph.join(device, source) do - system[device].switch_to input + graph.join(device, source) do |edge| + edge.device = device + edge.input = input end # Check is the input is a matrix switcher or multi-output @@ -298,8 +308,10 @@ def self.from_map(map) matrix_inputs = map[upstream_device] matrix_inputs.each_pair do |matrix_input, upstream_source| graph << upstream_source - graph.join(source, upstream_source) do - system[upstream_device].switch matrix_input => output + graph.join(source, upstream_source) do |edge| + edge.device = upstream_device + edge.input = matrix_input + edge.output = output end end end From 811e6dcd44a2b3c4e10f8482897696d24bd68012 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 27 Apr 2018 19:18:38 +1000 Subject: [PATCH 0374/1752] (aca:router) provide useful error when attempting to use invalid settings --- modules/aca/router.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index f58652cd..f11d540d 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -25,7 +25,12 @@ def on_update @path_cache = nil - @signal_graph = SignalGraph.from_map(setting(:connections) || {}).freeze + connections = setting(:connections) || {} + begin + @signal_graph = SignalGraph.from_map(connections).freeze + rescue + logger.error 'invalid connection settings' + end self[:nodes] = signal_graph.map(&:id) self[:inputs] = signal_graph.sinks.map(&:id) From 2aac513e04360e9dd926e8f8817ee2da6205a8f0 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 27 Apr 2018 20:35:30 +1000 Subject: [PATCH 0375/1752] (aca:router) check for existance and compatability of modules in graph --- modules/aca/router.rb | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index f11d540d..b29fcfb3 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -32,6 +32,8 @@ def on_update logger.error 'invalid connection settings' end + check_compatability + self[:nodes] = signal_graph.map(&:id) self[:inputs] = signal_graph.sinks.map(&:id) self[:outputs] = signal_graph.sources.map(&:id) @@ -85,7 +87,7 @@ def route(source, sink) raise "no route from #{source} to #{sink}" if distance.infinite? logger.debug do - "found route from #{source} to #{sink} in #{distance} hops" + "found route connecting #{source} to #{sink} in #{distance} hops" end nodes = [] @@ -102,6 +104,31 @@ def route(source, sink) [nodes, edges] end + + def check_compatability + invalid = Set.new + + signal_graph.each do |node| + node.edges.each_pair do |_, edge| + mod = system[edge.device] + + is_switch = edge.output.nil? && mod.respond_to?(:switch_to) + is_matrix = !edge.output.nil? && mod.respond_to?(:switch) + + invalid << edge.device if mod.nil? || !(is_switch || is_matrix) + end + end + + if invalid.empty? + true + else + logger.warn do + modules = invalid.to_a.join ', ' + "incompatible or non-existent modules in config: #{modules}" + end + false + end + end end # Graph data structure for respresentating abstract signal networks. From a1b245be832898014c079735dda1e4b57f5897ff Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 27 Apr 2018 21:19:12 +1000 Subject: [PATCH 0376/1752] (aca:router) fix issue with routes not working with string keys --- modules/aca/router.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index b29fcfb3..47d177f5 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -73,7 +73,7 @@ def signal_graph end def paths - @path_cache ||= Hash.new do |hash, node| + @path_cache ||= HashWithIndifferentAccess.new do |hash, node| hash[node] = signal_graph.dijkstra node end end @@ -257,7 +257,7 @@ def outdegree(id) def dijkstra(id) active = Containers::PriorityQueue.new { |x, y| (x <=> y) == -1 } - distance_to = Hash.new { 1.0 / 0.0 } + distance_to = HashWithIndifferentAccess.new { 1.0 / 0.0 } predecessor = {} distance_to[id] = 0 From 6bcd76ed06ecaa8dbc20ee9db4387e880c5da95c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 29 Apr 2018 19:40:30 +1000 Subject: [PATCH 0377/1752] (aca:router) implement path finding for connecting one source to many sinks --- modules/aca/router.rb | 68 ++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 47d177f5..6efd1455 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -41,29 +41,32 @@ def on_update # Route a set of signals to arbitrary destinations. # - # `map` is a hashmap of the structure `{ source: target | [targets] }` + # `signal_map` is a hash of the structure `{ source: sink | [sinks] }` + # 'atomic' may be used to throw an exception, prior to any device + # interaction taking place if any of the routes are not + # possible # # Multiple sources can be specified simultaneously, or if connecting a # single source to a single destination, Ruby's implicit hash syntax can be - # used to let you express it neatly as `connect source => target`. - def connect(map) - # 1. Turn map -> list of paths (list of lists of nodes) - # 2. Check for intersections of node lists for different sources - # - get unique nodes for each source - # - intersect lists - # - confirm empty + # used to let you express it neatly as `connect source => sink`. + def connect(signal_map, atomic: false) + # Transform the signal map to the nodes and edges used by each source + routes = {} + signal_map.each_pair do |source, sinks| + routes[source] = route_many source, sinks, atomic: atomic + end + + logger.debug do + nodes = routes.transform_values { |n, _| n.map(&:to_s) } + "Nodes to connect: #{nodes}" + end + + # Check for intersections / conflicts across sources + # 3. Perform device interactions - # - step through paths and pair device interactions into tuples representing switch events - # - flatten and uniq - # - remove singular nodes (passthrough) - # - execute interactions # 4. Consolidate each path into a success / fail # 5. Raise exceptions / log errors for failures # 6. Return consolidated promise - - map.each_pair do |source, targets| - Array(targets).each { |target| route source, target } - end end protected @@ -105,6 +108,33 @@ def route(source, sink) [nodes, edges] end + # Find the optimum combined paths requires to route a single source to + # multiple sink devices. + def route_many(source, sinks, atomic: false) + node_exists = proc do |id| + signal_graph.include?(id).tap do |exists| + unless exists + message = "#{id} does not exist" + raise ArgumentError, message if atomic + logger.warn message + end + end + end + + nodes = Set.new + edges = Set.new + + if node_exists[source] + Array(sinks).select(&node_exists).each do |sink| + n, e = route source, sink + nodes |= n + edges |= e + end + end + + [nodes, edges] + end + def check_compatability invalid = Set.new @@ -187,7 +217,7 @@ def hash attr_reader :nodes def initialize - @nodes = ActiveSupport::HashWithIndifferentAccess.new do |_, id| + @nodes = HashWithIndifferentAccess.new do |_, id| raise ArgumentError, "\"#{id}\" does not exist" end end @@ -225,6 +255,10 @@ def each(&block) nodes.values.each(&block) end + def include?(id) + nodes.key? id + end + def successors(id) nodes[id].edges.keys.map { |x| nodes[x] } end From 9db8830e1c4e224564823b954ada1792bb38b88c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 29 Apr 2018 21:12:24 +1000 Subject: [PATCH 0378/1752] (aca:router) nicer debug messages --- modules/aca/router.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 6efd1455..ca78ed9d 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -103,7 +103,7 @@ def route(source, sink) node = predecessor end - logger.debug { nodes.join ' ---> ' } + logger.debug { edges.map(&:to_s).join ' then ' } [nodes, edges] end @@ -182,6 +182,10 @@ def activate system[device].switch input => output end end + + def to_s + "#{target} to #{device} (in #{input})" + end end class Node From dfff985af745d261a60599453b6078ef14aa9bb3 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 29 Apr 2018 21:37:22 +1000 Subject: [PATCH 0379/1752] (aca:router) protect against conflicting signal paths --- modules/aca/router.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index ca78ed9d..c7955361 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -61,7 +61,15 @@ def connect(signal_map, atomic: false) "Nodes to connect: #{nodes}" end - # Check for intersections / conflicts across sources + # Check for intersecting paths across sources + routes.values.map(&:first).reduce(&:&).tap do |conflicts| + unless conflicts.empty? + nodes = conflicts.map(&:to_s).join ', ' + message = "conflicting signal paths found for #{nodes}" + raise ArgumentError, message if atomic + logger.warn message + end + end # 3. Perform device interactions # 4. Consolidate each path into a success / fail From 3f8c52b080ede1adacb7c45458002be7553599a2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 29 Apr 2018 22:36:37 +1000 Subject: [PATCH 0380/1752] (aca:router) implement inter-module interaction --- modules/aca/router.rb | 59 ++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index c7955361..b2081e2c 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -53,7 +53,7 @@ def connect(signal_map, atomic: false) # Transform the signal map to the nodes and edges used by each source routes = {} signal_map.each_pair do |source, sinks| - routes[source] = route_many source, sinks, atomic: atomic + routes[source] = route_many source, sinks, strict: atomic end logger.debug do @@ -61,17 +61,11 @@ def connect(signal_map, atomic: false) "Nodes to connect: #{nodes}" end - # Check for intersecting paths across sources - routes.values.map(&:first).reduce(&:&).tap do |conflicts| - unless conflicts.empty? - nodes = conflicts.map(&:to_s).join ', ' - message = "conflicting signal paths found for #{nodes}" - raise ArgumentError, message if atomic - logger.warn message - end - end + check_conflicts routes, strict: atomic + + device_interactions = activate routes, strict: atomic + - # 3. Perform device interactions # 4. Consolidate each path into a success / fail # 5. Raise exceptions / log errors for failures # 6. Return consolidated promise @@ -118,12 +112,12 @@ def route(source, sink) # Find the optimum combined paths requires to route a single source to # multiple sink devices. - def route_many(source, sinks, atomic: false) + def route_many(source, sinks, strict: false) node_exists = proc do |id| signal_graph.include?(id).tap do |exists| unless exists message = "#{id} does not exist" - raise ArgumentError, message if atomic + raise ArgumentError, message if strict logger.warn message end end @@ -143,6 +137,37 @@ def route_many(source, sinks, atomic: false) [nodes, edges] end + def check_conflicts(routes, strict: false) + nodes = routes.values.map(&:first) + nodes.reduce(&:&).tap do |conflicts| + unless conflicts.empty? + nodes = conflicts.map(&:to_s).join ', ' + message = "conflicting signal paths found for #{nodes}" + raise message if strict + logger.warn message + end + end + end + + def activate(routes, strict: false) + device_exists = proc do |edge| + system[edge.device].nil?.!.tap do |exists| + message = "#{edge.device} not found in system" + raise message if strict + logger.warn message + end + end + + edges = routes.values.map(&:second).reduce(&:|) + edges.select(&device_exists).map do |edge| + if edge.output.nil? + system[edge.device].switch_to edge.input + else + system[edge.device].switch edge.input => edge.output + end + end + end + def check_compatability invalid = Set.new @@ -183,14 +208,6 @@ class Aca::Router::SignalGraph Paths = Struct.new :distance_to, :predecessor Edge = Struct.new :source, :target, :device, :input, :output do - def activate - if output.nil? - system[device].switch_to input - else - system[device].switch input => output - end - end - def to_s "#{target} to #{device} (in #{input})" end From 08168dc511d0cfaea2c23313be29569b5e3aede1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 30 Apr 2018 00:34:19 +1000 Subject: [PATCH 0381/1752] (aca:router) fix issue with incorrect conflict detection --- modules/aca/router.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index b2081e2c..7aea5ba0 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -139,6 +139,9 @@ def route_many(source, sinks, strict: false) def check_conflicts(routes, strict: false) nodes = routes.values.map(&:first) + + return Set.new if nodes.size <= 1 + nodes.reduce(&:&).tap do |conflicts| unless conflicts.empty? nodes = conflicts.map(&:to_s).join ', ' From 515884daff964e61c0300bfb2f013ce10eb759b2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 30 Apr 2018 01:10:00 +1000 Subject: [PATCH 0382/1752] (aca:router) combine device interaction results into a single promise --- modules/aca/router.rb | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 7aea5ba0..bbeee3c3 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -50,7 +50,6 @@ def on_update # single source to a single destination, Ruby's implicit hash syntax can be # used to let you express it neatly as `connect source => sink`. def connect(signal_map, atomic: false) - # Transform the signal map to the nodes and edges used by each source routes = {} signal_map.each_pair do |source, sinks| routes[source] = route_many source, sinks, strict: atomic @@ -63,12 +62,18 @@ def connect(signal_map, atomic: false) check_conflicts routes, strict: atomic - device_interactions = activate routes, strict: atomic - - - # 4. Consolidate each path into a success / fail - # 5. Raise exceptions / log errors for failures - # 6. Return consolidated promise + edges = routes.values.map(&:second).reduce(&:|) + interactions = edges.map { |e| activate e } + thread.finally(interactions).then do |results| + _, failed = results.partition(&:last) + if failed.empty? + logger.debug 'all routes activated successfully' + :success + else + failed.each { |result, _| logger.error result } + thread.defer.reject 'failed to activate all routes' + end + end end protected @@ -152,22 +157,11 @@ def check_conflicts(routes, strict: false) end end - def activate(routes, strict: false) - device_exists = proc do |edge| - system[edge.device].nil?.!.tap do |exists| - message = "#{edge.device} not found in system" - raise message if strict - logger.warn message - end - end - - edges = routes.values.map(&:second).reduce(&:|) - edges.select(&device_exists).map do |edge| - if edge.output.nil? - system[edge.device].switch_to edge.input - else - system[edge.device].switch edge.input => edge.output - end + def activate(edge) + if edge.output.nil? + system[edge.device].switch_to edge.input + else + system[edge.device].switch edge.input => edge.output end end From bf8bfdd98c5932caf49b0984feb4b63cfd35fe66 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 30 Apr 2018 01:17:49 +1000 Subject: [PATCH 0383/1752] (aca:router) add TODO's --- modules/aca/router.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index bbeee3c3..e7ac0fa5 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -34,6 +34,7 @@ def on_update check_compatability + # TODO: track active signal source at each node and expose as a hash self[:nodes] = signal_graph.map(&:id) self[:inputs] = signal_graph.sinks.map(&:id) self[:outputs] = signal_graph.sources.map(&:id) @@ -165,6 +166,7 @@ def activate(edge) end end + # TODO: execute this on system device create / remove / stop / start etc def check_compatability invalid = Set.new From d020797e09b7a23d8cac5a5121df272c1b67ad5a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Apr 2018 14:57:27 +1000 Subject: [PATCH 0384/1752] Add description setting to exchange driver --- modules/aca/exchange_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 357e000d..1f3e1d73 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -96,6 +96,7 @@ def on_update self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false self[:name] = self[:room_name] = setting(:room_name) || system.name + self[:description] = setting(:description) || nil self[:timeout] = setting(:timeout) || false self[:control_url] = setting(:booking_control_url) || system.config.support_url From da4d3a77202cb01c23b50c3ccfb6acd90b2afdc2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Apr 2018 15:48:01 +1000 Subject: [PATCH 0385/1752] Add title setting for indigenous rooms --- modules/aca/exchange_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 1f3e1d73..3b2c372d 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -97,6 +97,7 @@ def on_update self[:touch_enabled] = setting(:touch_enabled) || false self[:name] = self[:room_name] = setting(:room_name) || system.name self[:description] = setting(:description) || nil + self[:title] = setting(:title) || nil self[:timeout] = setting(:timeout) || false self[:control_url] = setting(:booking_control_url) || system.config.support_url From 9add17a581e6e1d9fb4914de23418521a412aa7f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 1 May 2018 15:08:12 +1000 Subject: [PATCH 0386/1752] Update exchange library to use impersonation --- lib/microsoft/exchange.rb | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index d4a10414..ef1d8b1f 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -24,7 +24,7 @@ def initialize( STDERR.puts '--------------- NEW CLIENT CREATED --------------' STDERR.puts "At URL: #{@ews_url} with email: #{@service_account_email} and password #{@service_account_password}" STDERR.puts '-------------------------------------------------' - @@ews_client ||= Viewpoint::EWSClient.new @ews_url, @service_account_email, @service_account_password, ews_opts + @ews_client ||= Viewpoint::EWSClient.new @ews_url, @service_account_email, @service_account_password, ews_opts end def basic_text(field, name) @@ -32,7 +32,7 @@ def basic_text(field, name) end def close - @@ews_client.ews.connection.httpcli.reset_all + @ews_client.ews.connection.httpcli.reset_all end def email_list(field, name=nil) @@ -58,7 +58,7 @@ def phone_list(field, name=nil) def get_users(q: nil, limit: nil) - ews_users = @@ews_client.search_contacts(q) + ews_users = @ews_client.search_contacts(q) users = [] fields = { display_name: 'name:basic_text', @@ -121,7 +121,7 @@ def get_available_rooms(rooms:, start_time:, end_time:) STDERR.flush # Get booking data for all rooms between time range bounds - user_free_busy = @@ews_client.get_user_availability(rooms, + user_free_busy = @ews_client.get_user_availability(rooms, start_time: start_time, end_time: end_time, requested_view: :detailed, @@ -166,7 +166,7 @@ def get_available_rooms(rooms:, start_time:, end_time:) free_rooms end - def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime.now.midnight + 2.days)) + def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime.now.midnight + 2.days), use_act_as: false) begin if [Integer, String].include?(start_param.class) start_param = DateTime.parse(Time.at(start_param.to_i / 1000).to_s) @@ -176,9 +176,14 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. STDERR.puts "At email: #{email} with start: #{start_param} and end: #{end_param}" STDERR.puts '-------------------------------------------------' bookings = [] - calendar_id = @@ews_client.get_folder(:calendar, opts = {act_as: email }).id - events = @@ews_client.find_items(folder_id: calendar_id, calendar_view: {start_date: start_param, end_date: end_param}) - # events = @@ews_client.get_item(:calendar, opts = {act_as: email}).items + if use_act_as + calendar_id = @ews_client.get_folder(:calendar, opts = {act_as: email }).id + events = @ews_client.find_items(folder_id: calendar_id, calendar_view: {start_date: start_param, end_date: end_param}) + else + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], email) + items = @ews_client.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start_param.utc.iso8601, :end_date => end_param.utc.iso8601}}) + end + # events = @ews_client.get_item(:calendar, opts = {act_as: email}).items events.each{|event| event.get_all_properties! booking = {} @@ -229,7 +234,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: }) end - folder = @@ews_client.get_folder(:calendar, { act_as: room_email }) + folder = @ews_client.get_folder(:calendar, { act_as: room_email }) appointment = folder.create_item(booking) { id: appointment.id, From 7914bbb05cd3bbc3a3e0a1e5e3d55691fd9292c2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 1 May 2018 15:09:55 +1000 Subject: [PATCH 0387/1752] Update exchange library to use impersonation --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index ef1d8b1f..ce98d675 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -181,7 +181,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. events = @ews_client.find_items(folder_id: calendar_id, calendar_view: {start_date: start_param, end_date: end_param}) else @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], email) - items = @ews_client.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start_param.utc.iso8601, :end_date => end_param.utc.iso8601}}) + events = @ews_client.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start_param.utc.iso8601, :end_date => end_param.utc.iso8601}}) end # events = @ews_client.get_item(:calendar, opts = {act_as: email}).items events.each{|event| From d1842e75ad3fc61a5a43b929ddff369bc5b9e17d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 3 May 2018 00:14:56 +1000 Subject: [PATCH 0388/1752] (cisco:ce) deprecate Spark naming --- .../external_source.rb | 6 +++--- .../{spark => collaboration_endpoint}/room_os.rb | 14 +++++++------- .../room_os_spec.rb | 2 +- .../{spark => collaboration_endpoint}/sx20.rb | 10 +++++----- .../{spark => collaboration_endpoint}/sx80.rb | 10 +++++----- .../ui_extensions.rb | 6 +++--- .../util/case_insensitive_hash.rb | 6 +++--- .../util/feedback_trie.rb | 6 +++--- .../util/git.rb | 6 +++--- .../cisco/collaboration_endpoint/util/meta.rb | 16 ++++++++++++++++ .../xapi/action.rb | 6 +++--- .../xapi/mapper.rb | 6 +++--- .../xapi/response.rb | 6 +++--- .../xapi/tokens.rb | 6 +++--- modules/cisco/spark/util/meta.rb | 16 ---------------- 15 files changed, 61 insertions(+), 61 deletions(-) rename modules/cisco/{spark => collaboration_endpoint}/external_source.rb (92%) rename modules/cisco/{spark => collaboration_endpoint}/room_os.rb (96%) rename modules/cisco/{spark => collaboration_endpoint}/room_os_spec.rb (99%) rename modules/cisco/{spark => collaboration_endpoint}/sx20.rb (91%) rename modules/cisco/{spark => collaboration_endpoint}/sx80.rb (93%) rename modules/cisco/{spark => collaboration_endpoint}/ui_extensions.rb (93%) rename modules/cisco/{spark => collaboration_endpoint}/util/case_insensitive_hash.rb (66%) rename modules/cisco/{spark => collaboration_endpoint}/util/feedback_trie.rb (88%) rename modules/cisco/{spark => collaboration_endpoint}/util/git.rb (78%) create mode 100644 modules/cisco/collaboration_endpoint/util/meta.rb rename modules/cisco/{spark => collaboration_endpoint}/xapi/action.rb (94%) rename modules/cisco/{spark => collaboration_endpoint}/xapi/mapper.rb (96%) rename modules/cisco/{spark => collaboration_endpoint}/xapi/response.rb (94%) rename modules/cisco/{spark => collaboration_endpoint}/xapi/tokens.rb (74%) delete mode 100644 modules/cisco/spark/util/meta.rb diff --git a/modules/cisco/spark/external_source.rb b/modules/cisco/collaboration_endpoint/external_source.rb similarity index 92% rename from modules/cisco/spark/external_source.rb rename to modules/cisco/collaboration_endpoint/external_source.rb index 1ad3b6d7..478b6d03 100644 --- a/modules/cisco/spark/external_source.rb +++ b/modules/cisco/collaboration_endpoint/external_source.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module Cisco; end -module Cisco::Spark; end +module Cisco::CollaborationEndpoint; end -module Cisco::Spark::ExternalSource - include ::Cisco::Spark::Xapi::Mapper +module Cisco::CollaborationEndpoint::ExternalSource + include ::Cisco::CollaborationEndpoint::Xapi::Mapper module Hooks def connected diff --git a/modules/cisco/spark/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb similarity index 96% rename from modules/cisco/spark/room_os.rb rename to modules/cisco/collaboration_endpoint/room_os.rb index 8b6c9fd8..89a0d3ac 100644 --- a/modules/cisco/spark/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -6,25 +6,25 @@ Dir[File.join(__dir__, '{xapi,util}', '*.rb')].each { |lib| load lib } module Cisco; end -module Cisco::Spark; end +module Cisco::CollaborationEndpoint; end -class Cisco::Spark::RoomOs +class Cisco::CollaborationEndpoint::RoomOs include ::Orchestrator::Constants include ::Orchestrator::Security - include ::Cisco::Spark::Xapi - include ::Cisco::Spark::Util + include ::Cisco::CollaborationEndpoint::Xapi + include ::Cisco::CollaborationEndpoint::Util implements :ssh - descriptive_name 'Cisco Spark Room Device' + descriptive_name 'Cisco Collaboration Endpoint' generic_name :VidConf description <<~DESC - Low level driver for any Cisco Spark Room OS device. This may be used + Low level driver for any Cisco Room OS device. This may be used if direct access is required to the device API, or a required feature is not provided by the device specific implementation. Where possible use the implementation for room device in use - (i.e. SX80, Spark Room Kit etc). + (i.e. SX80, Room Kit etc). DESC tokenize delimiter: Tokens::COMMAND_RESPONSE, diff --git a/modules/cisco/spark/room_os_spec.rb b/modules/cisco/collaboration_endpoint/room_os_spec.rb similarity index 99% rename from modules/cisco/spark/room_os_spec.rb rename to modules/cisco/collaboration_endpoint/room_os_spec.rb index 4a0fa70c..2d1b6e42 100644 --- a/modules/cisco/spark/room_os_spec.rb +++ b/modules/cisco/collaboration_endpoint/room_os_spec.rb @@ -1,6 +1,6 @@ require 'thread' -Orchestrator::Testing.mock_device 'Cisco::Spark::RoomOs', +Orchestrator::Testing.mock_device 'Cisco::CollaborationEndpoint::RoomOs', settings: { peripheral_id: 'MOCKED_ID', version: 'MOCKED_VERSION' diff --git a/modules/cisco/spark/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb similarity index 91% rename from modules/cisco/spark/sx20.rb rename to modules/cisco/collaboration_endpoint/sx20.rb index cce0597e..6b55586a 100644 --- a/modules/cisco/spark/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -4,12 +4,12 @@ load File.join(__dir__, 'ui_extensions.rb') load File.join(__dir__, 'external_source.rb') -class Cisco::Spark::Sx20 < Cisco::Spark::RoomOs - include ::Cisco::Spark::Xapi::Mapper - include ::Cisco::Spark::UiExtensions - include ::Cisco::Spark::ExternalSource +class Cisco::CollaborationEndpoint::Sx20 < Cisco::CollaborationEndpoint::RoomOs + include ::Cisco::CollaborationEndpoint::Xapi::Mapper + include ::Cisco::CollaborationEndpoint::UiExtensions + include ::Cisco::CollaborationEndpoint::ExternalSource - descriptive_name 'Cisco Spark SX20' + descriptive_name 'Cisco SX20' description <<~DESC Device access requires an API user to be created on the endpoint. DESC diff --git a/modules/cisco/spark/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb similarity index 93% rename from modules/cisco/spark/sx80.rb rename to modules/cisco/collaboration_endpoint/sx80.rb index 081ff252..45ffc185 100644 --- a/modules/cisco/spark/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -4,12 +4,12 @@ load File.join(__dir__, 'ui_extensions.rb') load File.join(__dir__, 'external_source.rb') -class Cisco::Spark::Sx80 < Cisco::Spark::RoomOs - include ::Cisco::Spark::Xapi::Mapper - include ::Cisco::Spark::UiExtensions - include ::Cisco::Spark::ExternalSource +class Cisco::CollaborationEndpoint::Sx80 < Cisco::CollaborationEndpoint::RoomOs + include ::Cisco::CollaborationEndpoint::Xapi::Mapper + include ::Cisco::CollaborationEndpoint::UiExtensions + include ::Cisco::CollaborationEndpoint::ExternalSource - descriptive_name 'Cisco Spark SX80' + descriptive_name 'Cisco SX80' description <<~DESC Device access requires an API user to be created on the endpoint. DESC diff --git a/modules/cisco/spark/ui_extensions.rb b/modules/cisco/collaboration_endpoint/ui_extensions.rb similarity index 93% rename from modules/cisco/spark/ui_extensions.rb rename to modules/cisco/collaboration_endpoint/ui_extensions.rb index 1254e151..2147c0f3 100644 --- a/modules/cisco/spark/ui_extensions.rb +++ b/modules/cisco/collaboration_endpoint/ui_extensions.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module Cisco; end -module Cisco::Spark; end +module Cisco::CollaborationEndpoint; end -module Cisco::Spark::UiExtensions - include ::Cisco::Spark::Xapi::Mapper +module Cisco::CollaborationEndpoint::UiExtensions + include ::Cisco::CollaborationEndpoint::Xapi::Mapper module Hooks def connected diff --git a/modules/cisco/spark/util/case_insensitive_hash.rb b/modules/cisco/collaboration_endpoint/util/case_insensitive_hash.rb similarity index 66% rename from modules/cisco/spark/util/case_insensitive_hash.rb rename to modules/cisco/collaboration_endpoint/util/case_insensitive_hash.rb index 0624ad6a..2cab7063 100644 --- a/modules/cisco/spark/util/case_insensitive_hash.rb +++ b/modules/cisco/collaboration_endpoint/util/case_insensitive_hash.rb @@ -3,10 +3,10 @@ require 'active_support/core_ext/hash/indifferent_access' module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Util; end +module Cisco::CollaborationEndpoint; end +module Cisco::CollaborationEndpoint::Util; end -class Cisco::Spark::Util::CaseInsensitiveHash < \ +class Cisco::CollaborationEndpoint::Util::CaseInsensitiveHash < \ ActiveSupport::HashWithIndifferentAccess def [](key) super convert_key(key) diff --git a/modules/cisco/spark/util/feedback_trie.rb b/modules/cisco/collaboration_endpoint/util/feedback_trie.rb similarity index 88% rename from modules/cisco/spark/util/feedback_trie.rb rename to modules/cisco/collaboration_endpoint/util/feedback_trie.rb index 567420cd..73f76d08 100644 --- a/modules/cisco/spark/util/feedback_trie.rb +++ b/modules/cisco/collaboration_endpoint/util/feedback_trie.rb @@ -3,10 +3,10 @@ require_relative 'case_insensitive_hash' module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Util; end +module Cisco::CollaborationEndpoint; end +module Cisco::CollaborationEndpoint::Util; end -class Cisco::Spark::Util::FeedbackTrie < Cisco::Spark::Util::CaseInsensitiveHash +class Cisco::CollaborationEndpoint::Util::FeedbackTrie < Cisco::CollaborationEndpoint::Util::CaseInsensitiveHash # Insert a response handler block to be notified of updates effecting the # specified feedback path. def insert(path, &handler) diff --git a/modules/cisco/spark/util/git.rb b/modules/cisco/collaboration_endpoint/util/git.rb similarity index 78% rename from modules/cisco/spark/util/git.rb rename to modules/cisco/collaboration_endpoint/util/git.rb index d00a92dc..6f245ccb 100644 --- a/modules/cisco/spark/util/git.rb +++ b/modules/cisco/collaboration_endpoint/util/git.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Util; end +module Cisco::CollaborationEndpoint; end +module Cisco::CollaborationEndpoint::Util; end -module Cisco::Spark::Util::Git +module Cisco::CollaborationEndpoint::Util::Git module_function # Get the commit hash for the passed path. diff --git a/modules/cisco/collaboration_endpoint/util/meta.rb b/modules/cisco/collaboration_endpoint/util/meta.rb new file mode 100644 index 00000000..0646cc29 --- /dev/null +++ b/modules/cisco/collaboration_endpoint/util/meta.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative 'git' + +module Cisco; end +module Cisco::CollaborationEndpoint; end +module Cisco::CollaborationEndpoint::Util; end + +module Cisco::CollaborationEndpoint::Util::Meta + module_function + + def version(instance) + hash = Cisco::CollaborationEndpoint::Util::Git.hash __dir__ + "#{instance.class.name}-#{hash}" + end +end diff --git a/modules/cisco/spark/xapi/action.rb b/modules/cisco/collaboration_endpoint/xapi/action.rb similarity index 94% rename from modules/cisco/spark/xapi/action.rb rename to modules/cisco/collaboration_endpoint/xapi/action.rb index 54ed8c65..2d3955f9 100644 --- a/modules/cisco/spark/xapi/action.rb +++ b/modules/cisco/collaboration_endpoint/xapi/action.rb @@ -3,11 +3,11 @@ require 'set' module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Xapi; end +module Cisco::CollaborationEndpoint; end +module Cisco::CollaborationEndpoint::Xapi; end # Pure utility methods for building Cisco xAPI actions. -module Cisco::Spark::Xapi::Action +module Cisco::CollaborationEndpoint::Xapi::Action ACTION_TYPE ||= Set.new [ :xConfiguration, :xCommand, diff --git a/modules/cisco/spark/xapi/mapper.rb b/modules/cisco/collaboration_endpoint/xapi/mapper.rb similarity index 96% rename from modules/cisco/spark/xapi/mapper.rb rename to modules/cisco/collaboration_endpoint/xapi/mapper.rb index f77f159c..a0ee8c63 100644 --- a/modules/cisco/spark/xapi/mapper.rb +++ b/modules/cisco/collaboration_endpoint/xapi/mapper.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Xapi; end +module Cisco::CollaborationEndpoint; end +module Cisco::CollaborationEndpoint::Xapi; end # Minimal DSL for mapping Cisco's xAPI to methods. -module Cisco::Spark::Xapi::Mapper +module Cisco::CollaborationEndpoint::Xapi::Mapper module ApiMapperMethods # Bind an xCommand to a module method. # diff --git a/modules/cisco/spark/xapi/response.rb b/modules/cisco/collaboration_endpoint/xapi/response.rb similarity index 94% rename from modules/cisco/spark/xapi/response.rb rename to modules/cisco/collaboration_endpoint/xapi/response.rb index f448d132..3cd0e912 100644 --- a/modules/cisco/spark/xapi/response.rb +++ b/modules/cisco/collaboration_endpoint/xapi/response.rb @@ -3,10 +3,10 @@ require 'json' module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Xapi; end +module Cisco::CollaborationEndpoint; end +module Cisco::CollaborationEndpoint::Xapi; end -module Cisco::Spark::Xapi::Response +module Cisco::CollaborationEndpoint::Xapi::Response class ParserError < StandardError; end module_function diff --git a/modules/cisco/spark/xapi/tokens.rb b/modules/cisco/collaboration_endpoint/xapi/tokens.rb similarity index 74% rename from modules/cisco/spark/xapi/tokens.rb rename to modules/cisco/collaboration_endpoint/xapi/tokens.rb index 08e58cf7..0428b09d 100644 --- a/modules/cisco/spark/xapi/tokens.rb +++ b/modules/cisco/collaboration_endpoint/xapi/tokens.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Xapi; end +module Cisco::CollaborationEndpoint; end +module Cisco::CollaborationEndpoint::Xapi; end # Regexp's for tokenizing the xAPI command and response structure. -module Cisco::Spark::Xapi::Tokens +module Cisco::CollaborationEndpoint::Xapi::Tokens JSON_RESPONSE ||= /(?<=^})|(?<=^{})[\r\n]+/ INVALID_COMMAND ||= /(?<=^Command not recognized\.)[\r\n]+/ diff --git a/modules/cisco/spark/util/meta.rb b/modules/cisco/spark/util/meta.rb deleted file mode 100644 index 2b83b39f..00000000 --- a/modules/cisco/spark/util/meta.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require_relative 'git' - -module Cisco; end -module Cisco::Spark; end -module Cisco::Spark::Util; end - -module Cisco::Spark::Util::Meta - module_function - - def version(instance) - hash = Cisco::Spark::Util::Git.hash __dir__ - "#{instance.class.name}-#{hash}" - end -end From 093fbae03635df8c96600fe43e834e455bfed09d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 3 May 2018 00:35:32 +1000 Subject: [PATCH 0389/1752] (spark:ce) add implementation for RoomKit --- .../cisco/collaboration_endpoint/room_kit.rb | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 modules/cisco/collaboration_endpoint/room_kit.rb diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb new file mode 100644 index 00000000..419525b1 --- /dev/null +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +load File.join(__dir__, 'room_os.rb') +load File.join(__dir__, 'ui_extensions.rb') +load File.join(__dir__, 'external_source.rb') + +class Cisco::CollaborationEndpoint::RoomKit < Cisco::CollaborationEndpoint::RoomOs + include ::Cisco::CollaborationEndpoint::Xapi::Mapper + include ::Cisco::CollaborationEndpoint::UiExtensions + include ::Cisco::CollaborationEndpoint::ExternalSource + + descriptive_name 'Cisco Room Kit' + + tokenize delimiter: Tokens::COMMAND_RESPONSE, + wait_ready: Tokens::LOGIN_COMPLETE + clear_queue_on_disconnect! + + def connected + super + + register_feedback '/Event/PresentationPreviewStarted' do + self[:local_presentation] = true + end + register_feedback '/Event/PresentationPreviewStopped' do + self[:local_presentation] = false + end + end + + status 'Audio Microphones Mute' => :mic_mute + status 'Audio Volume' => :volume + status 'Cameras PresenterTrack' => :presenter_track + status 'Cameras SpeakerTrack' => :speaker_track + status 'RoomAnalytics PeoplePresence' => :presence_detected + status 'RoomAnalytics PeopleCount Current' => :people_count + status 'Conference DoNotDisturb' => :do_not_disturb + status 'Conference Presentation Mode' => :presentation + status 'Peripherals ConnectedDevice' => :peripherals + status 'SystemUnit State NumberOfActiveCalls' => :active_calls + status 'Video SelfView Mode' => :selfview + status 'Video Input' => :video_input + status 'Video Output' => :video_output + status 'Standby State' => :standby + + command 'Audio Microphones Mute' => :mic_mute_on + command 'Audio Microphones Unmute' => :mic_mute_off + command 'Audio Microphones ToggleMute' => :mic_mute_toggle + + command 'Audio Sound Play' => :play_sound, + Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, + :CallWaiting, :Dial, :KeyInput, :KeyInputDelete, :KeyTone, + :Nav, :NavBack, :Notification, :OK, :PresentationConnect, + :Ringing, :SignIn, :SpecialInfo, :TelephoneCall, + :VideoCall, :VolumeAdjust, :WakeUp], + Loop_: [:Off, :On] + command 'Audio Sound Stop' => :stop_sound + + command 'Call Disconnect' => :hangup, CallId_: Integer + command 'Dial' => :dial, + Number: String, + Protocol_: [:H320, :H323, :Sip, :Spark], + CallRate_: (64..6000), + CallType_: [:Audio, :Video] + + command 'Camera PositionReset' => :camera_position_reset, + CameraId: (1..1), + Axis_: [:All, :Focus, :PanTilt, :Zoom] + command 'Camera Ramp' => :camera_move, + CameraId: (1..1), + Pan_: [:Left, :Right, :Stop], + PanSpeed_: (1..15), + Tilt_: [:Down, :Up, :Stop], + TiltSpeed_: (1..15), + Zoom_: [:In, :Out, :Stop], + ZoomSpeed_: (1..15), + Focus_: [:Far, :Near, :Stop] + + command! 'Cameras AutoFocus Diagnostics Start' => \ + :autofocus_diagnostics_start, + CameraId: (1..1) + command! 'Cameras AutoFocus Diagnostics Stop' => \ + :autofocus_diagnostics_stop, + CameraId: (1..1) + + command! 'Cameras PresenterTrack ClearPosition' => :presenter_track_clear + command! 'Cameras PresenterTrack StorePosition' => :presenter_track_store + command! 'Cameras PresenterTrack Set' => :presenter_track, + Mode: [:Off, :Follow, :Diagnostic, :Background, :Setup, + :Persistant] + + command! 'Cameras SpeakerTrack Diagnostics Start' => \ + :speaker_track_diagnostics_start + command! 'Cameras SpeakerTrack Diagnostics Stop' => \ + :speaker_track_diagnostics_stop + + # The 'integrator' account can't active/deactive SpeakerTrack, but we can + # cut off access via a configuration setting. + def speaker_track(state = On) + if is_affirmative? state + send_xconfiguration 'Cameras SpeakerTrack', :Mode, :Auto + else + send_xconfiguration 'Cameras SpeakerTrack', :Mode, :Off + end + end + + command 'Standby Deactivate' => :powerup + command 'Standby HalfWake' => :half_wake + command 'Standby Activate' => :standby + command 'Standby ResetTimer' => :reset_standby_timer, Delay: (1..480) + def power(state = false) + if is_affirmative? state + powerup + elsif is_negatory? state + standby + elsif state.to_s =~ /wake/i + half_wake + else + logger.error "Invalid power state: #{state}" + end + end + + command! 'SystemUnit Boot' => :reboot, Action_: [:Restart, :Shutdown] +end From 2f2cfa94054b2d9606b482355010b92cb23d3e68 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 3 May 2018 10:51:46 +1000 Subject: [PATCH 0390/1752] (cisco:ce) update module descriptions --- modules/cisco/collaboration_endpoint/room_kit.rb | 6 ++++++ modules/cisco/collaboration_endpoint/sx20.rb | 5 ++++- modules/cisco/collaboration_endpoint/sx80.rb | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 419525b1..b9d27aab 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -10,6 +10,12 @@ class Cisco::CollaborationEndpoint::RoomKit < Cisco::CollaborationEndpoint::Room include ::Cisco::CollaborationEndpoint::ExternalSource descriptive_name 'Cisco Room Kit' + description <<~DESC + Control of Cisco RoomKit devices. + + API access requires a local user with the 'integrator' role to be + created on the codec. + DESC tokenize delimiter: Tokens::COMMAND_RESPONSE, wait_ready: Tokens::LOGIN_COMPLETE diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 6b55586a..2c45b74f 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -11,7 +11,10 @@ class Cisco::CollaborationEndpoint::Sx20 < Cisco::CollaborationEndpoint::RoomOs descriptive_name 'Cisco SX20' description <<~DESC - Device access requires an API user to be created on the endpoint. + Control of Cisco SX20 devices. + + API access requires a local user with the 'integrator' role to be + created on the codec. DESC tokenize delimiter: Tokens::COMMAND_RESPONSE, diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 45ffc185..7cc92432 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -11,7 +11,10 @@ class Cisco::CollaborationEndpoint::Sx80 < Cisco::CollaborationEndpoint::RoomOs descriptive_name 'Cisco SX80' description <<~DESC - Device access requires an API user to be created on the endpoint. + Control of Cisco SX80 devices. + + API access requires a local user with the 'integrator' role to be + created on the codec. DESC tokenize delimiter: Tokens::COMMAND_RESPONSE, From 5c73bbe52ec648929a39b18ad1b2f52d68bc3dfa Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 3 May 2018 13:14:03 +1000 Subject: [PATCH 0391/1752] Add debugging to create method --- lib/microsoft/exchange.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index ce98d675..cc352a50 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -215,6 +215,16 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. end def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney') + STDERR.puts "CREATING NEW BOOKING IN LIBRARY" + STDERR.puts "room_email is #{room_email}" + STDERR.puts "start_param is #{start_param}" + STDERR.puts "end_param is #{end_param}" + STDERR.puts "subject is #{subject}" + STDERR.puts "description is #{description}" + STDERR.puts "current_user is #{current_user}" + STDERR.puts "attendees is #{attendees}" + STDERR.puts "timezone is #{timezone}" + STDERR.flush description = String(description) attendees = Array(attendees) @@ -233,7 +243,9 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: attendee: { mailbox: { email_address: attendee}} }) end - + STDERR.puts "MAKING REQUEST WITH" + STDERR.puts booking + STDERR.flush folder = @ews_client.get_folder(:calendar, { act_as: room_email }) appointment = folder.create_item(booking) { From 9e985b974fcb3af7585e5a80929e613c96eccf31 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 3 May 2018 15:38:22 +1000 Subject: [PATCH 0392/1752] Extract attendee email address --- lib/microsoft/exchange.rb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index cc352a50..755d8d68 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -214,17 +214,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. end end - def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney') - STDERR.puts "CREATING NEW BOOKING IN LIBRARY" - STDERR.puts "room_email is #{room_email}" - STDERR.puts "start_param is #{start_param}" - STDERR.puts "end_param is #{end_param}" - STDERR.puts "subject is #{subject}" - STDERR.puts "description is #{description}" - STDERR.puts "current_user is #{current_user}" - STDERR.puts "attendees is #{attendees}" - STDERR.puts "timezone is #{timezone}" - STDERR.flush + def create_booking(room_email5c73bb description = String(description) attendees = Array(attendees) @@ -239,6 +229,9 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: attendee: { mailbox: { email_address: current_user.email } } }] attendees.each do |attendee| + if attendee.class != String + attendee = attendee['email'] + end booking[:required_attendees].push({ attendee: { mailbox: { email_address: attendee}} }) From 0a3c93a7978c8f077d2213a8df7e92598240383b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 3 May 2018 15:39:05 +1000 Subject: [PATCH 0393/1752] (cisco:ce) add mic_mute method with boolean param --- modules/cisco/collaboration_endpoint/room_kit.rb | 3 +++ modules/cisco/collaboration_endpoint/sx20.rb | 3 +++ modules/cisco/collaboration_endpoint/sx80.rb | 3 +++ 3 files changed, 9 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index b9d27aab..5b1b9f7c 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -50,6 +50,9 @@ def connected command 'Audio Microphones Mute' => :mic_mute_on command 'Audio Microphones Unmute' => :mic_mute_off command 'Audio Microphones ToggleMute' => :mic_mute_toggle + def mic_mute(state = On) + is_affirmative? state ? mic_mute_on : mic_mute_off + end command 'Audio Sound Play' => :play_sound, Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 2c45b74f..76c2daf3 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -36,6 +36,9 @@ class Cisco::CollaborationEndpoint::Sx20 < Cisco::CollaborationEndpoint::RoomOs command 'Audio Microphones Mute' => :mic_mute_on command 'Audio Microphones Unmute' => :mic_mute_off command 'Audio Microphones ToggleMute' => :mic_mute_toggle + def mic_mute(state = On) + is_affirmative? state ? mic_mute_on : mic_mute_off + end command 'Audio Sound Play' => :play_sound, Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 7cc92432..98b1be89 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -49,6 +49,9 @@ def connected command 'Audio Microphones Mute' => :mic_mute_on command 'Audio Microphones Unmute' => :mic_mute_off command 'Audio Microphones ToggleMute' => :mic_mute_toggle + def mic_mute(state = On) + is_affirmative? state ? mic_mute_on : mic_mute_off + end command 'Audio Sound Play' => :play_sound, Sound: [:Alert, :Bump, :Busy, :CallDisconnect, :CallInitiate, From 32a3aa641c16971f529965e536753a219a76de04 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 3 May 2018 15:42:58 +1000 Subject: [PATCH 0394/1752] Fix silly little typo --- lib/microsoft/exchange.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 755d8d68..14998fff 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -214,7 +214,17 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. end end - def create_booking(room_email5c73bb + def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney') + STDERR.puts "CREATING NEW BOOKING IN LIBRARY" + STDERR.puts "room_email is #{room_email}" + STDERR.puts "start_param is #{start_param}" + STDERR.puts "end_param is #{end_param}" + STDERR.puts "subject is #{subject}" + STDERR.puts "description is #{description}" + STDERR.puts "current_user is #{current_user}" + STDERR.puts "attendees is #{attendees}" + STDERR.puts "timezone is #{timezone}" + STDERR.flush description = String(description) attendees = Array(attendees) From 215bc69f8d0eb8bcd08932ee11b0ca8491554b9e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 3 May 2018 16:52:54 +1000 Subject: [PATCH 0395/1752] Add description to booking creation --- lib/microsoft/exchange.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 14998fff..fb08f47f 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -246,6 +246,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: attendee: { mailbox: { email_address: attendee}} }) end + booking[:body] = description STDERR.puts "MAKING REQUEST WITH" STDERR.puts booking STDERR.flush From 23e63160dd04a7b3b9c7d2d8d4ffe7adddf75fd5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 3 May 2018 16:57:25 +1000 Subject: [PATCH 0396/1752] Remove debugging --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index fb08f47f..f077f77f 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -225,7 +225,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: STDERR.puts "attendees is #{attendees}" STDERR.puts "timezone is #{timezone}" STDERR.flush - description = String(description) + # description = String(description) attendees = Array(attendees) From 9659a144e37e2a37b7d94234483d1363bdd84182 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 3 May 2018 17:02:04 +1000 Subject: [PATCH 0397/1752] (cisco:ce) fix issue with standby state using multi data types --- modules/cisco/collaboration_endpoint/xapi/response.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/xapi/response.rb b/modules/cisco/collaboration_endpoint/xapi/response.rb index 3cd0e912..daf51b4f 100644 --- a/modules/cisco/collaboration_endpoint/xapi/response.rb +++ b/modules/cisco/collaboration_endpoint/xapi/response.rb @@ -83,7 +83,9 @@ def convert(value, valuespace) end def truthy?(value) - ::Orchestrator::Constants::On_vars.include? value + (::Orchestrator::Constants::On_vars + [ + 'Standby' # ensure standby state is properly mapped + ]).include? value end def falsey?(value) From 43cf37a72df12b9eaa5508ad458c148adcb4cc6a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 4 May 2018 12:11:40 +1000 Subject: [PATCH 0398/1752] Add username to user retreival --- lib/microsoft/exchange.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index f077f77f..9dc2ac48 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -34,9 +34,13 @@ def basic_text(field, name) def close @ews_client.ews.connection.httpcli.reset_all end - - def email_list(field, name=nil) - field[:email_addresses][:elems][-1][:entry][:text].gsub(/SMTP:|SIP:|sip:|smtp:/,'') + + def username(field, name=nil) + username = field[:email_addresses][:elems][0][:entry][:text].split("@")[0].gsub(/SMTP:|SIP:|sip:|smtp:/,'') + if username.downcase.include?('x500') || username.downcase.include?('x400') + username = field[:email_addresses][:elems][-1][:entry][:text].split("@")[0].gsub(/SMTP:|SIP:|sip:|smtp:/,'') + end + username end def phone_list(field, name=nil) @@ -62,10 +66,10 @@ def get_users(q: nil, limit: nil) users = [] fields = { display_name: 'name:basic_text', - # email_addresses: 'email:email_list', phone_numbers: 'phone:phone_list', culture: 'locale:basic_text', - department: 'department:basic_text' + department: 'department:basic_text', + email_addresses: 'username:username' } keys = fields.keys ews_users.each do |user| From 4eff57647decc6ad80d81b3bc3de80ebf8404f3c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 4 May 2018 13:28:03 +1000 Subject: [PATCH 0399/1752] Change username field to id --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 9dc2ac48..9f8edb89 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -34,7 +34,7 @@ def basic_text(field, name) def close @ews_client.ews.connection.httpcli.reset_all end - + def username(field, name=nil) username = field[:email_addresses][:elems][0][:entry][:text].split("@")[0].gsub(/SMTP:|SIP:|sip:|smtp:/,'') if username.downcase.include?('x500') || username.downcase.include?('x400') @@ -69,7 +69,7 @@ def get_users(q: nil, limit: nil) phone_numbers: 'phone:phone_list', culture: 'locale:basic_text', department: 'department:basic_text', - email_addresses: 'username:username' + email_addresses: 'id:username' } keys = fields.keys ews_users.each do |user| From 32d4b722326813bdf8c420786baa1133df552c86 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 4 May 2018 14:40:53 +1000 Subject: [PATCH 0400/1752] Exhaustive search for username in users models --- lib/microsoft/exchange.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 9f8edb89..f45205f8 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -36,9 +36,21 @@ def close end def username(field, name=nil) - username = field[:email_addresses][:elems][0][:entry][:text].split("@")[0].gsub(/SMTP:|SIP:|sip:|smtp:/,'') - if username.downcase.include?('x500') || username.downcase.include?('x400') - username = field[:email_addresses][:elems][-1][:entry][:text].split("@")[0].gsub(/SMTP:|SIP:|sip:|smtp:/,'') + username = field[:email_addresses][:elems][0][:entry][:text].split("@")[0] + if ['smt','sip'].include?(username.downcase[0..2]) + username = username.gsub(/SMTP:|SIP:|sip:|smtp:/,'') + else + username = field[:email_addresses][:elems][-1][:entry][:text].split("@")[0] + if ['smt','sip'].include?(username.downcase[0..2]) + username = username.gsub(/SMTP:|SIP:|sip:|smtp:/,'') + else + username = field[:email_addresses][:elems][1][:entry][:text].split("@")[0] + if ['smt','sip'].include?(username.downcase[0..2]) + username = username.gsub(/SMTP:|SIP:|sip:|smtp:/,'') + else + username = nil + end + end end username end From be935dc7d672f03d52de80a363b2f41006288e86 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 8 May 2018 19:48:30 +1000 Subject: [PATCH 0401/1752] (QSC:remote) improve compatibility with biamp and BSS --- modules/qsc/q_sys_remote.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index d69e3de5..9e2ac068 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -420,7 +420,7 @@ def process(values, name: nil) str = value[:String] if BoolVals.include?(str) - self["fader#{component}#{name}_mute"] = str == 'true' + self["fader#{name}_#{component}_mute"] = str == 'true' else # Seems like string values can be independant of the other values # This should mostly work to detect a string value @@ -428,13 +428,13 @@ def process(values, name: nil) self["#{component}#{name}"] = str next end - + if pos # Float between 0 and 1 if @integer_faders - self["fader#{component}#{name}"] = (pos * 1000).to_i + self["fader#{name}_#{component}"] = (pos * 1000).to_i else - self["fader#{component}#{name}"] = pos + self["fader#{name}_#{component}"] = pos end elsif val.is_a?(String) self["#{component}#{name}"] = val From f6b943fa7c5097349e1b88e13cf315a6f0b6c074 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 8 May 2018 21:09:23 +1000 Subject: [PATCH 0402/1752] (QSC:remote) improve compatibility with biamp and BSS --- modules/qsc/q_sys_remote.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index 9e2ac068..4cbf2d55 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -409,7 +409,7 @@ def received(data, resolve, command) BoolVals = ['true', 'false'] def process(values, name: nil) - component = name.present? ? "#{name}_" : '' + component = name.present? ? "_#{name}" : '' values.each do |value| name = value[:Name] val = value[:Value] @@ -420,29 +420,29 @@ def process(values, name: nil) str = value[:String] if BoolVals.include?(str) - self["fader#{name}_#{component}_mute"] = str == 'true' + self["fader#{name}#{component}_mute"] = str == 'true' else # Seems like string values can be independant of the other values # This should mostly work to detect a string value if val == 0.0 && pos == 0.0 && str[0] != '0' - self["#{component}#{name}"] = str + self["#{name}#{component}"] = str next end if pos # Float between 0 and 1 if @integer_faders - self["fader#{name}_#{component}"] = (pos * 1000).to_i + self["fader#{name}#{component}"] = (pos * 1000).to_i else - self["fader#{name}_#{component}"] = pos + self["fader#{name}#{component}"] = pos end elsif val.is_a?(String) - self["#{component}#{name}"] = val + self["#{name}#{component}"] = val else if @integer_faders - self["fader#{component}#{name}"] = (val * 10).to_i + self["fader#{name}#{component}"] = (val * 10).to_i else - self["fader#{component}#{name}"] = val + self["fader#{name}#{component}"] = val end end end From 378b0dc449acd024ddba9a2557bb196c86828afa Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 10 May 2018 01:12:58 +1000 Subject: [PATCH 0403/1752] (qsc:mixer) add mute_toggle --- modules/qsc/q_sys_control.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/qsc/q_sys_control.rb b/modules/qsc/q_sys_control.rb index f2e5eab1..381bca0f 100644 --- a/modules/qsc/q_sys_control.rb +++ b/modules/qsc/q_sys_control.rb @@ -177,6 +177,9 @@ def unmute(mute_id, index = nil) mute(mute_id, false, index) end + def mute_toggle(mute_id, index = nil) + mute(mute_id, !self["fader#{mute_id}_mute"], index) + end def snapshot(name, index, ramp_time = 1.5) send "ssl #{name} #{index} #{ramp_time}\n", wait: false From f0c234835187ee7efb4e4b5629013f349a0712a2 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 10 May 2018 01:19:06 +1000 Subject: [PATCH 0404/1752] (shure:mxw) Track mute button presses --- modules/shure/microphone/mxw.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/shure/microphone/mxw.rb b/modules/shure/microphone/mxw.rb index eef32179..72f54547 100644 --- a/modules/shure/microphone/mxw.rb +++ b/modules/shure/microphone/mxw.rb @@ -21,7 +21,7 @@ def on_load end def on_update - + do_send('GET MUTE_BUTTON_STATUS') end def connected @@ -39,7 +39,17 @@ def disconnected def received(data, resolve, command) logger.debug { "-- received: #{data}" } - + response = data.split(' ') + return if response[0] != 'REP' + + property = response[1] + value = response[2] + + case property + when 'MUTE_BUTTON_STATUS' + self[:mute_button] = value == 'ON' + end + return :success end @@ -54,6 +64,6 @@ def do_poll def do_send(command, options = {}) logger.debug { "-- sending: < #{command} >" } - send("< #{command} >", options) + send("< #{command}" >, options) end end From 92c5499d14fc88f21f1c29b9cb6d10bd9415d328 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 10 May 2018 07:48:54 +1000 Subject: [PATCH 0405/1752] (aca:router) provide ability to lookup inputs used for routes --- modules/aca/router.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index e7ac0fa5..4b8d074f 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -77,6 +77,13 @@ def connect(signal_map, atomic: false) end end + # Lookup the name of the input on a sink node that would be used to connect + # a source to it. + def input_for(source, sink) + _, edges = route source, sink + edges.last.input + end + protected def signal_graph From e40d964f3ca1a72a6d240a9217b6fda94de93c1d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 10 May 2018 08:30:31 +1000 Subject: [PATCH 0406/1752] (aca:router) support specifying sparse numeric / index based inputs as hash --- modules/aca/router.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 4b8d074f..757cd165 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -16,6 +16,11 @@ class Aca::Router devices and complex/layered switching infrastructure. DESC + default_settings( + # Nested hash of signal connectivity. See SignalGraph.from_map. + connections: {} + ) + def on_load on_update end @@ -25,7 +30,15 @@ def on_update @path_cache = nil - connections = setting(:connections) || {} + connections = setting(:connections).transform_values do |inputs| + # Read in numeric inputs as ints (as JSON based settings do not + # allow non-string keys) + if inputs.is_a? Hash + inputs.transform_keys! { |i| Integer(i) rescue i } + else + inputs + end + end begin @signal_graph = SignalGraph.from_map(connections).freeze rescue From 7fa8249090ee3e959ae62844db592c49f6151dee Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 10 May 2018 11:58:48 +1000 Subject: [PATCH 0407/1752] Add impersionation for booking creation in exchange lib --- lib/microsoft/exchange.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index f45205f8..1b9da5a8 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -230,7 +230,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. end end - def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney') + def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney', use_act_as: false) STDERR.puts "CREATING NEW BOOKING IN LIBRARY" STDERR.puts "room_email is #{room_email}" STDERR.puts "start_param is #{start_param}" @@ -266,7 +266,12 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: STDERR.puts "MAKING REQUEST WITH" STDERR.puts booking STDERR.flush - folder = @ews_client.get_folder(:calendar, { act_as: room_email }) + if use_act_as + folder = @ews_client.get_folder(:calendar, { act_as: room_email }) + else + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user.email) + folder = @ews_client.get_folder(:calendar) + end appointment = folder.create_item(booking) { id: appointment.id, From 50a418e60df521640436787ce9aa452549a8f3f6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 10 May 2018 12:20:02 +1000 Subject: [PATCH 0408/1752] Add room as attendee --- lib/microsoft/exchange.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 1b9da5a8..2bde313e 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -262,6 +262,9 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: attendee: { mailbox: { email_address: attendee}} }) end + booking[:required_attendees].push({ + attendee: { mailbox: { email_address: room_email}} + }) booking[:body] = description STDERR.puts "MAKING REQUEST WITH" STDERR.puts booking @@ -278,6 +281,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: start: start_param, end: end_param, attendees: attendees, + location: room_email, subject: subject } end From b115886b46753da0f22bca37ff48537c327703f2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 10 May 2018 12:29:50 +1000 Subject: [PATCH 0409/1752] Add location field to ews booking creation --- lib/microsoft/exchange.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 2bde313e..b07a08e2 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -248,6 +248,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: booking = {} booking[:subject] = subject booking[:title] = subject + booking[:location] = room_email booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop # booking[:body] = description booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop From 081a79d992c1a96bd11f809b93a944a85cef4247 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 10 May 2018 13:18:24 +1000 Subject: [PATCH 0410/1752] Add location name not email to bookings --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index b07a08e2..e7555f8d 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -282,7 +282,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: start: start_param, end: end_param, attendees: attendees, - location: room_email, + location: Orchestrator::ControlSystem.find_by_email(room_email).name, subject: subject } end From f10bb17ed04fab67a25524f0ce91ffede57276f2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 10 May 2018 14:08:00 +1000 Subject: [PATCH 0411/1752] Add room as resource if impersonating --- lib/microsoft/exchange.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index e7555f8d..c48c6b80 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -249,6 +249,13 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: booking[:subject] = subject booking[:title] = subject booking[:location] = room_email + booking[:resources] = { + attendee: { + mailbox: { + email_address: room_email + } + } + } booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop # booking[:body] = description booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop From 52ce2dc3ccf1969db45b2b8388735cbf6e6ddb8e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 10 May 2018 16:47:24 +1000 Subject: [PATCH 0412/1752] (aca:router) remove compatability check on update Checking module's for existance / API compatability at time of settings update was of little value as these could be spun up or swapped out at a later time. --- modules/aca/router.rb | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 757cd165..5e6c1fa4 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -45,8 +45,6 @@ def on_update logger.error 'invalid connection settings' end - check_compatability - # TODO: track active signal source at each node and expose as a hash self[:nodes] = signal_graph.map(&:id) self[:inputs] = signal_graph.sinks.map(&:id) @@ -186,29 +184,10 @@ def activate(edge) end end - # TODO: execute this on system device create / remove / stop / start etc - def check_compatability - invalid = Set.new - signal_graph.each do |node| - node.edges.each_pair do |_, edge| - mod = system[edge.device] - is_switch = edge.output.nil? && mod.respond_to?(:switch_to) - is_matrix = !edge.output.nil? && mod.respond_to?(:switch) - invalid << edge.device if mod.nil? || !(is_switch || is_matrix) - end - end - - if invalid.empty? - true else - logger.warn do - modules = invalid.to_a.join ', ' - "incompatible or non-existent modules in config: #{modules}" - end - false end end end From a9dfc55b85148000bfe523239cae394c9aa9509d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 10 May 2018 16:48:08 +1000 Subject: [PATCH 0413/1752] (aca:router) provide nicer edge activation --- modules/aca/router.rb | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 5e6c1fa4..62cef300 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -177,17 +177,22 @@ def check_conflicts(routes, strict: false) end def activate(edge) - if edge.output.nil? - system[edge.device].switch_to edge.input - else - system[edge.device].switch edge.input => edge.output - end - end + mod = system[edge.device] + if edge.nx1? && mod.respond_to(:switch_to) + mod.switch_to edge.input + elsif edge.nxn? && mod.respond_to(:switch) + mod.switch edge.input => edge.output + elsif edge.nx1? && signal_graph.outdegree(edge.source) == 1 + logger.warn "cannot perform switch on #{edge.device}. " \ + "This may be ok as only one input (#{edge.target}) is defined." + thread.defer.resolve else + thread.defer.reject "cannot interact with #{edge.device}. " \ + 'Module may be offline or incompatible.' end end end @@ -209,6 +214,16 @@ class Aca::Router::SignalGraph def to_s "#{target} to #{device} (in #{input})" end + + # Check if the edge is a switchable input on a single output device + def nx1? + edge.output.nil? + end + + # Check if the edge a matrix switcher / multi-output device + def nxn? + !nx1? + end end class Node From 1b93567ad17923457bd5a8ad16d39d09e37e2fe7 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 10 May 2018 17:40:48 +1000 Subject: [PATCH 0414/1752] (aca:router) fix nx1 check --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 62cef300..09259ef7 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -217,7 +217,7 @@ def to_s # Check if the edge is a switchable input on a single output device def nx1? - edge.output.nil? + output.nil? end # Check if the edge a matrix switcher / multi-output device From 1345d9994df8c04f8be1aa6ff24044ef2755c4ee Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 10 May 2018 17:47:03 +1000 Subject: [PATCH 0415/1752] (aca:router) fix module API compatability check --- modules/aca/router.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 09259ef7..dbbd39e5 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -179,10 +179,10 @@ def check_conflicts(routes, strict: false) def activate(edge) mod = system[edge.device] - if edge.nx1? && mod.respond_to(:switch_to) + if edge.nx1? && mod.respond_to?(:switch_to) mod.switch_to edge.input - elsif edge.nxn? && mod.respond_to(:switch) + elsif edge.nxn? && mod.respond_to?(:switch) mod.switch edge.input => edge.output elsif edge.nx1? && signal_graph.outdegree(edge.source) == 1 From 2977b5e4d34058a0db1d7a8645369d55508d2d27 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 11 May 2018 01:07:11 +1000 Subject: [PATCH 0416/1752] (aca:router) implement ability to retrieve upstream device of an input --- modules/aca/router.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index dbbd39e5..f130c464 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -90,11 +90,34 @@ def connect(signal_map, atomic: false) # Lookup the name of the input on a sink node that would be used to connect # a source to it. + # + # This can be used to register the source on devices such as codecs that + # support upstream switching. def input_for(source, sink) _, edges = route source, sink edges.last.input end + # Get the device and associated input immediately upstream of a node. + # + # Depending on the device API, this may be of use for determining signal + # presence. + def upstream(source, sink = nil) + if sink.nil? + edges = signal_graph.incoming_edges source + unless edges.size == 1 + raise ArgumentError "more than one edge to #{source} " \ + 'please specify a sink' + end + edge = edges.values.first + else + _, edges = route source, sink + edge = edges.first + end + + [edge.device, edge.input] + end + protected def signal_graph From fdceb3c91e21b13f58b01bab5eabcd67362b45e5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 11 May 2018 15:24:06 +1000 Subject: [PATCH 0417/1752] If wills email then use gerards --- lib/microsoft/exchange.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index c48c6b80..5686f3f7 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -280,7 +280,12 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: if use_act_as folder = @ews_client.get_folder(:calendar, { act_as: room_email }) else - @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user.email) + if current_user.email == "w.le@acaprojects.com" + impersonation_email = "Gerard.Seeto@didata.com.au" + else + impersonation_email = current_user.email + end + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], impersonation_email) folder = @ews_client.get_folder(:calendar) end appointment = folder.create_item(booking) From d828e96fc73f27729acf006d2534b9f85219bb2a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 11 May 2018 15:32:04 +1000 Subject: [PATCH 0418/1752] Remove setting of the location field --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 5686f3f7..2d85ba85 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -294,7 +294,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: start: start_param, end: end_param, attendees: attendees, - location: Orchestrator::ControlSystem.find_by_email(room_email).name, + # location: Orchestrator::ControlSystem.find_by_email(room_email).name, subject: subject } end From d253dad89cd6679e847f4ea283c3ccc335c5cba2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 11 May 2018 15:33:12 +1000 Subject: [PATCH 0419/1752] Remove setting of the location field --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 2d85ba85..506b7010 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -248,7 +248,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: booking = {} booking[:subject] = subject booking[:title] = subject - booking[:location] = room_email + # booking[:location] = room_email booking[:resources] = { attendee: { mailbox: { From bff5eeee0992b13e2ac673a066495b330cdeff77 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 11 May 2018 15:57:37 +1000 Subject: [PATCH 0420/1752] Fix resources field in create xml --- lib/microsoft/exchange.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 506b7010..a49af013 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -249,13 +249,13 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: booking[:subject] = subject booking[:title] = subject # booking[:location] = room_email - booking[:resources] = { + booking[:resources] = [{ attendee: { mailbox: { email_address: room_email } } - } + }] booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop # booking[:body] = description booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop @@ -285,7 +285,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: else impersonation_email = current_user.email end - @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], impersonation_email) + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user.email) folder = @ews_client.get_folder(:calendar) end appointment = folder.create_item(booking) From abf6c94468ff837bf26ae2919759c56b8d6091a8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 11 May 2018 16:01:16 +1000 Subject: [PATCH 0421/1752] Final fixes for resource booking --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index a49af013..eaa117c0 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -285,7 +285,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: else impersonation_email = current_user.email end - @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user.email) + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], impersonation_email) folder = @ews_client.get_folder(:calendar) end appointment = folder.create_item(booking) From 799b25c6ca533e40b4bc75c007fdff21d6731e11 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 11 May 2018 17:13:49 +1000 Subject: [PATCH 0422/1752] (exchange:bookings) support Arrows on UI --- modules/aca/exchange_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 6caf563e..2651d6ee 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -114,6 +114,7 @@ def on_update self[:booking_disable_future] = setting(:booking_disable_future) self[:booking_max_duration] = setting(:booking_max_duration) self[:timeout] = setting(:timeout) + self[:arrow_direction] = setting(:arrow_direction) @check_meeting_ending = setting(:check_meeting_ending) # seconds before meeting ending @extend_meeting_by = setting(:extend_meeting_by) || 15.minutes.to_i From 9df40b8ac9d5a9248435b741535789588f67b715 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 14 May 2018 16:11:47 +1000 Subject: [PATCH 0423/1752] (zen control:lighting) add driver --- modules/zencontrol/lighting.rb | 67 +++++++++++++++++++++++++++++ modules/zencontrol/lighting_spec.rb | 8 ++++ 2 files changed, 75 insertions(+) create mode 100644 modules/zencontrol/lighting.rb create mode 100644 modules/zencontrol/lighting_spec.rb diff --git a/modules/zencontrol/lighting.rb b/modules/zencontrol/lighting.rb new file mode 100644 index 00000000..c05b5be7 --- /dev/null +++ b/modules/zencontrol/lighting.rb @@ -0,0 +1,67 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Zencontrol; end + +# Documentation: https://aca.im/driver_docs/zencontrol/lighting_udp.pdf + +class Zencontrol::Lighting + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + udp_port 5108 + descriptive_name 'Zencontrol Lighting' + generic_name :Lighting + + # Communication settings + wait_response false + + def on_load + on_update + end + + def on_update + @version = setting(:version) || 1 + controller = setting(:controller_id)&.to_i + + if controller + @controller = int_to_array(controller, bytes: 6) + else + @controller = [0xFF] * 6 + end + end + + # Using indirect commands + def trigger(area, scene) + # Area 128 – 191 == Address 0 – 63 + # Area 192 – 207 == Group 0 – 15 + # Area 255 == Broadcast + # + # Scene 0 - 15 + area = in_range(area.to_i, 127) + 128 + scene = in_range(scene.to_i, 15) + 16 + do_send(area, scene) + end + + # Using direct command + def light_level(area, level, channel = nil, fade = nil) + area = in_range(area.to_i, 127) + level = in_range(level.to_i, 255) + do_send(area, level.to_i) + end + + def received(data, resolve, command) + logger.debug { "received 0x#{byte_to_hex(data)}" } + :success + end + + + protected + + + def do_send(address, command, **options) + cmd = [@version, *@controller, address, command] + send(cmd, options) + end +end diff --git a/modules/zencontrol/lighting_spec.rb b/modules/zencontrol/lighting_spec.rb new file mode 100644 index 00000000..64a1fbc3 --- /dev/null +++ b/modules/zencontrol/lighting_spec.rb @@ -0,0 +1,8 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +Orchestrator::Testing.mock_device 'Zencontrol::Lighting' do + # Set Group 15 to Arc Level 240 on all controllers + exec(:light_level, 0x4F, 240) + .should_send("\x01\xFF\xFF\xFF\xFF\xFF\xFF\x4F\xF0") +end From 56a12382594cd8a9a51d8cdcaca42ba2653172a9 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 14 May 2018 16:12:41 +1000 Subject: [PATCH 0424/1752] (aca:http_ping) add simple http ping module for monitoring a HTTP device --- modules/aca/http_ping.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 modules/aca/http_ping.rb diff --git a/modules/aca/http_ping.rb b/modules/aca/http_ping.rb new file mode 100644 index 00000000..e9fb3162 --- /dev/null +++ b/modules/aca/http_ping.rb @@ -0,0 +1,27 @@ +module Aca; end + +class Aca::HttpPing + include ::Orchestrator::Constants + + implements :service + descriptive_name 'Check if service is live' + generic_name :HttpPing + + keepalive false + + def on_load + schedule.every('60s') { check_status } + on_update + end + + def on_update + @path = setting(:path) || '/' + @result = setting(:result) || 200 + end + + def check_status + get(@path) do |data| + set_connected_state(data.status == @result) + end + end +end From b6626c5277be330403b7a24d1f26d343e9694644 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 14 May 2018 16:22:15 +1000 Subject: [PATCH 0425/1752] (clear_one:converge) alias preset function as trigger for better cross audio system compatibility --- modules/clear_one/converge.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/clear_one/converge.rb b/modules/clear_one/converge.rb index 210f067f..d45797de 100755 --- a/modules/clear_one/converge.rb +++ b/modules/clear_one/converge.rb @@ -107,6 +107,7 @@ def preset(number, type = :macro) do_send type, number, name: type.to_sym end alias_method :macro, :preset + alias_method :trigger, :preset def start_audio do_send "startAudio" From 8375176744d95fa13f2eaa51ec85fb74a985d8d8 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 14 May 2018 16:39:30 +1000 Subject: [PATCH 0426/1752] (shure:mxw) move polling into do_poll --- modules/shure/microphone/mxw.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/shure/microphone/mxw.rb b/modules/shure/microphone/mxw.rb index 72f54547..633dba16 100644 --- a/modules/shure/microphone/mxw.rb +++ b/modules/shure/microphone/mxw.rb @@ -21,7 +21,6 @@ def on_load end def on_update - do_send('GET MUTE_BUTTON_STATUS') end def connected @@ -55,7 +54,7 @@ def received(data, resolve, command) def do_poll - # TODO + do_send('GET MUTE_BUTTON_STATUS') end From 2f413a0da84928a3e83d11790d4287f7277ef263 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 14 May 2018 16:48:27 +1000 Subject: [PATCH 0427/1752] Remove code specific to a client --- lib/microsoft/exchange.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index eaa117c0..df04e918 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -280,11 +280,6 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: if use_act_as folder = @ews_client.get_folder(:calendar, { act_as: room_email }) else - if current_user.email == "w.le@acaprojects.com" - impersonation_email = "Gerard.Seeto@didata.com.au" - else - impersonation_email = current_user.email - end @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], impersonation_email) folder = @ews_client.get_folder(:calendar) end From 352c48201d519428838ab99ab989a71dbf9dbaab Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 14 May 2018 20:40:49 +1000 Subject: [PATCH 0428/1752] (shure:mxw) syntax --- modules/shure/microphone/mxw.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shure/microphone/mxw.rb b/modules/shure/microphone/mxw.rb index 633dba16..302ec26c 100644 --- a/modules/shure/microphone/mxw.rb +++ b/modules/shure/microphone/mxw.rb @@ -63,6 +63,6 @@ def do_poll def do_send(command, options = {}) logger.debug { "-- sending: < #{command} >" } - send("< #{command}" >, options) + send("< #{command} >", options) end end From b0359b78d834da23d42f069e29bffd860f0c59d4 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 14 May 2018 22:24:53 +1000 Subject: [PATCH 0429/1752] (aca:http_ping) set connected state as false when request errors --- modules/aca/http_ping.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aca/http_ping.rb b/modules/aca/http_ping.rb index e9fb3162..3075bb97 100644 --- a/modules/aca/http_ping.rb +++ b/modules/aca/http_ping.rb @@ -20,8 +20,10 @@ def on_update end def check_status - get(@path) do |data| + get(@path, name: :check_status) { |data| set_connected_state(data.status == @result) + }.catch do + set_connected_state(false) end end end From 5dbae185cba39e48b1ddaf21644c5682b5002ef9 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 14 May 2018 22:29:46 +1000 Subject: [PATCH 0430/1752] (aca:http_ping) don't update connected status when a request succeeds or fails as we want to manually maintain it. --- modules/aca/http_ping.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/aca/http_ping.rb b/modules/aca/http_ping.rb index 3075bb97..2a62d82a 100644 --- a/modules/aca/http_ping.rb +++ b/modules/aca/http_ping.rb @@ -17,6 +17,9 @@ def on_load def on_update @path = setting(:path) || '/' @result = setting(:result) || 200 + + # Don't update status on connection failure as we maintaining this + config update_status: false end def check_status From 11e260189943137431c6412db57ae57cf514244e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 15 May 2018 12:04:13 +1000 Subject: [PATCH 0431/1752] Add icon setting to exchange bookings --- modules/aca/exchange_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 2651d6ee..d5d74cc2 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -115,6 +115,7 @@ def on_update self[:booking_max_duration] = setting(:booking_max_duration) self[:timeout] = setting(:timeout) self[:arrow_direction] = setting(:arrow_direction) + self[:icon] = setting(:icon) @check_meeting_ending = setting(:check_meeting_ending) # seconds before meeting ending @extend_meeting_by = setting(:extend_meeting_by) || 15.minutes.to_i From 5748470647e4f6e20c9ab6cb8548b9833a2713db Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 15 May 2018 13:52:30 +1000 Subject: [PATCH 0432/1752] Fix impersonation request --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index df04e918..0d55dd06 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -280,7 +280,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: if use_act_as folder = @ews_client.get_folder(:calendar, { act_as: room_email }) else - @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], impersonation_email) + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user.email) folder = @ews_client.get_folder(:calendar) end appointment = folder.create_item(booking) From 86ef2624e9694b8ed5dfcd6b30624fdb4348adfa Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 17 May 2018 11:20:23 +1000 Subject: [PATCH 0433/1752] Catch error from no viewpoint gem present --- lib/microsoft/exchange.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 0d55dd06..db285750 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -1,6 +1,5 @@ require 'active_support/time' require 'logger' -require 'viewpoint2' module Microsoft; end @@ -15,6 +14,13 @@ def initialize( internet_proxy:nil, logger: Rails.logger ) + begin + require 'viewpoint2' + rescue LoadError + STDERR.puts 'VIEWPOINT NOT PRESENT' + STDERR.flush + end + end @ews_url = ews_url @service_account_email = service_account_email @service_account_password = service_account_password From 9622cc297e5ab7b414ebc0584aa63d1c2c421205 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 17 May 2018 11:22:32 +1000 Subject: [PATCH 0434/1752] Fix error catch syntax --- lib/microsoft/exchange.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index db285750..54ddda42 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -17,9 +17,8 @@ def initialize( begin require 'viewpoint2' rescue LoadError - STDERR.puts 'VIEWPOINT NOT PRESENT' - STDERR.flush - end + STDERR.puts 'VIEWPOINT NOT PRESENT' + STDERR.flush end @ews_url = ews_url @service_account_email = service_account_email From 02c7f1f6e42e205f5841ba17236f1b963ba4c854 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 17 May 2018 13:13:14 +1000 Subject: [PATCH 0435/1752] Set title to private for sensitive meetings --- modules/aca/exchange_booking.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index d5d74cc2..73d5b27a 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -754,6 +754,11 @@ def todays_bookings subject = item[:subject] + # Set subject to private if sensitive + if ['private', 'confidential'].include?(meeting.sensitivity.downcase) + subject = "Private" + end + { :Start => start, :End => ending, From 724903776e8e4991dd04271c18bd25d0b4395a8b Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 17 May 2018 15:34:41 +1000 Subject: [PATCH 0436/1752] (lightware:switcher) add switch_audio for compatibility --- modules/lightware/switcher/lightware_protocol.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/lightware/switcher/lightware_protocol.rb b/modules/lightware/switcher/lightware_protocol.rb index b1d78b45..cba8a99d 100644 --- a/modules/lightware/switcher/lightware_protocol.rb +++ b/modules/lightware/switcher/lightware_protocol.rb @@ -88,6 +88,10 @@ def switch_video(map) switch map end + def switch_audio(map) + switch map + end + def routing_state?(**options) send("{VC}\r\n", options) end From 927c1d10de4b52f85788dbd4c33fa97165edc1d6 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 17 May 2018 15:39:36 +1000 Subject: [PATCH 0437/1752] (aca:meeting) audio_deembed switches audio only --- modules/aca/meeting_room.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index 637d453a..e1002be6 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -1267,7 +1267,7 @@ def show(source, display) end if disp_source[:audio_deembed] - switcher.switch({disp_source[:input] => disp_source[:audio_deembed]}) + switcher.switch_audio({disp_source[:input] => disp_source[:audio_deembed]}) end end From 48c4373aaf291a280f2fd256e051f101c8deac8e Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 17 May 2018 21:56:13 +1000 Subject: [PATCH 0438/1752] (lutron:lighting) add new driver written by steve tested ok --- modules/lutron/lighting.rb | 195 +++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 modules/lutron/lighting.rb diff --git a/modules/lutron/lighting.rb b/modules/lutron/lighting.rb new file mode 100644 index 00000000..10adf68e --- /dev/null +++ b/modules/lutron/lighting.rb @@ -0,0 +1,195 @@ +module Lutron; end + +class Lutron::Lighting + + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + tcp_port 23 + descriptive_name 'Lutron Lighting Gateway' + generic_name :Lighting + + tokenize delimiter: "\r\n" + wait_response false + delay between_sends: 100 + + def on_load + on_update + end + + def on_unload + end + + def on_update + @login = setting(:login) || 'nwk' + @trigger_type = setting(:trigger) || :area + end + + def connected + send "#{@login}\r\n", priority: 9999 + schedule.every('40s') do + logger.debug "-- Polling Lutron" + scene? 1 + end + end + + def disconnected + schedule.clear + end + + def restart + send_cmd 'RESET', 0 + end + + def lighting(device, state, action = 1) + level = is_affirmative?(state) ? 100 : 0 + light_level(device, level, 1, 0) + end + + def level(device, level, rate = 1000, component = :output) + level = in_range(level.to_i, 100) + seconds = (rate.to_i / 1000).to_i + min = seconds / 60 + seconds = seconds - (min * 60) + time = "#{min.to_s.rjust(2, '0')}:#{seconds.to_s.rjust(2, '0')}" + send_cmd component.to_s.upcase, device, 1, level, time + end + + def blinds(device, action, component = :shadegrp) + case action.to_s.downcase + when 'raise', 'up' + send_cmd component.to_s.upcase, device, 3 + when 'lower', 'down' + send_cmd component.to_s.upcase, device, 2 + when 'stop' + send_cmd component.to_s.upcase, device, 4 + end + end + + + def scene(area, scene, component = :area) + send_cmd(component.to_s.upcase, area, 6, scene).then do + scene?(area, component) + end + end + + def scene?(area, component = :area) + send_query component.to_s.upcase, area, 6 + end + + + + def occupancy?(area) + send_query 'AREA', area, 8 + end + + def daylight_mode?(area) + send_query 'AREA', area, 7 + end + + def daylight(area, mode) + val = is_affirmative?(mode) ? 1 : 2 + send_cmd 'AREA', area, 7, val + end + + def button_press(area, button) + send_cmd 'DEVICE', area, button, 3 + end + + + def led(area, device, state) + val = if state.is_a?(Integer) + state + else + is_affirmative?(state) ? 1 : 0 + end + + send_cmd 'DEVICE', area, device, 9, val + end + + def led?(area, device) + send_query 'DEVICE', area, device, 9 + end + + def trigger(area, scene) + scene(area, scene, @trigger_type) + end + + def light_level(area, level, component = nil, fade = 1000) + if component + level(area, level, rate = 1000, component) + else + level(area, level, rate = 1000, :area) + end + end + + Errors = { + '1' => 'Parameter count mismatch', + '2' => 'Object does not exist', + '3' => 'Invalid action number', + '4' => 'Parameter data out of range', + '5' => 'Parameter data malformed', + '6' => 'Unsupported Command' + } + + Occupancy = { + '1' => :unknown, + '2' => :inactive, + '3' => :occupied, + '4' => :unoccupied + } + + def received(data, resolve, command) + logger.debug { "Lutron sent: #{data}" } + parts = data.split(',') + component = parts[0][1..-1].downcase + + case component.to_sym + when :area, :output, :shadegrp + area = parts[1] + action = parts[2].to_i + param = parts[3] + + case action + when 1 + self[:"#{component}#{area}_level"] = param.to_f + when 6 + self[:"#{component}#{area}"] = param.to_i + when 7 + self[:"#{component}#{area}_daylight"] = param == '1' + when 8 + self[:"#{component}#{area}_occupied"] = Occupancy[param] + end + when :device + area = parts[1] + device = parts[2] + action = parts[3].to_i + + case action + when 7 + self[:"device#{area}_#{device}"] = parts[4].to_i + when 9 + self[:"device#{area}_#{device}_led"] = parts[4].to_i + end + when :error + logger.warn "error #{parts[1]}: #{Errors[parts[1]]}" + return :abort + end + return :success + end + + + protected + + def send_cmd(*command, **options) + cmd = "##{command.join(',')}" + logger.debug { "Requesting: #{cmd}" } + send("#{cmd}\r\n", options) + end + + def send_query(*command, **options) + cmd = "?#{command.join(',')}" + logger.debug { "Requesting: #{cmd}" } + send("#{cmd}\r\n", options) + end +end From 782e678eb25ab50ac6272a64c02caa53fed3c77c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 18 May 2018 15:16:04 +1000 Subject: [PATCH 0439/1752] Update google driver to use proper gem --- modules/aca/google_refresh_booking.rb | 46 +++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/modules/aca/google_refresh_booking.rb b/modules/aca/google_refresh_booking.rb index 43a872a1..19a1e589 100644 --- a/modules/aca/google_refresh_booking.rb +++ b/modules/aca/google_refresh_booking.rb @@ -30,7 +30,9 @@ class Aca::GoogleRefreshBooking false end CAN_GOOGLE = begin - require 'google_calendar' + require 'googleauth' + require 'google/apis/admin_directory_v1' + require 'google/apis/calendar_v3' true rescue LoadError false @@ -81,8 +83,10 @@ class Aca::GoogleRefreshBooking # Optional EWS for creating and removing bookings google_organiser_location: 'attendees' - # google_client_id: ENV["GOOGLE_APP_CLIENT_ID"], - # google_secret: ENV["GOOGLE_APP_CLIENT_SECRET"], + google_client_id: '', + google_secret: '', + google_redirect_uri: '', + google_scope: 'https://www.googleapis.com/auth/calendar', # google_scope: ENV['GOOGLE_APP_SCOPE'], # google_site: ENV["GOOGLE_APP_SITE"], # google_token_url: ENV["GOOGLE_APP_TOKEN_URL"], @@ -157,6 +161,7 @@ def on_update @google_secret = setting(:google_client_secret) @google_redirect_uri = setting(:google_redirect_uri) @google_refresh_token = setting(:google_refresh_token) + @google_scope = setting(:google_scope) @google_room = (setting(:google_room) || system.email) # supports: SMTP, PSMTP, SID, UPN (user principle name) # NOTE:: Using UPN we might be able to remove the LDAP requirement @@ -318,27 +323,22 @@ def fetch_bookings(*args) # client = OAuth2::Client.new(@google_client_id, @google_secret, {site: @google_site, token_url: @google_token_url}) - # Create an instance of the calendar. - params = {:client_id => @google_client_id, - :client_secret => @google_secret, - :calendar => @google_room, - :redirect_url => @google_redirect_uri}.to_json - logger.debug params - begin - cal = Google::Calendar.new(:client_id => @google_client_id, - :client_secret => @google_secret, - :calendar => @google_room, - :redirect_url => @google_redirect_uri # this is what Google uses for 'applications' - ) - cal.connection.login_with_refresh_token(@google_refresh_token) - - events = cal.find_events_in_range(Time.now.midnight, Time.now.tomorrow.midnight, {max_results: 2500}) - rescue Exception => e - logger.debug e.message - logger.debug e.backtrace.inspect - raise e - end + options = { + client_id: @google_client_id, + client_secret: @google_secret, + scope: @google_scope, + redirect_uri: @google_redirect_uri, + refresh_token: @google_refresh_token, + grant_type: "refresh_token" + } + authorization = Google::Auth::UserRefreshCredentials.new options + + Calendar = Google::Apis::CalendarV3 + calendar = Calendar::CalendarService.new + calendar.authorization = authorization + events = calendar.list_events(system.email) + task { todays_bookings(events) }.then(proc { |bookings| From cf7dfc97847375335dd2b33a95739cc97f4cc9a0 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 18 May 2018 18:50:48 +1000 Subject: [PATCH 0440/1752] (cisco:ce) add support for pushing device config --- .../cisco/collaboration_endpoint/room_os.rb | 33 ++++++++++++++----- modules/cisco/collaboration_endpoint/sx20.rb | 11 +++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 89a0d3ac..5b9d6b2d 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -55,6 +55,8 @@ def connected schedule.every('30s') { heartbeat timeout: 35 } end + push_config + sync_config end @@ -116,7 +118,7 @@ def xcommand(command, args = {}) # @param settings [Hash] the configuration values to apply # @param [::Libuv::Q::Promise] resolves when the commands complete def xconfiguration(path, settings) - send_xconfigurations path, settings + send_xconfigurations path => settings end # Trigger a status update for the specified path. @@ -190,9 +192,22 @@ def send_xconfiguration(path, setting, value) end # Apply a set of configurations. - def send_xconfigurations(path, settings) + def send_xconfigurations(config) + flatten = lambda do |hash| + hash.each_with_object({}) do |(k, v), h| + if v.is_a? Hash + v = flatten[v] if v.values.any? { |x| x.is_a? Hash } + h["#{k} #{h_k}"] = h_v + else + h[k] = v + end + end + end + + config = flatten[config] + # The API only allows a single setting to be applied with each request. - interactions = settings.to_a.map do |(setting, value)| + interactions = settings.map do |setting, value| send_xconfiguration(path, setting, value) end @@ -201,12 +216,7 @@ def send_xconfigurations(path, settings) if resolved.all? :success else - failures = resolved.zip(settings.keys) - .reject(&:first) - .map(&:last) - - thread.defer.reject 'Could not apply all settings. ' \ - "Failed on #{failures.join ', '}." + thread.defer.reject 'Failed to apply all settings.' end end end @@ -357,6 +367,11 @@ def bind_status(path, status_key) end end + def push_config + config = setting(:device_config) || {} + send_xconfigurations config + end + def sync_config bind_feedback '/Configuration', :configuration send "xConfiguration *\n", wait: false diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 76c2daf3..57d34c6f 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -21,6 +21,17 @@ class Cisco::CollaborationEndpoint::Sx20 < Cisco::CollaborationEndpoint::RoomOs wait_ready: Tokens::LOGIN_COMPLETE clear_queue_on_disconnect! + def connected + super + + register_feedback '/Event/PresentationPreviewStarted' do + self[:local_presentation] = true + end + register_feedback '/Event/PresentationPreviewStopped' do + self[:local_presentation] = false + end + end + status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume status 'RoomAnalytics PeoplePresence' => :presence_detected From 1fb07e0d7548603b16e68f738707de279f441575 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 19 May 2018 15:10:44 +1000 Subject: [PATCH 0441/1752] (cisco:ce) fix incorrect spec for standby state subscription --- modules/cisco/collaboration_endpoint/room_os_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os_spec.rb b/modules/cisco/collaboration_endpoint/room_os_spec.rb index 2d1b6e42..aca95ea0 100644 --- a/modules/cisco/collaboration_endpoint/room_os_spec.rb +++ b/modules/cisco/collaboration_endpoint/room_os_spec.rb @@ -306,7 +306,7 @@ def section(message) } JSON ) - expect(status[:standby_state]).to be :Standby + expect(status[:standby_state]).to be true # ------------------------------------------------------------------------- section 'Commands' From 8fc1e314483209a5fdbfae1e863a0177a6214caf Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 19 May 2018 15:56:41 +1000 Subject: [PATCH 0442/1752] (cisco:ce) fix parsing of config structure --- .../cisco/collaboration_endpoint/room_os.rb | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 5b9d6b2d..cceb3849 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -193,22 +193,17 @@ def send_xconfiguration(path, setting, value) # Apply a set of configurations. def send_xconfigurations(config) - flatten = lambda do |hash| - hash.each_with_object({}) do |(k, v), h| - if v.is_a? Hash - v = flatten[v] if v.values.any? { |x| x.is_a? Hash } - h["#{k} #{h_k}"] = h_v - else - h[k] = v - end - end + # Reduce the config to a strucure of { [path] => value } + flatten = lambda do |h, path = [], settings = {}| + return settings.update(path => h) unless h.is_a? Hash + h.each { |key, subtree| flatten[subtree, path + [key], settings] } + settings end - config = flatten[config] # The API only allows a single setting to be applied with each request. - interactions = settings.map do |setting, value| - send_xconfiguration(path, setting, value) + interactions = config.map do |(*path, setting), value| + send_xconfiguration path, setting, value end thread.finally(interactions).then do |results| From 99219478b08666d55a60cde87af34e57404e5c3c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 19 May 2018 15:56:59 +1000 Subject: [PATCH 0443/1752] (cisco:ce) add method docs --- modules/cisco/collaboration_endpoint/room_os.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index cceb3849..7e5fc4e4 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -144,6 +144,10 @@ def self.extended(child) # Perform the actual command execution - this allows device implementations # to protect access to #xcommand and still refer the gruntwork here. + # + # @param comand [String] the xAPI command to execute + # @param args [Hash] the command keyword args + # @return [::Libuv::Q::Promise] that will resolve when execution is complete def send_xcommand(command, args = {}) request = Action.xcommand command, args @@ -176,6 +180,11 @@ def send_xcommand(command, args = {}) end # Apply a single configuration on the device. + # + # @param path [String] the configuration path + # @param setting [String] the configuration parameter + # @param value [#to_s] the configuration value + # @return [::Libuv::Q::Promise] def send_xconfiguration(path, setting, value) request = Action.xconfiguration path, setting, value @@ -192,6 +201,9 @@ def send_xconfiguration(path, setting, value) end # Apply a set of configurations. + # + # @param config [Hash] a deeply nested hash of the configurations to apply + # @return [::Libuv::Q::Promise] def send_xconfigurations(config) # Reduce the config to a strucure of { [path] => value } flatten = lambda do |h, path = [], settings = {}| @@ -220,6 +232,7 @@ def send_xconfigurations(config) # # @param path [String] # @yield [response] a pre-parsed response object for the status query + # @return [::Libuv::Q:Promise] def send_xstatus(path) request = Action.xstatus path From a6bf40da2e4a3b22e840e2787eac247016b91ad5 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 20 May 2018 16:12:46 +1000 Subject: [PATCH 0444/1752] (cisco:ce) neaten up speakertrack control --- modules/cisco/collaboration_endpoint/sx80.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 98b1be89..9e503734 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -103,11 +103,8 @@ def mic_mute(state = On) # The 'integrator' account can't active/deactive SpeakerTrack, but we can # cut off access via a configuration setting. def speaker_track(state = On) - if is_affirmative? state - send_xconfiguration 'Cameras SpeakerTrack', :Mode, :Auto - else - send_xconfiguration 'Cameras SpeakerTrack', :Mode, :Off - end + mode = is_affirmative? state ? :Auto : :Off + send_xconfiguration 'Cameras SpeakerTrack', :Mode, mode end command 'Standby Deactivate' => :powerup From d02bcd31ac59bc17439965024b9af0abf5a26108 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 20 May 2018 16:59:06 +1000 Subject: [PATCH 0445/1752] (cisco:ce) remove #source_audio in favour of direct xconfiguration control --- modules/cisco/collaboration_endpoint/external_source.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/external_source.rb b/modules/cisco/collaboration_endpoint/external_source.rb index 478b6d03..c0ff03a2 100644 --- a/modules/cisco/collaboration_endpoint/external_source.rb +++ b/modules/cisco/collaboration_endpoint/external_source.rb @@ -24,11 +24,6 @@ def self.included(base) base.prepend Hooks end - def source_audio(state) - mode = is_affirmative?(state) ? :On : :Off - send_xconfiguration 'Audio Input HDMI 3', :Mode, mode - end - # TODO: protect methods (via ::Orchestrator::Security) that manipulate # sources. Currently mapper does not support this from within a module. command 'UserInterface Presentation ExternalSource Add' => :add_source, From 3d03de3af59f4805e4f39573e4aa825e4b057fc2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 20 May 2018 20:06:57 +1000 Subject: [PATCH 0446/1752] (cisco:ce) ensure xapi mapper is available --- modules/cisco/collaboration_endpoint/external_source.rb | 2 ++ modules/cisco/collaboration_endpoint/ui_extensions.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/external_source.rb b/modules/cisco/collaboration_endpoint/external_source.rb index c0ff03a2..1870708f 100644 --- a/modules/cisco/collaboration_endpoint/external_source.rb +++ b/modules/cisco/collaboration_endpoint/external_source.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +load File.join(__dir__, 'xapi', 'mapper.rb') + module Cisco; end module Cisco::CollaborationEndpoint; end diff --git a/modules/cisco/collaboration_endpoint/ui_extensions.rb b/modules/cisco/collaboration_endpoint/ui_extensions.rb index 2147c0f3..1b693db7 100644 --- a/modules/cisco/collaboration_endpoint/ui_extensions.rb +++ b/modules/cisco/collaboration_endpoint/ui_extensions.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +load File.join(__dir__, 'xapi', 'mapper.rb') + module Cisco; end module Cisco::CollaborationEndpoint; end From c6c1522f31c1275028aa17fc571bd196c97a1ec2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 20 May 2018 22:38:43 +1000 Subject: [PATCH 0447/1752] (cisco:ce) add test for settings push --- .../collaboration_endpoint/room_os_spec.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os_spec.rb b/modules/cisco/collaboration_endpoint/room_os_spec.rb index aca95ea0..348921ce 100644 --- a/modules/cisco/collaboration_endpoint/room_os_spec.rb +++ b/modules/cisco/collaboration_endpoint/room_os_spec.rb @@ -3,7 +3,12 @@ Orchestrator::Testing.mock_device 'Cisco::CollaborationEndpoint::RoomOs', settings: { peripheral_id: 'MOCKED_ID', - version: 'MOCKED_VERSION' + version: 'MOCKED_VERSION', + device_config: { + Audio: { + DefaultVolume: 100 + } + } } do # Patch in some tracking of request UUID's so we can form and validate # device comms. @@ -75,6 +80,18 @@ def section(message) JSON ) + # ------------------------------------------------------------------------- + section 'Config push' + + should_send "xConfiguration Audio DefaultVolume: 100 | resultId=\"#{id_peek}\"\n" + responds( + <<~JSON + { + "ResultId": \"#{id_pop}\" + } + JSON + ) + # ------------------------------------------------------------------------- section 'Initial state sync' From 7c4e7dadeabfe2be6197b12fbdd85bdc08a5878e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 20 May 2018 23:30:48 +1000 Subject: [PATCH 0448/1752] (cisco:ce) fix logic in settings push --- modules/cisco/collaboration_endpoint/room_os.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 7e5fc4e4..2ac8ad3e 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -207,7 +207,7 @@ def send_xconfiguration(path, setting, value) def send_xconfigurations(config) # Reduce the config to a strucure of { [path] => value } flatten = lambda do |h, path = [], settings = {}| - return settings.update(path => h) unless h.is_a? Hash + return settings.merge!(path => h) unless h.is_a? Hash h.each { |key, subtree| flatten[subtree, path + [key], settings] } settings end @@ -215,7 +215,7 @@ def send_xconfigurations(config) # The API only allows a single setting to be applied with each request. interactions = config.map do |(*path, setting), value| - send_xconfiguration path, setting, value + send_xconfiguration path.join(' '), setting, value end thread.finally(interactions).then do |results| From d3f082ae9ce998ba597e082094f8f95befb59813 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 20 May 2018 23:31:16 +1000 Subject: [PATCH 0449/1752] (cisco:ce) protect against using strings instead of syms --- modules/cisco/collaboration_endpoint/xapi/action.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/xapi/action.rb b/modules/cisco/collaboration_endpoint/xapi/action.rb index 2d3955f9..f272e9f0 100644 --- a/modules/cisco/collaboration_endpoint/xapi/action.rb +++ b/modules/cisco/collaboration_endpoint/xapi/action.rb @@ -35,6 +35,8 @@ def create_action(type, *args, **kwargs) "Invalid action type. Must be one of #{ACTION_TYPE}." end + kwargs.merge! args.pop if args.last.is_a? Hash + kwargs = kwargs.compact.map do |name, value| value = "\"#{value}\"" if value.is_a? String "#{name}: #{value}" From 42037bbe49cb84c21257ccbfdeb4c0a6620e6e4a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 21 May 2018 20:51:50 +1000 Subject: [PATCH 0450/1752] (cisco:ce) support pushing xconfiguration as complete hash --- modules/cisco/collaboration_endpoint/room_os.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 2ac8ad3e..22ea8ef3 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -114,11 +114,19 @@ def xcommand(command, args = {}) # Push a configuration settings to the device. # - # @param path [String] the configuration path + # May be specified as either a deeply nested hash of settings, or a + # pre-concatenated path along with a subhash for drilling through deeper + # parts of the tree. + # + # @param path [Hash, String] the configuration or top level path # @param settings [Hash] the configuration values to apply # @param [::Libuv::Q::Promise] resolves when the commands complete - def xconfiguration(path, settings) - send_xconfigurations path => settings + def xconfiguration(path, settings = nil) + if settings.nil? + send_xconfigurations path + else + send_xconfigurations path => settings + end end # Trigger a status update for the specified path. From 65a7debb67c52c4c82134091931d1b88e3456000 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 22 May 2018 23:41:09 +1000 Subject: [PATCH 0451/1752] (cisco:sx80) add phonebook source as setting (corporate or local) --- modules/cisco/tele_presence/sx_series_common.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 493c3a59..bbc3c8e2 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -9,6 +9,7 @@ def on_load def on_update @default_source = setting(:presentation) || 3 + @phonebook_source = setting(:phonebook_source) || "Local" #"Local" or "Corporate" @count = 0 end @@ -107,7 +108,7 @@ def mute_status end SearchDefaults = { - :PhonebookType => :Local, # Should probably make this a setting + :PhonebookType => @phonebook_source.to_sym, :Limit => 10, :ContactType => :Contact, :SearchField => :Name From 0256b1d783861c5b9f973155d85aceaaa9efeb0b Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 22 May 2018 23:46:23 +1000 Subject: [PATCH 0452/1752] (cisco:sx80) new format for phonebook results; add clear_search_results() fix phonebook searching allow frontend to now show last sessions phonebook search results System.init_vc should call VidConf.clear_search.results() --- modules/cisco/tele_presence/sx_series_common.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index bbc3c8e2..59de65c1 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -118,6 +118,11 @@ def search(text, opts = {}) opts[:SearchString] = text command(:phonebook, :search, params(opts), name: :phonebook, max_waits: 400) end + + def clear_search_results + self[:search_results] = nil + end + # Options include: auto, custom, equal, fullscreen, overlay, presentationlargespeaker, presentationsmallspeaker, prominent, single, speaker_full def layout(mode, target = :local) @@ -304,7 +309,7 @@ def received(data, resolve, command) def process_results(result) case result[1].downcase.to_sym - when :resultset + when :phonebooksearchresult @listing_phonebook = true case result[2] From ec206c62cd3a7f51c0e27422d31332b120151f98 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 22 May 2018 23:47:56 +1000 Subject: [PATCH 0453/1752] (aca:meeting) clear past VC search results from UI when tab is selected --- modules/aca/meeting_room.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index e1002be6..47f17b56 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -882,6 +882,7 @@ def shutdown_actual(scheduled_shutdown = false) def init_vc start_cameras system[:VidConf].wake + system[:VidConf].clear_search.results end def vc_status_changed(state) From f81bca8d08897bb8bb0435034f92916550166ff7 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 22 May 2018 23:55:28 +1000 Subject: [PATCH 0454/1752] (cisco:sx80) support phonebook results in old format too --- modules/cisco/tele_presence/sx_series_common.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 59de65c1..a80e8ae6 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -309,7 +309,7 @@ def received(data, resolve, command) def process_results(result) case result[1].downcase.to_sym - when :phonebooksearchresult + when :phonebooksearchresult, :resultset @listing_phonebook = true case result[2] From e67c3674d6b3663de06037da0cc365dfe6f4e95e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 23 May 2018 09:30:52 +1000 Subject: [PATCH 0455/1752] (cisco:vc sx series) default to Local phonebook --- modules/cisco/tele_presence/sx_series_common.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index b8437fbe..8cb15d77 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -10,7 +10,6 @@ def on_load def on_update @corporate_dir = setting(:use_corporate_directory) || false @default_source = setting(:presentation) || 3 - @phonebook_source = setting(:phonebook_source) || "Local" #"Local" or "Corporate" @count = 0 end @@ -109,7 +108,7 @@ def mute_status end SearchDefaults = { - :PhonebookType => @phonebook_source.to_sym, + :PhonebookType => :Local, :Limit => 10, :ContactType => :Contact, :SearchField => :Name From 7ad8f9c896507e786aff1a67c2be556613f80f49 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 23 May 2018 11:09:09 +1000 Subject: [PATCH 0456/1752] (cisco:ce) fix issue with speakertrack control not working --- modules/cisco/collaboration_endpoint/room_kit.rb | 7 ++----- modules/cisco/collaboration_endpoint/sx80.rb | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 5b1b9f7c..036dbea7 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -104,11 +104,8 @@ def mic_mute(state = On) # The 'integrator' account can't active/deactive SpeakerTrack, but we can # cut off access via a configuration setting. def speaker_track(state = On) - if is_affirmative? state - send_xconfiguration 'Cameras SpeakerTrack', :Mode, :Auto - else - send_xconfiguration 'Cameras SpeakerTrack', :Mode, :Off - end + mode = is_affirmative?(state) ? :Auto : :Off + send_xconfiguration 'Cameras SpeakerTrack', :Mode, mode end command 'Standby Deactivate' => :powerup diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 9e503734..06f5a345 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -103,7 +103,7 @@ def mic_mute(state = On) # The 'integrator' account can't active/deactive SpeakerTrack, but we can # cut off access via a configuration setting. def speaker_track(state = On) - mode = is_affirmative? state ? :Auto : :Off + mode = is_affirmative?(state) ? :Auto : :Off send_xconfiguration 'Cameras SpeakerTrack', :Mode, mode end From 1204fc29e3be97d27f6538d0fed1cbce2c2907a9 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 25 May 2018 11:18:15 +1000 Subject: [PATCH 0457/1752] (cisco:ce) remove command naming due to issues with name conflicts / dropped commands --- modules/cisco/collaboration_endpoint/room_os.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 22ea8ef3..ab80486e 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -159,7 +159,10 @@ def self.extended(child) def send_xcommand(command, args = {}) request = Action.xcommand command, args - do_send request, name: command do |response| + # FIXME: commands are currently unnamed. Need to add a way of tagging + # related commands for better queue management. + + do_send request do |response| # The result keys are a little odd: they're a concatenation of the # last two command elements and 'Result', unless the command # failed in which case it's just 'Result'. From 421b2f5c72a36e193d19b7246af0cb6245ae7760 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 28 May 2018 20:51:25 +1000 Subject: [PATCH 0458/1752] (aca:router) add query of the downstream device of an output node --- modules/aca/router.rb | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index f130c464..23fcd195 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -98,7 +98,7 @@ def input_for(source, sink) edges.last.input end - # Get the device and associated input immediately upstream of a node. + # Get the device and associated input immediately upstream of an input node. # # Depending on the device API, this may be of use for determining signal # presence. @@ -106,10 +106,10 @@ def upstream(source, sink = nil) if sink.nil? edges = signal_graph.incoming_edges source unless edges.size == 1 - raise ArgumentError "more than one edge to #{source} " \ + raise ArgumentError, "more than one edge to #{source}, " \ 'please specify a sink' end - edge = edges.values.first + _, edge = edges.first else _, edges = route source, sink edge = edges.first @@ -118,6 +118,26 @@ def upstream(source, sink = nil) [edge.device, edge.input] end + # Get the device immediately preceeding an output node. + # + # This may be used walking back up the signal graph to find a decoder for + # and output device. + def downstream(sink, source = nil) + if source.nil? + edges = signal_graph.outgoing_edges sink + unless edges.size == 1 + raise ArgumentError, "more than one input to #{sink}, " \ + 'please specify a source' + end + _, edge = edges.first + else + _, edges = route source, sink + edge = edges.last + end + + edge.target + end + protected def signal_graph From c20317decf1530be05f3e0a179f6b4f2f128a5a4 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 29 May 2018 16:41:36 +1000 Subject: [PATCH 0459/1752] (samsung:display) implement screen split control --- modules/samsung/displays/md_series.rb | 30 +++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index df02e0e0..2cf1c3e6 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -12,7 +12,7 @@ class Samsung::Displays::MdSeries # Discovery Information tcp_port 1515 - descriptive_name 'Samsung MD & DM Series LCD' + descriptive_name 'Samsung MD, DM & QM Series LCD' generic_name :Display # Markdown description @@ -97,7 +97,8 @@ def disconnected :speaker => 0x68, :net_standby => 0xB5, # Keep NIC active in standby :eco_solution => 0xE6, # Eco options (auto power off) - :auto_power => 0x33 + :auto_power => 0x33, + :screen_split => 0xB2 # Tri / quad split (larger panels only) } COMMAND.merge!(COMMAND.invert) @@ -174,6 +175,31 @@ def switch_to(input, options = {}) do_send(:input, INPUTS[input], options) end + + SCALE = { + fill: 0x09, + fit: 0x20 + }.tap { |x| x.merge!(x.invert).freeze } + + def split(inputs = [:hdmi, :hdmi2, :hdmi3, :dvi], layout = 0, options = {}) + unless (3..4).cover? inputs.size + logger.error 'display can only be split between 3 or 4 sources' + return + end + + data = [ + 1, # enable split + 0, # sound from screen section 1 + layout + ] + data += inputs.flat_map do |input| + [INPUTS[input], SCALE[:fit]] + end + + do_send(:screen_split, data, options) + end + + def volume(vol, options = {}) vol = in_range(vol.to_i, 100) do_send(:volume, vol, options) From e7885a8818bc91757b4b714a481d90340954bcf0 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 29 May 2018 17:32:31 +1000 Subject: [PATCH 0460/1752] (samsung:display) add 4th HDMI input --- modules/samsung/displays/md_series.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 2cf1c3e6..3a82534a 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -159,6 +159,8 @@ def unmute :hdmi2_pc => 0x24, :hdmi3 => 0x31, :hdmi3_pc => 0x32, + :hdmi4 => 0x33, + :hdmi4_pc => 0x34, :display_port => 0x25, :dtv => 0x40, :media => 0x60, @@ -181,7 +183,7 @@ def switch_to(input, options = {}) fit: 0x20 }.tap { |x| x.merge!(x.invert).freeze } - def split(inputs = [:hdmi, :hdmi2, :hdmi3, :dvi], layout = 0, options = {}) + def split(inputs = [:hdmi, :hdmi2, :hdmi3, :hdmi4], layout = 0, options = {}) unless (3..4).cover? inputs.size logger.error 'display can only be split between 3 or 4 sources' return @@ -193,6 +195,7 @@ def split(inputs = [:hdmi, :hdmi2, :hdmi3, :dvi], layout = 0, options = {}) layout ] data += inputs.flat_map do |input| + input = input.to_sym if input.is_a? String [INPUTS[input], SCALE[:fit]] end From 69b16131475a9699cf01e041f11a67771aa8e44b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 29 May 2018 18:52:57 +1000 Subject: [PATCH 0461/1752] (samsung:display) fix incorrect packet structure for screen split --- modules/samsung/displays/md_series.rb | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 3a82534a..e6cf6b32 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -183,21 +183,25 @@ def switch_to(input, options = {}) fit: 0x20 }.tap { |x| x.merge!(x.invert).freeze } - def split(inputs = [:hdmi, :hdmi2, :hdmi3, :hdmi4], layout = 0, options = {}) - unless (3..4).cover? inputs.size - logger.error 'display can only be split between 3 or 4 sources' + def split(enable = On, inputs = [:hdmi2, :hdmi3, :hdmi4], layout = 0, options = {}) + unless (2..3).cover? inputs.size + logger.error 'display can only be split between 3 or 4 sources ' \ + '(including current primary source)' return end + enable = is_affirmative? enable + data = [ - 1, # enable split - 0, # sound from screen section 1 - layout - ] - data += inputs.flat_map do |input| - input = input.to_sym if input.is_a? String - [INPUTS[input], SCALE[:fit]] - end + enable ? 1 : 0, + 0, # sound from screen section 1 + layout, # layout mode (1..6) + SCALE[:fit], # scaling for main source + inputs.flat_map do |input| + input = input.to_sym if input.is_a? String + [INPUTS[input], SCALE[:fit]] + end + ].flatten do_send(:screen_split, data, options) end From fc1dd64dbbe1cfe77ae801ba25e3fe2111446f5e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 29 May 2018 20:26:58 +1000 Subject: [PATCH 0462/1752] (samsung:display) neaten up split screen control --- modules/samsung/displays/md_series.rb | 28 ++++++++++++--------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index e6cf6b32..bb0ff4e7 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -178,35 +178,31 @@ def switch_to(input, options = {}) end - SCALE = { + SCALE_MODE = { fill: 0x09, fit: 0x20 }.tap { |x| x.merge!(x.invert).freeze } - def split(enable = On, inputs = [:hdmi2, :hdmi3, :hdmi4], layout = 0, options = {}) - unless (2..3).cover? inputs.size - logger.error 'display can only be split between 3 or 4 sources ' \ - '(including current primary source)' - return - end - - enable = is_affirmative? enable + # 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 = [ - enable ? 1 : 0, - 0, # sound from screen section 1 - layout, # layout mode (1..6) - SCALE[:fit], # scaling for main source + 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[:fit]] + [INPUTS[input], SCALE_MODE[scale]] end ].flatten - do_send(:screen_split, data, options) + 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) From 4f6f8bac5816b9108875c6a0a50f93b577b3f47a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 29 May 2018 20:27:30 +1000 Subject: [PATCH 0463/1752] (samsung:display) fix issue where non-trivial device responses would be aborted --- modules/samsung/displays/md_series.rb | 70 ++++++++++++++------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index bb0ff4e7..59f9c518 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -329,45 +329,47 @@ def received(response, resolve, command) logger.debug { "Samsung sent #{byte_to_hex(response)}" } data = str_to_array(response) - if data[2] == 3 # Check for correct data length - status = data[3] - command = data[4] - value = data[5] - - if status == 0x41 # 'A' - case COMMAND[command] - when :panel_mute - self[:power] = value == 0 - when :volume - self[:volume] = value - if self[:audio_mute] && value > 0 - self[:audio_mute] = false - end - when :brightness - self[:brightness] = value - when :input - self[:input] = INPUTS[value] - if not self[:input_stable] - if self[:input_target] == self[:input] - self[:input_stable] = true - else - switch_to(self[:input_target]) - end + + len = data[2] + status = data[3] + command = data[4] + value = len == 1 ? data[5] : data[5, len] + + case status + when 0x41 # Ack + case COMMAND[command] + when :panel_mute + self[:power] = value == 0 + when :volume + self[:volume] = value + if self[:audio_mute] && value > 0 + self[:audio_mute] = false + end + when :brightness + self[:brightness] = value + when :input + self[:input] = INPUTS[value] + if not self[:input_stable] + if self[:input_target] == self[:input] + self[:input_stable] = true + else + switch_to(self[:input_target]) end - when :speaker - self[:speaker] = Speaker_Modes[value] - when :hard_off - self[:hard_off] = value == 0 end - - return :success - else - logger.debug "Samsung failed with: #{byte_to_hex(array_to_str(data))}" - return :failed # Failed response + when :speaker + self[:speaker] = Speaker_Modes[value] + when :hard_off + self[:hard_off] = value == 0 end + :success + + when 0x4e # Nak + logger.debug "Samsung failed with: #{byte_to_hex(array_to_str(data))}" + :failed # Failed response + else logger.debug "Samsung aborted with: #{byte_to_hex(array_to_str(data))}" - return :abort # unknown result + :abort # unknown result end end From ea2c1ccf7296e5ce30fdd341305f853c2b61c2cf Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 29 May 2018 20:31:19 +1000 Subject: [PATCH 0464/1752] (samsung:display) add tracking of screen split state --- modules/samsung/displays/md_series.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 59f9c518..bb1ec0cf 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -360,6 +360,8 @@ def received(response, resolve, command) self[:speaker] = Speaker_Modes[value] when :hard_off self[:hard_off] = value == 0 + when :screen_split + self[:screen_split] = value.positive? end :success From f88d10588db4fc703f2dee870ba10cee949ebeb3 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 29 May 2018 20:45:54 +1000 Subject: [PATCH 0465/1752] (samsung:display) fix issue with split screen state tracking --- modules/samsung/displays/md_series.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index bb1ec0cf..e98c7f1e 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -361,7 +361,8 @@ def received(response, resolve, command) when :hard_off self[:hard_off] = value == 0 when :screen_split - self[:screen_split] = value.positive? + state = value[0] + self[:screen_split] = state.positive? end :success From 3c292b051964c6f49355a8d5f0b8404ab2f405bf Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 30 May 2018 17:06:25 +1000 Subject: [PATCH 0466/1752] (samsung:display) neaten up response handling --- modules/samsung/displays/md_series.rb | 61 ++++++++++----------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index e98c7f1e..34391d24 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -325,37 +325,38 @@ def wake(broadcast = nil) end end + RESPONSE_STATUS = { + ack: 0x41, + nak: 0x4e + }.tap { |x| x.merge!(x.invert).freeze } + def received(response, resolve, command) logger.debug { "Samsung sent #{byte_to_hex(response)}" } - data = str_to_array(response) + rx = str_to_array response + + unless rx.pop == (rx.reduce(:+) & 0xFF) + logger.error 'invalid checksum' + return :retry + end - len = data[2] - status = data[3] - command = data[4] - value = len == 1 ? data[5] : data[5, len] + _, _, _, status, command, *value = rx + value = value.first if value.length == 1 - case status - when 0x41 # Ack + case RESPONSE_STATUS[status] + when :ack case COMMAND[command] when :panel_mute self[:power] = value == 0 when :volume self[:volume] = value - if self[:audio_mute] && value > 0 - self[:audio_mute] = false - end + self[:audio_mute] = false if value > 0 when :brightness self[:brightness] = value when :input self[:input] = INPUTS[value] - if not self[:input_stable] - if self[:input_target] == self[:input] - self[:input_stable] = true - else - switch_to(self[:input_target]) - end - end + self[:input_stable] = self[:input] == self[:input_target] + switch_to self[:input_target] unless self[:input_stable] when :speaker self[:speaker] = Speaker_Modes[value] when :hard_off @@ -366,7 +367,7 @@ def received(response, resolve, command) end :success - when 0x4e # Nak + when :nak logger.debug "Samsung failed with: #{byte_to_hex(array_to_str(data))}" :failed # Failed response @@ -376,16 +377,6 @@ def received(response, resolve, command) end end - # Currently not used. We could check it if we like :) - def check_checksum(byte_str) - response = str_to_array(byte_str) - check = 0 - response[0..-2].each do |byte| - check = (check + byte) & 0xFF - end - response[-1] == check - end - # Called by the Abstract Tokenizer def check_length(byte_str) response = str_to_array(byte_str) @@ -400,15 +391,6 @@ def check_length(byte_str) end end - # Called by do_send to create a checksum - def checksum(command) - check = 0 - command.each do |byte| - check = (check + byte) & 0xFF - end - command << check - end - def do_send(command, data = [], options = {}) data = Array(data) @@ -417,9 +399,10 @@ def do_send(command, data = [], options = {}) command = COMMAND[command] end - data = [command, @id, data.length] + data # Build request (0xFF is screen id) - checksum(data) # Add checksum + data = [command, @id, data.length] + data # Build request + data << (data.reduce(:+) & 0xFF) # Add checksum data = [0xAA] + data # Add header + send(array_to_str(data), options) end end From dfd77ff9ea3fc80cb66bf49ea4dce38e87c0abda Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 31 May 2018 12:13:03 +1000 Subject: [PATCH 0467/1752] (aca:demo_logic) expose system name Name binding is used in initial example in ngx-composer-starter --- modules/aca/demo_logic.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/demo_logic.rb b/modules/aca/demo_logic.rb index 7af505aa..92e9bdd6 100644 --- a/modules/aca/demo_logic.rb +++ b/modules/aca/demo_logic.rb @@ -9,6 +9,7 @@ class Aca::DemoLogic def on_load + self[:name] = system.name self[:volume] = 0 self[:mute] = false self[:views] = 0 From 0fb24c279a04595f48b4d7503180d00bdb1996e0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 31 May 2018 12:36:55 +1000 Subject: [PATCH 0468/1752] [Exchange driver]Check for unfetched conflicting bookings on create --- modules/aca/exchange_booking.rb | 67 +++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 99a8cfc8..1ef6f3ef 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -423,8 +423,30 @@ def set_end_meeting_warning(meeting_ref = nil, extendable = false) def clear_end_meeting_warning self[:meeting_ending] = self[:last_meeting_started] end + + def log(msg) + STDERR.puts msg + logger.info msg + STDERR.flush + end # --------- + def check_conflict(start_time) + + items = get_todays_bookings + + conflict = false + items.each do |meeting| + meeting_start = Time.parse(meeting.ews_item[:start][:text]).to_i + meeting_end = Time.parse(meeting.ews_item[:end][:text]).to_i + # Remove any meetings that match the start time provided + if start_time >= meeting_start && start_time < meeting_end + conflict = true + end + end + conflict + end + def create_meeting(options) # Check that the required params exist required_fields = [:start, :end] @@ -435,6 +457,9 @@ def create_meeting(options) raise "missing required fields: #{check}" end + # Check for existing booking that hasn't been fetched yet + raise "Booking conflict" if check_conflict((options[:start].to_i / 1000)) + req_params = {} req_params[:room_email] = @ews_room req_params[:subject] = options[:title] @@ -634,33 +659,8 @@ def make_ews_booking(user_email: nil, subject: 'On the spot booking', room_email end def delete_ews_booking(delete_at) - now = Time.now - if @timezone - start = now.in_time_zone(@timezone).midnight - ending = now.in_time_zone(@timezone).tomorrow.midnight - else - start = now.midnight - ending = now.tomorrow.midnight - end - count = 0 - - cli = Viewpoint::EWSClient.new(*@ews_creds) - - if @use_act_as - # TODO:: think this line can be removed?? - # delete_at = Time.parse(delete_at.to_s).to_i - - opts = {} - opts[:act_as] = @ews_room if @ews_room - - folder = cli.get_folder(:calendar, opts) - items = folder.items({:calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) - else - cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room - items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) - end - + items = get_todays_bookings items.each do |meeting| meeting_time = Time.parse(meeting.ews_item[:start][:text]) @@ -675,7 +675,8 @@ def delete_ews_booking(delete_at) count end - def todays_bookings + + def get_todays_bookings now = Time.now if @timezone start = now.in_time_zone(@timezone).midnight @@ -685,10 +686,13 @@ def todays_bookings ending = now.tomorrow.midnight end + cli = Viewpoint::EWSClient.new(*@ews_creds) - if @use_act_as + # TODO:: think this line can be removed?? + # delete_at = Time.parse(delete_at.to_s).to_i + opts = {} opts[:act_as] = @ews_room if @ews_room @@ -698,6 +702,13 @@ def todays_bookings cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) end + items + end + + + def todays_bookings + items = get_todays_bookings + now = Time.now skype_exists = set_skype_url = system.exists?(:Skype) set_skype_url = true if @force_skype_extract From b1218db4fff802897d458fbd98ddbe22ace45354 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 31 May 2018 16:41:17 +1000 Subject: [PATCH 0469/1752] (samsung:display) set default target input on init --- modules/samsung/displays/md_series.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 34391d24..d1e504bf 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -43,6 +43,7 @@ def on_load # Meta data for inquiring interfaces self[:type] = :lcd self[:input_stable] = true + self[:input_target] ||= :hdmi end def on_unload From 89ac59e908ca0164577d111ab6e64b4ed0b074b3 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 May 2018 18:45:35 +1000 Subject: [PATCH 0470/1752] (biamp:tesira) add 'trigger' --- modules/biamp/tesira.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/biamp/tesira.rb b/modules/biamp/tesira.rb index 2a3d693b..ac903272 100755 --- a/modules/biamp/tesira.rb +++ b/modules/biamp/tesira.rb @@ -78,7 +78,8 @@ def preset(number_or_name) do_send build(:DEVICE, :recallPresetByName, number_or_name) end end - + alias_method :trigger, :preset + def start_audio do_send "DEVICE startAudio" end From 7c4c46b32a9d58689d595bc73ae7bb9a60725893 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 May 2018 18:46:27 +1000 Subject: [PATCH 0471/1752] (hitachi:projector) add hdbaset input --- modules/hitachi/projector/cp_tw_series_basic.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/hitachi/projector/cp_tw_series_basic.rb b/modules/hitachi/projector/cp_tw_series_basic.rb index cec2cf6b..46f604ab 100755 --- a/modules/hitachi/projector/cp_tw_series_basic.rb +++ b/modules/hitachi/projector/cp_tw_series_basic.rb @@ -170,7 +170,8 @@ def filter_hours_reset InputCodes = { 0x03 => :hdmi, - 0x0d => :hdmi2 + 0x0d => :hdmi2, + 0x11 => :hdbaset } ErrorCodes = { From d7edca905d1ad974faf3f2e79709de686498176f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 31 May 2018 19:43:12 +1000 Subject: [PATCH 0472/1752] Fix private booking issue --- modules/aca/exchange_booking.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 1ef6f3ef..3eaec3b3 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -766,17 +766,18 @@ def todays_bookings # Prevent connections handing with TIME_WAIT # cli.ews.connection.httpcli.reset_all - subject = item[:subject] # Set subject to private if sensitive if ['private', 'confidential'].include?(meeting.sensitivity.downcase) subject = "Private" + else + subject = item[:subject] end { :Start => start, :End => ending, - :Subject => subject ? subject[:text] : "Private", + :Subject => subject, :owner => item[:organizer][:elems][0][:mailbox][:elems][0][:name][:text], :setup => 0, :breakdown => 0, From 4311ccca0f03e24729824b23dbfd4510eac244d6 Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 2 Jun 2018 15:56:53 +1000 Subject: [PATCH 0473/1752] (aca:meeting_room): VC shutdown and init tweaks shutdown: end call, mute mics init: unmute mics, fix clearing of phonebook search results (on VC tab) --- modules/aca/meeting_room.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index 47f17b56..5585b1ba 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -751,7 +751,9 @@ def shutdown_actual(scheduled_shutdown = false) end switch_mode(@defaults[:shutdown_mode]) if @defaults[:shutdown_mode] + self[:vc_content_source] = nil + shutdown_vc mixer = system[:Mixer] @@ -879,10 +881,18 @@ def shutdown_actual(scheduled_shutdown = false) # # MISC FUNCTIONS # + + def shutdown_vc + vidconf = system[:VidConf] + vidconf.call('disconnect') + vc_mute true + end + def init_vc - start_cameras + system[:VidConf].clear_search_results system[:VidConf].wake - system[:VidConf].clear_search.results + start_cameras + vc_mute false end def vc_status_changed(state) @@ -898,10 +908,9 @@ def vc_status_changed(state) end def vc_mute(mute) - perform_action(mod: :System, func: :vc_mute_actual, args: [mute]) - vidconf = system[:VidConf] vidconf.mute(mute) unless vidconf.nil? + perform_action(mod: :System, func: :vc_mute_actual, args: [mute]) end def vc_mute_actual(mute) From 2467fd75e202ff93f3ff5c690a23194f018c7fef Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 2 Jun 2018 17:04:52 +1000 Subject: [PATCH 0474/1752] (cisc:sx) add wake() --- modules/cisco/tele_presence/sx_series_common.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 8cb15d77..8f344b31 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -245,6 +245,10 @@ def far_end_camera(action, call_id = @last_call_id) command :FarEndControl, :Camera, :Move, "CallId:#{call_id} Value:#{req}" end end + + def wake(**options) + command :Standby, :Deactivate, params(options) + end From 4e884c6e035be859c51930f4420c3e14a98ea742 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 4 Jun 2018 10:44:57 +1000 Subject: [PATCH 0475/1752] Extract string from title --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 3eaec3b3..b29fd3f7 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -771,7 +771,7 @@ def todays_bookings if ['private', 'confidential'].include?(meeting.sensitivity.downcase) subject = "Private" else - subject = item[:subject] + subject = item[:subject][:text] end { From 5b874ec8228ef88d1511daa2e6d529b17b761cda Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 4 Jun 2018 14:41:37 +1000 Subject: [PATCH 0476/1752] (cisco:sx) fix select_presentation() --- modules/cisco/tele_presence/sx_series_common.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 8f344b31..5df06b06 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -187,9 +187,9 @@ def select_camera(index) end def select_presentation(index) - # NOTE:: Index should be a number - command('xConfiguration Video', params({ - :DefaultPresentationSource => index + # NOTE: Index should be a number (generally 1-4) + configuration('Video Presentation', params({ + :DefaultSource => index }), name: :select_presentation) end From 147183f4d3ebfbd1708678ed3ac5944da1dd4cf6 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 4 Jun 2018 14:42:19 +1000 Subject: [PATCH 0477/1752] (cisc:sx) add video_output_mode(Single|Dual|Auto) --- modules/cisco/tele_presence/sx_series_common.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 5df06b06..5817954a 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -193,6 +193,12 @@ def select_presentation(index) }), name: :select_presentation) end + def video_output_mode(video_mode) + # NOTE: video_mode should be Single or Dual or Auto + configuration('Video', params({ + :Monitors => video_mode + }), name: :video_output_mode) + end # ==================== # END Common functions # ==================== From addb6fe16808e56b4c347d03aee38264c8bed1d9 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 4 Jun 2018 20:56:47 +1000 Subject: [PATCH 0478/1752] (tvone:coriomaster) implement comms proxy --- modules/tv_one/corio_master.rb | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index a6e09d94..22f0448f 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -77,6 +77,45 @@ def window(id, property, value = nil) end end + # Provide some meta programming to enable the driver format to match the + # device capabilities. + # + # @see Proxy + def method_missing(*context) + sender = ->(command) { do_send command } + Proxy.new(sender).__send__(*context) + end + + # Build an execution context for deeply nested device state / behaviour. + # + # This will continue to return itself, building up a path, until called + # with a method ending in one of the following execution flags: + # '=' assign a value to a device property + # '?' query the current value of a property + # '!' execute an on-device action + class Proxy + def initialize(sender, path = []) + @sender = sender + @path = path + end + + def method_missing(name, *args) + segment, action = name.to_s.match(/^(\w+)(\=|\?|\!)?$/).captures + @path << segment + + case action + when '=' + @sender.call "#{@path.join '.'} = #{args.first}" + when '?' + @sender.call "#{@path.join '.'}" + when '!' + @sender.call "#{@path.join '.'}()" + else + self + end + end + end + end # Runs any command provided def send_command(cmd) From cdeb53eaa2f061537b2de88e12c913913fc87341 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 5 Jun 2018 00:29:10 +1000 Subject: [PATCH 0479/1752] (cisco:sx) add video_output_mode? and response parsing --- modules/cisco/tele_presence/sx_series_common.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 5817954a..68222089 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -199,6 +199,11 @@ def video_output_mode(video_mode) :Monitors => video_mode }), name: :video_output_mode) end + + def video_output_mode? + status 'Video Monitors' + end + # ==================== # END Common functions # ==================== @@ -386,8 +391,13 @@ def process_status(result) end end when :video - if result[2] == 'Selfview' && result[3] == 'Mode:' - self[:camera_pip] = result[4] == 'On' + case result[2] + when 'Monitors:' + self[:video_output_mode] = result[3] + when 'Selfview' + if result[3] == 'Mode:' + self[:camera_pip] = (result[4] == 'On') + end end when :audio if result[2] == 'Microphones' && result[3] == 'Mute:' From f8f80e8a00c246226ab4100da4a646f984d81195 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 5 Jun 2018 00:36:18 +1000 Subject: [PATCH 0480/1752] (cisco:sx) track video_output_mode --- modules/cisco/tele_presence/sx_series_common.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 68222089..3c8290da 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -194,10 +194,11 @@ def select_presentation(index) end def video_output_mode(video_mode) - # NOTE: video_mode should be Single or Dual or Auto + # NOTE: video_mode should be "Single" or "Dual" or "Auto" configuration('Video', params({ :Monitors => video_mode }), name: :video_output_mode) + self[:video_output_mode] = video_mode end def video_output_mode? From bb42d6b06fc52e388e75b70a390cb96f81dca38f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 5 Jun 2018 16:24:37 +1000 Subject: [PATCH 0481/1752] Add logic for completely empty title --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index b29fd3f7..5c17c201 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -768,7 +768,7 @@ def todays_bookings # Set subject to private if sensitive - if ['private', 'confidential'].include?(meeting.sensitivity.downcase) + if ['private', 'confidential'].include?(meeting.sensitivity.downcase) || item[:subject].empty? subject = "Private" else subject = item[:subject][:text] From d4faa8aa0fa64a796e44473fa6027ab62f6d0195 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Jun 2018 14:48:49 +1000 Subject: [PATCH 0482/1752] (tvone:coriomaster) rewrite module to provide stricter response parsing --- modules/tv_one/corio_master.rb | 199 +++++++++++++++------------------ 1 file changed, 88 insertions(+), 111 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 22f0448f..88caf3e0 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -3,30 +3,30 @@ module TvOne; end # Documentation: https://aca.im/driver_docs/TV+One/CORIOmaster-Commands-v1.7.0.pdf class TvOne::CorioMaster - include ::Orchestrator::Constants # these provide optional helper methods - include ::Orchestrator::Transcoder # (not used in this module) + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder - # Discovery Information tcp_port 10001 - descriptive_name 'TV One CORIOmaster video wall' + descriptive_name 'tvOne CORIOmaster image processor' generic_name :VideoWall - # Communication settings - tokenize delimiter: "\r\n", wait_ready: "Interface Ready" - delay between_sends: 150 + tokenize delimiter: /(?<=^!(Info)|(Done)|(Error)|(Event)).*\r\n/, + wait_ready: 'Interface Ready' + default_settings username: 'admin', password: 'adminpw' + + + # ------------------------------ + # Calllbacks def on_load on_update end - def on_unload - end - def on_update - @username = setting(:username) || 'admin' - @password = setting(:password) || 'adminpw' + @username = setting :username + @password = setting :password end def connected @@ -34,133 +34,110 @@ def connected do_poll end - login + exec('login', @username, @password, priority: 99).then do + query 'CORIOmax.Serial_Number', expose_as: :serial_number + query 'CORIOmax.Software_Version', expose_as: :version + end end def disconnected - # Disconnected will be called before connect if initial connect fails schedule.clear end - def login - send "login(#{@username},#{@password})\r\n", priotity: 99 - end - def reboot - send "System.Reset()\r\n" - end + # ------------------------------ + # Main API - def preset(number = nil) - if number - send "Preset.Take = #{number}\r\n", name: :preset - else - send "Preset.Take\r\n" - end + def preset(id) + set('Preset.Take', id).then { self[:preset] = id } end - alias_method :switch_to, :preset + alias switch_to preset - # For switcher like compatibility - def switch(map) - map.each do |key, value| - preset key - end + def window(id, property, value) + set "Window#{id}.#{property}", value end - # Set or query window properties - def window(id, property, value = nil) - command = "Window#{id}.#{property}" - if value - send "#{command} = #{value}\r\n", name: :"#{command}" - else - send "#{command}\r\n" - end - end - - # Provide some meta programming to enable the driver format to match the - # device capabilities. - # - # @see Proxy - def method_missing(*context) - sender = ->(command) { do_send command } - Proxy.new(sender).__send__(*context) - end + protected - # Build an execution context for deeply nested device state / behaviour. - # - # This will continue to return itself, building up a path, until called - # with a method ending in one of the following execution flags: - # '=' assign a value to a device property - # '?' query the current value of a property - # '!' execute an on-device action - class Proxy - def initialize(sender, path = []) - @sender = sender - @path = path - end - def method_missing(name, *args) - segment, action = name.to_s.match(/^(\w+)(\=|\?|\!)?$/).captures - @path << segment - - case action - when '=' - @sender.call "#{@path.join '.'} = #{args.first}" - when '?' - @sender.call "#{@path.join '.'}" - when '!' - @sender.call "#{@path.join '.'}()" - else - self - end - end - end - end + def do_poll + logger.debug 'polling device state' - # Runs any command provided - def send_command(cmd) - send "#{cmd}\r\n", wait: false + query 'Preset.Take', expose_as: :preset end + # ------------------------------ + # Base device comms - protected + def exec(command, *params, **opts) + logger.debug { "executing #{command}" } + send "#{command}(#{params.join ','})\r\n", opts + end + def set(path, val, **opts) + logger.debug { "setting #{path} to #{val}" } + opts[:name] ||= path.to_sym + send "#{path} = #{val}\r\n", opts + end - def do_poll - logger.debug "-- Polling CORIOmaster" - preset + def query(path, expose_as: nil, **opts, &blk) + logger.debug { "querying #{path}" } + blk = ->(val) { self[expose_as] = val } unless expose_as.nil? + opts[:emit] ||= ->(d, r, c) { received d, r, c, &blk } unless blk.nil? + send "#{path}\r\n", opts + end + def parse_response(lines) + updates = lines.map { |line| line.split ' = ' } + .to_h + .transform_values! do |val| + case val + when /^\d+$/ then Integer val + when 'NULL' then nil + when 'Off' then false + when 'On' then true + else val + end + end + + if updates.size == 1 && updates.include?(message) + # Single property query + updates.first.value + elsif updates.values.all?(&:nil?) + # Property list + updates.keys + else + # Property set + updates.reject { |k, _| k.end_with '()' } + .transform_keys! do |x| + x.sub(/^#{message}\.?/, '').downcase!.to_sym + end + end end def received(data, resolve, command) - if data[1..5] == 'Error' - logger.warn "CORIO error: #{data}" - - # Attempt to login if we are not currently - if data =~ /Not Logged In/i - login - end + logger.debug { "received: #{data}" } - return :abort if command - else - logger.debug { "CORIO sent: #{data}" } - end + *body, result = data.lines + type, message = /^!(\w+)\W*(.*)$/.match(result).captures - if command - if data[0] == '!' - result = data.split(' ') - case result[1].to_sym - when :"Preset.Take" - self[:preset] = result[-1].to_i - end - - :success - else - :ignore - end - else + case type + when 'Done' + yield parse_response body if block_given? + :success + when 'Info' + logger.info message :success + when 'Error' + logger.error message + :fail + when 'Event' + logger.warn { "unhandled event: #{message}" } + :ignore + else + logger.error { "unhandled response: #{data}" } + :abort end end end - From 3f3f4eaa14319e3b5e9ffdd458fe5078921619dd Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Jun 2018 18:35:21 +1000 Subject: [PATCH 0483/1752] (tvone:coriomaster) add tests --- modules/tv_one/corio_master_spec.rb | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 modules/tv_one/corio_master_spec.rb diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb new file mode 100644 index 00000000..fa7ec24e --- /dev/null +++ b/modules/tv_one/corio_master_spec.rb @@ -0,0 +1,39 @@ +Orchestrator::Testing.mock_device 'TvOne::CorioMaster', + settings: { + username: 'admin', + password: 'adminpw' + } do + transmit <<~INIT + // ===================\r + // CORIOmaster - CORIOmax\r + // ===================\r + // Command Interface Ready\r + Please login. Use 'login(username,password)'\r + INIT + + should_send "login(admin,adminpw)\r\n" + responds "!Info : User admin Logged In\r\n" + expect(status[:connected]).to be(true) + + should_send "CORIOmax.Serial_Number\r\n" + responds <<~RX + CORIOmax.Serial_Number = 2218031005149\r + !Done CORIOmax.Serial_Number\r + RX + expect(status[:serial_number]).to be(2218031005149) + + should_send "CORIOmax.Software_Version\r\n" + responds <<~RX + CORIOmax.Software_Version = V1.30701.P4 Master\r + !Done CORIOmax.Software_Version\r + RX + expect(status[:firmware]).to be('V1.30701.P4 Master') + + exec(:preset, 1) + .should_send("Preset.Take = 1\r\n") + .responds <<~RX + Preset.Take = 1\r + !Done Preset.Take\r + RX + expect(status[:preset]).to be(1) +end From 18c6cec195d49171b7202e0249a31784344c8e7d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Jun 2018 18:36:02 +1000 Subject: [PATCH 0484/1752] (tvone:coriomaster) rename firmware status var --- modules/tv_one/corio_master.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 88caf3e0..b8cf30b4 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -36,7 +36,7 @@ def connected exec('login', @username, @password, priority: 99).then do query 'CORIOmax.Serial_Number', expose_as: :serial_number - query 'CORIOmax.Software_Version', expose_as: :version + query 'CORIOmax.Software_Version', expose_as: :firmware end end From f589633a804f09edbf4acf7d203c658970a27ef1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Jun 2018 18:47:58 +1000 Subject: [PATCH 0485/1752] (tvone:coriomaster) fix response tokenization --- modules/tv_one/corio_master.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index b8cf30b4..2fb215d6 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -11,8 +11,7 @@ class TvOne::CorioMaster descriptive_name 'tvOne CORIOmaster image processor' generic_name :VideoWall - tokenize delimiter: /(?<=^!(Info)|(Done)|(Error)|(Event)).*\r\n/, - wait_ready: 'Interface Ready' + tokenize wait_ready: 'Interface Ready', callback: :tokenize default_settings username: 'admin', password: 'adminpw' @@ -116,6 +115,16 @@ def parse_response(lines) end end + def tokenize(buffer) + result_line_start = buffer.index(/^!/) + + return false unless result_line_start + + result_line_end = buffer.index("\r\n", result_line_start) + + result_line_end || false + end + def received(data, resolve, command) logger.debug { "received: #{data}" } From e0bcc6ebe6673f2bc9ebcbdc7b91f4cc22a14fd8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Jun 2018 21:06:34 +1000 Subject: [PATCH 0486/1752] (tvone:coriomaster) fix reponse parsing --- modules/tv_one/corio_master.rb | 42 +++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 2fb215d6..1936eb07 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -80,15 +80,23 @@ def set(path, val, **opts) send "#{path} = #{val}\r\n", opts end - def query(path, expose_as: nil, **opts, &blk) + def query(path, expose_as: nil, **opts, &callback) logger.debug { "querying #{path}" } - blk = ->(val) { self[expose_as] = val } unless expose_as.nil? - opts[:emit] ||= ->(d, r, c) { received d, r, c, &blk } unless blk.nil? + + if expose_as || callback + opts[:on_receive] = lambda do |*args| + received(*args) do |val| + self[expose_as] = val unless expose_as.nil? + callback&.call val + end + end + end + send "#{path}\r\n", opts end - def parse_response(lines) - updates = lines.map { |line| line.split ' = ' } + def parse_response(lines, command) + updates = lines.map { |line| line.chop.split ' = ' } .to_h .transform_values! do |val| case val @@ -100,17 +108,17 @@ def parse_response(lines) end end - if updates.size == 1 && updates.include?(message) + if updates.size == 1 && updates.include?(command) # Single property query - updates.first.value + updates.values.first elsif updates.values.all?(&:nil?) # Property list updates.keys else # Property set - updates.reject { |k, _| k.end_with '()' } + updates.reject { |k, _| k.end_with? '()' } .transform_keys! do |x| - x.sub(/^#{message}\.?/, '').downcase!.to_sym + x.sub(/^#{command}\.?/, '').downcase!.to_sym end end end @@ -122,19 +130,27 @@ def tokenize(buffer) result_line_end = buffer.index("\r\n", result_line_start) - result_line_end || false + if result_line_end + result_line_end + 2 + else + false + end end def received(data, resolve, command) logger.debug { "received: #{data}" } *body, result = data.lines - type, message = /^!(\w+)\W*(.*)$/.match(result).captures + type, message = /^!(\w+)\W*(.*)\r\n$/.match(result).captures case type when 'Done' - yield parse_response body if block_given? - :success + if command[:data].start_with? message + yield parse_response body, message if block_given? + :success + else + :ignore + end when 'Info' logger.info message :success From 1d06825524a8f428eba25402ac37a828ec2d1546 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Jun 2018 21:07:15 +1000 Subject: [PATCH 0487/1752] (tvone:coriomaster) fix tests --- modules/tv_one/corio_master_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index fa7ec24e..984ddf08 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -27,7 +27,7 @@ CORIOmax.Software_Version = V1.30701.P4 Master\r !Done CORIOmax.Software_Version\r RX - expect(status[:firmware]).to be('V1.30701.P4 Master') + expect(status[:firmware]).to eq('V1.30701.P4 Master') exec(:preset, 1) .should_send("Preset.Take = 1\r\n") @@ -35,5 +35,6 @@ Preset.Take = 1\r !Done Preset.Take\r RX + wait_tick expect(status[:preset]).to be(1) end From 7ea4b1ed5ba7b98178e54c0a125c0c2ec62b5805 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Jun 2018 23:12:41 +1000 Subject: [PATCH 0488/1752] (tvone:coriomaster) read auth directly in connected event --- modules/tv_one/corio_master.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 1936eb07..0b82567a 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -19,21 +19,14 @@ class TvOne::CorioMaster # ------------------------------ # Calllbacks - def on_load - on_update - end - - def on_update - @username = setting :username - @password = setting :password - end - def connected schedule.every('60s') do do_poll end - exec('login', @username, @password, priority: 99).then do + username = setting :username + password = setting :password + exec('login', username, password, priority: 99).then do query 'CORIOmax.Serial_Number', expose_as: :serial_number query 'CORIOmax.Software_Version', expose_as: :firmware end From dd5145e2c49e58d2eaeb04aac7432a08c5ffff47 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 6 Jun 2018 23:55:06 +1000 Subject: [PATCH 0489/1752] (tvone:coriomaster) sync device state --- modules/tv_one/corio_master.rb | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 0b82567a..de8f44fa 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -29,6 +29,7 @@ def connected exec('login', username, password, priority: 99).then do query 'CORIOmax.Serial_Number', expose_as: :serial_number query 'CORIOmax.Software_Version', expose_as: :firmware + sync_state end end @@ -41,7 +42,7 @@ def disconnected # Main API def preset(id) - set('Preset.Take', id).then { self[:preset] = id } + set('Preset.Take', id).then { sync_state } end alias switch_to preset @@ -54,11 +55,27 @@ def window(id, property, value) def do_poll - logger.debug 'polling device state' + logger.debug 'polling device' query 'Preset.Take', expose_as: :preset end + def sync_state + deep_query = lambda do |key| + query key do |children| + status_var = key.downcase.to_sym + self[status_var] ||= {} + children.each do |child| + query(child) { |status| self[status_var][child] = status } + end + end + end + + deep_query['Windows'] + deep_query['Canvases'] + deep_query['Layouts'] + end + # ------------------------------ # Base device comms @@ -79,7 +96,7 @@ def query(path, expose_as: nil, **opts, &callback) if expose_as || callback opts[:on_receive] = lambda do |*args| received(*args) do |val| - self[expose_as] = val unless expose_as.nil? + self[expose_as.to_sym] = val unless expose_as.nil? callback&.call val end end From 31363fd5e4d5d596257f6d02b5042a276d5d00b8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 01:38:14 +1000 Subject: [PATCH 0490/1752] (tvone:coriomaster) add swtich method for mapping input to windows --- modules/tv_one/corio_master.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index de8f44fa..a18634b6 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -46,6 +46,13 @@ def preset(id) end alias switch_to preset + def switch(signal_map) + interactions = signal_map.flat_map do |slot, windows| + Array(windows).map { |window| set "#{window}.Input", slot } + end + thread.finally(*interactions).then { sync_state } + end + def window(id, property, value) set "Window#{id}.#{property}", value end From 74883ca265b7e30bfcac7de6c1b3c473cb5608f9 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 01:51:00 +1000 Subject: [PATCH 0491/1752] (tvone:coriomaster) neaten up deep_query --- modules/tv_one/corio_master.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index a18634b6..41348f1b 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -69,12 +69,13 @@ def do_poll def sync_state deep_query = lambda do |key| - query key do |children| + query key do |properties| status_var = key.downcase.to_sym - self[status_var] ||= {} - children.each do |child| - query(child) { |status| self[status_var][child] = status } - end + self[status_var] = properties + properties.select { |_, v| v == '<...>' } + .each do |k, _| + query(k) { |subtree| properties[k] = subtree } + end end end From a97653d45883c09fa3153df538c665250a220d4b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 02:06:51 +1000 Subject: [PATCH 0492/1752] (tvone:coriomaster) convert negative ints and boolean vals --- modules/tv_one/corio_master.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 41348f1b..b77a752c 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -118,11 +118,11 @@ def parse_response(lines, command) .to_h .transform_values! do |val| case val - when /^\d+$/ then Integer val - when 'NULL' then nil - when 'Off' then false - when 'On' then true - else val + when /^-?\d+$/ then Integer val + when 'NULL' then nil + when /(Off)|(No)/ then false + when /(On)|(Yes)/ then true + else val end end From b4f0df29fdc38a34edb2d4855a62c7dd1b0694e8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 02:16:17 +1000 Subject: [PATCH 0493/1752] (tvone:coriomaster) ensure status var only updates with final deep query --- modules/tv_one/corio_master.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index b77a752c..fb8cdc0c 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -70,12 +70,15 @@ def do_poll def sync_state deep_query = lambda do |key| query key do |properties| - status_var = key.downcase.to_sym - self[status_var] = properties - properties.select { |_, v| v == '<...>' } - .each do |k, _| - query(k) { |subtree| properties[k] = subtree } - end + subqueries = \ + properties.select { |_, v| v == '<...>' } + .map do |k, _| + query(k) { |subtree| properties[k] = subtree } + end + thread.finally(*subqueries).then do + status_var = key.downcase.to_sym + self[status_var] = properties + end end end From d3a5ce2bb0d00715f113c68bdde13aca15003f5f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 11:36:23 +1000 Subject: [PATCH 0494/1752] Push new meeting start times when start button pushed --- modules/aca/exchange_booking.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 5c17c201..a8f1cacc 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -367,11 +367,24 @@ def fetch_bookings(*args) # ====================================== def start_meeting(meeting_ref) - self[:last_meeting_started] = meeting_ref - self[:meeting_pending] = meeting_ref + self[:last_meeting_started] = Time.now.to_i * 1000 + self[:meeting_pending] = Time.now.to_i * 1000 self[:meeting_ending] = false self[:meeting_pending_notice] = false - define_setting(:last_meeting_started, meeting_ref) + define_setting(:last_meeting_started, Time.now.to_i * 1000) + + # Actually edit the booking to have the new start time + items = get_todays_bookings + + items.each do |meeting| + meeting_start = Time.parse(meeting.ews_item[:start][:text]).to_i * 1000 + # meeting_end = Time.parse(meeting.ews_item[:end][:text]).to_i + # Remove any meetings that match the start time provided + if start_time == meeting_red + meeting.ews_item.update_item!(start: Time.now.iso8601) + end + end + end def cancel_meeting(start_time) @@ -541,6 +554,7 @@ def extend_meeting end + protected From 776d8965779b35fcfa6e47383c5aeac75c72e74b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 11:40:30 +1000 Subject: [PATCH 0495/1752] Add five min threshold to meeting edit --- modules/aca/exchange_booking.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index a8f1cacc..feec6d02 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -373,15 +373,17 @@ def start_meeting(meeting_ref) self[:meeting_pending_notice] = false define_setting(:last_meeting_started, Time.now.to_i * 1000) - # Actually edit the booking to have the new start time - items = get_todays_bookings - - items.each do |meeting| - meeting_start = Time.parse(meeting.ews_item[:start][:text]).to_i * 1000 - # meeting_end = Time.parse(meeting.ews_item[:end][:text]).to_i - # Remove any meetings that match the start time provided - if start_time == meeting_red - meeting.ews_item.update_item!(start: Time.now.iso8601) + if meeting_ref - (Time.now.to_i * 1000) > 300000 + # Actually edit the booking to have the new start time + items = get_todays_bookings + + items.each do |meeting| + meeting_start = Time.parse(meeting.ews_item[:start][:text]).to_i * 1000 + # meeting_end = Time.parse(meeting.ews_item[:end][:text]).to_i + # Remove any meetings that match the start time provided + if start_time == meeting_red + meeting.ews_item.update_item!(start: Time.now.iso8601) + end end end From b0ec1c21d92953183266e1183e87f0ba027db984 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 12:06:00 +1000 Subject: [PATCH 0496/1752] (tvone:coriomaster) add tests for base comms methods --- modules/tv_one/corio_master_spec.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index 984ddf08..99b7074c 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -29,6 +29,30 @@ RX expect(status[:firmware]).to eq('V1.30701.P4 Master') + exec(:exec, 'System.Reset') + .should_send("System.Reset()\r\n") + .responds <<~RX + !Info: Rebooting...\r + RX + expect(result).to be(:success) + + exec(:set, 'Window1.Input', 'Slot3.In1') + .should_send("Window1.Input = Slot3.In1\r\n") + .responds <<~RX + Window1.Input = Slot3.In1\r + !Done Window1.Input\r + RX + expect(result).to be(:success) + + exec(:query, 'Window1.Input', expose_as: :status_var_test) + .should_send("Window1.Input\r\n") + .responds <<~RX + Window1.Input = Slot3.In1\r + !Done Window1.Input\r + RX + expect(result).to be(:success) + expect(status[:status_var_test]).to eq('Slot3.In1') + exec(:preset, 1) .should_send("Preset.Take = 1\r\n") .responds <<~RX @@ -36,5 +60,6 @@ !Done Preset.Take\r RX wait_tick + expect(result).to be(:success) expect(status[:preset]).to be(1) end From b9e44fcd9f25ce72aa1da0ebf46d3b3ea5dabf15 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 12:15:23 +1000 Subject: [PATCH 0497/1752] Fix syntax issues --- modules/aca/exchange_booking.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index feec6d02..c4695b2a 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -367,13 +367,14 @@ def fetch_bookings(*args) # ====================================== def start_meeting(meeting_ref) - self[:last_meeting_started] = Time.now.to_i * 1000 - self[:meeting_pending] = Time.now.to_i * 1000 + now = Time.now + self[:last_meeting_started] = now.to_i * 1000 + self[:meeting_pending] = now.to_i * 1000 self[:meeting_ending] = false self[:meeting_pending_notice] = false - define_setting(:last_meeting_started, Time.now.to_i * 1000) + define_setting(:last_meeting_started, now.to_i * 1000) - if meeting_ref - (Time.now.to_i * 1000) > 300000 + if meeting_ref - (now.to_i * 1000) > 300000 # Actually edit the booking to have the new start time items = get_todays_bookings @@ -381,8 +382,8 @@ def start_meeting(meeting_ref) meeting_start = Time.parse(meeting.ews_item[:start][:text]).to_i * 1000 # meeting_end = Time.parse(meeting.ews_item[:end][:text]).to_i # Remove any meetings that match the start time provided - if start_time == meeting_red - meeting.ews_item.update_item!(start: Time.now.iso8601) + if start_time == meeting_ref + meeting.ews_item.update_item!(start: now.iso8601) end end end From 8055611e32d9f66e0d8218fe55e66f48507d1998 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 12:19:02 +1000 Subject: [PATCH 0498/1752] Fix logic in meeting update --- modules/aca/exchange_booking.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index c4695b2a..4a967602 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -367,14 +367,15 @@ def fetch_bookings(*args) # ====================================== def start_meeting(meeting_ref) - now = Time.now - self[:last_meeting_started] = now.to_i * 1000 - self[:meeting_pending] = now.to_i * 1000 - self[:meeting_ending] = false - self[:meeting_pending_notice] = false - define_setting(:last_meeting_started, now.to_i * 1000) if meeting_ref - (now.to_i * 1000) > 300000 + now = Time.now + self[:last_meeting_started] = now.to_i * 1000 + self[:meeting_pending] = now.to_i * 1000 + self[:meeting_ending] = false + self[:meeting_pending_notice] = false + define_setting(:last_meeting_started, now.to_i * 1000) + # Actually edit the booking to have the new start time items = get_todays_bookings @@ -386,6 +387,12 @@ def start_meeting(meeting_ref) meeting.ews_item.update_item!(start: now.iso8601) end end + else + self[:last_meeting_started] = meeting_ref + self[:meeting_pending] = meeting_ref + self[:meeting_ending] = false + self[:meeting_pending_notice] = false + define_setting(:last_meeting_started, meeting_ref) end end From c5ffd95206b841acf673b2229c125d4661334a7a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 12:58:26 +1000 Subject: [PATCH 0499/1752] Revart start meeting edit --- modules/aca/exchange_booking.rb | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 4a967602..2a519a0b 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -367,34 +367,11 @@ def fetch_bookings(*args) # ====================================== def start_meeting(meeting_ref) - - if meeting_ref - (now.to_i * 1000) > 300000 - now = Time.now - self[:last_meeting_started] = now.to_i * 1000 - self[:meeting_pending] = now.to_i * 1000 - self[:meeting_ending] = false - self[:meeting_pending_notice] = false - define_setting(:last_meeting_started, now.to_i * 1000) - - # Actually edit the booking to have the new start time - items = get_todays_bookings - - items.each do |meeting| - meeting_start = Time.parse(meeting.ews_item[:start][:text]).to_i * 1000 - # meeting_end = Time.parse(meeting.ews_item[:end][:text]).to_i - # Remove any meetings that match the start time provided - if start_time == meeting_ref - meeting.ews_item.update_item!(start: now.iso8601) - end - end - else self[:last_meeting_started] = meeting_ref self[:meeting_pending] = meeting_ref self[:meeting_ending] = false self[:meeting_pending_notice] = false define_setting(:last_meeting_started, meeting_ref) - end - end def cancel_meeting(start_time) From 0ccc6c68c63392d9781c515f81551d9e8417cf35 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 13:14:14 +1000 Subject: [PATCH 0500/1752] (tvone:coriomaster) fix case sensitivity issue in response parsing --- modules/tv_one/corio_master.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index fb8cdc0c..b5608ecc 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -166,7 +166,7 @@ def received(data, resolve, command) case type when 'Done' - if command[:data].start_with? message + if command[:data] =~ /^#{message}/i yield parse_response body, message if block_given? :success else From 53a8b40bd73c5e6c209348431ad0aa07be1073f5 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 13:26:23 +1000 Subject: [PATCH 0501/1752] (tvone:coriomaster) implement deep_query --- modules/tv_one/corio_master.rb | 43 +++++----- modules/tv_one/corio_master_spec.rb | 119 ++++++++++++++++++++++++++-- 2 files changed, 138 insertions(+), 24 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index b5608ecc..8ccc3510 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -27,7 +27,7 @@ def connected username = setting :username password = setting :password exec('login', username, password, priority: 99).then do - query 'CORIOmax.Serial_Number', expose_as: :serial_number + query 'CORIOmax.Serial_Number', expose_as: :serial_number query 'CORIOmax.Software_Version', expose_as: :firmware sync_state end @@ -63,28 +63,14 @@ def window(id, property, value) def do_poll logger.debug 'polling device' - query 'Preset.Take', expose_as: :preset end def sync_state - deep_query = lambda do |key| - query key do |properties| - subqueries = \ - properties.select { |_, v| v == '<...>' } - .map do |k, _| - query(k) { |subtree| properties[k] = subtree } - end - thread.finally(*subqueries).then do - status_var = key.downcase.to_sym - self[status_var] = properties - end - end - end - - deep_query['Windows'] - deep_query['Canvases'] - deep_query['Layouts'] + query 'Preset.Take', expose_as: :preset + deep_query 'Windows', expose_as: :windows + deep_query 'Canvases', expose_as: :canvases + deep_query 'Layouts', expose_as: :layouts end # ------------------------------ @@ -116,6 +102,25 @@ def query(path, expose_as: nil, **opts, &callback) send "#{path}\r\n", opts end + def deep_query(path, expose_as: nil, **opts, &callback) + defer = thread.defer + query path, opts do |props| + subqueries = [] + if props.is_a? Hash + subqueries = props.select { |_, v| v == '<...>' } + .map do |k, _| + deep_query(k) { |v| props[k] = v } + end + end + thread.finally(*subqueries).then do + self[expose_as] = props unless expose_as.nil? + callback&.call props + defer.resolve + end + end + defer.promise + end + def parse_response(lines, command) updates = lines.map { |line| line.chop.split ' = ' } .to_h diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index 99b7074c..e38d78e1 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -29,6 +29,25 @@ RX expect(status[:firmware]).to eq('V1.30701.P4 Master') + # Fudge the initial status query - check this latest in the tests + should_send "Preset.Take\r\n" + responds <<~RX + Preset.Take = 1\r + !Done Preset.Take\r + RX + should_send "Windows\r\n" + responds <<~RX + !Done Windows\r + RX + should_send "Canvases\r\n" + responds <<~RX + !Done Canvases\r + RX + should_send "Layouts\r\n" + responds <<~RX + !Done Layouts\r + RX + exec(:exec, 'System.Reset') .should_send("System.Reset()\r\n") .responds <<~RX @@ -53,13 +72,103 @@ expect(result).to be(:success) expect(status[:status_var_test]).to eq('Slot3.In1') + exec(:deep_query, 'Windows') + .should_send("Windows\r\n") + .responds( + <<~RX + Windows.Window1 = <...>\r + Windows.Window2 = <...>\r + !Done Windows\r + RX + ) + .should_send("window1\r\n") + .responds( + <<~RX + Window1.FullName = Window1\r + Window1.Status = FREE\r + Window1.Alias = NULL\r + Window1.Input = Slot3.In1\r + Window1.Canvas = Canvas1\r + Window1.CanWidth = 1280\r + Window1.CanHeight = 720\r + Window1.CanXCentre = 689\r + Window1.CanYCentre = 0\r + Window1.Zorder = 1\r + Window1.RotateDeg = 0\r + Window1.WDP = 0\r + Window1.WDPQ = 2048\r + Window1.BdrPixWidth = 1\r + Window1.BdrRGB = 0\r + Window1.HFlip = Off\r + Window1.VFlip = Off\r + Window1.FTB = 0\r + Window1.SCFTB = Off\r + Window1.SCHShrink = Off\r + Window1.SCVShrink = Off\r + Window1.SCSpin = 0\r + Window1.AccountForBezel = No\r + Window1.PhysicalCenterX = 547800\r + Window1.PhysicalCenterY = 0\r + Window1.PhysicalWidth = 1018300\r + Window1.PhysicalHeight = 572800\r + !Done Window1\r + RX + ) + .should_send("window2\r\n") + .responds( + <<~RX + Window2.FullName = Window2\r + Window2.Status = FREE\r + Window2.Alias = NULL\r + Window2.Input = Slot3.In2\r + Window2.Canvas = Canvas1\r + Window2.CanWidth = 1280\r + Window2.CanHeight = 720\r + Window2.CanXCentre = 689\r + Window2.CanYCentre = 0\r + Window2.Zorder = 1\r + Window2.RotateDeg = 0\r + Window2.WDP = 0\r + Window2.WDPQ = 2048\r + Window2.BdrPixWidth = 1\r + Window2.BdrRGB = 0\r + Window2.HFlip = Off\r + Window2.VFlip = Off\r + Window2.FTB = 0\r + Window2.SCFTB = Off\r + Window2.SCHShrink = Off\r + Window2.SCVShrink = Off\r + Window2.SCSpin = 0\r + Window2.AccountForBezel = No\r + Window2.PhysicalCenterX = 547800\r + Window2.PhysicalCenterY = 0\r + Window2.PhysicalWidth = 1018300\r + Window2.PhysicalHeight = 572800\r + !Done Window2\r + RX + ) + exec(:preset, 1) .should_send("Preset.Take = 1\r\n") - .responds <<~RX - Preset.Take = 1\r - !Done Preset.Take\r - RX + .responds( + <<~RX + Preset.Take = 1\r + !Done Preset.Take\r + RX + ) + .should_send("Preset.Take\r\n") # Mock the status query + .responds( + <<~RX + Preset.Take = 1\r + !Done Preset.Take\r + RX + ) + .should_send("Windows\r\n") + .responds("!Done Windows\r\n") + .should_send("Canvases\r\n") + .responds("!Done Canvases\r\n") + .should_send("Layouts\r\n") + .responds("!Done Layouts\r\n") wait_tick - expect(result).to be(:success) expect(status[:preset]).to be(1) end From 21638fc394eac8d0967311f626c781ad4dcd1d42 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 15:55:45 +1000 Subject: [PATCH 0502/1752] Add flag to check if we hide all day bookings --- lib/microsoft/exchange.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 54ddda42..40a59585 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -12,6 +12,7 @@ def initialize( service_account_email:, service_account_password:, internet_proxy:nil, + all_day_bookings:true, logger: Rails.logger ) begin @@ -225,6 +226,10 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. email: attendee.email } } if event.required_attendees + + if !all_day_bookings + next if event.all_day? + end bookings.push(booking) } bookings From c18a9b6474b2660e466534a91f5904bf290cd809 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 16:00:20 +1000 Subject: [PATCH 0503/1752] Add support for hiding all day bookings to exchange panel driver --- modules/aca/exchange_booking.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 2a519a0b..dba7fd6c 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -717,6 +717,10 @@ def todays_bookings items.select! { |booking| !booking.cancelled? } results = items.collect do |meeting| + all_day_bookings = ENV['ALL_DAY_BOOKINGS'] || true + if !all_day_bookings + next if meeting.ews_item.all_day? + end item = meeting.ews_item start = item[:start][:text] ending = item[:end][:text] From b405c130e5f05bf9cface854b237fb1a4c7228c6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 16:10:41 +1000 Subject: [PATCH 0504/1752] Fix syntax for all day check --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index dba7fd6c..dc86a360 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -719,7 +719,7 @@ def todays_bookings results = items.collect do |meeting| all_day_bookings = ENV['ALL_DAY_BOOKINGS'] || true if !all_day_bookings - next if meeting.ews_item.all_day? + next if meeting.all_day? end item = meeting.ews_item start = item[:start][:text] From 040ba8d2d1fea29ea0b858e88b2fa3b9cebd4680 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 16:27:16 +1000 Subject: [PATCH 0505/1752] Change all day check to check actual duration --- lib/microsoft/exchange.rb | 2 +- modules/aca/exchange_booking.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 40a59585..a537cb5b 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -228,7 +228,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. } if event.required_attendees if !all_day_bookings - next if event.all_day? + next if (event.end - event.start) > 86399 end bookings.push(booking) } diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index dc86a360..37dd64de 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -717,13 +717,13 @@ def todays_bookings items.select! { |booking| !booking.cancelled? } results = items.collect do |meeting| - all_day_bookings = ENV['ALL_DAY_BOOKINGS'] || true - if !all_day_bookings - next if meeting.all_day? - end item = meeting.ews_item start = item[:start][:text] ending = item[:end][:text] + all_day_bookings = ENV['ALL_DAY_BOOKINGS'] || true + if !all_day_bookings + next if (Time.parse(start) - Time.parse(ending)).to_i > 86399 + end real_start = Time.parse(start) real_end = Time.parse(ending) From d3fe96a4feea074e6819d587b558a7b3ba310128 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 16:30:07 +1000 Subject: [PATCH 0506/1752] Mixed start and end up like the tard i be --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 37dd64de..cd05ccff 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -722,7 +722,7 @@ def todays_bookings ending = item[:end][:text] all_day_bookings = ENV['ALL_DAY_BOOKINGS'] || true if !all_day_bookings - next if (Time.parse(start) - Time.parse(ending)).to_i > 86399 + next if (Time.parse(ending) - Time.parse(start)).to_i > 86399 end real_start = Time.parse(start) From e69ae77e09eed1d32bb899bcee4c102e17c4cdcd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 17:08:35 +1000 Subject: [PATCH 0507/1752] Make the all day check a compliment --- lib/microsoft/exchange.rb | 4 ++-- modules/aca/exchange_booking.rb | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index a537cb5b..c8eb1fa3 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -12,7 +12,7 @@ def initialize( service_account_email:, service_account_password:, internet_proxy:nil, - all_day_bookings:true, + hide_all_day_bookings:false, logger: Rails.logger ) begin @@ -227,7 +227,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. } } if event.required_attendees - if !all_day_bookings + if hide_all_day_bookings next if (event.end - event.start) > 86399 end bookings.push(booking) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index cd05ccff..5b02ab4c 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -720,9 +720,13 @@ def todays_bookings item = meeting.ews_item start = item[:start][:text] ending = item[:end][:text] - all_day_bookings = ENV['ALL_DAY_BOOKINGS'] || true + all_day_bookings = Boolean(ENV['HIDE_ALL_DAY_BOOKINGS']) || false if !all_day_bookings - next if (Time.parse(ending) - Time.parse(start)).to_i > 86399 + if (Time.parse(ending) - Time.parse(start)).to_i > 86399 + STDERR.puts "SKIPPING #{item[:subject][:text]}" + STDERR.flush + next + end end real_start = Time.parse(start) From 776dd28c770657f9fa57a99a95a5331ddf13febc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Jun 2018 17:17:16 +1000 Subject: [PATCH 0508/1752] Change exchange module to use setting to hdie bookings --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 5b02ab4c..efebe2ea 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -720,7 +720,7 @@ def todays_bookings item = meeting.ews_item start = item[:start][:text] ending = item[:end][:text] - all_day_bookings = Boolean(ENV['HIDE_ALL_DAY_BOOKINGS']) || false + all_day_bookings = setting(:hide_all_day_bookings) || false if !all_day_bookings if (Time.parse(ending) - Time.parse(start)).to_i > 86399 STDERR.puts "SKIPPING #{item[:subject][:text]}" From 7a09e9c74af8b65fd5d36362eb0754e074af7cde Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 17:56:48 +1000 Subject: [PATCH 0509/1752] (tvone:coriomaster) replace callbacks with promises --- modules/tv_one/corio_master.rb | 50 ++++++++++++---------- modules/tv_one/corio_master_spec.rb | 64 +++++++++-------------------- 2 files changed, 49 insertions(+), 65 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 8ccc3510..373d3329 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -83,41 +83,49 @@ def exec(command, *params, **opts) def set(path, val, **opts) logger.debug { "setting #{path} to #{val}" } + + defer = thread.defer + opts[:name] ||= path.to_sym - send "#{path} = #{val}\r\n", opts + send("#{path} = #{val}\r\n", opts).then do + defer.resolve val + end + + defer.promise end - def query(path, expose_as: nil, **opts, &callback) + def query(path, expose_as: nil, **opts) logger.debug { "querying #{path}" } - if expose_as || callback - opts[:on_receive] = lambda do |*args| - received(*args) do |val| - self[expose_as.to_sym] = val unless expose_as.nil? - callback&.call val - end + defer = thread.defer + + opts[:on_receive] = lambda do |*args| + received(*args) do |val| + self[expose_as.to_sym] = val unless expose_as.nil? + defer.resolve val end end send "#{path}\r\n", opts + + defer.promise end - def deep_query(path, expose_as: nil, **opts, &callback) + def deep_query(path, expose_as: nil, **opts) + logger.debug { "deep querying #{path}" } + defer = thread.defer - query path, opts do |props| - subqueries = [] - if props.is_a? Hash - subqueries = props.select { |_, v| v == '<...>' } - .map do |k, _| - deep_query(k) { |v| props[k] = v } - end - end - thread.finally(*subqueries).then do - self[expose_as] = props unless expose_as.nil? - callback&.call props - defer.resolve + + query(path, opts).then do |val| + if val.is_a? Hash + val.each_pair do |k, v| + val[k] = deep_query(k).value if v == '<...>' + end end + self[expose_as] = val unless expose_as.nil? + defer.resolve val end + defer.promise end diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index e38d78e1..f206b879 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -61,7 +61,7 @@ Window1.Input = Slot3.In1\r !Done Window1.Input\r RX - expect(result).to be(:success) + expect(result).to eq('Slot3.In1') exec(:query, 'Window1.Input', expose_as: :status_var_test) .should_send("Window1.Input\r\n") @@ -69,7 +69,7 @@ Window1.Input = Slot3.In1\r !Done Window1.Input\r RX - expect(result).to be(:success) + expect(result).to eq('Slot3.In1') expect(status[:status_var_test]).to eq('Slot3.In1') exec(:deep_query, 'Windows') @@ -85,32 +85,11 @@ .responds( <<~RX Window1.FullName = Window1\r - Window1.Status = FREE\r Window1.Alias = NULL\r Window1.Input = Slot3.In1\r Window1.Canvas = Canvas1\r Window1.CanWidth = 1280\r Window1.CanHeight = 720\r - Window1.CanXCentre = 689\r - Window1.CanYCentre = 0\r - Window1.Zorder = 1\r - Window1.RotateDeg = 0\r - Window1.WDP = 0\r - Window1.WDPQ = 2048\r - Window1.BdrPixWidth = 1\r - Window1.BdrRGB = 0\r - Window1.HFlip = Off\r - Window1.VFlip = Off\r - Window1.FTB = 0\r - Window1.SCFTB = Off\r - Window1.SCHShrink = Off\r - Window1.SCVShrink = Off\r - Window1.SCSpin = 0\r - Window1.AccountForBezel = No\r - Window1.PhysicalCenterX = 547800\r - Window1.PhysicalCenterY = 0\r - Window1.PhysicalWidth = 1018300\r - Window1.PhysicalHeight = 572800\r !Done Window1\r RX ) @@ -118,35 +97,32 @@ .responds( <<~RX Window2.FullName = Window2\r - Window2.Status = FREE\r Window2.Alias = NULL\r Window2.Input = Slot3.In2\r Window2.Canvas = Canvas1\r Window2.CanWidth = 1280\r Window2.CanHeight = 720\r - Window2.CanXCentre = 689\r - Window2.CanYCentre = 0\r - Window2.Zorder = 1\r - Window2.RotateDeg = 0\r - Window2.WDP = 0\r - Window2.WDPQ = 2048\r - Window2.BdrPixWidth = 1\r - Window2.BdrRGB = 0\r - Window2.HFlip = Off\r - Window2.VFlip = Off\r - Window2.FTB = 0\r - Window2.SCFTB = Off\r - Window2.SCHShrink = Off\r - Window2.SCVShrink = Off\r - Window2.SCSpin = 0\r - Window2.AccountForBezel = No\r - Window2.PhysicalCenterX = 547800\r - Window2.PhysicalCenterY = 0\r - Window2.PhysicalWidth = 1018300\r - Window2.PhysicalHeight = 572800\r !Done Window2\r RX ) + expect(result).to eq( + window1: { + fullname: 'Window1', + alias: nil, + input: 'Slot3.In1', + canvas: 'Canvas1', + canwidth: 1280, + canheight: 720 + }, + window2: { + fullname: 'Window2', + alias: nil, + input: 'Slot3.In2', + canvas: 'Canvas1', + canwidth: 1280, + canheight: 720 + } + ) exec(:preset, 1) .should_send("Preset.Take = 1\r\n") From d5c4c3e21baeab8961b78038ee3492c6a63822bd Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 17:57:24 +1000 Subject: [PATCH 0510/1752] (tvone:coriomaster) allow switching based on window indexes --- modules/tv_one/corio_master.rb | 10 ++++- modules/tv_one/corio_master_spec.rb | 68 +++++++++++++++++------------ 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 373d3329..f46b8973 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -48,7 +48,15 @@ def preset(id) def switch(signal_map) interactions = signal_map.flat_map do |slot, windows| - Array(windows).map { |window| set "#{window}.Input", slot } + Array(windows).map do |window| + if window.is_a?(String) && /Window\d+/i =~ window + set "#{window}.Input", slot + elsif window.is_a? Integer + set "Window#{window}.Input", slot + else + thread.defer.reject "invalid window: #{window}" + end + end end thread.finally(*interactions).then { sync_state } end diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index f206b879..21636e19 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -3,6 +3,27 @@ username: 'admin', password: 'adminpw' } do + # Util to clear out any state_sync queries + def sync_state + should_send "Preset.Take\r\n" + responds <<~RX + Preset.Take = 1\r + !Done Preset.Take\r + RX + should_send "Windows\r\n" + responds <<~RX + !Done Windows\r + RX + should_send "Canvases\r\n" + responds <<~RX + !Done Canvases\r + RX + should_send "Layouts\r\n" + responds <<~RX + !Done Layouts\r + RX + end + transmit <<~INIT // ===================\r // CORIOmaster - CORIOmax\r @@ -29,24 +50,8 @@ RX expect(status[:firmware]).to eq('V1.30701.P4 Master') - # Fudge the initial status query - check this latest in the tests - should_send "Preset.Take\r\n" - responds <<~RX - Preset.Take = 1\r - !Done Preset.Take\r - RX - should_send "Windows\r\n" - responds <<~RX - !Done Windows\r - RX - should_send "Canvases\r\n" - responds <<~RX - !Done Canvases\r - RX - should_send "Layouts\r\n" - responds <<~RX - !Done Layouts\r - RX + sync_state + exec(:exec, 'System.Reset') .should_send("System.Reset()\r\n") @@ -124,6 +129,7 @@ } ) + exec(:preset, 1) .should_send("Preset.Take = 1\r\n") .responds( @@ -132,19 +138,25 @@ !Done Preset.Take\r RX ) - .should_send("Preset.Take\r\n") # Mock the status query + wait_tick + sync_state + expect(status[:preset]).to be(1) + + exec(:switch, 'Slot1.In1' => 1, 'Slot1.In2' => 2) + .should_send("Window1.Input = Slot1.In1\r\n") .responds( <<~RX - Preset.Take = 1\r - !Done Preset.Take\r + Window1.Input = Slot1.In1\r + !Done Window1.Input\r + RX + ) + .should_send("Window2.Input = Slot1.In2\r\n") + .responds( + <<~RX + Window2.Input = Slot1.In2\r + !Done Window2.Input\r RX ) - .should_send("Windows\r\n") - .responds("!Done Windows\r\n") - .should_send("Canvases\r\n") - .responds("!Done Canvases\r\n") - .should_send("Layouts\r\n") - .responds("!Done Layouts\r\n") wait_tick - expect(status[:preset]).to be(1) + sync_state end From 6ff5e52830a605c1034b6e31b1a7218d942c68dc Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 20:07:19 +1000 Subject: [PATCH 0511/1752] (tvone:coriomaster) await all queries before resolving sync_state --- modules/tv_one/corio_master.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index f46b8973..0b0896df 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -75,10 +75,12 @@ def do_poll end def sync_state - query 'Preset.Take', expose_as: :preset - deep_query 'Windows', expose_as: :windows - deep_query 'Canvases', expose_as: :canvases - deep_query 'Layouts', expose_as: :layouts + thread.finally( + query('Preset.Take', expose_as: :preset), + deep_query('Windows', expose_as: :windows), + deep_query('Canvases', expose_as: :canvases), + deep_query('Layouts', expose_as: :layouts) + ) end # ------------------------------ From b3eb502c8dc71afe8fdf1855945fc2d5f8e4e810 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 20:07:50 +1000 Subject: [PATCH 0512/1752] (tvone:coriomaster) move connnection init to it's own method --- modules/tv_one/corio_master.rb | 12 ++++++++---- modules/tv_one/corio_master_spec.rb | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 0b0896df..6706f48d 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -24,12 +24,9 @@ def connected do_poll end - username = setting :username - password = setting :password - exec('login', username, password, priority: 99).then do + init_connection.then do query 'CORIOmax.Serial_Number', expose_as: :serial_number query 'CORIOmax.Software_Version', expose_as: :firmware - sync_state end end @@ -69,6 +66,13 @@ def window(id, property, value) protected + def init_connection + username = setting :username + password = setting :password + + exec('login', username, password, priority: 99).then { sync_state } + end + def do_poll logger.debug 'polling device' query 'Preset.Take', expose_as: :preset diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index 21636e19..2a479e69 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -36,6 +36,8 @@ def sync_state responds "!Info : User admin Logged In\r\n" expect(status[:connected]).to be(true) + sync_state + should_send "CORIOmax.Serial_Number\r\n" responds <<~RX CORIOmax.Serial_Number = 2218031005149\r @@ -50,8 +52,6 @@ def sync_state RX expect(status[:firmware]).to eq('V1.30701.P4 Master') - sync_state - exec(:exec, 'System.Reset') .should_send("System.Reset()\r\n") From 2a9af7231cd4150e22c9a9c3058b000cb665d894 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 7 Jun 2018 21:31:31 +1000 Subject: [PATCH 0513/1752] (cisco:ce) add mappings for do not disturb control --- modules/cisco/collaboration_endpoint/sx80.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 06f5a345..f530ce91 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -107,6 +107,10 @@ def speaker_track(state = On) send_xconfiguration 'Cameras SpeakerTrack', :Mode, mode end + command 'Conference DoNotDisturb Activate' => :do_not_disturb_activate, + Timeout_: (1..1440) + command 'Conference DoNotDisturb Deactivate' => :do_not_disturb_deactivate + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby From 2e6a8a2318cf86a6440b74890774d88c029610e7 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 8 Jun 2018 19:13:21 +1000 Subject: [PATCH 0514/1752] (tvone:coriomaster) return result of main device interaction, not state sync --- modules/tv_one/corio_master.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 6706f48d..2465eb24 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -39,7 +39,7 @@ def disconnected # Main API def preset(id) - set('Preset.Take', id).then { sync_state } + set('Preset.Take', id).finally { sync_state } end alias switch_to preset @@ -55,7 +55,7 @@ def switch(signal_map) end end end - thread.finally(*interactions).then { sync_state } + thread.finally(*interactions).finally { sync_state } end def window(id, property, value) From 2e54dc5c2cfb5c45787ca1345b5949fd58a2514a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 8 Jun 2018 19:14:19 +1000 Subject: [PATCH 0515/1752] (tvone:coriomaster) update module state following window interaction --- modules/tv_one/corio_master.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 2465eb24..eebc5fa4 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -59,7 +59,9 @@ def switch(signal_map) end def window(id, property, value) - set "Window#{id}.#{property}", value + set("Window#{id}.#{property}", value).then do + self[:windows][:"window#{id}"] = query("Window#{id}").value + end end From a619e83454b3f4e84713549595ba6de3117df723 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 8 Jun 2018 20:54:44 +1000 Subject: [PATCH 0516/1752] (tvone:coriomaster) remove excess device interactions --- modules/tv_one/corio_master.rb | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index eebc5fa4..cba513b7 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -45,22 +45,18 @@ def preset(id) def switch(signal_map) interactions = signal_map.flat_map do |slot, windows| - Array(windows).map do |window| - if window.is_a?(String) && /Window\d+/i =~ window - set "#{window}.Input", slot - elsif window.is_a? Integer - set "Window#{window}.Input", slot - else - thread.defer.reject "invalid window: #{window}" - end + Array(windows).map do |id| + id = win[/\d+/].to_i if win.is_a? String + window id, 'Input', slot end end - thread.finally(*interactions).finally { sync_state } + thread.finally(*interactions) end def window(id, property, value) set("Window#{id}.#{property}", value).then do - self[:windows][:"window#{id}"] = query("Window#{id}").value + self[:windows][:"window#{id}"][property.downcase.to_sym] = value + signal_status :windows end end From de096230581ef3fe0b9a4fd957bda2f331cf5439 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 8 Jun 2018 21:00:44 +1000 Subject: [PATCH 0517/1752] (tvone:coriomaster) fix incorrect variable name --- modules/tv_one/corio_master.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index cba513b7..32bbf547 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -46,7 +46,7 @@ def preset(id) def switch(signal_map) interactions = signal_map.flat_map do |slot, windows| Array(windows).map do |id| - id = win[/\d+/].to_i if win.is_a? String + id = id[/\d+/].to_i if id.is_a? String window id, 'Input', slot end end From bfd6b937ac9589bf9b01ac12339ddf36855b18c1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 8 Jun 2018 21:12:17 +1000 Subject: [PATCH 0518/1752] (tvone:coriomaster) freeze string literals --- modules/tv_one/corio_master.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 32bbf547..deea831d 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module TvOne; end # Documentation: https://aca.im/driver_docs/TV+One/CORIOmaster-Commands-v1.7.0.pdf From daa49870733b7bbf812ce3ae535921b692a91f17 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 11 Jun 2018 02:33:19 +1000 Subject: [PATCH 0519/1752] (aca:router) fix return types to provide promises, not deferables --- modules/aca/router.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 23fcd195..8a6379e1 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -80,10 +80,10 @@ def connect(signal_map, atomic: false) _, failed = results.partition(&:last) if failed.empty? logger.debug 'all routes activated successfully' - :success + signal_map else failed.each { |result, _| logger.error result } - thread.defer.reject 'failed to activate all routes' + thread.reject 'failed to activate all routes' end end end @@ -231,10 +231,10 @@ def activate(edge) elsif edge.nx1? && signal_graph.outdegree(edge.source) == 1 logger.warn "cannot perform switch on #{edge.device}. " \ "This may be ok as only one input (#{edge.target}) is defined." - thread.defer.resolve + thread.defer.resolve.promise else - thread.defer.reject "cannot interact with #{edge.device}. " \ + thread.reject "cannot interact with #{edge.device}. " \ 'Module may be offline or incompatible.' end end From 1a2a951292dbc7257c151f28d3f73e0685d99f9c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 11 Jun 2018 02:39:01 +1000 Subject: [PATCH 0520/1752] (cisco:ce) return promise in place of deferable --- modules/cisco/collaboration_endpoint/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index ab80486e..d660f67f 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -287,7 +287,7 @@ def register_feedback(path, &update_handler) device_subscriptions.insert path, &update_handler - result || thread.defer.resolve(:success) + result || thread.defer.resolve.promise(:success) end def unregister_feedback(path) From ca6a589c89d90a68c704d461f7c38baa7630d2e2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 11 Jun 2018 17:25:20 +1000 Subject: [PATCH 0521/1752] (aca:router) only perform switch events when required --- modules/aca/router.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 8a6379e1..1a99c4bd 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -57,11 +57,14 @@ def on_update # 'atomic' may be used to throw an exception, prior to any device # interaction taking place if any of the routes are not # possible + # `force` may be used to force all switching, regardless of if the + # associated device module is already reporting it's on the + # correct input # # Multiple sources can be specified simultaneously, or if connecting a # single source to a single destination, Ruby's implicit hash syntax can be # used to let you express it neatly as `connect source => sink`. - def connect(signal_map, atomic: false) + def connect(signal_map, atomic: false, force: false) routes = {} signal_map.each_pair do |source, sinks| routes[source] = route_many source, sinks, strict: atomic @@ -75,7 +78,7 @@ def connect(signal_map, atomic: false) check_conflicts routes, strict: atomic edges = routes.values.map(&:second).reduce(&:|) - interactions = edges.map { |e| activate e } + interactions = edges.map { |e| activate e, force: force } thread.finally(interactions).then do |results| _, failed = results.partition(&:last) if failed.empty? @@ -219,19 +222,20 @@ def check_conflicts(routes, strict: false) end end - def activate(edge) + def activate(edge, force: false) mod = system[edge.device] if edge.nx1? && mod.respond_to?(:switch_to) - mod.switch_to edge.input + needs_switch = mod[:input] != edge.input + mod.switch_to edge.input if needs_switch || force elsif edge.nxn? && mod.respond_to?(:switch) + # TODO: define standard API for exposing matrix state mod.switch edge.input => edge.output elsif edge.nx1? && signal_graph.outdegree(edge.source) == 1 logger.warn "cannot perform switch on #{edge.device}. " \ "This may be ok as only one input (#{edge.target}) is defined." - thread.defer.resolve.promise else thread.reject "cannot interact with #{edge.device}. " \ From 0b0b16fdaa3e23777dbdec98dec2c4a88f1b97e5 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 11 Jun 2018 18:42:24 +1000 Subject: [PATCH 0522/1752] (aca:router) symbolise IO --- modules/aca/router.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 1a99c4bd..f035b26d 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -258,6 +258,18 @@ class Aca::Router::SignalGraph Paths = Struct.new :distance_to, :predecessor Edge = Struct.new :source, :target, :device, :input, :output do + def device=(device) + super(device.try(:to_sym) || device) + end + + def input=(input) + super(input.try(:to_sym) || input) + end + + def output=(output) + super(output.try(:to_sym) || output) + end + def to_s "#{target} to #{device} (in #{input})" end From f9c7814d57872f2d429fec954521405cc916d80f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 11 Jun 2018 19:26:32 +1000 Subject: [PATCH 0523/1752] (aca:router) make Edges a stand-alone class --- modules/aca/router.rb | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index f035b26d..9a164f63 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -257,17 +257,19 @@ def activate(edge, force: false) class Aca::Router::SignalGraph Paths = Struct.new :distance_to, :predecessor - Edge = Struct.new :source, :target, :device, :input, :output do - def device=(device) - super(device.try(:to_sym) || device) - end + class Edge + attr_reader :source, :target, :device, :input, :output - def input=(input) - super(input.try(:to_sym) || input) - end + Meta = Struct.new(:device, :input, :output) + + def initialize(source, target, &blk) + @source = source + @target = target - def output=(output) - super(output.try(:to_sym) || output) + meta = Meta.new.tap(&blk) + @device = meta.device&.to_sym + @input = meta.input.try(:to_sym) || meta.input + @output = meta.output.try(:to_sym) || meta.output end def to_s @@ -347,7 +349,7 @@ def delete(id, check_incoming_edges: true) end def join(source, target, &block) - datum = Edge.new(source, target).tap(&block) + datum = Edge.new(source, target, &block) nodes[source].join target, datum self end From 2bb1b5fd5221656066eafd5deedc00e3fbd593ec Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Jun 2018 20:44:07 +1000 Subject: [PATCH 0524/1752] Fix logic to skip all day bookings --- lib/microsoft/exchange.rb | 3 ++- modules/aca/exchange_booking.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index c8eb1fa3..3caa3b7f 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -25,6 +25,7 @@ def initialize( @service_account_email = service_account_email @service_account_password = service_account_password @internet_proxy = internet_proxy + @hide_all_day_bookings = hide_all_day_bookings ews_opts = { http_opts: { ssl_verify_mode: 0 } } ews_opts[:http_opts][:http_client] = @internet_proxy if @internet_proxy STDERR.puts '--------------- NEW CLIENT CREATED --------------' @@ -227,7 +228,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. } } if event.required_attendees - if hide_all_day_bookings + if @hide_all_day_bookings next if (event.end - event.start) > 86399 end bookings.push(booking) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index efebe2ea..af27bc41 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -721,7 +721,7 @@ def todays_bookings start = item[:start][:text] ending = item[:end][:text] all_day_bookings = setting(:hide_all_day_bookings) || false - if !all_day_bookings + if all_day_bookings if (Time.parse(ending) - Time.parse(start)).to_i > 86399 STDERR.puts "SKIPPING #{item[:subject][:text]}" STDERR.flush From e48f65da1ef7455df003de00910e3154bab2dd00 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Jun 2018 21:50:25 +1000 Subject: [PATCH 0525/1752] Fix logiv to hide all day bookings --- lib/microsoft/exchange.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 3caa3b7f..1d285a99 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -227,9 +227,10 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. email: attendee.email } } if event.required_attendees - if @hide_all_day_bookings - next if (event.end - event.start) > 86399 + STDERR.puts "SKIPPING #{event.subject}" + STDERR.flush + next if event.end.to_time - event.start.to_time > 86399 end bookings.push(booking) } From 48c7b1f903425bb88472a17740a7f511ed532734 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Jun 2018 22:05:44 +1000 Subject: [PATCH 0526/1752] [Exchange driver] Remove nils in fetch results --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index af27bc41..b3c92ac2 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -801,7 +801,7 @@ def todays_bookings system[:Skype].set_uri(nil) if skype_exists end - results + results.compact end # ======================================= end From 8b28f13b4d741bf9d9da729026ff6bf2596cca42 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 12 Jun 2018 12:39:55 +1000 Subject: [PATCH 0527/1752] (aca:meeting) auto Cisco speaker_track en/disable --- modules/aca/meeting_room.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index 5585b1ba..295e7d6f 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -949,6 +949,10 @@ def select_camera(source, input, output = nil) inp = src[:input] out = src[:output] system[:Switcher].switch({inp => out}) if inp && out + + # Enable or disable Cisco Speakertrack for this camera + speaker_track_setting = src[:auto_camera] # true/false/nil. When nil, no command is sent + system[:VidConf].speaker_track(speaker_track_setting) unless speaker_track_setting.nil? end end From f7fbd2891ac586d448ec86b99a25994d20095531 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 12 Jun 2018 21:28:08 +1000 Subject: [PATCH 0528/1752] (aca:device_config) add base module --- modules/aca/device_config.rb | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 modules/aca/device_config.rb diff --git a/modules/aca/device_config.rb b/modules/aca/device_config.rb new file mode 100644 index 00000000..6c87f12a --- /dev/null +++ b/modules/aca/device_config.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +module Aca; end + +class Aca::DeviceConfig + include ::Orchestrator::Constants + + descriptive_name 'Device Config Manager' + generic_name :DeviceConfig + implements :logic + description <<~DESC + Utility module for executing device setup actions when connectivity is + established. + + Actions may be specified under the `device_config` setting. This should + be of the form: + + mod => { method => args } + + Or, if a method must be executed multiple times + + mod => [{ method => args }] + DESC + + default_settings( + # Setup actions to perform on any devices to ensure they are correctly + # configured for interaction with this system. Structure should be of + # the form device => { method => args }. These actions will be pushed + # to the device on connect. + device_config: {} + ) + + def on_load + system.load_complete do + setup_config_subscriptions + end + end + + def on_update + setup_config_subscriptions + end + + + protected + + + # Setup event subscriptions to push device setup actions to devices when + # they connect. + def setup_config_subscriptions + @device_config_subscriptions&.each { |ref| unsubscribe ref } + + @device_config_subscriptions = load_config.map do |dev, actions| + mod, idx = mod_idx_for dev + + system.subscribe(mod, idx, :connected) do |notification| + next unless notification.value + logger.debug { "pushing system defined config to #{dev}" } + device = system.get mod, idx + actions.each { |(method, args)| device.send method, *args } + end + end + end + + def load_config + actions = setting(:device_config) || {} + + # Allow device config actions to either be specified as a single hash + # of method => arg mappings, or an array of these if the same method + # needs to be called multiple times. + actions.transform_values! do |exec_methods| + exec_methods = Array.wrap exec_methods + exec_methods.flat_map(&:to_a).map do |method, args| + [method.to_sym, Array.wrap(args)] + end + end + + actions.freeze + end + + + + # Map a module id in the form name_idx out into its [name, idx] components. + # + # @param device [Symbol, String] the module id to destructure + # @return [[Symbol, Integer]] + def mod_idx_for(device) + mod, idx = device.to_s.split '_' + mod = mod.to_sym + idx = idx&.to_i || 1 + [mod, idx] + end +end From c8f1e81cee59c94ac5e578651e9aff99adafe3e4 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 01:52:54 +1000 Subject: [PATCH 0529/1752] (aca:router) neaten up signature of internal method --- modules/aca/router.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 9a164f63..d6c9192a 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -78,7 +78,7 @@ def connect(signal_map, atomic: false, force: false) check_conflicts routes, strict: atomic edges = routes.values.map(&:second).reduce(&:|) - interactions = edges.map { |e| activate e, force: force } + interactions = edges.map { |e| activate e, force } thread.finally(interactions).then do |results| _, failed = results.partition(&:last) if failed.empty? @@ -222,7 +222,7 @@ def check_conflicts(routes, strict: false) end end - def activate(edge, force: false) + def activate(edge, force = false) mod = system[edge.device] if edge.nx1? && mod.respond_to?(:switch_to) From 89682e866ff5ed83aaa6e56ed196f3916e3c3a3f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 01:53:28 +1000 Subject: [PATCH 0530/1752] (aca:router) add section comments --- modules/aca/router.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index d6c9192a..a4489cc9 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -16,11 +16,16 @@ class Aca::Router devices and complex/layered switching infrastructure. DESC + default_settings( # Nested hash of signal connectivity. See SignalGraph.from_map. connections: {} ) + + # ------------------------------ + # Callbacks + def on_load on_update end @@ -51,6 +56,10 @@ def on_update self[:outputs] = signal_graph.sources.map(&:id) end + + # ------------------------------ + # Public API + # Route a set of signals to arbitrary destinations. # # `signal_map` is a hash of the structure `{ source: sink | [sinks] }` @@ -141,6 +150,10 @@ def downstream(sink, source = nil) edge.target end + + # ------------------------------ + # Internals + protected def signal_graph From f21f09b6836cb558fa0029317fbf73bd0f5a6f6a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 02:44:18 +1000 Subject: [PATCH 0531/1752] (aca:router) minor comment update --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index a4489cc9..4d591590 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -66,7 +66,7 @@ def on_update # 'atomic' may be used to throw an exception, prior to any device # interaction taking place if any of the routes are not # possible - # `force` may be used to force all switching, regardless of if the + # `force` control if switch events should be forced, even when the # associated device module is already reporting it's on the # correct input # From 937e14bd70f1266563cfba7087b7bd19ecbcef37 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 02:44:48 +1000 Subject: [PATCH 0532/1752] (aca:router) return edges as list rather than hash --- modules/aca/router.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 4d591590..734d5324 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -388,13 +388,13 @@ def sinks end def incoming_edges(id) - reduce(HashWithIndifferentAccess.new) do |edges, node| - edges.tap { |e| e[node.id] = node.edges[id] if node.edges.key? id } + reduce([]) do |edges, node| + edges << node.edges[id] if node.edges.key? id end end def outgoing_edges(id) - nodes[id].edges + nodes[id].edges.values end def indegree(id) From b6b6d4da821cd4ec5ea15632660c1a5d1e0dc18e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 02:55:00 +1000 Subject: [PATCH 0533/1752] (aca:router) make #upstream and #downstream return node id's --- modules/aca/router.rb | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 734d5324..4f5f920b 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -100,54 +100,51 @@ def connect(signal_map, atomic: false, force: false) end end - # Lookup the name of the input on a sink node that would be used to connect - # a source to it. + # Lookup the input on a sink node that would be used to connect a specific + # source to it. # - # This can be used to register the source on devices such as codecs that - # support upstream switching. - def input_for(source, sink) + # `on` may be ommited if the source node has only one neighbour (e.g. is + # an input node) and you wish to query the phsycial input associated with + # it. Similarly `on` maybe used to look up the input used by any other node + # within the graph that would be used to show `source`. + def input_for(source, on: nil) + sink = on || upstream(source) _, edges = route source, sink edges.last.input end - # Get the device and associated input immediately upstream of an input node. + # Get the node immediately upstream of an input node. # # Depending on the device API, this may be of use for determining signal # presence. def upstream(source, sink = nil) if sink.nil? edges = signal_graph.incoming_edges source - unless edges.size == 1 - raise ArgumentError, "more than one edge to #{source}, " \ - 'please specify a sink' - end - _, edge = edges.first + raise "no outputs from #{source}" if edges.empty? + raise "multiple outputs from #{source}, please specify a sink" \ + if edges.size > 1 else _, edges = route source, sink - edge = edges.first end - [edge.device, edge.input] + edges.first.source end - # Get the device immediately preceeding an output node. + # Get the node immediately downstream of an output node. # # This may be used walking back up the signal graph to find a decoder for - # and output device. + # an output device. def downstream(sink, source = nil) if source.nil? edges = signal_graph.outgoing_edges sink - unless edges.size == 1 - raise ArgumentError, "more than one input to #{sink}, " \ - 'please specify a source' - end - _, edge = edges.first + raise "no inputs to #{sink}" if edges.empty? + raise "multiple inputs to #{sink}, please specify a source" \ + if edges.size > 1 else _, edges = route source, sink - edge = edges.last end - edge.target + edges.last.target end From 6016c363c648003ea476864bb0b43a18f9d41b68 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 03:54:38 +1000 Subject: [PATCH 0534/1752] (aca:device_config) update module name --- modules/aca/device_config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/device_config.rb b/modules/aca/device_config.rb index 6c87f12a..a326939c 100644 --- a/modules/aca/device_config.rb +++ b/modules/aca/device_config.rb @@ -5,7 +5,7 @@ module Aca; end class Aca::DeviceConfig include ::Orchestrator::Constants - descriptive_name 'Device Config Manager' + descriptive_name 'ACA Device Config Manager' generic_name :DeviceConfig implements :logic description <<~DESC From bcdbe049ff0dd0f065a1c40a1c76f73eec79f459 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 05:13:14 +1000 Subject: [PATCH 0535/1752] (aca:router) fix issue where nil would be returned for incoming edges --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 4f5f920b..5a9899e9 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -385,7 +385,7 @@ def sinks end def incoming_edges(id) - reduce([]) do |edges, node| + each_with_object([]) do |node, edges| edges << node.edges[id] if node.edges.key? id end end From a2958b0facf3bfd9b6e8861c649f690a3ab106f5 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 14:39:15 +1000 Subject: [PATCH 0536/1752] (aca:router) gracefully handle error encountered during edge activation --- modules/aca/router.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 5a9899e9..de3feb6d 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -251,6 +251,8 @@ def activate(edge, force = false) thread.reject "cannot interact with #{edge.device}. " \ 'Module may be offline or incompatible.' end + rescue => e + thread.reject "error connecting #{edge.target} to #{edge.source}: #{e}" end end From 63e0c2de8cadf88c019a067461a314212547e57d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 19:18:56 +1000 Subject: [PATCH 0537/1752] Add ID to exchange booking driver --- modules/aca/exchange_booking.rb | 3 +- modules/aca/people_counter.rb | 161 ++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 modules/aca/people_counter.rb diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index b3c92ac2..80321582 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -791,7 +791,8 @@ def todays_bookings :setup => 0, :breakdown => 0, :start_epoch => real_start.to_i, - :end_epoch => real_end.to_i + :end_epoch => real_end.to_i, + :id => meeting.id } end diff --git a/modules/aca/people_counter.rb b/modules/aca/people_counter.rb new file mode 100644 index 00000000..321dd178 --- /dev/null +++ b/modules/aca/people_counter.rb @@ -0,0 +1,161 @@ +module Enumerable + def each_with_previous + self.inject(nil){|prev, curr| yield prev, curr; curr} + self + end +end + +module Aca; end +module Aca::Tracking; end + +class Aca::Tracking::PeopleCounter + include ::Orchestrator::Constants + include Orchestrator::StateBinder + descriptive_name 'ACA Demo Logic' + generic_name :Demo + implements :logic + + bind :VidConf, :people_count, to: :count_changed + + bind :Bookings, :today, to: :booking_changed + + durations = [] + total_duration = 0 + @todays_bookings = nil + events = [] + + def booking_changed(details) + return if details.nil? + @todays_bookings = details + details.each do |meeting| + schedule.at(meeting[:End]) { + calculate_average(meeting) + } + end + + end + + def get_current_booking(details) + start_time = Time.now.to_i + # For every meeting + details.each do |meeting| + # Grab the start and end + meeting_start = Time.at(meeting[:start_epoch]).to_i + meeting_end = Time.at(meeting[:start_epoch]).to_i + + # If it's past the start time and before the end time + if start_time >= meeting_start && start_time < meeting_end + return meeting + end + end + end + + def count_changed(new_count) + # Check the current meeting + current = get_current_booking(@todays_bookings) + + # Add the change to the dataset for that meeting + current_dataset = ::Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") || create_dataset(new_count, current) + + # Check if the new count is max + dataset.maximum = new_count if new_count > dataset.maximum + + # Update the dataset with the new count + current_dataset.counts.push(Time.now.to_i, new_count) + + # Save it back + current_dataset.save! + end + + def create_dataset(count, booking) + dataset = ::Aca::Tracking::PeopleCount.new + + # # Dataset attrs + # attribute :room_email, type: String + # attribute :booking_id, type: String + # attribute :system_id, type: String + # attribute :capacity, type: Integer + # attribute :maximum, type: Integer + # attribute :average, type: Integer + # attribute :median, type: Integer + # attribute :organiser, type: String + + dataset.room_email = system.email + dataset.system_id = system.id + dataset.capacity = system.capacity + dataset.maximum = count + dataset.average = count + dataset.median = count + dataset.booking_id = booking[:id] + dataset.organiser = booking[:owner] + return dataset if dataset.save! + end + + def calculate_average(meeting) + # Set up our holding vars + durations = [] + total_duration = 0 + + # Get the dataset + dataset = ::Aca::Tracking::PeopleCount.find_by_id("count-#{meeting[:id]}") + + events = dataset.counts + + # Calculate array of weighted durations + events.each_with_previous do |prev, curr| + if prev + time = curr[0] + count = curr[1] + prev_time = prev[0] + prev_count = prev[1] + durations[prev_count] ||= 0 + durations[prev_count] += (time - prev_time) + total_duration += (time - prev_time) + end + end + + # Remove nils + durations = durations.each_with_index.map {|x,y| [x,y] }.delete_if { |x| x[0].nil? } + + # Generate weighted average + running_total = 0 + average = nil + durations.each {|reading| + duration = reading[0] + count = reading[1] + running_total += duration + if running_total / total_duration > 0.5 + average = reading[1] + break + end + } + + dataset.average = average + dataset.save! + + return average + end + + + def on_load + self[:name] = system.name + self[:views] = 0 + self[:state] = 'Idle' + end + + def on_update + schedule.clear + schedule.every('10s') { update_state } + end + + def update_state + if self[:state] == 'Stopped' + state('Idle') + end + self[:views] += rand(7) + end + + def state(status) + self[:state] = status + end +end From a4a8089bcc5867342c3eb01a4c1727b1ab9819f6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 19:19:39 +1000 Subject: [PATCH 0538/1752] Add people count module (oops it was in the last commit) --- lib/aca/tracking/people_count.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 lib/aca/tracking/people_count.rb diff --git a/lib/aca/tracking/people_count.rb b/lib/aca/tracking/people_count.rb new file mode 100644 index 00000000..a3826bb6 --- /dev/null +++ b/lib/aca/tracking/people_count.rb @@ -0,0 +1,14 @@ +class Aca::Tracking::PeopleCount < CouchbaseOrm::Base + design_document :pcount + + # Connection details + attribute :room_email, type: String + attribute :booking_id, type: String + attribute :system_id, type: String + attribute :capacity, type: Integer + attribute :maximum, type: Integer + attribute :average, type: Integer + attribute :median, type: Integer + attribute :organiser, type: String + attribute :counts, type: Array, default: [] +end \ No newline at end of file From b8349fa389123d1390aaabb23f1852551e273a8f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 19:55:48 +1000 Subject: [PATCH 0539/1752] Forgot the name! --- modules/aca/people_counter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/people_counter.rb b/modules/aca/people_counter.rb index 321dd178..63a05bc6 100644 --- a/modules/aca/people_counter.rb +++ b/modules/aca/people_counter.rb @@ -11,8 +11,8 @@ module Aca::Tracking; end class Aca::Tracking::PeopleCounter include ::Orchestrator::Constants include Orchestrator::StateBinder - descriptive_name 'ACA Demo Logic' - generic_name :Demo + descriptive_name 'ACA People Count' + generic_name :Count implements :logic bind :VidConf, :people_count, to: :count_changed From 5707dec7c0fa61cea2eed39a8e6dc2aaef8d33e8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 20:06:30 +1000 Subject: [PATCH 0540/1752] Move people counter to tracking folder --- modules/aca/{ => tracking}/people_counter.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/aca/{ => tracking}/people_counter.rb (100%) diff --git a/modules/aca/people_counter.rb b/modules/aca/tracking/people_counter.rb similarity index 100% rename from modules/aca/people_counter.rb rename to modules/aca/tracking/people_counter.rb From 5a6014d543aeac65ca1a6fdc6d8254010e626cd6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 20:22:48 +1000 Subject: [PATCH 0541/1752] [People counter] Update logic and add logging --- modules/aca/tracking/people_counter.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 63a05bc6..1a65ac1c 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -51,6 +51,8 @@ def get_current_booking(details) end def count_changed(new_count) + logger.info "Count changed: #{new_count}" + # Check the current meeting current = get_current_booking(@todays_bookings) @@ -68,6 +70,7 @@ def count_changed(new_count) end def create_dataset(count, booking) + logger.info "Creating a dataset" dataset = ::Aca::Tracking::PeopleCount.new # # Dataset attrs @@ -88,10 +91,14 @@ def create_dataset(count, booking) dataset.median = count dataset.booking_id = booking[:id] dataset.organiser = booking[:owner] + dataset.id = "count-#{booking[:id]}" + logger.info "Created dataset with ID: #{dataset.id}" return dataset if dataset.save! end def calculate_average(meeting) + logger.info "Calculating average for: #{meeting[:id]}" + # Set up our holding vars durations = [] total_duration = 0 @@ -141,11 +148,18 @@ def on_load self[:name] = system.name self[:views] = 0 self[:state] = 'Idle' + self[:todays_bookings] = [] + on_update end def on_update schedule.clear - schedule.every('10s') { update_state } + logger.info "Starting booking update in 30s" + schedule.in('30s') { + logger.info "Grabbing bookings to update" + self[:todays_bookings] = system[:Bookings][:today] + booking_changed(self[:todays_bookings]) + } end def update_state From 6f4e529ae424228ec6e61f54ab1e797cd34761b8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 20:24:47 +1000 Subject: [PATCH 0542/1752] Change load logic --- modules/aca/tracking/people_counter.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 1a65ac1c..4ed697e3 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -145,14 +145,14 @@ def calculate_average(meeting) def on_load - self[:name] = system.name - self[:views] = 0 - self[:state] = 'Idle' - self[:todays_bookings] = [] on_update end def on_update + self[:name] = system.name + self[:views] = 0 + self[:state] = 'Idle' + self[:todays_bookings] = [] schedule.clear logger.info "Starting booking update in 30s" schedule.in('30s') { From d29175f543c132a3c53cfac741191112f7447341 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 20:27:14 +1000 Subject: [PATCH 0543/1752] Reduce timings on retreival --- modules/aca/tracking/people_counter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 4ed697e3..fb59e36e 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -57,7 +57,7 @@ def count_changed(new_count) current = get_current_booking(@todays_bookings) # Add the change to the dataset for that meeting - current_dataset = ::Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") || create_dataset(new_count, current) + current_dataset = Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") || create_dataset(new_count, current) # Check if the new count is max dataset.maximum = new_count if new_count > dataset.maximum @@ -71,7 +71,7 @@ def count_changed(new_count) def create_dataset(count, booking) logger.info "Creating a dataset" - dataset = ::Aca::Tracking::PeopleCount.new + dataset = Aca::Tracking::PeopleCount.new # # Dataset attrs # attribute :room_email, type: String @@ -155,7 +155,7 @@ def on_update self[:todays_bookings] = [] schedule.clear logger.info "Starting booking update in 30s" - schedule.in('30s') { + schedule.in('10s') { logger.info "Grabbing bookings to update" self[:todays_bookings] = system[:Bookings][:today] booking_changed(self[:todays_bookings]) From 2b8c028183da97ce56ed9ecffb4f64c44b3af430 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 20:30:10 +1000 Subject: [PATCH 0544/1752] Fix ID format --- modules/aca/tracking/people_counter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index fb59e36e..191a3f47 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -29,7 +29,7 @@ def booking_changed(details) @todays_bookings = details details.each do |meeting| schedule.at(meeting[:End]) { - calculate_average(meeting) + calculate_average(meeting) if Time.parse(meeting[:End]) > Time.now } end @@ -91,7 +91,6 @@ def create_dataset(count, booking) dataset.median = count dataset.booking_id = booking[:id] dataset.organiser = booking[:owner] - dataset.id = "count-#{booking[:id]}" logger.info "Created dataset with ID: #{dataset.id}" return dataset if dataset.save! end From 5425f440bb007752df6464d6a5f9c3637ac73f03 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 20:49:31 +1000 Subject: [PATCH 0545/1752] Add ID generation to count class --- lib/aca/tracking/people_count.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/aca/tracking/people_count.rb b/lib/aca/tracking/people_count.rb index a3826bb6..c3f28d91 100644 --- a/lib/aca/tracking/people_count.rb +++ b/lib/aca/tracking/people_count.rb @@ -1,3 +1,5 @@ +module Aca; end +module Aca::Tracking class Aca::Tracking::PeopleCount < CouchbaseOrm::Base design_document :pcount @@ -11,4 +13,12 @@ class Aca::Tracking::PeopleCount < CouchbaseOrm::Base attribute :median, type: Integer attribute :organiser, type: String attribute :counts, type: Array, default: [] + + protected + + + before_create :set_id + def set_id + self.id = "count-#{self.booking_id}" + end end \ No newline at end of file From 1d6e88d681cac728dd5d2750ca256de91bc23dc9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 20:49:43 +1000 Subject: [PATCH 0546/1752] Add bindin to booking update --- modules/aca/tracking/people_counter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 191a3f47..a41999b0 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -26,6 +26,7 @@ class Aca::Tracking::PeopleCounter def booking_changed(details) return if details.nil? + self[:todays_bookings] = details @todays_bookings = details details.each do |meeting| schedule.at(meeting[:End]) { From 2360d19384bc951a75d40d0c95740d1d5c30db7b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 21:07:18 +1000 Subject: [PATCH 0547/1752] Fix include statebinder statement --- modules/aca/tracking/people_counter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index a41999b0..eafed0df 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -10,7 +10,7 @@ module Aca::Tracking; end class Aca::Tracking::PeopleCounter include ::Orchestrator::Constants - include Orchestrator::StateBinder + include ::Orchestrator::StateBinder descriptive_name 'ACA People Count' generic_name :Count implements :logic From e440db44ff488d2f8d5798663eedd2c33e3b2daf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 22:03:10 +1000 Subject: [PATCH 0548/1752] Require people count library properly --- lib/aca/tracking/people_count.rb | 4 +++- modules/aca/tracking/people_counter.rb | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/aca/tracking/people_count.rb b/lib/aca/tracking/people_count.rb index c3f28d91..5ff24bef 100644 --- a/lib/aca/tracking/people_count.rb +++ b/lib/aca/tracking/people_count.rb @@ -1,5 +1,5 @@ module Aca; end -module Aca::Tracking +module Aca::Tracking; end class Aca::Tracking::PeopleCount < CouchbaseOrm::Base design_document :pcount @@ -18,7 +18,9 @@ class Aca::Tracking::PeopleCount < CouchbaseOrm::Base before_create :set_id + def set_id self.id = "count-#{self.booking_id}" end + end \ No newline at end of file diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index eafed0df..09c7106d 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -1,3 +1,4 @@ +require 'aca/tracking/people_count' module Enumerable def each_with_previous self.inject(nil){|prev, curr| yield prev, curr; curr} From d3c41699f1fb5b39daa2b215a413a4c93b02d612 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 22:05:13 +1000 Subject: [PATCH 0549/1752] Return current booking correctly --- modules/aca/tracking/people_counter.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 09c7106d..62c22ce3 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -40,6 +40,7 @@ def booking_changed(details) def get_current_booking(details) start_time = Time.now.to_i # For every meeting + current = nil details.each do |meeting| # Grab the start and end meeting_start = Time.at(meeting[:start_epoch]).to_i @@ -47,9 +48,10 @@ def get_current_booking(details) # If it's past the start time and before the end time if start_time >= meeting_start && start_time < meeting_end - return meeting + current = meeting end end + current end def count_changed(new_count) From d018701312aa904d390eaf5fb0d8111875aa803a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 22:07:08 +1000 Subject: [PATCH 0550/1752] (aca:router) check edges can activate prior to executing device actions --- modules/aca/router.rb | 56 ++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index de3feb6d..ef76afec 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -79,22 +79,23 @@ def connect(signal_map, atomic: false, force: false) routes[source] = route_many source, sinks, strict: atomic end - logger.debug do - nodes = routes.transform_values { |n, _| n.map(&:to_s) } - "Nodes to connect: #{nodes}" - end - check_conflicts routes, strict: atomic edges = routes.values.map(&:second).reduce(&:|) + edges, unroutable = edges.partition { |e| can_activate? e } + raise 'can not perform all routes' if unroutable.any? && atomic + interactions = edges.map { |e| activate e, force } + thread.finally(interactions).then do |results| - _, failed = results.partition(&:last) + failed = edges.zip(results).reject { |_, (_, resolved)| resolved } if failed.empty? logger.debug 'all routes activated successfully' signal_map else - failed.each { |result, _| logger.error result } + failed.each do |edge, (error, _)| + logger.error "could not switch #{edge}: #{error}" + end thread.reject 'failed to activate all routes' end end @@ -232,27 +233,44 @@ def check_conflicts(routes, strict: false) end end + def can_activate?(edge) + mod = system[edge.device] + + fail_with = proc do |reason| + logger.warn "#{edge.device} #{reason} - can not switch #{edge}" + return false + end + + fail_with['does not exist'] if mod.nil? + + fail_with['offline'] if mod[:connected] == false + + if edge.nx1? && !mod.respond_to?(:switch_to) + if signal_graph.outdegree(edge.source) == 1 + logger.info "can not switch #{edge.device}. " \ + "This may be ok as only one input (#{edge.target}) exists." + else + fail_with['missing #switch_to'] + end + end + + fail_with['missing #switch'] if edge.nxn? && !mod.respond_to?(:switch) + + true + end + def activate(edge, force = false) mod = system[edge.device] - if edge.nx1? && mod.respond_to?(:switch_to) + if edge.nx1? needs_switch = mod[:input] != edge.input mod.switch_to edge.input if needs_switch || force - - elsif edge.nxn? && mod.respond_to?(:switch) + elsif edge.nxn? # TODO: define standard API for exposing matrix state mod.switch edge.input => edge.output - - elsif edge.nx1? && signal_graph.outdegree(edge.source) == 1 - logger.warn "cannot perform switch on #{edge.device}. " \ - "This may be ok as only one input (#{edge.target}) is defined." - else - thread.reject "cannot interact with #{edge.device}. " \ - 'Module may be offline or incompatible.' + raise 'unexpected edge type' end - rescue => e - thread.reject "error connecting #{edge.target} to #{edge.source}: #{e}" end end From 72ce23c4700967ec4b72126d8a4741ee82bd436c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 22:09:12 +1000 Subject: [PATCH 0551/1752] [People count] Add debugging --- modules/aca/tracking/people_counter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 62c22ce3..d5c874fb 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -55,7 +55,7 @@ def get_current_booking(details) end def count_changed(new_count) - logger.info "Count changed: #{new_count}" + logger.info "Count changed: #{new_count} and ID: #{current[:id]}" # Check the current meeting current = get_current_booking(@todays_bookings) From 4a56aa3039ef715c996a0795ac78b3070508a78a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 22:11:13 +1000 Subject: [PATCH 0552/1752] Fix current booking logic --- modules/aca/tracking/people_counter.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index d5c874fb..2671a808 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -22,13 +22,11 @@ class Aca::Tracking::PeopleCounter durations = [] total_duration = 0 - @todays_bookings = nil events = [] def booking_changed(details) return if details.nil? self[:todays_bookings] = details - @todays_bookings = details details.each do |meeting| schedule.at(meeting[:End]) { calculate_average(meeting) if Time.parse(meeting[:End]) > Time.now @@ -55,10 +53,11 @@ def get_current_booking(details) end def count_changed(new_count) - logger.info "Count changed: #{new_count} and ID: #{current[:id]}" # Check the current meeting - current = get_current_booking(@todays_bookings) + current = get_current_booking(self[:todays_bookings]) + + logger.info "Count changed: #{new_count} and ID: #{current[:id]}" # Add the change to the dataset for that meeting current_dataset = Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") || create_dataset(new_count, current) From 968399a605abb1e55c3fe5bee258976958f76e41 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 22:12:57 +1000 Subject: [PATCH 0553/1752] Add check for empty bookings --- modules/aca/tracking/people_counter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 2671a808..0e1f6be0 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -53,7 +53,7 @@ def get_current_booking(details) end def count_changed(new_count) - + return if self[:todays_bookings].nil? || self[:todays_bookings].empty? # Check the current meeting current = get_current_booking(self[:todays_bookings]) From 30c5024018441a1d8a3e14b7b1ee64dbc94ac6c3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 22:26:01 +1000 Subject: [PATCH 0554/1752] Fix variable misname --- modules/aca/tracking/people_counter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 0e1f6be0..74e46cdf 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -63,7 +63,7 @@ def count_changed(new_count) current_dataset = Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") || create_dataset(new_count, current) # Check if the new count is max - dataset.maximum = new_count if new_count > dataset.maximum + current_dataset.maximum = new_count if new_count > current_dataset.maximum # Update the dataset with the new count current_dataset.counts.push(Time.now.to_i, new_count) From b2cb0f2bcf757a4e18f6ab4877d053b00eb9b6c6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 22:26:26 +1000 Subject: [PATCH 0555/1752] Fix variable misname --- modules/aca/tracking/people_counter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 74e46cdf..cff0c41d 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -42,7 +42,7 @@ def get_current_booking(details) details.each do |meeting| # Grab the start and end meeting_start = Time.at(meeting[:start_epoch]).to_i - meeting_end = Time.at(meeting[:start_epoch]).to_i + meeting_end = Time.at(meeting[:end_epoch]).to_i # If it's past the start time and before the end time if start_time >= meeting_start && start_time < meeting_end From b86f2baae6c54321990192b297886ac64fe28b7b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 22:32:38 +1000 Subject: [PATCH 0556/1752] (aca:router) include short-cicuited interactions in rejection case --- modules/aca/router.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index ef76afec..bd267503 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -82,6 +82,7 @@ def connect(signal_map, atomic: false, force: false) check_conflicts routes, strict: atomic edges = routes.values.map(&:second).reduce(&:|) + edges, unroutable = edges.partition { |e| can_activate? e } raise 'can not perform all routes' if unroutable.any? && atomic @@ -89,7 +90,7 @@ def connect(signal_map, atomic: false, force: false) thread.finally(interactions).then do |results| failed = edges.zip(results).reject { |_, (_, resolved)| resolved } - if failed.empty? + if (failed + unroutable).empty? logger.debug 'all routes activated successfully' signal_map else From d9e12465f4ed5cca13b99fea2ec2747b2219e89c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 22:37:40 +1000 Subject: [PATCH 0557/1752] Add will_change! --- modules/aca/tracking/people_counter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index cff0c41d..c31c5dd5 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -66,6 +66,7 @@ def count_changed(new_count) current_dataset.maximum = new_count if new_count > current_dataset.maximum # Update the dataset with the new count + current_dataset.counts_will_change! current_dataset.counts.push(Time.now.to_i, new_count) # Save it back From a18b9014cdb6b51e8e4c210685f8a6c4198a29c5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 23:14:54 +1000 Subject: [PATCH 0558/1752] [People counter] Clear schedule on booking change --- modules/aca/tracking/people_counter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index c31c5dd5..5931b86b 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -27,6 +27,7 @@ class Aca::Tracking::PeopleCounter def booking_changed(details) return if details.nil? self[:todays_bookings] = details + schedule.clear details.each do |meeting| schedule.at(meeting[:End]) { calculate_average(meeting) if Time.parse(meeting[:End]) > Time.now @@ -67,7 +68,7 @@ def count_changed(new_count) # Update the dataset with the new count current_dataset.counts_will_change! - current_dataset.counts.push(Time.now.to_i, new_count) + current_dataset.counts.push([Time.now.to_i, new_count]) # Save it back current_dataset.save! From 61b7e2036e49b2ad8027eb677b53809361c0e15a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 23:19:44 +1000 Subject: [PATCH 0559/1752] [People counter] Clear schedule on booking change --- modules/aca/tracking/people_counter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 5931b86b..cb9c265d 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -57,6 +57,7 @@ def count_changed(new_count) return if self[:todays_bookings].nil? || self[:todays_bookings].empty? # Check the current meeting current = get_current_booking(self[:todays_bookings]) + return if current.nil? logger.info "Count changed: #{new_count} and ID: #{current[:id]}" From a23aaa310efd18774930871d416ce17737ac7870 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 13 Jun 2018 23:29:25 +1000 Subject: [PATCH 0560/1752] (aca:router) log error as well as failing request if routes fail --- modules/aca/router.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index bd267503..5ecbbe3c 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -95,9 +95,11 @@ def connect(signal_map, atomic: false, force: false) signal_map else failed.each do |edge, (error, _)| - logger.error "could not switch #{edge}: #{error}" + logger.warn "could not switch #{edge}: #{error}" end - thread.reject 'failed to activate all routes' + message = 'failed to activate all routes' + logger.error message + thread.reject message end end end From 9628601737fda6cdebb7527e5ed1f91cc4cded6c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 23:38:20 +1000 Subject: [PATCH 0561/1752] [People counter] Only schedule in the future --- modules/aca/tracking/people_counter.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index cb9c265d..59c6783f 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -27,11 +27,15 @@ class Aca::Tracking::PeopleCounter def booking_changed(details) return if details.nil? self[:todays_bookings] = details + logger.info "Got new bookings, clearing schedule" schedule.clear - details.each do |meeting| - schedule.at(meeting[:End]) { - calculate_average(meeting) if Time.parse(meeting[:End]) > Time.now - } + if Time.parse(meeting[:End]) > Time.now + logger.info "Calculating average at #{meeting[:End]}" + details.each do |meeting| + schedule.at(meeting[:End]) { + calculate_average(meeting) + } + end end end From 6b9234d109fe09114860e16ef50c04e6e2c416c9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Jun 2018 23:40:36 +1000 Subject: [PATCH 0562/1752] [People counter] Only schedule in the future --- modules/aca/tracking/people_counter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 59c6783f..b47ade2f 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -29,9 +29,9 @@ def booking_changed(details) self[:todays_bookings] = details logger.info "Got new bookings, clearing schedule" schedule.clear - if Time.parse(meeting[:End]) > Time.now - logger.info "Calculating average at #{meeting[:End]}" - details.each do |meeting| + details.each do |meeting| + if Time.parse(meeting[:End]) > Time.now + logger.info "Calculating average at #{meeting[:End]}" schedule.at(meeting[:End]) { calculate_average(meeting) } From 025b151052eb55a6407189b9cedc062eede2c639 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Jun 2018 00:00:37 +1000 Subject: [PATCH 0563/1752] [People counter] Only schedule in the future --- modules/aca/tracking/people_counter.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index b47ade2f..2e8c97fc 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -66,7 +66,12 @@ def count_changed(new_count) logger.info "Count changed: #{new_count} and ID: #{current[:id]}" # Add the change to the dataset for that meeting - current_dataset = Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") || create_dataset(new_count, current) + current_dataset = Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") + if current_dataset.nil? + current_dataset = create_dataset(new_count, current) + logger.info "Created dataset with ID: #{current_dataset.id}" + logger.info "Created dataset with counts: #{current_dataset.counts}" + end # Check if the new count is max current_dataset.maximum = new_count if new_count > current_dataset.maximum @@ -101,7 +106,6 @@ def create_dataset(count, booking) dataset.median = count dataset.booking_id = booking[:id] dataset.organiser = booking[:owner] - logger.info "Created dataset with ID: #{dataset.id}" return dataset if dataset.save! end From 03f9519680ed7ca25f1a07136cf314e9872bd6bb Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Jun 2018 00:31:29 +1000 Subject: [PATCH 0564/1752] [People counter] Only schedule in the future --- modules/aca/tracking/people_counter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 2e8c97fc..6ff75109 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -119,7 +119,7 @@ def calculate_average(meeting) # Get the dataset dataset = ::Aca::Tracking::PeopleCount.find_by_id("count-#{meeting[:id]}") - events = dataset.counts + events = dataset.counts.dup # Calculate array of weighted durations events.each_with_previous do |prev, curr| From 69da42522c7e5a3e87eb670dce3f314a6a8d64c5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Jun 2018 00:32:50 +1000 Subject: [PATCH 0565/1752] [People counter] Set count to 0 if count is -1 --- modules/aca/tracking/people_counter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 6ff75109..210bc810 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -58,6 +58,7 @@ def get_current_booking(details) end def count_changed(new_count) + new_count = 0 if new_count == -1 return if self[:todays_bookings].nil? || self[:todays_bookings].empty? # Check the current meeting current = get_current_booking(self[:todays_bookings]) From 1d35f54003a35aaa6a317ee2a153336ab299ff7d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 15 Jun 2018 10:16:57 +1000 Subject: [PATCH 0566/1752] Change created meeting name to panel specific --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 80321582..70675e8e 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -462,7 +462,7 @@ def create_meeting(options) req_params = {} req_params[:room_email] = @ews_room - req_params[:subject] = options[:title] + req_params[:subject] = "Quick Book by Display Panel On #{system.name}" req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.iso8601.chop req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.iso8601.chop From aa162c5fa65dda5f41808f17fdb4cb3644d31eab Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 15 Jun 2018 14:31:43 +1000 Subject: [PATCH 0567/1752] Make default field array not shared --- lib/aca/tracking/people_count.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aca/tracking/people_count.rb b/lib/aca/tracking/people_count.rb index 5ff24bef..aae12be8 100644 --- a/lib/aca/tracking/people_count.rb +++ b/lib/aca/tracking/people_count.rb @@ -12,7 +12,7 @@ class Aca::Tracking::PeopleCount < CouchbaseOrm::Base attribute :average, type: Integer attribute :median, type: Integer attribute :organiser, type: String - attribute :counts, type: Array, default: [] + attribute :counts, type: Array, default: lambda { [] } protected From f7eba6eda226cc85ffff46c749ca29656be6f2f9 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Jun 2018 16:09:55 +1000 Subject: [PATCH 0568/1752] (aca:router) add tests for internal graph structure --- modules/aca/router_spec.rb | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 modules/aca/router_spec.rb diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb new file mode 100644 index 00000000..97ec119b --- /dev/null +++ b/modules/aca/router_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +Orchestrator::Testing.mock_device 'Aca::Router' do + def section(message) + puts "\n\n#{'-' * 80}" + puts message + puts "\n" + end + + # ------------------------------------------------------------------------- + section 'Internal graph tests' + + graph = Aca::Router::SignalGraph.new + + # Node insertion + graph << :test_node + expect(graph).to include(:test_node) + + # Node access + expect(graph[:test_node]).to be_a(Aca::Router::SignalGraph::Node) + expect { graph[:does_not_exist] }.to raise_error(ArgumentError) + + # Node deletion + graph.delete :test_node + expect(graph).not_to include(:test_node) + + # Edge creation + graph << :display + graph << :laptop + graph.join(:display, :laptop) do |edge| + edge.device = :Display_1 + edge.input = :hdmi + end + expect(graph.successors(:display)).to include(graph[:laptop]) + + # Graph structural inspection + # note: signal flow is inverted from graph directivity + expect(graph.indegree(:display)).to be(0) + expect(graph.indegree(:laptop)).to be(1) + expect(graph.outdegree(:display)).to be(1) + expect(graph.outdegree(:laptop)).to be(0) + expect(graph.sources.map(&:id)).to include(:display) + expect(graph.sinks.map(&:id)).to include(:laptop) + + # Edge inspection + edge = graph[:display].edges[:laptop] + expect(edge.device).to be(:Display_1) + expect(edge.input).to be(:hdmi) + expect(edge).to be_nx1 + expect(edge).not_to be_nxn + + # Creation from a signal map + graph = Aca::Router::SignalGraph.from_map( + Display_1: { + hdmi: :Switcher_1__1 + }, + Display_2: { + hdmi: :Switcher_1__2 + }, + Display_3: { + display_port: :Switcher_2 + }, + Switcher_1: [:Laptop_1, :Laptop_2, :Switcher_2], + Switcher_2: { + usbc: :Laptop_3, + wireless: :Wireless + } + ) + + expect(graph.sources.map(&:id)).to \ + contain_exactly(:Display_1, :Display_2, :Display_3) + + expect(graph.sinks.map(&:id)).to \ + contain_exactly(:Laptop_1, :Laptop_2, :Laptop_3, :Wireless) + + # Path finding + routes = graph.sources.map(&:id).map { |id| [id, graph.dijkstra(id)] }.to_h + expect(routes[:Display_1].distance_to[:Laptop_1]).to be(2) + expect(routes[:Display_1].distance_to[:Laptop_3]).to be(3) + expect(routes[:Display_3].distance_to[:Laptop_1]).to be_infinite + expect(routes[:Display_3].distance_to[:Laptop_3]).to be(2) +end From ce7c906f156f1ceef9487dfa3959ca2b7ea3dd1b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Jun 2018 16:10:42 +1000 Subject: [PATCH 0569/1752] (aca:router) fix issue with parsing signal maps with symbolised keys --- modules/aca/router.rb | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 5ecbbe3c..588ff96f 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -485,9 +485,16 @@ def self.from_map(map) matrix_nodes = [] - to_hash = proc { |x| x.is_a?(Array) ? Hash[(1..x.size).zip x] : x } + connections = map.with_indifferent_access.transform_values! do |inputs| + case inputs + when Array then (1..inputs.size).zip(inputs).to_h + when Hash then inputs + else + raise ArgumentError, 'inputs must be a Hash or Array' + end + end - map.transform_values!(&to_hash).each_pair do |device, inputs| + connections.each_pair do |device, inputs| # Create the node for the signal sink graph << device @@ -501,13 +508,13 @@ def self.from_map(map) # Check is the input is a matrix switcher or multi-output # device (such as a USB switch). - upstream_device, output = source.split '__' + upstream_device, output = source.to_s.split '__' next if output.nil? matrix_nodes |= [upstream_device] # Push in nodes and edges to each matrix input - matrix_inputs = map[upstream_device] + matrix_inputs = connections[upstream_device] matrix_inputs.each_pair do |matrix_input, upstream_source| graph << upstream_source graph.join(source, upstream_source) do |edge| @@ -521,8 +528,8 @@ def self.from_map(map) # Remove any temp 'matrix device nodes' as we now how fully connected # nodes for each input and output. - matrix_nodes.reduce(graph) do |g, node| - g.delete node, check_incoming_edges: false - end + matrix_nodes.each { |id| graph.delete id, check_incoming_edges: false } + + graph end end From fc4d99ba4c7854766e60637d04e2f48d46ab99a8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Jun 2018 18:16:29 +1000 Subject: [PATCH 0570/1752] (aca:router) raise exception if deleting a node that doesn't exist --- modules/aca/router.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 588ff96f..2af3532e 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -374,10 +374,8 @@ def insert(id) # to false to keep this O(1) rather than O(n). Using this flag at any other # time will result a corrupt structure. def delete(id, check_incoming_edges: true) - nodes.except! id - + nodes.delete(id) { raise ArgumentError, "\"#{id}\" does not exist" } each { |node| node.edges.delete id } if check_incoming_edges - self end From 2aa3cd229e80c8ddc068dee38241e61ca138dead Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 18 Jun 2018 23:09:54 +1000 Subject: [PATCH 0571/1752] (aca:router) neaten up parsing from connection map settings --- modules/aca/router.rb | 62 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 2af3532e..556805e6 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -31,29 +31,7 @@ def on_load end def on_update - logger.debug 'building graph from signal map' - - @path_cache = nil - - connections = setting(:connections).transform_values do |inputs| - # Read in numeric inputs as ints (as JSON based settings do not - # allow non-string keys) - if inputs.is_a? Hash - inputs.transform_keys! { |i| Integer(i) rescue i } - else - inputs - end - end - begin - @signal_graph = SignalGraph.from_map(connections).freeze - rescue - logger.error 'invalid connection settings' - end - - # TODO: track active signal source at each node and expose as a hash - self[:nodes] = signal_graph.map(&:id) - self[:inputs] = signal_graph.sinks.map(&:id) - self[:outputs] = signal_graph.sources.map(&:id) + load_from_map setting(:connections) end @@ -167,6 +145,18 @@ def paths end end + def load_from_map(connections) + logger.debug 'building graph from signal map' + + @path_cache = nil + @signal_graph = SignalGraph.from_map(connections).freeze + + # TODO: track active signal source at each node and expose as a hash + self[:nodes] = signal_graph.map(&:id) + self[:inputs] = signal_graph.sinks.map(&:id) + self[:outputs] = signal_graph.sources.map(&:id) + end + # Find the shortest path between between two nodes and return a list of the # nodes which this passes through and their connecting edges. def route(source, sink) @@ -194,7 +184,7 @@ def route(source, sink) [nodes, edges] end - # Find the optimum combined paths requires to route a single source to + # Find the optimum combined paths required to route a single source to # multiple sink devices. def route_many(source, sinks, strict: false) node_exists = proc do |id| @@ -455,6 +445,21 @@ def to_s "{ #{to_a.join ', '} }" end + # Pre-parse a connection map into a normalised nested hash structure for + # parsing into a graph. + def self.normalise(map) + map.with_indifferent_access.transform_values! do |inputs| + case inputs + when Array + (1..inputs.size).zip(inputs).to_h + when Hash + inputs.transform_keys { |x| Integer(x) rescue x } + else + raise ArgumentError, 'inputs must be a Hash or Array' + end + end + end + # Build a signal map from a nested hash of input connectivity. # # `map` should be of the structure @@ -483,14 +488,7 @@ def self.from_map(map) matrix_nodes = [] - connections = map.with_indifferent_access.transform_values! do |inputs| - case inputs - when Array then (1..inputs.size).zip(inputs).to_h - when Hash then inputs - else - raise ArgumentError, 'inputs must be a Hash or Array' - end - end + connections = normalise map connections.each_pair do |device, inputs| # Create the node for the signal sink From db6f50a6a4146f9446a98a1c9ac799a9ee6fb674 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 19 Jun 2018 23:39:22 +1000 Subject: [PATCH 0572/1752] (aca:meeting) allow muting to optionally NOT mute the VC codec bool "disable_vc_mute" --- modules/aca/meeting_room.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index 295e7d6f..09fbc9d1 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -908,6 +908,7 @@ def vc_status_changed(state) end def vc_mute(mute) + return if setting(:disable_vc_mute) vidconf = system[:VidConf] vidconf.mute(mute) unless vidconf.nil? perform_action(mod: :System, func: :vc_mute_actual, args: [mute]) From 34348358eef2612f56e6e31f6087d30d1b545a5f Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 20 Jun 2018 00:00:18 +1000 Subject: [PATCH 0573/1752] (aca:meeting) fix logic of disable_vc_mute setting we still want the Mixer to be muted --- modules/aca/meeting_room.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index 09fbc9d1..c2940afb 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -908,9 +908,8 @@ def vc_status_changed(state) end def vc_mute(mute) - return if setting(:disable_vc_mute) vidconf = system[:VidConf] - vidconf.mute(mute) unless vidconf.nil? + vidconf.mute(mute) unless vidconf.nil? || setting(:disable_vc_mute) perform_action(mod: :System, func: :vc_mute_actual, args: [mute]) end From 9acdbefe0b1265b7e134e384f9236bf0656bbb63 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Jun 2018 11:32:54 +1000 Subject: [PATCH 0574/1752] Update slack modules to support last message time --- modules/aca/slack.rb | 2 ++ modules/aca/slack_concierge.rb | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/modules/aca/slack.rb b/modules/aca/slack.rb index 02cf9209..f2c02fd8 100644 --- a/modules/aca/slack.rb +++ b/modules/aca/slack.rb @@ -66,6 +66,8 @@ def send_message(message_text) User.bucket.set("slack-user-#{thread_id}", user.id) on_message(message.message) end + user.last_message_sent = Time.now.to_i * 1000 + user.save! end def get_historic_messages diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 2c6e285d..f8ec5361 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -32,6 +32,13 @@ def send_message(message_text, thread_id) message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, thread_ts: thread_id, username: 'Concierge' end + def update_last_message_read(email) + authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id + user = User.find_by_email(authority_id, email) + user.last_message_read = Time.now.to_i * 1000 + user.save! + end + def get_threads messages = @client.web_client.channels_history({channel: setting(:channel), oldest: (Time.now - 12.months).to_i, count: 1000})['messages'] messages.delete_if{ |message| @@ -42,6 +49,11 @@ def get_threads if message['username'].include?('(') messages[i]['name'] = message['username'].split(' (')[0] if message.key?('username') messages[i]['email'] = message['username'].split(' (')[1][0..-2] if message.key?('username') + authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id + user = User.find_by_email(authority_id, email) + messages[i]['last_sent'] = user.last_message_sent + messages[i]['last_read'] = user.last_message_read + # update_last_message_read(messages[i]['email']) else messages[i]['name'] = message['username'] end @@ -70,6 +82,12 @@ def get_thread(thread_id) return nil end + def update_read_time(thread_id) + user = User.find(User.bucket.get("slack-user-#{thread_id}", quiet: true)) + user.last_message_read = Time.now.to_i * 1000 + user.save! + end + protected # Create a realtime WS connection to the Slack servers @@ -110,20 +128,31 @@ def create_websocket if data.key?('subtype') && data['subtype'] == 'message_replied' next end + user_email = nil # # This is not a reply if data.key?('thread_ts') + # if data['username'].include?('(') + # user_email = data['username'].split(' (')[1][0..-2] if data.key?('username') + # end get_thread(data['ts']) get_thread(data['thread_ts']) else - logger.info "Adding thread too binding" + logger.info "Adding thread to binding" if data['username'].include?('(') data['name'] = data['username'].split(' (')[0] if data.key?('username') data['email'] = data['username'].split(' (')[1][0..-2] if data.key?('username') + # user_email = data['email'] else data['name'] = data['username'] end messages = self["threads"].dup.unshift(data) self["threads"] = messages + # if user_email + # authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id + # user = User.find_by_email(authority_id, user_email) + # user.last_message_read = Time.now.to_i * 1000 + # user.save! + # end logger.debug "Getting threads! " get_threads end From 02271ef4aabcd398185d20b81ed8c995dd397785 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Jun 2018 11:46:12 +1000 Subject: [PATCH 0575/1752] Fix small syntax issue with slack driver --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f8ec5361..f85a740e 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -50,7 +50,7 @@ def get_threads messages[i]['name'] = message['username'].split(' (')[0] if message.key?('username') messages[i]['email'] = message['username'].split(' (')[1][0..-2] if message.key?('username') authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id - user = User.find_by_email(authority_id, email) + user = User.find_by_email(authority_id, messages[i]['email']) messages[i]['last_sent'] = user.last_message_sent messages[i]['last_read'] = user.last_message_read # update_last_message_read(messages[i]['email']) From 6d2c499fa8ecc9e752e5864521fa711d53d19ae7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Jun 2018 11:50:31 +1000 Subject: [PATCH 0576/1752] Add more message read times to historic message request --- modules/aca/slack.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/aca/slack.rb b/modules/aca/slack.rb index f2c02fd8..43bcf92d 100644 --- a/modules/aca/slack.rb +++ b/modules/aca/slack.rb @@ -87,11 +87,15 @@ def get_historic_messages messages = JSON.parse(response.body)['messages'] { + last_sent: user.last_message_sent + last_read: user.last_message_read thread_id: thread_id, messages: messages } else { + last_sent: user.last_message_sent + last_read: user.last_message_read thread_id: nil, messages: [] } From c2ff746707be6420434e8eb68010e1eeac00eedf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Jun 2018 12:00:16 +1000 Subject: [PATCH 0577/1752] Only update found users --- modules/aca/slack_concierge.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f85a740e..16a9d3cd 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -51,8 +51,10 @@ def get_threads messages[i]['email'] = message['username'].split(' (')[1][0..-2] if message.key?('username') authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id user = User.find_by_email(authority_id, messages[i]['email']) - messages[i]['last_sent'] = user.last_message_sent - messages[i]['last_read'] = user.last_message_read + if !user.nil? + messages[i]['last_sent'] = user.last_message_sent + messages[i]['last_read'] = user.last_message_read + end # update_last_message_read(messages[i]['email']) else messages[i]['name'] = message['username'] From 8a5fda99646efa1ed45d7d07cdbe80fd2035424d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Jun 2018 12:08:47 +1000 Subject: [PATCH 0578/1752] Put last_read in regardless of whether user found --- modules/aca/slack_concierge.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 16a9d3cd..6ecfef9d 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -54,6 +54,9 @@ def get_threads if !user.nil? messages[i]['last_sent'] = user.last_message_sent messages[i]['last_read'] = user.last_message_read + else + messages[i]['last_sent'] = nil + messages[i]['last_read'] = nil end # update_last_message_read(messages[i]['email']) else From 08ec507e95196f8e3cc449a3fa31d4e638d69da7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Jun 2018 12:09:20 +1000 Subject: [PATCH 0579/1752] Syntax fixes --- modules/aca/slack.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aca/slack.rb b/modules/aca/slack.rb index 43bcf92d..8b14a748 100644 --- a/modules/aca/slack.rb +++ b/modules/aca/slack.rb @@ -87,15 +87,15 @@ def get_historic_messages messages = JSON.parse(response.body)['messages'] { - last_sent: user.last_message_sent - last_read: user.last_message_read + last_sent: user.last_message_sent, + last_read: user.last_message_read, thread_id: thread_id, messages: messages } else { - last_sent: user.last_message_sent - last_read: user.last_message_read + last_sent: user.last_message_sent, + last_read: user.last_message_read, thread_id: nil, messages: [] } From d76cc1a85c89802bcf0bd6e81b371646c54d1cc3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Jun 2018 12:35:45 +1000 Subject: [PATCH 0580/1752] Add debugging and logging --- modules/aca/slack_concierge.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 6ecfef9d..2ca76cca 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -11,6 +11,12 @@ class Aca::SlackConcierge generic_name :Slack implements :logic + def log(msg) + logger.info msg + STDERR.puts msg + STDERR.flush + end + def on_load on_update end @@ -50,6 +56,10 @@ def get_threads messages[i]['name'] = message['username'].split(' (')[0] if message.key?('username') messages[i]['email'] = message['username'].split(' (')[1][0..-2] if message.key?('username') authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id + log("GOT AUTHORITY ID") + log(authority_id) + log("GOT EMAIL") + log(messages[i]['email']) user = User.find_by_email(authority_id, messages[i]['email']) if !user.nil? messages[i]['last_sent'] = user.last_message_sent From 6df4b8d4e6c9f50c75d690b2c2ee73fd9ef08ec6 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 20 Jun 2018 21:40:58 +1000 Subject: [PATCH 0581/1752] (sony:projector) add more inputs tested on a VPL-FHZ65 --- modules/sony/projector/serial_control.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/sony/projector/serial_control.rb b/modules/sony/projector/serial_control.rb index 8ed39118..d595e7fa 100755 --- a/modules/sony/projector/serial_control.rb +++ b/modules/sony/projector/serial_control.rb @@ -62,11 +62,13 @@ def power?(**options, &block) # Input selection # INPUTS = { - hdmi: [0x00, 0x03], - hdmi2: [0x00, 0x03], + hdmi: [0x00, 0x03], #aka inputb inputa: [0x00, 0x02], inputb: [0x00, 0x03], - inputc: [0x00, 0x04] + inputc: [0x00, 0x04], + inputd: [0x00, 0x05], + usb: [0x00, 0x06], # usb type B + network: [0x00, 0x07] # network } INPUTS.merge!(INPUTS.invert) From 8050ec787f265515fe4e51958388bcbeb9081502 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 21 Jun 2018 12:27:59 +1000 Subject: [PATCH 0582/1752] (aca:router) provide support for segmenting devices --- modules/aca/router.rb | 55 ++++++++++++--- modules/aca/router_spec.rb | 140 ++++++++++++++++++++++++++++++------- 2 files changed, 161 insertions(+), 34 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 556805e6..fd14e302 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -445,8 +445,15 @@ def to_s "{ #{to_a.join ', '} }" end - # Pre-parse a connection map into a normalised nested hash structure for - # parsing into a graph. + # Pre-parse a connection map into a normalised nested hash structure + # suitable for parsing into the graph. + # + # This assums the input map has come from passed JSON so takes care of + # mapping keys back to integers (where suitable) and expanding sources + # specified as an array into a nested Hash. The target normalised output is + # + # { output: { device_input: source } } + # def self.normalise(map) map.with_indifferent_access.transform_values! do |inputs| case inputs @@ -460,16 +467,36 @@ def self.normalise(map) end end - # Build a signal map from a nested hash of input connectivity. + # Extract module references from a connection map. + # + # This is a destructive operation that will tranform outputs specified as + # `device as output` to simply `output` and return a Hash of the structure + # `{ output: device }`. + def self.extract_mods!(map) + mods = HashWithIndifferentAccess.new + + map.transform_keys! do |key| + mod, node = key.to_s.split ' as ' + node ||= mod + mods[node] = mod.to_sym + node + end + + mods + end + + # Build a signal map from a nested hash of input connectivity. The input + # map should be of the structure # - # `map` should be of the structure # { device: { input_name: source } } # or # { device: [source] } # # When inputs are specified as an array, 1-based indicies will be used. # - # Sources which exist on matrix switchers are defined as "device__output". + # Sources that refer to the output of a matrix switcher are defined as + # "device__output" (using two underscores to seperate the output + # name/number and device). # # For example, a map containing two displays and 2 laptop inputs, all # connected via 2x2 matrix switcher would be: @@ -480,9 +507,15 @@ def self.normalise(map) # Display_2: { # hdmi: :Switcher_1__2 # }, - # Switcher_1: [:Laptop_1, :Laptop_2] + # Switcher_1: [:Laptop_1, :Laptop_2], # } # + # Device keys should relate to module id's for control. These may also be + # aliased by defining them as as "mod as device". This can be used to + # provide better readability (e.g. "Display_1 as Left_LCD") or to segment + # them so that only specific routes are allowed. This approach enables + # devices such as centralised matrix switchers split into multiple virtual + # switchers that only have access to a subset of the inputs. def self.from_map(map) graph = new @@ -490,6 +523,8 @@ def self.from_map(map) connections = normalise map + mods = extract_mods! connections + connections.each_pair do |device, inputs| # Create the node for the signal sink graph << device @@ -498,8 +533,8 @@ def self.from_map(map) # Create a node and edge to each input source graph << source graph.join(device, source) do |edge| - edge.device = device - edge.input = input + edge.device = mods[device] + edge.input = input end # Check is the input is a matrix switcher or multi-output @@ -514,8 +549,8 @@ def self.from_map(map) matrix_inputs.each_pair do |matrix_input, upstream_source| graph << upstream_source graph.join(source, upstream_source) do |edge| - edge.device = upstream_device - edge.input = matrix_input + edge.device = mods[upstream_device] + edge.input = matrix_input edge.output = output end end diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index 97ec119b..75e4db2d 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'json' + Orchestrator::Testing.mock_device 'Aca::Router' do def section(message) puts "\n\n#{'-' * 80}" @@ -7,17 +9,19 @@ def section(message) puts "\n" end + SignalGraph = Aca::Router::SignalGraph + # ------------------------------------------------------------------------- - section 'Internal graph tests' + section 'Internal graph structure' - graph = Aca::Router::SignalGraph.new + graph = SignalGraph.new # Node insertion graph << :test_node expect(graph).to include(:test_node) # Node access - expect(graph[:test_node]).to be_a(Aca::Router::SignalGraph::Node) + expect(graph[:test_node]).to be_a(SignalGraph::Node) expect { graph[:does_not_exist] }.to raise_error(ArgumentError) # Node deletion @@ -49,34 +53,122 @@ def section(message) expect(edge).to be_nx1 expect(edge).not_to be_nxn - # Creation from a signal map - graph = Aca::Router::SignalGraph.from_map( - Display_1: { - hdmi: :Switcher_1__1 + + # ------------------------------------------------------------------------- + section 'Parse from signal map' + + signal_map = JSON.parse <<-JSON + { + "Display_1 as Left_LCD": { + "hdmi": "Switcher_1__1", + "hdmi2": "SubSwitchA__1" + }, + "Display_2 as Right_LCD": { + "hdmi": "Switcher_1__2", + "hdmi2": "SubSwitchB__2", + "display_port": "g" + }, + "Switcher_1": ["a", "b"], + "Switcher_2 as SubSwitchA": { + "1": "c", + "2": "d" + }, + "Switcher_2 as SubSwitchB": { + "3": "e", + "4": "f" + } + } + JSON + + normalised_map = SignalGraph.normalise(signal_map) + expect(normalised_map).to eq( + 'Display_1 as Left_LCD' => { + 'hdmi' => 'Switcher_1__1', + 'hdmi2' => 'SubSwitchA__1' + }, + 'Display_2 as Right_LCD' => { + 'hdmi' => 'Switcher_1__2', + 'hdmi2' => 'SubSwitchB__2', + 'display_port' => 'g' + }, + 'Switcher_1' => { + 1 => 'a', + 2 => 'b' + }, + 'Switcher_2 as SubSwitchA' => { + 1 => 'c', + 2 => 'd' + }, + 'Switcher_2 as SubSwitchB' => { + 3 => 'e', + 4 => 'f' + } + ) + + mods = SignalGraph.extract_mods!(normalised_map) + expect(mods).to eq( + 'Left_LCD' => :Display_1, + 'Right_LCD' => :Display_2, + 'Switcher_1' => :Switcher_1, + 'SubSwitchA' => :Switcher_2, + 'SubSwitchB' => :Switcher_2 + ) + expect(normalised_map).to eq( + 'Left_LCD' => { + 'hdmi' => 'Switcher_1__1', + 'hdmi2' => 'SubSwitchA__1' }, - Display_2: { - hdmi: :Switcher_1__2 + 'Right_LCD' => { + 'hdmi' => 'Switcher_1__2', + 'hdmi2' => 'SubSwitchB__2', + 'display_port' => 'g' }, - Display_3: { - display_port: :Switcher_2 + 'Switcher_1' => { + 1 => 'a', + 2 => 'b' }, - Switcher_1: [:Laptop_1, :Laptop_2, :Switcher_2], - Switcher_2: { - usbc: :Laptop_3, - wireless: :Wireless + 'SubSwitchA' => { + 1 => 'c', + 2 => 'd' + }, + 'SubSwitchB' => { + 3 => 'e', + 4 => 'f' } ) - expect(graph.sources.map(&:id)).to \ - contain_exactly(:Display_1, :Display_2, :Display_3) + graph = SignalGraph.from_map(signal_map) + + expect(graph.sources.map(&:id)).to contain_exactly(:Left_LCD, :Right_LCD) - expect(graph.sinks.map(&:id)).to \ - contain_exactly(:Laptop_1, :Laptop_2, :Laptop_3, :Wireless) + expect(graph.sinks.map(&:id)).to contain_exactly(*(:a..:g).to_a) - # Path finding routes = graph.sources.map(&:id).map { |id| [id, graph.dijkstra(id)] }.to_h - expect(routes[:Display_1].distance_to[:Laptop_1]).to be(2) - expect(routes[:Display_1].distance_to[:Laptop_3]).to be(3) - expect(routes[:Display_3].distance_to[:Laptop_1]).to be_infinite - expect(routes[:Display_3].distance_to[:Laptop_3]).to be(2) + expect(routes[:Left_LCD].distance_to[:a]).to be(2) + expect(routes[:Left_LCD].distance_to[:c]).to be(2) + expect(routes[:Left_LCD].distance_to[:e]).to be_infinite + expect(routes[:Left_LCD].distance_to[:g]).to be_infinite + expect(routes[:Right_LCD].distance_to[:g]).to be(1) + expect(routes[:Right_LCD].distance_to[:a]).to be(2) + expect(routes[:Right_LCD].distance_to[:g]).to be(1) + expect(routes[:Right_LCD].distance_to[:c]).to be_infinite + + + # ------------------------------------------------------------------------- + section 'Module methods' + + exec(:load_from_map, signal_map) + + exec(:route, :a, :Left_LCD) + nodes, = result + nodes.map!(&:id) + expect(nodes).to contain_exactly(:Left_LCD, :Switcher_1__1, :a) + + exec(:route, :c, :Left_LCD) + nodes, = result + nodes.map!(&:id) + expect(nodes).to contain_exactly(:Left_LCD, :SubSwitchA__1, :c) + + expect { exec(:route, :e, :Left_LCD) }.to \ + raise_error('no route from e to Left_LCD') end From 3cc0725596569d31890839928f7d970178debeab Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Jun 2018 15:31:00 +1000 Subject: [PATCH 0583/1752] [Office365 library] Always get a new token --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 91ec4b3f..709cd420 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -47,13 +47,13 @@ def initialize( end def graph_token - @graph_token ||= @graph_client.client_credentials.get_token({ + @graph_token = @graph_client.client_credentials.get_token({ :scope => @app_scope }).token end def password_graph_token - @graph_token ||= @graph_client.password.get_token( + @graph_token = @graph_client.password.get_token( @service_account_email, @service_account_password, { From c1881f451b848bcc770b39c0ad07e2928d8aa896 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 21 Jun 2018 16:10:56 +1000 Subject: [PATCH 0584/1752] (aca:router) fix issue where node and edge ordering were inverted --- modules/aca/router.rb | 2 +- modules/aca/router_spec.rb | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index fd14e302..517d51e0 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -173,7 +173,7 @@ def route(source, sink) edges = [] node = signal_graph[source] until node.nil? - nodes << node + nodes.unshift node predecessor = path.predecessor[node.id] edges << predecessor.edges[node.id] unless predecessor.nil? node = predecessor diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index 75e4db2d..bb820a1c 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -155,19 +155,24 @@ def section(message) # ------------------------------------------------------------------------- - section 'Module methods' + section 'Routing' exec(:load_from_map, signal_map) exec(:route, :a, :Left_LCD) - nodes, = result - nodes.map!(&:id) - expect(nodes).to contain_exactly(:Left_LCD, :Switcher_1__1, :a) + nodes, edges = result + expect(nodes.map(&:id)).to contain_exactly(:a, :Switcher_1__1, :Left_LCD) + expect(edges.first).to be_nxn + expect(edges.first.device).to be(:Switcher_1) + expect(edges.first.input).to be(1) + expect(edges.first.output).to be(1) + expect(edges.second).to be_nx1 + expect(edges.second.device).to be(:Display_1) + expect(edges.second.input).to be(:hdmi) exec(:route, :c, :Left_LCD) nodes, = result - nodes.map!(&:id) - expect(nodes).to contain_exactly(:Left_LCD, :SubSwitchA__1, :c) + expect(nodes.map(&:id)).to contain_exactly(:c, :SubSwitchA__1, :Left_LCD) expect { exec(:route, :e, :Left_LCD) }.to \ raise_error('no route from e to Left_LCD') From 28d49d4db16466552a49ad9b56602ebffcdde92d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Jun 2018 16:18:43 +1000 Subject: [PATCH 0585/1752] [Office Booking] Fix timezone issues with fetch_bookings --- modules/aca/office_booking.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 57e9d2ed..bcc0c007 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -612,8 +612,10 @@ def todays_bookings(response, office_organiser_location) response.each{|booking| # start_time = Time.parse(booking['start']['dateTime']).utc.iso8601[0..18] + 'Z' # end_time = Time.parse(booking['end']['dateTime']).utc.iso8601[0..18] + 'Z' - start_time = ActiveSupport::TimeZone.new('UTC').parse(booking['start']['dateTime']).iso8601 - end_time = ActiveSupport::TimeZone.new('UTC').parse(booking['end']['dateTime']).iso8601 + if booking['start'].key?("timeZone") + start_time = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']).utc.iso8601 + end_time = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['end']['dateTime']).utc.iso8601 + end if office_organiser_location == 'attendees' # Grab the first attendee From 60a3f23ea7d79de76e01ef49d4beebd8d5483d50 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 21 Jun 2018 16:22:06 +1000 Subject: [PATCH 0586/1752] (aca:router) update comments --- modules/aca/router.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 517d51e0..8d2ccee5 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -448,11 +448,11 @@ def to_s # Pre-parse a connection map into a normalised nested hash structure # suitable for parsing into the graph. # - # This assums the input map has come from passed JSON so takes care of + # This assumes the input map has been parsed from JSON so takes care of # mapping keys back to integers (where suitable) and expanding sources # specified as an array into a nested Hash. The target normalised output is # - # { output: { device_input: source } } + # { device: { input: source } } # def self.normalise(map) map.with_indifferent_access.transform_values! do |inputs| @@ -492,7 +492,7 @@ def self.extract_mods!(map) # or # { device: [source] } # - # When inputs are specified as an array, 1-based indicies will be used. + # When inputs are specified as an array, 1-based indices will be used. # # Sources that refer to the output of a matrix switcher are defined as # "device__output" (using two underscores to seperate the output From 73c4a6a406aa8a59f09330d5bd179a15fc42b353 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 21 Jun 2018 16:22:40 +1000 Subject: [PATCH 0587/1752] (aca:router) fix issue where outputs were stored as strings --- modules/aca/router.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 8d2ccee5..53b1b55f 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -290,9 +290,16 @@ def initialize(source, target, &blk) @target = target meta = Meta.new.tap(&blk) + normalise_io = lambda do |x| + if x.is_a? String + x[/^\d+$/]&.to_i || x.to_sym + else + x + end + end @device = meta.device&.to_sym - @input = meta.input.try(:to_sym) || meta.input - @output = meta.output.try(:to_sym) || meta.output + @input = normalise_io[meta.input] + @output = normalise_io[meta.output] end def to_s From 35f074ae81f355fc1c2c38422eca43b6f65621f0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Jun 2018 16:32:07 +1000 Subject: [PATCH 0588/1752] [O365 lib] Add current_user only if exists --- lib/microsoft/office.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 709cd420..91128c2a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -365,15 +365,25 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil locationEmailAddress: room.email }, isOrganizer: false, - organizer: { + attendees: attendees + } + + if current_user + event[:organizer] = { emailAddress: { address: current_user.email, name: current_user.name } - }, - attendees: attendees - } - + } + else + event[:organizer] = { + emailAddress: { + address: room.email, + name: room.name + } + } + end + if recurrence event[:recurrence] = { pattern: { From 254d9becfbbb41c53d72bdf0967de13a9dc989ac Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Jun 2018 16:33:14 +1000 Subject: [PATCH 0589/1752] [O365 Driver] Remove attendees if no organizer --- modules/aca/office_booking.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index bcc0c007..3f6b8719 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -520,6 +520,11 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em attendees: [ emailAddress: { address: organizer, name: "User"}] }.to_json + + if organizer.nil? + booking_data[:attendees] = [] + end + logger.debug "Creating booking:" logger.debug booking_data From 23438008eaf4ceae5adbd92343f3453325aca81e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Jun 2018 16:34:53 +1000 Subject: [PATCH 0590/1752] [O365 Driver] Remove attendees if no organizer --- modules/aca/office_booking.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 3f6b8719..fcb73a6c 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -518,13 +518,15 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em end: { dateTime: end_time, timeZone: "UTC" }, location: { displayName: @office_room, locationEmailAddress: @office_room }, attendees: [ emailAddress: { address: organizer, name: "User"}] - }.to_json + } + - if organizer.nil? booking_data[:attendees] = [] end + booking_data = booking_data.to_json + logger.debug "Creating booking:" logger.debug booking_data From 3d1ce1d0c3bdef71ebfe4382ec696775f8db0e2f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Jun 2018 16:38:09 +1000 Subject: [PATCH 0591/1752] [O365 Driver] Remove attendees if no organizer --- modules/aca/office_booking.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index fcb73a6c..de2486fe 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -512,6 +512,17 @@ def get_attr(entry, attr_name) # ======================================= def make_office_booking(user_email: nil, subject: 'On the spot booking', room_email:, start_time:, end_time:, organizer:) + STDERR.puts organizer + logger.info organizer + + STDERR.puts organizer.class + logger.info organizer.class + + STDERR.puts organizer.nil? + logger.info organizer.nil? + + STDERR.flush + booking_data = { subject: subject, start: { dateTime: start_time, timeZone: "UTC" }, @@ -561,7 +572,7 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em # Make the request # response = office_api.post(path: "#{domain}#{endpoint}", body: booking_data, headers: headers).value - response = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: {email: organizer, name: "User"}) + response = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: nil) logger.debug response.body logger.debug response.to_json logger.debug response['id'] From b088c9a158205c56015cfecc64854a9a1127b5e6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Jun 2018 16:39:07 +1000 Subject: [PATCH 0592/1752] [O365 Lib] Only add c_u if present --- lib/microsoft/office.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 91128c2a..3c955ccf 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -338,12 +338,14 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil }) # Add the current user as an attendee - attendees.push({ - emailAddress: { - address: current_user[:email], - name: current_user[:name] - } - }) + if current_user + attendees.push({ + emailAddress: { + address: current_user[:email], + name: current_user[:name] + } + }) + end # Create our event which will eventually be stringified event = { From dd85996212f5962b260341701aa77bf96f66e401 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 25 Jun 2018 16:34:45 +1000 Subject: [PATCH 0593/1752] Start current meeting on load for exchange driver --- modules/aca/exchange_booking.rb | 121 ++++++++++++++------------------ 1 file changed, 53 insertions(+), 68 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 70675e8e..212b1043 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -204,8 +204,12 @@ def on_update end schedule.clear - schedule.in(rand(10000)) { fetch_bookings } - schedule.every((setting(:update_every) || 120000).to_i + rand(10000)) { fetch_bookings } + schedule.in(rand(10000)) { + fetch_bookings + } + schedule.every((setting(:update_every) || 120000).to_i + rand(10000)) { + fetch_bookings + } end @@ -349,10 +353,10 @@ def order_complete # ====================================== # ROOM BOOKINGS: # ====================================== - def fetch_bookings(*args) + def fetch_bookings(first=false) logger.debug { "looking up todays emails for #{@ews_room}" } task { - todays_bookings + todays_bookings(first) }.then(proc { |bookings| self[:today] = bookings if @check_meeting_ending @@ -367,11 +371,11 @@ def fetch_bookings(*args) # ====================================== def start_meeting(meeting_ref) - self[:last_meeting_started] = meeting_ref - self[:meeting_pending] = meeting_ref - self[:meeting_ending] = false - self[:meeting_pending_notice] = false - define_setting(:last_meeting_started, meeting_ref) + self[:last_meeting_started] = meeting_ref + self[:meeting_pending] = meeting_ref + self[:meeting_ending] = false + self[:meeting_pending_notice] = false + define_setting(:last_meeting_started, meeting_ref) end def cancel_meeting(start_time) @@ -401,7 +405,6 @@ def cancel_meeting(start_time) # If last meeting started !== meeting pending then # we'll show a warning on the in room touch panel def set_meeting_pending(meeting_ref) - return if self[:last_meeting_started] == meeting_ref self[:meeting_ending] = false self[:meeting_pending] = meeting_ref self[:meeting_pending_notice] = true @@ -423,30 +426,8 @@ def set_end_meeting_warning(meeting_ref = nil, extendable = false) def clear_end_meeting_warning self[:meeting_ending] = self[:last_meeting_started] end - - def log(msg) - STDERR.puts msg - logger.info msg - STDERR.flush - end # --------- - def check_conflict(start_time) - - items = get_todays_bookings - - conflict = false - items.each do |meeting| - meeting_start = Time.parse(meeting.ews_item[:start][:text]).to_i - meeting_end = Time.parse(meeting.ews_item[:end][:text]).to_i - # Remove any meetings that match the start time provided - if start_time >= meeting_start && start_time < meeting_end - conflict = true - end - end - conflict - end - def create_meeting(options) # Check that the required params exist required_fields = [:start, :end] @@ -457,12 +438,9 @@ def create_meeting(options) raise "missing required fields: #{check}" end - # Check for existing booking that hasn't been fetched yet - raise "Booking conflict" if check_conflict((options[:start].to_i / 1000)) - req_params = {} req_params[:room_email] = @ews_room - req_params[:subject] = "Quick Book by Display Panel On #{system.name}" + req_params[:subject] = options[:title] req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.iso8601.chop req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.iso8601.chop @@ -535,13 +513,10 @@ def extend_meeting return false unless starting ending = starting + @extend_meeting_by - create_meeting(start: starting * 1000, end: ending * 1000, title: @current_meeting_title).then do - start_meeting(starting * 1000) - end + create_meeting start: starting * 1000, end: ending * 1000, title: @current_meeting_title end - protected @@ -660,8 +635,33 @@ def make_ews_booking(user_email: nil, subject: 'On the spot booking', room_email end def delete_ews_booking(delete_at) + now = Time.now + if @timezone + start = now.in_time_zone(@timezone).midnight + ending = now.in_time_zone(@timezone).tomorrow.midnight + else + start = now.midnight + ending = now.tomorrow.midnight + end + count = 0 - items = get_todays_bookings + + cli = Viewpoint::EWSClient.new(*@ews_creds) + + if @use_act_as + # TODO:: think this line can be removed?? + # delete_at = Time.parse(delete_at.to_s).to_i + + opts = {} + opts[:act_as] = @ews_room if @ews_room + + folder = cli.get_folder(:calendar, opts) + items = folder.items({:calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) + else + cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room + items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) + end + items.each do |meeting| meeting_time = Time.parse(meeting.ews_item[:start][:text]) @@ -676,8 +676,7 @@ def delete_ews_booking(delete_at) count end - - def get_todays_bookings + def todays_bookings(first=false) now = Time.now if @timezone start = now.in_time_zone(@timezone).midnight @@ -687,13 +686,10 @@ def get_todays_bookings ending = now.tomorrow.midnight end - cli = Viewpoint::EWSClient.new(*@ews_creds) + if @use_act_as - # TODO:: think this line can be removed?? - # delete_at = Time.parse(delete_at.to_s).to_i - opts = {} opts[:act_as] = @ews_room if @ews_room @@ -703,13 +699,6 @@ def get_todays_bookings cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) end - items - end - - - def todays_bookings - items = get_todays_bookings - now = Time.now skype_exists = set_skype_url = system.exists?(:Skype) set_skype_url = true if @force_skype_extract @@ -720,14 +709,6 @@ def todays_bookings item = meeting.ews_item start = item[:start][:text] ending = item[:end][:text] - all_day_bookings = setting(:hide_all_day_bookings) || false - if all_day_bookings - if (Time.parse(ending) - Time.parse(start)).to_i > 86399 - STDERR.puts "SKIPPING #{item[:subject][:text]}" - STDERR.flush - next - end - end real_start = Time.parse(start) real_end = Time.parse(ending) @@ -739,6 +720,9 @@ def todays_bookings end_integer = real_end.to_i - @skype_end_offset if now_int > start_integer && now_int < end_integer + if first + self[:last_meeting_started] = start_integer + end meeting.get_all_properties! if meeting.body @@ -775,14 +759,16 @@ def todays_bookings # Prevent connections handing with TIME_WAIT # cli.ews.connection.httpcli.reset_all + subject = item[:subject] + - # Set subject to private if sensitive - if ['private', 'confidential'].include?(meeting.sensitivity.downcase) || item[:subject].empty? + if ['private', 'confidential'].include?(meeting.sensitivity.downcase) || subject.nil? || subject.empty? subject = "Private" else - subject = item[:subject][:text] + subject = subject[:text] end + { :Start => start, :End => ending, @@ -791,8 +777,7 @@ def todays_bookings :setup => 0, :breakdown => 0, :start_epoch => real_start.to_i, - :end_epoch => real_end.to_i, - :id => meeting.id + :end_epoch => real_end.to_i } end @@ -802,7 +787,7 @@ def todays_bookings system[:Skype].set_uri(nil) if skype_exists end - results.compact + results end # ======================================= end From 18a515372556c787c8f6eac3e5c02be6f0e25aab Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 25 Jun 2018 16:49:58 +1000 Subject: [PATCH 0594/1752] Actually pass first flag to fetch_bookings --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 212b1043..6676f3d2 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -205,7 +205,7 @@ def on_update schedule.clear schedule.in(rand(10000)) { - fetch_bookings + fetch_bookings(true) } schedule.every((setting(:update_every) || 120000).to_i + rand(10000)) { fetch_bookings From 1234f38f262b9e6a916b0af7f6cb8b9f3a3b04ce Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 25 Jun 2018 16:50:23 +1000 Subject: [PATCH 0595/1752] Actually pass first flag to fetch_bookings --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 6676f3d2..581165a9 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -721,7 +721,7 @@ def todays_bookings(first=false) if now_int > start_integer && now_int < end_integer if first - self[:last_meeting_started] = start_integer + self[:last_meeting_started] = start_integer * 1000 end meeting.get_all_properties! From b220faa91cbb7cb08795b1713da30fe24640b214 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 25 Jun 2018 16:51:18 +1000 Subject: [PATCH 0596/1752] Actually pass first flag to fetch_bookings --- modules/aca/exchange_booking.rb | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 581165a9..21305f86 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -99,6 +99,7 @@ def on_update self[:description] = setting(:description) || nil self[:title] = setting(:title) || nil self[:timeout] = setting(:timeout) || false + self[:endable] = setting(:endable) || false self[:control_url] = setting(:booking_control_url) || system.config.support_url self[:booking_controls] = setting(:booking_controls) @@ -704,7 +705,7 @@ def todays_bookings(first=false) set_skype_url = true if @force_skype_extract now_int = now.to_i - items.select! { |booking| !booking.cancelled? } + # items.select! { |booking| !booking.cancelled? } results = items.collect do |meeting| item = meeting.ews_item start = item[:start][:text] @@ -759,16 +760,7 @@ def todays_bookings(first=false) # Prevent connections handing with TIME_WAIT # cli.ews.connection.httpcli.reset_all - subject = item[:subject] - - - if ['private', 'confidential'].include?(meeting.sensitivity.downcase) || subject.nil? || subject.empty? - subject = "Private" - else - subject = subject[:text] - end - - + subject = item[:subject][:text] { :Start => start, :End => ending, From 7ad5dd088012a3d6e4469868e72cd357b98610bb Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 27 Jun 2018 09:42:19 +1000 Subject: [PATCH 0597/1752] (aca:router) improve warning messages --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 53b1b55f..7e0b2db0 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -230,7 +230,7 @@ def can_activate?(edge) mod = system[edge.device] fail_with = proc do |reason| - logger.warn "#{edge.device} #{reason} - can not switch #{edge}" + logger.warn "mod #{edge.device} #{reason} - can not switch #{edge}" return false end From 6941d5f0abdee4d0dc3b1cd9b1677593ac180caa Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 27 Jun 2018 12:01:17 +1000 Subject: [PATCH 0598/1752] [Exchange Driver] Update to use alexs new settings --- modules/aca/exchange_booking.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 21305f86..ebf8f5b1 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -99,7 +99,8 @@ def on_update self[:description] = setting(:description) || nil self[:title] = setting(:title) || nil self[:timeout] = setting(:timeout) || false - self[:endable] = setting(:endable) || false + self[:booking_endable] = setting(:booking_endable) || false + self[:booking_ask_end] = setting(:booking_ask_end) || false self[:control_url] = setting(:booking_control_url) || system.config.support_url self[:booking_controls] = setting(:booking_controls) @@ -355,7 +356,7 @@ def order_complete # ROOM BOOKINGS: # ====================================== def fetch_bookings(first=false) - logger.debug { "looking up todays emails for #{@ews_room}" } + logger.debug { "looking up todays emails for #{@ews_room}" }z task { todays_bookings(first) }.then(proc { |bookings| From eacbe591f9bd10632e8e1a921eae43b8e4547c09 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 27 Jun 2018 12:05:00 +1000 Subject: [PATCH 0599/1752] Fix typo --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index ebf8f5b1..df6e3af4 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -356,7 +356,7 @@ def order_complete # ROOM BOOKINGS: # ====================================== def fetch_bookings(first=false) - logger.debug { "looking up todays emails for #{@ews_room}" }z + logger.debug { "looking up todays emails for #{@ews_room}" } task { todays_bookings(first) }.then(proc { |bookings| From 6a83129642fa2b1f8d90785ad74f1256db9399af Mon Sep 17 00:00:00 2001 From: Cam Reeves Date: Wed, 27 Jun 2018 15:25:16 +1000 Subject: [PATCH 0600/1752] Remove logging --- modules/aca/slack_concierge.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 2ca76cca..40164932 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -56,10 +56,6 @@ def get_threads messages[i]['name'] = message['username'].split(' (')[0] if message.key?('username') messages[i]['email'] = message['username'].split(' (')[1][0..-2] if message.key?('username') authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id - log("GOT AUTHORITY ID") - log(authority_id) - log("GOT EMAIL") - log(messages[i]['email']) user = User.find_by_email(authority_id, messages[i]['email']) if !user.nil? messages[i]['last_sent'] = user.last_message_sent From 016726a9873be47ab53642b5c0bb5b276a72c1c2 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 28 Jun 2018 10:21:41 +1000 Subject: [PATCH 0601/1752] (aca:exchange bookings) ensure meetings are started properly --- modules/aca/exchange_booking.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index df6e3af4..f9e3c49a 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -407,6 +407,7 @@ def cancel_meeting(start_time) # If last meeting started !== meeting pending then # we'll show a warning on the in room touch panel def set_meeting_pending(meeting_ref) + return if self[:last_meeting_started] == meeting_ref self[:meeting_ending] = false self[:meeting_pending] = meeting_ref self[:meeting_pending_notice] = true @@ -515,7 +516,9 @@ def extend_meeting return false unless starting ending = starting + @extend_meeting_by - create_meeting start: starting * 1000, end: ending * 1000, title: @current_meeting_title + create_meeting(start: starting * 1000, end: ending * 1000, title: @current_meeting_title).then do + start_meeting(starting * 1000) + end end From 6d4e824452e017ee2367e418adc940b7c262d361 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 29 Jun 2018 14:30:28 +1000 Subject: [PATCH 0602/1752] (tvone:coriomaster) support symbolised window names in signal map --- modules/tv_one/corio_master.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index deea831d..af5ac588 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -48,7 +48,7 @@ def preset(id) def switch(signal_map) interactions = signal_map.flat_map do |slot, windows| Array(windows).map do |id| - id = id[/\d+/].to_i if id.is_a? String + id = id.to_s[/\d+/].to_i unless id.is_a? Integer window id, 'Input', slot end end From e63fe03e7674b0bb652027670c6411fdce1d9139 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 1 Jul 2018 12:43:51 +1000 Subject: [PATCH 0603/1752] (QSC:remote) freeze not required on string literal --- modules/qsc/q_sys_remote.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index 4cbf2d55..0ff0101d 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -18,7 +18,7 @@ class Qsc::QSysRemote tokenize delimiter: "\0" - JsonRpcVer = '2.0'.freeze + JsonRpcVer = '2.0' Errors = { -32700 => 'Parse error. Invalid JSON was received by the server.', -32600 => 'Invalid request. The JSON sent is not a valid Request object.', @@ -330,7 +330,7 @@ def mute(fader_id, value = true, component = nil, type = :fader) def mutes(ids:, muted: true, component: nil, type: :fader, **_) mute(ids, muted, component, type) end - + def unmute(fader_id, component = nil, type = :fader) mute(fader_id, false, component, type) end From 39bda80a3830d40e5ef0dbe7311b281b60d23ca3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 2 Jul 2018 10:55:07 +1000 Subject: [PATCH 0604/1752] [Exchange lib] Access current user by brackets as may not be a model --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 1d285a99..24f50a21 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -272,7 +272,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: # booking[:body] = description booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop booking[:required_attendees] = [{ - attendee: { mailbox: { email_address: current_user.email } } + attendee: { mailbox: { email_address: current_user[:email] } } }] attendees.each do |attendee| if attendee.class != String @@ -292,7 +292,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: if use_act_as folder = @ews_client.get_folder(:calendar, { act_as: room_email }) else - @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user.email) + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user[:email]) folder = @ews_client.get_folder(:calendar) end appointment = folder.create_item(booking) From ff643cff3df25902621ed51b63acbfbb26ec6e11 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 2 Jul 2018 11:05:02 +1000 Subject: [PATCH 0605/1752] [Exchange lib] Add organisers name if present --- lib/microsoft/exchange.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 24f50a21..caee6c98 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -272,7 +272,9 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: # booking[:body] = description booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop booking[:required_attendees] = [{ - attendee: { mailbox: { email_address: current_user[:email] } } + mailbox = { email_address: current_user[:email] } + mailbox[:name] = current_user[:name] if current_user.key?('name') + attendee: { mailbox: mailbox } }] attendees.each do |attendee| if attendee.class != String From fa643cec7937099f36c628f1ccfaa2f3725f07a4 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 2 Jul 2018 15:34:31 +1000 Subject: [PATCH 0606/1752] Change use_act_as to be a string called permission --- lib/microsoft/exchange.rb | 71 +++++++++++++++------------------------ 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index caee6c98..c827ae37 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -242,7 +242,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. end end - def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney', use_act_as: false) + def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney', permission: 'impersonation') STDERR.puts "CREATING NEW BOOKING IN LIBRARY" STDERR.puts "room_email is #{room_email}" STDERR.puts "start_param is #{start_param}" @@ -258,9 +258,12 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: booking = {} + + # Allow for naming of subject or title booking[:subject] = subject booking[:title] = subject - # booking[:location] = room_email + + # Set the room email as a resource booking[:resources] = [{ attendee: { mailbox: { @@ -268,15 +271,21 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: } } }] + + # Add start and end times booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop - # booking[:body] = description booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop + + # Add the current user passed in as an attendee + mailbox = { email_address: current_user[:email] } + mailbox[:name] = current_user[:name] if current_user.key?('name') booking[:required_attendees] = [{ - mailbox = { email_address: current_user[:email] } - mailbox[:name] = current_user[:name] if current_user.key?('name') attendee: { mailbox: mailbox } }] + + # Add the attendees attendees.each do |attendee| + # If we don't have an array of emails then it's an object in the form {email: "a@b.com", name: "Blahman Blahson"} if attendee.class != String attendee = attendee['email'] end @@ -284,63 +293,37 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: attendee: { mailbox: { email_address: attendee}} }) end - booking[:required_attendees].push({ - attendee: { mailbox: { email_address: room_email}} - }) + + # Add the room as an attendee (it seems as if some clients require this and others don't) + booking[:required_attendees].push({ attendee: { mailbox: { email_address: room_email}}}) booking[:body] = description + + # A little debugging STDERR.puts "MAKING REQUEST WITH" STDERR.puts booking STDERR.flush - if use_act_as + + # Determine whether to use delegation, impersonation or neither + if permission == 'delegation' folder = @ews_client.get_folder(:calendar, { act_as: room_email }) - else + elsif permission == 'impersonation' @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user[:email]) folder = @ews_client.get_folder(:calendar) + elsif permission == 'none' || !permission + folder = @ews_client.get_folder(:calendar) end + + # Create the booking and return data relating to it appointment = folder.create_item(booking) { id: appointment.id, start: start_param, end: end_param, attendees: attendees, - # location: Orchestrator::ControlSystem.find_by_email(room_email).name, subject: subject } end - def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, timezone:'Sydney') - # We will always need a room and endpoint passed in - room = Orchestrator::ControlSystem.find(room_id) - endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" - event = {} - event[:subject] = subject if subject - - event[:start] = { - dateTime: start_param, - timeZone: TIMEZONE_MAPPING[timezone.to_sym] - } if start_param - - event[:end] = { - dateTime: end_param, - timeZone: TIMEZONE_MAPPING[timezone.to_sym] - } if end_param - - event[:body] = { - contentType: 'html', - content: description - } if description - - # Let's assume that the request has the current user and room as an attendee already - event[:attendees] = attendees.map{|a| - { emailAddress: { - address: a[:email], - name: a[:name] - } } - } if attendees - - response = JSON.parse(graph_request('patch', endpoint, event).to_json.value.body)['value'] - end - # Takes a date of any kind (epoch, string, time object) and returns a time object def ensure_ruby_date(date) if !(date.class == Time || date.class == DateTime) From a0305196f885751536020d6d59533c3da2930892 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 2 Jul 2018 16:20:35 +1000 Subject: [PATCH 0607/1752] Merge branch 'atlona-drivers' into beta --- .../cisco/tele_presence/sx_series_common.rb | 3 +- modules/echo360/device_capture.rb | 163 +++++++++++++++++ modules/echo360/device_capture_spec.rb | 168 ++++++++++++++++++ modules/kentix/multi_sensor.rb | 63 +++++++ 4 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 modules/echo360/device_capture.rb create mode 100644 modules/echo360/device_capture_spec.rb create mode 100644 modules/kentix/multi_sensor.rb diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 15efe582..79c3b5ea 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -23,6 +23,7 @@ def connected schedule.every('5s') do logger.debug "-- Polling Cisco SX" call_status + video_output_mode? if @count <= 0 mute_status @@ -121,7 +122,7 @@ def search(text, opts = {}) end def clear_search_results - self[:search_results] = nil + self[:search_results] = [] end diff --git a/modules/echo360/device_capture.rb b/modules/echo360/device_capture.rb new file mode 100644 index 00000000..cc74324e --- /dev/null +++ b/modules/echo360/device_capture.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +module Echo360; end + +# Documentation: https://aca.im/driver_docs/Echo360/EchoSystemCaptureAPI_v301.pdf + +class Echo360::DeviceCapture + include ::Orchestrator::Constants + include ::Orchestrator::Security + + # Discovery Information + descriptive_name 'Echo365 Device Capture' + generic_name :Capture + implements :service + + # Communication settings + keepalive true + inactivity_timeout 15000 + + def on_load + on_update + end + + def on_update + # Configure authentication + config({ + headers: { + authorization: [setting(:username), setting(:password)] + } + }) + end + + STATUS_CMDS = { + system_status: :system, + capture_status: :captures, + next: :next_capture, + current: :current_capture, + state: :monitoring + } + + STATUS_CMDS.each do |function, route| + define_method function do + get("/status/#{route}") do |response| + check(response) { |json| process_status json } + end + end + end + + protect_method :restart_application, :reboot, :captures, :upload + + def restart_application + post('/diagnostics/restart_all') { :success } + end + + def reboot + post('/diagnostics/reboot') { :success } + end + + def captures + get('/diagnostics/recovery/saved-content') do |response| + check(response) { |json| self[:captures] = json['captures']['capture'] } + end + end + + def upload(id) + post("/diagnostics/recovery/#{id}/upload") do |response| + response.status == 200 ? response.data : :abort + end + end + + def capture(name, duration, profile = nil) + profile ||= self[:capture_profiles][0] + post('capture/new_capture', body: { + description: name, + duration: duration.to_i, + capture_profile_name: profile + }) do |response| + response.status == 200 ? Hash.from_xml(response.data)['ok']['text'] : :abort + end + state + end + + def test_capture(name, duration, profile = nil) + profile ||= self[:capture_profiles][0] + post('capture/confidence_monitor', body: { + description: name, + duration: duration.to_i, + capture_profile_name: profile + }) do |response| + response.status == 200 ? Hash.from_xml(response.data)['ok']['text'] : :abort + end + state + end + + def extend(duration) + post('capture/confidence_monitor', body: { + duration: duration.to_i + }) do |response| + response.status == 200 ? Hash.from_xml(response.data)['ok']['text'] : :abort + end + end + + def pause + post('capture/pause') do |response| + response.status == 200 ? Hash.from_xml(response.data)['ok']['text'] : :abort + end + end + + def start + post('capture/record') do |response| + response.status == 200 ? Hash.from_xml(response.data)['ok']['text'] : :abort + end + end + + alias_method :resume, :start + alias_method :record, :start + + def stop + post('capture/stop') do |response| + response.status == 200 ? Hash.from_xml(response.data)['ok']['text'] : :abort + end + end + + protected + + # Converts the response into the appropriate format and indicates success / failure + def check(response, defer = nil) + if response.status == 200 + begin + yield Hash.from_xml(response.body) + :success + rescue => e + defer.reject e if defer + :abort + end + else + defer.reject :failed if defer + :abort + end + end + + CHECK = %w(next current) + + # Grabs the status information and sets the keys. + # Keys ending in 's' are typically an array of the inner element + def process_status(data) + data['status'].each do |key, value| + if CHECK.include?(key) && value.length < 2 && value['schedule'] == "\n" + self[key] = nil + elsif key[-1] == 's' && value.is_a?(Hash) + inner = value[key[0..-2]] + if inner + self[key] = inner + else + self[key] = value + end + else + self[key] = value + end + end + end +end diff --git a/modules/echo360/device_capture_spec.rb b/modules/echo360/device_capture_spec.rb new file mode 100644 index 00000000..32bde5d7 --- /dev/null +++ b/modules/echo360/device_capture_spec.rb @@ -0,0 +1,168 @@ + +Orchestrator::Testing.mock_device 'Echo360::DeviceCapture' do + capture_status = <<~HEREDOC + + 2014-02-12T15:02:19.037Z + + 3.0 + + + Audio Only (Podcast). Balanced between file size & quality + Display Only (Podcast/Vodcast/EchoPlayer). Balanced between file size & quality + Display/Video (Podcast/Vodcast/EchoPlayer). Balanced between file size & quality + Display/Video (Podcast/Vodcast/EchoPlayer). Optimized for quality/full motion video + DualDisplay (Podcast/Vodcast/EchoPlayer). Optimized for file size & bandwidth + Dual Video (Podcast/Vodcast/EchoPlayer) -Balance between file size & quality + Dual Video (Podcast/Vodcast/EchoPlayer) -High Quality + Video Only (Podcast/Vodcast/EchoPlayer). Balanced between file size & quality + + + Display/Video (Podcast/Vodcast/EchoPlayer). Balanced between file size & quality + + + media + 2014-02-12T23:00:00.000Z + 3000 + + Underwater Basket Weaving 101 (UWBW-101-100) Spring 2014 +
Underwater Basket Weaving 101 (UWBW-101-100) Spring 2014
+ + John Doe + + + Display/Video (Podcast/Vodcast/EchoPlayer). Optimized for quality/full motion video + archive + + + + balanced + stereo + -6 + 44100 + 0 + false + + + 1 + dvi + 50 + 50 + 50 + 10.0 + 960 + 720 + true + true + + + 2 + composite + 50 + 50 + 50 + 29.97 + 704 + 480 + true + false + ntsc + + + audio + aac + true + + 128000 + lc + + + + graphics1 + h264 + + vbr + 736000 + 1104000 + base + 50 + + + + graphics2 + h264 + + vbr + 1056000 + 1584000 + base + 150 + + + + audio-archive + + file + audio.aac + + + + graphics1-archive + + file + display.h264 + + + + graphics2-archive + + file + video.h264 + + + + + +
+
+ + + +
+ HEREDOC + + # NOTE:: ignore warnings about private functions + resp = Hash.from_xml(capture_status) + exec(:process_status, resp) + + expect(status[:api_versions]).to eq('3.0') + + captures = <<~HEREDOC + + + Underwater Basket Weaving 101 (UWBW-101-100) Spring 2014 + 2014-02-12T15:30:00.000Z + 3000 +
Underwater Basket Weaving 101 (UWBW-101-100) Spring 2014
+ + + John Doe + + +
+ + Some other capture + 2014-02-13T15:30:00.000Z + 1500 +
Some other capture
+ + + Steve + + +
+
+ HEREDOC + + resp = Hash.from_xml(captures) + expect(resp['captures']['capture'][0]['id']).to eq('0797b8dd-4c2d-415a-adf9-daf7f10e1759') +end diff --git a/modules/kentix/multi_sensor.rb b/modules/kentix/multi_sensor.rb new file mode 100644 index 00000000..02eff2c1 --- /dev/null +++ b/modules/kentix/multi_sensor.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +# TODO:: We can use traps instead of polling +# require 'aca/trap_dispatcher' + +module Kentix; end + +# Documentation: https://aca.im/driver_docs/Kentix/Kentix-KMS-LAN-API-1_0.pdf +# https://aca.im/driver_docs/Kentix/kentixdevices.mib + +class Kentix::MultiSensor + include ::Orchestrator::Constants + + descriptive_name 'Kentix MultiSensor' + generic_name :Sensor + implements :service + + default_settings communication_key: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + + def on_load + on_update + end + + def on_update + # default is a hash of an empty string + @key = setting(:communication_key) + end + + def connected + schedule.clear + schedule.every('10s', true) do + get_state + end + end + + def get_state + post('/api/1.0/', body: { + command: 2200, + type: :get, + auth: @key, + version: '1.0' + }.to_json, headers: { + 'Content-Type' => 'application/json' + }, name: :state) do |response| + if response.status == 200 + data = ::JSON.parse(response.body, symbolize_names: true) + if data[:error] + logger.debug { "error response\n#{data}" } + :abort + else + self[:last_updated] = data[:timestamp] + data[:data][:system][:device].each do |key, value| + self[key] = value + end + :success + end + else + :abort + end + end + end +end From 9fb9f9d270d68a96c093aa840fdf9f3421698bb4 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 3 Jul 2018 00:59:46 +1000 Subject: [PATCH 0608/1752] (cisco:ce) remove version from peripheral registration inspection of repo to get current git commit hash was a fragile and also caused path issues with other system components --- .../cisco/collaboration_endpoint/room_os.rb | 2 -- .../collaboration_endpoint/room_os_spec.rb | 3 +-- .../cisco/collaboration_endpoint/util/git.rb | 24 ------------------- .../cisco/collaboration_endpoint/util/meta.rb | 16 ------------- 4 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 modules/cisco/collaboration_endpoint/util/git.rb delete mode 100644 modules/cisco/collaboration_endpoint/util/meta.rb diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index d660f67f..fc199113 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -368,7 +368,6 @@ def load_setting(name, default:, persist: false) def load_settings load_setting :peripheral_id, default: SecureRandom.uuid, persist: true - load_setting :version, default: Meta.version(self) end # Bind arbitary device feedback to a status variable. @@ -404,7 +403,6 @@ def register_control_system send_xcommand 'Peripherals Connect', ID: self[:peripheral_id], Name: 'ACAEngine', - SoftwareInfo: self[:version], Type: :ControlSystem end diff --git a/modules/cisco/collaboration_endpoint/room_os_spec.rb b/modules/cisco/collaboration_endpoint/room_os_spec.rb index 348921ce..b6dd8b26 100644 --- a/modules/cisco/collaboration_endpoint/room_os_spec.rb +++ b/modules/cisco/collaboration_endpoint/room_os_spec.rb @@ -3,7 +3,6 @@ Orchestrator::Testing.mock_device 'Cisco::CollaborationEndpoint::RoomOs', settings: { peripheral_id: 'MOCKED_ID', - version: 'MOCKED_VERSION', device_config: { Audio: { DefaultVolume: 100 @@ -66,7 +65,7 @@ def section(message) # ------------------------------------------------------------------------- section 'System registration' - should_send "xCommand Peripherals Connect ID: \"MOCKED_ID\" Name: \"ACAEngine\" SoftwareInfo: \"MOCKED_VERSION\" Type: ControlSystem | resultId=\"#{id_peek}\"\n" + should_send "xCommand Peripherals Connect ID: \"MOCKED_ID\" Name: \"ACAEngine\" Type: ControlSystem | resultId=\"#{id_peek}\"\n" responds( <<~JSON { diff --git a/modules/cisco/collaboration_endpoint/util/git.rb b/modules/cisco/collaboration_endpoint/util/git.rb deleted file mode 100644 index 6f245ccb..00000000 --- a/modules/cisco/collaboration_endpoint/util/git.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module Cisco; end -module Cisco::CollaborationEndpoint; end -module Cisco::CollaborationEndpoint::Util; end - -module Cisco::CollaborationEndpoint::Util::Git - module_function - - # Get the commit hash for the passed path. - # - # @param path [String] the path to the repo - # @return [String, nil] the short commit hash - def hash(path) - Dir.chdir(path) { `git rev-parse --short HEAD`.strip } if installed? - end - - # Check if git is installed and accessible to the curent process. - # - # @return [Boolean] - def installed? - system 'git --version' - end -end diff --git a/modules/cisco/collaboration_endpoint/util/meta.rb b/modules/cisco/collaboration_endpoint/util/meta.rb deleted file mode 100644 index 0646cc29..00000000 --- a/modules/cisco/collaboration_endpoint/util/meta.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require_relative 'git' - -module Cisco; end -module Cisco::CollaborationEndpoint; end -module Cisco::CollaborationEndpoint::Util; end - -module Cisco::CollaborationEndpoint::Util::Meta - module_function - - def version(instance) - hash = Cisco::CollaborationEndpoint::Util::Git.hash __dir__ - "#{instance.class.name}-#{hash}" - end -end From 4a85edace8fd79c95289d0cb6cad0fd39e26311b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 3 Jul 2018 10:31:35 +1000 Subject: [PATCH 0609/1752] (cisco:ce) remove presenter track commands from room kit module --- modules/cisco/collaboration_endpoint/room_kit.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 036dbea7..164f9be0 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -34,7 +34,6 @@ def connected status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume - status 'Cameras PresenterTrack' => :presenter_track status 'Cameras SpeakerTrack' => :speaker_track status 'RoomAnalytics PeoplePresence' => :presence_detected status 'RoomAnalytics PeopleCount Current' => :people_count @@ -90,12 +89,6 @@ def mic_mute(state = On) :autofocus_diagnostics_stop, CameraId: (1..1) - command! 'Cameras PresenterTrack ClearPosition' => :presenter_track_clear - command! 'Cameras PresenterTrack StorePosition' => :presenter_track_store - command! 'Cameras PresenterTrack Set' => :presenter_track, - Mode: [:Off, :Follow, :Diagnostic, :Background, :Setup, - :Persistant] - command! 'Cameras SpeakerTrack Diagnostics Start' => \ :speaker_track_diagnostics_start command! 'Cameras SpeakerTrack Diagnostics Stop' => \ From 50c0f9bd1debea461d743f9a6cc05e3788908d52 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 3 Jul 2018 10:50:46 +1000 Subject: [PATCH 0610/1752] (aca:meeting) add camera to modes input updates --- modules/aca/meeting_room.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index c2940afb..f2b3e92d 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -528,7 +528,9 @@ def switch_mode(mode_name, from_join = false, booting: false) # Update the inputs inps = (setting(:inputs) + (mode[:inputs] || [])) - (mode[:remove_inputs] || []) inps.uniq! - inps.each do |input| + + # Camera is special + (inps + [:Camera]).uniq.each do |input| inp = setting(input) || mode[input] if inp From 5f40d199e3dedd044c147c4eea79abe1e5e38dc2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 3 Jul 2018 17:05:01 +1000 Subject: [PATCH 0611/1752] (aca:router) prevent errors when loading on a unconfigured system --- modules/aca/router.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 7e0b2db0..f0890a93 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -31,7 +31,11 @@ def on_load end def on_update - load_from_map setting(:connections) + connections = setting :connections + + logger.warn 'no connections defined' unless connections + + load_from_map(connections || {}) end From 8ee547f78ea702a78a2a72c82e5331cf0a4033de Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 4 Jul 2018 17:01:12 +1000 Subject: [PATCH 0612/1752] Minor o365 updates --- lib/microsoft/office.rb | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 3c955ccf..2752efef 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -181,7 +181,7 @@ def get_room(room_id:) room_response.select! { |room| room['email'] == room_id } end - def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) + def get_available_rooms(rooms:, start_param:, end_param:) endpoint = "/v1.0/users/#{@service_account_email}/findMeetingTimes" now = Time.now start_ruby_param = ensure_ruby_date((start_param || now)) @@ -191,26 +191,15 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) end_param = (end_ruby_param + 30.minutes).utc.iso8601.split("+")[0] # Add the attendees - attendees.map!{|a| + rooms.map!{|room| { type: 'required', emailAddress: { - address: a[:email], - name: a[:name] + address: room[:email], + name: room[:name] } } } - location_constraint = { - isRequired: true, - locations: room_ids.map{ |email| - { - locationEmailAddress: email, - resolveAvailability: true - } - }, - suggestLocation: false - } - time_constraint = { activityDomain: 'unrestricted', timeslots: [{ @@ -226,8 +215,7 @@ def get_available_rooms(room_ids:, start_param:, end_param:, attendees:[]) } post_data = { - attendees: attendees, - locationConstraint: location_constraint, + attendees: rooms, timeConstraint: time_constraint, maxCandidates: 1000, returnSuggestionReasons: true, From 4d672abb9fe28a59c173e53b23ec5e0131c9bd23 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 4 Jul 2018 18:10:24 +1000 Subject: [PATCH 0613/1752] (QSC:remote) add support for QSC cameras --- modules/qsc/q_sys_camera.rb | 65 +++++++++++++++++++++++++++++++++++++ modules/qsc/q_sys_remote.rb | 17 ++++++---- 2 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 modules/qsc/q_sys_camera.rb diff --git a/modules/qsc/q_sys_camera.rb b/modules/qsc/q_sys_camera.rb new file mode 100644 index 00000000..62441ae2 --- /dev/null +++ b/modules/qsc/q_sys_camera.rb @@ -0,0 +1,65 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Qsc; end + +class Qsc::QSysCamera + include ::Orchestrator::Constants + + # Discovery Information + implements :logic + descriptive_name 'QSC PTZ Camera Proxy' + generic_name :Camera + + def on_load + on_update + end + + def on_update + @mod_id = setting(:driver) || :Mixer + @component = setting(:component) + end + + def power(state) + powered = is_affirmative?(state) + camera.mute('toggle_privacy', state, @component) + end + + def adjust_tilt(direction) + direction = direction.to_sym + + case direction + when :down + camera.mute('tilt_down', true, @component) + when :up + camera.mute('tilt_up', true, @component) + else # stop + camera.mute('toggle_privacy', false, @component) + camera.mute('tilt_down', false, @component) + end + end + + def adjust_pan(direction) + direction = direction.to_sym + + case direction + when :right + camera.mute('pan_right', true, @component) + when :left + camera.mute('pan_left', true, @component) + else # stop + camera.mute('pan_right', false, @component) + camera.mute('pan_left', false, @component) + end + end + + def home + camera.component_trigger(@component, 'preset_home_load') + end + + protected + + def camera + system[@mod_id] + end +end diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index 0ff0101d..2a321119 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -131,6 +131,13 @@ def component_set(name, value, **options) }, **options) end + def component_trigger(component, trigger, **options) + do_send(next_id, cmd: :"Component.Trigger", params: { + :Name => component, + :Controls => [{ :Name => trigger }] + }, **options) + end + def get_components(**options) do_send(next_id, cmd: :"Component.GetComponents", **options) end @@ -299,15 +306,11 @@ def fader(fader_id, level, component = nil, type = :fader, use_value: false) faders = Array(fader_id) if component if @db_based_faders || use_value - level = level.to_f / 10.0 if @integer_faders - fads = faders.map do |fad| - {Name: fad, Value: level} - end + level = level.to_f / 10.0 if @integer_faders && !use_value + fads = faders.map { |fad| {Name: fad, Value: level} } else level = level.to_f / 1000.0 if @integer_faders - fads = faders.map do |fad| - {Name: fad, Position: level} - end + fads = faders.map { |fad| {Name: fad, Position: level} } end component_set(component, fads, name: "level_#{faders[0]}").then do component_get(component, faders) From 2959d677afa27429994a190f435e4011276a9d2e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 5 Jul 2018 22:06:42 +1000 Subject: [PATCH 0614/1752] (qsc:qsys) add link to documentation --- modules/qsc/q_sys_control.rb | 1 - modules/qsc/q_sys_remote.rb | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/qsc/q_sys_control.rb b/modules/qsc/q_sys_control.rb index 7f67049e..bf806657 100644 --- a/modules/qsc/q_sys_control.rb +++ b/modules/qsc/q_sys_control.rb @@ -3,7 +3,6 @@ module Qsc; end -# Documentation: https://aca.im/driver_docs/QSC/QRCDocumentation.pdf # The older V1 protocol # http://q-syshelp.qschome.com/Content/External%20Control/Q-Sys%20External%20Control/007%20Q-Sys%20External%20Control%20Protocol.htm diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index 2a321119..fef1f31b 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -3,6 +3,8 @@ module Qsc; end +# Documentation: https://aca.im/driver_docs/QSC/QRCDocumentation.pdf + class Qsc::QSysRemote include ::Orchestrator::Constants include ::Orchestrator::Transcoder From d3df62cc33ce2ab40dfbaebc7d87c9dc03fc9cb1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 8 Jul 2018 19:01:12 +1000 Subject: [PATCH 0615/1752] (tvone:coriomaster) pull available presets from the device --- modules/tv_one/corio_master.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index af5ac588..9710215b 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -78,9 +78,30 @@ def do_poll query 'Preset.Take', expose_as: :preset end + # Get the presets available for recall - for some inexplicible reason this + # has a wildly different API to the rest of the system state... + def query_preset_list(expose_as: nil) + exec('Preset.PresetList').then do |presets| + presets.transform_keys { |key| key[/\d+/].to_i } + .transform_values do |val| + name, canvas, time = val.split ',' + { + name: name, + canvas: canvas, + time: time + } + end + + self[expose_as] = presets unless expose_as.nil? + + presets + end + end + def sync_state thread.finally( query('Preset.Take', expose_as: :preset), + query_preset_list( expose_as: :presets), deep_query('Windows', expose_as: :windows), deep_query('Canvases', expose_as: :canvases), deep_query('Layouts', expose_as: :layouts) From e0ced66f30a7657439192021a563607bd100cb68 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 8 Jul 2018 19:16:31 +1000 Subject: [PATCH 0616/1752] (tvone:coriomaster) return output from exec commands via promise --- modules/tv_one/corio_master.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 9710215b..35f87802 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -113,7 +113,16 @@ def sync_state def exec(command, *params, **opts) logger.debug { "executing #{command}" } + + defer = thread.defer + + opts[:on_receive] = lambda do |*args| + received(*args) { |val| defer.resolve val } + end + send "#{command}(#{params.join ','})\r\n", opts + + defer.promise end def set(path, val, **opts) From 59a355c4fe6f865898a006d0b0d8c3f5e168a93e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 8 Jul 2018 20:16:02 +1000 Subject: [PATCH 0617/1752] (tvone:coriomaster) fix issue with tracking changes to window properties --- modules/tv_one/corio_master.rb | 5 +++-- modules/tv_one/corio_master_spec.rb | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 35f87802..28a87473 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -57,8 +57,9 @@ def switch(signal_map) def window(id, property, value) set("Window#{id}.#{property}", value).then do - self[:windows][:"window#{id}"][property.downcase.to_sym] = value - signal_status :windows + self[:windows] = (self[:windows] || {}).deep_merge( + :"window#{id}" => { property.downcase.to_sym => value } + ) end end diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index 2a479e69..6997bd65 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -158,5 +158,6 @@ def sync_state RX ) wait_tick - sync_state + expect(status[:windows][:window1][:input]).to eq('Slot1.In1') + expect(status[:windows][:window2][:input]).to eq('Slot1.In2') end From 1a20add6ed0d8b0b8d5cc3a572672a64661da502 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 8 Jul 2018 20:20:08 +1000 Subject: [PATCH 0618/1752] (tvone:coriomaster) fix issue where exec requests would never resolve --- modules/tv_one/corio_master.rb | 3 ++- modules/tv_one/corio_master_spec.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 28a87473..624e84c4 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -190,7 +190,7 @@ def parse_response(lines, command) if updates.size == 1 && updates.include?(command) # Single property query updates.values.first - elsif updates.values.all?(&:nil?) + elsif !updates.empty? && updates.values.all?(&:nil?) # Property list updates.keys else @@ -232,6 +232,7 @@ def received(data, resolve, command) end when 'Info' logger.info message + yield message if block_given? :success when 'Error' logger.error message diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index 6997bd65..281d3764 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -58,7 +58,7 @@ def sync_state .responds <<~RX !Info: Rebooting...\r RX - expect(result).to be(:success) + expect(result).to eq('Rebooting...') exec(:set, 'Window1.Input', 'Slot3.In1') .should_send("Window1.Input = Slot3.In1\r\n") From b236dc545b1bec7c420200c1ef0f864f8f143371 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 8 Jul 2018 20:20:30 +1000 Subject: [PATCH 0619/1752] (tvone:coriomaster) add router preset query to tests --- modules/tv_one/corio_master.rb | 5 +++-- modules/tv_one/corio_master_spec.rb | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 624e84c4..47f75652 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -82,7 +82,8 @@ def do_poll # Get the presets available for recall - for some inexplicible reason this # has a wildly different API to the rest of the system state... def query_preset_list(expose_as: nil) - exec('Preset.PresetList').then do |presets| + exec('Routing.Preset.PresetList').then do |presets| + presets.transform_keys { |key| key[/\d+/].to_i } .transform_values do |val| name, canvas, time = val.split ',' @@ -93,7 +94,7 @@ def query_preset_list(expose_as: nil) } end - self[expose_as] = presets unless expose_as.nil? + self[expose_as.to_sym] = presets unless expose_as.nil? presets end diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index 281d3764..a4cf4713 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -10,6 +10,10 @@ def sync_state Preset.Take = 1\r !Done Preset.Take\r RX + should_send "Routing.Preset.PresetList()\r\n" + responds <<~RX + !Done Routing.Preset.PresetList()\r + RX should_send "Windows\r\n" responds <<~RX !Done Windows\r From ddbf129d923a931bec98969c453606a70fed6375 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 8 Jul 2018 21:05:06 +1000 Subject: [PATCH 0620/1752] (tvone:coriomaster) fix issues with response parsing for preset query --- modules/tv_one/corio_master.rb | 47 +++++++++++++++-------------- modules/tv_one/corio_master_spec.rb | 18 +++++++++++ 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 47f75652..36002e96 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -82,17 +82,17 @@ def do_poll # Get the presets available for recall - for some inexplicible reason this # has a wildly different API to the rest of the system state... def query_preset_list(expose_as: nil) - exec('Routing.Preset.PresetList').then do |presets| - - presets.transform_keys { |key| key[/\d+/].to_i } - .transform_values do |val| - name, canvas, time = val.split ',' - { - name: name, - canvas: canvas, - time: time - } - end + exec('Routing.Preset.PresetList').then do |preset_list| + presets = preset_list.each_with_object({}) do |preset, h| + key, val = preset.split '=' + id = key[/\d+/].to_i + name, canvas, time = val.split ',' + h[id] = { + name: name, + canvas: canvas, + time: time.to_i + } + end self[expose_as.to_sym] = presets unless expose_as.nil? @@ -176,17 +176,20 @@ def deep_query(path, expose_as: nil, **opts) end def parse_response(lines, command) - updates = lines.map { |line| line.chop.split ' = ' } - .to_h - .transform_values! do |val| - case val - when /^-?\d+$/ then Integer val - when 'NULL' then nil - when /(Off)|(No)/ then false - when /(On)|(Yes)/ then true - else val - end - end + kv_pairs = lines.map do |line| + k, v = line.chop.split ' = ' + [k, v] + end + + updates = kv_pairs.to_h.transform_values! do |val| + case val + when /^-?\d+$/ then Integer val + when 'NULL' then nil + when /(Off)|(No)/ then false + when /(On)|(Yes)/ then true + else val + end + end if updates.size == 1 && updates.include?(command) # Single property query diff --git a/modules/tv_one/corio_master_spec.rb b/modules/tv_one/corio_master_spec.rb index a4cf4713..7f3a7f09 100644 --- a/modules/tv_one/corio_master_spec.rb +++ b/modules/tv_one/corio_master_spec.rb @@ -133,6 +133,24 @@ def sync_state } ) + exec(:query_preset_list) + .should_send("Routing.Preset.PresetList()\r\n") + .responds( + <<~RX + Routing.Preset.PresetList[1]=Sharing-Standard,Canvas1,0\r + Routing.Preset.PresetList[2]=Standard-4-Screen,Canvas1,0\r + Routing.Preset.PresetList[3]=Standard-10-Screen,Canvas1,0\r + Routing.Preset.PresetList[11]=Clear,Canvas1,0\r + !Done Routing.Preset.PresetList()\r + RX + ) + expect(result).to eq( + 1 => { name: 'Sharing-Standard', canvas: 'Canvas1', time: 0 }, + 2 => { name: 'Standard-4-Screen', canvas: 'Canvas1', time: 0 }, + 3 => { name: 'Standard-10-Screen', canvas: 'Canvas1', time: 0 }, + 11 => { name: 'Clear', canvas: 'Canvas1', time: 0 } + ) + exec(:preset, 1) .should_send("Preset.Take = 1\r\n") From 3dbe4f56be3eb360c5ab4b9d57343c9065268d78 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 9 Jul 2018 02:19:18 +1000 Subject: [PATCH 0621/1752] (aca:router) prevent the need for exception creation during normalisation --- modules/aca/router.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index f0890a93..502aa8c3 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -471,7 +471,9 @@ def self.normalise(map) when Array (1..inputs.size).zip(inputs).to_h when Hash - inputs.transform_keys { |x| Integer(x) rescue x } + inputs.transform_keys do |key| + key.to_s[/^\d+$/]&.to_i || key + end else raise ArgumentError, 'inputs must be a Hash or Array' end From 1965ec278fe774a1f6ca82463358fbeb6b652c4a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 9 Jul 2018 02:23:30 +1000 Subject: [PATCH 0622/1752] (aca:router) refactor edge parsing / optimisation during switch events --- modules/aca/router.rb | 82 +++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 502aa8c3..771958fb 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -56,19 +56,16 @@ def on_update # single source to a single destination, Ruby's implicit hash syntax can be # used to let you express it neatly as `connect source => sink`. def connect(signal_map, atomic: false, force: false) - routes = {} - signal_map.each_pair do |source, sinks| - routes[source] = route_many source, sinks, strict: atomic - end + edges = map_to_edges signal_map, strict: atomic - check_conflicts routes, strict: atomic + edge_list = edges.values.reduce(&:|) - edges = routes.values.map(&:second).reduce(&:|) + edge_list.select! { |e| needs_activation? e, ignore_status: force } - edges, unroutable = edges.partition { |e| can_activate? e } + edge_list, unroutable = edge_list.partition { |e| can_activate? e } raise 'can not perform all routes' if unroutable.any? && atomic - interactions = edges.map { |e| activate e, force } + interactions = edge_list.map { |e| activate e } thread.finally(interactions).then do |results| failed = edges.zip(results).reject { |_, (_, resolved)| resolved } @@ -215,19 +212,49 @@ def route_many(source, sinks, strict: false) [nodes, edges] end - def check_conflicts(routes, strict: false) - nodes = routes.values.map(&:first) + # Given a signal map, convert it to a hash still keyed on source id's, but + # containing the edges within the graph to be utilised. + def map_to_edges(signal_map, strict: false) + nodes = {} + edges = {} - return Set.new if nodes.size <= 1 + signal_map.each_pair do |source, sinks| + n, e = route_many source, sinks, strict: atomic + nodes[source] = n + edges[source] = e + end - nodes.reduce(&:&).tap do |conflicts| - unless conflicts.empty? - nodes = conflicts.map(&:to_s).join ', ' - message = "conflicting signal paths found for #{nodes}" - raise message if strict - logger.warn message - end + conflicts = nodes.size > 1 ? nodes.values.reduce(&:&) : Set.new + unless conflicts.empty? + sources = nodes.reject { |(_, n)| (n & conflicts).empty? }.keys + message = "routes for #{sources.join ', '} intersect" + raise message if strict + logger.warn message + end + + edges + end + + def needs_activation?(edge, ignore_status: false) + mod = system[edge.device] + + fail_with = proc do |reason| + logger.info "module for #{edge.device} #{reason} - skipping #{edge}" + return false end + + single_source = signal_graph.outdegree(edge.source) == 1 + + fail_with['does not exist, but appears to be an alias'] \ + if mod.nil? && single_source + + fail_with['already on correct input'] \ + if edge.nx1? && mod[:input] == edge.input && !ignore_status + + fail_with['has an incompatible api, but only a single input defined'] \ + if edge.nx1? && !mod.respond_to?(:switch_to) && single_source + + true end def can_activate?(edge) @@ -242,28 +269,21 @@ def can_activate?(edge) fail_with['offline'] if mod[:connected] == false - if edge.nx1? && !mod.respond_to?(:switch_to) - if signal_graph.outdegree(edge.source) == 1 - logger.info "can not switch #{edge.device}. " \ - "This may be ok as only one input (#{edge.target}) exists." - else - fail_with['missing #switch_to'] - end - end + fail_with['has an incompatible api (missing #switch_to)'] \ + if edge.nx1? && !mod.respond_to?(:switch_to) - fail_with['missing #switch'] if edge.nxn? && !mod.respond_to?(:switch) + fail_with['has an incompatible api (missing #switch)'] \ + if edge.nxn? && !mod.respond_to?(:switch) true end - def activate(edge, force = false) + def activate(edge) mod = system[edge.device] if edge.nx1? - needs_switch = mod[:input] != edge.input - mod.switch_to edge.input if needs_switch || force + mod.switch_to edge.input elsif edge.nxn? - # TODO: define standard API for exposing matrix state mod.switch edge.input => edge.output else raise 'unexpected edge type' From 055fa2d3c5862831b2df3b3be7873379e574ab86 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 9 Jul 2018 02:40:47 +1000 Subject: [PATCH 0623/1752] (aca:router) fix incorrect name --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 771958fb..c9fb1dba 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -219,7 +219,7 @@ def map_to_edges(signal_map, strict: false) edges = {} signal_map.each_pair do |source, sinks| - n, e = route_many source, sinks, strict: atomic + n, e = route_many source, sinks, strict: strict nodes[source] = n edges[source] = e end From 9a52bd9bab7e7d981163281ffb1dc01fb7c33a54 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 9 Jul 2018 03:29:20 +1000 Subject: [PATCH 0624/1752] (aca:router) return partial signal map dependant on successful routes --- modules/aca/router.rb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index c9fb1dba..187a14c7 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -68,17 +68,23 @@ def connect(signal_map, atomic: false, force: false) interactions = edge_list.map { |e| activate e } thread.finally(interactions).then do |results| - failed = edges.zip(results).reject { |_, (_, resolved)| resolved } - if (failed + unroutable).empty? + failed = edge_list.zip(results).reject { |_, (_, success)| success } + + edges_with_errors = unroutable + failed.each do |edge, (error, _)| + logger.warn "could not switch #{edge}: #{error}" + edges_with_errors << edge + end + + if edges_with_errors.empty? logger.debug 'all routes activated successfully' signal_map + elsif atomic + thread.reject 'failed to activate all routes' else - failed.each do |edge, (error, _)| - logger.warn "could not switch #{edge}: #{error}" + signal_map.select do |source, _| + (edges[source] & edges_with_errors).empty? end - message = 'failed to activate all routes' - logger.error message - thread.reject message end end end From 5950ece9d391cd4050833f49602023317e3adaff Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 10 Jul 2018 00:25:08 +1000 Subject: [PATCH 0625/1752] (aca:meeting) add vc_show_pres_layout setting --- modules/aca/meeting_room.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index f2b3e92d..e67a09e1 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -22,6 +22,7 @@ def on_update self[:analytics] = setting(:analytics) self[:Camera] = setting(:Camera) self[:Wired] = setting(:Wired) + self[:vc_show_pres_layout] = setting(:vc_show_pres_layout) self[:hide_vc_sources] = setting(:hide_vc_sources) self[:mics_mutes] = setting(:mics_mutes) @confidence_monitor = setting(:confidence_monitor) From b2ee41dacae67e363444582b522b4fde9cfbd290 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 10 Jul 2018 10:47:37 +1000 Subject: [PATCH 0626/1752] Add all view to people count model --- lib/aca/tracking/people_count.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/aca/tracking/people_count.rb b/lib/aca/tracking/people_count.rb index aae12be8..24881639 100644 --- a/lib/aca/tracking/people_count.rb +++ b/lib/aca/tracking/people_count.rb @@ -19,6 +19,8 @@ class Aca::Tracking::PeopleCount < CouchbaseOrm::Base before_create :set_id + view :all, emit_key: :room_email + def set_id self.id = "count-#{self.booking_id}" end From cd4d688790513041131da52144f2a16bccd08a34 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 10 Jul 2018 10:47:48 +1000 Subject: [PATCH 0627/1752] Add hiding of all day bookings --- modules/aca/exchange_booking.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index f9e3c49a..c2480e0b 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -115,9 +115,11 @@ def on_update self[:booking_min_duration] = setting(:booking_min_duration) self[:booking_disable_future] = setting(:booking_disable_future) self[:booking_max_duration] = setting(:booking_max_duration) + self[:hide_all_day_bookings] = setting(:hide_all_day_bookings) self[:timeout] = setting(:timeout) self[:arrow_direction] = setting(:arrow_direction) self[:icon] = setting(:icon) + @hide_all_day_bookings = Boolean(setting(:hide_all_day_bookings)) @check_meeting_ending = setting(:check_meeting_ending) # seconds before meeting ending @extend_meeting_by = setting(:extend_meeting_by) || 15.minutes.to_i @@ -761,6 +763,10 @@ def todays_bookings(first=false) logger.debug { item.inspect } + if @hide_all_day_bookings + next if Time.parse(ending) - Time.parse(start) > 86399 + end + # Prevent connections handing with TIME_WAIT # cli.ews.connection.httpcli.reset_all @@ -776,6 +782,7 @@ def todays_bookings(first=false) :end_epoch => real_end.to_i } end + results.compact! if set_skype_url self[:can_join_skype_meeting] = false From 722e2a952a8cfac8d9e801ba7c45ec096a9bd823 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 10 Jul 2018 12:10:22 +1000 Subject: [PATCH 0628/1752] (exchange) perform multiple searches to avoid limits --- lib/microsoft/exchange.rb | 86 ++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index c827ae37..98720dcd 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -29,7 +29,7 @@ def initialize( ews_opts = { http_opts: { ssl_verify_mode: 0 } } ews_opts[:http_opts][:http_client] = @internet_proxy if @internet_proxy STDERR.puts '--------------- NEW CLIENT CREATED --------------' - STDERR.puts "At URL: #{@ews_url} with email: #{@service_account_email} and password #{@service_account_password}" + STDERR.puts "At URL: #{@ews_url} with email: #{@service_account_email}" STDERR.puts '-------------------------------------------------' @ews_client ||= Viewpoint::EWSClient.new @ews_url, @service_account_email, @service_account_password, ews_opts end @@ -141,51 +141,55 @@ def get_available_rooms(rooms:, start_time:, end_time:) STDERR.puts "Getting available rooms with" STDERR.puts start_time STDERR.puts end_time - STDERR.flush - - # Get booking data for all rooms between time range bounds - user_free_busy = @ews_client.get_user_availability(rooms, - start_time: start_time, - end_time: end_time, - requested_view: :detailed, - time_zone: { - bias: -600, - standard_time: { - bias: -60, - time: "03:00:00", - day_order: 1, - month: 10, - day_of_week: 'Sunday' - }, - daylight_time: { - bias: 0, - time: "02:00:00", - day_order: 1, - month: 4, - day_of_week: 'Sunday' + STDERR.flush + + rooms.each_slice(99).each do |room_subset| + + # Get booking data for all rooms between time range bounds + user_free_busy = @ews_client.get_user_availability(room_subset, + start_time: start_time, + end_time: end_time, + requested_view: :detailed, + time_zone: { + bias: -600, + standard_time: { + bias: -60, + time: "03:00:00", + day_order: 1, + month: 10, + day_of_week: 'Sunday' + }, + daylight_time: { + bias: 0, + time: "02:00:00", + day_order: 1, + month: 4, + day_of_week: 'Sunday' + } } - } - ) + ) - user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| - # Remove meta data (business hours and response type) - resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| item[:free_busy_view_type] || item[:working_hours] } + user_free_busy.body[0][:get_user_availability_response][:elems][0][:free_busy_response_array][:elems].each_with_index {|r,index| + # Remove meta data (business hours and response type) + resp = r[:free_busy_response][:elems][1][:free_busy_view][:elems].delete_if { |item| item[:free_busy_view_type] || item[:working_hours] } - # Back to back meetings still show up so we need to remove these from the results - if resp.length == 1 - resp = resp[0][:calendar_event_array][:elems] + # Back to back meetings still show up so we need to remove these from the results + if resp.length == 1 + resp = resp[0][:calendar_event_array][:elems] - if resp.length == 0 - free_rooms.push(rooms[index]) - elsif resp.length == 1 - free_rooms.push(rooms[index]) if find_time(resp[0], :start_time) == end_time - free_rooms.push(rooms[index]) if find_time(resp[0], :end_time) == start_time + if resp.length == 0 + free_rooms.push(room_subset[index]) + elsif resp.length == 1 + free_rooms.push(room_subset[index]) if find_time(resp[0], :start_time) == end_time + free_rooms.push(room_subset[index]) if find_time(resp[0], :end_time) == start_time + end + elsif resp.length == 0 + # If response length is 0 then the room is free + free_rooms.push(room_subset[index]) end - elsif resp.length == 0 - # If response length is 0 then the room is free - free_rooms.push(rooms[index]) - end - } + } + end + free_rooms end From 4ef0ac7ad493bff4fd7d3707b89d6935eabd3201 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 10 Jul 2018 14:58:42 +1000 Subject: [PATCH 0629/1752] (cisco:ce) add volume control --- modules/cisco/collaboration_endpoint/room_kit.rb | 3 +++ modules/cisco/collaboration_endpoint/sx80.rb | 3 +++ 2 files changed, 6 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 164f9be0..a5f22dd4 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -62,6 +62,9 @@ def mic_mute(state = On) Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound + command 'Audio Volume Set' => :volume, + Level: (0..100) + command 'Call Disconnect' => :hangup, CallId_: Integer command 'Dial' => :dial, Number: String, diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index f530ce91..74d5db22 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -62,6 +62,9 @@ def mic_mute(state = On) Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound + command 'Audio Volume Set' => :volume, + Level: (0..100) + command 'Call Disconnect' => :hangup, CallId_: Integer command 'Dial' => :dial, Number: String, From 89995a5a7f6b9e21f0e057fe2be75f44211b180a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 11 Jul 2018 14:18:50 +1000 Subject: [PATCH 0630/1752] (aca:router) support querying inputs for nodes on matrix switchers --- modules/aca/router.rb | 11 +++++++++-- modules/aca/router_spec.rb | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 187a14c7..86411832 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -97,8 +97,15 @@ def connect(signal_map, atomic: false, force: false) # it. Similarly `on` maybe used to look up the input used by any other node # within the graph that would be used to show `source`. def input_for(source, on: nil) - sink = on || upstream(source) - _, edges = route source, sink + if on.nil? + edges = signal_graph.incoming_edges source + raise "no outputs from #{source}" if edges.empty? + raise "multiple outputs from #{source}, please specify a sink" \ + unless edges.map(&:device).uniq.size == 1 + else + _, edges = route source, on + end + edges.last.input end diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index bb820a1c..6471ba4a 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -154,6 +154,10 @@ def section(message) expect(routes[:Right_LCD].distance_to[:c]).to be_infinite + # ------------------------------------------------------------------------- + + exec(:load_from_map, signal_map) + # ------------------------------------------------------------------------- section 'Routing' @@ -176,4 +180,14 @@ def section(message) expect { exec(:route, :e, :Left_LCD) }.to \ raise_error('no route from e to Left_LCD') + + + # ------------------------------------------------------------------------- + section 'Graph queries' + + exec(:input_for, :a) + expect(result).to be(1) + + exec(:input_for, :a, on: :Left_LCD) + expect(result).to be(:hdmi) end From 90990189f91591013eb36274e077ff9c3002dff1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 01:58:52 +1000 Subject: [PATCH 0631/1752] (aca:router) make all internal id's symbols --- modules/aca/router.rb | 30 +++++++++++++++------------- modules/aca/router_spec.rb | 40 +++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 86411832..3d629e57 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -154,7 +154,7 @@ def signal_graph end def paths - @path_cache ||= HashWithIndifferentAccess.new do |hash, node| + @path_cache ||= Hash.new do |hash, node| hash[node] = signal_graph.dijkstra node end end @@ -323,8 +323,8 @@ class Edge Meta = Struct.new(:device, :input, :output) def initialize(source, target, &blk) - @source = source - @target = target + @source = source.to_sym + @target = target.to_sym meta = Meta.new.tap(&blk) normalise_io = lambda do |x| @@ -359,13 +359,13 @@ class Node def initialize(id) @id = id.to_sym - @edges = HashWithIndifferentAccess.new do |_, other_id| + @edges = Hash.new do |_, other_id| raise ArgumentError, "No edge from \"#{id}\" to \"#{other_id}\"" end end def join(other_id, datum) - edges[other_id] = datum + edges[other_id.to_sym] = datum self end @@ -387,7 +387,7 @@ def hash attr_reader :nodes def initialize - @nodes = HashWithIndifferentAccess.new do |_, id| + @nodes = Hash.new do |_, id| raise ArgumentError, "\"#{id}\" does not exist" end end @@ -397,7 +397,8 @@ def [](id) end def insert(id) - nodes[id] = Node.new id unless nodes.key? id + id = id.to_sym + nodes[id] = Node.new id unless nodes.include? id self end @@ -414,6 +415,8 @@ def delete(id, check_incoming_edges: true) end def join(source, target, &block) + source = source.to_sym + target = target.to_sym datum = Edge.new(source, target, &block) nodes[source].join target, datum self @@ -459,7 +462,7 @@ def outdegree(id) def dijkstra(id) active = Containers::PriorityQueue.new { |x, y| (x <=> y) == -1 } - distance_to = HashWithIndifferentAccess.new { 1.0 / 0.0 } + distance_to = Hash.new { 1.0 / 0.0 } predecessor = {} distance_to[id] = 0 @@ -499,13 +502,13 @@ def to_s # { device: { input: source } } # def self.normalise(map) - map.with_indifferent_access.transform_values! do |inputs| + map.transform_values do |inputs| case inputs when Array (1..inputs.size).zip(inputs).to_h when Hash inputs.transform_keys do |key| - key.to_s[/^\d+$/]&.to_i || key + key.to_s[/^\d+$/]&.to_i || key.to_sym end else raise ArgumentError, 'inputs must be a Hash or Array' @@ -519,12 +522,12 @@ def self.normalise(map) # `device as output` to simply `output` and return a Hash of the structure # `{ output: device }`. def self.extract_mods!(map) - mods = HashWithIndifferentAccess.new + mods = {} map.transform_keys! do |key| - mod, node = key.to_s.split ' as ' + mod, node = key.to_s.split(' as ').map(&:to_sym) node ||= mod - mods[node] = mod.to_sym + mods[node] = mod node end @@ -588,6 +591,7 @@ def self.from_map(map) upstream_device, output = source.to_s.split '__' next if output.nil? + upstream_device = upstream_device.to_sym matrix_nodes |= [upstream_device] # Push in nodes and edges to each matrix input diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index 6471ba4a..4d5874b2 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -83,13 +83,13 @@ def section(message) normalised_map = SignalGraph.normalise(signal_map) expect(normalised_map).to eq( 'Display_1 as Left_LCD' => { - 'hdmi' => 'Switcher_1__1', - 'hdmi2' => 'SubSwitchA__1' + hdmi: 'Switcher_1__1', + hdmi2: 'SubSwitchA__1' }, 'Display_2 as Right_LCD' => { - 'hdmi' => 'Switcher_1__2', - 'hdmi2' => 'SubSwitchB__2', - 'display_port' => 'g' + hdmi: 'Switcher_1__2', + hdmi2: 'SubSwitchB__2', + display_port: 'g' }, 'Switcher_1' => { 1 => 'a', @@ -107,31 +107,31 @@ def section(message) mods = SignalGraph.extract_mods!(normalised_map) expect(mods).to eq( - 'Left_LCD' => :Display_1, - 'Right_LCD' => :Display_2, - 'Switcher_1' => :Switcher_1, - 'SubSwitchA' => :Switcher_2, - 'SubSwitchB' => :Switcher_2 + Left_LCD: :Display_1, + Right_LCD: :Display_2, + Switcher_1: :Switcher_1, + SubSwitchA: :Switcher_2, + SubSwitchB: :Switcher_2 ) expect(normalised_map).to eq( - 'Left_LCD' => { - 'hdmi' => 'Switcher_1__1', - 'hdmi2' => 'SubSwitchA__1' + Left_LCD: { + hdmi: 'Switcher_1__1', + hdmi2: 'SubSwitchA__1' }, - 'Right_LCD' => { - 'hdmi' => 'Switcher_1__2', - 'hdmi2' => 'SubSwitchB__2', - 'display_port' => 'g' + Right_LCD: { + hdmi: 'Switcher_1__2', + hdmi2: 'SubSwitchB__2', + display_port: 'g' }, - 'Switcher_1' => { + Switcher_1: { 1 => 'a', 2 => 'b' }, - 'SubSwitchA' => { + SubSwitchA: { 1 => 'c', 2 => 'd' }, - 'SubSwitchB' => { + SubSwitchB: { 3 => 'e', 4 => 'f' } From 57f005b2ffb1fd0b8a0760dbb3ac11f757968bd6 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 02:12:44 +1000 Subject: [PATCH 0632/1752] (aca:router) refector to provide a partial signal map as a result of a partial recall --- modules/aca/router.rb | 156 +++++++++++++++++++++---------------- modules/aca/router_spec.rb | 14 +++- 2 files changed, 103 insertions(+), 67 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 3d629e57..f7b5d2bd 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -45,9 +45,9 @@ def on_update # Route a set of signals to arbitrary destinations. # # `signal_map` is a hash of the structure `{ source: sink | [sinks] }` - # 'atomic' may be used to throw an exception, prior to any device - # interaction taking place if any of the routes are not - # possible + # 'atomic' may be used to prevent activation of any part of the signal + # map, prior to any device interaction taking place, if any + # of the routes are not possible # `force` control if switch events should be forced, even when the # associated device module is already reporting it's on the # correct input @@ -56,34 +56,29 @@ def on_update # single source to a single destination, Ruby's implicit hash syntax can be # used to let you express it neatly as `connect source => sink`. def connect(signal_map, atomic: false, force: false) - edges = map_to_edges signal_map, strict: atomic + # Convert the signal map to a nested hash of routes + # { source => { dest => [edges] } } + edge_map = build_edge_map signal_map, atomic: atomic - edge_list = edges.values.reduce(&:|) - - edge_list.select! { |e| needs_activation? e, ignore_status: force } - - edge_list, unroutable = edge_list.partition { |e| can_activate? e } - raise 'can not perform all routes' if unroutable.any? && atomic + # Reduce the edge map to a set of edges + edges_to_connect = edge_map.reduce(Set.new) do |s, (_, routes)| + s | routes.values.reduce(&:|) + end - interactions = edge_list.map { |e| activate e } + switch = activate_all edges_to_connect, atomic: atomic, force: force - thread.finally(interactions).then do |results| - failed = edge_list.zip(results).reject { |_, (_, success)| success } + switch.then do |success, failed| + if failed.empty? + logger.debug 'signal map fully activated' + edge_map.transform_values(&:keys) - edges_with_errors = unroutable - failed.each do |edge, (error, _)| - logger.warn "could not switch #{edge}: #{error}" - edges_with_errors << edge - end + elsif success.empty? + thread.reject 'failed to activate, devices untouched' - if edges_with_errors.empty? - logger.debug 'all routes activated successfully' - signal_map - elsif atomic - thread.reject 'failed to activate all routes' else - signal_map.select do |source, _| - (edges[source] & edges_with_errors).empty? + logger.warn 'signal map partially activated' + edge_map.transform_values do |routes| + routes.select { |_, edges| success.superset? edges }.keys end end end @@ -198,57 +193,88 @@ def route(source, sink) [nodes, edges] end - # Find the optimum combined paths required to route a single source to - # multiple sink devices. - def route_many(source, sinks, strict: false) - node_exists = proc do |id| - signal_graph.include?(id).tap do |exists| - unless exists - message = "#{id} does not exist" - raise ArgumentError, message if strict - logger.warn message - end - end - end + # Convert a signal map of the structure + # + # source => [dest] + # + # to a nested hash of the structure + # + # source => { dest => [edges] } + # + def build_edge_map(signal_map, atomic: false) + nodes_in_use = Set.new + edge_map = {} - nodes = Set.new - edges = Set.new + signal_map.each_pair do |source, sinks| + source = source.to_sym + sinks = Array(sinks).map(&:to_sym) + + source_nodes = Set.new + edge_map[source] = {} - if node_exists[source] - Array(sinks).select(&node_exists).each do |sink| - n, e = route source, sink - nodes |= n - edges |= e + sinks.each do |sink| + begin + nodes, edges = route source, sink + + if nodes_in_use.intersect? Set[nodes] + partial_map = edge_map.transform_values(&:keys) + route = "route from #{source} to #{sink}" + raise "#{route} conflicts with routes in #{partial_map}" + end + + source_nodes |= nodes + edge_map[source][sink] = edges + rescue e + # note `route` may also throw an exception (e.g. when there + # is an invalid source / sink or unroutable path) + raise if atomic + logger.warn e.message + end end + + nodes_in_use |= source_nodes end - [nodes, edges] + edge_map end - # Given a signal map, convert it to a hash still keyed on source id's, but - # containing the edges within the graph to be utilised. - def map_to_edges(signal_map, strict: false) - nodes = {} - edges = {} + # Given a set of edges, activate them all and return a promise that will + # resolve following the completion of all device interactions. + # + # The returned promise contains the original edges, partitioned into + # success and failure sets. + def activate_all(edges, atomic: false, force: false) + success = Set.new + failed = Set.new - signal_map.each_pair do |source, sinks| - n, e = route_many source, sinks, strict: strict - nodes[source] = n - edges[source] = e - end + # Filter out any edges we can skip over + skippable = edges.reject { |e| needs_activation? e, force: force } + success |= skippable + edges -= skippable - conflicts = nodes.size > 1 ? nodes.values.reduce(&:&) : Set.new - unless conflicts.empty? - sources = nodes.reject { |(_, n)| (n & conflicts).empty? }.keys - message = "routes for #{sources.join ', '} intersect" - raise message if strict - logger.warn message - end + # Remove anything that we know will fail up front + unroutable = edges.reject { |e| can_activate? e } + failed |= unroutable + edges -= unroutable - edges + raise 'can not perform all routes' if atomic && unroutable.any? + + interactions = edges.map { |e| activate e } + + thread.finally(interactions).then do |results| + edges.zip(results).each do |edge, (result, resolved)| + if resolved + success |= edge + else + logger.warn "failed to switch #{edge}: #{result}" + failed |= edge + end + end + [success, failed] + end end - def needs_activation?(edge, ignore_status: false) + def needs_activation?(edge, force: false) mod = system[edge.device] fail_with = proc do |reason| @@ -262,7 +288,7 @@ def needs_activation?(edge, ignore_status: false) if mod.nil? && single_source fail_with['already on correct input'] \ - if edge.nx1? && mod[:input] == edge.input && !ignore_status + if edge.nx1? && mod[:input] == edge.input && !force fail_with['has an incompatible api, but only a single input defined'] \ if edge.nx1? && !mod.respond_to?(:switch_to) && single_source diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index 4d5874b2..fb88bffa 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -161,8 +161,6 @@ def section(message) # ------------------------------------------------------------------------- section 'Routing' - exec(:load_from_map, signal_map) - exec(:route, :a, :Left_LCD) nodes, edges = result expect(nodes.map(&:id)).to contain_exactly(:a, :Switcher_1__1, :Left_LCD) @@ -181,6 +179,15 @@ def section(message) expect { exec(:route, :e, :Left_LCD) }.to \ raise_error('no route from e to Left_LCD') + # ------------------------------------------------------------------------- + section 'Edge maps' + + exec(:build_edge_map, a: :Left_LCD, b: :Right_LCD) + edge_map = result + expect(edge_map.keys).to contain_exactly(:a, :b) + expect(edge_map[:a]).to be_a(Hash) + expect(edge_map[:a][:Left_LCD]).to be_a(Array) + # ------------------------------------------------------------------------- section 'Graph queries' @@ -190,4 +197,7 @@ def section(message) exec(:input_for, :a, on: :Left_LCD) expect(result).to be(:hdmi) + + exec(:upstream, :g) + expect(result).to be(:Right_LCD) end From bbb3a34f1e3878e9142fe84631aaef7418968623 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 02:30:38 +1000 Subject: [PATCH 0633/1752] (aca:router) only return / store node id's from graph search --- modules/aca/router.rb | 24 ++++++++++++------------ modules/aca/router_spec.rb | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index f7b5d2bd..66aaaf46 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -180,12 +180,12 @@ def route(source, sink) nodes = [] edges = [] - node = signal_graph[source] + node = source until node.nil? nodes.unshift node - predecessor = path.predecessor[node.id] - edges << predecessor.edges[node.id] unless predecessor.nil? - node = predecessor + prev = path.predecessor[node] + edges << signal_graph[prev].edges[node] unless prev.nil? + node = prev end logger.debug { edges.map(&:to_s).join ' then ' } @@ -457,7 +457,7 @@ def include?(id) end def successors(id) - nodes[id].edges.keys.map { |x| nodes[x] } + nodes[id].edges.keys end def sources @@ -492,16 +492,16 @@ def dijkstra(id) predecessor = {} distance_to[id] = 0 - active.push nodes[id], distance_to[id] + active.push id, distance_to[id] until active.empty? u = active.pop - successors(u.id).each do |v| - alt = distance_to[u.id] + 1 - next unless alt < distance_to[v.id] - distance_to[v.id] = alt - predecessor[v.id] = u - active.push v, distance_to[v.id] + successors(u).each do |v| + alt = distance_to[u] + 1 + next unless alt < distance_to[v] + distance_to[v] = alt + predecessor[v] = u + active.push v, distance_to[v] end end diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index fb88bffa..77e0b481 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -35,7 +35,7 @@ def section(message) edge.device = :Display_1 edge.input = :hdmi end - expect(graph.successors(:display)).to include(graph[:laptop]) + expect(graph.successors(:display)).to include(:laptop) # Graph structural inspection # note: signal flow is inverted from graph directivity @@ -163,7 +163,7 @@ def section(message) exec(:route, :a, :Left_LCD) nodes, edges = result - expect(nodes.map(&:id)).to contain_exactly(:a, :Switcher_1__1, :Left_LCD) + expect(nodes).to contain_exactly(:a, :Switcher_1__1, :Left_LCD) expect(edges.first).to be_nxn expect(edges.first.device).to be(:Switcher_1) expect(edges.first.input).to be(1) @@ -174,7 +174,7 @@ def section(message) exec(:route, :c, :Left_LCD) nodes, = result - expect(nodes.map(&:id)).to contain_exactly(:c, :SubSwitchA__1, :Left_LCD) + expect(nodes).to contain_exactly(:c, :SubSwitchA__1, :Left_LCD) expect { exec(:route, :e, :Left_LCD) }.to \ raise_error('no route from e to Left_LCD') From 5021b75ca76afd18712066311639126afd7cf41a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 03:21:34 +1000 Subject: [PATCH 0634/1752] (aca:router) minor syntax issue --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 66aaaf46..c9dd4f01 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -224,7 +224,7 @@ def build_edge_map(signal_map, atomic: false) source_nodes |= nodes edge_map[source][sink] = edges - rescue e + rescue => e # note `route` may also throw an exception (e.g. when there # is an invalid source / sink or unroutable path) raise if atomic From 314abcacd04ce6f1044eddd34c25ab0966d1343b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 03:21:57 +1000 Subject: [PATCH 0635/1752] (aca:router) only return node id's --- modules/aca/router.rb | 14 +++++++++----- modules/aca/router_spec.rb | 10 +++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index c9dd4f01..6c539776 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -161,9 +161,9 @@ def load_from_map(connections) @signal_graph = SignalGraph.from_map(connections).freeze # TODO: track active signal source at each node and expose as a hash - self[:nodes] = signal_graph.map(&:id) - self[:inputs] = signal_graph.sinks.map(&:id) - self[:outputs] = signal_graph.sources.map(&:id) + self[:nodes] = signal_graph.node_ids + self[:inputs] = signal_graph.sinks + self[:outputs] = signal_graph.sources end # Find the shortest path between between two nodes and return a list of the @@ -456,16 +456,20 @@ def include?(id) nodes.key? id end + def node_ids + map(&:id) + end + def successors(id) nodes[id].edges.keys end def sources - select { |node| indegree(node.id).zero? } + node_ids.select { |id| indegree(id).zero? } end def sinks - select { |node| outdegree(node.id).zero? } + node_ids.select { |id| outdegree(id).zero? } end def incoming_edges(id) diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index 77e0b481..7e008fb2 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -43,8 +43,8 @@ def section(message) expect(graph.indegree(:laptop)).to be(1) expect(graph.outdegree(:display)).to be(1) expect(graph.outdegree(:laptop)).to be(0) - expect(graph.sources.map(&:id)).to include(:display) - expect(graph.sinks.map(&:id)).to include(:laptop) + expect(graph.sources).to include(:display) + expect(graph.sinks).to include(:laptop) # Edge inspection edge = graph[:display].edges[:laptop] @@ -139,11 +139,11 @@ def section(message) graph = SignalGraph.from_map(signal_map) - expect(graph.sources.map(&:id)).to contain_exactly(:Left_LCD, :Right_LCD) + expect(graph.sources).to contain_exactly(:Left_LCD, :Right_LCD) - expect(graph.sinks.map(&:id)).to contain_exactly(*(:a..:g).to_a) + expect(graph.sinks).to contain_exactly(*(:a..:g).to_a) - routes = graph.sources.map(&:id).map { |id| [id, graph.dijkstra(id)] }.to_h + routes = graph.sources.map { |id| [id, graph.dijkstra(id)] }.to_h expect(routes[:Left_LCD].distance_to[:a]).to be(2) expect(routes[:Left_LCD].distance_to[:c]).to be(2) expect(routes[:Left_LCD].distance_to[:e]).to be_infinite From 07d403a26938fa5e8c873506a3ef88b18c5e907c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 03:58:21 +1000 Subject: [PATCH 0636/1752] (aca:router) fix issue with string / sym mismatch --- modules/aca/router.rb | 6 +++--- modules/aca/router_spec.rb | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 6c539776..35d6d97a 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -552,13 +552,13 @@ def self.normalise(map) # `device as output` to simply `output` and return a Hash of the structure # `{ output: device }`. def self.extract_mods!(map) - mods = {} + mods = HashWithIndifferentAccess.new map.transform_keys! do |key| - mod, node = key.to_s.split(' as ').map(&:to_sym) + mod, node = key.to_s.split(' as ') node ||= mod mods[node] = mod - node + node.to_sym end mods diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index 7e008fb2..49085132 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -107,11 +107,11 @@ def section(message) mods = SignalGraph.extract_mods!(normalised_map) expect(mods).to eq( - Left_LCD: :Display_1, - Right_LCD: :Display_2, - Switcher_1: :Switcher_1, - SubSwitchA: :Switcher_2, - SubSwitchB: :Switcher_2 + 'Left_LCD' => 'Display_1', + 'Right_LCD' => 'Display_2', + 'Switcher_1' => 'Switcher_1', + 'SubSwitchA' => 'Switcher_2', + 'SubSwitchB' => 'Switcher_2' ) expect(normalised_map).to eq( Left_LCD: { From 97654a43b8f9f40b2a6c88c6d73e5d6fd7e06415 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 04:06:47 +1000 Subject: [PATCH 0637/1752] (aca:router) fix incorrect syntax for set insertion --- modules/aca/router.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 35d6d97a..2370f3d0 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -264,10 +264,10 @@ def activate_all(edges, atomic: false, force: false) thread.finally(interactions).then do |results| edges.zip(results).each do |edge, (result, resolved)| if resolved - success |= edge + success << edge else logger.warn "failed to switch #{edge}: #{result}" - failed |= edge + failed << edge end end [success, failed] From bf61a6c148dd25eda4fd2d9f94bb94786a55fb49 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 04:40:24 +1000 Subject: [PATCH 0638/1752] (aca:router) fix issue when attempting to activate empty routes --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 2370f3d0..63b5e04e 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -62,7 +62,7 @@ def connect(signal_map, atomic: false, force: false) # Reduce the edge map to a set of edges edges_to_connect = edge_map.reduce(Set.new) do |s, (_, routes)| - s | routes.values.reduce(&:|) + s | routes.values.flatten end switch = activate_all edges_to_connect, atomic: atomic, force: force From e6d47aea087386d76b84a919f7d0aad2234a6f96 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 04:50:06 +1000 Subject: [PATCH 0639/1752] (aca:router) make issues with routing errors rather than warnings --- modules/aca/router.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 63b5e04e..a3261c92 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -69,7 +69,7 @@ def connect(signal_map, atomic: false, force: false) switch.then do |success, failed| if failed.empty? - logger.debug 'signal map fully activated' + logger.debug 'signal map activated' edge_map.transform_values(&:keys) elsif success.empty? @@ -228,7 +228,7 @@ def build_edge_map(signal_map, atomic: false) # note `route` may also throw an exception (e.g. when there # is an invalid source / sink or unroutable path) raise if atomic - logger.warn e.message + logger.error e.message end end From ca5be8c61b70f4625237617ac45875c53dbdd1f1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 07:38:18 +1000 Subject: [PATCH 0640/1752] (aca:router) provide device_for and devices_between --- modules/aca/router.rb | 43 +++++++++++++------------------------- modules/aca/router_spec.rb | 7 +++++-- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index a3261c92..47e6f362 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -104,38 +104,25 @@ def input_for(source, on: nil) edges.last.input end - # Get the node immediately upstream of an input node. + # Find the device that input node is attached to. # - # Depending on the device API, this may be of use for determining signal - # presence. - def upstream(source, sink = nil) - if sink.nil? - edges = signal_graph.incoming_edges source - raise "no outputs from #{source}" if edges.empty? - raise "multiple outputs from #{source}, please specify a sink" \ - if edges.size > 1 - else - _, edges = route source, sink - end - - edges.first.source + # Efficiently queries the graph for the device that an signal input connects + # to for checking signal properties revealed by the device state. + def device_for(source) + edges = signal_graph.incoming_edges source + raise "no outputs from #{source}" if edges.empty? + raise "#{source} is not an input node" if edges.size > 1 + edges.first.device end - # Get the node immediately downstream of an output node. + # Get a list of devices that a signal passes through for a specific route. # - # This may be used walking back up the signal graph to find a decoder for - # an output device. - def downstream(sink, source = nil) - if source.nil? - edges = signal_graph.outgoing_edges sink - raise "no inputs to #{sink}" if edges.empty? - raise "multiple inputs to #{sink}, please specify a source" \ - if edges.size > 1 - else - _, edges = route source, sink - end - - edges.last.target + # This may be used to walk up or down a path to find encoders, decoders or + # other devices that may provide some interesting state, or require + # additional interactions (signal presence monitoring etc). + def devices_between(source, sink) + _, edges = route source, sink + edges.map(&:device) end diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index 49085132..a7552bfd 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -198,6 +198,9 @@ def section(message) exec(:input_for, :a, on: :Left_LCD) expect(result).to be(:hdmi) - exec(:upstream, :g) - expect(result).to be(:Right_LCD) + exec(:device_for, :g) + expect(result).to be(:Display_2) + + exec(:devices_between, :c, :Left_LCD) + expect(result).to contain_exactly(:Switcher_2, :Display_1) end From 5ec217fd495eea71e9b515a47d17c0532b0574c8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 12 Jul 2018 11:46:23 +1000 Subject: [PATCH 0641/1752] (tvone:coriomaster) resolve preset recall after initial interaction --- modules/tv_one/corio_master.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 36002e96..47ed9ae5 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -41,7 +41,10 @@ def disconnected # Main API def preset(id) - set('Preset.Take', id).finally { sync_state } + set('Preset.Take', id).then do |result| + sync_state + result + end end alias switch_to preset From 4de48d0e815f8fe57911ca12893c8595257bc40c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Jul 2018 11:25:31 +1000 Subject: [PATCH 0642/1752] Add fields for create permissions and mailbox --- lib/microsoft/exchange.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 98720dcd..1fc0b7aa 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -246,7 +246,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. end end - def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney', permission: 'impersonation') + def create_booking(room_email:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, timezone:'Sydney', permission: 'impersonation', mailbox_location: 'user') STDERR.puts "CREATING NEW BOOKING IN LIBRARY" STDERR.puts "room_email is #{room_email}" STDERR.puts "start_param is #{start_param}" @@ -307,13 +307,19 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: STDERR.puts booking STDERR.flush + if mailbox_location == 'user' + mailbox = current_user[:email] + elsif mailbox_location == 'room' + mailbox = room_email + end + # Determine whether to use delegation, impersonation or neither if permission == 'delegation' - folder = @ews_client.get_folder(:calendar, { act_as: room_email }) + folder = @ews_client.get_folder(:calendar, { act_as: mailbox }) elsif permission == 'impersonation' - @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], current_user[:email]) + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], mailbox) folder = @ews_client.get_folder(:calendar) - elsif permission == 'none' || !permission + elsif permission == 'none' || permission.nil? folder = @ews_client.get_folder(:calendar) end From ddc209e609ade420541b41fccd790a2956b1948f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Jul 2018 11:47:16 +1000 Subject: [PATCH 0643/1752] Add ID to bookings get --- lib/microsoft/exchange.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 1fc0b7aa..6c73f8d6 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -216,6 +216,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking = {} booking[:subject] = event.subject booking[:title] = event.subject + booking[:id] = event.id # booking[:start_date] = event.start.utc.iso8601 # booking[:end_date] = event.end.utc.iso8601 booking[:start_date] = event.start.to_i * 1000 @@ -319,7 +320,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: elsif permission == 'impersonation' @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], mailbox) folder = @ews_client.get_folder(:calendar) - elsif permission == 'none' || permission.nil? + elsif permission == 'none' || permission.nil? folder = @ews_client.get_folder(:calendar) end From b4e83e2523c6349fdde7dc69b454190e0e504f3d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 13 Jul 2018 12:45:54 +1000 Subject: [PATCH 0644/1752] [Exchange lib[ Change current user to not use .key method --- lib/microsoft/exchange.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 6c73f8d6..85e1bcd9 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -282,8 +282,8 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop # Add the current user passed in as an attendee - mailbox = { email_address: current_user[:email] } - mailbox[:name] = current_user[:name] if current_user.key?('name') + mailbox = { email_address: current_user.email } + mailbox[:name] = current_user.name if current_user.name booking[:required_attendees] = [{ attendee: { mailbox: mailbox } }] @@ -309,7 +309,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: STDERR.flush if mailbox_location == 'user' - mailbox = current_user[:email] + mailbox = current_user.email elsif mailbox_location == 'room' mailbox = room_email end From d1ab0860ef7fcb772bac99b32599308a693de2d1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Jul 2018 10:55:05 +1000 Subject: [PATCH 0645/1752] [Exchange lib] Add meeting location to booking creation --- lib/microsoft/exchange.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 85e1bcd9..a2d6f0d7 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -267,6 +267,8 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: # Allow for naming of subject or title booking[:subject] = subject booking[:title] = subject + booking[:location] = Orchestrator::ControlSystem.find_by_email(room_email).name + # Set the room email as a resource booking[:resources] = [{ @@ -335,6 +337,11 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: } end + def delete_booking(id) + booking = ews_client.get_item(id) + booking.delete!(:recycle, send_meeting_cancellations: "SendOnlyToAll") + end + # Takes a date of any kind (epoch, string, time object) and returns a time object def ensure_ruby_date(date) if !(date.class == Time || date.class == DateTime) From 13c2a7ca043837f9fd1a3a908ac9f8d473b363f6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Jul 2018 14:51:05 +1000 Subject: [PATCH 0646/1752] Add update method --- lib/microsoft/exchange.rb | 40 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index a2d6f0d7..d4c72e0e 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -337,8 +337,46 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: } end + def update_booking(id:, room_email:nil, start_param:nil, end_param:nil, subject:nil, description:nil, current_user:nil, attendees: nil, timezone:'Sydney', permission: 'impersonation', mailbox_location: 'user') + + booking = @ews_client.get_item(id) + + # Add attendees if passed in + attendees = Array(attendees) + attendees.each do |attendee| + if attendee.class != String + attendee = attendee['email'] + end + booking[:required_attendees].push({ + attendee: { mailbox: { email_address: attendee}} + }) + end if attendees && !attendees.empty? + + # Add subject or title + booking[:subject] = subject if subject + booking[:title] = subject if subject + + # Add location + booking[:location] = Orchestrator::ControlSystem.find_by_email(room_email).name if email + + # Add new times if passed + booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop if start_param + booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop if end_param + + new_booking = booking.update_item!(booking) + + + { + id: new_booking.id, + start: new_booking.start, + end: new_booking.end, + attendees: new_booking.required_attendees, + subject: new_booking.subject + } + end + def delete_booking(id) - booking = ews_client.get_item(id) + booking = @ews_client.get_item(id) booking.delete!(:recycle, send_meeting_cancellations: "SendOnlyToAll") end From ee5155fc6489e01ec0c37776c655f73969ad9e73 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Jul 2018 15:17:57 +1000 Subject: [PATCH 0647/1752] [Exchange lib] Change update param names --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index d4c72e0e..91c51b89 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -337,7 +337,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: } end - def update_booking(id:, room_email:nil, start_param:nil, end_param:nil, subject:nil, description:nil, current_user:nil, attendees: nil, timezone:'Sydney', permission: 'impersonation', mailbox_location: 'user') + def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, subject:nil, description:nil, current_user:nil, attendees: nil, timezone:'Sydney', permission: 'impersonation', mailbox_location: 'user') booking = @ews_client.get_item(id) From 58ee667c93a4629071621d7dbb205f0771d5f6a1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Jul 2018 15:21:24 +1000 Subject: [PATCH 0648/1752] [Exchange lib] fix some param names --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 91c51b89..4eb296de 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -339,7 +339,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, subject:nil, description:nil, current_user:nil, attendees: nil, timezone:'Sydney', permission: 'impersonation', mailbox_location: 'user') - booking = @ews_client.get_item(id) + booking = @ews_client.get_item(booking_id) # Add attendees if passed in attendees = Array(attendees) @@ -357,7 +357,7 @@ def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, booking[:title] = subject if subject # Add location - booking[:location] = Orchestrator::ControlSystem.find_by_email(room_email).name if email + booking[:location] = Orchestrator::ControlSystem.find_by_email(room_email).name if room_email # Add new times if passed booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop if start_param From 57ea214a733b9a7a46922a1870daf7284e3a1f67 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Jul 2018 15:22:39 +1000 Subject: [PATCH 0649/1752] [Exchange lib] fix some param names --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 4eb296de..81bf7343 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -339,7 +339,7 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, subject:nil, description:nil, current_user:nil, attendees: nil, timezone:'Sydney', permission: 'impersonation', mailbox_location: 'user') - booking = @ews_client.get_item(booking_id) + event = @ews_client.get_item(booking_id) # Add attendees if passed in attendees = Array(attendees) @@ -363,7 +363,7 @@ def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop if start_param booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop if end_param - new_booking = booking.update_item!(booking) + new_booking = event.update_item!(booking) { From 615fe8766f8b707be39ba2a490910332a117e3f3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Jul 2018 15:41:04 +1000 Subject: [PATCH 0650/1752] [Exchange lib] fix some param names --- lib/microsoft/exchange.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 81bf7343..d0e62158 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -340,7 +340,8 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, subject:nil, description:nil, current_user:nil, attendees: nil, timezone:'Sydney', permission: 'impersonation', mailbox_location: 'user') event = @ews_client.get_item(booking_id) - + booking = {} + # Add attendees if passed in attendees = Array(attendees) attendees.each do |attendee| From 9a492dc473b3c07ba54b09fb81b8fdfd4d953235 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Jul 2018 15:44:09 +1000 Subject: [PATCH 0651/1752] [Exchange lib] fix some param names --- lib/microsoft/exchange.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index d0e62158..531f9c69 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -341,13 +341,14 @@ def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, event = @ews_client.get_item(booking_id) booking = {} - + # Add attendees if passed in attendees = Array(attendees) attendees.each do |attendee| if attendee.class != String attendee = attendee['email'] end + booking[:required_attendees] ||= [] booking[:required_attendees].push({ attendee: { mailbox: { email_address: attendee}} }) From c5976758066e78dbc81e79b0a4d501b61432fbe8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Jul 2018 15:59:42 +1000 Subject: [PATCH 0652/1752] [Exchange lib] fix some param names --- lib/microsoft/exchange.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 531f9c69..8203d624 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -365,6 +365,16 @@ def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop if start_param booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop if end_param + if mailbox_location == 'user' + mailbox = current_user.email + elsif mailbox_location == 'room' + mailbox = room_email + end + + if permission == 'impersonation' + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], mailbox) + end + new_booking = event.update_item!(booking) From 251f8191a48c75cce2171c9ca018b175edade5fa Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 17 Jul 2018 09:46:21 +1000 Subject: [PATCH 0653/1752] (tvone:coriomaster) speed up preset recall --- modules/tv_one/corio_master.rb | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 47ed9ae5..2740e9ce 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -42,8 +42,30 @@ def disconnected def preset(id) set('Preset.Take', id).then do |result| - sync_state - result + self[:preset] = id + + # The full query of window params can take up to ~15 seconds. To + # speed things up a little for other modules that depend on this + # state, cache window info against preset ID's. These are then used + # to provide instant status updates. + # + # As the device only supports a single connection the only time the + # cache will contain stale data is following editing of presets, in + # which case window state will update silently in the background. + @window_cache ||= {} + + update_cache = deep_query('Windows').then do |windows| + @window_cache[id] = windows + end + + update_status = ->() { self[:windows] = @window_cache[id] } + + if @window_cache.include? id + update_status.call + result + else + update_cache.then(&update_status).then { result } + end end end alias switch_to preset From 37f2b4c44c01c27a256f76fe7c58cc1b650d093f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 17 Jul 2018 10:11:22 +1000 Subject: [PATCH 0654/1752] (tvone:coriomaster) defer state update until full window info is available --- modules/tv_one/corio_master.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index 2740e9ce..ea28623f 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -41,9 +41,7 @@ def disconnected # Main API def preset(id) - set('Preset.Take', id).then do |result| - self[:preset] = id - + set('Preset.Take', id).then do # The full query of window params can take up to ~15 seconds. To # speed things up a little for other modules that depend on this # state, cache window info against preset ID's. These are then used @@ -55,16 +53,21 @@ def preset(id) @window_cache ||= {} update_cache = deep_query('Windows').then do |windows| + logger.debug "window cache for preset #{id} updated" @window_cache[id] = windows end - update_status = ->() { self[:windows] = @window_cache[id] } + update_state = lambda do + self[:windows] = @window_cache[id] + self[:preset] = id + end if @window_cache.include? id - update_status.call - result + logger.debug 'loading cached window state' + update_state.call else - update_cache.then(&update_status).then { result } + logger.debug "no cached window state available for preset #{id}" + update_cache.then(&update_state) end end end From 06b14ee07c285ce17eb2bc754b0483b7dad3703b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 17 Jul 2018 10:44:57 +1000 Subject: [PATCH 0655/1752] (tvone:coriomaster) fix issue with status update on cache miss --- modules/tv_one/corio_master.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index ea28623f..eb2e4201 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -57,14 +57,14 @@ def preset(id) @window_cache[id] = windows end - update_state = lambda do - self[:windows] = @window_cache[id] + update_state = lambda do |windows| + self[:windows] = windows self[:preset] = id end if @window_cache.include? id logger.debug 'loading cached window state' - update_state.call + update_state.call @window_cache[id] else logger.debug "no cached window state available for preset #{id}" update_cache.then(&update_state) From c0b5078f5a59c64c48a8864feb1f797e4b46da85 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 11:07:55 +1000 Subject: [PATCH 0656/1752] Fix no query param for user search --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 2752efef..111bb404 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -134,7 +134,7 @@ def check_response(response) end def get_users(q: nil, limit: nil) - if q.include?(" ") + if q && q.include?(" ") queries = q.split(" ") filter_params = [] queries.each do |q| From e9daeaaf67c7911c4d661fb5bfe5d34f141454c5 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 11:22:23 +1000 Subject: [PATCH 0657/1752] Default to user mailbox location --- lib/microsoft/office.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 111bb404..56ec9b82 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -24,7 +24,8 @@ def initialize( service_account_email:, service_account_password:, internet_proxy:nil, - delegated:false, + permission: 'impersonation', + mailbox_location: 'user', logger: Rails.logger ) @client_id = client_id @@ -36,7 +37,8 @@ def initialize( @service_account_email = service_account_email @service_account_password = service_account_password @internet_proxy = internet_proxy - @delegated = delegated + @permission = permission + @mailbox_location = mailbox_location oauth_options = { site: @app_site, token_url: @app_token_url } oauth_options[:connection_opts] = { proxy: @internet_proxy } if @internet_proxy @graph_client ||= OAuth2::Client.new( @@ -298,8 +300,11 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil # Get our room room = Orchestrator::ControlSystem.find(room_id) - # Set our endpoint with the email - endpoint = "/v1.0/users/#{room.email}/events" + if mailbox_location == 'room' + endpoint = "/v1.0/users/#{room.email}/events" + elsif mailbox_location == 'user' + endpoint = "/v1.0/users/#{current_user.email}/events" + end # Ensure our start and end params are Ruby dates and format them in Graph format start_object = ensure_ruby_date(start_param).in_time_zone(timezone) From 03083297206c7fd335013f83d84c1ea39dd75010 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 11:26:37 +1000 Subject: [PATCH 0658/1752] Fix syntax issue --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 56ec9b82..85eab67c 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -300,9 +300,9 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil # Get our room room = Orchestrator::ControlSystem.find(room_id) - if mailbox_location == 'room' + if @mailbox_location == 'room' endpoint = "/v1.0/users/#{room.email}/events" - elsif mailbox_location == 'user' + elsif @mailbox_location == 'user' endpoint = "/v1.0/users/#{current_user.email}/events" end From 33655cae998ef83077eb7b0d370e90fc197a1105 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 11:52:50 +1000 Subject: [PATCH 0659/1752] [Office Lib] Add UTC start and end --- lib/microsoft/office.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 85eab67c..98ba22f4 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -264,6 +264,11 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 return recurring_bookings else bookings.concat recurring_bookings + bookings.each do |booking| + booking['Start'] = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']).utc.iso8601 + booking['End'] = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']).utc.iso8601 + end + bookings end end From 3a32a4ffa3b14587dc5b9ea042e59dee023ce619 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 12:27:09 +1000 Subject: [PATCH 0660/1752] [Office Lib] Add UTC start and end --- lib/microsoft/office.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 98ba22f4..dbac5267 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -261,15 +261,16 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 check_response(bookings_response) bookings = JSON.parse(bookings_response.body)['value'] if bookings.nil? - return recurring_bookings + all_bookings = recurring_bookings else bookings.concat recurring_bookings - bookings.each do |booking| - booking['Start'] = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']).utc.iso8601 - booking['End'] = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']).utc.iso8601 - end - bookings + all_bookings = bookings + end + all_bookings.each do |booking| + booking['Start'] = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']).utc.iso8601 + booking['End'] = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']).utc.iso8601 end + all_bookings end def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) From 6e95d5363a7e04ba1a9c3a8a1bf7d5e32c742a67 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 12:38:07 +1000 Subject: [PATCH 0661/1752] [Office Lib] Add UTC start and end --- lib/microsoft/office.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index dbac5267..4323766d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -267,8 +267,12 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 all_bookings = bookings end all_bookings.each do |booking| - booking['Start'] = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']).utc.iso8601 - booking['End'] = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']).utc.iso8601 + start_object = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) + end_object = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) + booking['Start'] = start_object.utc.iso8601 + booking['End'] = end_object.utc.iso8601 + booking['start'] = start_object.to_i + booking['end'] = end_object.to_i end all_bookings end From eacc5b0b66664b9a6fcb25b281bd74eea6f8d962 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 12:38:40 +1000 Subject: [PATCH 0662/1752] [Office Lib] Add UTC start and end --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4323766d..cbb01976 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -271,8 +271,8 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end_object = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) booking['Start'] = start_object.utc.iso8601 booking['End'] = end_object.utc.iso8601 - booking['start'] = start_object.to_i - booking['end'] = end_object.to_i + booking['start_epoch'] = start_object.to_i + booking['end_epoch'] = end_object.to_i end all_bookings end From 3992ba7ad6b87d386fbf186b6d80c053e3144bc2 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 12:39:51 +1000 Subject: [PATCH 0663/1752] Add subject to bookings (rather than title) --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index cbb01976..4450e5df 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -273,6 +273,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 booking['End'] = end_object.utc.iso8601 booking['start_epoch'] = start_object.to_i booking['end_epoch'] = end_object.to_i + booking['title'] = booking['subject'] end all_bookings end From c347b4b216b5a36a9a850912c2dea3a0f9da2dd0 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 12:47:12 +1000 Subject: [PATCH 0664/1752] Add more booking data --- lib/microsoft/office.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4450e5df..d8e7d5b2 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -274,6 +274,10 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 booking['start_epoch'] = start_object.to_i booking['end_epoch'] = end_object.to_i booking['title'] = booking['subject'] + booking['room_name'] = booking['subject'] + if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? + booking['room_name'] = booking['location']['displayName'] + end end all_bookings end From 2fc5acca3050204746b4246008ecdb7bcb00fe92 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 14:40:03 +1000 Subject: [PATCH 0665/1752] Add more booking data --- lib/microsoft/office.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index d8e7d5b2..1961e8f6 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -275,6 +275,11 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 booking['end_epoch'] = end_object.to_i booking['title'] = booking['subject'] booking['room_name'] = booking['subject'] + booking['attendees'].each do |attendee| + if attendee['type'] == 'resource' + booking['room_id'] = attendee['emailAddress']['address'] + end + end if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? booking['room_name'] = booking['location']['displayName'] end From ac6d82f2f73e79474cb29c3988fe0e8ad0b6de06 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 14:43:35 +1000 Subject: [PATCH 0666/1752] Add more booking data --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 1961e8f6..da00d905 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -277,7 +277,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 booking['room_name'] = booking['subject'] booking['attendees'].each do |attendee| if attendee['type'] == 'resource' - booking['room_id'] = attendee['emailAddress']['address'] + booking['room_id'] = attendee['emailAddress']['address'].downcase end end if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? From 6e9416d238db3da1ac573ddf42301ff3bec2a7ad Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 17 Jul 2018 14:46:27 +1000 Subject: [PATCH 0667/1752] Add more booking data --- lib/microsoft/office.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index da00d905..439e2a75 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -275,14 +275,22 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 booking['end_epoch'] = end_object.to_i booking['title'] = booking['subject'] booking['room_name'] = booking['subject'] + new_attendees = [] booking['attendees'].each do |attendee| if attendee['type'] == 'resource' booking['room_id'] = attendee['emailAddress']['address'].downcase + else + new_attendees.push({ + email: attendee['emailAddress']['address'], + name: attendee['emailAddress']['name'] + }) end end + booking['attendees'] = new_attendees if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? booking['room_name'] = booking['location']['displayName'] end + end all_bookings end From 01d1cd35d3d2c69a6a48e0ea15888e20cdd36890 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 19 Jul 2018 11:27:07 +1000 Subject: [PATCH 0668/1752] Remove the need for room_id in delete --- lib/microsoft/office.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 439e2a75..30400967 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -39,6 +39,7 @@ def initialize( @internet_proxy = internet_proxy @permission = permission @mailbox_location = mailbox_location + @delegated = false oauth_options = { site: @app_site, token_url: @app_token_url } oauth_options[:connection_opts] = { proxy: @internet_proxy } if @internet_proxy @graph_client ||= OAuth2::Client.new( @@ -231,9 +232,8 @@ def get_available_rooms(rooms:, start_param:, end_param:) JSON.parse(request.body) end - def delete_booking(room_id:, booking_id:) - room = Orchestrator::ControlSystem.find(room_id) - endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" + def delete_booking(booking_id:) + endpoint = "/v1.0/users/#{current_user.email}/events/#{booking_id}" request = graph_request(request_method: 'delete', endpoint: endpoint, password: @delegated) check_response(request) response = JSON.parse(request.body) From 485fc2d1789775327d7ae1fa775d1327c92f0edb Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 19 Jul 2018 11:31:49 +1000 Subject: [PATCH 0669/1752] Add more logic to grab room email --- lib/microsoft/office.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 30400967..e4f2787a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -287,6 +287,9 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end booking['attendees'] = new_attendees + if !booking.key?('location') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] + booking['room_id'] = booking['locations'][0]['uniqueId'].downcase + end if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? booking['room_name'] = booking['location']['displayName'] end From b66d1b0973549a26bcd63847c6fd4d7940925a3f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 19 Jul 2018 11:36:56 +1000 Subject: [PATCH 0670/1752] Add more logic to grab room email --- lib/microsoft/office.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e4f2787a..78d1998a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -287,7 +287,12 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end booking['attendees'] = new_attendees - if !booking.key?('location') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] + STDERR.puts !booking.key?('location') + STDERR.puts booking['locations'] + STDERR.puts !booking['locations'].empty? + STDERR.puts booking['locations'][0]['uniqueId'] + STDERR.flush + if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] booking['room_id'] = booking['locations'][0]['uniqueId'].downcase end if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? From d696ce7b8fc76dc9e62ac6b9779229e81ec253b5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 19 Jul 2018 11:48:25 +1000 Subject: [PATCH 0671/1752] Pass current user to delete --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 78d1998a..f2abb42c 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -232,7 +232,7 @@ def get_available_rooms(rooms:, start_param:, end_param:) JSON.parse(request.body) end - def delete_booking(booking_id:) + def delete_booking(booking_id:, current_user:) endpoint = "/v1.0/users/#{current_user.email}/events/#{booking_id}" request = graph_request(request_method: 'delete', endpoint: endpoint, password: @delegated) check_response(request) From 437757f6708c4763e848dbd58723c7c4e3f29147 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 19 Jul 2018 12:03:59 +1000 Subject: [PATCH 0672/1752] Fix delete response --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f2abb42c..f11aaca4 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -236,7 +236,7 @@ def delete_booking(booking_id:, current_user:) endpoint = "/v1.0/users/#{current_user.email}/events/#{booking_id}" request = graph_request(request_method: 'delete', endpoint: endpoint, password: @delegated) check_response(request) - response = JSON.parse(request.body) + 200 end def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) From 79a2a79f290051239d59a3958b6eccfc7ff23ce5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 19 Jul 2018 15:05:49 +1000 Subject: [PATCH 0673/1752] [O365 Lib] Keep old attendees in another field --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f11aaca4..062cc5e0 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -274,7 +274,6 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 booking['start_epoch'] = start_object.to_i booking['end_epoch'] = end_object.to_i booking['title'] = booking['subject'] - booking['room_name'] = booking['subject'] new_attendees = [] booking['attendees'].each do |attendee| if attendee['type'] == 'resource' @@ -286,6 +285,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 }) end end + booking['old_attendees'] = booking['attendees'] booking['attendees'] = new_attendees STDERR.puts !booking.key?('location') STDERR.puts booking['locations'] From 8b8aa2342f60e649ca8f3e3d4a5c852e15f86538 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 19 Jul 2018 16:09:32 +1000 Subject: [PATCH 0674/1752] Add organizer to bookings --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 062cc5e0..0ebf738a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -286,6 +286,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end booking['old_attendees'] = booking['attendees'] + booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} booking['attendees'] = new_attendees STDERR.puts !booking.key?('location') STDERR.puts booking['locations'] From 2c7a2c2c768be62cc547989647c63c8a7c69dab2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 14:17:35 +1000 Subject: [PATCH 0675/1752] [O365 Lib] fix syntax issue --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 0ebf738a..e33ae422 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -326,7 +326,7 @@ def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Tim end def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1.week)) - return get_bookings_by_user(room_id: room_id, start_param: start_param, end_param: end_param) + return get_bookings_by_user(user_id: room_id, start_param: start_param, end_param: end_param) end From d5f188e2e9640f65633f32c09344a178c1d1769d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 15:13:20 +1000 Subject: [PATCH 0676/1752] [O365 Lib] Only make a single bookings request for each room but use utc --- lib/microsoft/office.rb | 43 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e33ae422..b2369418 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -241,32 +241,33 @@ def delete_booking(booking_id:, current_user:) def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) # Allow passing in epoch, time string or ruby Time class - start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] - end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] + start_param = ensure_ruby_date(start_param).utc.iso8601.split("+")[0] + end_param = ensure_ruby_date(end_param).utc.iso8601.split("+")[0] # Array of all bookings within our period recurring_bookings = get_recurring_bookings_by_user(user_id, start_param, end_param) - endpoint = "/v1.0/users/#{user_id}/events" + # endpoint = "/v1.0/users/#{user_id}/events" - query_hash = {} - query_hash['$top'] = "200" - - # Build our query to only get bookings within our datetimes - if not start_param.nil? - query_hash['$filter'.to_sym] = "(Start/DateTime le '#{start_param}' and End/DateTime ge '#{start_param}') or (Start/DateTime ge '#{start_param}' and Start/DateTime le '#{end_param}')" - end - - bookings_response = graph_request(request_method: 'get', endpoint: endpoint, query: query_hash, password: @delegated) - check_response(bookings_response) - bookings = JSON.parse(bookings_response.body)['value'] - if bookings.nil? - all_bookings = recurring_bookings - else - bookings.concat recurring_bookings - all_bookings = bookings - end - all_bookings.each do |booking| + # query_hash = {} + # query_hash['$top'] = "200" + + # # Build our query to only get bookings within our datetimes + # if not start_param.nil? + # query_hash['$filter'.to_sym] = "(Start/DateTime le '#{start_param}' and End/DateTime ge '#{start_param}') or (Start/DateTime ge '#{start_param}' and Start/DateTime le '#{end_param}')" + # end + + # bookings_response = graph_request(request_method: 'get', endpoint: endpoint, query: query_hash, password: @delegated) + # check_response(bookings_response) + # bookings = JSON.parse(bookings_response.body)['value'] + # if bookings.nil? + # all_bookings = recurring_bookings + # else + # bookings.concat recurring_bookings + # all_bookings = bookings + # end + + recurring_bookings.each do |booking| start_object = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) end_object = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) booking['Start'] = start_object.utc.iso8601 From 873eed0282e096bc23e898d4e89b6f35c3c514cf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 15:15:24 +1000 Subject: [PATCH 0677/1752] [O365 Lib] Only make a single bookings request for each room but use utc --- lib/microsoft/office.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b2369418..249d0ed5 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -302,7 +302,6 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end - all_bookings end def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) From fa8afc4bd89175582592fbbb58cad73d5c35c030 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 15:30:13 +1000 Subject: [PATCH 0678/1752] Fix fields passed back from booking get --- lib/microsoft/office.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 249d0ed5..9472c6cb 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -289,11 +289,6 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 booking['old_attendees'] = booking['attendees'] booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} booking['attendees'] = new_attendees - STDERR.puts !booking.key?('location') - STDERR.puts booking['locations'] - STDERR.puts !booking['locations'].empty? - STDERR.puts booking['locations'][0]['uniqueId'] - STDERR.flush if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] booking['room_id'] = booking['locations'][0]['uniqueId'].downcase end From 76af0033c9bca1b4a2ab93eb8d40dfd0c259d5f9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 16:00:54 +1000 Subject: [PATCH 0679/1752] [O365 Lib] Add booking_id alias for id --- lib/microsoft/office.rb | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 9472c6cb..e7483f4f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -247,26 +247,6 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 # Array of all bookings within our period recurring_bookings = get_recurring_bookings_by_user(user_id, start_param, end_param) - # endpoint = "/v1.0/users/#{user_id}/events" - - # query_hash = {} - # query_hash['$top'] = "200" - - # # Build our query to only get bookings within our datetimes - # if not start_param.nil? - # query_hash['$filter'.to_sym] = "(Start/DateTime le '#{start_param}' and End/DateTime ge '#{start_param}') or (Start/DateTime ge '#{start_param}' and Start/DateTime le '#{end_param}')" - # end - - # bookings_response = graph_request(request_method: 'get', endpoint: endpoint, query: query_hash, password: @delegated) - # check_response(bookings_response) - # bookings = JSON.parse(bookings_response.body)['value'] - # if bookings.nil? - # all_bookings = recurring_bookings - # else - # bookings.concat recurring_bookings - # all_bookings = bookings - # end - recurring_bookings.each do |booking| start_object = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) end_object = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) @@ -275,6 +255,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 booking['start_epoch'] = start_object.to_i booking['end_epoch'] = end_object.to_i booking['title'] = booking['subject'] + booking['booking_id'] = booking['id'] new_attendees = [] booking['attendees'].each do |attendee| if attendee['type'] == 'resource' From b11bc198a027aaf0dfc9c1cd4413fcdc319c2038 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 22:29:51 +1000 Subject: [PATCH 0680/1752] [O365 Lib] Add bulk bookings retreival request --- lib/microsoft/office.rb | 168 ++++++++++++++++++++++++++++++++-------- 1 file changed, 136 insertions(+), 32 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e7483f4f..3594419b 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -103,6 +103,55 @@ def graph_request(request_method:, endpoint:, data:nil, query:{}, headers:nil, p return response_value end + + def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers:nil, password:false) + query = Hash(query) + headers = Hash(headers) + + if password + headers['Authorization'] = "Bearer #{password_graph_token}" + else + headers['Authorization'] = "Bearer #{graph_token}" + end + + # Set our unchanging headers + headers['Content-Type'] = ENV['GRAPH_CONTENT_TYPE'] || "application/json" + headers['Prefer'] = ENV['GRAPH_PREFER'] || 'outlook.timezone="Australia/Sydney"' + + graph_path = "#{@graph_domain}/v1.0/$batch" + query_string = "?#{query.map { |k,v| "#{k}=#{v}" }.join('&')}" + + request_array = [] + endpoints.each_with_index do |endpoint, i| + request_array.push({ + id: i + 1, + method: request_method.upcase, + url: "#{endpoint}#{query_string}" + }) + end + bulk_data = { + requests: request_array + }.to_json + + graph_api_options = {inactivity_timeout: 25000, keepalive: false} + + if @internet_proxy + proxy = URI.parse(@internet_proxy) + graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } + end + + graph_api = UV::HttpEndpoint.new(@graph_domain, graph_api_options) + response = graph_api.__send__('post', path: graph_path, headers: headers, body: bulk_data) + + start_timing = Time.now.to_i + response_value = response.value + end_timing = Time.now.to_i + STDERR.puts "Bulk Graph request took #{end_timing - start_timing} seconds" + STDERR.flush + return response_value + end + + def log_graph_request(request_method, data, query, headers, graph_path, password) STDERR.puts "--------------NEW GRAPH REQUEST------------" STDERR.puts "#{request_method} to #{graph_path}" @@ -239,48 +288,77 @@ def delete_booking(booking_id:, current_user:) 200 end - def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week)) + def get_bookings_by_user(user_ids:, start_param:Time.now, end_param:(Time.now + 1.week), bulk: false) + # The user_ids param can be passed in as a string or array but is always worked on as an array + user_ids = Array(user_ids) + # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).utc.iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).utc.iso8601.split("+")[0] # Array of all bookings within our period - recurring_bookings = get_recurring_bookings_by_user(user_id, start_param, end_param) - - recurring_bookings.each do |booking| - start_object = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) - end_object = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) - booking['Start'] = start_object.utc.iso8601 - booking['End'] = end_object.utc.iso8601 - booking['start_epoch'] = start_object.to_i - booking['end_epoch'] = end_object.to_i - booking['title'] = booking['subject'] - booking['booking_id'] = booking['id'] - new_attendees = [] - booking['attendees'].each do |attendee| - if attendee['type'] == 'resource' - booking['room_id'] = attendee['emailAddress']['address'].downcase - else - new_attendees.push({ - email: attendee['emailAddress']['address'], - name: attendee['emailAddress']['name'] - }) - end - end - booking['old_attendees'] = booking['attendees'] - booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} - booking['attendees'] = new_attendees - if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] - booking['room_id'] = booking['locations'][0]['uniqueId'].downcase + if bulk + recurring_bookings = bookings_request_by_users(user_ids, start_param, end_param) + else + recurring_bookings = bookings_request_by_user(user_ids, start_param, end_param) + end + + recurring_bookings.each do |user_id, bookings| + bookings.each_with_index do |booking, i| + bookings[i] = extract_booking_data(booking) end - if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? - booking['room_name'] = booking['location']['displayName'] + end + + if bulk + return recurring_bookings + else + return recurring_bookings[user_id] + end + end + + def extract_booking_data(booking) + # Create time objects of the start and end for easier use + start_object = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) + end_object = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) + + # Grab the start and end in the right format for the frontend + booking['Start'] = start_object.utc.iso8601 + booking['End'] = end_object.utc.iso8601 + booking['start_epoch'] = start_object.to_i + booking['end_epoch'] = end_object.to_i + + # Get some data about the booking + booking['title'] = booking['subject'] + booking['booking_id'] = booking['id'] + + # Format the attendees and save the old format + new_attendees = [] + booking['attendees'].each do |attendee| + if attendee['type'] == 'resource' + booking['room_id'] = attendee['emailAddress']['address'].downcase + else + new_attendees.push({ + email: attendee['emailAddress']['address'], + name: attendee['emailAddress']['name'] + }) end + end + booking['old_attendees'] = booking['attendees'] + booking['attendees'] = new_attendees + # Get the organiser and location data + booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} + if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] + booking['room_id'] = booking['locations'][0]['uniqueId'].downcase + end + if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? + booking['room_name'] = booking['location']['displayName'] end + + booking end - def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) + def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] @@ -298,7 +376,33 @@ def get_recurring_bookings_by_user(user_id, start_param=Time.now, end_param=(Tim recurring_response = graph_request(request_method: 'get', endpoint: recurring_endpoint, query: query_hash, password: @delegated) check_response(recurring_response) - recurring_bookings = JSON.parse(recurring_response.body)['value'] + recurring_bookings = {} + recurring_booking[user_id] = JSON.parse(recurring_response.body)['value'] + recurring_bookings + end + + def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.now + 1.week)) + # Allow passing in epoch, time string or ruby Time class + start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] + end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] + + endpoints = user_ids.map do |email| + "/users/#{email}/calendarView" + end + query = { + '$top': 200, + startDateTime: start_param, + endDateTime: end_param, + } + bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) + + check_response(bulk_response) + responses = JSON.parse(recurring_response.body)['responses'] + recurring_bookings = {} + responses.each_with_index do |res, i| + recurring_bookings[user_ids[res['id'].to_i]] = res['body']['value'] + end + recurring_bookings end def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1.week)) From d273cefb682f9adcb7bd85be3266345af795ed13 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 22:40:39 +1000 Subject: [PATCH 0681/1752] [O365 Lib] Add bulk bookings retreival request --- lib/microsoft/office.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 3594419b..ead90642 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -288,9 +288,9 @@ def delete_booking(booking_id:, current_user:) 200 end - def get_bookings_by_user(user_ids:, start_param:Time.now, end_param:(Time.now + 1.week), bulk: false) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), bulk: false) # The user_ids param can be passed in as a string or array but is always worked on as an array - user_ids = Array(user_ids) + user_id = Array(user_id) # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).utc.iso8601.split("+")[0] @@ -298,9 +298,9 @@ def get_bookings_by_user(user_ids:, start_param:Time.now, end_param:(Time.now + # Array of all bookings within our period if bulk - recurring_bookings = bookings_request_by_users(user_ids, start_param, end_param) + recurring_bookings = bookings_request_by_users(user_id, start_param, end_param) else - recurring_bookings = bookings_request_by_user(user_ids, start_param, end_param) + recurring_bookings = bookings_request_by_user(user_id, start_param, end_param) end recurring_bookings.each do |user_id, bookings| From 207361897e19809b5629614871523540589dd418 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 22:42:21 +1000 Subject: [PATCH 0682/1752] [O365 Lib] Add debugging --- lib/microsoft/office.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index ead90642..4aa7682e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -171,6 +171,9 @@ def check_response(response) when 200, 201, 204 return when 400 + STDERR.puts "GOT ERROR" + STDERR.puts response.inspect + STDERR.flush if response['error']['code'] == 'ErrorInvalidIdMalformed' raise Microsoft::Error::ErrorInvalidIdMalformed.new(response.body) else From 07832a882ff3f8f5b0dbd123bc3c75108be2aa27 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 22:47:11 +1000 Subject: [PATCH 0683/1752] [O365 Lib] Fix syntax issue --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4aa7682e..ed4dbae6 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -400,7 +400,7 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) check_response(bulk_response) - responses = JSON.parse(recurring_response.body)['responses'] + responses = JSON.parse(bulk_response.body)['responses'] recurring_bookings = {} responses.each_with_index do |res, i| recurring_bookings[user_ids[res['id'].to_i]] = res['body']['value'] From 5a59d7e7d77ce26f910cf9e4b12b65660adca34c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 23:18:12 +1000 Subject: [PATCH 0684/1752] [O365 Lib] Add availability to booking check --- lib/microsoft/office.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index ed4dbae6..9f7450f7 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -307,9 +307,12 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end recurring_bookings.each do |user_id, bookings| + is_available = true bookings.each_with_index do |booking, i| - bookings[i] = extract_booking_data(booking) + bookings[i] = extract_booking_data(booking, start_param, end_param) + is_available = bookings[i]['free'] end + recurring_bookings[user_id] = is_available end if bulk @@ -319,11 +322,21 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end - def extract_booking_data(booking) + def extract_booking_data(booking, start_param, end_param) # Create time objects of the start and end for easier use start_object = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) end_object = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) + # Check if this means the room is unavailable + booking_overlaps_start = start_object < start_param && end_object > end_param + booking_in_between = start_object >= start_param && end_object <= end_param + booking_overlaps_end = start_object < end_param && end_object > end_param + if booking_overlaps_start || booking_in_between || booking_overlaps_end + booking['free'] = false + else + booking['free'] = true + end + # Grab the start and end in the right format for the frontend booking['Start'] = start_object.utc.iso8601 booking['End'] = end_object.utc.iso8601 From 32a81d507de02bed5ee212b6e3a88ab3f7a02792 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 23:20:42 +1000 Subject: [PATCH 0685/1752] [O365 Lib] Add availability to booking check --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 9f7450f7..32075721 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -312,13 +312,13 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 bookings[i] = extract_booking_data(booking, start_param, end_param) is_available = bookings[i]['free'] end - recurring_bookings[user_id] = is_available + recurring_bookings[user_id] = {available: is_available, bookings: bookings} end if bulk return recurring_bookings else - return recurring_bookings[user_id] + return recurring_bookings[user_id][:bookings] end end From 8ca3584c107c1f1d611e6e104a7baf4cf8547f56 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 23:28:35 +1000 Subject: [PATCH 0686/1752] [O365 Lib] Fix naming --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 32075721..58dd8339 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -306,13 +306,13 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 recurring_bookings = bookings_request_by_user(user_id, start_param, end_param) end - recurring_bookings.each do |user_id, bookings| + recurring_bookings.each do |u_id, bookings| is_available = true bookings.each_with_index do |booking, i| bookings[i] = extract_booking_data(booking, start_param, end_param) is_available = bookings[i]['free'] end - recurring_bookings[user_id] = {available: is_available, bookings: bookings} + recurring_bookings[u_id] = {available: is_available, bookings: bookings} end if bulk From 2636a006b999c3a4909ae1d99b47e4e7007dffb3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 23 Jul 2018 23:39:12 +1000 Subject: [PATCH 0687/1752] [O365 Lib] Fix naming --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 58dd8339..59df9abd 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -124,7 +124,7 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers request_array = [] endpoints.each_with_index do |endpoint, i| request_array.push({ - id: i + 1, + id: i, method: request_method.upcase, url: "#{endpoint}#{query_string}" }) @@ -318,7 +318,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 if bulk return recurring_bookings else - return recurring_bookings[user_id][:bookings] + return recurring_bookings[user_id[0]][:bookings] end end From 6dba0bcbeaa1ebf43f1b8453bc03b6d1f1a16c55 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 26 Jul 2018 11:14:46 +1000 Subject: [PATCH 0688/1752] (qsc:remote protocol) fix compatibility with other mixers --- modules/qsc/q_sys_remote.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/qsc/q_sys_remote.rb b/modules/qsc/q_sys_remote.rb index fef1f31b..9df473a2 100644 --- a/modules/qsc/q_sys_remote.rb +++ b/modules/qsc/q_sys_remote.rb @@ -323,8 +323,8 @@ def fader(fader_id, level, component = nil, type = :fader, use_value: false) end end - def faders(ids:, level:, component: nil, type: :fader, **_) - fader(ids, level, component, type) + def faders(ids:, level:, index: nil, type: :fader, **_) + fader(ids, level, index, type) end def mute(fader_id, value = true, component = nil, type = :fader) @@ -332,8 +332,8 @@ def mute(fader_id, value = true, component = nil, type = :fader) fader(fader_id, val, component, type, use_value: true) end - def mutes(ids:, muted: true, component: nil, type: :fader, **_) - mute(ids, muted, component, type) + def mutes(ids:, muted: true, index: nil, type: :fader, **_) + mute(ids, muted, index, type) end def unmute(fader_id, component = nil, type = :fader) @@ -350,7 +350,7 @@ def query_fader(fader_id, component = nil, type = :fader) end end - def query_faders(ids:, component: nil, type: :fader, **_) + def query_faders(ids:, index: nil, type: :fader, **_) query_fader(ids, component, type) end @@ -358,7 +358,7 @@ def query_mute(fader_id, component = nil, type = :fader) query_fader(fader_id, component, type) end - def query_mutes(ids:, component: nil, type: :fader, **_) + def query_mutes(ids:, index: nil, type: :fader, **_) query_fader(ids, component, type) end From 7940f28898a7b04139e012d13618f489410f7919 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Jul 2018 12:38:08 +1000 Subject: [PATCH 0689/1752] [o365 lib] Fix single user lookup syntax --- lib/microsoft/office.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 59df9abd..a0495281 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -375,6 +375,9 @@ def extract_booking_data(booking, start_param, end_param) end def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) + if user_id.class == Array + user_id = user_id[0] + end # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] From 9c6e3f1252c7b42e4cdcf4b36168e8cf8c30d481 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Jul 2018 13:15:11 +1000 Subject: [PATCH 0690/1752] [o365 lib] Fix single user lookup syntax --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a0495281..8f2563de 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -396,7 +396,7 @@ def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now recurring_response = graph_request(request_method: 'get', endpoint: recurring_endpoint, query: query_hash, password: @delegated) check_response(recurring_response) recurring_bookings = {} - recurring_booking[user_id] = JSON.parse(recurring_response.body)['value'] + recurring_bookings[user_id] = JSON.parse(recurring_response.body)['value'] recurring_bookings end From 0fb8bc47bdb275b94fa0bf8377c6e77533725906 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Jul 2018 14:27:49 +1000 Subject: [PATCH 0691/1752] Update office365 sip driver --- modules/aca/office_booking.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index de2486fe..76552856 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -175,8 +175,7 @@ def on_update graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", service_account_email: @office_user_password || ENV['OFFICE_ACCOUNT_EMAIL'], service_account_password: @office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], - internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'], - delegated: @office_delegated || false + internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'] }) else logger.warn "oauth2 gem not available" if setting(:office_creds) From 6d96eff14a551ef11be56155bbdee35b35313688 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Jul 2018 14:38:10 +1000 Subject: [PATCH 0692/1752] [Exchange driver] Move system check to outside task --- modules/aca/exchange_booking.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index c2480e0b..7341acf9 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -359,8 +359,9 @@ def order_complete # ====================================== def fetch_bookings(first=false) logger.debug { "looking up todays emails for #{@ews_room}" } + skype_exists = system.exists?(:Skype) task { - todays_bookings(first) + todays_bookings(first, skype_exists) }.then(proc { |bookings| self[:today] = bookings if @check_meeting_ending @@ -683,7 +684,7 @@ def delete_ews_booking(delete_at) count end - def todays_bookings(first=false) + def todays_bookings(first=false, skype_exists=false) now = Time.now if @timezone start = now.in_time_zone(@timezone).midnight @@ -707,7 +708,7 @@ def todays_bookings(first=false) items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) end - skype_exists = set_skype_url = system.exists?(:Skype) + set_skype_url = skype_exists set_skype_url = true if @force_skype_extract now_int = now.to_i From 04bd111a4e6e87d0445e699158c86efe49cc81be Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 27 Jul 2018 14:44:16 +1000 Subject: [PATCH 0693/1752] [office driver] fix graph api format --- modules/aca/office_booking.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 76552856..b830894c 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -636,10 +636,10 @@ def todays_bookings(response, office_organiser_location) if office_organiser_location == 'attendees' # Grab the first attendee - organizer = booking['attendees'][0]['emailAddress']['name'] + organizer = booking['attendees'][0]['name'] else # Grab the organiser - organizer = booking['organizer']['emailAddress']['name'] + organizer = booking['organizer']['name'] end results.push({ From 64875a6af2cf1d8f9803ab8bdc4bafe775bba62b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 11:11:22 +1000 Subject: [PATCH 0694/1752] Fix small bug in availability request --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8f2563de..af92f6e5 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -243,7 +243,7 @@ def get_available_rooms(rooms:, start_param:, end_param:) end_ruby_param = ensure_ruby_date((end_param || (now + 1.hour))) duration_string = "PT#{end_ruby_param.to_i-start_ruby_param.to_i}S" start_param = start_ruby_param.utc.iso8601.split("+")[0] - end_param = (end_ruby_param + 30.minutes).utc.iso8601.split("+")[0] + end_param = end_ruby_param.utc.iso8601.split("+")[0] # Add the attendees rooms.map!{|room| From 4894ecca2345b23843780810446dac91c0484c0c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 11:24:07 +1000 Subject: [PATCH 0695/1752] Fix small bug in availability request --- lib/microsoft/office.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index af92f6e5..8cad42d6 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -274,7 +274,8 @@ def get_available_rooms(rooms:, start_param:, end_param:) timeConstraint: time_constraint, maxCandidates: 1000, returnSuggestionReasons: true, - meetingDuration: duration_string + meetingDuration: duration_string, + isOrganizerOptional: true }.to_json From ff54c58abe74cff5732545cdd1dbb7fb3610c34e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 11:51:26 +1000 Subject: [PATCH 0696/1752] Add bindings to office driver --- modules/aca/office_booking.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index b830894c..4d7e2374 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -118,6 +118,8 @@ def on_update self[:booking_hide_user] = setting(:booking_hide_user) self[:booking_hide_description] = setting(:booking_hide_description) self[:booking_hide_timeline] = setting(:booking_hide_timeline) + self[:booking_endable] = setting(:booking_endable) + self[:timeout] = setting(:timeout) # Skype join button available 2min before the start of a meeting @skype_start_offset = setting(:skype_start_offset) || 120 From 1dd22711ac1a6aee5097ba437ef60e759bdc1be9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 12:13:59 +1000 Subject: [PATCH 0697/1752] [O365 Lib] Access current user email through square brackets --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8cad42d6..4511d2b2 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -440,7 +440,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil if @mailbox_location == 'room' endpoint = "/v1.0/users/#{room.email}/events" elsif @mailbox_location == 'user' - endpoint = "/v1.0/users/#{current_user.email}/events" + endpoint = "/v1.0/users/#{current_user[:email]}/events" end # Ensure our start and end params are Ruby dates and format them in Graph format From abfe9d4d2257eeb77a2803d35d066ae5d0d666c9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 12:14:58 +1000 Subject: [PATCH 0698/1752] [O365 Lib] Access current user email through square brackets --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4511d2b2..454563af 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -437,7 +437,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil # Get our room room = Orchestrator::ControlSystem.find(room_id) - if @mailbox_location == 'room' + if @mailbox_location == 'room' || current_user.nil? endpoint = "/v1.0/users/#{room.email}/events" elsif @mailbox_location == 'user' endpoint = "/v1.0/users/#{current_user[:email]}/events" From 074f8a52c4aac9af7f569d6cf21e52a95692c6c8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 14:09:10 +1000 Subject: [PATCH 0699/1752] [Office driver] Add ID to bookings to delete --- modules/aca/office_booking.rb | 70 +++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 4d7e2374..35b68e6f 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -349,7 +349,13 @@ def start_meeting(meeting_ref) def cancel_meeting(start_time) task { - delete_ews_booking (start_time / 1000).to_i + if start_time.class == Integer + delete_ews_booking (start_time / 1000).to_i + else + # Converts to time object regardless of start_time being string or time object + start_time = Time.parse(start_time.to_s) + delete_ews_booking start_time.to_i + end }.then(proc { |count| logger.debug { "successfully removed #{count} bookings" } @@ -585,40 +591,49 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em end def delete_ews_booking(delete_at) - now = Time.now - if @timezone - start = now.in_time_zone(@timezone).midnight - ending = now.in_time_zone(@timezone).tomorrow.midnight - else - start = now.midnight - ending = now.tomorrow.midnight - end + # now = Time.now + # if @timezone + # start = now.in_time_zone(@timezone).midnight + # ending = now.in_time_zone(@timezone).tomorrow.midnight + # else + # start = now.midnight + # ending = now.tomorrow.midnight + # end count = 0 - cli = Viewpoint::EWSClient.new(*@ews_creds) + # cli = Viewpoint::EWSClient.new(*@ews_creds) - if @use_act_as - # TODO:: think this line can be removed?? - delete_at = Time.parse(delete_at.to_s).to_i + # if @use_act_as + # # TODO:: think this line can be removed?? + # delete_at = Time.parse(delete_at.to_s).to_i - opts = {} - opts[:act_as] = @ews_room if @ews_room + # opts = {} + # opts[:act_as] = @ews_room if @ews_room - folder = cli.get_folder(:calendar, opts) - items = folder.items({:calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) - else - cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room - items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) - end + # folder = cli.get_folder(:calendar, opts) + # items = folder.items({:calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) + # else + # cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room + # items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) + # end - items.each do |meeting| - meeting_time = Time.parse(meeting.ews_item[:start][:text]) + # items.each do |meeting| + # meeting_time = Time.parse(meeting.ews_item[:start][:text]) - # Remove any meetings that match the start time provided - if meeting_time.to_i == delete_at - meeting.delete!(:recycle, send_meeting_cancellations: 'SendOnlyToAll') - count += 1 + # # Remove any meetings that match the start time provided + # if meeting_time.to_i == delete_at + # meeting.delete!(:recycle, send_meeting_cancellations: 'SendOnlyToAll') + # count += 1 + # end + # end + delete_at_object = Time.at(delete_at).utc.iso8601 + if self[:today] + self[:today].each do |booking| + if delete_at_object == booking[:Start] + response = @client.delete_booking(booking_id: booking[:id], current_user: system) + count += 1 if response == 200 + end end end @@ -648,6 +663,7 @@ def todays_bookings(response, office_organiser_location) :Start => start_time, :End => end_time, :Subject => booking['subject'], + :id => booking['id'], :owner => organizer }) } From 76114f188e8b9c2ee5a2a1850ca6e7ab2046a7b3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 14:10:42 +1000 Subject: [PATCH 0700/1752] [Office driver] Add ID to bookings to delete --- modules/aca/office_booking.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 35b68e6f..a85253dc 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -580,9 +580,10 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em # response = office_api.post(path: "#{domain}#{endpoint}", body: booking_data, headers: headers).value response = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: nil) - logger.debug response.body - logger.debug response.to_json - logger.debug response['id'] + STDERR.puts "BOOKING SIP CREATE RESPONSE:" + STDERR.puts response.inspect + STDERR.puts response['id'] + STDERR.flush id = response['id'] From 3bb7ac791a7021a10e816c0ea1fcce10a41a9fee Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 14:55:53 +1000 Subject: [PATCH 0701/1752] Remove tasks if not required --- modules/aca/office_booking.rb | 36 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index a85253dc..0b69d6b5 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -326,12 +326,7 @@ def order_complete def fetch_bookings(*args) # Make the request response = @client.get_bookings_by_user(user_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight) - - task { - todays_bookings(response, @office_organiser_location) - }.then(proc { |bookings| - self[:today] = bookings - }, proc { |e| logger.print_error(e, 'error fetching bookings') }) + self[:today] = todays_bookings(response, @office_organiser_location) end @@ -348,24 +343,17 @@ def start_meeting(meeting_ref) end def cancel_meeting(start_time) - task { - if start_time.class == Integer - delete_ews_booking (start_time / 1000).to_i - else - # Converts to time object regardless of start_time being string or time object - start_time = Time.parse(start_time.to_s) - delete_ews_booking start_time.to_i - end - }.then(proc { |count| - logger.debug { "successfully removed #{count} bookings" } - - self[:last_meeting_started] = start_time - self[:meeting_pending] = start_time - self[:meeting_ending] = false - self[:meeting_pending_notice] = false - }, proc { |error| - logger.print_error error, 'removing ews booking' - }) + if start_time.class == Integer + delete_ews_booking (start_time / 1000).to_i + else + # Converts to time object regardless of start_time being string or time object + start_time = Time.parse(start_time.to_s) + delete_ews_booking start_time.to_i + end + self[:last_meeting_started] = start_time + self[:meeting_pending] = start_time + self[:meeting_ending] = false + self[:meeting_pending_notice] = false end # If last meeting started !== meeting pending then From b552bc5492b5f90dc1f44aae14ead86e24159838 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 15:10:05 +1000 Subject: [PATCH 0702/1752] [O365 Driver] Add logging helper --- modules/aca/office_booking.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 0b69d6b5..40e5a52b 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -619,7 +619,7 @@ def delete_ews_booking(delete_at) delete_at_object = Time.at(delete_at).utc.iso8601 if self[:today] self[:today].each do |booking| - if delete_at_object == booking[:Start] + if delete_at_object.to_i == booking[:Start].to_i response = @client.delete_booking(booking_id: booking[:id], current_user: system) count += 1 if response == 200 end @@ -659,5 +659,11 @@ def todays_bookings(response, office_organiser_location) results end + + def log(msg) + STDERR.puts msg + logger.info msg + STDERR.flush + end # ======================================= end From 2526fe900dc693e23cb268da5bbe5929e5a3ecc5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 15:32:32 +1000 Subject: [PATCH 0703/1752] [O365 Driver] Add logging helper --- modules/aca/office_booking.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 40e5a52b..416fda03 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -618,10 +618,13 @@ def delete_ews_booking(delete_at) # end delete_at_object = Time.at(delete_at).utc.iso8601 if self[:today] - self[:today].each do |booking| + self[:today].each_with_index do |booking, i| if delete_at_object.to_i == booking[:Start].to_i response = @client.delete_booking(booking_id: booking[:id], current_user: system) - count += 1 if response == 200 + if response == 200 + count += 1 + self[:today].delete(i) + end end end end From 33b75e302c261b165093afedca8d3b85afcb742d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 16:31:58 +1000 Subject: [PATCH 0704/1752] [O365 lib] Pass availability to show method --- lib/microsoft/office.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 454563af..42807933 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -292,6 +292,7 @@ def delete_booking(booking_id:, current_user:) 200 end + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), bulk: false) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) @@ -319,7 +320,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 if bulk return recurring_bookings else - return recurring_bookings[user_id[0]][:bookings] + return recurring_bookings[user_id[0]] end end From 39a7be2079a9822e5c452b56f47cb512d05bdcc0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 17:04:15 +1000 Subject: [PATCH 0705/1752] [O365 Lib] Fix small logic issue --- lib/microsoft/office.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 42807933..5c3807eb 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -326,13 +326,13 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 def extract_booking_data(booking, start_param, end_param) # Create time objects of the start and end for easier use - start_object = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) - end_object = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) + booking_start = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) + booking_end = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) # Check if this means the room is unavailable - booking_overlaps_start = start_object < start_param && end_object > end_param - booking_in_between = start_object >= start_param && end_object <= end_param - booking_overlaps_end = start_object < end_param && end_object > end_param + booking_overlaps_start = booking_start < start_param && booking_end > start_param + booking_in_between = booking_start >= start_param && booking_end <= end_param + booking_overlaps_end = booking_start < end_param && booking_end > end_param if booking_overlaps_start || booking_in_between || booking_overlaps_end booking['free'] = false else @@ -340,10 +340,10 @@ def extract_booking_data(booking, start_param, end_param) end # Grab the start and end in the right format for the frontend - booking['Start'] = start_object.utc.iso8601 - booking['End'] = end_object.utc.iso8601 - booking['start_epoch'] = start_object.to_i - booking['end_epoch'] = end_object.to_i + booking['Start'] = booking_start.utc.iso8601 + booking['End'] = booking_end.utc.iso8601 + booking['start_epoch'] = booking_start.to_i + booking['end_epoch'] = booking_end.to_i # Get some data about the booking booking['title'] = booking['subject'] From 2c92b7b1ae8a36ba768511aebcc84322b4777452 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 30 Jul 2018 17:24:24 +1000 Subject: [PATCH 0706/1752] O365 driver fix format of booking response --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 416fda03..a47018cb 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -325,7 +325,7 @@ def order_complete # ====================================== def fetch_bookings(*args) # Make the request - response = @client.get_bookings_by_user(user_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight) + response = @client.get_bookings_by_user(user_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] self[:today] = todays_bookings(response, @office_organiser_location) end From ec1ee3422c389550e3015013d36c7dcd984e9325 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 2 Aug 2018 13:23:53 +1000 Subject: [PATCH 0707/1752] [O365 Lib] Fix single user get method --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 5c3807eb..8dd2db7e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -211,7 +211,7 @@ def get_users(q: nil, limit: nil) def get_user(user_id:) endpoint = "/v1.0/users/#{user_id}" - request = graph_request('get', endpoint) + request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) check_response(request) JSON.parse(request.body) end From e0d960a6468fcc014a534eeda2e94f64e379b838 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 2 Aug 2018 13:32:37 +1000 Subject: [PATCH 0708/1752] [O365 Lib] add user check method --- lib/microsoft/office.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8dd2db7e..99ad30e9 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -216,6 +216,16 @@ def get_user(user_id:) JSON.parse(request.body) end + def has_user(user_id:) + endpoint = "/v1.0/users/#{user_id}" + request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) + if [200, 201, 204].include?(request.status) + return true + else + return false + end + end + def get_rooms(q: nil, limit: nil) filter_param = "startswith(name,'#{q}') or startswith(address,'#{q}')" if q query_params = { From 8b2aedec9ac3ea84363c238739ba4d6d2c6ec56c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 3 Aug 2018 07:59:26 +1000 Subject: [PATCH 0709/1752] (aca:router) symbolize all id's in graph interactions --- modules/aca/router.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 47e6f362..fab800ab 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -156,6 +156,9 @@ def load_from_map(connections) # Find the shortest path between between two nodes and return a list of the # nodes which this passes through and their connecting edges. def route(source, sink) + source = source.to_sym + sink = sink.to_sym + path = paths[sink] distance = path.distance_to[source] @@ -406,6 +409,7 @@ def initialize end def [](id) + id = id.to_sym nodes[id] end @@ -422,6 +426,7 @@ def insert(id) # to false to keep this O(1) rather than O(n). Using this flag at any other # time will result a corrupt structure. def delete(id, check_incoming_edges: true) + id = id.to_sym nodes.delete(id) { raise ArgumentError, "\"#{id}\" does not exist" } each { |node| node.edges.delete id } if check_incoming_edges self @@ -440,6 +445,7 @@ def each(&block) end def include?(id) + id = id.to_sym nodes.key? id end @@ -448,6 +454,7 @@ def node_ids end def successors(id) + id = id.to_sym nodes[id].edges.keys end @@ -460,12 +467,14 @@ def sinks end def incoming_edges(id) + id = id.to_sym each_with_object([]) do |node, edges| edges << node.edges[id] if node.edges.key? id end end def outgoing_edges(id) + id = id.to_sym nodes[id].edges.values end @@ -478,6 +487,8 @@ def outdegree(id) end def dijkstra(id) + id = id.to_sym + active = Containers::PriorityQueue.new { |x, y| (x <=> y) == -1 } distance_to = Hash.new { 1.0 / 0.0 } predecessor = {} From 2e549ee64e18e8442b822578819637e6da76e05f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 8 Aug 2018 01:50:00 +1000 Subject: [PATCH 0710/1752] (cisco:ce) fix issue with return of sparse id based arrays --- modules/cisco/collaboration_endpoint/xapi/response.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/xapi/response.rb b/modules/cisco/collaboration_endpoint/xapi/response.rb index daf51b4f..7568209d 100644 --- a/modules/cisco/collaboration_endpoint/xapi/response.rb +++ b/modules/cisco/collaboration_endpoint/xapi/response.rb @@ -37,7 +37,11 @@ def compress(fragment) fragment.transform_values { |item| compress item } end when Array - fragment.map { |item| compress item } + fragment.each_with_object({}) do |item, h| + id = item.delete(:id) + id = id.is_a?(String) && id[/^\d+$/]&.to_i || id + h[id] = compress item + end else fragment end From 22274482ea69c7caa9d937ae9d7cdf9443eae1f8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 8 Aug 2018 01:51:41 +1000 Subject: [PATCH 0711/1752] (cisco:ce) preserve partial device states when binding to upper levels of the tree --- modules/cisco/collaboration_endpoint/room_os.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index fc199113..9d6212b7 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -373,6 +373,8 @@ def load_settings # Bind arbitary device feedback to a status variable. def bind_feedback(path, status_key) register_feedback path do |value| + value = self[status_key].deep_merge value \ + if self[status_key].is_a?(Hash) && value.is_a?(Hash) self[status_key] = value end end From e97dbaf7021dfa1fe2577433ffd3c8fa98f28212 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 12:08:38 +1000 Subject: [PATCH 0712/1752] Fix attendee organizer check --- modules/aca/office_booking.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index a47018cb..1d5bbc2c 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -645,7 +645,11 @@ def todays_bookings(response, office_organiser_location) if office_organiser_location == 'attendees' # Grab the first attendee - organizer = booking['attendees'][0]['name'] + if booking.key?('attendees') && !booking['attendees'].empty? + organizer = booking['attendees'][0]['name'] + else + organizer = "" + end else # Grab the organiser organizer = booking['organizer']['name'] From e946c0a254f553a1fbcbba3846a0d2575753c2eb Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 12:33:19 +1000 Subject: [PATCH 0713/1752] [O365 Driver] Fix deletion check --- modules/aca/office_booking.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 1d5bbc2c..ff9fbd2e 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -619,7 +619,8 @@ def delete_ews_booking(delete_at) delete_at_object = Time.at(delete_at).utc.iso8601 if self[:today] self[:today].each_with_index do |booking, i| - if delete_at_object.to_i == booking[:Start].to_i + booking_start_object = Time.parse(booking[:Start]) + if delete_at_object.to_i == booking_start_object.to_i response = @client.delete_booking(booking_id: booking[:id], current_user: system) if response == 200 count += 1 From c41bafbbec6db71f0a53926f89dd33929b6d5c66 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 12:49:54 +1000 Subject: [PATCH 0714/1752] [O365 Driver] Fix deletion check --- modules/aca/office_booking.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index ff9fbd2e..4a149fcf 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -616,11 +616,18 @@ def delete_ews_booking(delete_at) # count += 1 # end # end - delete_at_object = Time.at(delete_at).utc.iso8601 + delete_at_object = Time.parse(delete_at).utc.iso8601 if self[:today] self[:today].each_with_index do |booking, i| booking_start_object = Time.parse(booking[:Start]) + log("---- DELETING A BOOKING ----") + log("DELETE AT OBJECT TO INT IS") + log(delete_at_object.to_i) + log("BOOKING START OBJECT TO INT IS") + log("---- ------------------ ----") + log(booking_start_object.to_i) if delete_at_object.to_i == booking_start_object.to_i + log("MATCHED AND NOW DELETING") response = @client.delete_booking(booking_id: booking[:id], current_user: system) if response == 200 count += 1 From b2578523726004b1288cffae6616348a8b494d67 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 12:53:55 +1000 Subject: [PATCH 0715/1752] [O365 Driver] Fix deletion check --- modules/aca/office_booking.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 4a149fcf..68744b35 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -580,6 +580,9 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em end def delete_ews_booking(delete_at) + log("DELETE AT IS") + log(delete_at) + log(delete_at.class) # now = Time.now # if @timezone # start = now.in_time_zone(@timezone).midnight @@ -616,7 +619,7 @@ def delete_ews_booking(delete_at) # count += 1 # end # end - delete_at_object = Time.parse(delete_at).utc.iso8601 + delete_at_object = Time.at(delete_at).utc.iso8601 if self[:today] self[:today].each_with_index do |booking, i| booking_start_object = Time.parse(booking[:Start]) From 8ae9053864889fe7bd0dc6d9358fac51c7d6e7e5 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 12:56:11 +1000 Subject: [PATCH 0716/1752] [O365 Driver] Fix deletion check --- modules/aca/office_booking.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 68744b35..b40b13f7 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -619,7 +619,7 @@ def delete_ews_booking(delete_at) # count += 1 # end # end - delete_at_object = Time.at(delete_at).utc.iso8601 + delete_at_object = Time.at(delete_at) if self[:today] self[:today].each_with_index do |booking, i| booking_start_object = Time.parse(booking[:Start]) @@ -627,8 +627,8 @@ def delete_ews_booking(delete_at) log("DELETE AT OBJECT TO INT IS") log(delete_at_object.to_i) log("BOOKING START OBJECT TO INT IS") - log("---- ------------------ ----") log(booking_start_object.to_i) + log("---- ------------------ ----") if delete_at_object.to_i == booking_start_object.to_i log("MATCHED AND NOW DELETING") response = @client.delete_booking(booking_id: booking[:id], current_user: system) From af39fbacbbf5443bffc13984fb1ba08a8e3f0d2b Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 12:57:45 +1000 Subject: [PATCH 0717/1752] [O365 Driver] Fix deletion check --- modules/aca/office_booking.rb | 45 ----------------------------------- 1 file changed, 45 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index b40b13f7..a00f6470 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -580,57 +580,12 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em end def delete_ews_booking(delete_at) - log("DELETE AT IS") - log(delete_at) - log(delete_at.class) - # now = Time.now - # if @timezone - # start = now.in_time_zone(@timezone).midnight - # ending = now.in_time_zone(@timezone).tomorrow.midnight - # else - # start = now.midnight - # ending = now.tomorrow.midnight - # end - count = 0 - - # cli = Viewpoint::EWSClient.new(*@ews_creds) - - # if @use_act_as - # # TODO:: think this line can be removed?? - # delete_at = Time.parse(delete_at.to_s).to_i - - # opts = {} - # opts[:act_as] = @ews_room if @ews_room - - # folder = cli.get_folder(:calendar, opts) - # items = folder.items({:calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) - # else - # cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room - # items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) - # end - - # items.each do |meeting| - # meeting_time = Time.parse(meeting.ews_item[:start][:text]) - - # # Remove any meetings that match the start time provided - # if meeting_time.to_i == delete_at - # meeting.delete!(:recycle, send_meeting_cancellations: 'SendOnlyToAll') - # count += 1 - # end - # end delete_at_object = Time.at(delete_at) if self[:today] self[:today].each_with_index do |booking, i| booking_start_object = Time.parse(booking[:Start]) - log("---- DELETING A BOOKING ----") - log("DELETE AT OBJECT TO INT IS") - log(delete_at_object.to_i) - log("BOOKING START OBJECT TO INT IS") - log(booking_start_object.to_i) - log("---- ------------------ ----") if delete_at_object.to_i == booking_start_object.to_i - log("MATCHED AND NOW DELETING") response = @client.delete_booking(booking_id: booking[:id], current_user: system) if response == 200 count += 1 From 85cf8ce475e8b0e0788aa15843a00cf78d8532d5 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 13:03:20 +1000 Subject: [PATCH 0718/1752] [O365 Lib] Add private flag for meetings --- lib/microsoft/office.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 99ad30e9..ed2ee1e4 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -441,7 +441,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, timezone:'Sydney') + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney') description = String(description) attendees = Array(attendees) @@ -541,6 +541,10 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil } end + if is_private + event[:sensitivity] = 'private' + end + event = event.to_json request = graph_request(request_method: 'post', endpoint: endpoint, data: event, password: @delegated) From 9bd53baa24cb18fd445f021366b90ccfc54d135a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 8 Aug 2018 13:57:03 +1000 Subject: [PATCH 0719/1752] (aca:router) fix issue where promise from partial recall wouldn't resolve --- modules/aca/router.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index fab800ab..ef68c5f0 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -78,7 +78,9 @@ def connect(signal_map, atomic: false, force: false) else logger.warn 'signal map partially activated' edge_map.transform_values do |routes| - routes.select { |_, edges| success.superset? edges }.keys + routes.each_with_object([]) do |(output, edges), completed| + completed << output if success.superset? Set.new(edges) + end end end end From f105de8a76393f3e9f0abcaec08815171261718c Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 14:04:44 +1000 Subject: [PATCH 0720/1752] [Office Driver] Add private event support --- modules/aca/office_booking.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index a00f6470..0abe7525 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -620,11 +620,17 @@ def todays_bookings(response, office_organiser_location) # Grab the organiser organizer = booking['organizer']['name'] end + + subject = booking['subject'] + if booking.key?('sensitivity') && ['private','confidential'].include?(booking['sensitivity']) + organizer = "" + subject = "" + end results.push({ :Start => start_time, :End => end_time, - :Subject => booking['subject'], + :Subject => subject, :id => booking['id'], :owner => organizer }) From 55d460d30b968f1482fc623af9e2bf2a89eda151 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 14:23:38 +1000 Subject: [PATCH 0721/1752] [O365 Lib] Add support to get availability by flag --- lib/microsoft/office.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index ed2ee1e4..6256ab1d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -303,7 +303,7 @@ def delete_booking(booking_id:, current_user:) end - def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), bulk: false) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), bulk: false, availability: true) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) @@ -330,7 +330,11 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 if bulk return recurring_bookings else - return recurring_bookings[user_id[0]] + if availability + return recurring_bookings[user_id[0]] + else + return recurring_bookings[user_id[0]][:bookings] + end end end From ca762b6d6fbae163a1cd23e3483544568120809a Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 15:08:25 +1000 Subject: [PATCH 0722/1752] Add availability check to booking grab --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 6256ab1d..f3576552 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -303,7 +303,7 @@ def delete_booking(booking_id:, current_user:) end - def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), bulk: false, availability: true) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) @@ -321,7 +321,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 recurring_bookings.each do |u_id, bookings| is_available = true bookings.each_with_index do |booking, i| - bookings[i] = extract_booking_data(booking, start_param, end_param) + bookings[i] = extract_booking_data(booking, available_from, available_to) is_available = bookings[i]['free'] end recurring_bookings[u_id] = {available: is_available, bookings: bookings} From 0766ddb67ba3584580a70b518bdf9cfd491e6b2e Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 16:21:54 +1000 Subject: [PATCH 0723/1752] Fix logic in availability check --- lib/microsoft/office.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f3576552..7ba1a276 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -321,8 +321,10 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 recurring_bookings.each do |u_id, bookings| is_available = true bookings.each_with_index do |booking, i| - bookings[i] = extract_booking_data(booking, available_from, available_to) - is_available = bookings[i]['free'] + bookings[i] = extract_booking_data(booking, available_from, end_param) + if bookings[i]['free'] == false + is_available = false + end end recurring_bookings[u_id] = {available: is_available, bookings: bookings} end From d9d219a945792f9ff020c9d13a377519edcdc3ab Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 23:43:49 +1000 Subject: [PATCH 0724/1752] [o365 lib] Add get booking method --- lib/microsoft/office.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 7ba1a276..a3775812 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -295,6 +295,13 @@ def get_available_rooms(rooms:, start_param:, end_param:) JSON.parse(request.body) end + def get_booking(booking_id:, mailbox:) + endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" + request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) + check_response(request) + JSON.parse(request.body) + end + def delete_booking(booking_id:, current_user:) endpoint = "/v1.0/users/#{current_user.email}/events/#{booking_id}" request = graph_request(request_method: 'delete', endpoint: endpoint, password: @delegated) From 6c962e7255c08dfb63e585a4cf4cc5f4cf477ad8 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 8 Aug 2018 23:47:40 +1000 Subject: [PATCH 0725/1752] [o365 lib] Add get booking method --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a3775812..f16a4ea2 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -302,8 +302,8 @@ def get_booking(booking_id:, mailbox:) JSON.parse(request.body) end - def delete_booking(booking_id:, current_user:) - endpoint = "/v1.0/users/#{current_user.email}/events/#{booking_id}" + def delete_booking(booking_id:, mailbox:) + endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" request = graph_request(request_method: 'delete', endpoint: endpoint, password: @delegated) check_response(request) 200 From 2e47b1783f3e20d5cf96d98a9ee0455bee360609 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 9 Aug 2018 16:40:24 +1000 Subject: [PATCH 0726/1752] Pass in room email not room id --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f16a4ea2..623b76f8 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -569,7 +569,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, timezone:'Sydney') # We will always need a room and endpoint passed in - room = Orchestrator::ControlSystem.find(room_id) + room = Orchestrator::ControlSystem.find_by_email(room_id) endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" STDERR.puts "ENDPOINT IS" STDERR.puts endpoint From 49734bcd12d8617d6664e8e96f78a8ddcca90239 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 9 Aug 2018 16:43:27 +1000 Subject: [PATCH 0727/1752] Pass in room email not room id --- lib/microsoft/office.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 623b76f8..af9bee5a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -574,6 +574,13 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec STDERR.puts "ENDPOINT IS" STDERR.puts endpoint STDERR.flush + + + start_object = ensure_ruby_date(start_param).in_time_zone(timezone) + end_object = ensure_ruby_date(end_param).in_time_zone(timezone) + start_param = ensure_ruby_date(start_param).in_time_zone(timezone).iso8601.split("+")[0] + end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] + event = {} event[:subject] = subject if subject From c239805eefaec45f5a957a72160e3c2e6499e792 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 9 Aug 2018 16:48:05 +1000 Subject: [PATCH 0728/1752] Pass in room email not room id --- lib/microsoft/office.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index af9bee5a..e15b88b0 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -567,20 +567,27 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil response = JSON.parse(request.body) end - def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, timezone:'Sydney') + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney') # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find_by_email(room_id) - endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" + + + if @mailbox_location == 'room' || current_user.nil? + endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" + elsif @mailbox_location == 'user' + endpoint = "/v1.0/users/#{current_user[:email]}/events/#{booking_id}" + end + STDERR.puts "ENDPOINT IS" STDERR.puts endpoint STDERR.flush - + start_object = ensure_ruby_date(start_param).in_time_zone(timezone) end_object = ensure_ruby_date(end_param).in_time_zone(timezone) start_param = ensure_ruby_date(start_param).in_time_zone(timezone).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] - + event = {} event[:subject] = subject if subject From 7e90c5ce27ef3e7d36ff3c1ecfe1f58d5952868c Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 9 Aug 2018 16:53:40 +1000 Subject: [PATCH 0729/1752] Pass in room email not room id --- lib/microsoft/office.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e15b88b0..6b98d512 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -608,12 +608,23 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec # Let's assume that the request has the current user and room as an attendee already event[:attendees] = attendees.map{|a| - { emailAddress: { + { + emailAddress: { address: a[:email], name: a[:name] - } } + }, + type: 'required' + } } if attendees + event[:attendees].push({ + emailAddress: { + address: room.email, + name: room.name + }, + type: 'resource' + }) + request = graph_request(request_method: 'patch', endpoint: endpoint, data: event.to_json, password: @delegated) check_response(request) response = JSON.parse(request.body)['value'] From 4b86db3f445f71c9677d6f60961ae74670fe246c Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 9 Aug 2018 22:45:03 +1000 Subject: [PATCH 0730/1752] [O365 lib] Add optional attendee support --- lib/microsoft/office.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 6b98d512..31fbda69 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -608,12 +608,19 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec # Let's assume that the request has the current user and room as an attendee already event[:attendees] = attendees.map{|a| + @item.property = params[:property] ? true : false + if a[:optional] + attendee_type = 'optional' + else + attendee_type = 'required' + end + attendee_type = a[:option] { emailAddress: { address: a[:email], name: a[:name] }, - type: 'required' + type: attendee_type } } if attendees From 8a0c348335517c7fbcd00d158755a80560a64b3b Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 9 Aug 2018 13:49:11 +0100 Subject: [PATCH 0731/1752] [O365 lib] Fix availability bug --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 31fbda69..6415d136 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -328,7 +328,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 recurring_bookings.each do |u_id, bookings| is_available = true bookings.each_with_index do |booking, i| - bookings[i] = extract_booking_data(booking, available_from, end_param) + bookings[i] = extract_booking_data(booking, available_from, available_to) if bookings[i]['free'] == false is_available = false end From 2b6523b0033ec83b5c7a9b5a486eb88f7dbebb3e Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 14:08:58 +1000 Subject: [PATCH 0732/1752] [EWS Lib] add param alias for start --- lib/microsoft/exchange.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 8203d624..0eda14aa 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -219,8 +219,10 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:id] = event.id # booking[:start_date] = event.start.utc.iso8601 # booking[:end_date] = event.end.utc.iso8601 - booking[:start_date] = event.start.to_i * 1000 - booking[:end_date] = event.end.to_i * 1000 + # booking[:start_date] = event.start.to_i * 1000 + # booking[:end_date] = event.end.to_i * 1000 + booking[:Start] = event.start.utc.iso8601 + booking[:End] = event.end.utc.iso8601 booking[:body] = event.body booking[:organiser] = { name: event.organizer.name, From 368a5b5b13023bf2ea70ca9abde677d366b2c6d3 Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 14:11:28 +1000 Subject: [PATCH 0733/1752] [EWS Lib] add param alias for start --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 0eda14aa..53e79fd9 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -219,8 +219,8 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:id] = event.id # booking[:start_date] = event.start.utc.iso8601 # booking[:end_date] = event.end.utc.iso8601 - # booking[:start_date] = event.start.to_i * 1000 - # booking[:end_date] = event.end.to_i * 1000 + booking[:start_epoch] = event.start.to_i + booking[:end_epoch] = event.end.to_i booking[:Start] = event.start.utc.iso8601 booking[:End] = event.end.utc.iso8601 booking[:body] = event.body From 629d31614464a641a31e5d969718d2c39c25bd8e Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 14:19:07 +1000 Subject: [PATCH 0734/1752] [EWS Lib] add param alias for start --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 53e79fd9..47d3ad43 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -217,8 +217,8 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:subject] = event.subject booking[:title] = event.subject booking[:id] = event.id - # booking[:start_date] = event.start.utc.iso8601 - # booking[:end_date] = event.end.utc.iso8601 + booking[:start_date] = event.start.to_i * 1000 + booking[:end_date] = event.end.to_i * 1000 booking[:start_epoch] = event.start.to_i booking[:end_epoch] = event.end.to_i booking[:Start] = event.start.utc.iso8601 From ee619693656792747206fe26fdb585302ce78daf Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 14:20:17 +1000 Subject: [PATCH 0735/1752] [EWS Lib] add param alias for start --- lib/microsoft/exchange.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 47d3ad43..8203d624 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -217,12 +217,10 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:subject] = event.subject booking[:title] = event.subject booking[:id] = event.id + # booking[:start_date] = event.start.utc.iso8601 + # booking[:end_date] = event.end.utc.iso8601 booking[:start_date] = event.start.to_i * 1000 booking[:end_date] = event.end.to_i * 1000 - booking[:start_epoch] = event.start.to_i - booking[:end_epoch] = event.end.to_i - booking[:Start] = event.start.utc.iso8601 - booking[:End] = event.end.utc.iso8601 booking[:body] = event.body booking[:organiser] = { name: event.organizer.name, From bd83deb216ead252a771cabeb3077d634563ea26 Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 14:52:23 +1000 Subject: [PATCH 0736/1752] [EWS Lib] Set start param --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 8203d624..b8c5a2fb 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -219,8 +219,8 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:id] = event.id # booking[:start_date] = event.start.utc.iso8601 # booking[:end_date] = event.end.utc.iso8601 - booking[:start_date] = event.start.to_i * 1000 - booking[:end_date] = event.end.to_i * 1000 + booking[:start_epoch] = event.start.to_i + booking[:end_epoch] = event.end.to_i booking[:body] = event.body booking[:organiser] = { name: event.organizer.name, From b2f6cc66827b4a815d88674485701252f95a9350 Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 14:55:10 +1000 Subject: [PATCH 0737/1752] [office Lib] Set start param --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 6415d136..7c624146 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -363,8 +363,8 @@ def extract_booking_data(booking, start_param, end_param) end # Grab the start and end in the right format for the frontend - booking['Start'] = booking_start.utc.iso8601 - booking['End'] = booking_end.utc.iso8601 + # booking['Start'] = booking_start.utc.iso8601 + # booking['End'] = booking_end.utc.iso8601 booking['start_epoch'] = booking_start.to_i booking['end_epoch'] = booking_end.to_i From f4398d1fe77b9e1105282851b885d7e5caf6b51c Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 15:20:47 +1000 Subject: [PATCH 0738/1752] [EWS Lib] Change params again --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index b8c5a2fb..d3c54427 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -219,8 +219,8 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:id] = event.id # booking[:start_date] = event.start.utc.iso8601 # booking[:end_date] = event.end.utc.iso8601 - booking[:start_epoch] = event.start.to_i - booking[:end_epoch] = event.end.to_i + booking[:start] = event.start.to_i * 1000 + booking[:end] = event.end.to_i * 1000 booking[:body] = event.body booking[:organiser] = { name: event.organizer.name, From f9778ac8c923c5236a03b9b9d7e79a9d1a1ec8b1 Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 15:30:30 +1000 Subject: [PATCH 0739/1752] [EWS Lib] Change params again --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index d3c54427..932e99fd 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -280,8 +280,8 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: }] # Add start and end times - booking[:start] = Time.at(start_param.to_i / 1000).utc.iso8601.chop - booking[:end] = Time.at(end_param.to_i / 1000).utc.iso8601.chop + booking[:start] = Time.at(start_param.to_i).utc.iso8601.chop + booking[:end] = Time.at(end_param.to_i).utc.iso8601.chop # Add the current user passed in as an attendee mailbox = { email_address: current_user.email } From 35acca436acddab9e0d5bf4e18c4260f45d2b2dc Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 15:38:31 +1000 Subject: [PATCH 0740/1752] [EWS Lib] Add room ID to requests --- lib/microsoft/exchange.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 932e99fd..f416d78d 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -195,6 +195,8 @@ def get_available_rooms(rooms:, start_time:, end_time:) def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime.now.midnight + 2.days), use_act_as: false) begin + # Get all the room emails + room_emails = Orchestrator::ControlSystem.all.to_a.map { |sys| sys.email } if [Integer, String].include?(start_param.class) start_param = DateTime.parse(Time.at(start_param.to_i / 1000).to_s) end_param = DateTime.parse(Time.at(end_param.to_i / 1000).to_s) @@ -226,7 +228,10 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. name: event.organizer.name, email: event.organizer.email } - booking[:attendees] = event.required_attendees.map {|attendee| + booking[:attendees] = event.required_attendees.map {|attendee| + if room_emails.include?(attendee.email) + booking[:room_id] = attendee.email + end { name: attendee.name, email: attendee.email From 6744a8dfe427c96ab337b0079ec5360eed4b7c82 Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 15:40:21 +1000 Subject: [PATCH 0741/1752] [EWS Lib] Add room ID to requests --- lib/microsoft/exchange.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index f416d78d..feb2a0f1 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -105,6 +105,7 @@ def get_users(q: nil, limit: nil) output['name'] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] end output['email'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] + output['id'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] users.push(output) rescue => e STDERR.puts "GOT USER WITHOUT EMAIL" From 28172e1c7468ba48f69dc167829bf43e0f2aa0ff Mon Sep 17 00:00:00 2001 From: Cam Date: Fri, 10 Aug 2018 15:50:31 +1000 Subject: [PATCH 0742/1752] [EWS Lib] Add room ID to requests --- lib/microsoft/exchange.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index feb2a0f1..b4e67525 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -105,7 +105,6 @@ def get_users(q: nil, limit: nil) output['name'] = user[:resolution][:elems][0][:mailbox][:elems][0][:name][:text] end output['email'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] - output['id'] = user[:resolution][:elems][0][:mailbox][:elems][1][:email_address][:text] users.push(output) rescue => e STDERR.puts "GOT USER WITHOUT EMAIL" @@ -232,12 +231,13 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:attendees] = event.required_attendees.map {|attendee| if room_emails.include?(attendee.email) booking[:room_id] = attendee.email + next end { name: attendee.name, email: attendee.email } - } if event.required_attendees + }.compact if event.required_attendees if @hide_all_day_bookings STDERR.puts "SKIPPING #{event.subject}" STDERR.flush From 3998385d05796be724e18db9dd0f8cae58a0252d Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 13 Aug 2018 10:47:58 +1000 Subject: [PATCH 0743/1752] added wolfvision driver --- modules/wolfvision/eye14.rb | 164 ++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 modules/wolfvision/eye14.rb diff --git a/modules/wolfvision/eye14.rb b/modules/wolfvision/eye14.rb new file mode 100644 index 00000000..4248bec1 --- /dev/null +++ b/modules/wolfvision/eye14.rb @@ -0,0 +1,164 @@ +module Wolfvision; end + +# Documentation: https://www.wolfvision.com/wolf/protocol_command_wolfvision/protocol/commands_eye-14.pdf + +class Wolfvision::Eye14 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 50915 # Need to go through an RS232 gatway + descriptive_name 'WolfVision EYE-14' + generic_name :Camera + + # Communication settings + tokenize indicator: /\x00|\x01|/, callback: :check_length + delay between_sends: 150 + + def on_load + self[:zoom_max] = 3923 + self[:iris_max] = 4094 + self[:zoom_min] = self[:iris_min] = 0 + on_update + end + + def on_update + power? + end + + def on_unload + end + + def connected + schedule.every('60s') do + logger.debug "-- Polling Sony Camera" + power? do + if self[:power] == On + zoom? + iris? + autofocus? + end + end + end + end + + def disconnected + # Disconnected will be called before connect if initial connect fails + schedule.clear + end + + def power(state) + target = is_affirmative?(state) + self[:power_target] = target + + # Execute command + logger.debug { "Target = #{target} and self[:power] = #{self[:power]}" } + if target == On && self[:power] != On + send_cmd("\x30\x01\x01", name: :power_cmd) + elsif target == Off && self[:power] != Off + send_cmd("\x30\x01\x00", name: :power_cmd) + end + end + + # uses only optical zoom + def zoom(position) + val = in_range(position, self[:zoom_max], self[:zoom_min]) + self[:zoom_target] = val + val = sprintf("%04X", val) + logger.debug { "position in decimal is #{position} and hex is #{val}" } + send_cmd("\x20\x02#{hex_to_byte(val)}", name: :zoom_cmd) + end + + def zoom? + send_inq("\x20\x00", priority: 0, name: :zoom_inq) + end + + # set autofocus to on + def autofocus(state) + send_cmd("\x31\x01\01", name: :autofocus_cmd) + end + + def autofocus? + send_inq("\x31\00", priority: 0, name: :autofocus_inq) + end + + def iris(position) + val = in_range(position, self[:iris_max], self[:iris_min]) + self[:iris_target] = val + val = sprintf("%04X", val) + logger.debug { "position in decimal is #{position} and hex is #{val}" } + send_cmd("\x22\x02#{hex_to_byte(val)}", name: :iris_cmd) + end + + def iris? + send_inq("\x22\x00", priority: 0, name: :iris_inq) + end + + def power?() + send_inq("\x30\x00", priority: 0, name: :power_inq) + end + + def send_cmd(cmd, options = {}) + req = "\x01#{cmd}" + logger.debug { "tell -- 0x#{byte_to_hex(req)} -- #{options[:name]}" } + send(req, options) + end + + def send_inq(inq, options = {}) + req = "\x00#{inq}" + logger.debug { "ask -- 0x#{byte_to_hex(req)} -- #{options[:name]}" } + send(req, options) + end + + def received(data, deferrable, command) + logger.debug { "Received 0x#{byte_to_hex(data)}\n#{command}" } + + bytes = str_to_array(data) + + if command && !command[:name].nil? + case command[:name] + when :power_cmd + self[:power] = self[:power_target] if byte_to_hex(data) == "3000" + when :zoom_cmd + self[:zoom] = self[:zoom_target] if byte_to_hex(data) == "2000" + when :iris_cmd + self[:iris] = self[:iris_target] if byte_to_hex(data) == "2200" + when :power_inq + # -1 index for array refers to the last element in Ruby + self[:power] = bytes[-1] == 1 + when :zoom_inq +=begin + for some reason the after changing the zoom position + the first zoom inquiry sends "2000" regardless of the actaul zoom value + consecutive zoom inquiries will then return the correct zoom value +=end + return :ignore if byte_to_hex(data) == "2000" + hex = byte_to_hex(data[-2..-1]) + self[:zoom] = hex.to_i(16) + when :autofocus_inq + self[:autofocus] = bytes[-1] == 1 + when :iris_inq + # same thing as zoom inq happens here + return :ignore if byte_to_hex(data) == "2200" + hex = byte_to_hex(data[-2..-1]) + self[:iris] = hex.to_i(16) + end + end + + return :success + end + + def check_length(byte_str) + response = str_to_array(byte_str) + + return false if response.length <= 2 # header is 2 bytes + + len = response[1] + 2 # (data length + header) + + if response.length >= len + return len + else + return false + end + end +end From 33057d70bd06b34b9e2c10aaaae92cf4294cc717 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 13 Aug 2018 13:02:47 +1000 Subject: [PATCH 0744/1752] "bug fixes" --- modules/wolfvision/eye14.rb | 8 ++-- modules/wolfvision/eye14_spec.rb | 72 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 modules/wolfvision/eye14_spec.rb diff --git a/modules/wolfvision/eye14.rb b/modules/wolfvision/eye14.rb index 4248bec1..da869fdc 100644 --- a/modules/wolfvision/eye14.rb +++ b/modules/wolfvision/eye14.rb @@ -23,7 +23,7 @@ def on_load end def on_update - power? + #power? end def on_unload @@ -142,16 +142,18 @@ def received(data, deferrable, command) return :ignore if byte_to_hex(data) == "2200" hex = byte_to_hex(data[-2..-1]) self[:iris] = hex.to_i(16) + else + return :ignore end + return :success end - return :success end def check_length(byte_str) response = str_to_array(byte_str) - return false if response.length <= 2 # header is 2 bytes + return false if response.length <= 1 # header is 2 bytes len = response[1] + 2 # (data length + header) diff --git a/modules/wolfvision/eye14_spec.rb b/modules/wolfvision/eye14_spec.rb new file mode 100644 index 00000000..b261ad41 --- /dev/null +++ b/modules/wolfvision/eye14_spec.rb @@ -0,0 +1,72 @@ +Orchestrator::Testing.mock_device 'Wolfvision::Eye14' do +=begin + should_send("\x00\x30\x00") + transmit("\x00\x30\x01\x00") # tell driver device is off +=end + exec(:power?) + .should_send("\x00\x30\x00") # power query + .responds("\x00\x30\x01\x01") # respond with on + .expect(status[:power]).to be(true) + + wait(150) + + exec(:power, false) + .should_send("\x01\x30\x01\x00") # turn off device + .responds("\x01\x30\x00") # respond with success + .expect(status[:power]).to be(false) + + wait(150) + + exec(:zoom, 6) + .should_send("\x01\x20\x02\x00\x06") + .transmit("\x01\x20\x00") + +=begin + exec(:zoom, 6) + .should_send("\x01\x20\x02\x00\x06") + .transmit("\x01\x20\x00") + + transmit("anything") +=begin + expect(status[:power]).to be(true) + + exec(:zoom, 6) + transmit("\x00\x20\x00") +=end + +=begin + exec(:power, false) # turn on the device + .should_send("\x01\x30\x01\x00") + .responds("\x01\x30\x00") + + exec(:zoom, 6) + .should_send("\x01\x20\x02\x00\x06") + .responds("\x00\x20\x00") + + + exec(:power?) + .should_send("\x00\x30\x00") # power query + .responds("\x00\x30\x01\x01") # respond with on + .expect(status[:power]).to be(true) + + exec(:power, false) # turn off the device + transmit("\x00\x30\x01\x00") # respond with off + expect(status[:power]).to be(false) + + exec(:autofocus?) + transmit("\x00\x31\x01\x00") # respond with off + expect(status[:autofocus]).to be(false) + + exec(:zoom?) + .should_send("\x00\x20\x00") + .responds("\x00\x20\x02\xFF\xFF") + + exec(:zoom, "\xFF\xFF") + .should_send("\x01\x20\x02\xFF\xFF") + .responds("\x00\x20\x00") + + exec(:iris, 0xFFFF) + .should_send("\x01\x22\x02\xFF\xFF") + .responds("\x00\x22\x00") +=end +end From 6dfbdc0b89f9f1868f05beb32a46c8e8dba0240c Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 13 Aug 2018 13:32:33 +1000 Subject: [PATCH 0745/1752] more bug fixes and updated spec test --- modules/wolfvision/eye14.rb | 8 +-- modules/wolfvision/eye14_spec.rb | 93 ++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/modules/wolfvision/eye14.rb b/modules/wolfvision/eye14.rb index da869fdc..7a93740a 100644 --- a/modules/wolfvision/eye14.rb +++ b/modules/wolfvision/eye14.rb @@ -74,12 +74,12 @@ def zoom? end # set autofocus to on - def autofocus(state) - send_cmd("\x31\x01\01", name: :autofocus_cmd) + def autofocus() + send_cmd("\x31\x01\x01", name: :autofocus_cmd) end def autofocus? - send_inq("\x31\00", priority: 0, name: :autofocus_inq) + send_inq("\x31\x00", priority: 0, name: :autofocus_inq) end def iris(position) @@ -123,6 +123,8 @@ def received(data, deferrable, command) self[:zoom] = self[:zoom_target] if byte_to_hex(data) == "2000" when :iris_cmd self[:iris] = self[:iris_target] if byte_to_hex(data) == "2200" + when :autofocus_cmd + self[:autofocus] = true if byte_to_hex(data) == "3100" when :power_inq # -1 index for array refers to the last element in Ruby self[:power] = bytes[-1] == 1 diff --git a/modules/wolfvision/eye14_spec.rb b/modules/wolfvision/eye14_spec.rb index b261ad41..8e36b867 100644 --- a/modules/wolfvision/eye14_spec.rb +++ b/modules/wolfvision/eye14_spec.rb @@ -1,8 +1,4 @@ Orchestrator::Testing.mock_device 'Wolfvision::Eye14' do -=begin - should_send("\x00\x30\x00") - transmit("\x00\x30\x01\x00") # tell driver device is off -=end exec(:power?) .should_send("\x00\x30\x00") # power query .responds("\x00\x30\x01\x01") # respond with on @@ -17,56 +13,71 @@ wait(150) - exec(:zoom, 6) - .should_send("\x01\x20\x02\x00\x06") - .transmit("\x01\x20\x00") + exec(:power, true) + .should_send("\x01\x30\x01\x01") # turn off device + .responds("\x01\x30\x00") # respond with success + .expect(status[:power]).to be(true) + + wait(150) + + exec(:power?) + .should_send("\x00\x30\x00") # power query + .responds("\x00\x30\x01\x01") # respond with on + .expect(status[:power]).to be(true) + + wait(150) + + exec(:zoom?) + .should_send("\x00\x20\x00") + .responds("\x00\x20\x02\x00\x09") + .expect(status[:zoom]).to be(9) + + wait(150) -=begin exec(:zoom, 6) .should_send("\x01\x20\x02\x00\x06") .transmit("\x01\x20\x00") + .expect(status[:zoom]).to be(6) - transmit("anything") -=begin - expect(status[:power]).to be(true) + wait(150) - exec(:zoom, 6) - transmit("\x00\x20\x00") -=end + exec(:zoom?) + .should_send("\x00\x20\x00") + .responds("\x00\x20\x02\x00\x06") + .expect(status[:zoom]).to be(6) -=begin - exec(:power, false) # turn on the device - .should_send("\x01\x30\x01\x00") - .responds("\x01\x30\x00") + wait(150) - exec(:zoom, 6) - .should_send("\x01\x20\x02\x00\x06") - .responds("\x00\x20\x00") + exec(:iris?) + .should_send("\x00\x22\x00") + .responds("\x00\x22\x02\x00\x20") + .expect(status[:iris]).to be(32) + wait(150) - exec(:power?) - .should_send("\x00\x30\x00") # power query - .responds("\x00\x30\x01\x01") # respond with on - .expect(status[:power]).to be(true) + exec(:iris, 8) + .should_send("\x01\x22\x02\x00\x08") + .transmit("\x01\x22\x00") + .expect(status[:iris]).to be(8) - exec(:power, false) # turn off the device - transmit("\x00\x30\x01\x00") # respond with off - expect(status[:power]).to be(false) + wait(150) - exec(:autofocus?) - transmit("\x00\x31\x01\x00") # respond with off - expect(status[:autofocus]).to be(false) + exec(:iris?) + .should_send("\x00\x22\x00") + .responds("\x00\x22\x02\x00\x08") + .expect(status[:iris]).to be(8) - exec(:zoom?) - .should_send("\x00\x20\x00") - .responds("\x00\x20\x02\xFF\xFF") + wait(150) + + exec(:autofocus?) + .should_send("\x00\x31\x00") + .responds("\x00\x31\x01\x00") + .expect(status[:autofocus]).to be(false) - exec(:zoom, "\xFF\xFF") - .should_send("\x01\x20\x02\xFF\xFF") - .responds("\x00\x20\x00") + wait(150) - exec(:iris, 0xFFFF) - .should_send("\x01\x22\x02\xFF\xFF") - .responds("\x00\x22\x00") -=end + exec(:autofocus) + .should_send("\x01\x31\x01\x01") + .transmit("\x01\x31\x00") # respond with success + .expect(status[:autofocus]).to be(true) end From 980d9b2d445b5225631af6e449eae973af94e03f Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 13 Aug 2018 16:39:34 +1000 Subject: [PATCH 0746/1752] removed some debugging --- modules/wolfvision/eye14.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/wolfvision/eye14.rb b/modules/wolfvision/eye14.rb index 7a93740a..edc3358b 100644 --- a/modules/wolfvision/eye14.rb +++ b/modules/wolfvision/eye14.rb @@ -23,7 +23,6 @@ def on_load end def on_update - #power? end def on_unload @@ -111,7 +110,7 @@ def send_inq(inq, options = {}) end def received(data, deferrable, command) - logger.debug { "Received 0x#{byte_to_hex(data)}\n#{command}" } + logger.debug { "Received 0x#{byte_to_hex(data)}\n" } bytes = str_to_array(data) From 5338062c02ff749699635835673eff992237d143 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 14 Aug 2018 11:39:54 +1000 Subject: [PATCH 0747/1752] Pass 200 back from exchange delete method --- lib/microsoft/exchange.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index b4e67525..d7bf6365 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -396,6 +396,7 @@ def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, def delete_booking(id) booking = @ews_client.get_item(id) booking.delete!(:recycle, send_meeting_cancellations: "SendOnlyToAll") + 200 end # Takes a date of any kind (epoch, string, time object) and returns a time object From fb4d7aa48dfe6ac288a1c0ba1b53365f37bf856d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 14 Aug 2018 11:47:53 +1000 Subject: [PATCH 0748/1752] Pass 200 back from exchange delete method --- lib/microsoft/exchange.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index d7bf6365..39e487fd 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -393,7 +393,8 @@ def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, } end - def delete_booking(id) + def delete_booking(booking_id:, mailbox:) + @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], mailbox) booking = @ews_client.get_item(id) booking.delete!(:recycle, send_meeting_cancellations: "SendOnlyToAll") 200 From b1318f5607b06c1d4ed7cc448f0f56d0829ef855 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 14 Aug 2018 11:49:14 +1000 Subject: [PATCH 0749/1752] Pass 200 back from exchange delete method --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 39e487fd..0f081e59 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -395,7 +395,7 @@ def update_booking(booking_id:, room_email:nil, start_param:nil, end_param:nil, def delete_booking(booking_id:, mailbox:) @ews_client.set_impersonation(Viewpoint::EWS::ConnectingSID[:SMTP], mailbox) - booking = @ews_client.get_item(id) + booking = @ews_client.get_item(booking_id) booking.delete!(:recycle, send_meeting_cancellations: "SendOnlyToAll") 200 end From b6f2800603274a76ed610ff103c929b12ca0084d Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 14 Aug 2018 14:14:39 +1000 Subject: [PATCH 0750/1752] (lg:display) Add instructions for enabling network while powered off --- modules/lg/lcd/model_ls5.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/lg/lcd/model_ls5.rb b/modules/lg/lcd/model_ls5.rb index bb90b225..4f76f4ea 100644 --- a/modules/lg/lcd/model_ls5.rb +++ b/modules/lg/lcd/model_ls5.rb @@ -4,7 +4,10 @@ module Lg::Lcd; end # Documentation: https://aca.im/driver_docs/LG/LS5_models.pdf # also https://aca.im/driver_docs/LG/SM_models.pdf # -# There is a secret menu that allows you to disable power management +# To ensure that the display does not go network offline when told to power off, this setting needs to be set: +# General>Power>PM mode:Screen off always +# +# For older displays, the same setting is in a secret menu that is accessed via the IR remote: # 1. Press and hold the 'Setting' button on the remote for 7 seconds # 2. Press: 0 0 0 0 OK (Press Zero four times and then OK) # 3. From the signage setup, turn off DPM From f77c319bb52b66b09bc4a31c1c4222a8406fbfec Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 14 Aug 2018 14:18:34 +1000 Subject: [PATCH 0751/1752] (lg:display) Add instructions for enabling network while powered off --- modules/lg/lcd/model_ls5.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/lg/lcd/model_ls5.rb b/modules/lg/lcd/model_ls5.rb index 39f55376..f8e014d8 100644 --- a/modules/lg/lcd/model_ls5.rb +++ b/modules/lg/lcd/model_ls5.rb @@ -4,7 +4,10 @@ module Lg::Lcd; end # Documentation: https://aca.im/driver_docs/LG/LS5_models.pdf # also https://aca.im/driver_docs/LG/SM_models.pdf # -# There is a secret menu that allows you to disable power management +# To ensure that the display does not go network offline when told to power off, this setting needs to be set: +# General>Power>PM mode:Screen off always +# +# For older displays, the same setting is in a secret menu that is accessed via the IR remote: # 1. Press and hold the 'Setting' button on the remote for 7 seconds # 2. Press: 0 0 0 0 OK (Press Zero four times and then OK) # 3. From the signage setup, turn off DPM From 8bfa17899fd39f9ebffa7e5249d273763e053f60 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 14 Aug 2018 14:46:42 +1000 Subject: [PATCH 0752/1752] (lg:display) add pm_mode for network online during power off untested --- modules/lg/lcd/model_ls5.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/lg/lcd/model_ls5.rb b/modules/lg/lcd/model_ls5.rb index f8e014d8..2a979683 100644 --- a/modules/lg/lcd/model_ls5.rb +++ b/modules/lg/lcd/model_ls5.rb @@ -59,6 +59,7 @@ def connected no_signal_off(false) auto_off(false) local_button_lock(true) + pm_mode(3) do_poll end @@ -84,7 +85,8 @@ def disconnected no_signal_off: 'g', auto_off: 'n', dpm: 'j', - local_button_lock: 'o' + local_button_lock: 'o', + pm_mode: 'n' } Lookup = Command.invert @@ -229,6 +231,10 @@ def configure_dpm(time_out = 4) # The action should be set to: screen off always end + def pm_mode(mode = 3) + do_send(Command[:pm_mode], mode, :s, name: :pm_mode) + end + def local_button_lock(enable = true) #0=off, 1=lock all except Power buttons, 2=lock all buttons. Default to 2 as power off from local button results in network offline val = is_affirmative?(enable) ? 2 : 0 From e48e09bd86c1ec1a02053fc67811a1a16e6ec64c Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 14 Aug 2018 17:17:28 +1000 Subject: [PATCH 0753/1752] (cisco:collaboration) add presets and camera select --- modules/cisco/collaboration_endpoint/sx20.rb | 12 ++++++++++++ modules/cisco/collaboration_endpoint/sx80.rb | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 57d34c6f..3effa404 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -44,6 +44,11 @@ def connected status 'Video Output' => :video_output status 'Standby State' => :standby + def set_selfview(state) + state = is_affirmative?(state) ? 'On' : 'Off' + send_xcommand 'Video Selfview Set', Mode: state + end + command 'Audio Microphones Mute' => :mic_mute_on command 'Audio Microphones Unmute' => :mic_mute_off command 'Audio Microphones ToggleMute' => :mic_mute_toggle @@ -67,6 +72,13 @@ def mic_mute(state = On) CallRate_: (64..6000), CallType_: [:Audio, :Video] + command 'Camera Preset Activate Preset' => camera_preset, + PresetId: (1..15) + + command 'Camera Preset Store' => camera_store_preset, + CameraId: (1..7), + PresetId: (1..15) + command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..2), Axis_: [:All, :Focus, :PanTilt, :Zoom] diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 74d5db22..e9bd91af 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -72,6 +72,13 @@ def mic_mute(state = On) CallRate_: (64..6000), CallType_: [:Audio, :Video] + command 'Camera Preset Activate Preset' => camera_preset, + PresetId: (1..15) + + command 'Camera Preset Store' => camera_store_preset, + CameraId: (1..7), + PresetId: (1..15) + command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..7), Axis_: [:All, :Focus, :PanTilt, :Zoom] @@ -85,6 +92,15 @@ def mic_mute(state = On) ZoomSpeed_: (1..15), Focus_: [:Far, :Near, :Stop] + def camera_select(camera_id) + send_xcommand 'Video Input SetMainVideoSource', ConnectorId: camera_id + end + + def set_selfview(state) + state = is_affirmative?(state) ? 'On' : 'Off' + send_xcommand 'Video Selfview Set', Mode: state + end + command! 'Cameras AutoFocus Diagnostics Start' => \ :autofocus_diagnostics_start, CameraId: (1..7) From 5f87d7c12b1ef5728387abdd813152c9deebeede Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 14 Aug 2018 23:56:56 +1000 Subject: [PATCH 0754/1752] (cisco:ce) fixup bindings for camera control --- .../cisco/collaboration_endpoint/room_kit.rb | 21 +++++++++++++ modules/cisco/collaboration_endpoint/sx20.rb | 31 ++++++++++++------- modules/cisco/collaboration_endpoint/sx80.rb | 31 +++++++++++-------- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index a5f22dd4..5044371e 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -72,6 +72,15 @@ def mic_mute(state = On) CallRate_: (64..6000), CallType_: [:Audio, :Video] + command 'Camera Preset Activate' => :camera_preset, + PresetId: (1..35) + command 'Camera Preset Store' => :camera_store_preset, + CameraId: (1..1), + PresetId_: (1..35), # Optional - codec will auto-assign if omitted + Name_: String, + TakeSnapshot_: Boolean, + DefaultPosition_: Boolean + command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..1), Axis_: [:All, :Focus, :PanTilt, :Zoom] @@ -85,6 +94,18 @@ def mic_mute(state = On) ZoomSpeed_: (1..15), Focus_: [:Far, :Near, :Stop] + command 'Video Input SetMainVideoSource' => :camera_select, + ConnectorId_: (1..3), # Source can either be specified as the + Layout_: [:Equal, :PIP], # physical connector... + SourceId_: (1..3) # ...or the logical source ID + + command 'Video Selfview Set' => :selfview, + Mode_: [:On, :Off], + FullScreenMode_: [:On, :Off], + PIPPosition_: [:CenterLeft, :CenterRight, :LowerLeft, :LowerRight, + :UpperCenter, :UpperLeft, :UpperRight], + OnMonitorRole_: [:First, :Second, :Third, :Fourth] + command! 'Cameras AutoFocus Diagnostics Start' => \ :autofocus_diagnostics_start, CameraId: (1..1) diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 3effa404..e9099e20 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -44,11 +44,6 @@ def connected status 'Video Output' => :video_output status 'Standby State' => :standby - def set_selfview(state) - state = is_affirmative?(state) ? 'On' : 'Off' - send_xcommand 'Video Selfview Set', Mode: state - end - command 'Audio Microphones Mute' => :mic_mute_on command 'Audio Microphones Unmute' => :mic_mute_off command 'Audio Microphones ToggleMute' => :mic_mute_toggle @@ -72,12 +67,14 @@ def mic_mute(state = On) CallRate_: (64..6000), CallType_: [:Audio, :Video] - command 'Camera Preset Activate Preset' => camera_preset, - PresetId: (1..15) - - command 'Camera Preset Store' => camera_store_preset, - CameraId: (1..7), - PresetId: (1..15) + command 'Camera Preset Activate' => :camera_preset, + PresetId: (1..35) + command 'Camera Preset Store' => :camera_store_preset, + CameraId: (1..1), + PresetId_: (1..35), # Optional - codec will auto-assign if omitted + Name_: String, + TakeSnapshot_: Boolean, + DefaultPosition_: Boolean command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..2), @@ -92,6 +89,18 @@ def mic_mute(state = On) ZoomSpeed_: (1..15), Focus_: [:Far, :Near, :Stop] + command 'Video Input SetMainVideoSource' => :camera_select, + ConnectorId_: (1..3), # Source can either be specified as the + Layout_: [:Equal, :PIP], # physical connector... + SourceId_: (1..3) # ...or the logical source ID + + command 'Video Selfview Set' => :selfview, + Mode_: [:On, :Off], + FullScreenMode_: [:On, :Off], + PIPPosition_: [:CenterLeft, :CenterRight, :LowerLeft, :LowerRight, + :UpperCenter, :UpperLeft, :UpperRight], + OnMonitorRole_: [:First, :Second, :Third, :Fourth] + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index e9bd91af..469622b0 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -72,12 +72,14 @@ def mic_mute(state = On) CallRate_: (64..6000), CallType_: [:Audio, :Video] - command 'Camera Preset Activate Preset' => camera_preset, - PresetId: (1..15) - - command 'Camera Preset Store' => camera_store_preset, + command 'Camera Preset Activate' => :camera_preset, + PresetId: (1..35) + command 'Camera Preset Store' => :camera_store_preset, CameraId: (1..7), - PresetId: (1..15) + PresetId_: (1..35), # Optional - codec will auto-assign if omitted + Name_: String, + TakeSnapshot_: Boolean, + DefaultPosition_: Boolean command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..7), @@ -92,14 +94,17 @@ def mic_mute(state = On) ZoomSpeed_: (1..15), Focus_: [:Far, :Near, :Stop] - def camera_select(camera_id) - send_xcommand 'Video Input SetMainVideoSource', ConnectorId: camera_id - end - - def set_selfview(state) - state = is_affirmative?(state) ? 'On' : 'Off' - send_xcommand 'Video Selfview Set', Mode: state - end + command 'Video Input SetMainVideoSource' => :camera_select, + ConnectorId_: (1..5), # Source can either be specified as the + Layout_: [:Equal, :PIP], # physical connector... + SourceId_: (1..4) # ...or the logical source ID + + command 'Video Selfview Set' => :selfview, + Mode_: [:On, :Off], + FullScreenMode_: [:On, :Off], + PIPPosition_: [:CenterLeft, :CenterRight, :LowerLeft, :LowerRight, + :UpperCenter, :UpperLeft, :UpperRight], + OnMonitorRole_: [:First, :Second, :Third, :Fourth] command! 'Cameras AutoFocus Diagnostics Start' => \ :autofocus_diagnostics_start, From c1427a0d024a6fbbd88152c0c082e4c9d82a75fd Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 16 Aug 2018 13:43:49 +1000 Subject: [PATCH 0755/1752] (aca:router) fix exception if attempting to route through stopped modules --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index ef68c5f0..90fd2a70 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -280,7 +280,7 @@ def needs_activation?(edge, force: false) if mod.nil? && single_source fail_with['already on correct input'] \ - if edge.nx1? && mod[:input] == edge.input && !force + if edge.nx1? && mod && mod[:input] == edge.input && !force fail_with['has an incompatible api, but only a single input defined'] \ if edge.nx1? && !mod.respond_to?(:switch_to) && single_source From 7bedb674e176a45b6af77b1c9ed3c4fc84bc8c2b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 17 Aug 2018 14:46:57 +1000 Subject: [PATCH 0756/1752] Only use UTC in exchange --- lib/microsoft/exchange.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 0f081e59..50f8cbcd 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -126,9 +126,7 @@ def find_time(cal_event, time) start_time = nil elems.each do |item| if item[time] - Time.use_zone 'Sydney' do - start_time = Time.parse(item[time][:text]) - end + start_time = ActionController::TimeZone.new("UTC").parse(item[time][:text]) break end end @@ -137,13 +135,14 @@ def find_time(cal_event, time) def get_available_rooms(rooms:, start_time:, end_time:) free_rooms = [] - + start_time = start_time.utc + end_time = end_time.utc STDERR.puts "Getting available rooms with" STDERR.puts start_time STDERR.puts end_time STDERR.flush - rooms.each_slice(99).each do |room_subset| + rooms.each_slice(30).each do |room_subset| # Get booking data for all rooms between time range bounds user_free_busy = @ews_client.get_user_availability(room_subset, @@ -151,9 +150,9 @@ def get_available_rooms(rooms:, start_time:, end_time:) end_time: end_time, requested_view: :detailed, time_zone: { - bias: -600, + bias: -0, standard_time: { - bias: -60, + bias: 0, time: "03:00:00", day_order: 1, month: 10, @@ -180,8 +179,8 @@ def get_available_rooms(rooms:, start_time:, end_time:) if resp.length == 0 free_rooms.push(room_subset[index]) elsif resp.length == 1 - free_rooms.push(room_subset[index]) if find_time(resp[0], :start_time) == end_time - free_rooms.push(room_subset[index]) if find_time(resp[0], :end_time) == start_time + free_rooms.push(room_subset[index]) if find_time(resp[0], :start_time).to_i == end_time.to_i + free_rooms.push(room_subset[index]) if find_time(resp[0], :end_time).to_i == start_time.to_i end elsif resp.length == 0 # If response length is 0 then the room is free From 535d8527cb31f7461849ef5557d7948e954c179f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 17 Aug 2018 14:48:05 +1000 Subject: [PATCH 0757/1752] Only use UTC in exchange --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 50f8cbcd..062fa8ff 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -126,7 +126,7 @@ def find_time(cal_event, time) start_time = nil elems.each do |item| if item[time] - start_time = ActionController::TimeZone.new("UTC").parse(item[time][:text]) + start_time = ActiveSupport::TimeZone.new("UTC").parse(item[time][:text]) break end end From fc2dc986d9038afae4e17161826195c69e1bdfe1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 20 Aug 2018 12:09:29 +1000 Subject: [PATCH 0758/1752] [EWS Driver] dont delete bookings just change end time --- modules/aca/exchange_booking.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index d727f309..0761cb61 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -674,8 +674,10 @@ def delete_ews_booking(delete_at) meeting_time = Time.parse(meeting.ews_item[:start][:text]) # Remove any meetings that match the start time provided - if meeting_time.to_i == delete_at - meeting.delete!(:recycle, send_meeting_cancellations: 'SendOnlyToAll') + if meeting_time.to_i == delete_at + new_booking = meeting.update_item!({ end: Time.now.utc.iso8601.chop }) + + # meeting.delete!(:recycle, send_meeting_cancellations: 'SendOnlyToAll') count += 1 end end From a8e3f75a76e86c29bfc68050ddb35fc31efb43fa Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 21 Aug 2018 10:30:54 +1000 Subject: [PATCH 0759/1752] [O365 Lib] add contact methods --- lib/microsoft/office.rb | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 7c624146..b8a3af61 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -226,6 +226,51 @@ def has_user(user_id:) end end + def get_contacts(owner_email:, q:nil, limit:nil) + query_params = { '$top': (limit || 1000) } + query_params['$filter'] = "startswith(displayName, #{q}) or startswith(givenName, #{q}) or startswith(surname, #{q}) or emailAddresses/any(a:a/address eq #{q})" if q + endpoint = "/v1.0/users/#{owner_email}/contacts" + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) + check_response(request) + JSON.parse(request.body) + end + + def get_contact(owner_email:, contact_email:) + endpoint = "/v1.0/users/#{owner_email}/contacts" + query_params = { + '$top': 1, + '$search': contact_email + } + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) + check_response(request) + JSON.parse(request.body) + end + + def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, company:nil, other:{}) + # This data is required so add it unconditionally + contact_data = { + givenName: first_name, + surname: last_name, + emailAddresses: [{ + address: email, + name: "#{first_name} #{last_name}" + }] + } + + # Only add these fields if passed in + contact_data[:businessPhones] = [ phone ] if phone + contact_data[:companyName] = company if company + other.each do |field, value| + contact_data[field.to_sym] = value + end + + + endpoint = "/v1.0/users/#{owner_email}" + request = graph_request(request_method: 'post', endpoint: endpoint, data: contact_data, password: @delegated) + check_response(request) + JSON.parse(request.body) + end + def get_rooms(q: nil, limit: nil) filter_param = "startswith(name,'#{q}') or startswith(address,'#{q}')" if q query_params = { From ca546046ca9fcb59aca5d8eaca12249d20aa4e3b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 22 Aug 2018 11:33:30 +1000 Subject: [PATCH 0760/1752] Fix edit on cancel causing issues --- modules/aca/exchange_booking.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 44029ec8..ca04ef13 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -675,9 +675,9 @@ def delete_ews_booking(delete_at) # Remove any meetings that match the start time provided if meeting_time.to_i == delete_at - new_booking = meeting.update_item!({ end: Time.now.utc.iso8601.chop }) + # new_booking = meeting.update_item!({ end: Time.now.utc.iso8601.chop }) - # meeting.delete!(:recycle, send_meeting_cancellations: 'SendOnlyToAll') + meeting.delete!(:recycle, send_meeting_cancellations: 'SendOnlyToAll') count += 1 end end From 34963bcd6a29574c0a7d7906e1cd2fde26462688 Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 22 Aug 2018 13:50:29 +1000 Subject: [PATCH 0761/1752] initial maxhub driver --- modules/maxhub/tv.rb | 154 ++++++++++++++++++++++++++++++++++++++ modules/maxhub/tv_spec.rb | 44 +++++++++++ 2 files changed, 198 insertions(+) create mode 100644 modules/maxhub/tv.rb create mode 100644 modules/maxhub/tv_spec.rb diff --git a/modules/maxhub/tv.rb b/modules/maxhub/tv.rb new file mode 100644 index 00000000..83462f82 --- /dev/null +++ b/modules/maxhub/tv.rb @@ -0,0 +1,154 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Maxhub; end + +class Maxhub::Tv + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 8899 + descriptive_name 'Maxhub P75PC-G1 TV' + generic_name :Display + + tokenize delimiter: "\xDD\xEE\xFF", indicator: "\xAA\xBB\xCC" + + def on_load + on_update + self[:volume_min] = 0 + self[:volume_max] = 100 + end + + def on_unload + end + + def on_update + end + + def connected + end + + def disconnected + schedule.clear + end + + def power(state) + target = is_affirmative?(state) + self[:power_target] = target + + logger.debug { "Target = #{target} and self[:power] = #{self[:power]}" } + if target == On && self[:power] != On + send_cmd("01000001", name: :power_cmd, delay: 2000, timeout: 10000) + elsif target == Off && self[:power] != Off + send_cmd("01010002", name: :power_cmd, timeout: 10000) + end + end + + def power? + send_cmd("01020003", name: :power_inq, priority: 0) + end + + def volume(vol) + val = in_range(vol, self[:volume_max], self[:volume_min]) + self[:volume_target] = val + send_cmd("0300#{val.to_s(16).rjust(2, '0')}00", wait: false) + volume? + end + + def volume? + send_cmd("03020005", name: :volume, priority: 0) + end + + def mute_audio + send_cmd("03010004", wait: false) + mute? + end + + def unmute_audio + send_cmd("03010105", wait: false) + mute? + end + + def mute? + send_cmd("03030006", name: :mute, priority: 0) + end + + INPUTS_CMD = { + :tv => "02010003", + :av => "02020004", + :vga3 => "020B000D", + :vga1 => "02010003", + :vga2 => "02040006", + :hdmi1 => "02060008", + :hdmi2 => "02070009", + :hdmi3 => "02050007", + :pc => "0208000A", + :android => "020A000C", + :hdmi4k => "020D000F", + :whdi => "020C000E", + :ypbpr => "020F0005", + :androidslot => "020E0005" + } + + INPUTS_INQ = { + "81010082" => "tv", + "81020083"=> "av", + "81030084" => "vga1", + "81040085" => "vga2", + "81050086" => "hdmi3", + "81060087" => "hdmi1", + "81070088" => "hdmi2", + "81080089" => "pc", + "810A008B" => "android", + "810D008E" => "hdmi4k", + "810C008D" => "whdi", + "810B008C" => "vga3" + } + + def switch_to(input) + self[:input_target] = input + input = input.to_sym if input.class == String + send_cmd(INPUTS_CMD[input], wait: false) + input? + end + + def input? + send_cmd("02000002", name: :input, priority: 0) + end + + def send_cmd(cmd, options = {}) + req = "AABBCC#{cmd}DDEEFF" + logger.debug { "tell -- 0x#{req} -- #{options[:name]}" } + options[:hex_string] = true + send(req, options) + end + + def received(data, deferrable, command) + hex = byte_to_hex(data) + return :success if command.nil? || command[:name].nil? + return :ignore if (hex == "80000080" || hex == "80010081") && command[:name] != :power_cmd && command[:name] != :power_inq + + case command[:name] + when :power_cmd + if (self[:power_target] == On && hex == "80000080") || (self[:power_target] == Off && hex == "80010081") + self[:power] = self[:power_target] + else + return :ignore + end + when :power_inq + self[:power] = On if hex == "80000080" + self[:power] = Off if hex == "80010081" + when :input + self[:input] = INPUTS_INQ[hex] + when :volume + self[:volume] = byte_to_hex(data[-2]).to_i(16) + when :mute + self[:mute] = On if hex == "82010083" + self[:mute] = Off if hex == "82010184" + end + + logger.debug { "Received 0x#{hex}\n" } + return :success + end +end diff --git a/modules/maxhub/tv_spec.rb b/modules/maxhub/tv_spec.rb new file mode 100644 index 00000000..65bf1340 --- /dev/null +++ b/modules/maxhub/tv_spec.rb @@ -0,0 +1,44 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +Orchestrator::Testing.mock_device 'Maxhub::Tv' do + exec(:power?) + .should_send("\xAA\xBB\xCC\x01\x02\x00\x03\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x80\x01\x00\x81\xDD\xEE\xFF") + .expect(status[:power]).to be(false) + + exec(:power, true) + .should_send("\xAA\xBB\xCC\x01\x00\x00\x01\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x80\x00\x00\x80\xDD\xEE\xFF") # this response is probably wrong + .expect(status[:power]).to be(true) + + exec(:power?) + .should_send("\xAA\xBB\xCC\x01\x02\x00\x03\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x80\x00\x00\x80\xDD\xEE\xFF") + .expect(status[:power]).to be(true) + + exec(:input?) + .should_send("\xAA\xBB\xCC\x02\x00\x00\x02\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x81\x05\x00\x86\xDD\xEE\xFF") + .expect(status[:input]).to be("hdmi3") + + exec(:switch_to, "pc") + .should_send("\xAA\xBB\xCC\x02\x08\x00\x0A\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x81\x05\x00\x86\xDD\xEE\xFF") # this response is probably incorrect + .expect(status[:input]).to be("pc") + + exec(:mute?) + .should_send("\xAA\xBB\xCC\x03\x03\x00\x06\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x82\x01\x01\x84\xDD\xEE\xFF") + .expect(status[:mute]).to be(false) + + exec(:volume?) + .should_send("\xAA\xBB\xCC\x03\x02\x00\x05\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x82\x00\x06\x09\xDD\xEE\xFF") + .expect(status[:volume]).to be(6) + + exec(:volume, 99) + .should_send("\xAA\xBB\xCC\x03\x00\x63\x00\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x82\x00\x06\x09\xDD\xEE\xFF") # this is not the correct response + .expect(status[:volume]).to be(99) +end From a7674e764be13da14b06e6602159d6b322c2014b Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 22 Aug 2018 14:30:05 +1000 Subject: [PATCH 0762/1752] fixed maxhub spec --- modules/maxhub/tv_spec.rb | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/maxhub/tv_spec.rb b/modules/maxhub/tv_spec.rb index 65bf1340..d4ef4af7 100644 --- a/modules/maxhub/tv_spec.rb +++ b/modules/maxhub/tv_spec.rb @@ -7,13 +7,10 @@ .responds("\xAA\xBB\xCC\x80\x01\x00\x81\xDD\xEE\xFF") .expect(status[:power]).to be(false) + wait(2000) + exec(:power, true) .should_send("\xAA\xBB\xCC\x01\x00\x00\x01\xDD\xEE\xFF") - .responds("\xAA\xBB\xCC\x80\x00\x00\x80\xDD\xEE\xFF") # this response is probably wrong - .expect(status[:power]).to be(true) - - exec(:power?) - .should_send("\xAA\xBB\xCC\x01\x02\x00\x03\xDD\xEE\xFF") .responds("\xAA\xBB\xCC\x80\x00\x00\x80\xDD\xEE\xFF") .expect(status[:power]).to be(true) @@ -24,7 +21,7 @@ exec(:switch_to, "pc") .should_send("\xAA\xBB\xCC\x02\x08\x00\x0A\xDD\xEE\xFF") - .responds("\xAA\xBB\xCC\x81\x05\x00\x86\xDD\xEE\xFF") # this response is probably incorrect + .responds("\xAA\xBB\xCC\x81\x08\x00\x89\xDD\xEE\xFF") .expect(status[:input]).to be("pc") exec(:mute?) @@ -32,6 +29,16 @@ .responds("\xAA\xBB\xCC\x82\x01\x01\x84\xDD\xEE\xFF") .expect(status[:mute]).to be(false) + exec(:mute_audio) + .should_send("\xAA\xBB\xCC\x03\x01\x00\x04\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x82\x01\x00\x83\xDD\xEE\xFF") + .expect(status[:mute]).to be(true) + + exec(:unmute_audio) + .should_send("\xAA\xBB\xCC\x03\x01\x01\x05\xDD\xEE\xFF") + .responds("\xAA\xBB\xCC\x82\x01\x01\x84\xDD\xEE\xFF") + .expect(status[:mute]).to be(false) + exec(:volume?) .should_send("\xAA\xBB\xCC\x03\x02\x00\x05\xDD\xEE\xFF") .responds("\xAA\xBB\xCC\x82\x00\x06\x09\xDD\xEE\xFF") @@ -39,6 +46,6 @@ exec(:volume, 99) .should_send("\xAA\xBB\xCC\x03\x00\x63\x00\xDD\xEE\xFF") - .responds("\xAA\xBB\xCC\x82\x00\x06\x09\xDD\xEE\xFF") # this is not the correct response + .responds("\xAA\xBB\xCC\x82\x00\x63\x00\xDD\xEE\xFF") .expect(status[:volume]).to be(99) end From 88e547488a281e6686c5b78258f12e5d1ea0dedd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 23 Aug 2018 11:33:14 +1000 Subject: [PATCH 0763/1752] Add optional attendee support --- lib/microsoft/office.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b8a3af61..2f539d67 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -653,7 +653,6 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec # Let's assume that the request has the current user and room as an attendee already event[:attendees] = attendees.map{|a| - @item.property = params[:property] ? true : false if a[:optional] attendee_type = 'optional' else From e293184b2cb9838e28ab9c9ba3db53b01a24da75 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 23 Aug 2018 13:26:41 +1000 Subject: [PATCH 0764/1752] Add get_orgs method to o365 lib --- lib/microsoft/office.rb | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 2f539d67..c1ab305e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -271,6 +271,20 @@ def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, compan JSON.parse(request.body) end + def get_organisations(owner_email:) + query_params = { + '$top': 1000 + } + endpoint = "/v1.0/users/#{owner_email}/contacts" + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) + check_response(request) + contacts = JSON.parse(request.body)['value'] + orgs = [] + contacts.each do |cont| orgs.push(cont['companyName']) if !cont['companyName'].nil? && !cont['companyName'].empty? end + orgs + end + + def get_rooms(q: nil, limit: nil) filter_param = "startswith(name,'#{q}') or startswith(address,'#{q}')" if q query_params = { @@ -521,10 +535,17 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil # Add the attendees attendees.map!{|a| + if a[:optional] + attendee_type = 'optional' + else + attendee_type = 'required' + end { emailAddress: { address: a[:email], name: a[:name] - } } + }, + type: attendee_type + } } # Add the room as an attendee @@ -653,18 +674,11 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec # Let's assume that the request has the current user and room as an attendee already event[:attendees] = attendees.map{|a| - if a[:optional] - attendee_type = 'optional' - else - attendee_type = 'required' - end - attendee_type = a[:option] { emailAddress: { address: a[:email], name: a[:name] - }, - type: attendee_type + } } } if attendees From c54f703401ca5031216dc778f4b052ba7f4e0c73 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 23 Aug 2018 15:24:03 +1000 Subject: [PATCH 0765/1752] [O365 Lib] Minor syntax fix --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index c1ab305e..f7d08c4a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -265,7 +265,7 @@ def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, compan end - endpoint = "/v1.0/users/#{owner_email}" + endpoint = "/v1.0/users/#{owner_email}/contacts" request = graph_request(request_method: 'post', endpoint: endpoint, data: contact_data, password: @delegated) check_response(request) JSON.parse(request.body) From 31c95cde8b1e1c5b85f6953cb2c3bccdf1080538 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 23 Aug 2018 15:24:41 +1000 Subject: [PATCH 0766/1752] [O365 Lib] Minor syntax fix --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f7d08c4a..988b4709 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -86,7 +86,7 @@ def graph_request(request_method:, endpoint:, data:nil, query:{}, headers:nil, p log_graph_request(request_method, data, query, headers, graph_path, password) - graph_api_options = {inactivity_timeout: 25000} + graph_api_options = {inactivity_timeout: 25000, keepalive: false} if @internet_proxy proxy = URI.parse(@internet_proxy) graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } From 38587048ca58cdc88e0e888070df7497311c99e3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 23 Aug 2018 15:36:55 +1000 Subject: [PATCH 0767/1752] Remove duplicate orgs --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 988b4709..84fb3fed 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -281,7 +281,7 @@ def get_organisations(owner_email:) contacts = JSON.parse(request.body)['value'] orgs = [] contacts.each do |cont| orgs.push(cont['companyName']) if !cont['companyName'].nil? && !cont['companyName'].empty? end - orgs + orgs.uniq.compact end From 4c350f0219eb7f8a7c88c0a0299c62ef42c59736 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 10:37:01 +1000 Subject: [PATCH 0768/1752] Fix get_contact method to not use search --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 84fb3fed..b59c687b 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -239,7 +239,7 @@ def get_contact(owner_email:, contact_email:) endpoint = "/v1.0/users/#{owner_email}/contacts" query_params = { '$top': 1, - '$search': contact_email + '$filter': "emailAddresses/any(a:a/address eq #{contact_email})" } request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) From 2ebf36337132089a42de3814742cbf6546cf546d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 10:38:58 +1000 Subject: [PATCH 0769/1752] Fix get_contact method to not use search --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b59c687b..b5084c79 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -239,7 +239,7 @@ def get_contact(owner_email:, contact_email:) endpoint = "/v1.0/users/#{owner_email}/contacts" query_params = { '$top': 1, - '$filter': "emailAddresses/any(a:a/address eq #{contact_email})" + '$filter': "emailAddresses/any(a:a/address eq '#{contact_email}')" } request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) From c8056d7e065b7a32f47031091ef3135a8acd9bb8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 10:40:27 +1000 Subject: [PATCH 0770/1752] Fix get_contact method to not use search --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b5084c79..490ed6da 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -232,7 +232,7 @@ def get_contacts(owner_email:, q:nil, limit:nil) endpoint = "/v1.0/users/#{owner_email}/contacts" request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) - JSON.parse(request.body) + JSON.parse(request.body)['value'] end def get_contact(owner_email:, contact_email:) @@ -243,7 +243,7 @@ def get_contact(owner_email:, contact_email:) } request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) - JSON.parse(request.body) + JSON.parse(request.body)['value'] end def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, company:nil, other:{}) From 121864bb555a38d258c6d324193d4302f59e0e30 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 10:41:54 +1000 Subject: [PATCH 0771/1752] Add has_contact method --- lib/microsoft/office.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 490ed6da..a2396684 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -246,6 +246,10 @@ def get_contact(owner_email:, contact_email:) JSON.parse(request.body)['value'] end + def has_contact(owner_email:, contact_email:) + return get_contact(owner_email: owner_email, contact_email: contact_email).length > 0 + end + def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, company:nil, other:{}) # This data is required so add it unconditionally contact_data = { From e1a11611ef3025ca39964ae1caf37a8e424d984d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 10:54:45 +1000 Subject: [PATCH 0772/1752] Add has_contact method --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a2396684..e02ea62d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -250,7 +250,7 @@ def has_contact(owner_email:, contact_email:) return get_contact(owner_email: owner_email, contact_email: contact_email).length > 0 end - def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, company:nil, other:{}) + def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, organisation:nil, other:{}) # This data is required so add it unconditionally contact_data = { givenName: first_name, @@ -263,7 +263,7 @@ def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, compan # Only add these fields if passed in contact_data[:businessPhones] = [ phone ] if phone - contact_data[:companyName] = company if company + contact_data[:organisationName] = organisation if organisation other.each do |field, value| contact_data[field.to_sym] = value end From 556b594f07c31b2d46e8d25bc7c2ca19da2978af Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 11:43:30 +1000 Subject: [PATCH 0773/1752] Fix MS field names --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e02ea62d..9ff6a05d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -263,7 +263,7 @@ def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, organi # Only add these fields if passed in contact_data[:businessPhones] = [ phone ] if phone - contact_data[:organisationName] = organisation if organisation + contact_data[:companyName] = organisation if organisation other.each do |field, value| contact_data[field.to_sym] = value end From e2d45f45541c0535ce752e22cb70ddcd9495a91d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 13:03:21 +1000 Subject: [PATCH 0774/1752] Format o365 contact data before return --- lib/microsoft/office.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 9ff6a05d..1951ad16 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -232,7 +232,21 @@ def get_contacts(owner_email:, q:nil, limit:nil) endpoint = "/v1.0/users/#{owner_email}/contacts" request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) - JSON.parse(request.body)['value'] + return format_contacts(JSON.parse(request.body)['value']) + end + + def format_contacts(contacts) + output_contacts = {} + contacts.each do |contact| + output_format = {} + output_format[:first_name] = contact['givenName'] + output_format[:last_name] = contact['surname'] + output_format[:phone] = contact['businessPhones'][0] + output_format[:organisation] = contact['companyName'] + output_format[:email] = contact['emailAddresses'][0]['address'] + output_contacts.push(output_format) + end + output_contacts end def get_contact(owner_email:, contact_email:) From 2b0545490a55318a05f84d423415adb455df6ea7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 13:04:15 +1000 Subject: [PATCH 0775/1752] Format o365 contact data before return --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 1951ad16..b6563f30 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -236,7 +236,7 @@ def get_contacts(owner_email:, q:nil, limit:nil) end def format_contacts(contacts) - output_contacts = {} + output_contacts = [] contacts.each do |contact| output_format = {} output_format[:first_name] = contact['givenName'] From 73468636d15f4b040769287f640c3735a041ac20 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 24 Aug 2018 15:14:29 +1000 Subject: [PATCH 0776/1752] Return contacts id --- lib/microsoft/office.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b6563f30..db98d0cb 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -193,12 +193,13 @@ def get_users(q: nil, limit: nil) queries = q.split(" ") filter_params = [] queries.each do |q| - filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))") + filter_params.push("(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))") end filter_param = filter_params.join(" or ") else - filter_param = "startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}')" if q + filter_param = "(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))" if q end + filter_param = "accountEnabled eq true" if q.nil? query_params = { '$filter': filter_param, '$top': limit @@ -239,6 +240,7 @@ def format_contacts(contacts) output_contacts = [] contacts.each do |contact| output_format = {} + output_format[:id] = contact['id'] output_format[:first_name] = contact['givenName'] output_format[:last_name] = contact['surname'] output_format[:phone] = contact['businessPhones'][0] From 30ce050fe872b4d657d984737c444c7055ef5a4f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Aug 2018 13:10:41 +1000 Subject: [PATCH 0777/1752] Add get_contact_by_id method --- lib/microsoft/office.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index db98d0cb..663b4a2f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -262,6 +262,13 @@ def get_contact(owner_email:, contact_email:) JSON.parse(request.body)['value'] end + def get_contact_by_id(owner_email:, contact_id:) + endpoint = "/v1.0/users/#{owner_email}/contacts/#{contact_id}" + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) + check_response(request) + JSON.parse(request.body)['value'] + end + def has_contact(owner_email:, contact_email:) return get_contact(owner_email: owner_email, contact_email: contact_email).length > 0 end From 58e759b381b0163fa280f253fbadee8ebbd5e497 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Aug 2018 13:17:28 +1000 Subject: [PATCH 0778/1752] Fix visitor request --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 663b4a2f..c7e378a1 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -264,7 +264,7 @@ def get_contact(owner_email:, contact_email:) def get_contact_by_id(owner_email:, contact_id:) endpoint = "/v1.0/users/#{owner_email}/contacts/#{contact_id}" - request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) + request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) check_response(request) JSON.parse(request.body)['value'] end From 585572b1bd39d4c2d8d355aaccda52628b28e9ed Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 27 Aug 2018 13:30:24 +1000 Subject: [PATCH 0779/1752] Fix visitor request --- lib/microsoft/office.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index c7e378a1..f152192a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -265,8 +265,7 @@ def get_contact(owner_email:, contact_email:) def get_contact_by_id(owner_email:, contact_id:) endpoint = "/v1.0/users/#{owner_email}/contacts/#{contact_id}" request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) - check_response(request) - JSON.parse(request.body)['value'] + JSON.parse(request.body) end def has_contact(owner_email:, contact_email:) From 36ada067d830781c1557a9c8ac0e640d2c75d5e8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Aug 2018 18:45:42 +1000 Subject: [PATCH 0780/1752] (aca:router) prevent excess object creation for outdegree lookup --- modules/aca/router.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 90fd2a70..4fac57b0 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -485,7 +485,8 @@ def indegree(id) end def outdegree(id) - outgoing_edges(id).size + id = id.to_sym + nodes[id].edges.size end def dijkstra(id) From 80d3ed2c81ce5bfc563ddb7edc5f1355eb578929 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Aug 2018 21:32:52 +1000 Subject: [PATCH 0781/1752] (aca:router) prevent non-symbolised input id's from staying in mem --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 4fac57b0..4ac0da21 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -378,7 +378,7 @@ class Node def initialize(id) @id = id.to_sym @edges = Hash.new do |_, other_id| - raise ArgumentError, "No edge from \"#{id}\" to \"#{other_id}\"" + raise ArgumentError, "No edge from \"#{@id}\" to \"#{other_id}\"" end end From 001109818dbb15a2636e5318b6580083b43d7d96 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 27 Aug 2018 21:52:04 +1000 Subject: [PATCH 0782/1752] (aca:router) add method for querying linear signal paths --- modules/aca/router.rb | 35 ++++++++++++++++++++++++++++ modules/aca/router_spec.rb | 47 ++++++++++++++++++++++++++++++-------- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 4ac0da21..9977c95f 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -127,6 +127,41 @@ def devices_between(source, sink) edges.map(&:device) end + # Given a sink id, find the chain of devices that sit immediately upstream + # in the signal path. The returned list will include all devices which for + # a static, linear chain exists before any routing is possible + # + # This may be used to find devices that are installed for the use of this + # output only (decoders, image processors etc). + # + # If the sink itself has mutiple inputs, the input to retrieve the chain for + # may be specified with the `on_input` param. + def upstream_devices_of(sink, on_input: nil) + device_chain = [] + + # Bail out early if there's no linear signal path from the sink + return device_chain unless on_input || signal_graph.outdegree(sink) == 1 + + # Otherwise, grab the initial edge from the sink node + initial = signal_graph[sink].edges.values.find do |edge| + if on_input + edge.input == on_input.to_sym + else + true + end + end + + # Then walk the graph and accumulate devices until we reach a branch + edges = signal_graph[initial.target].edges + until edges.empty? || edges.size > 1 + _, edge = edges.first + device_chain << edge.device + edges = signal_graph[edge.target].edges + end + + device_chain + end + # ------------------------------ # Internals diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index a7552bfd..9a4fe390 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -61,7 +61,8 @@ def section(message) { "Display_1 as Left_LCD": { "hdmi": "Switcher_1__1", - "hdmi2": "SubSwitchA__1" + "hdmi2": "SubSwitchA__1", + "hdmi3": "Receiver_1" }, "Display_2 as Right_LCD": { "hdmi": "Switcher_1__2", @@ -76,6 +77,12 @@ def section(message) "Switcher_2 as SubSwitchB": { "3": "e", "4": "f" + }, + "Receiver_1": { + "hdbaset": "Transmitter_1" + }, + "Transmitter_1": { + "hdmi": "h" } } JSON @@ -84,7 +91,8 @@ def section(message) expect(normalised_map).to eq( 'Display_1 as Left_LCD' => { hdmi: 'Switcher_1__1', - hdmi2: 'SubSwitchA__1' + hdmi2: 'SubSwitchA__1', + hdmi3: 'Receiver_1' }, 'Display_2 as Right_LCD' => { hdmi: 'Switcher_1__2', @@ -102,21 +110,30 @@ def section(message) 'Switcher_2 as SubSwitchB' => { 3 => 'e', 4 => 'f' + }, + 'Receiver_1' => { + hdbaset: 'Transmitter_1' + }, + 'Transmitter_1' => { + hdmi: 'h' } ) mods = SignalGraph.extract_mods!(normalised_map) expect(mods).to eq( - 'Left_LCD' => 'Display_1', - 'Right_LCD' => 'Display_2', - 'Switcher_1' => 'Switcher_1', - 'SubSwitchA' => 'Switcher_2', - 'SubSwitchB' => 'Switcher_2' + 'Left_LCD' => 'Display_1', + 'Right_LCD' => 'Display_2', + 'Switcher_1' => 'Switcher_1', + 'SubSwitchA' => 'Switcher_2', + 'SubSwitchB' => 'Switcher_2', + 'Receiver_1' => 'Receiver_1', + 'Transmitter_1' => 'Transmitter_1' ) expect(normalised_map).to eq( Left_LCD: { hdmi: 'Switcher_1__1', - hdmi2: 'SubSwitchA__1' + hdmi2: 'SubSwitchA__1', + hdmi3: 'Receiver_1' }, Right_LCD: { hdmi: 'Switcher_1__2', @@ -134,6 +151,12 @@ def section(message) SubSwitchB: { 3 => 'e', 4 => 'f' + }, + Receiver_1: { + hdbaset: 'Transmitter_1' + }, + Transmitter_1: { + hdmi: 'h' } ) @@ -141,7 +164,7 @@ def section(message) expect(graph.sources).to contain_exactly(:Left_LCD, :Right_LCD) - expect(graph.sinks).to contain_exactly(*(:a..:g).to_a) + expect(graph.sinks).to contain_exactly(*(:a..:h).to_a) routes = graph.sources.map { |id| [id, graph.dijkstra(id)] }.to_h expect(routes[:Left_LCD].distance_to[:a]).to be(2) @@ -203,4 +226,10 @@ def section(message) exec(:devices_between, :c, :Left_LCD) expect(result).to contain_exactly(:Switcher_2, :Display_1) + + exec(:upstream_devices_of, :Left_LCD, on_input: :hdmi3) + expect(result).to contain_exactly(:Receiver_1, :Transmitter_1) + + exec(:upstream_devices_of, :Left_LCD) + expect(result).to be_empty end From 582fc2518e91d10ab9fd07ab5df720630f353795 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 Aug 2018 02:22:04 +1000 Subject: [PATCH 0783/1752] (aca:router) ensure upstream search includes nodes without input --- modules/aca/router.rb | 10 +++++----- modules/aca/router_spec.rb | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 9977c95f..751bbaf6 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -152,11 +152,11 @@ def upstream_devices_of(sink, on_input: nil) end # Then walk the graph and accumulate devices until we reach a branch - edges = signal_graph[initial.target].edges - until edges.empty? || edges.size > 1 - _, edge = edges.first - device_chain << edge.device - edges = signal_graph[edge.target].edges + successors = [initial.target] + while successors.size == 1 + node = successors.first + device_chain << node + successors = signal_graph.successors node end device_chain diff --git a/modules/aca/router_spec.rb b/modules/aca/router_spec.rb index 9a4fe390..43cba2c4 100644 --- a/modules/aca/router_spec.rb +++ b/modules/aca/router_spec.rb @@ -228,7 +228,7 @@ def section(message) expect(result).to contain_exactly(:Switcher_2, :Display_1) exec(:upstream_devices_of, :Left_LCD, on_input: :hdmi3) - expect(result).to contain_exactly(:Receiver_1, :Transmitter_1) + expect(result).to contain_exactly(:Receiver_1, :Transmitter_1, :h) exec(:upstream_devices_of, :Left_LCD) expect(result).to be_empty From 3cbf88cea5dd5cbb1e837525419e79995b9f38d8 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 28 Aug 2018 11:51:30 +1000 Subject: [PATCH 0784/1752] (sony:projector) Add FH series (tested OK) no lamp hours query yet --- modules/sony/projector/fh.rb | 150 +++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 modules/sony/projector/fh.rb diff --git a/modules/sony/projector/fh.rb b/modules/sony/projector/fh.rb new file mode 100644 index 00000000..41f1a29b --- /dev/null +++ b/modules/sony/projector/fh.rb @@ -0,0 +1,150 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'shellwords' + +module Sony; end +module Sony::Projector; end + +# Device Protocol Documentation: https://drive.google.com/a/room.tools/file/d/1C0gAWNOtkbrHFyky_9LfLCkPoMcYU9lO/view?usp=sharing + +class Sony::Projector::Fh + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + + # Discovery Information + descriptive_name 'Sony Projector FH Series' + generic_name :Display + + # Communication settings + tokenize delimiter: "\x0D" + delay on_receive: 50, between_sends: 50 + + + def on_load + self[:type] = :projector + end + + + def connected + schedule.every('60s') { do_poll } + end + + def disconnected + schedule.clear + end + + def power(state) + state = is_affirmative?(state) + target = state ? "on" : "off" + set("power", target).then { self[:power] = state } + end + + def power? + get("power_status").then do |response| + self[:power] = response == "on" + end + end + + def mute(state = true) + state = is_affirmative?(state) + target = state ? "on" : "off" + set("blank", target).then { self[:mute] = state } + end + + def unmute + mute(false) + end + + def mute? + get("blank").then do |response| + self[:mute] = response == "on" + end + end + + INPUTS = { + hdmi: 'hdmi1', #Input C + dvi: 'dvi1', #Input B + video: 'video1', + svideo: 'svideo1', + rgb: 'rgb1', #Input A + hdbaset:'hdbaset1', #Input D + inputa: 'input_a', + inputb: 'input_b', + inputc: 'input_c', + inputd: 'input_d', + inpute: 'input_e' + } + INPUTS.merge!(INPUTS.invert) + + def switch_to(input) + target=input.to_sym + set("input", INPUTS[target]).then { self[:input] = target } + end + + def input? + get("input").then do |response| + self[:input] = response.to_sym + end + end + + def lamp_time? + #get "timer" + end + + + # + # Automatically creates a callable function for each command + # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html + # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html + # + [:contrast, :brightness, :color, :hue, :sharpness].each do |command| + # Query command + define_method :"#{command}?" do + get "#{command}" + end + + # Set value command + define_method command do |level| + level = in_range(level, 0x64) + set command, level + end + end + + protected + + def received(response, resolve, command) + logger.debug { "Sony proj sent: #{response.inspect}" } + + data = response.strip.downcase.shellsplit + logger.debug { "Sony proj sent: #{data}" } + + return :success if data[0] == "ok" + return :abort if data[0] == "err_cmd" + #return data[1] if data.length > 1 + data[0] + end + + def do_poll + power?.finally do + if self[:power] + input? + mute? + lamp_time? + end + end + end + + def get(path, **options) + cmd = "#{path} ?\r\n" + logger.debug { "requesting: #{cmd}" } + send(cmd, options) + end + + def set(path, arg, **options) + cmd = "#{path} \"#{arg}\"\r\n" + logger.debug { "sending: #{cmd}" } + send(cmd, options) + end +end From 55e6b120d7f5495aa35ec40c6705341afb360a0e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Aug 2018 00:23:45 +1000 Subject: [PATCH 0785/1752] (aca:router) indicate if map recall was full or partial in response --- modules/aca/router.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 751bbaf6..844d6e77 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -55,6 +55,10 @@ def on_update # Multiple sources can be specified simultaneously, or if connecting a # single source to a single destination, Ruby's implicit hash syntax can be # used to let you express it neatly as `connect source => sink`. + # + # A promise is returned that will resolve when all device interactions have + # completed. This will be fullfilled with the applied signal map and a + # boolean - true if this was a complete recall, or false if partial. def connect(signal_map, atomic: false, force: false) # Convert the signal map to a nested hash of routes # { source => { dest => [edges] } } @@ -70,18 +74,20 @@ def connect(signal_map, atomic: false, force: false) switch.then do |success, failed| if failed.empty? logger.debug 'signal map activated' - edge_map.transform_values(&:keys) + recalled_map = edge_map.transform_values(&:keys) + [recalled_map, true] elsif success.empty? thread.reject 'failed to activate, devices untouched' else logger.warn 'signal map partially activated' - edge_map.transform_values do |routes| + recalled_map = edge_map.transform_values do |routes| routes.each_with_object([]) do |(output, edges), completed| completed << output if success.superset? Set.new(edges) end end + [recalled_map, false] end end end From 54fd1e1492eab541afb95e89ad365e85024e081e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Aug 2018 13:05:49 +1000 Subject: [PATCH 0786/1752] (samsung:display) add support for colour calibration --- modules/samsung/displays/md_series.rb | 42 ++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index df271f70..5f70fe00 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -85,7 +85,14 @@ def disconnected :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, @@ -209,12 +216,6 @@ def volume(vol, options = {}) do_send(:volume, vol, options) end - def brightness(val, options = {}) - val = in_range(val.to_i, 100) - do_send(:brightness, val, options) - end - - # # Emulate mute def mute_audio(val = true) @@ -292,13 +293,40 @@ def auto_power(enable, options = {}) end + # + # 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 + + protected DEVICE_SETTINGS = [ :network_standby, :auto_off_timer, - :auto_power + :auto_power, + :contrast, + :brightness, + :sharpness, + :colour, + :tint, + :red_gain, + :green_gain, + :blue_gain ] # # Push any configured device settings From a1a5d49f819d1eb5e1c5d9a582237b8130272263 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 29 Aug 2018 21:47:08 +1000 Subject: [PATCH 0787/1752] (cisco:ce) name outgoing commands generated by calls to send_xcommand --- modules/cisco/collaboration_endpoint/room_os.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 9d6212b7..4043345e 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -159,10 +159,14 @@ def self.extended(child) def send_xcommand(command, args = {}) request = Action.xcommand command, args - # FIXME: commands are currently unnamed. Need to add a way of tagging - # related commands for better queue management. - - do_send request do |response| + # Multi-arg commands (external source registration, UI interaction etc) + # all need to be properly queued and sent without be overriden. In + # these cases, leave the outgoing commands unnamed. + opts = {} + opts[:name] = command if args.empty? + opts[:name] = "#{command} #{args.keys.first}" if args.size == 1 + + do_send request, **opts do |response| # The result keys are a little odd: they're a concatenation of the # last two command elements and 'Result', unless the command # failed in which case it's just 'Result'. From 6b6f78d31d6fa38f940d711b13ec169b1b775287 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 4 Sep 2018 11:31:19 +0800 Subject: [PATCH 0788/1752] (foxtel:q2) fix channel up/down Tested OK on Foxtel IQ2 HD --- modules/foxtel/iq2.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/foxtel/iq2.rb b/modules/foxtel/iq2.rb index 19e28b28..6ce71cc1 100644 --- a/modules/foxtel/iq2.rb +++ b/modules/foxtel/iq2.rb @@ -65,8 +65,8 @@ def channel(number) menu: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,22,6,10,6,28,6,22,6,3212', setup: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,16,6,10,6,3237', enter: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,28,6,10,6,3224', - channel_up: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,22,6,10,6,10,6,3243', - channel_down:'1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,22,6,16,6,3224', + channel_up: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,22,6,10,6,10,6,3231', + channel_down:'1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,22,6,10,6,16,6,3225', guide: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,28,6,10,6,28,6,10,6,3218' } @@ -82,4 +82,4 @@ def channel(number) def do_send(cmd) system[@ir_driver].ir(@ir_index, cmd) end -end \ No newline at end of file +end From e0deabc7e823c1bf9cc52babc1f528bcf112b7f9 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 4 Sep 2018 15:39:24 +1000 Subject: [PATCH 0789/1752] (cisco:ce) add commands for presentation control --- modules/cisco/collaboration_endpoint/room_kit.rb | 9 +++++++++ modules/cisco/collaboration_endpoint/sx20.rb | 9 +++++++++ modules/cisco/collaboration_endpoint/sx80.rb | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 5044371e..b1053122 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -125,6 +125,15 @@ def speaker_track(state = On) send_xconfiguration 'Cameras SpeakerTrack', :Mode, mode end + command 'Presentation Start' => :presentation_start, + PresentationSource_: (1..2), + SendingMode_: [:LocalRemote, :LocalOnly], + ConnectorId_: (1..2), + Instance_: [:New, *(1..6)] + command 'Presentation Stop' => :presentation_stop, + Instance_: (1..6), + PresentationSource_: (1..2) + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index e9099e20..7d66589e 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -101,6 +101,15 @@ def mic_mute(state = On) :UpperCenter, :UpperLeft, :UpperRight], OnMonitorRole_: [:First, :Second, :Third, :Fourth] + command 'Presentation Start' => :presentation_start, + PresentationSource_: (1..2), + SendingMode_: [:LocalRemote, :LocalOnly], + ConnectorId_: (1..2), + Instance_: [:New, *(1..6)] + command 'Presentation Stop' => :presentation_stop, + Instance_: (1..6), + PresentationSource_: (1..4) + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 469622b0..fd0a4d9c 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -135,6 +135,15 @@ def speaker_track(state = On) Timeout_: (1..1440) command 'Conference DoNotDisturb Deactivate' => :do_not_disturb_deactivate + command 'Presentation Start' => :presentation_start, + PresentationSource_: (1..4), + SendingMode_: [:LocalRemote, :LocalOnly], + ConnectorId_: (1..2), + Instance_: [:New, *(1..6)] + command 'Presentation Stop' => :presentation_stop, + Instance_: (1..6), + PresentationSource_: (1..4) + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby From 594c7ab2b563162b3ef7c064b522678f5b672bc0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Sep 2018 11:37:11 +1000 Subject: [PATCH 0790/1752] [EWS lib] Allow for either milisecond or second epochs on booking creation --- lib/microsoft/exchange.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 062fa8ff..f740c43d 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -284,9 +284,16 @@ def create_booking(room_email:, start_param:, end_param:, subject:, description: } }] + # start_object = Time.at(start_param.to_i) + # end_object = Time.at(end_param.to_i) + + start_object = ensure_ruby_date(start_param.to_i) + end_object = ensure_ruby_date(end_param.to_i) + + # Add start and end times - booking[:start] = Time.at(start_param.to_i).utc.iso8601.chop - booking[:end] = Time.at(end_param.to_i).utc.iso8601.chop + booking[:start] = start_object.utc.iso8601.chop + booking[:end] = end_object.utc.iso8601.chop # Add the current user passed in as an attendee mailbox = { email_address: current_user.email } From 007a6ae48003097309b28f6b710ef52e5f3fcf9e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 5 Sep 2018 14:04:48 +1000 Subject: [PATCH 0791/1752] (cisco:ce) provide api compatability with router mod --- modules/cisco/collaboration_endpoint/room_kit.rb | 9 +++++++++ modules/cisco/collaboration_endpoint/sx20.rb | 9 +++++++++ modules/cisco/collaboration_endpoint/sx80.rb | 11 ++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index b1053122..6cf63ebc 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -134,6 +134,15 @@ def speaker_track(state = On) Instance_: (1..6), PresentationSource_: (1..2) + # Provide compatabilty with the router module for activating presentation. + def switch_to(input) + if [0, nil, :none, 'none', :blank, 'blank'].include? input + presentation_stop + else + presentation_start presentation_source: input + end + end + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 7d66589e..ee4e0103 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -110,6 +110,15 @@ def mic_mute(state = On) Instance_: (1..6), PresentationSource_: (1..4) + # Provide compatabilty with the router module for activating presentation. + def switch_to(input) + if [0, nil, :none, 'none', :blank, 'blank'].include? input + presentation_stop + else + presentation_start presentation_source: input + end + end + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index fd0a4d9c..d9c0d09b 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -138,12 +138,21 @@ def speaker_track(state = On) command 'Presentation Start' => :presentation_start, PresentationSource_: (1..4), SendingMode_: [:LocalRemote, :LocalOnly], - ConnectorId_: (1..2), + ConnectorId_: (1..5), Instance_: [:New, *(1..6)] command 'Presentation Stop' => :presentation_stop, Instance_: (1..6), PresentationSource_: (1..4) + # Provide compatabilty with the router module for activating presentation. + def switch_to(input) + if [0, nil, :none, 'none', :blank, 'blank'].include? input + presentation_stop + else + presentation_start presentation_source: input + end + end + command 'Standby Deactivate' => :powerup command 'Standby HalfWake' => :half_wake command 'Standby Activate' => :standby From b3fdaaed9c4111c32f35b8855d42b2280de63c76 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Sep 2018 15:59:30 +1000 Subject: [PATCH 0792/1752] [O365 Lib] Fix user token filtering --- lib/microsoft/office.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f152192a..a5f886fd 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -189,13 +189,18 @@ def check_response(response) end def get_users(q: nil, limit: nil) + + # If we have a query and the query has at least one space if q && q.include?(" ") + # Split it into word tokens queries = q.split(" ") filter_params = [] + # For each word, create a filtering statement queries.each do |q| - filter_params.push("(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))") + filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))") end - filter_param = filter_params.join(" or ") + # Join these filtering statements using 'or' and add accountEnabled filter + filter_param = "(accountEnabled eq true) and #{filter_params.join(" or ")}" else filter_param = "(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))" if q end From bfaa7b0849ae3a9d605f0c3d8223dec6ea33f9a4 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Sep 2018 16:01:16 +1000 Subject: [PATCH 0793/1752] [O365 Lib] Fix user token filtering --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a5f886fd..07ecdd62 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -197,12 +197,12 @@ def get_users(q: nil, limit: nil) filter_params = [] # For each word, create a filtering statement queries.each do |q| - filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))") + filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))") end # Join these filtering statements using 'or' and add accountEnabled filter filter_param = "(accountEnabled eq true) and #{filter_params.join(" or ")}" else - filter_param = "(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}') or startswith(userPrincipalName,'#{q}'))" if q + filter_param = "(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))" if q end filter_param = "accountEnabled eq true" if q.nil? query_params = { From 4b6cadfdf49dbbba48f127aa4f5a5d645717ed59 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Sep 2018 16:02:54 +1000 Subject: [PATCH 0794/1752] [O365 Lib] Fix user token filtering --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 07ecdd62..2535c68a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -200,7 +200,7 @@ def get_users(q: nil, limit: nil) filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))") end # Join these filtering statements using 'or' and add accountEnabled filter - filter_param = "(accountEnabled eq true) and #{filter_params.join(" or ")}" + filter_param = "(accountEnabled eq true) and #{filter_params.join(" and ")}" else filter_param = "(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))" if q end From 1b44b1eefbc1a18985a7904773e61239aaec4b5a Mon Sep 17 00:00:00 2001 From: pkheav Date: Fri, 7 Sep 2018 17:04:23 +1000 Subject: [PATCH 0795/1752] initial commit --- modules/digital_projection/evision_7500.rb | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 modules/digital_projection/evision_7500.rb diff --git a/modules/digital_projection/evision_7500.rb b/modules/digital_projection/evision_7500.rb new file mode 100644 index 00000000..c1cbe0ee --- /dev/null +++ b/modules/digital_projection/evision_7500.rb @@ -0,0 +1,82 @@ +module Wolfvision; end + +# Documentation: https://www.wolfvision.com/wolf/protocol_command_wolfvision/protocol/commands_eye-14.pdf + +class Wolfvision::Eye14 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 7000 + descriptive_name 'Digital Projection E-Vision Laser 7500' + generic_name :Display + + tokenize delimiter: "\r" + + INPUTS = { + :hdmi => 0, + :hdmi2 => 1, + :vga => 2, + :comp => 3, + :dvi => 4, + :displayport => 5, + :hdbaset => 6, + :sdi3g => 7 + } + INPUTS.merge!(INPUTS.invert) + + def power(state) + target = is_affirmative?(state) + self[:power_target] = target + + logger.debug { "Target = #{target} and self[:power] = #{self[:power]}" } + if target == On && self[:power] != On + send_cmd("power = 1", name: :power, delay: 2000, timeout: 10000) + elsif target == Off && self[:power] != Off + send_cmd("power = 0", name: :power, timeout: 10000) + end + end + + def power? + send_cmd("power ?", name: :power) + end + + def switch_to(input) + input = input.to_sym if input.class == String + send_cmd("input = #{INPUTS[input]}", name: :input) + end + + def input? + send_cmd("power ?", name: :input) + end + + def send_cmd(cmd, options = {}) + req = "*#{cmd}" + req << 0x0D + logger.debug { "tell -- 0x#{byte_to_hex(req)} -- #{options[:name]}" } + send(req, options) + end + + def received(data, deferrable, command) + logger.debug { "Received 0x#{byte_to_hex(data)}" } + + return :success if command.nil? || command[:name].nil? + + # \A is the beginning of the line + if(!data =~ /\Aack/) # if it doesn't return ack it's a failed response + return :failed + end + + # regex match the value + data = data[-1].to_i + case command[:name] + when :power + self[:power] = data == 1 + when :input + self[:input] = INPUT[data] + end + return :success + end + + +end From d9e0d88ba7f123ec9dcdfd9b85f6ddaa964864e3 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 10 Sep 2018 10:29:42 +1000 Subject: [PATCH 0796/1752] evision projector updates --- modules/digital_projection/evision_7500.rb | 64 ++++++++++++++++------ 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/modules/digital_projection/evision_7500.rb b/modules/digital_projection/evision_7500.rb index c1cbe0ee..f234d6f2 100644 --- a/modules/digital_projection/evision_7500.rb +++ b/modules/digital_projection/evision_7500.rb @@ -1,8 +1,8 @@ -module Wolfvision; end +module DigitalProjection; end -# Documentation: https://www.wolfvision.com/wolf/protocol_command_wolfvision/protocol/commands_eye-14.pdf +# Documentation: http://www.digitalprojection.co.uk/dpdownloads/Protocol/Simplified-Protocol-Guide-Rev-H.pdf -class Wolfvision::Eye14 +class DigitalProjection::Evision7500 include ::Orchestrator::Constants include ::Orchestrator::Transcoder @@ -11,19 +11,35 @@ class Wolfvision::Eye14 descriptive_name 'Digital Projection E-Vision Laser 7500' generic_name :Display - tokenize delimiter: "\r" + tokenize indicator: /ack|ACK/, delimiter: "\x0D" - INPUTS = { - :hdmi => 0, - :hdmi2 => 1, - :vga => 2, - :comp => 3, - :dvi => 4, - :displayport => 5, - :hdbaset => 6, - :sdi3g => 7 - } - INPUTS.merge!(INPUTS.invert) + def on_load + on_update + end + + def on_update + + end + + def connected + do_poll + schedule.every('60s') do + logger.debug "-- Polling Display" + do_poll + end + end + + def disconnected + schedule.clear + end + + def do_poll + power? do + if self[:power] + input? + end + end + end def power(state) target = is_affirmative?(state) @@ -38,16 +54,27 @@ def power(state) end def power? - send_cmd("power ?", name: :power) + send_cmd("power ?", name: :power, priority: 0) end + INPUTS = { + :hdmi => 0, + :hdmi2 => 1, + :vga => 2, + :comp => 3, + :dvi => 4, + :displayport => 5, + :hdbaset => 6, + :sdi3g => 7 + } + INPUTS.merge!(INPUTS.invert) def switch_to(input) input = input.to_sym if input.class == String send_cmd("input = #{INPUTS[input]}", name: :input) end def input? - send_cmd("power ?", name: :input) + send_cmd("power ?", name: :input, priority: 0) end def send_cmd(cmd, options = {}) @@ -63,7 +90,8 @@ def received(data, deferrable, command) return :success if command.nil? || command[:name].nil? # \A is the beginning of the line - if(!data =~ /\Aack/) # if it doesn't return ack it's a failed response + if(data =~ /\ANAK|\Anack/) # syntax error or other + logger.debug { data } return :failed end From 3e6efb8d46ea5135b6b0a71bb74658460958ea85 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 10 Sep 2018 16:21:09 +1000 Subject: [PATCH 0797/1752] fixes to evision projector driver --- modules/digital_projection/evision_7500.rb | 55 +++++++---- modules/elo/display_4202L.rb | 43 +++++++++ modules/shure/mixer/p300.rb | 104 +++++++++++++++++++++ 3 files changed, 183 insertions(+), 19 deletions(-) create mode 100644 modules/elo/display_4202L.rb create mode 100644 modules/shure/mixer/p300.rb diff --git a/modules/digital_projection/evision_7500.rb b/modules/digital_projection/evision_7500.rb index f234d6f2..978ec6f0 100644 --- a/modules/digital_projection/evision_7500.rb +++ b/modules/digital_projection/evision_7500.rb @@ -8,17 +8,16 @@ class DigitalProjection::Evision7500 # Discovery Information tcp_port 7000 - descriptive_name 'Digital Projection E-Vision Laser 7500' + descriptive_name 'Digital Projection E-Vision Laser 4K' generic_name :Display - tokenize indicator: /ack|ACK/, delimiter: "\x0D" + tokenize delimiter: "\x0D" def on_load on_update end def on_update - end def connected @@ -58,14 +57,13 @@ def power? end INPUTS = { - :hdmi => 0, - :hdmi2 => 1, - :vga => 2, - :comp => 3, - :dvi => 4, - :displayport => 5, - :hdbaset => 6, - :sdi3g => 7 + :display_port => 0, + :hdmi => 1, + :hdmi2 => 2, + :hdbaset => 3, + :sdi3g => 4, + :hdmi3 => 5, + :hdmi4 => 6 } INPUTS.merge!(INPUTS.invert) def switch_to(input) @@ -74,34 +72,53 @@ def switch_to(input) end def input? - send_cmd("power ?", name: :input, priority: 0) + send_cmd("input ?", name: :input, priority: 0) + end + + # this projector uses a laser instead of a lamp + def laser? + send_cmd("laser.hours ?", name: :laser_inq, priority: 0) + end + + def laser_reset + send_cmd("laser.reset", name: :laser_reset) + end + + def error? + send_cmd("errcode", name: :error, priority: 0) + end + + def freeze? + send_cmd("freeze", name: :freeze, priority: 0) end def send_cmd(cmd, options = {}) req = "*#{cmd}" + logger.debug { "Sending: #{req}" } req << 0x0D - logger.debug { "tell -- 0x#{byte_to_hex(req)} -- #{options[:name]}" } send(req, options) end def received(data, deferrable, command) - logger.debug { "Received 0x#{byte_to_hex(data)}" } + logger.debug { "Received: #{data}" } return :success if command.nil? || command[:name].nil? # \A is the beginning of the line if(data =~ /\ANAK|\Anack/) # syntax error or other - logger.debug { data } return :failed end - # regex match the value - data = data[-1].to_i case command[:name] when :power - self[:power] = data == 1 + self[:power] = data[-1].to_i == 1 when :input - self[:input] = INPUT[data] + self[:input] = INPUT[data[-1].to_i] + when :laser_inq + # return whatever number at the end of the string + self[:laser] = data[/\d+\z/].to_i + when :error + end return :success end diff --git a/modules/elo/display_4202L.rb b/modules/elo/display_4202L.rb new file mode 100644 index 00000000..f742fb45 --- /dev/null +++ b/modules/elo/display_4202L.rb @@ -0,0 +1,43 @@ +module Elo; end + +# Documentation: https://docs.elotouch.com/collateral/ELO_APP_Notes_17084AEB00033_Web.pdf + +class Elo::Display4202L + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 7000 + descriptive_name 'Elo 4202L' + generic_name :Display + + tokenize indicator: /ack|ACK/, delimiter: "\x0D" + + def on_load + on_update + end + + def on_update + + end + + def connected + do_poll + schedule.every('60s') do + logger.debug "-- Polling Display" + do_poll + end + end + + def disconnected + schedule.clear + end + + def do_poll + power? do + if self[:power] + input? + end + end + end +end diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb new file mode 100644 index 00000000..49fb0ec0 --- /dev/null +++ b/modules/shure/mixer/p300.rb @@ -0,0 +1,104 @@ +module Elo; end + +# Documentation: https://pubs.shure.com/guide/P300/en-US#c_c2b570b7-f7ef-444b-b01f-c1db82b064df + +class Elo::Display4202L + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 2202 + descriptive_name 'Shure P300 IntelliMix Audio Conferencing Processor' + generic_name :Mixer + + tokenize indicator: /ack|ACK/, delimiter: "\x0D" + + def on_load + on_update + + self[:output_volume_max] = 1400 + self[:output_volume_min] = 0 + end + + def on_update + end + + def connected + do_poll + schedule.every('60s') do + do_poll + end + end + + def disconnected + schedule.clear + end + + def do_poll + end + + def reboot + send_cmd("REBOOT", name: :reboot) + end + + def preset(number) + send_cmd("PRESET #{number}", name: :present_cmd) + end + + def preset? + send_inq("PRESET", name: :preset_inq, priority: 0) + end + + def flash_leds + send_cmd("FLASH ON", name: :flash_cmd) + end + + def volume(group, value) + val = in_range(value, self[:zoom_max], self[:zoom_min]) + + send_cmd("AUDIO_GAIN_HI_RES #{val.to_s.rjust(4, '0')}") + end + + def volume?(group) + end + + def mute(group, value = true) + state = is_affirmative?(value) ? "ON" : "OFF" + + faders = group.is_a?(Array) ? group : [group] + + faders.each do |fad| + send_cmd("#{fad} AUDIO_MUTE #{state}", group_type: :mute, wait: true) + end + end + + def unmute(group) + mute(group, false) + end + + def send_inq(cmd, options = {}) + req = "GET #{cmd}" + logger.debug { "Sending: #{req}" } + send(req, options) + end + + def send_cmd(cmd, options = {}) + req = "SET #{cmd}" + logger.debug { "Sending: #{req}" } + send(req, options) + end + + def received(data, deferrable, command) + logger.debug { "Received: #{data}" } + + return :success if command.nil? || command[:name].nil? + + case command[:name] + when :power + self[:power] = data == 1 + when :input + self[:input] = INPUT[data] + end + return :success + end +end From b73c67f892225dbc1f6e74bd4cc59cbdbb5e6d48 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 10 Sep 2018 16:52:26 +1000 Subject: [PATCH 0798/1752] added spec file for evision --- modules/digital_projection/evision_7500.rb | 17 ++++++++-- .../digital_projection/evision_7500_spec.rb | 31 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 modules/digital_projection/evision_7500_spec.rb diff --git a/modules/digital_projection/evision_7500.rb b/modules/digital_projection/evision_7500.rb index 978ec6f0..65716412 100644 --- a/modules/digital_projection/evision_7500.rb +++ b/modules/digital_projection/evision_7500.rb @@ -88,8 +88,20 @@ def error? send_cmd("errcode", name: :error, priority: 0) end + def freeze(state) + target = is_affirmative?(state) + self[:power_target] = target + + logger.debug { "Target = #{target} and self[:freeze] = #{self[:freeze]}" } + if target == On && self[:freeze] != On + send_cmd("freeze = 1", name: :freeze) + elsif target == Off && self[:freeze] != Off + send_cmd("freeze = 0", name: :freeze) + end + end + def freeze? - send_cmd("freeze", name: :freeze, priority: 0) + send_cmd("freeze ?", name: :freeze, priority: 0) end def send_cmd(cmd, options = {}) @@ -118,7 +130,8 @@ def received(data, deferrable, command) # return whatever number at the end of the string self[:laser] = data[/\d+\z/].to_i when :error - + when :freeze + self[:freeze] = data[-1].to_i == 1 end return :success end diff --git a/modules/digital_projection/evision_7500_spec.rb b/modules/digital_projection/evision_7500_spec.rb new file mode 100644 index 00000000..056ea8ea --- /dev/null +++ b/modules/digital_projection/evision_7500_spec.rb @@ -0,0 +1,31 @@ +Orchestrator::Testing.mock_device 'Wolfvision::Eye14' do + exec(:power?) + .should_send("*power ?\r") # power query + .responds("ack power = 0") # respond with on + .expect(status[:power]).to be(false) + + exec(:power, true) + .should_send("*power = 0\r") # power query + .responds("ack power = 0") # respond with on + .expect(status[:power]).to be(true) + + exec(:input?) + .should_send("*input ?\r") + .responds("ack input = 0") # respond with on + .expect(status[:input]).to be(:display_port) + + exec(:switch_to, "hdmi") + .should_send("*input = 1\r") + .responds("ack input = 1") # respond with on + .expect(status[:input]).to be(:hdmi) + + exec(:freeze?) + .should_send("*freeze ?\r") # power query + .responds("ack freeze = 0") # respond with on + .expect(status[:freeze]).to be(false) + + exec(:freeze, true) + .should_send("*freeze = 1\r") # power query + .responds("ack power = 1") # respond with on + .expect(status[:freeze]).to be(true) +end From 79bdb0c02f50634de16012277d470a72cc9bf597 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 11 Sep 2018 00:41:33 +1000 Subject: [PATCH 0799/1752] (cisco:ce) parse responses larger than 2kb in the thread pool --- modules/cisco/collaboration_endpoint/room_os.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 4043345e..ddf04ba7 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -73,7 +73,8 @@ def disconnected def received(data, deferrable, command) logger.debug { "<- #{data}" } - response = Response.parse data, into: CaseInsensitiveHash + do_parse = proc { Response.parse data, into: CaseInsensitiveHash } + response = data.length > 2048 ? task(&do_parse).value : do_parse.call if block_given? # Let any pending command response handlers have first pass... From f09fae726530defd2896e1241b72409f0fd040f2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 11 Sep 2018 17:35:55 +1000 Subject: [PATCH 0800/1752] (cisco:ce) add call accept/reject and status --- modules/cisco/collaboration_endpoint/room_kit.rb | 3 +++ modules/cisco/collaboration_endpoint/sx20.rb | 3 +++ modules/cisco/collaboration_endpoint/sx80.rb | 3 +++ 3 files changed, 9 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 6cf63ebc..256ddb76 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -34,6 +34,7 @@ def connected status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume + status 'Call' => :call_status status 'Cameras SpeakerTrack' => :speaker_track status 'RoomAnalytics PeoplePresence' => :presence_detected status 'RoomAnalytics PeopleCount Current' => :people_count @@ -65,6 +66,8 @@ def mic_mute(state = On) command 'Audio Volume Set' => :volume, Level: (0..100) + command 'Call Accept' => :call_accept, CallId_: Integer + command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer command 'Dial' => :dial, Number: String, diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index ee4e0103..c4ed6439 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -34,6 +34,7 @@ def connected status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume + status 'Call' => :call_status status 'RoomAnalytics PeoplePresence' => :presence_detected status 'Conference DoNotDisturb' => :do_not_disturb status 'Conference Presentation Mode' => :presentation @@ -60,6 +61,8 @@ def mic_mute(state = On) Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound + command 'Call Accept' => :call_accept, CallId_: Integer + command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer command 'Dial' => :dial, Number: String, diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index d9c0d09b..2d297e0b 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -34,6 +34,7 @@ def connected status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume + status 'Call' => :call status 'Cameras PresenterTrack' => :presenter_track status 'Cameras SpeakerTrack' => :speaker_track status 'RoomAnalytics PeoplePresence' => :presence_detected @@ -65,6 +66,8 @@ def mic_mute(state = On) command 'Audio Volume Set' => :volume, Level: (0..100) + command 'Call Accept' => :call_accept, CallId_: Integer + command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer command 'Dial' => :dial, Number: String, From 3aa1f2a4b06ad6f97511e2d239fb90532703cf73 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 11 Sep 2018 18:57:10 +1000 Subject: [PATCH 0801/1752] (cisco:ce) fix error when registering feedback a second time --- modules/cisco/collaboration_endpoint/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index ddf04ba7..6eaac2f0 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -292,7 +292,7 @@ def register_feedback(path, &update_handler) device_subscriptions.insert path, &update_handler - result || thread.defer.resolve.promise(:success) + result || :success end def unregister_feedback(path) From beaa6efc0383851dbee79a7382e4f513c85d868e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 11 Sep 2018 18:59:33 +1000 Subject: [PATCH 0802/1752] (cisco:ce) only track active calls --- modules/cisco/collaboration_endpoint/room_kit.rb | 11 +++++++++-- modules/cisco/collaboration_endpoint/sx20.rb | 11 +++++++++-- modules/cisco/collaboration_endpoint/sx80.rb | 11 +++++++++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 256ddb76..94997474 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -30,18 +30,25 @@ def connected register_feedback '/Event/PresentationPreviewStopped' do self[:local_presentation] = false end + + register_feedback '/Status/Call' do |call| + current = self[:calls].is_a?(Hash) ? self[:calls] : {} + calls = current.deep_merge(call) + calls.reject! do |_, props| + props[:status] == :Idle + end + self[:calls] = calls + end end status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume - status 'Call' => :call_status status 'Cameras SpeakerTrack' => :speaker_track status 'RoomAnalytics PeoplePresence' => :presence_detected status 'RoomAnalytics PeopleCount Current' => :people_count status 'Conference DoNotDisturb' => :do_not_disturb status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals - status 'SystemUnit State NumberOfActiveCalls' => :active_calls status 'Video SelfView Mode' => :selfview status 'Video Input' => :video_input status 'Video Output' => :video_output diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index c4ed6439..1e9ce3cd 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -30,16 +30,23 @@ def connected register_feedback '/Event/PresentationPreviewStopped' do self[:local_presentation] = false end + + register_feedback '/Status/Call' do |call| + current = self[:calls].is_a?(Hash) ? self[:calls] : {} + calls = current.deep_merge(call) + calls.reject! do |_, props| + props[:status] == :Idle + end + self[:calls] = calls + end end status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume - status 'Call' => :call_status status 'RoomAnalytics PeoplePresence' => :presence_detected status 'Conference DoNotDisturb' => :do_not_disturb status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals - status 'SystemUnit State NumberOfActiveCalls' => :active_calls status 'Video SelfView Mode' => :selfview status 'Video Input' => :video_input status 'Video Output' => :video_output diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 2d297e0b..9cd2f0ef 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -30,18 +30,25 @@ def connected register_feedback '/Event/PresentationPreviewStopped' do self[:local_presentation] = false end + + register_feedback '/Status/Call' do |call| + current = self[:calls].is_a?(Hash) ? self[:calls] : {} + calls = current.deep_merge(call) + calls.reject! do |_, props| + props[:status] == :Idle + end + self[:calls] = calls + end end status 'Audio Microphones Mute' => :mic_mute status 'Audio Volume' => :volume - status 'Call' => :call status 'Cameras PresenterTrack' => :presenter_track status 'Cameras SpeakerTrack' => :speaker_track status 'RoomAnalytics PeoplePresence' => :presence_detected status 'Conference DoNotDisturb' => :do_not_disturb status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals - status 'SystemUnit State NumberOfActiveCalls' => :active_calls status 'Video SelfView Mode' => :selfview status 'Video Input' => :video_input status 'Video Output' => :video_output From 28c88c696b099ab1a11d8629a9442f07515079a2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 11 Sep 2018 19:05:46 +1000 Subject: [PATCH 0803/1752] (cisco:ce) hide old call entries --- modules/cisco/collaboration_endpoint/room_kit.rb | 2 +- modules/cisco/collaboration_endpoint/sx20.rb | 2 +- modules/cisco/collaboration_endpoint/sx80.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 94997474..917b3c31 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -35,7 +35,7 @@ def connected current = self[:calls].is_a?(Hash) ? self[:calls] : {} calls = current.deep_merge(call) calls.reject! do |_, props| - props[:status] == :Idle + props[:status] == :Idle || props.include?(:ghost) end self[:calls] = calls end diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 1e9ce3cd..0bce71e0 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -35,7 +35,7 @@ def connected current = self[:calls].is_a?(Hash) ? self[:calls] : {} calls = current.deep_merge(call) calls.reject! do |_, props| - props[:status] == :Idle + props[:status] == :Idle || props.include?(:ghost) end self[:calls] = calls end diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 9cd2f0ef..41976cac 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -35,7 +35,7 @@ def connected current = self[:calls].is_a?(Hash) ? self[:calls] : {} calls = current.deep_merge(call) calls.reject! do |_, props| - props[:status] == :Idle + props[:status] == :Idle || props.include?(:ghost) end self[:calls] = calls end From 7e0cb7c8026af4787817e0fd7eb8d629c7ebfe4c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 11 Sep 2018 19:53:38 +1000 Subject: [PATCH 0804/1752] (cisco:ce) remove access restriction from presenter track control --- modules/cisco/collaboration_endpoint/sx80.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 41976cac..a16acad1 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -125,9 +125,8 @@ def mic_mute(state = On) command! 'Cameras PresenterTrack ClearPosition' => :presenter_track_clear command! 'Cameras PresenterTrack StorePosition' => :presenter_track_store - command! 'Cameras PresenterTrack Set' => :presenter_track, - Mode: [:Off, :Follow, :Diagnostic, :Background, :Setup, - :Persistant] + command 'Cameras PresenterTrack Set' => :presenter_track, + Mode: [:Off, :Follow, :Diagnostic, :Background, :Setup, :Persistant] command! 'Cameras SpeakerTrack Diagnostics Start' => \ :speaker_track_diagnostics_start From 58e3767f6b174cdc0c846a27f0cab4fe9a3e86b9 Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 12 Sep 2018 12:16:27 +1000 Subject: [PATCH 0805/1752] updated drivers --- modules/digital_projection/evision_7500.rb | 6 +++--- modules/shure/mixer/p300.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/digital_projection/evision_7500.rb b/modules/digital_projection/evision_7500.rb index 65716412..68840e9f 100644 --- a/modules/digital_projection/evision_7500.rb +++ b/modules/digital_projection/evision_7500.rb @@ -123,15 +123,15 @@ def received(data, deferrable, command) case command[:name] when :power - self[:power] = data[-1].to_i == 1 + self[:power] = data[-1] == "1" when :input self[:input] = INPUT[data[-1].to_i] when :laser_inq - # return whatever number at the end of the string + # return whatever number is at the end of the string self[:laser] = data[/\d+\z/].to_i when :error when :freeze - self[:freeze] = data[-1].to_i == 1 + self[:freeze] = data[-1] == "1" end return :success end diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index 49fb0ec0..0331652d 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -1,8 +1,8 @@ -module Elo; end +module Shure::Mixer; end # Documentation: https://pubs.shure.com/guide/P300/en-US#c_c2b570b7-f7ef-444b-b01f-c1db82b064df -class Elo::Display4202L +class Shure::Mixer::P300 include ::Orchestrator::Constants include ::Orchestrator::Transcoder From 2fd8fa3196c2e731d1268f0477ef9f1ba4e917cb Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 12 Sep 2018 15:20:00 +1000 Subject: [PATCH 0806/1752] added more commands to shure dsp --- modules/shure/mixer/p300.rb | 47 ++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index 0331652d..ee393afd 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -1,6 +1,6 @@ module Shure::Mixer; end -# Documentation: https://pubs.shure.com/guide/P300/en-US#c_c2b570b7-f7ef-444b-b01f-c1db82b064df +# Documentation: http://www.shure.pl/dms/shure/products/mixer/user_guides/shure_intellimix_p300_command_strings/shure_intellimix_p300_command_strings.pdf class Shure::Mixer::P300 include ::Orchestrator::Constants @@ -11,7 +11,7 @@ class Shure::Mixer::P300 descriptive_name 'Shure P300 IntelliMix Audio Conferencing Processor' generic_name :Mixer - tokenize indicator: /ack|ACK/, delimiter: "\x0D" + tokenize indicator: "< REP ", delimiter: " >" def on_load on_update @@ -53,13 +53,13 @@ def flash_leds send_cmd("FLASH ON", name: :flash_cmd) end - def volume(group, value) + def gain(group, value) val = in_range(value, self[:zoom_max], self[:zoom_min]) send_cmd("AUDIO_GAIN_HI_RES #{val.to_s.rjust(4, '0')}") end - def volume?(group) + def gain?(group) end def mute(group, value = true) @@ -76,14 +76,28 @@ def unmute(group) mute(group, false) end + def mute_all(value = true) + state = is_affirmative?(value) ? "ON" : "OFF" + + send_cmd("DEVICE_AUDIO_MUTE #{state}", name: :mute) + end + + def unmute_all + mute_all(false) + end + + def error? + send_inq("LAST_ERROR_EVENT", name: :error) + end + def send_inq(cmd, options = {}) - req = "GET #{cmd}" + req = "< GET #{cmd} >" logger.debug { "Sending: #{req}" } send(req, options) end def send_cmd(cmd, options = {}) - req = "SET #{cmd}" + req = "< SET #{cmd} >" logger.debug { "Sending: #{req}" } send(req, options) end @@ -93,11 +107,22 @@ def received(data, deferrable, command) return :success if command.nil? || command[:name].nil? - case command[:name] - when :power - self[:power] = data == 1 - when :input - self[:input] = INPUT[data] + data = data.split + cmd = data[-2].to_sym + + case cmd + when :PRESET + self[:preset] = data[-1].to_i + when :DEVICE_AUDIO_MUTE + self[:mute] = data[-1] == "ON" + when :AUDIO_MUTE + self["input#{data[0]}_mute"] = data[-1] == "ON" + when :AUDIO_GAIN_HI_RES + self["input#{data[0]}_gain"] = data[-1].to_i + when :LAST_ERROR_EVENT + error = data[1..-1].join(" ") + logger.debug { "Last error is :" } + logger.debug { error } end return :success end From 7a00476cfc2caf8a79a09a661185369dcb68c44a Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 12 Sep 2018 16:48:35 +1000 Subject: [PATCH 0807/1752] added preset aliases to shure dsp --- modules/shure/mixer/p300.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index ee393afd..c9fe3790 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -16,8 +16,8 @@ class Shure::Mixer::P300 def on_load on_update - self[:output_volume_max] = 1400 - self[:output_volume_min] = 0 + self[:output_gain_max] = 1400 + self[:output_gain_min] = 0 end def on_update @@ -44,6 +44,7 @@ def reboot def preset(number) send_cmd("PRESET #{number}", name: :present_cmd) end + alias_method :trigger, :snapshot def preset? send_inq("PRESET", name: :preset_inq, priority: 0) @@ -54,7 +55,7 @@ def flash_leds end def gain(group, value) - val = in_range(value, self[:zoom_max], self[:zoom_min]) + val = in_range(value, self[:output_gain_max], self[:output_gain_min]) send_cmd("AUDIO_GAIN_HI_RES #{val.to_s.rjust(4, '0')}") end From 3a277217f293822bb61fabb8d254598b33673e61 Mon Sep 17 00:00:00 2001 From: pkheav Date: Fri, 14 Sep 2018 14:02:27 +1000 Subject: [PATCH 0808/1752] shure p300 updates --- modules/shure/mixer/p300.rb | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index c9fe3790..b45ce27f 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -24,10 +24,6 @@ def on_update end def connected - do_poll - schedule.every('60s') do - do_poll - end end def disconnected @@ -57,11 +53,22 @@ def flash_leds def gain(group, value) val = in_range(value, self[:output_gain_max], self[:output_gain_min]) - send_cmd("AUDIO_GAIN_HI_RES #{val.to_s.rjust(4, '0')}") + faders = group.is_a?(Array) ? group : [group] + + faders.each do |fad| + send_cmd("#{fad} AUDIO_GAIN_HI_RES #{val.to_s.rjust(4, '0')}", group_type: :fader_cmd, wait: true) + end end + alias_method :fader def gain?(group) + faders = group.is_a?(Array) ? group : [group] + + faders.each do |fad| + send_inq("#{fad} AUDIO_GAIN_HI_RES", group_type: :fader_inq, wait: true, priority: 0) + end end + alias_method :fader? def mute(group, value = true) state = is_affirmative?(value) ? "ON" : "OFF" @@ -69,7 +76,15 @@ def mute(group, value = true) faders = group.is_a?(Array) ? group : [group] faders.each do |fad| - send_cmd("#{fad} AUDIO_MUTE #{state}", group_type: :mute, wait: true) + send_cmd("#{fad} AUDIO_MUTE #{state}", group_type: :mute_cmd, wait: true) + end + end + + def gain?(group) + faders = group.is_a?(Array) ? group : [group] + + faders.each do |fad| + send_inq("#{fad} AUDIO_MUTE", group_type: :mute_inq, wait: true, priority: 0) end end From 9561112a62a260c9b1373713f7d1592d2df3aa41 Mon Sep 17 00:00:00 2001 From: pkheav Date: Fri, 14 Sep 2018 14:03:49 +1000 Subject: [PATCH 0809/1752] remove elo display --- modules/elo/display_4202L.rb | 43 ------------------------------------ 1 file changed, 43 deletions(-) delete mode 100644 modules/elo/display_4202L.rb diff --git a/modules/elo/display_4202L.rb b/modules/elo/display_4202L.rb deleted file mode 100644 index f742fb45..00000000 --- a/modules/elo/display_4202L.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Elo; end - -# Documentation: https://docs.elotouch.com/collateral/ELO_APP_Notes_17084AEB00033_Web.pdf - -class Elo::Display4202L - include ::Orchestrator::Constants - include ::Orchestrator::Transcoder - - # Discovery Information - tcp_port 7000 - descriptive_name 'Elo 4202L' - generic_name :Display - - tokenize indicator: /ack|ACK/, delimiter: "\x0D" - - def on_load - on_update - end - - def on_update - - end - - def connected - do_poll - schedule.every('60s') do - logger.debug "-- Polling Display" - do_poll - end - end - - def disconnected - schedule.clear - end - - def do_poll - power? do - if self[:power] - input? - end - end - end -end From 176c4e0de546aa71a020787dd8a93a8618abdca9 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 14 Sep 2018 14:32:59 +1000 Subject: [PATCH 0810/1752] (panasonic:lcd) push changes re: delay tested against real device by Steve --- modules/panasonic/lcd/protocol2.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/panasonic/lcd/protocol2.rb b/modules/panasonic/lcd/protocol2.rb index f706c4ca..8a1db7ea 100755 --- a/modules/panasonic/lcd/protocol2.rb +++ b/modules/panasonic/lcd/protocol2.rb @@ -70,12 +70,12 @@ def power(state, opt = nil) self[:power_stable] = false if is_affirmative?(state) self[:power_target] = On - do_send(:power_on, retries: 10, name: :power, delay_on_receive: 8000) + do_send(:power_on, retries: 10, name: :power, delay: 10000, timeout: 15000) logger.debug "requested to power on" do_send(:power_query) else self[:power_target] = Off - do_send(:power_off, retries: 10, name: :power, delay_on_receive: 8000) + do_send(:power_off, retries: 10, name: :power, delay: 8000) logger.debug "requested to power off" do_send(:power_query) end @@ -178,6 +178,7 @@ def received(data, resolve, command) # Data is default received as a stri # We're actually handling the connection check performed by makebreak # This ensure that the connection is closed if command.nil? + logger.debug 'disconnecting as no command to process' disconnect return :success end From 3e4ab9cf7ea51fc06f2be0deaebc8a490ef38ced Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 14 Sep 2018 14:40:44 +1000 Subject: [PATCH 0811/1752] (shure:p300) fix alias syntax --- modules/shure/mixer/p300.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index b45ce27f..ec766e27 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -40,7 +40,7 @@ def reboot def preset(number) send_cmd("PRESET #{number}", name: :present_cmd) end - alias_method :trigger, :snapshot + alias_method :trigger, :snapshot, :preset def preset? send_inq("PRESET", name: :preset_inq, priority: 0) @@ -59,7 +59,7 @@ def gain(group, value) send_cmd("#{fad} AUDIO_GAIN_HI_RES #{val.to_s.rjust(4, '0')}", group_type: :fader_cmd, wait: true) end end - alias_method :fader + alias_method :fader, :gain def gain?(group) faders = group.is_a?(Array) ? group : [group] @@ -68,7 +68,7 @@ def gain?(group) send_inq("#{fad} AUDIO_GAIN_HI_RES", group_type: :fader_inq, wait: true, priority: 0) end end - alias_method :fader? + alias_method :fader?, :gain? def mute(group, value = true) state = is_affirmative?(value) ? "ON" : "OFF" From 5819180e0c91840e96b624894a1fde48ab219ee0 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 14 Sep 2018 14:45:39 +1000 Subject: [PATCH 0812/1752] (shure:p300) fix alias_method syntax it only takes 2 args --- modules/shure/mixer/p300.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index ec766e27..8f7ffe3a 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -40,7 +40,8 @@ def reboot def preset(number) send_cmd("PRESET #{number}", name: :present_cmd) end - alias_method :trigger, :snapshot, :preset + alias_method :trigger, :preset + alias_method :snapshot, :preset def preset? send_inq("PRESET", name: :preset_inq, priority: 0) From 6d4265298aa2deeb00737e0eede3b38ce0069729 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 17 Sep 2018 14:33:26 +1000 Subject: [PATCH 0813/1752] added shure p300 spec --- modules/shure/mixer/p300.rb | 30 ++++++++++--------- modules/shure/mixer/p300_spec.rb | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 modules/shure/mixer/p300_spec.rb diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index 8f7ffe3a..5157032e 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -1,3 +1,4 @@ +module Shure; end module Shure::Mixer; end # Documentation: http://www.shure.pl/dms/shure/products/mixer/user_guides/shure_intellimix_p300_command_strings/shure_intellimix_p300_command_strings.pdf @@ -41,11 +42,11 @@ def preset(number) send_cmd("PRESET #{number}", name: :present_cmd) end alias_method :trigger, :preset - alias_method :snapshot, :preset def preset? send_inq("PRESET", name: :preset_inq, priority: 0) end + alias_method :trigger?, :preset? def flash_leds send_cmd("FLASH ON", name: :flash_cmd) @@ -57,7 +58,7 @@ def gain(group, value) faders = group.is_a?(Array) ? group : [group] faders.each do |fad| - send_cmd("#{fad} AUDIO_GAIN_HI_RES #{val.to_s.rjust(4, '0')}", group_type: :fader_cmd, wait: true) + send_cmd("#{fad.to_s.rjust(2, '0')} AUDIO_GAIN_HI_RES #{val.to_s.rjust(4, '0')}", group_type: :fader_cmd, wait: true) end end alias_method :fader, :gain @@ -66,7 +67,7 @@ def gain?(group) faders = group.is_a?(Array) ? group : [group] faders.each do |fad| - send_inq("#{fad} AUDIO_GAIN_HI_RES", group_type: :fader_inq, wait: true, priority: 0) + send_inq("#{fad.to_s.rjust(2, '0')} AUDIO_GAIN_HI_RES", group_type: :fader_inq, wait: true, priority: 0) end end alias_method :fader?, :gain? @@ -77,22 +78,23 @@ def mute(group, value = true) faders = group.is_a?(Array) ? group : [group] faders.each do |fad| - send_cmd("#{fad} AUDIO_MUTE #{state}", group_type: :mute_cmd, wait: true) + send_cmd("#{fad.to_s.rjust(2, '0')} AUDIO_MUTE #{state}", group_type: :mute_cmd, wait: true) end end - def gain?(group) + def unmute(group) + mute(group, false) + end + + def mute?(group) faders = group.is_a?(Array) ? group : [group] faders.each do |fad| - send_inq("#{fad} AUDIO_MUTE", group_type: :mute_inq, wait: true, priority: 0) + send_inq("#{fad.to_s.rjust(2, '0')} AUDIO_MUTE", group_type: :mute_inq, wait: true, priority: 0) end end - def unmute(group) - mute(group, false) - end - + # not sure what the difference between this mute is def mute_all(value = true) state = is_affirmative?(value) ? "ON" : "OFF" @@ -122,7 +124,9 @@ def send_cmd(cmd, options = {}) def received(data, deferrable, command) logger.debug { "Received: #{data}" } - return :success if command.nil? || command[:name].nil? + # Exit function early if command is nil or + # if command is not nil and both name and group_type are nil + return :success if command.nil? || (command[:name].nil? && command[:group_type].nil?) data = data.split cmd = data[-2].to_sym @@ -133,9 +137,9 @@ def received(data, deferrable, command) when :DEVICE_AUDIO_MUTE self[:mute] = data[-1] == "ON" when :AUDIO_MUTE - self["input#{data[0]}_mute"] = data[-1] == "ON" + self["channel#{data[0].to_i}_mute"] = data[-1] == "ON" when :AUDIO_GAIN_HI_RES - self["input#{data[0]}_gain"] = data[-1].to_i + self["channel#{data[0].to_i}_gain"] = data[-1].to_i when :LAST_ERROR_EVENT error = data[1..-1].join(" ") logger.debug { "Last error is :" } diff --git a/modules/shure/mixer/p300_spec.rb b/modules/shure/mixer/p300_spec.rb new file mode 100644 index 00000000..e7ea288e --- /dev/null +++ b/modules/shure/mixer/p300_spec.rb @@ -0,0 +1,49 @@ +Orchestrator::Testing.mock_device 'Shure::Mixer::P300' do + exec(:trigger?) + .should_send("< GET PRESET >") + .responds("< REP PRESET 06 >") + .expect(status[:preset]).to be(6) + + exec(:trigger, 8) + .should_send("< SET PRESET 8 >") + .responds("< REP PRESET 8 >") + .expect(status[:preset]).to be(8) + + exec(:flash_leds) + .should_send("< SET FLASH ON >") + .responds("< REP FLASH ON >") + + exec(:fader?, 0) + .should_send("< GET 00 AUDIO_GAIN_HI_RES >") + .responds("< REP 00 AUDIO_GAIN_HI_RES 0022 >") + .expect(status[:channel0_gain]).to be(22) + + exec(:fader?, [1,2,3]) + .should_send("< GET 01 AUDIO_GAIN_HI_RES >") + .responds("< REP 01 AUDIO_GAIN_HI_RES 0001 >") + .should_send("< GET 02 AUDIO_GAIN_HI_RES >") + .responds("< REP 02 AUDIO_GAIN_HI_RES 1111 >") + .should_send("< GET 03 AUDIO_GAIN_HI_RES >") + .responds("< REP 03 AUDIO_GAIN_HI_RES 0321 >") + + exec(:fader, [1,2,3], 39) + .should_send("< SET 01 AUDIO_GAIN_HI_RES 0039 >") + .responds("< REP 01 AUDIO_GAIN_HI_RES 0039 >") + .should_send("< SET 02 AUDIO_GAIN_HI_RES 0039 >") + .responds("< REP 02 AUDIO_GAIN_HI_RES 0039 >") + .should_send("< SET 03 AUDIO_GAIN_HI_RES 0039 >") + .responds("< REP 03 AUDIO_GAIN_HI_RES 0039 >") + + exec(:mute?, 10) + .should_send("< GET 10 AUDIO_MUTE >") + .responds("< REP 10 AUDIO_MUTE OFF >") + .expect(status[:channel10_mute]).to be(false) + + exec(:mute, [1,2,3]) + .should_send("< SET 01 AUDIO_MUTE ON >") + .responds("< REP 01 AUDIO_MUTE ON >") + .should_send("< SET 02 AUDIO_MUTE ON >") + .responds("< REP 02 AUDIO_MUTE ON >") + .should_send("< SET 03 AUDIO_MUTE ON >") + .responds("< REP 03 AUDIO_MUTE ON >") +end From c829d0d1a0d021d84b7f35698f86ef42f8c4533c Mon Sep 17 00:00:00 2001 From: pkheav Date: Tue, 18 Sep 2018 11:03:19 +1000 Subject: [PATCH 0814/1752] updated spec file for evision projector and shure p300 --- modules/digital_projection/evision_7500.rb | 49 ++++++++----------- .../digital_projection/evision_7500_spec.rb | 35 ++++++++++--- modules/shure/mixer/p300.rb | 7 ++- modules/shure/mixer/p300_spec.rb | 5 ++ 4 files changed, 58 insertions(+), 38 deletions(-) diff --git a/modules/digital_projection/evision_7500.rb b/modules/digital_projection/evision_7500.rb index 68840e9f..6de1235f 100644 --- a/modules/digital_projection/evision_7500.rb +++ b/modules/digital_projection/evision_7500.rb @@ -1,8 +1,12 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + + module DigitalProjection; end # Documentation: http://www.digitalprojection.co.uk/dpdownloads/Protocol/Simplified-Protocol-Guide-Rev-H.pdf -class DigitalProjection::Evision7500 +class DigitalProjection::Evision_7500 include ::Orchestrator::Constants include ::Orchestrator::Transcoder @@ -11,7 +15,7 @@ class DigitalProjection::Evision7500 descriptive_name 'Digital Projection E-Vision Laser 4K' generic_name :Display - tokenize delimiter: "\x0D" + tokenize delimiter: "\r" def on_load on_update @@ -20,35 +24,19 @@ def on_load def on_update end - def connected - do_poll - schedule.every('60s') do - logger.debug "-- Polling Display" - do_poll - end - end - def disconnected schedule.clear end - def do_poll - power? do - if self[:power] - input? - end - end - end - def power(state) target = is_affirmative?(state) self[:power_target] = target logger.debug { "Target = #{target} and self[:power] = #{self[:power]}" } if target == On && self[:power] != On - send_cmd("power = 1", name: :power, delay: 2000, timeout: 10000) + send_cmd("power = 1", name: :power) elsif target == Off && self[:power] != Off - send_cmd("power = 0", name: :power, timeout: 10000) + send_cmd("power = 0", name: :power) end end @@ -105,9 +93,8 @@ def freeze? end def send_cmd(cmd, options = {}) - req = "*#{cmd}" + req = "*#{cmd}\r" logger.debug { "Sending: #{req}" } - req << 0x0D send(req, options) end @@ -116,8 +103,9 @@ def received(data, deferrable, command) return :success if command.nil? || command[:name].nil? - # \A is the beginning of the line - if(data =~ /\ANAK|\Anack/) # syntax error or other + data = data.split + + if(data[1] == "NAK" || data[1] == "nack") # syntax error or other return :failed end @@ -125,16 +113,19 @@ def received(data, deferrable, command) when :power self[:power] = data[-1] == "1" when :input - self[:input] = INPUT[data[-1].to_i] + self[:input] = INPUTS[data[-1].to_i] when :laser_inq - # return whatever number is at the end of the string - self[:laser] = data[/\d+\z/].to_i + logger.debug { "Laser inquiry response" } + self[:laser] = data[-1].to_i + when :laser_reset + self[:laser] = 0 when :error + error = data[3..-1].join(" ") + logger.debug { "Last error is :" } + logger.debug { error } when :freeze self[:freeze] = data[-1] == "1" end return :success end - - end diff --git a/modules/digital_projection/evision_7500_spec.rb b/modules/digital_projection/evision_7500_spec.rb index 056ea8ea..bc96c2bd 100644 --- a/modules/digital_projection/evision_7500_spec.rb +++ b/modules/digital_projection/evision_7500_spec.rb @@ -1,31 +1,50 @@ -Orchestrator::Testing.mock_device 'Wolfvision::Eye14' do +# encoding: ASCII-8BIT +# frozen_string_literal: true + + +Orchestrator::Testing.mock_device 'DigitalProjection::Evision_7500' do exec(:power?) .should_send("*power ?\r") # power query - .responds("ack power = 0") # respond with on + .responds("ack power = 0\r") # respond with off .expect(status[:power]).to be(false) exec(:power, true) - .should_send("*power = 0\r") # power query - .responds("ack power = 0") # respond with on + .should_send("*power = 1\r") # power query + .responds("ack power = 1\r") # respond with on .expect(status[:power]).to be(true) exec(:input?) .should_send("*input ?\r") - .responds("ack input = 0") # respond with on + .responds("ack input = 0\r") # respond with on .expect(status[:input]).to be(:display_port) exec(:switch_to, "hdmi") .should_send("*input = 1\r") - .responds("ack input = 1") # respond with on + .responds("ack input = 1\r") # respond with on .expect(status[:input]).to be(:hdmi) exec(:freeze?) .should_send("*freeze ?\r") # power query - .responds("ack freeze = 0") # respond with on + .responds("ack freeze = 0\r") # respond with on .expect(status[:freeze]).to be(false) exec(:freeze, true) .should_send("*freeze = 1\r") # power query - .responds("ack power = 1") # respond with on + .responds("ack power = 1\r") # respond with on .expect(status[:freeze]).to be(true) + + exec(:laser?) + .should_send("*laser.hours ?\r") + .responds("ack laser.hours = 1000\r") + .expect(status[:laser]).to be(1000) + + exec(:laser_reset) + .should_send("*laser.reset\r") + .responds("ack laser.reset\r") # this is suppose to respond with a check mark + .expect(status[:laser]).to be(0) + + exec(:error?) + .should_send("*errcode\r") + .responds("ack errorcode = this is a sample error code\r") + .expect(status[:laser]).to be(0) end diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index 5157032e..4af006d9 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -129,7 +129,12 @@ def received(data, deferrable, command) return :success if command.nil? || (command[:name].nil? && command[:group_type].nil?) data = data.split - cmd = data[-2].to_sym + + if command[:name] != :error + cmd = data[-2].to_sym + else + cmd = :LAST_ERROR_EVENT + end case cmd when :PRESET diff --git a/modules/shure/mixer/p300_spec.rb b/modules/shure/mixer/p300_spec.rb index e7ea288e..94e980a3 100644 --- a/modules/shure/mixer/p300_spec.rb +++ b/modules/shure/mixer/p300_spec.rb @@ -46,4 +46,9 @@ .responds("< REP 02 AUDIO_MUTE ON >") .should_send("< SET 03 AUDIO_MUTE ON >") .responds("< REP 03 AUDIO_MUTE ON >") + + exec(:error?) + .should_send("< GET LAST_ERROR_EVENT >") + .responds("< REP LAST_ERROR_EVENT this is a sample error >") + .expect(status[:channel10_mute]).to be(false) end From b112f2888742b5051f615450e0aaff6e96966e7a Mon Sep 17 00:00:00 2001 From: pkheav Date: Tue, 18 Sep 2018 11:07:55 +1000 Subject: [PATCH 0815/1752] further updates to evision/p300 spec --- modules/digital_projection/evision_7500.rb | 3 +-- modules/digital_projection/evision_7500_spec.rb | 2 +- modules/shure/mixer/p300.rb | 3 +-- modules/shure/mixer/p300_spec.rb | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/digital_projection/evision_7500.rb b/modules/digital_projection/evision_7500.rb index 6de1235f..fa93eb5a 100644 --- a/modules/digital_projection/evision_7500.rb +++ b/modules/digital_projection/evision_7500.rb @@ -121,8 +121,7 @@ def received(data, deferrable, command) self[:laser] = 0 when :error error = data[3..-1].join(" ") - logger.debug { "Last error is :" } - logger.debug { error } + self[:error] = error when :freeze self[:freeze] = data[-1] == "1" end diff --git a/modules/digital_projection/evision_7500_spec.rb b/modules/digital_projection/evision_7500_spec.rb index bc96c2bd..a0406dbc 100644 --- a/modules/digital_projection/evision_7500_spec.rb +++ b/modules/digital_projection/evision_7500_spec.rb @@ -46,5 +46,5 @@ exec(:error?) .should_send("*errcode\r") .responds("ack errorcode = this is a sample error code\r") - .expect(status[:laser]).to be(0) + .expect(status[:error]).to eq("this is a sample error code") end diff --git a/modules/shure/mixer/p300.rb b/modules/shure/mixer/p300.rb index 4af006d9..57645cd8 100644 --- a/modules/shure/mixer/p300.rb +++ b/modules/shure/mixer/p300.rb @@ -147,8 +147,7 @@ def received(data, deferrable, command) self["channel#{data[0].to_i}_gain"] = data[-1].to_i when :LAST_ERROR_EVENT error = data[1..-1].join(" ") - logger.debug { "Last error is :" } - logger.debug { error } + self[:error] = error end return :success end diff --git a/modules/shure/mixer/p300_spec.rb b/modules/shure/mixer/p300_spec.rb index 94e980a3..4b862256 100644 --- a/modules/shure/mixer/p300_spec.rb +++ b/modules/shure/mixer/p300_spec.rb @@ -50,5 +50,5 @@ exec(:error?) .should_send("< GET LAST_ERROR_EVENT >") .responds("< REP LAST_ERROR_EVENT this is a sample error >") - .expect(status[:channel10_mute]).to be(false) + .expect(status[:error]).to eq("this is a sample error") end From 108cdbf59f47f9ad739af6065a1f5777c089f1d2 Mon Sep 17 00:00:00 2001 From: pkheav Date: Tue, 18 Sep 2018 11:11:58 +1000 Subject: [PATCH 0816/1752] removed frozen string literal for evision --- modules/digital_projection/evision_7500.rb | 4 ---- modules/digital_projection/evision_7500_spec.rb | 4 ---- 2 files changed, 8 deletions(-) diff --git a/modules/digital_projection/evision_7500.rb b/modules/digital_projection/evision_7500.rb index fa93eb5a..9b094222 100644 --- a/modules/digital_projection/evision_7500.rb +++ b/modules/digital_projection/evision_7500.rb @@ -1,7 +1,3 @@ -# encoding: ASCII-8BIT -# frozen_string_literal: true - - module DigitalProjection; end # Documentation: http://www.digitalprojection.co.uk/dpdownloads/Protocol/Simplified-Protocol-Guide-Rev-H.pdf diff --git a/modules/digital_projection/evision_7500_spec.rb b/modules/digital_projection/evision_7500_spec.rb index a0406dbc..c86e168b 100644 --- a/modules/digital_projection/evision_7500_spec.rb +++ b/modules/digital_projection/evision_7500_spec.rb @@ -1,7 +1,3 @@ -# encoding: ASCII-8BIT -# frozen_string_literal: true - - Orchestrator::Testing.mock_device 'DigitalProjection::Evision_7500' do exec(:power?) .should_send("*power ?\r") # power query From e4572e0c44b2cea6c82c1ac2407228f9a3960aa7 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 20 Sep 2018 17:30:06 +1000 Subject: [PATCH 0817/1752] (cisco:ce) fix incorrect param validation --- modules/cisco/collaboration_endpoint/room_kit.rb | 4 ++-- modules/cisco/collaboration_endpoint/sx20.rb | 4 ++-- modules/cisco/collaboration_endpoint/sx80.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 917b3c31..cbd10feb 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -88,8 +88,8 @@ def mic_mute(state = On) CameraId: (1..1), PresetId_: (1..35), # Optional - codec will auto-assign if omitted Name_: String, - TakeSnapshot_: Boolean, - DefaultPosition_: Boolean + TakeSnapshot_: [true, false], + DefaultPosition_: [true, false] command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..1), diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 0bce71e0..8a8eeef2 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -83,8 +83,8 @@ def mic_mute(state = On) CameraId: (1..1), PresetId_: (1..35), # Optional - codec will auto-assign if omitted Name_: String, - TakeSnapshot_: Boolean, - DefaultPosition_: Boolean + TakeSnapshot_: [true, false], + DefaultPosition_: [true, false] command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..2), diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index a16acad1..876d06c5 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -88,8 +88,8 @@ def mic_mute(state = On) CameraId: (1..7), PresetId_: (1..35), # Optional - codec will auto-assign if omitted Name_: String, - TakeSnapshot_: Boolean, - DefaultPosition_: Boolean + TakeSnapshot_: [true, false], + DefaultPosition_: [true, false] command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..7), From da1c221ac07d9d6be3d8dea88e52a401de6e74c2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 24 Sep 2018 18:18:57 +1000 Subject: [PATCH 0818/1752] (aca:router) add method for querying if a source can be routed to a sink --- modules/aca/router.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index 844d6e77..b3b4593f 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -168,6 +168,11 @@ def upstream_devices_of(sink, on_input: nil) device_chain end + # Check if a source can be routed to a specific sink. + def path_exists_between?(source, sink) + paths[sink].distance_to[source].finite? + end + # ------------------------------ # Internals From c7e81e4eeede315931eb877bff88a29d54fc6960 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 24 Sep 2018 18:27:20 +1000 Subject: [PATCH 0819/1752] (cisco:ce) switch speaker track control to on/off rather than enable/disable --- modules/cisco/collaboration_endpoint/sx80.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 876d06c5..e4e94f88 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -133,11 +133,14 @@ def mic_mute(state = On) command! 'Cameras SpeakerTrack Diagnostics Stop' => \ :speaker_track_diagnostics_stop - # The 'integrator' account can't active/deactive SpeakerTrack, but we can - # cut off access via a configuration setting. + command 'Cameras SpeakerTrack Activate' => :speaker_track_activate + command 'Cameras SpeakerTrack Deactivate' => :speaker_track_deactivate def speaker_track(state = On) - mode = is_affirmative?(state) ? :Auto : :Off - send_xconfiguration 'Cameras SpeakerTrack', :Mode, mode + if is_affirmative? state + speaker_track_activate + else + speaker_track_deactivate + end end command 'Conference DoNotDisturb Activate' => :do_not_disturb_activate, From 7c4f72f4d47423dac477e2df0bb9afdc740e9e64 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 25 Sep 2018 12:49:29 +1000 Subject: [PATCH 0820/1752] Update locker library functionality --- lib/loqit/lockers.rb | 77 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 21c79dc2..99ee2ed7 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -7,9 +7,23 @@ module Loqit; end # lockers = Loqit::Lockers.new( # username: 'xmltester', # password: 'xmlPassword', -# wsdl: 'http://loqit.acgin.com.au/soap/server_wsdl.php?wsdl', +# wsdl: 'http://10.224.8.2/soap/server_wsdl.php?wsdl', # serial: 'BE434080-7277-11E3-BC4D-94C69111930A' -# ) +# ) +# all_lockers = lockers.list_lockers_detailed + +# random_locker = all_lockers.sample +# locker_number = random_locker['number'] +# locker_number = '31061' +# locker_number = '31025' +# locker_number = '30914' +# random_locker_status = lockers.show_locker_status(locker_number) +# random_locker_available = random_locker_status['LockerState'] + + + +# open_status = lockers.open_locker(locker_number) + # random_locker = lockers.list_lockers.sample['number'] # random_status = lockers.show_status(random_locker) # random_open = lockers.open_locker(random_locker) @@ -41,13 +55,70 @@ def initialize( } } end + def list_lockers response = @client.call(:list_lockers, message: { unitSerial: @serial }, soap_header: @header - ) + ).body[:list_lockers_response][:return] + JSON.parse(response) + end + + def list_lockers_detailed + all_lockers = self.list_lockers + all_lockers_detailed = [] + all_lockers.each_with_index do |locker, ind| + puts "Working on #{ind} of #{all_lockers.length}" + all_lockers_detailed.push(self.show_locker_status(locker['number'])) + end + all_lockers_detailed + end + + def show_locker_status(locker_number) + response = @client.call(:show_locker_status, + message: { + lockerNumber: locker_number, + unitSerial: @serial + }, + soap_header: @header + ).body[:show_locker_status_response][:return] + JSON.parse(response) + end + + def open_locker(locker_number) + response = @client.call(:open_locker, + message: { + lockerNumber: locker_number + }, + soap_header: @header + ).body[:locker_number_response][:return] + JSON.parse(response) + end + + def store_credentials(locker_number, user_pin_code, user_card, test_if_free=false) + response = @client.call(:store_credentials, + message: { + lockerNumber: locker_number, + userPincode: user_pin_code, + userCard: user_card, + testIfFree: test_if_free + }, + soap_header: @header + ).body[:store_credentials][:return] + JSON.parse(response) + end + + def customer_has_locker(user_card) + response = @client.call(:customer_has_locker, + message: { + lockerNumber: locker_number, + unitSerial: @serial + }, + soap_header: @header + ).body[:customer_has_locker_response][:return] + JSON.parse(response) end end From 1864c30b0a82bc2bb0caf364e28066f104fd5207 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 25 Sep 2018 22:49:47 +1000 Subject: [PATCH 0821/1752] (cisco:ce) return errors when send_xconfigurations fails --- modules/cisco/collaboration_endpoint/room_os.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 6eaac2f0..adf4e7ec 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -234,14 +234,7 @@ def send_xconfigurations(config) send_xconfiguration path.join(' '), setting, value end - thread.finally(interactions).then do |results| - resolved = results.map(&:last) - if resolved.all? - :success - else - thread.defer.reject 'Failed to apply all settings.' - end - end + thread.all(*interactions).then { :success } end # Query the device's current status. From a604b13ab8c196c232395f58087c80387fe2d552 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 25 Sep 2018 22:53:35 +1000 Subject: [PATCH 0822/1752] (cisco:ce) fix issue where send_xstatus could reject after first comms attempt --- modules/cisco/collaboration_endpoint/room_os.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index adf4e7ec..9f95a054 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -245,25 +245,22 @@ def send_xconfigurations(config) def send_xstatus(path) request = Action.xstatus path - defer = thread.defer - do_send request do |response| path_components = Action.tokenize path status_response = response.dig 'Status', *path_components if !status_response.nil? - yield status_response if block_given? - defer.resolve status_response - :success + if block_given? + yield status_response + else + status_response + end else error = response.dig 'CommandResponse', 'Status' logger.error "#{error['Reason']} (#{error['XPath']})" - defer.reject :abort end end - - defer.promise end From 656cd98a18508ccebc048cd4ef0e9e2b11df1114 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 25 Sep 2018 22:54:01 +1000 Subject: [PATCH 0823/1752] (cisco:ce) return command result rather than :success --- modules/cisco/collaboration_endpoint/room_os.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 9f95a054..4d52bf0d 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -183,7 +183,7 @@ def send_xcommand(command, args = {}) if result if result['status'] == 'OK' - :success + result else logger.error result['Reason'] :abort @@ -332,7 +332,7 @@ def do_send(command, **options) if block_given? yield response else - :success + response end else :ignore From 181b11ef6d9d52fb04d5585e4f910edb2052013e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 25 Sep 2018 23:03:22 +1000 Subject: [PATCH 0824/1752] (cisco:ce) add support for querying bookings --- modules/cisco/collaboration_endpoint/room_kit.rb | 6 ++++++ modules/cisco/collaboration_endpoint/sx20.rb | 6 ++++++ modules/cisco/collaboration_endpoint/sx80.rb | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index cbd10feb..7968a077 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -73,6 +73,12 @@ def mic_mute(state = On) command 'Audio Volume Set' => :volume, Level: (0..100) + command 'Bookings List' => :bookings, + Days_: (1..365), + DayOffset_: (0..365), + Limit_: Integer, + Offset_: Integer + command 'Call Accept' => :call_accept, CallId_: Integer command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 8a8eeef2..094368ff 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -68,6 +68,12 @@ def mic_mute(state = On) Loop_: [:Off, :On] command 'Audio Sound Stop' => :stop_sound + command 'Bookings List' => :bookings, + Days_: (1..365), + DayOffset_: (0..365), + Limit_: Integer, + Offset_: Integer + command 'Call Accept' => :call_accept, CallId_: Integer command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index e4e94f88..870fd1a0 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -73,6 +73,12 @@ def mic_mute(state = On) command 'Audio Volume Set' => :volume, Level: (0..100) + command 'Bookings List' => :bookings, + Days_: (1..365), + DayOffset_: (0..365), + Limit_: Integer, + Offset_: Integer + command 'Call Accept' => :call_accept, CallId_: Integer command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer From 74606ee494d395ab6641911ac5386b53a8a5b939 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 25 Sep 2018 23:59:27 +1000 Subject: [PATCH 0825/1752] (cisco:ce) fix issue where xstatus queries resulting in 'false' would log errors --- .../cisco/collaboration_endpoint/room_os.rb | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 4d52bf0d..b30b8482 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -327,22 +327,23 @@ def do_send(command, **options) request = "#{command} | resultId=\"#{request_id}\"\n" - handle_response = lambda do |response| - if response['ResultId'] == request_id - if block_given? - yield response - else - response - end - else - :ignore - end - end - logger.debug { "-> #{request}" } send request, **options do |response, defer, cmd| - received response, defer, cmd, &handle_response + received response, defer, cmd do |json| + if json['ResultId'] != request_id + :ignore + elsif block_given? + # Dowstream parsing may return a value that conflicts with + # special response values (e.g. false from an xstatus + # query). Use the async resolution path to bypass this and + # enable these results to be bubbled back to the caller. + defer.resolve yield(json) + :async + else + json + end + end end end From c65f3a3154584e58192abe9070b6e2eb4e78eefa Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 26 Sep 2018 00:27:33 +1000 Subject: [PATCH 0826/1752] (cisco:ce) provide xstatus results in response rather than requiring block --- .../cisco/collaboration_endpoint/room_os.rb | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index b30b8482..07a0bcab 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -245,22 +245,25 @@ def send_xconfigurations(config) def send_xstatus(path) request = Action.xstatus path - do_send request do |response| + defer = thread.defer + + interaction = do_send request do |response| path_components = Action.tokenize path status_response = response.dig 'Status', *path_components if !status_response.nil? - if block_given? - yield status_response - else - status_response - end + defer.resolve status_response + :success else error = response.dig 'CommandResponse', 'Status' logger.error "#{error['Reason']} (#{error['XPath']})" :abort end end + + interaction.catch { |e| defer.reject e } + + defer.promise end @@ -334,14 +337,9 @@ def do_send(command, **options) if json['ResultId'] != request_id :ignore elsif block_given? - # Dowstream parsing may return a value that conflicts with - # special response values (e.g. false from an xstatus - # query). Use the async resolution path to bypass this and - # enable these results to be bubbled back to the caller. - defer.resolve yield(json) - :async + yield json else - json + :success end end end @@ -378,9 +376,7 @@ def bind_feedback(path, status_key) # Bind device status to a module status variable. def bind_status(path, status_key) bind_feedback "/Status/#{path.tr ' ', '/'}", status_key - send_xstatus path do |value| - self[status_key] = value - end + self[status_key] = send_xstatus(path).value end def push_config From 12f14eb100fc64095711d0353fa2e031da6e0b74 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 26 Sep 2018 00:41:04 +1000 Subject: [PATCH 0827/1752] (cisco:ce) fix issue where status binding would result in comms blocking --- modules/cisco/collaboration_endpoint/room_os.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 07a0bcab..ea421326 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -376,7 +376,9 @@ def bind_feedback(path, status_key) # Bind device status to a module status variable. def bind_status(path, status_key) bind_feedback "/Status/#{path.tr ' ', '/'}", status_key - self[status_key] = send_xstatus(path).value + send_xstatus(path).then do |value| + self[status_key] = value + end end def push_config From 42376ec3fb1876559efa1c471793b07260d8f9b8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 26 Sep 2018 00:59:20 +1000 Subject: [PATCH 0828/1752] (cisco:ce) preserve case sensitivity in response data Key collissions are encountered if case is ignored. Specifically items like booking and calls contain both 'id' (element position) and 'Id' (UUID) subkeys. --- modules/cisco/collaboration_endpoint/room_os.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index ea421326..babe817b 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -73,7 +73,7 @@ def disconnected def received(data, deferrable, command) logger.debug { "<- #{data}" } - do_parse = proc { Response.parse data, into: CaseInsensitiveHash } + do_parse = proc { Response.parse data, into: HashWithIndifferentAccess } response = data.length > 2048 ? task(&do_parse).value : do_parse.call if block_given? From 731f63185e5a5ea0b2afc98dc4f9b857a0473547 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 26 Sep 2018 01:09:47 +1000 Subject: [PATCH 0829/1752] (cisco:ce) fix broken response compression due to case sensitivity --- modules/cisco/collaboration_endpoint/xapi/response.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/xapi/response.rb b/modules/cisco/collaboration_endpoint/xapi/response.rb index 7568209d..f8fd35dd 100644 --- a/modules/cisco/collaboration_endpoint/xapi/response.rb +++ b/modules/cisco/collaboration_endpoint/xapi/response.rb @@ -29,7 +29,7 @@ def parse(data, into: Hash) def compress(fragment) case fragment when Hash - value, valuespaceref = fragment.values_at(:value, :valuespaceref) + value, valuespaceref = fragment.values_at(:Value, :valueSpaceRef) if value&.is_a? String valuespace = valuespaceref&.split('/')&.last&.to_sym convert value, valuespace From eb1aa56f38317de752fa77ac8d562678dfe6f70c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 26 Sep 2018 01:16:19 +1000 Subject: [PATCH 0830/1752] (cisco:ce) adjust case to match API usage --- modules/cisco/collaboration_endpoint/room_kit.rb | 2 +- modules/cisco/collaboration_endpoint/sx20.rb | 2 +- modules/cisco/collaboration_endpoint/sx80.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 7968a077..ebd38c78 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -49,7 +49,7 @@ def connected status 'Conference DoNotDisturb' => :do_not_disturb status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals - status 'Video SelfView Mode' => :selfview + status 'Video Selfview Mode' => :selfview status 'Video Input' => :video_input status 'Video Output' => :video_output status 'Standby State' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 094368ff..14048794 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -47,7 +47,7 @@ def connected status 'Conference DoNotDisturb' => :do_not_disturb status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals - status 'Video SelfView Mode' => :selfview + status 'Video Selfview Mode' => :selfview status 'Video Input' => :video_input status 'Video Output' => :video_output status 'Standby State' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 870fd1a0..2b274860 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -49,7 +49,7 @@ def connected status 'Conference DoNotDisturb' => :do_not_disturb status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals - status 'Video SelfView Mode' => :selfview + status 'Video Selfview Mode' => :selfview status 'Video Input' => :video_input status 'Video Output' => :video_output status 'Standby State' => :standby From 4b16b128224a69f82e2f3fe12d119edc4eb90609 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 26 Sep 2018 13:12:03 +1000 Subject: [PATCH 0831/1752] Remove locker debugging --- lib/loqit/lockers.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 99ee2ed7..4e0bee9f 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -69,10 +69,11 @@ def list_lockers def list_lockers_detailed all_lockers = self.list_lockers all_lockers_detailed = [] + puts "STARTING LOCKER GET" all_lockers.each_with_index do |locker, ind| - puts "Working on #{ind} of #{all_lockers.length}" all_lockers_detailed.push(self.show_locker_status(locker['number'])) end + puts "FINISHED LOCKER GET" all_lockers_detailed end From fb249257278234f5571fabde94c9426e5303debc Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 27 Sep 2018 23:27:41 +1000 Subject: [PATCH 0832/1752] (samsung:display) fix issue when screen would change input when running in split mode --- modules/samsung/displays/md_series.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 5f70fe00..6ad5238a 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -384,8 +384,12 @@ def received(response, resolve, command) self[:brightness] = value when :input self[:input] = INPUTS[value] - self[:input_stable] = self[:input] == self[:input_target] - switch_to self[:input_target] unless self[:input_stable] + # 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] unless self[:input_stable] + end when :speaker self[:speaker] = Speaker_Modes[value] when :hard_off From 4aeb719d5e2d093259969ee0b326190de19a29c3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 2 Oct 2018 11:40:40 +1000 Subject: [PATCH 0833/1752] Update lockers to use params on list method --- lib/loqit/lockers.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 4e0bee9f..7e30ca0a 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -66,12 +66,18 @@ def list_lockers JSON.parse(response) end - def list_lockers_detailed - all_lockers = self.list_lockers + def list_lockers_detailed(start_number=nil, end_number=nil) all_lockers_detailed = [] puts "STARTING LOCKER GET" - all_lockers.each_with_index do |locker, ind| - all_lockers_detailed.push(self.show_locker_status(locker['number'])) + if start_number + (start_number.to_i..end_number.to_i).each do |num| + all_lockers_detailed.push(self.show_locker_status(num.to_s)) + end + else + all_lockers = self.list_lockers + all_lockers.each_with_index do |locker, ind| + all_lockers_detailed.push(self.show_locker_status(locker['number'])) + end end puts "FINISHED LOCKER GET" all_lockers_detailed From 02d3549ce7d24737f4d134bc4c7c36cd1f7d8f71 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 2 Oct 2018 15:31:41 +1000 Subject: [PATCH 0834/1752] (cisco:ce) Add Phonebook search --- modules/cisco/collaboration_endpoint/sx80.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 2b274860..1af64a08 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -153,6 +153,10 @@ def speaker_track(state = On) Timeout_: (1..1440) command 'Conference DoNotDisturb Deactivate' => :do_not_disturb_deactivate + command 'Phonebook Search' => :phonebook_search, + SearchString: String, + PhonebookType: [:Corporate, :Local] + command 'Presentation Start' => :presentation_start, PresentationSource_: (1..4), SendingMode_: [:LocalRemote, :LocalOnly], From 0c9f1d6985e5583badc7596e34fc6be1630717c7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 2 Oct 2018 16:18:58 +1000 Subject: [PATCH 0835/1752] Use just email on slack usernames --- modules/aca/slack.rb | 4 ++-- modules/aca/slack_concierge.rb | 24 +++++++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/aca/slack.rb b/modules/aca/slack.rb index 8b14a748..4a889dab 100644 --- a/modules/aca/slack.rb +++ b/modules/aca/slack.rb @@ -54,10 +54,10 @@ def send_message(message_text) if thread_id # Post to the slack channel using the thread ID - message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, username: "#{current_user.name} (#{current_user.email})", thread_ts: thread_id + message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, username: current_user.email, thread_ts: thread_id else - message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, username: "#{current_user.name} (#{current_user.email})" + message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, username: current_user.email # logger.debug "Message from frontend:" # logger.debug message.to_json # Store thread id diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 40164932..21a7c80d 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -51,23 +51,21 @@ def get_threads !((!message.key?('thread_ts') || message['thread_ts'] == message['ts']) && message['subtype'] == 'bot_message') } logger.debug "Processing messages in get_threads" - messages.each_with_index{|message, i| - if message['username'].include?('(') - messages[i]['name'] = message['username'].split(' (')[0] if message.key?('username') - messages[i]['email'] = message['username'].split(' (')[1][0..-2] if message.key?('username') + messages.each_with_index{|message, i| + if message.key?('username') authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id user = User.find_by_email(authority_id, messages[i]['email']) - if !user.nil? - messages[i]['last_sent'] = user.last_message_sent - messages[i]['last_read'] = user.last_message_read - else - messages[i]['last_sent'] = nil - messages[i]['last_read'] = nil - end - # update_last_message_read(messages[i]['email']) + messages[i]['email'] = message['username'] + messages[i]['name'] = user.name + end + if !user.nil? + messages[i]['last_sent'] = user.last_message_sent + messages[i]['last_read'] = user.last_message_read else - messages[i]['name'] = message['username'] + messages[i]['last_sent'] = nil + messages[i]['last_read'] = nil end + # update_last_message_read(messages[i]['email']) messages[i]['replies'] = get_message(message['ts']) } logger.debug "Finished processing messages in get_threads" From 65c7103753954a71a4183a82799ef7954c20599e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 26 Sep 2018 19:51:40 +1000 Subject: [PATCH 0836/1752] (cisco:ce) map available/unavailable to booleans --- modules/cisco/collaboration_endpoint/xapi/response.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/xapi/response.rb b/modules/cisco/collaboration_endpoint/xapi/response.rb index f8fd35dd..2c453cd6 100644 --- a/modules/cisco/collaboration_endpoint/xapi/response.rb +++ b/modules/cisco/collaboration_endpoint/xapi/response.rb @@ -88,11 +88,14 @@ def convert(value, valuespace) def truthy?(value) (::Orchestrator::Constants::On_vars + [ - 'Standby' # ensure standby state is properly mapped + 'Standby', # ensure standby state is properly mapped + 'Available' ]).include? value end def falsey?(value) - ::Orchestrator::Constants::Off_vars.include? value + (::Orchestrator::Constants::Off_vars + [ + 'Unavailable' + ]).include? value end end From e2a4618bdf9ecb766106244a12d5910d1c8fb3ff Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 4 Oct 2018 11:44:09 +1000 Subject: [PATCH 0837/1752] (cisco:ce) remove old call state on reload --- modules/cisco/collaboration_endpoint/room_kit.rb | 4 ++-- modules/cisco/collaboration_endpoint/sx20.rb | 4 ++-- modules/cisco/collaboration_endpoint/sx80.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index ebd38c78..5f4856be 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -31,9 +31,9 @@ def connected self[:local_presentation] = false end + self[:calls] = {} register_feedback '/Status/Call' do |call| - current = self[:calls].is_a?(Hash) ? self[:calls] : {} - calls = current.deep_merge(call) + calls = self[:calls].deep_merge call calls.reject! do |_, props| props[:status] == :Idle || props.include?(:ghost) end diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 14048794..04935fec 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -31,9 +31,9 @@ def connected self[:local_presentation] = false end + self[:calls] = {} register_feedback '/Status/Call' do |call| - current = self[:calls].is_a?(Hash) ? self[:calls] : {} - calls = current.deep_merge(call) + calls = self[:calls].deep_merge call calls.reject! do |_, props| props[:status] == :Idle || props.include?(:ghost) end diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 1af64a08..b6d325f4 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -31,9 +31,9 @@ def connected self[:local_presentation] = false end + self[:calls] = {} register_feedback '/Status/Call' do |call| - current = self[:calls].is_a?(Hash) ? self[:calls] : {} - calls = current.deep_merge(call) + calls = self[:calls].deep_merge call calls.reject! do |_, props| props[:status] == :Idle || props.include?(:ghost) end From 7772d2beb4d234e96bb2753e4ec1096c35598633 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 4 Oct 2018 11:45:25 +1000 Subject: [PATCH 0838/1752] (cisco:ce) use symbolised hash for compatability with YAJL parser --- modules/cisco/collaboration_endpoint/room_os.rb | 4 ++-- modules/cisco/collaboration_endpoint/xapi/response.rb | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index babe817b..725fd78f 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -73,8 +73,8 @@ def disconnected def received(data, deferrable, command) logger.debug { "<- #{data}" } - do_parse = proc { Response.parse data, into: HashWithIndifferentAccess } - response = data.length > 2048 ? task(&do_parse).value : do_parse.call + do_parse = proc { Response.parse data } + response = data.length > 1024 ? task(&do_parse).value : do_parse.call if block_given? # Let any pending command response handlers have first pass... diff --git a/modules/cisco/collaboration_endpoint/xapi/response.rb b/modules/cisco/collaboration_endpoint/xapi/response.rb index 2c453cd6..72009f9b 100644 --- a/modules/cisco/collaboration_endpoint/xapi/response.rb +++ b/modules/cisco/collaboration_endpoint/xapi/response.rb @@ -14,11 +14,10 @@ class ParserError < StandardError; end # Parse a raw device response. # # @param data [String] the raw device response to parse - # @param into [Class] the object class to parser into (subclass of Hash) # @return a nested structure containing the fully parsed response # @raise [ParserError] if data is invalid - def parse(data, into: Hash) - response = JSON.parse data, object_class: into + def parse(data) + response = JSON.parse data, symbolize_names: true compress response rescue JSON::ParserError => error raise ParserError, error From 63d9c29fb93baef3e20ba4f08c31e75f343c9332 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 4 Oct 2018 12:32:15 +1000 Subject: [PATCH 0839/1752] (cisco:ce) switch all response lookups to use sym keys --- .../cisco/collaboration_endpoint/room_os.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 725fd78f..0832d90b 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -176,16 +176,16 @@ def send_xcommand(command, args = {}) # becomes: # InputSetMainVideoSourceResult result_key = command.split(' ').last(2).join('') + 'Result' - command_result = response.dig 'CommandResponse', result_key - failure_result = response.dig 'CommandResponse', 'Result' + command_result = response.dig :CommandResponse, result_key.to_sym + failure_result = response.dig :CommandResponse, :Result result = command_result || failure_result if result - if result['status'] == 'OK' + if result[:status] == 'OK' result else - logger.error result['Reason'] + logger.error result[:Reason] :abort end else @@ -205,10 +205,10 @@ def send_xconfiguration(path, setting, value) request = Action.xconfiguration path, setting, value do_send request, name: "#{path} #{setting}" do |response| - result = response.dig 'CommandResponse', 'Configuration' + result = response.dig :CommandResponse, :Configuration - if result&.[]('status') == 'Error' - logger.error "#{result['Reason']} (#{result['XPath']})" + if result&.[](:status) == 'Error' + logger.error "#{result[:Reason]} (#{result[:XPath]})" :abort else :success @@ -249,14 +249,14 @@ def send_xstatus(path) interaction = do_send request do |response| path_components = Action.tokenize path - status_response = response.dig 'Status', *path_components + status_response = response.dig :Status, *path_components.map(&:to_sym) if !status_response.nil? defer.resolve status_response :success else - error = response.dig 'CommandResponse', 'Status' - logger.error "#{error['Reason']} (#{error['XPath']})" + error = response.dig :CommandResponse, :Status + logger.error "#{error[:Reason]} (#{error[:XPath]})" :abort end end @@ -334,7 +334,7 @@ def do_send(command, **options) send request, **options do |response, defer, cmd| received response, defer, cmd do |json| - if json['ResultId'] != request_id + if json[:ResultId] != request_id :ignore elsif block_given? yield json From 5274b3bb29f82b9edcc2c687705dbccbbeda3664 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 4 Oct 2018 12:44:23 +1000 Subject: [PATCH 0840/1752] (cisco:ce) fix device error when clearing feedback subscriptions --- modules/cisco/collaboration_endpoint/room_os.rb | 7 ++++++- .../cisco/collaboration_endpoint/xapi/action.rb | 17 +++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 0832d90b..e87670d8 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -293,7 +293,12 @@ def unregister_feedback(path) device_subscriptions.remove path - request = Action.xfeedback :deregister, path + request = if path == '/' + Action.xfeedback :deregisterall + else + Action.xfeedback :deregister, path + end + do_send request end diff --git a/modules/cisco/collaboration_endpoint/xapi/action.rb b/modules/cisco/collaboration_endpoint/xapi/action.rb index f272e9f0..ed0c7481 100644 --- a/modules/cisco/collaboration_endpoint/xapi/action.rb +++ b/modules/cisco/collaboration_endpoint/xapi/action.rb @@ -18,7 +18,9 @@ module Cisco::CollaborationEndpoint::Xapi::Action FEEDBACK_ACTION ||= Set.new [ :register, - :deregister + :deregister, + :deregisterall, + :list ] module_function @@ -74,18 +76,21 @@ def xstatus(path) # Serialize a xFeedback subscription request. # - # @param action [:register, :deregister] + # @param action [:register, :deregister, :deregisterall, :list] # @param path [String, Array] the feedback document path # @return [String] - def xfeedback(action, path) + def xfeedback(action, path = nil) unless FEEDBACK_ACTION.include? action raise ArgumentError, "Invalid feedback action. Must be one of #{FEEDBACK_ACTION}." end - xpath = tokenize path if path.is_a? String - - create_action :xFeedback, action, "/#{xpath.join '/'}" + if path + xpath = tokenize path if path.is_a? String + create_action :xFeedback, action, "/#{xpath.join '/'}" + else + create_action :xFeedback, action + end end def tokenize(path) From f26b523dc12f076b3294d2f69102061593aab67d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 4 Oct 2018 12:52:29 +1000 Subject: [PATCH 0841/1752] (cisco:ce) fix issue with action types not updating on module reload --- modules/cisco/collaboration_endpoint/xapi/action.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/xapi/action.rb b/modules/cisco/collaboration_endpoint/xapi/action.rb index ed0c7481..5adc5de2 100644 --- a/modules/cisco/collaboration_endpoint/xapi/action.rb +++ b/modules/cisco/collaboration_endpoint/xapi/action.rb @@ -8,7 +8,7 @@ module Cisco::CollaborationEndpoint::Xapi; end # Pure utility methods for building Cisco xAPI actions. module Cisco::CollaborationEndpoint::Xapi::Action - ACTION_TYPE ||= Set.new [ + ACTION_TYPE = Set.new [ :xConfiguration, :xCommand, :xStatus, @@ -16,7 +16,7 @@ module Cisco::CollaborationEndpoint::Xapi::Action :xPreferences ] - FEEDBACK_ACTION ||= Set.new [ + FEEDBACK_ACTION = Set.new [ :register, :deregister, :deregisterall, From ff4b58ecadd84d4ceca7da812a57fa5602fc1129 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 4 Oct 2018 16:15:33 +1000 Subject: [PATCH 0842/1752] (cisco:ce) add phonebook search for all devices --- modules/cisco/collaboration_endpoint/room_kit.rb | 6 ++++++ modules/cisco/collaboration_endpoint/sx20.rb | 6 ++++++ modules/cisco/collaboration_endpoint/sx80.rb | 4 +++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 5f4856be..71b03484 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -141,6 +141,12 @@ def speaker_track(state = On) send_xconfiguration 'Cameras SpeakerTrack', :Mode, mode end + command 'Phonebook Search' => :phonebook_search, + SearchString: String, + PhonebookType_: [:Corporate, :Local], + Limit_: Integer, + Offset_: Integer + command 'Presentation Start' => :presentation_start, PresentationSource_: (1..2), SendingMode_: [:LocalRemote, :LocalOnly], diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 04935fec..0b309019 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -117,6 +117,12 @@ def mic_mute(state = On) :UpperCenter, :UpperLeft, :UpperRight], OnMonitorRole_: [:First, :Second, :Third, :Fourth] + command 'Phonebook Search' => :phonebook_search, + SearchString: String, + PhonebookType_: [:Corporate, :Local], + Limit_: Integer, + Offset_: Integer + command 'Presentation Start' => :presentation_start, PresentationSource_: (1..2), SendingMode_: [:LocalRemote, :LocalOnly], diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index b6d325f4..1bf7297a 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -155,7 +155,9 @@ def speaker_track(state = On) command 'Phonebook Search' => :phonebook_search, SearchString: String, - PhonebookType: [:Corporate, :Local] + PhonebookType_: [:Corporate, :Local], + Limit_: Integer, + Offset_: Integer command 'Presentation Start' => :presentation_start, PresentationSource_: (1..4), From 67d4b0d2dcf5cb8d45776f3e6c59a21904c08733 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 4 Oct 2018 16:17:08 +1000 Subject: [PATCH 0843/1752] (cisco:roomkit) control speaker track on/off rather than enable/disable --- modules/cisco/collaboration_endpoint/room_kit.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 71b03484..e240a459 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -134,11 +134,14 @@ def mic_mute(state = On) command! 'Cameras SpeakerTrack Diagnostics Stop' => \ :speaker_track_diagnostics_stop - # The 'integrator' account can't active/deactive SpeakerTrack, but we can - # cut off access via a configuration setting. + command 'Cameras SpeakerTrack Activate' => :speaker_track_activate + command 'Cameras SpeakerTrack Deactivate' => :speaker_track_deactivate def speaker_track(state = On) - mode = is_affirmative?(state) ? :Auto : :Off - send_xconfiguration 'Cameras SpeakerTrack', :Mode, mode + if is_affirmative? state + speaker_track_activate + else + speaker_track_deactivate + end end command 'Phonebook Search' => :phonebook_search, From 6a68eb6a2035dd45fd83bc9137bde0916c22a35c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 8 Oct 2018 12:35:43 +1100 Subject: [PATCH 0844/1752] Fix param naming --- lib/loqit/lockers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 7e30ca0a..427a25e6 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -66,7 +66,7 @@ def list_lockers JSON.parse(response) end - def list_lockers_detailed(start_number=nil, end_number=nil) + def list_lockers_detailed(start_number:nil, end_number:nil) all_lockers_detailed = [] puts "STARTING LOCKER GET" if start_number From 876876b78588d5950d04a8fbc242c296487b0338 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 9 Oct 2018 11:59:48 +1100 Subject: [PATCH 0845/1752] (cisco:sx80) Add DTMF tone sending --- modules/cisco/collaboration_endpoint/sx80.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 1bf7297a..6d91829d 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -82,6 +82,11 @@ def mic_mute(state = On) command 'Call Accept' => :call_accept, CallId_: Integer command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer + + command 'Call DTMFSend' => :dtmf_send, + CallId: (0..65534), + DTMFString: String + command 'Dial' => :dial, Number: String, Protocol_: [:H320, :H323, :Sip, :Spark], From 78f9af39a6484440312464d355918676c9b5fa57 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 9 Oct 2018 12:12:31 +1100 Subject: [PATCH 0846/1752] (panasonic:projector) add RS232 driver --- modules/panasonic/projector/rs232.rb | 245 +++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 modules/panasonic/projector/rs232.rb diff --git a/modules/panasonic/projector/rs232.rb b/modules/panasonic/projector/rs232.rb new file mode 100644 index 00000000..db1ad33e --- /dev/null +++ b/modules/panasonic/projector/rs232.rb @@ -0,0 +1,245 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'digest/md5' + +module Panasonic; end +module Panasonic::Projector; end + +# Documentation: +# * Protocol: https://aca.im/driver_docs/Panasonic/lcd_protocol2.pdf +# * Commands: https://aca.im/driver_docs/Panasonic/panasonic_commands.pdf + +class Panasonic::Projector::Rs232 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 1024 + descriptive_name 'Panasonic Projector RS-232' + generic_name :Display + + # Communication settings + tokenize delimiter: "\x03", indicator: "\x02" + + + def on_load + self[:power] = false + self[:power_stable] = true # Stable by default (allows manual on and off) + + # Meta data for inquiring interfaces + self[:type] = :lcd + + # The projector drops the connection when there is no activity + schedule.every('60s') { do_poll if self[:connected] } + on_update + end + + def on_update + #@username = setting(:username) || 'dispadmin' + #@password = setting(:password) || '@Panasonic' + end + + def connected + + end + + def disconnected + end + + COMMANDS = { + power_on: 'PON', + power_off: 'POF', + power_query: 'QPW', + input: 'IMS', + volume: 'AVL', + volume_query: 'QAV', + audio_mute: 'AMT', + audio_mute_query: 'QAM', + current_input: 'QIN' + } + COMMANDS.merge!(COMMANDS.invert) + + # + # Power commands + # + def power(state, opt = nil) + self[:power_stable] = false + if is_affirmative?(state) + self[:power_target] = On + do_send(:power_on, retries: 10, name: :power, delay: 10000, timeout: 15000) + logger.debug "requested to power on" + do_send(:power_query) + else + self[:power_target] = Off + do_send(:power_off, retries: 10, name: :power, delay: 8000) + logger.debug "requested to power off" + do_send(:power_query) + end + end + + def power?(**options, &block) + options[:emit] = block if block_given? + do_send(:power_query, options) + end + + # + # Input selection + # + INPUTS = { + hdmi1: 'HM1', + hdmi: 'HM1', + hdmi2: 'HM2', + vga: 'PC1', + dvi: 'DV1', + hdbaset: 'DL1' + } + INPUTS.merge!(INPUTS.invert) + + def switch_to(input) + input = input.to_sym + return unless INPUTS.has_key? input + + # Projector doesn't automatically unmute + unmute if self[:mute] + + logger.debug { "requested to switch to: #{input}" } + do_send(:input, INPUTS[input], retries: 10, delay_on_receive: 2000).then do + # Can't query current input + self[:input] = input + end + end + + def input? + do_send(:current_input) + end + + # + # Mute Audio + # + def mute_audio(val = true) + actual = val ? 1 : 0 + logger.debug "requested to mute #{val}" + do_send(:audio_mute, actual) # Audio + Video + do_poll + end + alias_method :mute, :mute_audio + + def unmute_audio + mute false + end + alias_method :unmute, :unmute_audio + + def muted? + do_send(:audio_mute_query) + end + + def volume(level) + # Unable to query current volume + do_send(:volume, level.to_s.rjust(3, '0')).then { self[:volume] = level.to_i } + end + + def volume? + do_send :volume_query + end + + def do_poll + power?(priority: 0).then do + if self[:power] + input? + #volume? + muted? + end + end + end + + ERRORS = { + ERR1: '1: Undefined control command', + ERR2: '2: Out of parameter range', + ERR3: '3: Busy state or no-acceptable period', + ERR4: '4: Timeout or no-acceptable period', + ERR5: '5: Wrong data length', + ERRA: 'A: Password mismatch', + ER401: '401: Command cannot be executed', + ER402: '402: Invalid parameter is sent' + } + + def received(data, resolve, command) # Data is default received as a string + logger.debug { "sent \"#{data}\" for #{command ? command[:data] : 'unknown'}" } + + # Error Response (00ER401) + if data.start_with?('ER') + error = data.to_sym + self[:last_error] = ERRORS[error] + + # Check for busy or timeout + if error == :ERR3 || error == :ERR4 + logger.warn "Display busy: #{self[:last_error]}" + return :retry + else + logger.error "Display error: #{self[:last_error]}" + return :abort + end + end + + cmd = COMMANDS[data] + case cmd + when :power_on + self[:power] = true + ensure_power_state + when :power_off + self[:power] = false + ensure_power_state + else + res = data + case command[:name] + when :power_query + self[:power] = res.to_i == 1 + ensure_power_state + when :audio_mute_query + self[:audio_mute] = res.to_i == 1 + when :volume_query + self[:volume] = res.to_i + when :current_input + self[:input] = INPUTS[res] + end + end + + :success + end + + + protected + + + def ensure_power_state + if !self[:power_stable] && self[:power] != self[:power_target] + power(self[:power_target]) + else + self[:power_stable] = true + end + end + + def do_send(command, param = nil, **options) + if param.is_a? Hash + options = param + param = nil + end + + # Default to the command name if name isn't set + options[:name] = command unless options[:name] + # options[:disconnect] = true + + if param.nil? + cmd = COMMANDS[command] + else + cmd = "#{COMMANDS[command]}:#{param}" + end + + full_cmd = hex_to_byte('02') << cmd << hex_to_byte('030D') + + # Will only accept a single request at a time. + send(full_cmd, options) + end + +end From b613c6cbe324d3cf64be303f98cb795d2e105d20 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 9 Oct 2018 12:13:12 +1100 Subject: [PATCH 0847/1752] (pansonic:lcd) add RS232 driver --- modules/panasonic/lcd/rs232.rb | 257 +++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 modules/panasonic/lcd/rs232.rb diff --git a/modules/panasonic/lcd/rs232.rb b/modules/panasonic/lcd/rs232.rb new file mode 100644 index 00000000..a425f60e --- /dev/null +++ b/modules/panasonic/lcd/rs232.rb @@ -0,0 +1,257 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'digest/md5' + +module Panasonic; end +module Panasonic::LCD; end + +# Documentation: +# * Protocol: https://aca.im/driver_docs/Panasonic/lcd_protocol2.pdf +# * Commands: https://aca.im/driver_docs/Panasonic/panasonic_commands.pdf + +class Panasonic::LCD::Rs232 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 1024 + descriptive_name 'Panasonic LCD RS-232' + generic_name :Display + + # Communication settings + tokenize delimiter: "\x03" + + + def on_load + self[:power] = false + self[:power_stable] = true # Stable by default (allows manual on and off) + + # Meta data for inquiring interfaces + self[:type] = :lcd + + # The projector drops the connection when there is no activity + schedule.every('60s') { do_poll if self[:connected] } + on_update + end + + def on_update + #@username = setting(:username) || 'dispadmin' + #@password = setting(:password) || '@Panasonic' + end + + def connected + + end + + def disconnected + end + + COMMANDS = { + power_on: 'PON', + power_off: 'POF', + power_query: 'QPW', + input: 'IMS', + volume: 'AVL', + volume_query: 'QAV', + audio_mute: 'AMT', + audio_mute_query: 'QAM', + current_input: 'QMI' + } + COMMANDS.merge!(COMMANDS.invert) + + # + # Power commands + # + def power(state, opt = nil) + self[:power_stable] = false + if is_affirmative?(state) + self[:power_target] = On + do_send(:power_on, retries: 10, name: :power, delay: 10000, timeout: 15000) + logger.debug "requested to power on" + do_send(:power_query) + else + self[:power_target] = Off + do_send(:power_off, retries: 10, name: :power, delay: 8000) + logger.debug "requested to power off" + do_send(:power_query) + end + end + + def power?(**options, &block) + options[:emit] = block if block_given? + do_send(:power_query, options) + end + + # + # Input selection + # + INPUTS = { + hdmi1: 'HM1', + hdmi: 'HM1', + hdmi2: 'HM2', + vga: 'PC1', + dvi: 'DV1', + hdbaset: 'DL1' + } + INPUTS.merge!(INPUTS.invert) + + def switch_to(input) + input = input.to_sym + return unless INPUTS.has_key? input + + # Projector doesn't automatically unmute + unmute if self[:mute] + + logger.debug { "requested to switch to: #{input}" } + do_send(:input, INPUTS[input], retries: 10, delay_on_receive: 2000).then do + # Can't query current input + self[:input] = input + end + end + + def input? + do_send(:current_input) + end + + # + # Mute Audio + # + def mute_audio(val = true) + actual = val ? 1 : 0 + logger.debug "requested to mute #{val}" + do_send(:audio_mute, actual) # Audio + Video + do_poll + end + alias_method :mute, :mute_audio + + def unmute_audio + mute false + end + alias_method :unmute, :unmute_audio + + def muted? + do_send(:audio_mute_query) + end + + def volume(level) + # Unable to query current volume + do_send(:volume, level.to_s.rjust(3, '0')).then { self[:volume] = level.to_i } + end + + def volume? + do_send :volume_query + end + + def do_poll + power?(priority: 0).then do + if self[:power] + input? + volume? + muted? + end + end + end + + ERRORS = { + ERR1: '1: Undefined control command', + ERR2: '2: Out of parameter range', + ERR3: '3: Busy state or no-acceptable period', + ERR4: '4: Timeout or no-acceptable period', + ERR5: '5: Wrong data length', + ERRA: 'A: Password mismatch', + ER401: '401: Command cannot be executed', + ER402: '402: Invalid parameter is sent' + } + + def received(data, resolve, command) # Data is default received as a string + logger.debug { "sent #{data} for #{command ? command[:data] : 'unknown'}" } + + # This is the ready response + if data[0] == ' ' + # Ignore this as it is not a response, we can now make a request + return :ignore + end + + # remove the leading 00 + data = data[2..-1] + + # Error Response (00ER401) + if data.start_with?('ER') + error = data.to_sym + self[:last_error] = ERRORS[error] + + # Check for busy or timeout + if error == :ERR3 || error == :ERR4 + logger.warn "Display busy: #{self[:last_error]}" + return :retry + else + logger.error "Display error: #{self[:last_error]}" + return :abort + end + end + + cmd = COMMANDS[data] + case cmd + when :power_on + self[:power] = true + ensure_power_state + when :power_off + self[:power] = false + ensure_power_state + when '001' + self[:power] = true + ensure_power_state + else + res = data.split(':')[1] + case command[:name] + when :power_query + self[:power] = res.to_i == 1 + ensure_power_state + when :audio_mute_query + self[:audio_mute] = res.to_i == 1 + when :volume_query + self[:volume] = res.to_i + when :current_input + self[:input] = INPUTS[res] + end + end + + :success + end + + + protected + + + def ensure_power_state + if !self[:power_stable] && self[:power] != self[:power_target] + power(self[:power_target]) + else + self[:power_stable] = true + end + end + + def do_send(command, param = nil, **options) + if param.is_a? Hash + options = param + param = nil + end + + # Default to the command name if name isn't set + options[:name] = command unless options[:name] + # options[:disconnect] = true + + if param.nil? + cmd = COMMANDS[command] + else + cmd = "#{COMMANDS[command]}:#{param}" + end + + full_cmd = hex_to_byte('02') << cmd << hex_to_byte('030D') + + # Will only accept a single request at a time. + send(full_cmd, options) + end + +end From 79217de5f202b9556cc5f683de750f53a14b9375 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 10 Oct 2018 13:31:41 +1100 Subject: [PATCH 0848/1752] Add logging to bulk method --- lib/microsoft/office.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 2535c68a..4aaec780 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -135,10 +135,12 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers graph_api_options = {inactivity_timeout: 25000, keepalive: false} + if @internet_proxy proxy = URI.parse(@internet_proxy) graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } end + log_graph_request(request_method, data, query, headers, graph_path, password, endpoints) graph_api = UV::HttpEndpoint.new(@graph_domain, graph_api_options) response = graph_api.__send__('post', path: graph_path, headers: headers, body: bulk_data) @@ -152,7 +154,7 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers end - def log_graph_request(request_method, data, query, headers, graph_path, password) + def log_graph_request(request_method, data, query, headers, graph_path, password, endpoints=nil) STDERR.puts "--------------NEW GRAPH REQUEST------------" STDERR.puts "#{request_method} to #{graph_path}" STDERR.puts "Data:" @@ -161,6 +163,8 @@ def log_graph_request(request_method, data, query, headers, graph_path, password STDERR.puts query if query STDERR.puts "Headers:" STDERR.puts headers if headers + STDERR.puts "Endpoints:" + STDERR.puts endpoints if endpoints STDERR.puts "Password auth is: #{password}" STDERR.puts '--------------------------------------------' STDERR.flush From 8d1853492200033ea58b79d7b5f3a8c79465392f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 10 Oct 2018 13:33:50 +1100 Subject: [PATCH 0849/1752] Add logging to bulk method --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4aaec780..f814166d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -140,7 +140,7 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers proxy = URI.parse(@internet_proxy) graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } end - log_graph_request(request_method, data, query, headers, graph_path, password, endpoints) + log_graph_request(request_method, bulk_data, query, headers, graph_path, password, endpoints) graph_api = UV::HttpEndpoint.new(@graph_domain, graph_api_options) response = graph_api.__send__('post', path: graph_path, headers: headers, body: bulk_data) From 4e7914f524b513d0c1b6d29ed88cdfb571dc4fe4 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 10 Oct 2018 18:42:08 +1100 Subject: [PATCH 0850/1752] (panasonic:projector:rs232) update input keys. Ensure power before input switch --- modules/panasonic/projector/rs232.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/panasonic/projector/rs232.rb b/modules/panasonic/projector/rs232.rb index db1ad33e..1f37a00c 100644 --- a/modules/panasonic/projector/rs232.rb +++ b/modules/panasonic/projector/rs232.rb @@ -87,11 +87,11 @@ def power?(**options, &block) # Input selection # INPUTS = { - hdmi1: 'HM1', - hdmi: 'HM1', - hdmi2: 'HM2', - vga: 'PC1', - dvi: 'DV1', + hdmi1: 'HD1', + hdmi: 'HD1', + hdmi2: 'HD2', + vga: 'RG1', + dvi: 'RG2', hdbaset: 'DL1' } INPUTS.merge!(INPUTS.invert) @@ -102,6 +102,7 @@ def switch_to(input) # Projector doesn't automatically unmute unmute if self[:mute] + power(true) if (self[:power] == false) logger.debug { "requested to switch to: #{input}" } do_send(:input, INPUTS[input], retries: 10, delay_on_receive: 2000).then do From 5c5d85a4acd63241e540c34ac5bcf83a774f9a04 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 12 Oct 2018 13:06:53 +1100 Subject: [PATCH 0851/1752] Update lockers reutrn format --- lib/loqit/lockers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 427a25e6..6ac7ea4b 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -113,7 +113,7 @@ def store_credentials(locker_number, user_pin_code, user_card, test_if_free=fals testIfFree: test_if_free }, soap_header: @header - ).body[:store_credentials][:return] + ).body[:store_credentials_response][:return] JSON.parse(response) end From f3b12861153e11201b5d42dadc0c920d78a67b43 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 12 Oct 2018 15:00:00 +1100 Subject: [PATCH 0852/1752] Only add card to locker post if it exists --- lib/loqit/lockers.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 6ac7ea4b..52a3b1e2 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -105,13 +105,14 @@ def open_locker(locker_number) end def store_credentials(locker_number, user_pin_code, user_card, test_if_free=false) + payload = { + lockerNumber: locker_number, + userPincode: user_pin_code, + testIfFree: test_if_free + } + payload[:userCard] = user_card if user_card response = @client.call(:store_credentials, - message: { - lockerNumber: locker_number, - userPincode: user_pin_code, - userCard: user_card, - testIfFree: test_if_free - }, + message: payload, soap_header: @header ).body[:store_credentials_response][:return] JSON.parse(response) @@ -128,5 +129,4 @@ def customer_has_locker(user_card) JSON.parse(response) end -end - +end \ No newline at end of file From a1086225ea952e473e286e97fa6392bf1d6429e8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 15 Oct 2018 12:12:28 +1100 Subject: [PATCH 0853/1752] Fix lockers store method param order --- lib/loqit/lockers.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 52a3b1e2..5da42606 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -107,10 +107,10 @@ def open_locker(locker_number) def store_credentials(locker_number, user_pin_code, user_card, test_if_free=false) payload = { lockerNumber: locker_number, - userPincode: user_pin_code, - testIfFree: test_if_free + userPincode: user_pin_code } payload[:userCard] = user_card if user_card + payload[:testIfFree] = test_if_free response = @client.call(:store_credentials, message: payload, soap_header: @header From 3680e28427e0d46c1c100def448a4d5312ad7edf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 15 Oct 2018 12:14:55 +1100 Subject: [PATCH 0854/1752] Fix lockers store method param order --- lib/loqit/lockers.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 5da42606..75301697 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -40,7 +40,9 @@ def initialize( log_level: :debug ) savon_config = { - :wsdl => wsdl + :wsdl => wsdl, + :log => log, + :log_level => log_level } @client = Savon.client savon_config From 4a0841fa3559570f200e37fc30b5eac184314e1e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 17 Oct 2018 10:29:55 +1100 Subject: [PATCH 0855/1752] Add begining of skype library --- lib/microsoft/skype.rb | 102 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 lib/microsoft/skype.rb diff --git a/lib/microsoft/skype.rb b/lib/microsoft/skype.rb new file mode 100644 index 00000000..b4cce7fc --- /dev/null +++ b/lib/microsoft/skype.rb @@ -0,0 +1,102 @@ +require 'active_support/time' +require 'logger' +module Microsoft + class Error < StandardError + class ResourceNotFound < Error; end + class InvalidAuthenticationToken < Error; end + class BadRequest < Error; end + class ErrorInvalidIdMalformed < Error; end + class ErrorAccessDenied < Error; end + end +end + +class Microsoft::Skype + TIMEZONE_MAPPING = { + "Sydney": "AUS Eastern Standard Time" + } + def initialize( + domain:, + client_id:, + client_secret:, + username:, + password: + ) + @domain = domain + @username = username + @password = password + @client_id = client_id + @client_secret = client_secret + end + + # Probably the only public method that will be called + def create_meeting + user_url = dicover_user_url + end + + def get_token(url) + uri = URI(url) + resource = "#{uri.scheme}://#{uri.host}" + token_uri = URI("https://login.windows.net/#{@domain.split('.').first}.onmicrosoft.com/oauth2/token") + params = {:resource=>resource, :client_id=>@client_id, :grant_type=>"password", + :username=>@username, :password=>@password, :client_secret=>@client_secret} + puts "PARAMS ARE" + puts params + skype_auth_api = UV::HttpEndpoint.new(token_uri, {inactivity_timeout: 25000, keepalive: false}) + request = skype_auth_api.post({path: token_uri, body: params, headers: {"Content-Type":"application/x-www-form-urlencoded"}}) + auth_response = nil + reactor.run { + auth_response = request.value + } + JSON.parse(auth_response.body)["access_token"] + end + + def create_skype_meeting(subject) + my_online_meetings_url = @app["_embedded"]["onlineMeetings"]["_links"]["myOnlineMeetings"]["href"] + + body = {accessLevel: "Everyone", subject: subject} + + url = @base_url+my_online_meetings_url + r = RestClient.post url, body.to_json, @apps_headers + return JSON.parse(r.body) + end + + def discover_user_url + @skype_domain = "http://lyncdiscover.#{@domain}" + skype_discover_api = UV::HttpEndpoint.new(@skype_domain, {inactivity_timeout: 25000, keepalive: false}) + discover_request = skype_discover_api.get + discover_response = nil + reactor.run { + discover_response = discover_request.value + } + r = JSON.parse(discover_response.body) + r["_links"]["user"]["href"] + end + + def get_user_data + user_token = get_token(users_url) + + users_headers = { + "Accept" => "application/json", + "Content-Type" => "application/json", + "Authorization" => "Bearer #{user_token}" + } + + + # GET to users_url + skype_users_api = UV::HttpEndpoint.new(users_url, {inactivity_timeout: 25000, keepalive: false}) + user_request = skype_users_api.get({ + path: users_url, + headers: users_headers + }) + user_response = nil + reactor.run { + user_response = user_request.value + } + full_auth_response = JSON.parse(user_response.body) + + end + + def discover_apps_url(user_url) + + end +end From 4779a4407eaf7d6779f1606e2dd4a2d582f351b0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 17 Oct 2018 14:02:49 +1100 Subject: [PATCH 0856/1752] (aca:http pinger) fix memory leak --- modules/aca/http_ping.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/aca/http_ping.rb b/modules/aca/http_ping.rb index 2a62d82a..7d82fc39 100644 --- a/modules/aca/http_ping.rb +++ b/modules/aca/http_ping.rb @@ -24,9 +24,10 @@ def on_update def check_status get(@path, name: :check_status) { |data| + logger.debug { "request status was #{data.status.inspect}" } set_connected_state(data.status == @result) - }.catch do - set_connected_state(false) - end + :success + } + nil end end From 3f21fbd3c1830fd95fdcba985d9a84e79b1b3049 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Oct 2018 18:27:34 +1100 Subject: [PATCH 0857/1752] (panasonic:projector:rs232) fix input query --- modules/panasonic/projector/rs232.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/panasonic/projector/rs232.rb b/modules/panasonic/projector/rs232.rb index 1f37a00c..59d2593f 100644 --- a/modules/panasonic/projector/rs232.rb +++ b/modules/panasonic/projector/rs232.rb @@ -51,7 +51,7 @@ def disconnected power_on: 'PON', power_off: 'POF', power_query: 'QPW', - input: 'IMS', + input: 'IIS', volume: 'AVL', volume_query: 'QAV', audio_mute: 'AMT', From 6ac80ce97583e72bd406c103d31f2b8a623b78fc Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 6 Oct 2018 10:57:48 +1000 Subject: [PATCH 0858/1752] (cisco:ce) fix issue with non-symbolised keys --- modules/cisco/collaboration_endpoint/external_source.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/external_source.rb b/modules/cisco/collaboration_endpoint/external_source.rb index 1870708f..6b747a47 100644 --- a/modules/cisco/collaboration_endpoint/external_source.rb +++ b/modules/cisco/collaboration_endpoint/external_source.rb @@ -13,7 +13,7 @@ def connected super register_feedback \ '/Event/UserInterface/Presentation/ExternalSource' do |action| - source = action.dig 'Selected', 'SourceIdentifier' + source = action.dig :Selected, :SourceIdentifier unless source.nil? self[:external_source] = source signal_status(:external_source) From f2f4cf08378f7178409af13b614a8783bdc09590 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 22 Oct 2018 20:23:44 +1000 Subject: [PATCH 0859/1752] (cisco:ce) update spec to match recent module changes --- .../cisco/collaboration_endpoint/room_os_spec.rb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os_spec.rb b/modules/cisco/collaboration_endpoint/room_os_spec.rb index b6dd8b26..b7576e02 100644 --- a/modules/cisco/collaboration_endpoint/room_os_spec.rb +++ b/modules/cisco/collaboration_endpoint/room_os_spec.rb @@ -195,7 +195,7 @@ def section(message) } JSON ) - expect(status[:configuration].dig(:audio, :input, :microphone, 1, :mode)).to be true + expect(status[:configuration].dig(:Audio, :Input, :Microphone, 1, :Mode)).to be true # ------------------------------------------------------------------------- section 'Base comms (protected methods - ignore the access warnings)' @@ -342,7 +342,7 @@ def section(message) } JSON ) - expect(result).to be :success + expect(result).to eq status: 'OK' # Command with arguments exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, Layout: :PIP) @@ -359,7 +359,7 @@ def section(message) } JSON ) - expect(result).to be :success + expect(result).to eq status: 'OK' # Return device argument errors exec(:xcommand, 'Video Input SetMainVideoSource', ConnectorId: 1, SourceId: 1) @@ -448,7 +448,7 @@ def section(message) ) expect(result).to be :success - # Multuple settings with failure with return a promise that rejects + # Multiple settings with failure with return a command failure exec(:xconfiguration, 'Video Input Connector 1', InputSourceType: :Camera, Foo: 'Bar', Quality: :Motion) .should_send("xConfiguration Video Input Connector 1 InputSourceType: Camera | resultId=\"#{id_peek}\"\n") .responds( @@ -485,10 +485,7 @@ def section(message) } JSON ) - result.tap do |last_result| - expect(last_result.resolved?).to be true - expect { last_result.value }.to raise_error(CoroutineRejection) - end + expect { result }.to raise_error(Orchestrator::Error::CommandFailure) # ------------------------------------------------------------------------- @@ -564,5 +561,5 @@ def section(message) } JSON ) - expect(result['SystemTime']).to eq '2017-11-27T15:14:25+1000' + expect(result).to eq SystemTime: '2017-11-27T15:14:25+1000' end From 5315cd45b906730ef08950048a88719718c2860d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 22 Oct 2018 21:09:05 +1000 Subject: [PATCH 0860/1752] (cisco-ce) fix semantics around kwargs --- modules/cisco/collaboration_endpoint/room_os.rb | 16 ++++++++-------- .../cisco/collaboration_endpoint/xapi/action.rb | 14 ++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index e87670d8..775ebe22 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -107,10 +107,10 @@ def received(data, deferrable, command) # Execute an xCommand on the device. # # @param command [String] the command to execute - # @param args [Hash] the command arguments + # @param kwargs [Hash] the command arguments # @return [::Libuv::Q::Promise] resolves when the command completes - def xcommand(command, args = {}) - send_xcommand command, args + def xcommand(command, kwargs = {}) + send_xcommand command, kwargs end # Push a configuration settings to the device. @@ -155,17 +155,17 @@ def self.extended(child) # to protect access to #xcommand and still refer the gruntwork here. # # @param comand [String] the xAPI command to execute - # @param args [Hash] the command keyword args + # @param kwargs [Hash] the command keyword args # @return [::Libuv::Q::Promise] that will resolve when execution is complete - def send_xcommand(command, args = {}) - request = Action.xcommand command, args + def send_xcommand(command, kwargs = {}) + request = Action.xcommand command, kwargs # Multi-arg commands (external source registration, UI interaction etc) # all need to be properly queued and sent without be overriden. In # these cases, leave the outgoing commands unnamed. opts = {} - opts[:name] = command if args.empty? - opts[:name] = "#{command} #{args.keys.first}" if args.size == 1 + opts[:name] = command if kwargs.empty? + opts[:name] = "#{command} #{kwargs.keys.first}" if kwargs.size == 1 do_send request, **opts do |response| # The result keys are a little odd: they're a concatenation of the diff --git a/modules/cisco/collaboration_endpoint/xapi/action.rb b/modules/cisco/collaboration_endpoint/xapi/action.rb index 5adc5de2..1b79610d 100644 --- a/modules/cisco/collaboration_endpoint/xapi/action.rb +++ b/modules/cisco/collaboration_endpoint/xapi/action.rb @@ -28,17 +28,15 @@ module Cisco::CollaborationEndpoint::Xapi::Action # Serialize an xAPI action into transmittable command. # # @param type [ACTION_TYPE] the type of action to execute - # @param args [String, Array] the action args - # @param kwargs [Hash] an optional hash of keyword arguments for the action + # @param args [String|Hash, Array] the action args # @return [String] - def create_action(type, *args, **kwargs) + def create_action(type, *args) unless ACTION_TYPE.include? type raise ArgumentError, "Invalid action type. Must be one of #{ACTION_TYPE}." end - kwargs.merge! args.pop if args.last.is_a? Hash - + kwargs = args.last.is_a?(Hash) ? args.pop : {} kwargs = kwargs.compact.map do |name, value| value = "\"#{value}\"" if value.is_a? String "#{name}: #{value}" @@ -50,10 +48,10 @@ def create_action(type, *args, **kwargs) # Serialize an xCommand into transmittable command. # # @param path [String, Array] command path - # @param args [Hash] an optional hash of keyword arguments + # @param kwargs [Hash] an optional hash of keyword arguments # @return [String] - def xcommand(path, args) - create_action :xCommand, path, **args + def xcommand(path, kwargs = {}) + create_action :xCommand, path, kwargs end # Serialize an xConfiguration action into a transmittable command. From 2bbbeb9b5661b243ce99a7f318cb33176b8d3d6a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 22 Oct 2018 22:21:00 +1000 Subject: [PATCH 0861/1752] (cisco:ce) add support for multiline commands --- .../cisco/collaboration_endpoint/room_os.rb | 20 +++++++++++----- .../collaboration_endpoint/room_os_spec.rb | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 775ebe22..dc07608c 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -107,10 +107,11 @@ def received(data, deferrable, command) # Execute an xCommand on the device. # # @param command [String] the command to execute + # @param multiline_body [String] an optional multiline body for the command # @param kwargs [Hash] the command arguments # @return [::Libuv::Q::Promise] resolves when the command completes - def xcommand(command, kwargs = {}) - send_xcommand command, kwargs + def xcommand(command, multiline_body = nil, **kwargs) + send_xcommand command, multiline_body, kwargs end # Push a configuration settings to the device. @@ -155,9 +156,10 @@ def self.extended(child) # to protect access to #xcommand and still refer the gruntwork here. # # @param comand [String] the xAPI command to execute + # @param multiline_body [String] an optional multiline body for the command # @param kwargs [Hash] the command keyword args # @return [::Libuv::Q::Promise] that will resolve when execution is complete - def send_xcommand(command, kwargs = {}) + def send_xcommand(command, multiline_body = nil, **kwargs) request = Action.xcommand command, kwargs # Multi-arg commands (external source registration, UI interaction etc) @@ -167,7 +169,7 @@ def send_xcommand(command, kwargs = {}) opts[:name] = command if kwargs.empty? opts[:name] = "#{command} #{kwargs.keys.first}" if kwargs.size == 1 - do_send request, **opts do |response| + do_send request, multiline_body, **opts do |response| # The result keys are a little odd: they're a concatenation of the # last two command elements and 'Result', unless the command # failed in which case it's just 'Result'. @@ -325,15 +327,21 @@ def init_connection # Execute raw command on the device. # # @param command [String] the raw command to execute + # @param multiline_body [String] an optional multiline body for the command # @param options [Hash] options for the transport layer # @yield [response] # a pre-parsed response object for the command, if used this block # should return the response result # @return [::Libuv::Q::Promise] - def do_send(command, **options) + def do_send(command, multiline_body = nil, **options) request_id = generate_request_uuid - request = "#{command} | resultId=\"#{request_id}\"\n" + if multiline_body + multiline_body += "\n" unless multiline_body.end_with? "\n" + multiline_body << ".\n" + end + + request = "#{command} | resultId=\"#{request_id}\"\n#{multiline_body}" logger.debug { "-> #{request}" } diff --git a/modules/cisco/collaboration_endpoint/room_os_spec.rb b/modules/cisco/collaboration_endpoint/room_os_spec.rb index b7576e02..2439f384 100644 --- a/modules/cisco/collaboration_endpoint/room_os_spec.rb +++ b/modules/cisco/collaboration_endpoint/room_os_spec.rb @@ -404,6 +404,30 @@ def section(message) ) expect { result }.to raise_error(Orchestrator::Error::CommandFailure) + # Multiline commands + exec(:xcommand, 'SystemUnit SignInBanner Set', "Hello\nWorld!") + .should_send( + <<~COMMAND + xCommand SystemUnit SignInBanner Set | resultId=\"#{id_peek}\" + Hello + World! + . + COMMAND + ) + .responds( + <<~JSON + { + "CommandResponse":{ + "SignInBannerSetResult":{ + "status":"OK" + } + }, + "ResultId": \"#{id_pop}\" + } + JSON + ) + expect(result).to eq status: 'OK' + # ------------------------------------------------------------------------- section 'Configuration' From 88318ad381635632326a9d5e937411ac6dc46785 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 22 Oct 2018 22:36:03 +1000 Subject: [PATCH 0862/1752] (cisco:ce) add support for deploying touch 10 UI's --- modules/cisco/collaboration_endpoint/ui_extensions.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/ui_extensions.rb b/modules/cisco/collaboration_endpoint/ui_extensions.rb index 1b693db7..f21830e0 100644 --- a/modules/cisco/collaboration_endpoint/ui_extensions.rb +++ b/modules/cisco/collaboration_endpoint/ui_extensions.rb @@ -62,7 +62,9 @@ def ui_set_value(widget, value) end end - protected + def ui_extenions_deploy(id, xml_def) + send_xcommand 'UserInterface Extensions Set', xml_def, ConfigId: id + end def ui_extensions_list send_xcommand 'UserInterface Extensions List' From 731bd5e5416cc7dda5f8f4071b8f91ba8427be71 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 23 Oct 2018 11:16:28 +1000 Subject: [PATCH 0863/1752] (cisco:ce) note that admin user is required --- modules/cisco/collaboration_endpoint/room_kit.rb | 2 +- modules/cisco/collaboration_endpoint/sx20.rb | 2 +- modules/cisco/collaboration_endpoint/sx80.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index e240a459..d3a04267 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -13,7 +13,7 @@ class Cisco::CollaborationEndpoint::RoomKit < Cisco::CollaborationEndpoint::Room description <<~DESC Control of Cisco RoomKit devices. - API access requires a local user with the 'integrator' role to be + API access requires a local user with the 'admin' role to be created on the codec. DESC diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 0b309019..5a95ef4f 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -13,7 +13,7 @@ class Cisco::CollaborationEndpoint::Sx20 < Cisco::CollaborationEndpoint::RoomOs description <<~DESC Control of Cisco SX20 devices. - API access requires a local user with the 'integrator' role to be + API access requires a local user with the 'admin' role to be created on the codec. DESC diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 6d91829d..0ab74581 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -13,7 +13,7 @@ class Cisco::CollaborationEndpoint::Sx80 < Cisco::CollaborationEndpoint::RoomOs description <<~DESC Control of Cisco SX80 devices. - API access requires a local user with the 'integrator' role to be + API access requires a local user with the 'admin' role to be created on the codec. DESC From a5f328f60b641ed1991634e5a872d1a72184fd2a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 23 Oct 2018 11:33:15 +1000 Subject: [PATCH 0864/1752] (cisco:ce) fix typo in method name --- modules/cisco/collaboration_endpoint/ui_extensions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/ui_extensions.rb b/modules/cisco/collaboration_endpoint/ui_extensions.rb index f21830e0..b9a6ad1c 100644 --- a/modules/cisco/collaboration_endpoint/ui_extensions.rb +++ b/modules/cisco/collaboration_endpoint/ui_extensions.rb @@ -62,7 +62,7 @@ def ui_set_value(widget, value) end end - def ui_extenions_deploy(id, xml_def) + def ui_extensions_deploy(id, xml_def) send_xcommand 'UserInterface Extensions Set', xml_def, ConfigId: id end From 60e09b2f60706fe81b466abe3e6323949fe1839b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 24 Oct 2018 10:52:10 +1100 Subject: [PATCH 0865/1752] Slice bulk requests --- lib/microsoft/office.rb | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f814166d..6d982edf 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -524,18 +524,23 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] - endpoints = user_ids.map do |email| + all_endpoints = user_ids.map do |email| "/users/#{email}/calendarView" end - query = { - '$top': 200, - startDateTime: start_param, - endDateTime: end_param, - } - bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) - check_response(bulk_response) - responses = JSON.parse(bulk_response.body)['responses'] + responses = [] + all_endpoints.each_slice(10).each do |endpoints| + query = { + '$top': 200, + startDateTime: start_param, + endDateTime: end_param, + } + bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) + + check_response(bulk_response) + responses += JSON.parse(bulk_response.body)['responses'] + end + recurring_bookings = {} responses.each_with_index do |res, i| recurring_bookings[user_ids[res['id'].to_i]] = res['body']['value'] From dde4c1916695da7e6a64f4f353407cba845374aa Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 24 Oct 2018 11:56:33 +1100 Subject: [PATCH 0866/1752] Fix bulk request slicing --- lib/microsoft/office.rb | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 6d982edf..358b6b8f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -98,8 +98,6 @@ def graph_request(request_method:, endpoint:, data:nil, query:{}, headers:nil, p start_timing = Time.now.to_i response_value = response.value end_timing = Time.now.to_i - STDERR.puts "Graph request took #{end_timing - start_timing} seconds" - STDERR.flush return response_value end @@ -148,26 +146,11 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers start_timing = Time.now.to_i response_value = response.value end_timing = Time.now.to_i - STDERR.puts "Bulk Graph request took #{end_timing - start_timing} seconds" - STDERR.flush return response_value end def log_graph_request(request_method, data, query, headers, graph_path, password, endpoints=nil) - STDERR.puts "--------------NEW GRAPH REQUEST------------" - STDERR.puts "#{request_method} to #{graph_path}" - STDERR.puts "Data:" - STDERR.puts data if data - STDERR.puts "Query:" - STDERR.puts query if query - STDERR.puts "Headers:" - STDERR.puts headers if headers - STDERR.puts "Endpoints:" - STDERR.puts endpoints if endpoints - STDERR.puts "Password auth is: #{password}" - STDERR.puts '--------------------------------------------' - STDERR.flush end def check_response(response) @@ -175,9 +158,6 @@ def check_response(response) when 200, 201, 204 return when 400 - STDERR.puts "GOT ERROR" - STDERR.puts response.inspect - STDERR.flush if response['error']['code'] == 'ErrorInvalidIdMalformed' raise Microsoft::Error::ErrorInvalidIdMalformed.new(response.body) else @@ -527,9 +507,9 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no all_endpoints = user_ids.map do |email| "/users/#{email}/calendarView" end - + slice_size = 20 responses = [] - all_endpoints.each_slice(10).each do |endpoints| + all_endpoints.each_slice(slice_size).with_index do |endpoints, ind| query = { '$top': 200, startDateTime: start_param, @@ -538,7 +518,13 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) check_response(bulk_response) - responses += JSON.parse(bulk_response.body)['responses'] + parsed_response = JSON.parse(bulk_response.body)['responses'] + parsed_response.each do |res| + local_id = res['id'].to_i + global_id = local_id + (slice_size * ind.to_i) + res['id'] = global_id + responses.push(res) + end end recurring_bookings = {} @@ -684,9 +670,6 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec endpoint = "/v1.0/users/#{current_user[:email]}/events/#{booking_id}" end - STDERR.puts "ENDPOINT IS" - STDERR.puts endpoint - STDERR.flush start_object = ensure_ruby_date(start_param).in_time_zone(timezone) From c3775a335ae85a9bcee27cd31c935e56d43be63b Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Wed, 24 Oct 2018 19:58:59 +1100 Subject: [PATCH 0867/1752] Update modules/panasonic/lcd/touch_rs232.rb Created Pana Touch LCD Specific RS-232 Module --- modules/panasonic/lcd/touch_rs232.rb | 257 +++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 modules/panasonic/lcd/touch_rs232.rb diff --git a/modules/panasonic/lcd/touch_rs232.rb b/modules/panasonic/lcd/touch_rs232.rb new file mode 100644 index 00000000..0ada7d0d --- /dev/null +++ b/modules/panasonic/lcd/touch_rs232.rb @@ -0,0 +1,257 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'digest/md5' + +module Panasonic; end +module Panasonic::LCD; end + +# Documentation: +# * Protocol: https://aca.im/driver_docs/Panasonic/lcd_protocol2.pdf +# * Commands: https://aca.im/driver_docs/Panasonic/panasonic_commands.pdf + +class Panasonic::LCD::Rs232 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 1024 + descriptive_name 'Panasonic Touch LCD RS-232' #TP-65BFE1 at Arup + generic_name :Display + + # Communication settings + tokenize delimiter: "\x03" + + + def on_load + self[:power] = false + self[:power_stable] = true # Stable by default (allows manual on and off) + + # Meta data for inquiring interfaces + self[:type] = :lcd + + # The projector drops the connection when there is no activity + schedule.every('60s') { do_poll if self[:connected] } + on_update + end + + def on_update + #@username = setting(:username) || 'dispadmin' + #@password = setting(:password) || '@Panasonic' + end + + def connected + + end + + def disconnected + end + + COMMANDS = { + power_on: 'PON', + power_off: 'POF', + power_query: 'QPW', + input: 'IMS', + volume: 'AVL', + volume_query: 'QAV', + audio_mute: 'AMT', + audio_mute_query: 'QAM', + current_input: 'QMI' + } + COMMANDS.merge!(COMMANDS.invert) + + # + # Power commands + # + def power(state, opt = nil) + self[:power_stable] = false + if is_affirmative?(state) + self[:power_target] = On + do_send(:power_on, retries: 10, name: :power, delay: 10000, timeout: 15000) + logger.debug "requested to power on" + do_send(:power_query) + else + self[:power_target] = Off + do_send(:power_off, retries: 10, name: :power, delay: 8000) + logger.debug "requested to power off" + do_send(:power_query) + end + end + + def power?(**options, &block) + options[:emit] = block if block_given? + do_send(:power_query, options) + end + + # + # Input selection + # + INPUTS = { + hdmi1: 'HM1', + hdmi: 'HM1', + hdmi2: 'HM2', + vga: 'PC1', + dvi: 'DV1', + hdbaset: 'DL1' + } + INPUTS.merge!(INPUTS.invert) + + def switch_to(input) + input = input.to_sym + return unless INPUTS.has_key? input + + # Projector doesn't automatically unmute + unmute if self[:mute] + + logger.debug { "requested to switch to: #{input}" } + do_send(:input, INPUTS[input], retries: 10, delay_on_receive: 2000).then do + # Can't query current input + self[:input] = input + end + end + + def input? + do_send(:current_input) + end + + # + # Mute Audio + # + def mute_audio(val = true) + actual = val ? 1 : 0 + logger.debug "requested to mute #{val}" + do_send(:audio_mute, actual) # Audio + Video + do_poll + end + alias_method :mute, :mute_audio + + def unmute_audio + mute false + end + alias_method :unmute, :unmute_audio + + def muted? + do_send(:audio_mute_query) + end + + def volume(level) + # Unable to query current volume + do_send(:volume, level.to_s.rjust(3, '0')).then { self[:volume] = level.to_i } + end + + def volume? + do_send :volume_query + end + + def do_poll + power?(priority: 0).then do + if self[:power] + input? + volume? + muted? + end + end + end + + ERRORS = { + ERR1: '1: Undefined control command', + ERR2: '2: Out of parameter range', + ERR3: '3: Busy state or no-acceptable period', + ERR4: '4: Timeout or no-acceptable period', + ERR5: '5: Wrong data length', + ERRA: 'A: Password mismatch', + ER401: '401: Command cannot be executed', + ER402: '402: Invalid parameter is sent' + } + + def received(data, resolve, command) # Data is default received as a string + logger.debug { "sent #{data} for #{command ? command[:data] : 'unknown'}" } + + # This is the ready response + if data[0] == ' ' + # Ignore this as it is not a response, we can now make a request + return :ignore + end + + # remove the leading 00 + data = data[2..-1] + + # Error Response (00ER401) + if data.start_with?('ER') + error = data.to_sym + self[:last_error] = ERRORS[error] + + # Check for busy or timeout + if error == :ERR3 || error == :ERR4 + logger.warn "Display busy: #{self[:last_error]}" + return :retry + else + logger.error "Display error: #{self[:last_error]}" + return :abort + end + end + + cmd = COMMANDS[data] + case cmd + when :power_on + self[:power] = true + ensure_power_state + when :power_off + self[:power] = false + ensure_power_state + when '001' + self[:power] = true + ensure_power_state + else + res = data.split(':')[1] + case command[:name] + when :power_query + self[:power] = res.to_i == 1 + ensure_power_state + when :audio_mute_query + self[:audio_mute] = res.to_i == 1 + when :volume_query + self[:volume] = res.to_i + when :current_input + self[:input] = INPUTS[res] + end + end + + :success + end + + + protected + + + def ensure_power_state + if !self[:power_stable] && self[:power] != self[:power_target] + power(self[:power_target]) + else + self[:power_stable] = true + end + end + + def do_send(command, param = nil, **options) + if param.is_a? Hash + options = param + param = nil + end + + # Default to the command name if name isn't set + options[:name] = command unless options[:name] + # options[:disconnect] = true + + if param.nil? + cmd = COMMANDS[command] + else + cmd = "#{COMMANDS[command]}:#{param}" + end + #Standard Panasonic requires CR/LF - Touch LCD throw error on LF after CMD + full_cmd = hex_to_byte('02') << cmd << hex_to_byte('03') + + # Will only accept a single request at a time. + send(full_cmd, options) + end + +end From 44c1bdd2e49d5b48532f31deb49aa42c7f0eafc6 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Wed, 24 Oct 2018 20:03:54 +1100 Subject: [PATCH 0868/1752] (panasonic:touch-lcd) modify standard rs232 driver for touch --- modules/panasonic/lcd/touch_rs232.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/panasonic/lcd/touch_rs232.rb b/modules/panasonic/lcd/touch_rs232.rb index 0ada7d0d..da250ba1 100644 --- a/modules/panasonic/lcd/touch_rs232.rb +++ b/modules/panasonic/lcd/touch_rs232.rb @@ -247,7 +247,7 @@ def do_send(command, param = nil, **options) else cmd = "#{COMMANDS[command]}:#{param}" end - #Standard Panasonic requires CR/LF - Touch LCD throw error on LF after CMD + #Standard Panasonic requires CR/LF - Touch LCD throw error on LF after CMD full_cmd = hex_to_byte('02') << cmd << hex_to_byte('03') # Will only accept a single request at a time. From 4086b7fba1347773140d47fa91c642550e7edd14 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 24 Oct 2018 21:56:49 +1000 Subject: [PATCH 0869/1752] (cisco:ui) implement base module comms --- .../cisco/collaboration_endpoint/room_os.rb | 37 ++++++ modules/cisco/collaboration_endpoint/ui.rb | 117 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 modules/cisco/collaboration_endpoint/ui.rb diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index dc07608c..cc9770b6 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -146,6 +146,43 @@ def self.extended(child) end + # ------------------------------ + # External feedback subscriptions + + # Subscribe another module to async device events. + # + # Callback methods must be of arity 1 and public. + # + # @param path [String] the event path + # @param mod_id [Symbol] the module id containing the callback + # @param cb [Symbol] the callback method + def on_event(path, mod_id, cb) + logger.debug { "Registering callback for #{path} to #{mod_id}.#{cb}" } + + register_feedback path do |event| + logger.debug { "Proxying #{path} event to #{mod_id}.#{cb}" } + + reactor = ::Libuv::Reactor.current + # FIXME: switch to ModuleLoader.instance.get when available + mod = ::Orchestrator::Control.instance.loaded? mod_id + proxy = ::Orchestrator::Core::RequestProxy.new reactor, mod + + proxy.send cb, event + end + end + + # Clear external event subscribtions for a specific device path. + # + # @param path [String] the event path + def clear_event(path) + logger.debug { "Clearing event subscription for #{path}" } + + unregister_feedback path + end + + protect_method :on_event, :clear_event + + protected diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb new file mode 100644 index 00000000..9c9cdb7b --- /dev/null +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +module Cisco; end +module CollaborationEndpoint; end + +class Cisco::CollaborationEndpoint::Ui + include ::Orchestrator::Constants + + descriptive_name 'Cisco UI' + generic_name :CiscoUI + implements :logic + description 'Cisco Touch 10 UI extensions' + + + # ------------------------------ + # Module callbacks + + def on_load + on_update + end + + def on_unload + unbind + end + + def on_update + bind setting(:codec) || :VidConf + end + + + # ------------------------------ + # Device event callbacks + + def on_extensions_widget_action(event) + logger.debug event + end + + def on_presentation_externalsource(event) + logger.debug event + + source_id = event.dig :Selected, :SourceIdentifier + self[:external_source] = source_id + # FIXME: clear on sharing stop instead? + signal_status(:external_source) + end + + + protected + + + # ------------------------------ + # Internals + + # Bind to a Cisco CE device module. + # + # @param mod [Symbol] the id of the Cisco CE device module to bind to + def bind(mod) + logger.debug "binding to #{mod}" + + @codec_mod = mod.to_sym + + unsubscribe @event_binder if @event_binder + @event_binder = system.subscribe(@codec_mod, :connected) do |notify| + connected = notify.value + subscribe_events if connected + end + + @codec_mod + end + + # Unbind from the device module. + def unbind + logger.debug 'unbinding' + + unsubscribe @event_binder + @event_binder = nil + + clear_events + + @codec_mod = nil + end + + def bound? + @codec_mod.nil?.! + end + + def codec + raise 'not currently bound to a codec module' unless bound? + system[@codec_mod] + end + + def ui_callbacks + public_methods(false).each_with_object([]) do |method, callbacks| + next if ::Orchestrator::Core::PROTECTED[method] + callbacks << method if method[0..2] == 'on_' + end + end + + def event_mappings + ui_callbacks.map do |cb| + path = "/Event/UserInterface/#{cb[3..-1].tr! '_', '/'}" + [path, cb] + end + end + + def subscribe_events + event_mappings.map do |path, cb| + codec.on_event path, @__config__.settings.id, cb + end + end + + def clear_events + event_mappings.map do |path, _| + codec.clear_event path + end + end +end From 2033fcabe6720a2528cb7fce130931ea9baadf05 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Wed, 24 Oct 2018 23:18:11 +1000 Subject: [PATCH 0870/1752] (cisco:ui) add support for storing UI layouts in settings --- modules/cisco/collaboration_endpoint/ui.rb | 40 ++++++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 9c9cdb7b..d4ea1ab6 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -20,11 +20,29 @@ def on_load end def on_unload + clear_extensions unbind end def on_update - bind setting(:codec) || :VidConf + codec_mod = setting(:codec) || :VidConf + ui_layout = setting :cisco_ui_layout + + # Allow UI layouts to be stored as JSON + if ui_layout.is_a? Hash + logger.warn 'attempting experimental UI layout conversion' + # FIXME: does not currently work if keys are missing from generated + # xml (even if they are blank). Endpoints appear to ignore any + # layouts that do not match the expected structure perfectly. + ui_layout = (ui_layout[:Extensions] || ui_layout).to_xml \ + root: :Extensions, + skip_types: true, + skip_instruct: true + end + + bind(codec_mod) do + deploy_extensions 'test', ui_layout if ui_layout + end end @@ -35,13 +53,20 @@ def on_extensions_widget_action(event) logger.debug event end - def on_presentation_externalsource(event) - logger.debug event - source_id = event.dig :Selected, :SourceIdentifier - self[:external_source] = source_id - # FIXME: clear on sharing stop instead? - signal_status(:external_source) + # ------------------------------ + # UI deployment + + def deploy_extensions(id, xml_def) + codec.xcommand 'UserInterface Extensions Set', xml_def, ConfigId: id + end + + def list_extensions + codec.xcommand 'UserInterface Extensions List' + end + + def clear_extensions + codec.xcommand 'UserInterface Extensions Clear' end @@ -63,6 +88,7 @@ def bind(mod) @event_binder = system.subscribe(@codec_mod, :connected) do |notify| connected = notify.value subscribe_events if connected + yield if block_given? end @codec_mod From 994f3c285da0a3984c43c4a40c2707df0fa43729 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 00:51:46 +1000 Subject: [PATCH 0871/1752] (cisco:ui) add support for alert messages --- modules/cisco/collaboration_endpoint/ui.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index d4ea1ab6..49428b64 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -70,6 +70,21 @@ def clear_extensions end + # ------------------------------ + # Popup messages + + def msg_alert(text, title: '', duration: 0) + codec.xcommand 'UserInterface Message Alert Display', + Text: text, + Title: title, + Duration: duration + end + + def msg_alert_clear + codec.xcommand 'UserInterface Message Alert Clear' + end + + protected From 611a27b066e9b62a24664a84c70eb16a8f38f965 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 01:35:57 +1000 Subject: [PATCH 0872/1752] (cisco:ui) add support for UI widget interaction --- modules/cisco/collaboration_endpoint/ui.rb | 38 +++++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 49428b64..59edba8f 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -46,14 +46,6 @@ def on_update end - # ------------------------------ - # Device event callbacks - - def on_extensions_widget_action(event) - logger.debug event - end - - # ------------------------------ # UI deployment @@ -70,6 +62,36 @@ def clear_extensions end + # ------------------------------ + # UI element interaction + + def widget(id, value) + case value + when nil + codec.xcommand 'UserInterface Extensions Widget UnsetValue', + WidgetId: id + when true + widget id, :on + when false + widget id, :off + else + codec.xcommand 'UserInterface Extensions Widget SetValue', + Value: value, WidgetId: id + end + end + + BUTTON_EVENT = [:pressed, :released, :clicked].freeze + + def on_extensions_widget_action(event) + id, value, type = event.values_at :WidgetId, :Value, :Type + + logger.debug { "#{id} #{type}" } + + # Track values of stateful widgets as module state vars + self[id] = value unless BUTTON_EVENT.include?(type) && value == '' + end + + # ------------------------------ # Popup messages From f730800cca9067213b456ac741d1a580df022843 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 01:45:28 +1000 Subject: [PATCH 0873/1752] (cisco:ui) improve readbility --- modules/cisco/collaboration_endpoint/ui.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 59edba8f..77fcf668 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -49,14 +49,17 @@ def on_update # ------------------------------ # UI deployment + # Push a UI definition build with the in-room control editor to the device. def deploy_extensions(id, xml_def) codec.xcommand 'UserInterface Extensions Set', xml_def, ConfigId: id end + # Retrieve the extensions currently loaded. def list_extensions codec.xcommand 'UserInterface Extensions List' end + # Clear any deployed UI extensions. def clear_extensions codec.xcommand 'UserInterface Extensions Clear' end @@ -65,23 +68,27 @@ def clear_extensions # ------------------------------ # UI element interaction + # Set the value of a custom UI widget. def widget(id, value) + widget_action = lambda do |action, **args| + codec.xcommand "UserInterface Extensions Widget #{action}", args + end + case value when nil - codec.xcommand 'UserInterface Extensions Widget UnsetValue', - WidgetId: id + widget_action[:UnsetValue, WidgetId: id] when true widget id, :on when false widget id, :off else - codec.xcommand 'UserInterface Extensions Widget SetValue', - Value: value, WidgetId: id + widget_action[:SetValue, WidgetId: id, Value: value] end end BUTTON_EVENT = [:pressed, :released, :clicked].freeze + # Callback for changes to widget state. def on_extensions_widget_action(event) id, value, type = event.values_at :WidgetId, :Value, :Type From e54ee02ccf5d785e4a9a8a1398ed0e641ad71d33 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 01:45:49 +1000 Subject: [PATCH 0874/1752] (cisco:ui) remove old event subscriptions when rebinding --- modules/cisco/collaboration_endpoint/ui.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 77fcf668..74bd28e9 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -128,6 +128,8 @@ def bind(mod) @codec_mod = mod.to_sym + thread.all(clear_events).value + unsubscribe @event_binder if @event_binder @event_binder = system.subscribe(@codec_mod, :connected) do |notify| connected = notify.value From 5870e1f746e6cd6807b39343a9c4aa84b093c8ce Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 09:55:15 +1000 Subject: [PATCH 0875/1752] (cisco:ui) await device response when clearing events --- modules/cisco/collaboration_endpoint/ui.rb | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 74bd28e9..4dac8f61 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -128,7 +128,7 @@ def bind(mod) @codec_mod = mod.to_sym - thread.all(clear_events).value + clear_events unsubscribe @event_binder if @event_binder @event_binder = system.subscribe(@codec_mod, :connected) do |notify| @@ -175,14 +175,28 @@ def event_mappings end end + # Perform an action for each event -> callback mapping. + def each_mapping(async: false) + device_mod = codec + + interactions = event_mappings.map do |path, cb| + yield path, cb, device_mod + end + + result = thread.finally interactions + result.value unless async + end + def subscribe_events - event_mappings.map do |path, cb| - codec.on_event path, @__config__.settings.id, cb + mod_id = @__config__.settings.id + + each_mapping do |path, cb, codec| + codec.on_event path, mod_id, cb end end def clear_events - event_mappings.map do |path, _| + each_mapping do |path, _, codec| codec.clear_event path end end From adee6225381cea87a0b7bdbab4b67b217b3053ca Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 09:55:59 +1000 Subject: [PATCH 0876/1752] (cisco:ui) fix error when attempting to unbind when not bound --- modules/cisco/collaboration_endpoint/ui.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 4dac8f61..b9b7c7e1 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -144,7 +144,7 @@ def bind(mod) def unbind logger.debug 'unbinding' - unsubscribe @event_binder + unsubscribe @event_binder if @event_binder @event_binder = nil clear_events From 0a57b4a9cf89e0bb79529e16857f161b8f635aaa Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 09:57:36 +1000 Subject: [PATCH 0877/1752] (cisco:ui) enforce arity requirements for event callbacks --- modules/cisco/collaboration_endpoint/ui.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index b9b7c7e1..dafb1dc4 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -161,13 +161,19 @@ def codec system[@codec_mod] end + # Build a list of all callback methods that have been defined. + # + # Callback methods are denoted being single arity and beginning with `on_`. def ui_callbacks - public_methods(false).each_with_object([]) do |method, callbacks| - next if ::Orchestrator::Core::PROTECTED[method] - callbacks << method if method[0..2] == 'on_' + public_methods(false).each_with_object([]) do |name, callbacks| + next if ::Orchestrator::Core::PROTECTED[name] + next unless name[0..2] == 'on_' + next unless method(name).arity == 1 + callbacks << name end end + # Build a list of device XPath -> callback mappings. def event_mappings ui_callbacks.map do |cb| path = "/Event/UserInterface/#{cb[3..-1].tr! '_', '/'}" From 2cc0a6e9384088e8d85ed1943a07bf0871e6e874 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Thu, 25 Oct 2018 11:32:23 +1100 Subject: [PATCH 0878/1752] (panasonic:touch-lcd) fix class --- modules/panasonic/lcd/touch_rs232.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/panasonic/lcd/touch_rs232.rb b/modules/panasonic/lcd/touch_rs232.rb index da250ba1..50c83bfb 100644 --- a/modules/panasonic/lcd/touch_rs232.rb +++ b/modules/panasonic/lcd/touch_rs232.rb @@ -10,7 +10,7 @@ module Panasonic::LCD; end # * Protocol: https://aca.im/driver_docs/Panasonic/lcd_protocol2.pdf # * Commands: https://aca.im/driver_docs/Panasonic/panasonic_commands.pdf -class Panasonic::LCD::Rs232 +class Panasonic::LCD::TouchRs232 include ::Orchestrator::Constants include ::Orchestrator::Transcoder @@ -247,7 +247,7 @@ def do_send(command, param = nil, **options) else cmd = "#{COMMANDS[command]}:#{param}" end - #Standard Panasonic requires CR/LF - Touch LCD throw error on LF after CMD + #Standard Panasonic requires CR/LF - Touch LCD throw error on LF after CMD full_cmd = hex_to_byte('02') << cmd << hex_to_byte('03') # Will only accept a single request at a time. From 4402b1b9c6e6738f6c32787d3f60f8f6bcbae669 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 10:46:19 +1000 Subject: [PATCH 0879/1752] (cisco:ce) remove old debug event subscription --- .../cisco/collaboration_endpoint/ui_extensions.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui_extensions.rb b/modules/cisco/collaboration_endpoint/ui_extensions.rb index b9a6ad1c..2bdb1410 100644 --- a/modules/cisco/collaboration_endpoint/ui_extensions.rb +++ b/modules/cisco/collaboration_endpoint/ui_extensions.rb @@ -8,19 +8,6 @@ module Cisco::CollaborationEndpoint; end module Cisco::CollaborationEndpoint::UiExtensions include ::Cisco::CollaborationEndpoint::Xapi::Mapper - module Hooks - def connected - super - register_feedback '/Event/UserInterface/Extensions/Widget/Action' do |action| - logger.debug action - end - end - end - - def self.included(base) - base.prepend Hooks - end - command 'UserInterface Message Alert Clear' => :msg_alert_clear command 'UserInterface Message Alert Display' => :msg_alert, Text: String, From 2dfc55e321037c225e2d1d857d920b36fc0de8b6 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 10:47:36 +1000 Subject: [PATCH 0880/1752] (cisco:ui) fix issue where bind actions would exec on disconnect --- modules/cisco/collaboration_endpoint/ui.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index dafb1dc4..c3709458 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -132,8 +132,8 @@ def bind(mod) unsubscribe @event_binder if @event_binder @event_binder = system.subscribe(@codec_mod, :connected) do |notify| - connected = notify.value - subscribe_events if connected + next unless notify.value + subscribe_events yield if block_given? end From 2ac7bd13afb34e5a6f45de3a6b94513804a41807 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 11:22:54 +1000 Subject: [PATCH 0881/1752] (cisco:ui) change id of pushed UI's --- modules/cisco/collaboration_endpoint/ui.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index c3709458..462f793e 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -41,7 +41,7 @@ def on_update end bind(codec_mod) do - deploy_extensions 'test', ui_layout if ui_layout + deploy_extensions 'ACA', ui_layout if ui_layout end end From a2b7309b56a37656b6a185419d84d4e4a4d1ad2b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 25 Oct 2018 15:10:05 +1100 Subject: [PATCH 0882/1752] O365 Lib - Add contacts to user request --- lib/microsoft/office.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 358b6b8f..a93b9e81 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -172,7 +172,7 @@ def check_response(response) end end - def get_users(q: nil, limit: nil) + def get_users(q: nil, limit: nil, contact_email:nil) # If we have a query and the query has at least one space if q && q.include?(" ") @@ -196,7 +196,9 @@ def get_users(q: nil, limit: nil) endpoint = "/v1.0/users" request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) - JSON.parse(request.body)['value'] + user_list = JSON.parse(request.body)['value'] + user_list += self.get_contacts(contact_email) if contact_email + user_list end def get_user(user_id:) From fdc5584ba399716e05807e2de3d021050ec055af Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 25 Oct 2018 15:13:58 +1100 Subject: [PATCH 0883/1752] Fix call to get contacts --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a93b9e81..037c4684 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -197,7 +197,7 @@ def get_users(q: nil, limit: nil, contact_email:nil) request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) user_list = JSON.parse(request.body)['value'] - user_list += self.get_contacts(contact_email) if contact_email + user_list += self.get_contacts(owner_email:contact_email) if contact_email user_list end From beb2dd86f8f6d2cbacc7238aeba4a9ac91529dfb Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 20:49:04 +1000 Subject: [PATCH 0884/1752] (cisco:ui) provide support for arbitrary behaviour bindings via settings --- modules/cisco/collaboration_endpoint/ui.rb | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 462f793e..97b8386b 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -27,6 +27,7 @@ def on_unload def on_update codec_mod = setting(:codec) || :VidConf ui_layout = setting :cisco_ui_layout + @bindings = setting :cisco_ui_bindings || {} # Allow UI layouts to be stored as JSON if ui_layout.is_a? Hash @@ -96,6 +97,13 @@ def on_extensions_widget_action(event) # Track values of stateful widgets as module state vars self[id] = value unless BUTTON_EVENT.include?(type) && value == '' + + # Trigger any bindings defined for the widget action + begin + event_handler(id, type)&.call value + rescue => e + logger.error "error in binding for #{id}.#{type}: #{e}" + end end @@ -202,8 +210,55 @@ def subscribe_events end def clear_events + @event_handlers = nil + each_mapping do |path, _, codec| codec.clear_event path end end + + # Get the event handler for a UI widget interaction. + def event_handler(widget_id, event_type) + # Handlers are stored keyed on a tuple of [widget_id, event_type] and + # lazily built as required. + @event_handlers ||= Hash.new do |h, (id, type)| + config = @bindings.dig id, type + if config + handler = build_handler config + h.store [id, type].freeze, handler + end + end + + @event_handlers[[widget_id, event_type]] + end + + # Given the action for a binding, construct the executable event handler. + def build_handler(action) + case action + + # Implicit arguments + # "mod.method" + when String + mod, method = action.split '.' + proc do |value| + logger.debug { "proxying event to #{mod}.#{method}" } + proxy = system[mod] + case proxy.arity method + when 0 then proxy.send method + when 1 then proxy.send method, value + else raise "incompatible binding (#{action})" + end + end + + # Explicit / static arguments + # mod => { method => [params] } + when Hash + mod, command = action.first + method, args = command.first + proc do + logger.debug { "proxying event to #{mod}.#{method}" } + system[mod].send method, *args + end + end + end end From 508656686c645a5d101ea187f8ce648bb48228c8 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 25 Oct 2018 21:11:19 +1000 Subject: [PATCH 0885/1752] (cisco:ui) track button events as state vars --- modules/cisco/collaboration_endpoint/ui.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 97b8386b..35a2d397 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -95,8 +95,13 @@ def on_extensions_widget_action(event) logger.debug { "#{id} #{type}" } - # Track values of stateful widgets as module state vars - self[id] = value unless BUTTON_EVENT.include?(type) && value == '' + # Track values of stateful widgets as module state vars or provide an + # event stream for button based widgets + self[id] = if BUTTON_EVENT.include?(type) && value == '' + type + else + value + end # Trigger any bindings defined for the widget action begin From 1bf82c1ff3a1f26f30a0b35110c1b7b6ee88f5c2 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 26 Oct 2018 06:27:34 +0800 Subject: [PATCH 0886/1752] (cisco:ui) concat debug string in block Co-Authored-By: KimBurgess --- modules/cisco/collaboration_endpoint/ui.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 35a2d397..64bf3a05 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -137,7 +137,7 @@ def msg_alert_clear # # @param mod [Symbol] the id of the Cisco CE device module to bind to def bind(mod) - logger.debug "binding to #{mod}" + logger.debug { "binding to #{mod}" } @codec_mod = mod.to_sym From d5e1d9085061cfb562685749ef299f57584ac508 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 26 Oct 2018 09:53:20 +1000 Subject: [PATCH 0887/1752] (cisco:ui) provide event stream for external subscriptions --- modules/cisco/collaboration_endpoint/ui.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 64bf3a05..314fd7e5 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -87,21 +87,14 @@ def widget(id, value) end end - BUTTON_EVENT = [:pressed, :released, :clicked].freeze - # Callback for changes to widget state. def on_extensions_widget_action(event) id, value, type = event.values_at :WidgetId, :Value, :Type logger.debug { "#{id} #{type}" } - # Track values of stateful widgets as module state vars or provide an - # event stream for button based widgets - self[id] = if BUTTON_EVENT.include?(type) && value == '' - type - else - value - end + # Track values of stateful widgets + self[id] = value unless value == '' # Trigger any bindings defined for the widget action begin @@ -109,6 +102,9 @@ def on_extensions_widget_action(event) rescue => e logger.error "error in binding for #{id}.#{type}: #{e}" end + + # Provide an event stream for other modules to subscribe to + self[:__event_stream] = { id: id, type: type, value: value } end From 10fe120e06f0ad431c2702c5dbde97d36c8e13ea Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 26 Oct 2018 10:38:28 +1000 Subject: [PATCH 0888/1752] (cisco:ui) allow binding to methods with optional args --- modules/cisco/collaboration_endpoint/ui.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 314fd7e5..b000986d 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -244,11 +244,8 @@ def build_handler(action) proc do |value| logger.debug { "proxying event to #{mod}.#{method}" } proxy = system[mod] - case proxy.arity method - when 0 then proxy.send method - when 1 then proxy.send method, value - else raise "incompatible binding (#{action})" - end + args = proxy.arity(method).zero? ? nil : value + proxy.send method, *args end # Explicit / static arguments From 0eccec3adcffd9638d7a25e4529f8921c031a99b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 26 Oct 2018 14:57:32 +1000 Subject: [PATCH 0889/1752] (cisco:ui) allow module to unload when linked device is not responsive --- modules/cisco/collaboration_endpoint/ui.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index b000986d..827e9c6f 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -156,7 +156,7 @@ def unbind unsubscribe @event_binder if @event_binder @event_binder = nil - clear_events + clear_events async: true @codec_mod = nil end @@ -202,18 +202,18 @@ def each_mapping(async: false) result.value unless async end - def subscribe_events + def subscribe_events(**opts) mod_id = @__config__.settings.id - each_mapping do |path, cb, codec| + each_mapping(**opts) do |path, cb, codec| codec.on_event path, mod_id, cb end end - def clear_events + def clear_events(**opts) @event_handlers = nil - each_mapping do |path, _, codec| + each_mapping(**opts) do |path, _, codec| codec.clear_event path end end From a0a892b7e1946569d791e42786f4cbef959ebaa3 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 29 Oct 2018 15:36:40 +1100 Subject: [PATCH 0890/1752] (extron:switcher:dtp) fix unmute (was calling matrix mute instead of dsp mute) --- modules/extron/switcher/dtp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/extron/switcher/dtp.rb b/modules/extron/switcher/dtp.rb index d10cad90..6a5a1c14 100644 --- a/modules/extron/switcher/dtp.rb +++ b/modules/extron/switcher/dtp.rb @@ -181,7 +181,7 @@ def mute(group, value = true, index = nil) end def unmute(group, index = nil) - mute_audio(group, false, index) + mute(group, false, index) #do_send("\eD#{group}*0GRPM", :group_type => :mute) # Response: GrpmD#{group}*+00000 end From 714bb6255455aa2f80c51ead6939d0a2b7c36104 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 29 Oct 2018 18:12:39 +1100 Subject: [PATCH 0891/1752] (panasonic:lcd:protocol2) add instructions on how to set Protocol2 on display --- modules/panasonic/lcd/protocol2.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/panasonic/lcd/protocol2.rb b/modules/panasonic/lcd/protocol2.rb index 8a1db7ea..03eefe58 100755 --- a/modules/panasonic/lcd/protocol2.rb +++ b/modules/panasonic/lcd/protocol2.rb @@ -9,6 +9,10 @@ module Panasonic::LCD; end # Documentation: # * Protocol: https://aca.im/driver_docs/Panasonic/lcd_protocol2.pdf # * Commands: https://aca.im/driver_docs/Panasonic/panasonic_commands.pdf +# The display must be set to Protocol2 using the IR remote: +# - Press SETUP +# - Select OSD language and press ENTER for >3secs +# - Select Options > LAN Control Protocol: Protocol 2 class Panasonic::LCD::Protocol2 include ::Orchestrator::Constants From 56cc6982ae7d6dc2ceb496b9c77b0662da86a5a1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 30 Oct 2018 15:01:04 +1100 Subject: [PATCH 0892/1752] (microsoft:office) add links to documentation --- lib/microsoft/office.rb | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 037c4684..e1192cb5 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -1,5 +1,6 @@ require 'active_support/time' require 'logger' + module Microsoft class Error < StandardError class ResourceNotFound < Error; end @@ -47,7 +48,7 @@ def initialize( @client_secret, oauth_options ) - end + end def graph_token @graph_token = @graph_client.client_credentials.get_token({ @@ -172,6 +173,7 @@ def check_response(response) end end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list def get_users(q: nil, limit: nil, contact_email:nil) # If we have a query and the query has at least one space @@ -201,6 +203,7 @@ def get_users(q: nil, limit: nil, contact_email:nil) user_list end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_get def get_user(user_id:) endpoint = "/v1.0/users/#{user_id}" request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) @@ -218,6 +221,7 @@ def has_user(user_id:) end end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_contacts def get_contacts(owner_email:, q:nil, limit:nil) query_params = { '$top': (limit || 1000) } query_params['$filter'] = "startswith(displayName, #{q}) or startswith(givenName, #{q}) or startswith(surname, #{q}) or emailAddresses/any(a:a/address eq #{q})" if q @@ -229,7 +233,7 @@ def get_contacts(owner_email:, q:nil, limit:nil) def format_contacts(contacts) output_contacts = [] - contacts.each do |contact| + contacts.each do |contact| output_format = {} output_format[:id] = contact['id'] output_format[:first_name] = contact['givenName'] @@ -242,6 +246,7 @@ def format_contacts(contacts) output_contacts end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_contacts def get_contact(owner_email:, contact_email:) endpoint = "/v1.0/users/#{owner_email}/contacts" query_params = { @@ -263,6 +268,7 @@ def has_contact(owner_email:, contact_email:) return get_contact(owner_email: owner_email, contact_email: contact_email).length > 0 end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_contacts def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, organisation:nil, other:{}) # This data is required so add it unconditionally contact_data = { @@ -288,6 +294,7 @@ def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, organi JSON.parse(request.body) end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_contacts def get_organisations(owner_email:) query_params = { '$top': 1000 @@ -301,7 +308,7 @@ def get_organisations(owner_email:) orgs.uniq.compact end - + # https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/user_findrooms def get_rooms(q: nil, limit: nil) filter_param = "startswith(name,'#{q}') or startswith(address,'#{q}')" if q query_params = { @@ -314,6 +321,7 @@ def get_rooms(q: nil, limit: nil) room_response = JSON.parse(request.body)['value'] end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/user_findrooms def get_room(room_id:) endpoint = "/beta/users/#{@service_account_email}/findRooms" request = graph_request(request_method: 'get', endpoint: endpoint, password: true) @@ -322,8 +330,9 @@ def get_room(room_id:) room_response.select! { |room| room['email'] == room_id } end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/user_findmeetingtimes def get_available_rooms(rooms:, start_param:, end_param:) - endpoint = "/v1.0/users/#{@service_account_email}/findMeetingTimes" + endpoint = "/v1.0/users/#{@service_account_email}/findMeetingTimes" now = Time.now start_ruby_param = ensure_ruby_date((start_param || now)) end_ruby_param = ensure_ruby_date((end_param || (now + 1.hour))) @@ -333,7 +342,7 @@ def get_available_rooms(rooms:, start_param:, end_param:) # Add the attendees rooms.map!{|room| - { + { type: 'required', emailAddress: { address: room[:email], @@ -371,6 +380,7 @@ def get_available_rooms(rooms:, start_param:, end_param:) JSON.parse(request.body) end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_get def get_booking(booking_id:, mailbox:) endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) @@ -378,6 +388,7 @@ def get_booking(booking_id:, mailbox:) JSON.parse(request.body) end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_delete def delete_booking(booking_id:, mailbox:) endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" request = graph_request(request_method: 'delete', endpoint: endpoint, password: @delegated) @@ -466,7 +477,7 @@ def extract_booking_data(booking, start_param, end_param) # Get the organiser and location data booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] - booking['room_id'] = booking['locations'][0]['uniqueId'].downcase + booking['room_id'] = booking['locations'][0]['uniqueId'].downcase end if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? booking['room_name'] = booking['location']['displayName'] @@ -475,6 +486,7 @@ def extract_booking_data(booking, start_param, end_param) booking end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_calendarview def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) if user_id.class == Array user_id = user_id[0] @@ -501,6 +513,7 @@ def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now recurring_bookings end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_calendarview def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.now + 1.week)) # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] @@ -540,7 +553,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 return get_bookings_by_user(user_id: room_id, start_param: start_param, end_param: end_param) end - + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_events def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney') description = String(description) attendees = Array(attendees) @@ -633,7 +646,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil } } end - + if recurrence event[:recurrence] = { pattern: { @@ -661,6 +674,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil response = JSON.parse(request.body) end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney') # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find_by_email(room_id) @@ -722,7 +736,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec # Takes a date of any kind (epoch, string, time object) and returns a time object - def ensure_ruby_date(date) + def ensure_ruby_date(date) if !(date.class == Time || date.class == DateTime) if string_is_digits(date) @@ -735,9 +749,9 @@ def ensure_ruby_date(date) end # Convert to datetimes - date = Time.at(date) + date = Time.at(date) else - date = Time.parse(date) + date = Time.parse(date) end end return date From 7bf960611bebda670b72673bacf6452e78496f7b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 30 Oct 2018 20:06:47 +1000 Subject: [PATCH 0893/1752] (cisco:ui) add vanity methods for setting widget state --- modules/cisco/collaboration_endpoint/ui.rb | 50 +++++++++++++++------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 827e9c6f..9b476cb6 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -67,26 +67,46 @@ def clear_extensions # ------------------------------ - # UI element interaction - # Set the value of a custom UI widget. - def widget(id, value) - widget_action = lambda do |action, **args| - codec.xcommand "UserInterface Extensions Widget #{action}", args - end - case value - when nil - widget_action[:UnsetValue, WidgetId: id] - when true - widget id, :on - when false - widget id, :off - else - widget_action[:SetValue, WidgetId: id, Value: value] + + # ------------------------------ + # Element interaction + + # Set the value of a widget. + def set(id, value) + return unset id if value.nil? + + logger.debug { "setting #{id} to #{value}" } + + codec.xcommand 'UserInterface Extensions Widget SetValue', + WidgetId: id, Value: value end end + # Clear the value associated with a widget. + def unset(id) + logger.debug { "clearing #{id}" } + + codec.xcommand 'UserInterface Extensions Widget UnsetValue', + WidgetId: id + end + + # Set the state of a switch widget. + def switch(id, state) + value = is_affirmative?(state) ? :on : :off + set id, value + end + + # Set the highlight state for a button widget. + def highlight(id, state) + value = is_affirmative?(state) ? :active : :inactive + set id, value + end + + # Set the text label used on text or spinner widget. + alias label set + # Callback for changes to widget state. def on_extensions_widget_action(event) id, value, type = event.values_at :WidgetId, :Value, :Type From 41c1d8f47087de929b3588dd6250ca0b322bdf2f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 30 Oct 2018 20:07:16 +1000 Subject: [PATCH 0894/1752] (cisco:ui) simplify naming of alert popup methods --- modules/cisco/collaboration_endpoint/ui.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 9b476cb6..9d90d6cc 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -131,14 +131,14 @@ def on_extensions_widget_action(event) # ------------------------------ # Popup messages - def msg_alert(text, title: '', duration: 0) + def alert(text, title: '', duration: 0) codec.xcommand 'UserInterface Message Alert Display', Text: text, Title: title, Duration: duration end - def msg_alert_clear + def clear_alert codec.xcommand 'UserInterface Message Alert Clear' end From 7186c27cd74f82f19e0fe59c68ab2bb723a5f9e3 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 30 Oct 2018 20:12:35 +1000 Subject: [PATCH 0895/1752] (cisco:ui) add ability to close and track the active panel --- modules/cisco/collaboration_endpoint/ui.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 9d90d6cc..d9a64e76 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -67,7 +67,23 @@ def clear_extensions # ------------------------------ + # Panel interaction + def close_panel + codec.xcommand 'UserInterface Extensions Panel Close' + end + + def on_extensions_panel_clicked(event) + id = event[:PanelId] + + logger.debug { "#{id} opened" } + + self[:__active_panel] = id + end + + # FIXME: at the time of writing, the device API does not provide the ability + # to monitor for user initiated panel close events. When available, track + # these and update self[:__active_panel] accordingly. # ------------------------------ From fec923e482eab6dc211fd4112859b6ac5923f546 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 30 Oct 2018 22:33:42 +1000 Subject: [PATCH 0896/1752] (cisco:ui) provide ability to set widget state using status var API --- modules/cisco/collaboration_endpoint/ui.rb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index d9a64e76..7da6173c 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -48,7 +48,7 @@ def on_update # ------------------------------ - # UI deployment + # Deployment # Push a UI definition build with the in-room control editor to the device. def deploy_extensions(id, xml_def) @@ -78,7 +78,7 @@ def on_extensions_panel_clicked(event) logger.debug { "#{id} opened" } - self[:__active_panel] = id + track :__active_panel, id end # FIXME: at the time of writing, the device API does not provide the ability @@ -97,7 +97,6 @@ def set(id, value) codec.xcommand 'UserInterface Extensions Widget SetValue', WidgetId: id, Value: value - end end # Clear the value associated with a widget. @@ -130,7 +129,7 @@ def on_extensions_widget_action(event) logger.debug { "#{id} #{type}" } # Track values of stateful widgets - self[id] = value unless value == '' + track id, value unless ['', :increment, :decrement].include? value # Trigger any bindings defined for the widget action begin @@ -140,7 +139,17 @@ def on_extensions_widget_action(event) end # Provide an event stream for other modules to subscribe to - self[:__event_stream] = { id: id, type: type, value: value } + track :__event_stream, { id: id, type: type, value: value }.freeze + end + + # Alias the module state so that all widgets can be interacted with as if + # they were local status vars using []= and [] to set and get current value. + alias track []= + protected :track + # FIXME: the results in []= being enumerated by the funcs API + def []=(id, value) + set id, value + value end From 1756fe5cae4f0fb510fec7398317a020f4e6208f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 31 Oct 2018 09:50:42 +0000 Subject: [PATCH 0897/1752] Add logging back in --- lib/microsoft/office.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e1192cb5..caa83b37 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -84,7 +84,7 @@ def graph_request(request_method:, endpoint:, data:nil, query:{}, headers:nil, p graph_path = "#{@graph_domain}#{endpoint}" - log_graph_request(request_method, data, query, headers, graph_path, password) + log_graph_request(request_method, data, query, headers, graph_path, password) graph_api_options = {inactivity_timeout: 25000, keepalive: false} @@ -132,6 +132,9 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers requests: request_array }.to_json + + log_graph_request(request_method, data, query, headers, graph_path, password, endpoints) + graph_api_options = {inactivity_timeout: 25000, keepalive: false} @@ -152,6 +155,19 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers def log_graph_request(request_method, data, query, headers, graph_path, password, endpoints=nil) + STDERR.puts "--------------NEW GRAPH REQUEST------------" + STDERR.puts "#{request_method} to #{graph_path}" + STDERR.puts "Data:" + STDERR.puts data if data + STDERR.puts "Query:" + STDERR.puts query if query + STDERR.puts "Headers:" + STDERR.puts headers if headers + STDERR.puts "Endpoints:" + STDERR.puts endpoints if endpoints + STDERR.puts "Password auth is: #{password}" + STDERR.puts '--------------------------------------------' + STDERR.flush end def check_response(response) From 90f8082f7c72b1cd3abc0b1765ce2439c62d4908 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 31 Oct 2018 10:20:49 +0000 Subject: [PATCH 0898/1752] Update rooms by either ID or email --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index caa83b37..a499ee9e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -693,7 +693,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney') # We will always need a room and endpoint passed in - room = Orchestrator::ControlSystem.find_by_email(room_id) + room = Orchestrator::ControlSystem.find_by_email(room_id) || Orchestrator::ControlSystem.find(room_id) if @mailbox_location == 'room' || current_user.nil? From c5748a7944ea0f0277ddb046b8ab90ab0a8fc1c0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 1 Nov 2018 08:24:40 +1100 Subject: [PATCH 0899/1752] (microsoft:skype) basic functionality in place still requires error checking and improved logging --- lib/microsoft/skype.rb | 119 ++++++++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 43 deletions(-) diff --git a/lib/microsoft/skype.rb b/lib/microsoft/skype.rb index b4cce7fc..dd6bfeff 100644 --- a/lib/microsoft/skype.rb +++ b/lib/microsoft/skype.rb @@ -1,5 +1,7 @@ require 'active_support/time' -require 'logger' +require 'uv-rays' +require 'json' + module Microsoft class Error < StandardError class ResourceNotFound < Error; end @@ -11,92 +13,123 @@ class ErrorAccessDenied < Error; end end class Microsoft::Skype - TIMEZONE_MAPPING = { - "Sydney": "AUS Eastern Standard Time" - } def initialize( domain:, client_id:, client_secret:, username:, - password: + password:, + logger: nil ) @domain = domain @username = username @password = password @client_id = client_id @client_secret = client_secret - end + @logger = logger + end + + def create_meeting(subject) + users_url = discover_user_url + user_token = get_token(users_url) - # Probably the only public method that will be called - def create_meeting - user_url = dicover_user_url + apps_url = discover_application_url(users_url, user_token) + app_token = get_token(apps_url) + + apps_headers = { + "Accept" => "application/json", + "Content-Type" => "application/json", + "Authorization" => "Bearer #{app_token}" + } + + meetings_url = discover_meeting_url(apps_url, apps_headers, app_token) + + apps_uri = URI(apps_url) + base_url = "#{apps_uri.scheme}://#{apps_uri.host}" + create_skype_meeting(base_url, meetings_url, apps_headers, subject) end + protected + def get_token(url) uri = URI(url) resource = "#{uri.scheme}://#{uri.host}" - token_uri = URI("https://login.windows.net/#{@domain.split('.').first}.onmicrosoft.com/oauth2/token") - params = {:resource=>resource, :client_id=>@client_id, :grant_type=>"password", - :username=>@username, :password=>@password, :client_secret=>@client_secret} - puts "PARAMS ARE" - puts params - skype_auth_api = UV::HttpEndpoint.new(token_uri, {inactivity_timeout: 25000, keepalive: false}) - request = skype_auth_api.post({path: token_uri, body: params, headers: {"Content-Type":"application/x-www-form-urlencoded"}}) + token_uri = URI("https://login.microsoftonline.com/#{@domain.split('.').first}.onmicrosoft.com/oauth2/token") + params = {resource: resource, client_id: @client_id, grant_type: "password", + username: @username, password: @password, client_secret: @client_secret} + + @logger&.debug { + "Requesting token from #{token_uri}\nwith params:\n#{params}" + } + auth_response = nil - reactor.run { + ::Libuv.reactor { + skype_auth_api = UV::HttpEndpoint.new(token_uri, {inactivity_timeout: 25000, keepalive: false}) + request = skype_auth_api.post({path: token_uri, body: params, headers: {"Content-Type":"application/x-www-form-urlencoded"}}) auth_response = request.value } JSON.parse(auth_response.body)["access_token"] end - def create_skype_meeting(subject) - my_online_meetings_url = @app["_embedded"]["onlineMeetings"]["_links"]["myOnlineMeetings"]["href"] - - body = {accessLevel: "Everyone", subject: subject} - - url = @base_url+my_online_meetings_url - r = RestClient.post url, body.to_json, @apps_headers - return JSON.parse(r.body) - end - def discover_user_url - @skype_domain = "http://lyncdiscover.#{@domain}" - skype_discover_api = UV::HttpEndpoint.new(@skype_domain, {inactivity_timeout: 25000, keepalive: false}) - discover_request = skype_discover_api.get discover_response = nil - reactor.run { + ::Libuv.reactor { + @skype_domain = "http://lyncdiscover.#{@domain}" + skype_discover_api = ::UV::HttpEndpoint.new(@skype_domain, {inactivity_timeout: 25000, keepalive: false}) + discover_request = skype_discover_api.get discover_response = discover_request.value } - r = JSON.parse(discover_response.body) + r = ::JSON.parse(discover_response.body) r["_links"]["user"]["href"] end - def get_user_data - user_token = get_token(users_url) - + def discover_application_url(users_url, user_token) users_headers = { "Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => "Bearer #{user_token}" } - # GET to users_url - skype_users_api = UV::HttpEndpoint.new(users_url, {inactivity_timeout: 25000, keepalive: false}) - user_request = skype_users_api.get({ - path: users_url, - headers: users_headers - }) user_response = nil - reactor.run { + ::Libuv.reactor { + skype_users_api = UV::HttpEndpoint.new(users_url, {inactivity_timeout: 25000, keepalive: false}) + user_request = skype_users_api.get({ + path: users_url, + headers: users_headers + }) user_response = user_request.value } full_auth_response = JSON.parse(user_response.body) + full_auth_response["_links"]["applications"]["href"] + end + def discover_meeting_url(apps_url, apps_headers, app_token) + body = {Culture: 'en-us', EndpointId: @client_id, UserAgent: 'Ruby ACA Engine'} + apps_response = nil + ::Libuv.reactor { + skype_apps_api = UV::HttpEndpoint.new(apps_url, {inactivity_timeout: 25000, keepalive: false}) + apps_request = skype_apps_api.post({ + path: apps_url, + headers: apps_headers, + body: body.to_json + }) + apps_response = apps_request.value + } + app = JSON.parse(apps_response.body) + app["_embedded"]["onlineMeetings"]["_links"]["myOnlineMeetings"]["href"] end - def discover_apps_url(user_url) + def create_skype_meeting(base_url, meetings_url, apps_headers, subject) + body = {accessLevel: "Everyone", subject: subject} + url = base_url + meetings_url + meeting_response = nil + ::Libuv.reactor { + skype_auth_api = UV::HttpEndpoint.new(url, {inactivity_timeout: 25000, keepalive: false}) + meeting_request = skype_auth_api.post({path: url, body: body.to_json, headers: apps_headers}) + meeting_response = meeting_request.value + } + ::JSON.parse(meeting_response.body)['joinUrl'] end end From bd987b2a7a4727caf07bde84d525542b67f04269 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 1 Nov 2018 23:30:12 +1000 Subject: [PATCH 0900/1752] (cisco:ui) fix issue where non-user widget interaction would cause state drift --- modules/cisco/collaboration_endpoint/ui.rb | 34 ++++++++++++++----- .../collaboration_endpoint/xapi/response.rb | 4 +-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 7da6173c..b5ee6415 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +load File.join(__dir__, 'xapi', 'response.rb') + module Cisco; end module CollaborationEndpoint; end @@ -95,16 +97,26 @@ def set(id, value) logger.debug { "setting #{id} to #{value}" } - codec.xcommand 'UserInterface Extensions Widget SetValue', - WidgetId: id, Value: value + update = codec.xcommand 'UserInterface Extensions Widget SetValue', + WidgetId: id, Value: value + + # The device does not raise an event when a widget state is changed via + # the API. In these cases, ensure locally tracked state remains valid. + update.then do + # Ensure the value maps to the same as those recevied in responses + value = ::Cisco::CollaborationEndpoint::Xapi::Response.convert value + track id, value + end end # Clear the value associated with a widget. def unset(id) logger.debug { "clearing #{id}" } - codec.xcommand 'UserInterface Extensions Widget UnsetValue', - WidgetId: id + update = codec.xcommand 'UserInterface Extensions Widget UnsetValue', + WidgetId: id + + update.then { track id, nil } end # Set the state of a switch widget. @@ -144,13 +156,17 @@ def on_extensions_widget_action(event) # Alias the module state so that all widgets can be interacted with as if # they were local status vars using []= and [] to set and get current value. - alias track []= - protected :track - # FIXME: the results in []= being enumerated by the funcs API - def []=(id, value) - set id, value + def track(status, value) + # FIXME: as []= is injected by DependancyManager, it's not availble to + # directly alias via `alias track []=`. Look at other options here so + # that the same implementation is garunteed if things change and we + # don't leak internals here. + @__config__.trak(status.to_sym, value) value end + protected :track + # FIXME: this exposes []= as an API method + alias []= set # ------------------------------ diff --git a/modules/cisco/collaboration_endpoint/xapi/response.rb b/modules/cisco/collaboration_endpoint/xapi/response.rb index 72009f9b..f29d911a 100644 --- a/modules/cisco/collaboration_endpoint/xapi/response.rb +++ b/modules/cisco/collaboration_endpoint/xapi/response.rb @@ -64,14 +64,14 @@ def compress(fragment) # @param value [String] the value to convert # @param valuespace [Symbol] the Cisco value space reference # @return the value as an appropriate core datatype - def convert(value, valuespace) + def convert(value, valuespace = nil) parser = PARSERS[valuespace] if parser parser.call(value) else begin Integer(value) - rescue ArgumentError + rescue if truthy? value true elsif falsey? value From 48c91c7babdbd6a5ab65fd6cccff53297555b2cf Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 2 Nov 2018 00:54:47 +1000 Subject: [PATCH 0901/1752] (cisco:ui) simplify tracking of status vars --- modules/cisco/collaboration_endpoint/ui.rb | 34 ++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index b5ee6415..61aa6aaf 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -80,7 +80,7 @@ def on_extensions_panel_clicked(event) logger.debug { "#{id} opened" } - track :__active_panel, id + self[:__active_panel] = id end # FIXME: at the time of writing, the device API does not provide the ability @@ -104,8 +104,7 @@ def set(id, value) # the API. In these cases, ensure locally tracked state remains valid. update.then do # Ensure the value maps to the same as those recevied in responses - value = ::Cisco::CollaborationEndpoint::Xapi::Response.convert value - track id, value + self[id] = ::Cisco::CollaborationEndpoint::Xapi::Response.convert value end end @@ -116,7 +115,7 @@ def unset(id) update = codec.xcommand 'UserInterface Extensions Widget UnsetValue', WidgetId: id - update.then { track id, nil } + update.then { self[id] = nil } end # Set the state of a switch widget. @@ -141,7 +140,7 @@ def on_extensions_widget_action(event) logger.debug { "#{id} #{type}" } # Track values of stateful widgets - track id, value unless ['', :increment, :decrement].include? value + self[id] = value unless ['', :increment, :decrement].include? value # Trigger any bindings defined for the widget action begin @@ -151,22 +150,21 @@ def on_extensions_widget_action(event) end # Provide an event stream for other modules to subscribe to - track :__event_stream, { id: id, type: type, value: value }.freeze + self[:__event_stream] = { id: id, type: type, value: value }.freeze end - # Alias the module state so that all widgets can be interacted with as if - # they were local status vars using []= and [] to set and get current value. - def track(status, value) - # FIXME: as []= is injected by DependancyManager, it's not availble to - # directly alias via `alias track []=`. Look at other options here so - # that the same implementation is garunteed if things change and we - # don't leak internals here. - @__config__.trak(status.to_sym, value) - value + # Allow the all widget state to be interacted with externally as though + # is was local status vars by using []= and [] so set and get. + # FIXME: this cause []= to be exposed as an API method + def []=(status, value) + if caller_locations.map(&:path).include? __FILE__ + # Internal use follows standard behaviour provided by Core::Mixin + @__config__.trak(status.to_sym, value) + # FIXME: setting to nil does not remove from status - need delete + else + set status, value + end end - protected :track - # FIXME: this exposes []= as an API method - alias []= set # ------------------------------ From 5c03e8ab7b188d77ac079a9261d87ed4b50bbe95 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 2 Nov 2018 01:03:17 +1000 Subject: [PATCH 0902/1752] (cisco:ui) push widget state on bind --- modules/cisco/collaboration_endpoint/ui.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 61aa6aaf..03fdbf4d 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -203,6 +203,7 @@ def bind(mod) next unless notify.value subscribe_events yield if block_given? + sync_widget_state end @codec_mod @@ -277,6 +278,21 @@ def clear_events(**opts) end end + # Push the current module state to device widget state. + def sync_widget_state + @__config__.status.each do |key, value| + # Non-widget related status prefixed with `__` + next if key =~ /^__.*/ + + # Map bool values back to :on | :off + if [true, false].include? value + switch key, value + else + set key, value + end + end + end + # Get the event handler for a UI widget interaction. def event_handler(widget_id, event_type) # Handlers are stored keyed on a tuple of [widget_id, event_type] and From 8e3866f73fe0f72a3e9d138d6f57f07f1446c397 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 2 Nov 2018 01:04:14 +1000 Subject: [PATCH 0903/1752] (cisco:ui) toggle state if omitted from switch --- modules/cisco/collaboration_endpoint/ui.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 03fdbf4d..e9a69f4a 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -119,7 +119,7 @@ def unset(id) end # Set the state of a switch widget. - def switch(id, state) + def switch(id, state = !self[id]) value = is_affirmative?(state) ? :on : :off set id, value end From 997b286771efe523babded442cc262478c333969 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 2 Nov 2018 01:21:10 +1000 Subject: [PATCH 0904/1752] (cisco:ui) add support for momentary feedback on buttons --- modules/cisco/collaboration_endpoint/ui.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index e9a69f4a..e898c823 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -125,9 +125,10 @@ def switch(id, state = !self[id]) end # Set the highlight state for a button widget. - def highlight(id, state) + def highlight(id, state = true, momentary: false, time: 500) value = is_affirmative?(state) ? :active : :inactive set id, value + schedule.in(time) { highlight id, !value } if momentary end # Set the text label used on text or spinner widget. From a7507deb355e4cebce96ccf7c461aa217514651b Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 2 Nov 2018 01:21:40 +1000 Subject: [PATCH 0905/1752] (cisco:ui) fix issue where button state would not persist reloads --- modules/cisco/collaboration_endpoint/ui.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index e898c823..1e99a6ba 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -285,9 +285,11 @@ def sync_widget_state # Non-widget related status prefixed with `__` next if key =~ /^__.*/ - # Map bool values back to :on | :off + # Map bool values back to :on|:off or :active|:inactive if [true, false].include? value - switch key, value + # FIXME: the results in an error being logged due to the inital + # type mismatch - need a neater way to handle loss of info + switch(key, value).catch { highlight(key, value) } else set key, value end From be1253230f5a07ac6127e73060c4d85d0fec3780 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 2 Nov 2018 03:53:08 +1000 Subject: [PATCH 0906/1752] (cisco:ui) move type conversion to set method --- modules/cisco/collaboration_endpoint/ui.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 1e99a6ba..9a8bbfb8 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -93,7 +93,14 @@ def on_extensions_panel_clicked(event) # Set the value of a widget. def set(id, value) - return unset id if value.nil? + case value + when nil + return unset id + when true, false + # FIXME: the can result in an error being logged due to the inital + # type mismatch - need a neater way to handle loss of info + return switch(id, value).catch { highlight id, value } + end logger.debug { "setting #{id} to #{value}" } @@ -284,15 +291,7 @@ def sync_widget_state @__config__.status.each do |key, value| # Non-widget related status prefixed with `__` next if key =~ /^__.*/ - - # Map bool values back to :on|:off or :active|:inactive - if [true, false].include? value - # FIXME: the results in an error being logged due to the inital - # type mismatch - need a neater way to handle loss of info - switch(key, value).catch { highlight(key, value) } - else - set key, value - end + set key, value end end From 33bd2d2344a331005ee8280ca1e1279042cb9994 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Fri, 2 Nov 2018 04:16:27 +1000 Subject: [PATCH 0907/1752] (cisco:ui) implement two-way binding --- modules/cisco/collaboration_endpoint/ui.rb | 99 ++++++++++++++++------ 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 9a8bbfb8..646cdbae 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -29,7 +29,7 @@ def on_unload def on_update codec_mod = setting(:codec) || :VidConf ui_layout = setting :cisco_ui_layout - @bindings = setting :cisco_ui_bindings || {} + bindings = setting :cisco_ui_bindings || {} # Allow UI layouts to be stored as JSON if ui_layout.is_a? Hash @@ -45,6 +45,7 @@ def on_update bind(codec_mod) do deploy_extensions 'ACA', ui_layout if ui_layout + bindings.each { |id, config| link_widget id, config } end end @@ -147,12 +148,16 @@ def on_extensions_widget_action(event) logger.debug { "#{id} #{type}" } + id = id.to_sym + type = type.to_sym + # Track values of stateful widgets self[id] = value unless ['', :increment, :decrement].include? value # Trigger any bindings defined for the widget action begin - event_handler(id, type)&.call value + handler = event_handlers.fetch [id, type], nil + handler&.call value rescue => e logger.error "error in binding for #{id}.#{type}: #{e}" end @@ -162,7 +167,7 @@ def on_extensions_widget_action(event) end # Allow the all widget state to be interacted with externally as though - # is was local status vars by using []= and [] so set and get. + # is was local status vars by using []= and [] to set and get. # FIXME: this cause []= to be exposed as an API method def []=(status, value) if caller_locations.map(&:path).include? __FILE__ @@ -205,9 +210,11 @@ def bind(mod) @codec_mod = mod.to_sym clear_events + clear_subscriptions + + @subscriptions = [] - unsubscribe @event_binder if @event_binder - @event_binder = system.subscribe(@codec_mod, :connected) do |notify| + @subscriptions << system.subscribe(@codec_mod, :connected) do |notify| next unless notify.value subscribe_events yield if block_given? @@ -221,8 +228,7 @@ def bind(mod) def unbind logger.debug 'unbinding' - unsubscribe @event_binder if @event_binder - @event_binder = nil + clear_subscriptions clear_events async: true @@ -238,6 +244,15 @@ def codec system[@codec_mod] end + # Push the current module state to the device. + def sync_widget_state + @__config__.status.each do |key, value| + # Non-widget related status prefixed with `__` + next if key =~ /^__.*/ + set key, value + end + end + # Build a list of all callback methods that have been defined. # # Callback methods are denoted being single arity and beginning with `on_`. @@ -286,28 +301,64 @@ def clear_events(**opts) end end - # Push the current module state to device widget state. - def sync_widget_state - @__config__.status.each do |key, value| - # Non-widget related status prefixed with `__` - next if key =~ /^__.*/ - set key, value - end + def clear_subscriptions + @subscriptions&.each { |ref| unsubscribe ref } + @subscriptions = nil end - # Get the event handler for a UI widget interaction. - def event_handler(widget_id, event_type) - # Handlers are stored keyed on a tuple of [widget_id, event_type] and - # lazily built as required. - @event_handlers ||= Hash.new do |h, (id, type)| - config = @bindings.dig id, type - if config - handler = build_handler config - h.store [id, type].freeze, handler + def event_handlers + @events_handlers ||= {} + end + + # Wire up a widget based on a binding target. + def link_widget(id, bindings) + logger.debug { "setting up bindings for #{id}" } + + id = id.to_sym + + if bindings.is_a? String + bindings = [:clicked, :changed, :status].product([bindings]).to_h + end + + bindings.each do |type, target| + type = type.to_sym + + # Status / feedback state binding + if type == :status + case target + # "mod.status" + when String + mod, state = target.split '.' + link_feedback id, mod, state + + # mod => status (provided for compatability with event bindings) + when Hash + mod, state = target.first + link_feedback id, mod, state + + else + logger.warn { "invalid #{type} binding for #{id}" } + end + + # Event binding + else + handler = build_handler target + if handler + event_handlers.store [id, type].freeze, handler + else + logger.warn { "invalid #{type} binding for #{id}" } + end end end + end + + # Bind a widget to another modules status var for feedback. + def link_feedback(id, mod, state) + logger.debug { "linking #{id} state to #{mod}.#{state}" } - @event_handlers[[widget_id, event_type]] + @subscriptions << system.subscribe(mod, state) do |notify| + set id, notify.value + end end # Given the action for a binding, construct the executable event handler. From 9aa4e304fec79bbb2b0ea98a5daf5033f57cf6e1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 2 Nov 2018 12:02:49 +1100 Subject: [PATCH 0908/1752] (aca:exchange meetings) expose the skype URL --- modules/aca/exchange_booking.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index ca04ef13..a7fc86ed 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -674,7 +674,7 @@ def delete_ews_booking(delete_at) meeting_time = Time.parse(meeting.ews_item[:start][:text]) # Remove any meetings that match the start time provided - if meeting_time.to_i == delete_at + if meeting_time.to_i == delete_at # new_booking = meeting.update_item!({ end: Time.now.utc.iso8601.chop }) meeting.delete!(:recycle, send_meeting_cancellations: 'SendOnlyToAll') @@ -760,6 +760,7 @@ def todays_bookings(first=false, skype_exists=false) self[:skype_meeting_pending] = true end set_skype_url = false + self[:skype_meeting_address] = links[0] system[:Skype].set_uri(links[0]) if skype_exists end end From 26f1c6686cbd6aa102c21c12c528243e78a5ec97 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 2 Nov 2018 16:57:52 +1100 Subject: [PATCH 0909/1752] (aca:office365) add skype link extraction --- modules/aca/office_booking.rb | 64 +++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 0abe7525..96da901b 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -123,9 +123,11 @@ def on_update # Skype join button available 2min before the start of a meeting @skype_start_offset = setting(:skype_start_offset) || 120 + @skype_check_offset = setting(:skype_check_offset) || 380 # 5min + 20 seconds # Skype join button not available in the last 8min of a meeting @skype_end_offset = setting(:skype_end_offset) || 480 + @force_skype_extract = setting(:force_skype_extract) # Because restarting the modules results in a 'swipe' of the last read card ignore_first_swipe = true @@ -152,7 +154,7 @@ def on_update # Do we want to use exchange web services to manage bookings if CAN_OFFICE logger.debug "Setting OFFICE" - @office_organiser_location = setting(:office_organiser_location) + @office_organiser_location = setting(:office_organiser_location) @office_client_id = setting(:office_client_id) @office_secret = setting(:office_secret) @office_scope = setting(:office_scope) @@ -403,7 +405,7 @@ def create_meeting(options) req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.to_i req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.to_i - + # TODO:: Catch error for booking failure begin id = make_office_booking req_params @@ -413,7 +415,7 @@ def create_meeting(options) raise e end - + logger.debug { "successfully created booking: #{id}" } "Ok" end @@ -601,12 +603,54 @@ def delete_ews_booking(delete_at) def todays_bookings(response, office_organiser_location) results = [] - response.each{|booking| - # start_time = Time.parse(booking['start']['dateTime']).utc.iso8601[0..18] + 'Z' - # end_time = Time.parse(booking['end']['dateTime']).utc.iso8601[0..18] + 'Z' + + set_skype_url = true if @force_skype_extract + now_int = now.to_i + + response.each{ |booking| if booking['start'].key?("timeZone") - start_time = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']).utc.iso8601 - end_time = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['end']['dateTime']).utc.iso8601 + real_start = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']).utc + real_end = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['end']['dateTime']).utc + start_time = real_start.iso8601 + end_time = real_end.iso8601 + else + # TODO:: this needs review. Does MS ever respond without a timeZone? + real_start = Time.parse(booking['start']['dateTime']).utc + real_end = Time.parse(booking['end']['dateTime']).utc + start_time = real_start.iso8601[0..18] + 'Z' + end_time = real_end.iso8601[0..18] + 'Z' + end + + #logger.debug { "\n\ninspecting booking:\n#{booking.inspect}\n\n" } + # Get body data: booking['body'].values.join("") + + # Extract the skype meeting URL + if set_skype_url + start_integer = real_start.to_i - @skype_check_offset + join_integer = real_start.to_i - @skype_start_offset + end_integer = real_end.to_i - @skype_end_offset + + if now_int > start_integer && now_int < end_integer && booking['body'] + body = booking['body'].values.join('') + match = body.match(/\"pexip\:\/\/(.+?)\"/) + if match + set_skype_url = false + self[:pexip_meeting_uid] = start_integer + self[:pexip_meeting_address] = match[1] + else + links = URI.extract(body).select { |url| url.start_with?('https://meet.lync') } + if links[0].present? + if now_int > join_integer + self[:can_join_skype_meeting] = true + self[:skype_meeting_pending] = true + else + self[:skype_meeting_pending] = true + end + set_skype_url = false + self[:skype_meeting_address] = links[0] + end + end + end end if office_organiser_location == 'attendees' @@ -620,7 +664,7 @@ def todays_bookings(response, office_organiser_location) # Grab the organiser organizer = booking['organizer']['name'] end - + subject = booking['subject'] if booking.key?('sensitivity') && ['private','confidential'].include?(booking['sensitivity']) organizer = "" @@ -642,7 +686,7 @@ def todays_bookings(response, office_organiser_location) def log(msg) STDERR.puts msg logger.info msg - STDERR.flush + STDERR.flush end # ======================================= end From 45f9a00011b1ddc9e1e42ac5670073c53047699f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 3 Nov 2018 07:33:01 +1000 Subject: [PATCH 0910/1752] (cisco:ce) add dtmf method for all device variants --- modules/cisco/collaboration_endpoint/room_kit.rb | 5 +++++ modules/cisco/collaboration_endpoint/sx20.rb | 5 +++++ modules/cisco/collaboration_endpoint/sx80.rb | 6 +++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index d3a04267..5220543c 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -82,6 +82,11 @@ def mic_mute(state = On) command 'Call Accept' => :call_accept, CallId_: Integer command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer + + command 'Call DTMFSend' => :dtmf_send, + DTMFString: String, + CallId_: (0..65534) + command 'Dial' => :dial, Number: String, Protocol_: [:H320, :H323, :Sip, :Spark], diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 5a95ef4f..5e01812c 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -77,6 +77,11 @@ def mic_mute(state = On) command 'Call Accept' => :call_accept, CallId_: Integer command 'Call Reject' => :call_reject, CallId_: Integer command 'Call Disconnect' => :hangup, CallId_: Integer + + command 'Call DTMFSend' => :dtmf_send, + DTMFString: String, + CallId_: (0..65534) + command 'Dial' => :dial, Number: String, Protocol_: [:H320, :H323, :Sip, :Spark], diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 0ab74581..27019a70 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -84,9 +84,9 @@ def mic_mute(state = On) command 'Call Disconnect' => :hangup, CallId_: Integer command 'Call DTMFSend' => :dtmf_send, - CallId: (0..65534), - DTMFString: String - + DTMFString: String, + CallId_: (0..65534) + command 'Dial' => :dial, Number: String, Protocol_: [:H320, :H323, :Sip, :Spark], From d6149f7ab086eb39300e8a17bd24d6faa8630fbc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 3 Nov 2018 20:16:37 +0000 Subject: [PATCH 0911/1752] [O365 Lib] Added mailbox override --- lib/microsoft/office.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a499ee9e..154a9641 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -691,7 +691,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update - def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney') + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney', mailbox_location_override:nil) # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find_by_email(room_id) || Orchestrator::ControlSystem.find(room_id) @@ -702,6 +702,8 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec endpoint = "/v1.0/users/#{current_user[:email]}/events/#{booking_id}" end + endpoint = "/v1.0/users/#{mailbox_location_override}/events/#{booking_id}" if mailbox_location_override + start_object = ensure_ruby_date(start_param).in_time_zone(timezone) From ad0702cb06cb2f0cf8c0209ba4a3b4e2a997ad72 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 3 Nov 2018 21:17:07 +0000 Subject: [PATCH 0912/1752] Add mailbox location override for update --- lib/microsoft/office.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 154a9641..bc0021d8 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -397,8 +397,12 @@ def get_available_rooms(rooms:, start_param:, end_param:) end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_get - def get_booking(booking_id:, mailbox:) - endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" + def get_booking(booking_id:, mailbox:, icaluid:nil) + if icaluid && booking_id.nil? + endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" + else + endpoint = "/v1.0/users/#{mailbox}/events?$filter=iCalUId eq '#{icaluid}'" + end request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) check_response(request) JSON.parse(request.body) From eac2f8517545316101909f2acb7b41f36588704b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 3 Nov 2018 21:25:28 +0000 Subject: [PATCH 0913/1752] Add mailbox location override for update --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index bc0021d8..66c498d1 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -399,9 +399,9 @@ def get_available_rooms(rooms:, start_param:, end_param:) # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_get def get_booking(booking_id:, mailbox:, icaluid:nil) if icaluid && booking_id.nil? - endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" - else endpoint = "/v1.0/users/#{mailbox}/events?$filter=iCalUId eq '#{icaluid}'" + else + endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" end request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) check_response(request) From c4c42ca8c4e9c1bca701658f3d0ae505beeaee0f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 3 Nov 2018 21:33:35 +0000 Subject: [PATCH 0914/1752] [o365 lib] Set room details in update method --- lib/microsoft/office.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 66c498d1..a82f9c8d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -730,6 +730,10 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec event[:body] = { contentType: 'html', + location: { + displayName: room.name, + locationEmailAddress: room.email + }, content: description } if description From 32ccf86434fe044d6df0cbbf6863436cb500a273 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 3 Nov 2018 21:43:48 +0000 Subject: [PATCH 0915/1752] [o365 lib] Set room details in update method --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a82f9c8d..67261ee9 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -478,6 +478,7 @@ def extract_booking_data(booking, start_param, end_param) # Get some data about the booking booking['title'] = booking['subject'] booking['booking_id'] = booking['id'] + booking['icaluid'] = booking['iCalUId'] # Format the attendees and save the old format new_attendees = [] From 9a236c8160e6da0980c6c12ed52d9a1bd8733e75 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 3 Nov 2018 23:04:41 +0000 Subject: [PATCH 0916/1752] [o365 lib] Fix get method with caluid --- lib/microsoft/office.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 67261ee9..d815e16f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -399,11 +399,16 @@ def get_available_rooms(rooms:, start_param:, end_param:) # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_get def get_booking(booking_id:, mailbox:, icaluid:nil) if icaluid && booking_id.nil? - endpoint = "/v1.0/users/#{mailbox}/events?$filter=iCalUId eq '#{icaluid}'" + + # Build our query to only get bookings within our datetimes + query_hash = {} + query_hash['$filter'] = "iCalUId eq '#{icaluid}'" + endpoint = "/v1.0/users/#{mailbox}/events" + request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated, query: query_hash) else endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" - end request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) + end check_response(request) JSON.parse(request.body) end From 5e93400bc9afed19877a39cf1821e04c3db39415 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 3 Nov 2018 23:17:11 +0000 Subject: [PATCH 0917/1752] [o365 lib] Remove override --- lib/microsoft/office.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index d815e16f..49f2d7bf 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -701,7 +701,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update - def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney', mailbox_location_override:nil) + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney') # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find_by_email(room_id) || Orchestrator::ControlSystem.find(room_id) @@ -712,8 +712,6 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec endpoint = "/v1.0/users/#{current_user[:email]}/events/#{booking_id}" end - endpoint = "/v1.0/users/#{mailbox_location_override}/events/#{booking_id}" if mailbox_location_override - start_object = ensure_ruby_date(start_param).in_time_zone(timezone) From e2ba909d11825ee91a51966198137194ce93d10b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 3 Nov 2018 23:30:35 +0000 Subject: [PATCH 0918/1752] [o365 lib] Remove override --- lib/microsoft/office.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 49f2d7bf..584e39c4 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -732,12 +732,13 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec timeZone: TIMEZONE_MAPPING[timezone.to_sym] } if end_param + event[:location] = { + displayName: room.name, + locationEmailAddress: room.email + } + event[:body] = { contentType: 'html', - location: { - displayName: room.name, - locationEmailAddress: room.email - }, content: description } if description @@ -761,7 +762,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec request = graph_request(request_method: 'patch', endpoint: endpoint, data: event.to_json, password: @delegated) check_response(request) - response = JSON.parse(request.body)['value'] + response = JSON.parse(request.body) end From 54c2a2023c81844272d9a4d2d4940da21a4aade5 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 5 Nov 2018 15:01:44 +1100 Subject: [PATCH 0919/1752] initial commit --- modules/ricoh/d6500.rb | 195 ++++++++++++++++++++++++++++++++++++ modules/ricoh/d6500_spec.rb | 8 ++ 2 files changed, 203 insertions(+) create mode 100644 modules/ricoh/d6500.rb create mode 100644 modules/ricoh/d6500_spec.rb diff --git a/modules/ricoh/d6500.rb b/modules/ricoh/d6500.rb new file mode 100644 index 00000000..fae1878e --- /dev/null +++ b/modules/ricoh/d6500.rb @@ -0,0 +1,195 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Ricoh; end + +class Ricoh::D6500 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 50915 # Need to go through an RS232 gatway + descriptive_name 'Ricoh D6500 Interactive Whiteboard' + generic_name :Display + + # Communication settings + tokenize indicator: "\+", delimiter: "\r" + + default_settings( + id: '02' + ) + + def on_load + self[:volume_min] = 0 + self[:volume_max] = 100 + on_update + end + + def on_update + self[:id] = setting(:id) + end + + def on_unload; end + + def connected; end + + def disconnected + # Disconnected will be called before connect if initial connect fails + schedule.clear + end + + def power(state) + target = is_affirmative?(state) + self[:power_target] = target + + # Execute command + logger.debug { "Target = #{target} and self[:power] = #{self[:power]}" } + if target == On && self[:power] != On + send_cmd("#{COMMANDS[:power]}#{prepend('001')}", name: :power_cmd) + elsif target == Off && self[:power] != Off + send_cmd("#{COMMANDS[:power]}#{prepend('000')}", name: :power_cmd) + end + end + + def power? + send_inq(COMMANDS[:power_inq], name: :power_inq) + end + + INPUTS = { + vga: '000', + hdmi: '001', + hdmi2: '002', + av: '003', + ypbpr: '004', + svideo: '005', + dvi: '006', + display_port: '007', + sdi: '008', + multimedia: '009', + network: '010', + usb: '011' + }.freeze + def switch_to(input) + self[:input_target] = input + input = input.to_sym if input.class == String + send_cmd("#{COMMANDS[:input]}#{prepend(INPUTS[input])}", name: :input_cmd) + end + + def input? + send_inq(COMMANDS[:input_inq], name: :input_inq) + end + + AUDIO_INPUTS = { + audio: '000', + audio2: '001', + hdmi: '002', + hdmi2: '003', + display_port: '004', + sdi: '005', + multimedia: '006' + }.freeze + def switch_audio(input) + self[:audio] = input + input = input.to_sym if input.class == String + send_cmd("#{COMMANDS[:audio]}#{prepend(AUDIO_INPUTS[input])}", name: :audio_cmd) + end + + def audio? + send_inq(COMMANDS[:audio_inq], name: :audio_inq) + end + + def volume(vol) + val = in_range(vol, self[:volume_max], self[:volume_min]) + self[:volume_target] = val + val = val.to_s.rjust(3, '0') + logger.debug("volume = #{prepend(val)}") + send_cmd("#{COMMANDS[:volume]}#{prepend(val)}", name: :volume_cmd) + end + + def volume? + send_inq(COMMANDS[:volume_inq], name: :volume_inq) + end + + def mute(state) + target = is_affirmative?(state) + self[:mute_target] = target + + # Execute command + logger.debug { "Target = #{target} and self[:mute] = #{self[:mute]}" } + if target == On && self[:mute] != On + send_cmd("#{COMMANDS[:mute]}#{prepend('001')}", name: :mute_cmd) + elsif target == Off && self[:mute] != Off + send_cmd("#{COMMANDS[:mute]}#{prepend('000')}", name: :mute_cmd) + end + end + + def mute? + send_inq(COMMANDS[:mute_inq], name: :mute_inq) + end + + COMMANDS = { + power_cmd: '21', + input_cmd: '22', + volume_cmd: '35', + mute_cmd: '36', + audio_cmd: '88', + power_inq: '6C', + input_inq: '6A', + volume_inq: '66', + mute_inq: '67', + audio_inq: '88' + }.freeze + def send_cmd(cmd, options = {}) + req = "3#{get_length(cmd)}3#{self[:id][0]}3#{self[:id][1]}73#{cmd}0D" + logger.debug("tell -- 0x#{req} -- #{options[:name]}") + options[:hex_string] = true + send(req, options) + end + + def send_inq(inq, options = {}) + req = "3#{get_length(cmd)}3#{self[:id][0]}3#{self[:id][1]}67#{cmd}#{prepend('000')}0D" + logger.debug("ask -- 0x#{req} -- #{options[:name]}") + options[:hex_string] = true + send(req, options) + end + + def received(data, deferrable, command) + hex = byte_to_hex(data) + return :success if command.nil? || command[:name].nil? + + case command[:name] + when :power_cmd + self[:power] = self[:power_target] if true + when :power_inq + self[:power] = On if hex == "80000080" + self[:power] = Off if hex == "80010081" + when :input + self[:input] = INPUTS_INQ[hex] + when :volume + self[:volume] = byte_to_hex(data[-2]).to_i(16) + when :mute + self[:mute] = On if hex == "82010083" + self[:mute] = Off if hex == "82010184" + end + + logger.debug { "Received 0x#{hex}\n" } + end + + def get_length(req) + # req only contains value which is 3 bytes + # 2 bytes for id + # 1 byte for cmd type + # 1 byte for cmd code + # 1 byte for delimiter + req.length / 2 + 5 + end + + def prepend(str) + str = str.to_s if str.class != String + joined = '' + str.each_char do |c| + joined += "3#{c}" + end + joined + end +end diff --git a/modules/ricoh/d6500_spec.rb b/modules/ricoh/d6500_spec.rb new file mode 100644 index 00000000..a17ebb3d --- /dev/null +++ b/modules/ricoh/d6500_spec.rb @@ -0,0 +1,8 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +Orchestrator::Testing.mock_device 'Ricoh::D6500' do + exec(:power, true) + .should_send("\x38\x30\x32\x73\x30\x30\x31\x0D") # power on + .responds("\+\x38\x30\x32\x21\r") +end From 98dbaf3c42570354a88ed609478630839bf3d5eb Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 09:06:45 +0000 Subject: [PATCH 0920/1752] [o365 lib] Add new alias for showAs --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 584e39c4..d28c77af 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -484,6 +484,7 @@ def extract_booking_data(booking, start_param, end_param) booking['title'] = booking['subject'] booking['booking_id'] = booking['id'] booking['icaluid'] = booking['iCalUId'] + booking['show_as'] = booking['showAs'] # Format the attendees and save the old format new_attendees = [] From 378110d1a790deba13b633cfa0cb8fe0fe8c31e7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 11:42:55 +0000 Subject: [PATCH 0921/1752] [o365 lib] Add acceptance method --- lib/microsoft/office.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index d28c77af..2b6b4ddc 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -413,6 +413,20 @@ def get_booking(booking_id:, mailbox:, icaluid:nil) JSON.parse(request.body) end + def accept_booking(booking_id:, mailbox:, comment:nil) + endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}/accept" + params = { + request_method: 'post', + endpoint: endpoint, + password: @delegated + } + params[:data] = { "comment": comment } if comment + request = graph_request(params) + end + check_response(request) + JSON.parse(request.body) + + end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_delete def delete_booking(booking_id:, mailbox:) endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" From c2602370da5fb729d545c1fd7ae4404f338043ee Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 11:45:36 +0000 Subject: [PATCH 0922/1752] [o365 lib] Fix syntax --- lib/microsoft/office.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 2b6b4ddc..80ada290 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -420,9 +420,8 @@ def accept_booking(booking_id:, mailbox:, comment:nil) endpoint: endpoint, password: @delegated } - params[:data] = { "comment": comment } if comment + params[:data] = { comment: comment } if comment request = graph_request(params) - end check_response(request) JSON.parse(request.body) From eab39c1a4fbc8b3a455fe3dc54c2d35bc57a3701 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 12:48:04 +0000 Subject: [PATCH 0923/1752] [o365 lib] Fix syntax --- lib/microsoft/office.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 80ada290..dfcb9507 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -263,12 +263,17 @@ def format_contacts(contacts) end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_contacts - def get_contact(owner_email:, contact_email:) + def get_contact(owner_email:, contact_email:nil) endpoint = "/v1.0/users/#{owner_email}/contacts" - query_params = { - '$top': 1, - '$filter': "emailAddresses/any(a:a/address eq '#{contact_email}')" - } + if contact_email + query_params = { + '$top': 1, + '$filter': "emailAddresses/any(a:a/address eq '#{contact_email}')" + } + else + query_params = { '$top': 999 } + end + request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) JSON.parse(request.body)['value'] From cde42d0c20d743eb85c539bc8e81da2bcfda971e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 13:05:51 +0000 Subject: [PATCH 0924/1752] [o365 lib] Timezone fix --- lib/microsoft/office.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index dfcb9507..0e4cf278 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -14,6 +14,7 @@ class ErrorAccessDenied < Error; end class Microsoft::Office TIMEZONE_MAPPING = { "Sydney": "AUS Eastern Standard Time" + "Brisbane": "Australia/Brisbane" } def initialize( client_id:, @@ -599,14 +600,16 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_events - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney') + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney', endpoint_override:nil) description = String(description) attendees = Array(attendees) # Get our room room = Orchestrator::ControlSystem.find(room_id) - if @mailbox_location == 'room' || current_user.nil? + if endpoint_override + endpoint = "/v1.0/users/#{endpoint_override}/events" + elsif @mailbox_location == 'room' || current_user.nil? endpoint = "/v1.0/users/#{room.email}/events" elsif @mailbox_location == 'user' endpoint = "/v1.0/users/#{current_user[:email]}/events" From 1b24ee5d5bbecf6e93a7a29bf20fe1fb4198b397 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 13:06:03 +0000 Subject: [PATCH 0925/1752] [o365 lib] Timezone fix --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 0e4cf278..18a913fb 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -13,7 +13,7 @@ class ErrorAccessDenied < Error; end class Microsoft::Office TIMEZONE_MAPPING = { - "Sydney": "AUS Eastern Standard Time" + "Sydney": "AUS Eastern Standard Time", "Brisbane": "Australia/Brisbane" } def initialize( From 384de47e358b627771b7babe898aafd3039a7587 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 13:47:46 +0000 Subject: [PATCH 0926/1752] [o365 lib] Structural changes --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 18a913fb..4ccd4a4e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -600,7 +600,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_events - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney', endpoint_override:nil) + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML") description = String(description) attendees = Array(attendees) @@ -660,7 +660,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil event = { subject: subject, body: { - contentType: 'html', + contentType: content_type, content: description }, start: { From 6726104e454d1dde72f42bc6b21392452b9aab15 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 16:11:07 +0000 Subject: [PATCH 0927/1752] [o365] Fix syntax] --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4ccd4a4e..afe50b56 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -682,8 +682,8 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil if current_user event[:organizer] = { emailAddress: { - address: current_user.email, - name: current_user.name + address: current_user[:email], + name: current_user[:name] } } else From 043e55c4576aafebff08de333e4eeb34a1b2cd8a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 5 Nov 2018 16:29:26 +0000 Subject: [PATCH 0928/1752] [o365] Fix syntax --- lib/microsoft/office.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index afe50b56..cfeb2fcf 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -787,6 +787,21 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec response = JSON.parse(request.body) end + def check_in_attendee(booking_id:, room_id:, attendee_email:) + booking = self.get_booking(booking_id: booking_id, mailbox: room_id) + new_attendees = [] + booking['attendees'].each do |attendee| + new_attendee = attendee.dup + if new_attendee['emailAddress']['address'] == attendee_email + new_attendee['status']['response'] = 'Accepted' + end + new_attendees.push(new_attendee) + end + endpoint = "/v1.0/users/#{room_id}/events/#{booking_id}" + event = { attendees: new_attendees } + request = graph_request(request_method: 'patch', endpoint: endpoint, data: event.to_json, password: @delegated) + end + # Takes a date of any kind (epoch, string, time object) and returns a time object def ensure_ruby_date(date) From 2ba81900f156eba86aa1dbcb2d12392cbbd2140f Mon Sep 17 00:00:00 2001 From: pkheav Date: Tue, 6 Nov 2018 16:55:16 +1100 Subject: [PATCH 0929/1752] fixes to ricoh display --- modules/ricoh/d6500.rb | 47 +++++++++++++++++-------------------- modules/ricoh/d6500_spec.rb | 16 +++++++++++-- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/modules/ricoh/d6500.rb b/modules/ricoh/d6500.rb index fae1878e..c2af1c8f 100644 --- a/modules/ricoh/d6500.rb +++ b/modules/ricoh/d6500.rb @@ -13,7 +13,7 @@ class Ricoh::D6500 generic_name :Display # Communication settings - tokenize indicator: "\+", delimiter: "\r" + tokenize delimiter: "\r" default_settings( id: '02' @@ -45,9 +45,9 @@ def power(state) # Execute command logger.debug { "Target = #{target} and self[:power] = #{self[:power]}" } if target == On && self[:power] != On - send_cmd("#{COMMANDS[:power]}#{prepend('001')}", name: :power_cmd) + send_cmd("#{COMMANDS[:power_cmd]}#{prepend('001')}", name: :power_cmd) elsif target == Off && self[:power] != Off - send_cmd("#{COMMANDS[:power]}#{prepend('000')}", name: :power_cmd) + send_cmd("#{COMMANDS[:power_cmd]}#{prepend('000')}", name: :power_cmd) end end @@ -72,7 +72,7 @@ def power? def switch_to(input) self[:input_target] = input input = input.to_sym if input.class == String - send_cmd("#{COMMANDS[:input]}#{prepend(INPUTS[input])}", name: :input_cmd) + send_cmd("#{COMMANDS[:input_cmd]}#{prepend(INPUTS[input])}", name: :input_cmd) end def input? @@ -91,7 +91,7 @@ def input? def switch_audio(input) self[:audio] = input input = input.to_sym if input.class == String - send_cmd("#{COMMANDS[:audio]}#{prepend(AUDIO_INPUTS[input])}", name: :audio_cmd) + send_cmd("#{COMMANDS[:audio_cmd]}#{prepend(AUDIO_INPUTS[input])}", name: :audio_cmd) end def audio? @@ -103,7 +103,7 @@ def volume(vol) self[:volume_target] = val val = val.to_s.rjust(3, '0') logger.debug("volume = #{prepend(val)}") - send_cmd("#{COMMANDS[:volume]}#{prepend(val)}", name: :volume_cmd) + send_cmd("#{COMMANDS[:volume_cmd]}#{prepend(val)}", name: :volume_cmd) end def volume? @@ -117,9 +117,9 @@ def mute(state) # Execute command logger.debug { "Target = #{target} and self[:mute] = #{self[:mute]}" } if target == On && self[:mute] != On - send_cmd("#{COMMANDS[:mute]}#{prepend('001')}", name: :mute_cmd) + send_cmd("#{COMMANDS[:mute_cmd]}#{prepend('001')}", name: :mute_cmd) elsif target == Off && self[:mute] != Off - send_cmd("#{COMMANDS[:mute]}#{prepend('000')}", name: :mute_cmd) + send_cmd("#{COMMANDS[:mute_cmd]}#{prepend('000')}", name: :mute_cmd) end end @@ -140,31 +140,34 @@ def mute? audio_inq: '88' }.freeze def send_cmd(cmd, options = {}) - req = "3#{get_length(cmd)}3#{self[:id][0]}3#{self[:id][1]}73#{cmd}0D" + req = "3#{self[:id][0]}3#{self[:id][1]}73#{cmd}0D" + req = "3#{req.length / 2}#{req}" logger.debug("tell -- 0x#{req} -- #{options[:name]}") options[:hex_string] = true send(req, options) end def send_inq(inq, options = {}) - req = "3#{get_length(cmd)}3#{self[:id][0]}3#{self[:id][1]}67#{cmd}#{prepend('000')}0D" + req = "3#{self[:id][0]}3#{self[:id][1]}67#{inq}#{prepend('000')}0D" + req = "3#{req.length / 2}#{req}" logger.debug("ask -- 0x#{req} -- #{options[:name]}") options[:hex_string] = true send(req, options) end def received(data, deferrable, command) - hex = byte_to_hex(data) - return :success if command.nil? || command[:name].nil? + hex = byte_to_hex(data).upcase + value = hex[-5] + hex[-3] + hex[-1] + logger.debug("value is #{value}") case command[:name] when :power_cmd - self[:power] = self[:power_target] if true + self[:power] = self[:power_target] if hex[-2..-1] == '2B' when :power_inq - self[:power] = On if hex == "80000080" - self[:power] = Off if hex == "80010081" + self[:power] = On if value == '001' + self[:power] = Off if value == '000' when :input - self[:input] = INPUTS_INQ[hex] + self[:input] = INPUTS[value.to_sym] when :volume self[:volume] = byte_to_hex(data[-2]).to_i(16) when :mute @@ -172,16 +175,8 @@ def received(data, deferrable, command) self[:mute] = Off if hex == "82010184" end - logger.debug { "Received 0x#{hex}\n" } - end - - def get_length(req) - # req only contains value which is 3 bytes - # 2 bytes for id - # 1 byte for cmd type - # 1 byte for cmd code - # 1 byte for delimiter - req.length / 2 + 5 + logger.debug("Received 0x#{hex}\n") + :success end def prepend(str) diff --git a/modules/ricoh/d6500_spec.rb b/modules/ricoh/d6500_spec.rb index a17ebb3d..5016c5ed 100644 --- a/modules/ricoh/d6500_spec.rb +++ b/modules/ricoh/d6500_spec.rb @@ -3,6 +3,18 @@ Orchestrator::Testing.mock_device 'Ricoh::D6500' do exec(:power, true) - .should_send("\x38\x30\x32\x73\x30\x30\x31\x0D") # power on - .responds("\+\x38\x30\x32\x21\r") + .should_send("\x38\x30\x32\x73\x21\x30\x30\x31\x0D") # power on + .responds("\x34\x30\x32\+\r") + .expect(status[:power]).to be(true) + + exec(:power?) + .should_send("\x38\x30\x32\x67\x6C\x30\x30\x30\r") # power query + .responds("\x38\x30\x32\x72\x6C\x30\x30\x30\r") + .expect(status[:power]).to be(false) + + exec(:input?) + "\x38\x30\x32\x67\x6A\x30\x30\x30\x0D" + .should_send("\x38\x30\x32\x67\x88\x30\x30\x30\r") # power query + .responds("\x38\x30\x32\x72\x88\x30\x30\x31\r") + .expect(status[:input]).to be('hdmi') end From e142c266fa0586a99c94e945e2ff7a714054e9c2 Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 7 Nov 2018 13:24:02 +1100 Subject: [PATCH 0930/1752] simplified received function --- modules/ricoh/d6500.rb | 43 +++++++++++++++++++------------------ modules/ricoh/d6500_spec.rb | 18 ++++++++++------ 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/modules/ricoh/d6500.rb b/modules/ricoh/d6500.rb index c2af1c8f..aa383de5 100644 --- a/modules/ricoh/d6500.rb +++ b/modules/ricoh/d6500.rb @@ -13,7 +13,7 @@ class Ricoh::D6500 generic_name :Display # Communication settings - tokenize delimiter: "\r" + tokenize delimiter: "\x0D" default_settings( id: '02' @@ -68,10 +68,11 @@ def power? multimedia: '009', network: '010', usb: '011' - }.freeze + } + INPUTS.merge!(INPUTS.invert) def switch_to(input) - self[:input_target] = input input = input.to_sym if input.class == String + self[:input_target] = input send_cmd("#{COMMANDS[:input_cmd]}#{prepend(INPUTS[input])}", name: :input_cmd) end @@ -89,8 +90,8 @@ def input? multimedia: '006' }.freeze def switch_audio(input) - self[:audio] = input input = input.to_sym if input.class == String + self[:audio_target] = input send_cmd("#{COMMANDS[:audio_cmd]}#{prepend(AUDIO_INPUTS[input])}", name: :audio_cmd) end @@ -157,24 +158,24 @@ def send_inq(inq, options = {}) def received(data, deferrable, command) hex = byte_to_hex(data).upcase - value = hex[-5] + hex[-3] + hex[-1] - logger.debug("value is #{value}") - - case command[:name] - when :power_cmd - self[:power] = self[:power_target] if hex[-2..-1] == '2B' - when :power_inq - self[:power] = On if value == '001' - self[:power] = Off if value == '000' - when :input - self[:input] = INPUTS[value.to_sym] - when :volume - self[:volume] = byte_to_hex(data[-2]).to_i(16) - when :mute - self[:mute] = On if hex == "82010083" - self[:mute] = Off if hex == "82010184" + if hex[-2..-1] == '2B' + cmd = command[:name].to_s[/[a-z]+/] + self[cmd] = self[cmd + '_target'] + else + value = hex[-5] + hex[-3] + hex[-1] + case command[:name] + when :power_inq + self[:power] = value == '001' ? On : Off + when :input_inq + self[:input] = INPUTS[value] + when :audio_inq + self[:audio] = AUDIO_INPUTS[value] + when :volume_inq + self[:volume] = value.to_i + when :mute_inq + self[:mute] = value == '001' ? On : Off + end end - logger.debug("Received 0x#{hex}\n") :success end diff --git a/modules/ricoh/d6500_spec.rb b/modules/ricoh/d6500_spec.rb index 5016c5ed..8b25d7f2 100644 --- a/modules/ricoh/d6500_spec.rb +++ b/modules/ricoh/d6500_spec.rb @@ -4,17 +4,21 @@ Orchestrator::Testing.mock_device 'Ricoh::D6500' do exec(:power, true) .should_send("\x38\x30\x32\x73\x21\x30\x30\x31\x0D") # power on - .responds("\x34\x30\x32\+\r") + .responds("\x34\x30\x32\x2B\x0D") .expect(status[:power]).to be(true) exec(:power?) - .should_send("\x38\x30\x32\x67\x6C\x30\x30\x30\r") # power query - .responds("\x38\x30\x32\x72\x6C\x30\x30\x30\r") + .should_send("\x38\x30\x32\x67\x6C\x30\x30\x30\x0D") # power query + .responds("\x38\x30\x32\x72\x6C\x30\x30\x30\x0D") .expect(status[:power]).to be(false) exec(:input?) - "\x38\x30\x32\x67\x6A\x30\x30\x30\x0D" - .should_send("\x38\x30\x32\x67\x88\x30\x30\x30\r") # power query - .responds("\x38\x30\x32\x72\x88\x30\x30\x31\r") - .expect(status[:input]).to be('hdmi') + .should_send("\x38\x30\x32\x67\x6A\x30\x30\x30\r") # input query + .responds("\x38\x30\x32\x72\x6A\x30\x30\x31\x0D") + .expect(status[:input]).to eq(:hdmi) + + exec(:switch_to, 'dvi') + .should_send("\x38\x30\x32\x73\x22\x30\x30\x36\x0D") + .responds("\x34\x30\x32\x2B\x0D") + .expect(status[:input]).to eq(:dvi) end From e6f9017eef384e21b3ce0d1c7e3d13c87e4e2d0a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 7 Nov 2018 09:27:39 +0000 Subject: [PATCH 0931/1752] [o365 lib] Only add contacts with emails --- lib/microsoft/office.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index cfeb2fcf..b02d83f3 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -251,14 +251,16 @@ def get_contacts(owner_email:, q:nil, limit:nil) def format_contacts(contacts) output_contacts = [] contacts.each do |contact| - output_format = {} - output_format[:id] = contact['id'] - output_format[:first_name] = contact['givenName'] - output_format[:last_name] = contact['surname'] - output_format[:phone] = contact['businessPhones'][0] - output_format[:organisation] = contact['companyName'] - output_format[:email] = contact['emailAddresses'][0]['address'] - output_contacts.push(output_format) + if contact.key?('emailAddresses') && contact['emailAddresses'].empty? + output_format = {} + output_format[:id] = contact['id'] + output_format[:first_name] = contact['givenName'] + output_format[:last_name] = contact['surname'] + output_format[:phone] = contact['businessPhones'][0] + output_format[:organisation] = contact['companyName'] + output_format[:email] = contact['emailAddresses'][0]['address'] + output_contacts.push(output_format) + end end output_contacts end From 350ae20cad0088bdd86b005950e83f51df965b02 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 7 Nov 2018 09:32:01 +0000 Subject: [PATCH 0932/1752] [o365 lib] Only add contacts with emails --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b02d83f3..ed802d26 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -251,7 +251,7 @@ def get_contacts(owner_email:, q:nil, limit:nil) def format_contacts(contacts) output_contacts = [] contacts.each do |contact| - if contact.key?('emailAddresses') && contact['emailAddresses'].empty? + if contact.key?('emailAddresses') && !contact['emailAddresses'].empty? output_format = {} output_format[:id] = contact['id'] output_format[:first_name] = contact['givenName'] From 29fb09a50fb2ea3207f499aa7748e60984c5974f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 7 Nov 2018 09:54:40 +0000 Subject: [PATCH 0933/1752] [o365 lib] Only add contacts with emails --- lib/microsoft/office.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index ed802d26..cd53e8cf 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -461,7 +461,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 recurring_bookings.each do |u_id, bookings| is_available = true bookings.each_with_index do |booking, i| - bookings[i] = extract_booking_data(booking, available_from, available_to) + bookings[i] = extract_booking_data(booking, available_from, available_to, u_id) if bookings[i]['free'] == false is_available = false end @@ -480,7 +480,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end - def extract_booking_data(booking, start_param, end_param) + def extract_booking_data(booking, start_param, end_param, room_email) # Create time objects of the start and end for easier use booking_start = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) booking_end = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) @@ -524,9 +524,10 @@ def extract_booking_data(booking, start_param, end_param) # Get the organiser and location data booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} - if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] - booking['room_id'] = booking['locations'][0]['uniqueId'].downcase - end + # if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] + # booking['room_id'] = booking['locations'][0]['uniqueId'].downcase + # end + booking['room_id'] = room_email if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? booking['room_name'] = booking['location']['displayName'] end From 8b209a38aab52284efee6900bc2cf8db8871cf55 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 7 Nov 2018 10:49:09 +0000 Subject: [PATCH 0934/1752] [o365 lib] update accept response --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index cd53e8cf..1e5a2c0d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -431,7 +431,7 @@ def accept_booking(booking_id:, mailbox:, comment:nil) params[:data] = { comment: comment } if comment request = graph_request(params) check_response(request) - JSON.parse(request.body) + return true end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_delete From 4497fc7d50b7a0afa1b81c42d4819615ff7c51bb Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 7 Nov 2018 11:20:02 +0000 Subject: [PATCH 0935/1752] [o365 lib] update accept response --- lib/microsoft/office.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 1e5a2c0d..8a44b7b1 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -726,12 +726,14 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update - def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney') + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney', endpoint_override:nil) # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find_by_email(room_id) || Orchestrator::ControlSystem.find(room_id) - if @mailbox_location == 'room' || current_user.nil? + if endpoint_override + endpoint = "/v1.0/users/#{endpoint_override}/events/#{booking_id}" + elsif mailbox_location == 'room' || current_user.nil? endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" elsif @mailbox_location == 'user' endpoint = "/v1.0/users/#{current_user[:email]}/events/#{booking_id}" From 5076abbe27b1a51575c7f888146f5effa5f1ab48 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 7 Nov 2018 12:15:04 +0000 Subject: [PATCH 0936/1752] [o365 lib] Allow for multiple checkin types --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8a44b7b1..424e2c59 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -792,13 +792,13 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec response = JSON.parse(request.body) end - def check_in_attendee(booking_id:, room_id:, attendee_email:) + def check_in_attendee(booking_id:, room_id:, attendee_email:, response_type:'Accepted') booking = self.get_booking(booking_id: booking_id, mailbox: room_id) new_attendees = [] booking['attendees'].each do |attendee| new_attendee = attendee.dup if new_attendee['emailAddress']['address'] == attendee_email - new_attendee['status']['response'] = 'Accepted' + new_attendee['status']['response'] = response_type end new_attendees.push(new_attendee) end From 1749c9d871109fa849df0e965fa77a5502fe508c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 8 Nov 2018 13:13:31 +0000 Subject: [PATCH 0937/1752] Fix small issue in o365 driver --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 96da901b..abf67b11 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -605,7 +605,7 @@ def todays_bookings(response, office_organiser_location) results = [] set_skype_url = true if @force_skype_extract - now_int = now.to_i + now_int = Time.now.to_i response.each{ |booking| if booking['start'].key?("timeZone") From c38b72834fab4b051b41a4274c9334779104990f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 8 Nov 2018 13:45:34 +0000 Subject: [PATCH 0938/1752] [o365 lib] Add room_id based on attendees --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 424e2c59..a394921d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -527,7 +527,7 @@ def extract_booking_data(booking, start_param, end_param, room_email) # if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] # booking['room_id'] = booking['locations'][0]['uniqueId'].downcase # end - booking['room_id'] = room_email + # booking['room_id'] = room_email if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? booking['room_name'] = booking['location']['displayName'] end From 986e7636701f9edcce53ef757ed48cbdd27ba042 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 8 Nov 2018 13:48:41 +0000 Subject: [PATCH 0939/1752] [o365 lib] Add room_id based on attendees --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a394921d..4a439bfe 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -733,7 +733,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec if endpoint_override endpoint = "/v1.0/users/#{endpoint_override}/events/#{booking_id}" - elsif mailbox_location == 'room' || current_user.nil? + elsif @mailbox_location == 'room' || current_user.nil? endpoint = "/v1.0/users/#{room.email}/events/#{booking_id}" elsif @mailbox_location == 'user' endpoint = "/v1.0/users/#{current_user[:email]}/events/#{booking_id}" From 47b12c97e0f96bef742088b2dbb721163b462f09 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 8 Nov 2018 15:09:28 +0000 Subject: [PATCH 0940/1752] [o365 lib] Add room_id based on attendees --- lib/microsoft/office.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4a439bfe..efc39dc1 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -405,7 +405,7 @@ def get_available_rooms(rooms:, start_param:, end_param:) end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_get - def get_booking(booking_id:, mailbox:, icaluid:nil) + def get_booking(booking_id:, mailbox:, icaluid:nil, extensions=[]) if icaluid && booking_id.nil? # Build our query to only get bookings within our datetimes @@ -603,7 +603,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_events - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML") + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML", extensions:[]) description = String(description) attendees = Array(attendees) @@ -681,6 +681,16 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil isOrganizer: false, attendees: attendees } + exts = [] + extensions.each do |extension| + ext = { + "@odata.type": "microsoft.graph.openTypeExtension", + "extensionName": extension[:name] + } + ext[extension[:key]] = extension[:value] + exts.push(ext) + end + event[:extensions] = exts if current_user event[:organizer] = { From 0be24605debbbd44c298f5f6c01343887c5d26bf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 8 Nov 2018 15:13:11 +0000 Subject: [PATCH 0941/1752] [o365 lib] Add room_id based on attendees --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index efc39dc1..e4edd9f5 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -405,7 +405,7 @@ def get_available_rooms(rooms:, start_param:, end_param:) end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_get - def get_booking(booking_id:, mailbox:, icaluid:nil, extensions=[]) + def get_booking(booking_id:, mailbox:, icaluid:nil, extensions:[]) if icaluid && booking_id.nil? # Build our query to only get bookings within our datetimes From 2b87acc303fbb21be15c86bed7167d29bcfe2e16 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 8 Nov 2018 15:42:57 +0000 Subject: [PATCH 0942/1752] [o365 lib] add extension functionality --- lib/microsoft/office.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e4edd9f5..51bda20e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -411,11 +411,17 @@ def get_booking(booking_id:, mailbox:, icaluid:nil, extensions:[]) # Build our query to only get bookings within our datetimes query_hash = {} query_hash['$filter'] = "iCalUId eq '#{icaluid}'" + query_hash['$expand'] = "extensions($filter=id eq '#{extensions[0]}')" endpoint = "/v1.0/users/#{mailbox}/events" request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated, query: query_hash) else endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" - request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) + if extensions + query_hash = { '$expand': "extensions($filter=id eq '#{extensions[0]}')" } + request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated, query: query_hash) + else + request = graph_request(request_method: 'get', endpoint: endpoint, password: @delegated) + end end check_response(request) JSON.parse(request.body) From df34603ba28eac48f44e685aaf65ce8a1f57a3e5 Mon Sep 17 00:00:00 2001 From: pkheav Date: Tue, 13 Nov 2018 10:14:08 +1100 Subject: [PATCH 0943/1752] updates --- modules/ricoh/d6500.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ricoh/d6500.rb b/modules/ricoh/d6500.rb index aa383de5..2763ba55 100644 --- a/modules/ricoh/d6500.rb +++ b/modules/ricoh/d6500.rb @@ -158,7 +158,7 @@ def send_inq(inq, options = {}) def received(data, deferrable, command) hex = byte_to_hex(data).upcase - if hex[-2..-1] == '2B' + if hex[-2..-1] == '2B' # this means the sent command was valid cmd = command[:name].to_s[/[a-z]+/] self[cmd] = self[cmd + '_target'] else From 5a80d961823a550d342663a259ad51bbb5122d99 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Nov 2018 11:14:16 +1100 Subject: [PATCH 0944/1752] [o365] Update visitor logic --- lib/microsoft/office.rb | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 51bda20e..ae81479d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -449,7 +449,7 @@ def delete_booking(booking_id:, mailbox:) end - def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) @@ -467,7 +467,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 recurring_bookings.each do |u_id, bookings| is_available = true bookings.each_with_index do |booking, i| - bookings[i] = extract_booking_data(booking, available_from, available_to, u_id) + bookings[i] = extract_booking_data(booking, available_from, available_to, u_id, internal_domain) if bookings[i]['free'] == false is_available = false end @@ -486,7 +486,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end - def extract_booking_data(booking, start_param, end_param, room_email) + def extract_booking_data(booking, start_param, end_param, room_email, internal_domain=nil) # Create time objects of the start and end for easier use booking_start = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) booking_end = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) @@ -513,16 +513,28 @@ def extract_booking_data(booking, start_param, end_param, room_email) booking['icaluid'] = booking['iCalUId'] booking['show_as'] = booking['showAs'] + # Check whether this event has external attendees + booking_has_visitors = false + # Format the attendees and save the old format new_attendees = [] booking['attendees'].each do |attendee| + attendee_email = attendee['emailAddress']['address'] if attendee['type'] == 'resource' - booking['room_id'] = attendee['emailAddress']['address'].downcase + booking['room_id'] = attendee_email.downcase else - new_attendees.push({ - email: attendee['emailAddress']['address'], - name: attendee['emailAddress']['name'] - }) + # Check if attendee is external or internal + internal_domain = ENV['INTERNAL_DOMAIN'] || internal_domain + mail_object = Mail::Address.new(attendee_email) + mail_domain = mail_object.domain + booking_has_visitors = true if mail_domain != internal_domain + attendee_object = { + email: attendee_email, + name: attendee['emailAddress']['name'], + visitor: (mail_domain != internal_domain), + organisation: attendee_email.split('@')[1..-1].split('.')[0] + } + new_attendees.push(attendee_object) end end booking['old_attendees'] = booking['attendees'] From f753e649210b6ad1163b1f13b9c72599dc157641 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Nov 2018 11:22:54 +1100 Subject: [PATCH 0945/1752] [o365] Update visitor logic --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index ae81479d..290d495f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -537,6 +537,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d new_attendees.push(attendee_object) end end + booking['visitors'] = booking_has_visitors booking['old_attendees'] = booking['attendees'] booking['attendees'] = new_attendees From 9aea8c124f5c8f96b7737661cd59cd266e0b1259 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Nov 2018 11:27:09 +1100 Subject: [PATCH 0946/1752] [o365] Update visitor logic --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 290d495f..44913695 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -532,7 +532,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d email: attendee_email, name: attendee['emailAddress']['name'], visitor: (mail_domain != internal_domain), - organisation: attendee_email.split('@')[1..-1].split('.')[0] + organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize } new_attendees.push(attendee_object) end From cc5e4c1ade99aabe451612d941d7f066c54368ec Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Nov 2018 11:36:41 +1100 Subject: [PATCH 0947/1752] [o365] Update visitor logic --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 44913695..9bfb6b61 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -241,7 +241,7 @@ def has_user(user_id:) # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_contacts def get_contacts(owner_email:, q:nil, limit:nil) query_params = { '$top': (limit || 1000) } - query_params['$filter'] = "startswith(displayName, #{q}) or startswith(givenName, #{q}) or startswith(surname, #{q}) or emailAddresses/any(a:a/address eq #{q})" if q + query_params['$filter'] = "startswith(displayName, '#{q}') or startswith(givenName, '#{q}') or startswith(surname, '#{q}') or emailAddresses/any(a:a/address eq '#{q}')" if q endpoint = "/v1.0/users/#{owner_email}/contacts" request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) From cd76a86ca9b7284e4d3c42b15ce16f8523dc3aa7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Nov 2018 12:02:24 +1100 Subject: [PATCH 0948/1752] [o365] Update visitor logic --- lib/microsoft/office.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 9bfb6b61..1f94abb6 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -551,6 +551,12 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['room_name'] = booking['location']['displayName'] end + booking_info = User.bucket.get("bookinginfo-#{booking['id']}", quiet: true) + if booking_info + booking['catering'] = booking_info['catering'] if booking_info.key?('catering') + booking['parking'] = booking_info['parking'] if booking_info.key?('parking') + end + booking end From 979b4063a110a373569913ddf272e8066bf151da Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 14 Nov 2018 14:19:23 +1100 Subject: [PATCH 0949/1752] [o365] use icaluid not event ID to save data --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 1f94abb6..171cb133 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -551,7 +551,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['room_name'] = booking['location']['displayName'] end - booking_info = User.bucket.get("bookinginfo-#{booking['id']}", quiet: true) + booking_info = User.bucket.get("bookinginfo-#{booking['iCalUId']}", quiet: true) if booking_info booking['catering'] = booking_info['catering'] if booking_info.key?('catering') booking['parking'] = booking_info['parking'] if booking_info.key?('parking') From 407a87594ceee164485573c788b5e2e4fb9adf28 Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 14 Nov 2018 14:43:34 +1100 Subject: [PATCH 0950/1752] added ricoh projector driver --- modules/ricoh/d6500.rb | 2 +- modules/ricoh/pj_wxl4540.rb | 138 +++++++++++++++++++++++++++++++ modules/ricoh/pj_wxl4540_spec.rb | 39 +++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 modules/ricoh/pj_wxl4540.rb create mode 100644 modules/ricoh/pj_wxl4540_spec.rb diff --git a/modules/ricoh/d6500.rb b/modules/ricoh/d6500.rb index 2763ba55..a45fada9 100644 --- a/modules/ricoh/d6500.rb +++ b/modules/ricoh/d6500.rb @@ -158,6 +158,7 @@ def send_inq(inq, options = {}) def received(data, deferrable, command) hex = byte_to_hex(data).upcase + logger.debug("Received 0x#{hex}\n") if hex[-2..-1] == '2B' # this means the sent command was valid cmd = command[:name].to_s[/[a-z]+/] self[cmd] = self[cmd + '_target'] @@ -176,7 +177,6 @@ def received(data, deferrable, command) self[:mute] = value == '001' ? On : Off end end - logger.debug("Received 0x#{hex}\n") :success end diff --git a/modules/ricoh/pj_wxl4540.rb b/modules/ricoh/pj_wxl4540.rb new file mode 100644 index 00000000..5307fbc1 --- /dev/null +++ b/modules/ricoh/pj_wxl4540.rb @@ -0,0 +1,138 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Ricoh; end + +class Ricoh::PJ_WXL4540 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 50915 # Need to go through an RS232 gatway + descriptive_name 'Ricoh Projector Furud WXL4540' + generic_name :Display + + # Communication settings + tokenize indicator: '=', delimiter: "\x0D" + + def on_load + self[:volume_min] = 0 + self[:volume_max] = 100 + on_update + end + + def on_update + self[:id] = setting(:id) + end + + def on_unload; end + + def connected; end + + def disconnected + # Disconnected will be called before connect if initial connect fails + schedule.clear + end + + def power(state) + target = is_affirmative?(state) + self[:power_target] = target + + # Execute command + logger.debug { "Target = #{target} and self[:power] = #{self[:power]}" } + if target == On && self[:power] != On + send_cmd('PON', name: :power_cmd) + elsif target == Off && self[:power] != Off + send_cmd('POF', name: :power_cmd) + end + end + + def power? + send_cmd('SPS', name: :power_inq) + end + + INPUTS = { + vga: '3', + vga2: '5', + hdmi: '6', + hdmi2: '7', + video: '9' + } + INPUTS.merge!(INPUTS.invert) + def switch_to(input) + input = input.to_sym if input.class == String + send_cmd("INP:#{INPUTS[input]}", name: :input_cmd) + end + + def input? + send_cmd('SIS', name: :input_inq) + end + + ERRORS = { + '0' => 'No Error', + '1' => 'Light source Error', + '4' => 'Fan Speed Error', + '8' => 'Temperature Error', + '16' => 'Color Wheel (Phospher wheel)' + }.freeze + def error? + send_cmd('SER', name: :error_inq) + end + + def mute(state) + target = is_affirmative?(state) + + # Execute command + logger.debug { "Target = #{target} and self[:mute] = #{self[:mute]}" } + if target == On && self[:mute] != On + send_cmd('MUT:1', name: :mute_cmd) + elsif target == Off && self[:mute] != Off + send_cmd('MUT:0', name: :mute_cmd) + end + end + + PRESETS = { + bright: '0', + pc: '1', + movie: '2', + game: '3', + user: '4' + } + PRESETS.merge!(PRESETS.invert) + def preset(mode) + mode = mode.to_sym if mode.class == String + send_cmd("PIC:#{PRESETS[mode]}", name: :preset_cmd) + end + + def send_cmd(cmd, options = {}) + req = "##{cmd}\r" + logger.debug("tell -- #{cmd} -- #{options[:name]}") + send(req, options) + end + + def received(data, deferrable, command) + logger.debug("Received #{data}\n") + value = data[/[^:]+$/] + case command[:name] + when :power_cmd + # assuming that it replies with SC#{id} where id = 0 + # unsure if this is correct + self[:power] = self[:power_target] if value == 'SC0' + when :power_inq + self[:power] = value == '0' ? Off : On + when :input_cmd + self[:input] = INPUTS[value] + when :input_inq + self[:input] = INPUTS[value] + when :preset_cmd + self[:preset] = PRESETS[value] + when :mute_cmd + self[:mute] = value == '1' + when :mute_inq + self[:mute] = value == '1' + when :error_inq + self[:error] = ERRORS[value] + end + :success + end +end diff --git a/modules/ricoh/pj_wxl4540_spec.rb b/modules/ricoh/pj_wxl4540_spec.rb new file mode 100644 index 00000000..7d76d237 --- /dev/null +++ b/modules/ricoh/pj_wxl4540_spec.rb @@ -0,0 +1,39 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +Orchestrator::Testing.mock_device 'Ricoh::PJ_WXL4540' do + exec(:power?) + .should_send("#SPS\x0D") + .responds("=SPS:0\x0D") + .expect(status[:power]).to be(false) + + exec(:power, true) + .should_send("#PON\x0D") # power on + .responds("=PON:SC0\x0D") # not sure if this response is correct + .expect(status[:power]).to be(true) + + exec(:power?) + .should_send("#SPS\x0D") + .responds("=SPS:5\x0D") + .expect(status[:power]).to be(true) + + exec(:input?) + .should_send("#SIS\x0D") + .responds("=SIS:9\x0D") + .expect(status[:input]).to eq(:video) + + exec(:switch_to, 'hdmi') + .should_send("#INP:6\x0D") # input query + .responds("=INP:6\x0D") + .expect(status[:input]).to eq(:hdmi) + + exec(:preset, 'pc') + .should_send("#PIC:1\x0D") + .responds("=PIC:1\x0D") + .expect(status[:preset]).to eq(:pc) + + exec(:error?) + .should_send("#SER\x0D") + .responds("=SER:16\x0D") + .expect(status[:error]).to eq('Color Wheel (Phospher wheel)') +end From deee4d48c5b50a663425a2139fe65afab1d10b32 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 14 Nov 2018 16:12:27 +1100 Subject: [PATCH 0951/1752] (aca:tracking) track some basic stats on requests being made for use with an overview dashboard --- modules/aca/tracking/locate_user.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/aca/tracking/locate_user.rb b/modules/aca/tracking/locate_user.rb index 8e987e14..5389b5ec 100644 --- a/modules/aca/tracking/locate_user.rb +++ b/modules/aca/tracking/locate_user.rb @@ -62,6 +62,11 @@ def on_update @cmx = nil end + self[:domain_controller_posted_at] ||= 0 + self[:radius_controller_posted_at] ||= 0 + self[:meraki_failed_requests] ||= 0 + self[:cmx_failed_requests] ||= 0 + @temporary = Set.new((setting(:temporary_macs) || {}).values) @blacklist = Set.new((setting(:ignore_vendors) || {}).values) @ignore_hosts = Set.new((setting(:ignore_hostnames) || {}).values) @@ -108,6 +113,8 @@ def lookup(*ips) ips.each do |ip, login, domain, hostname| perform_lookup(ip, login, domain, hostname, ttl) end + + self[:domain_controller_posted_at] = Time.now.to_i end # This is used to directly map MAC addresses to usernames @@ -133,6 +140,8 @@ def associate(*macs) logger.print_error(e, "associating MAC #{mac} to #{login}") end end + + self[:radius_controller_posted_at] = Time.now.to_i end # For use with shared desktop computers that anyone can log into @@ -251,6 +260,12 @@ def perform_lookup(ip, login, domain, hostname, ttl) return end end + elsif resp.status != 404 + self[:meraki_failed_requests] += 1 + self[:meraki_failure] = { + time: Time.now.to_i, + code: resp.status + } end end @@ -273,6 +288,12 @@ def perform_lookup(ip, login, domain, hostname, ttl) return end end + elsif resp.status != 204 + self[:cmx_failed_requests] += 1 + self[:cmx_failure] = { + time: Time.now.to_i, + code: resp.status + } end end From 0384cb817d7b339f0a0e8fba7d8810cd2d41309b Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 14 Nov 2018 16:15:46 +1100 Subject: [PATCH 0952/1752] added comment re port --- modules/ricoh/d6500.rb | 2 +- modules/ricoh/pj_wxl4540.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ricoh/d6500.rb b/modules/ricoh/d6500.rb index a45fada9..9e617efc 100644 --- a/modules/ricoh/d6500.rb +++ b/modules/ricoh/d6500.rb @@ -8,7 +8,7 @@ class Ricoh::D6500 include ::Orchestrator::Transcoder # Discovery Information - tcp_port 50915 # Need to go through an RS232 gatway + tcp_port 50915 # This port number is probably wrong descriptive_name 'Ricoh D6500 Interactive Whiteboard' generic_name :Display diff --git a/modules/ricoh/pj_wxl4540.rb b/modules/ricoh/pj_wxl4540.rb index 5307fbc1..ca3f0c2d 100644 --- a/modules/ricoh/pj_wxl4540.rb +++ b/modules/ricoh/pj_wxl4540.rb @@ -8,7 +8,7 @@ class Ricoh::PJ_WXL4540 include ::Orchestrator::Transcoder # Discovery Information - tcp_port 50915 # Need to go through an RS232 gatway + tcp_port 50915 # This port number is probably wrong descriptive_name 'Ricoh Projector Furud WXL4540' generic_name :Display From 503e8210214859462db3e214c773f4935d4a5206 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 15 Nov 2018 08:38:11 +1000 Subject: [PATCH 0953/1752] (tvone:coriomaster) accept either or "window" as window refs --- modules/tv_one/corio_master.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/tv_one/corio_master.rb b/modules/tv_one/corio_master.rb index eb2e4201..c21c5409 100644 --- a/modules/tv_one/corio_master.rb +++ b/modules/tv_one/corio_master.rb @@ -75,15 +75,15 @@ def preset(id) def switch(signal_map) interactions = signal_map.flat_map do |slot, windows| - Array(windows).map do |id| - id = id.to_s[/\d+/].to_i unless id.is_a? Integer - window id, 'Input', slot - end + Array(windows).map { |id| window id, 'Input', slot } end - thread.finally(*interactions) + + thread.finally interactions end def window(id, property, value) + id = id.to_s[/\d+/].to_i unless id.is_a? Integer + set("Window#{id}.#{property}", value).then do self[:windows] = (self[:windows] || {}).deep_merge( :"window#{id}" => { property.downcase.to_sym => value } From a25c14183c7337b4b40d2d5154dc80af0a06a26f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 15 Nov 2018 12:54:01 +1100 Subject: [PATCH 0954/1752] [o365] update checkin functionality --- lib/microsoft/office.rb | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 171cb133..b12fd008 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -827,19 +827,16 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec response = JSON.parse(request.body) end - def check_in_attendee(booking_id:, room_id:, attendee_email:, response_type:'Accepted') - booking = self.get_booking(booking_id: booking_id, mailbox: room_id) - new_attendees = [] - booking['attendees'].each do |attendee| - new_attendee = attendee.dup - if new_attendee['emailAddress']['address'] == attendee_email - new_attendee['status']['response'] = response_type - end - new_attendees.push(new_attendee) - end - endpoint = "/v1.0/users/#{room_id}/events/#{booking_id}" - event = { attendees: new_attendees } - request = graph_request(request_method: 'patch', endpoint: endpoint, data: event.to_json, password: @delegated) + def check_in_attendee(owner_email:, attendee_email:, icaluid:, response_type:'Accepted') + # Do whatever this client requires upon checking + # VisitorEmail.deliver + + # Mark this user as checked in + booking_info = User.bucket.get("bookinginfo-#{icaluid}").dup + booking_info[:check_ins] ||= [] + booking_info[:check_ins].push(attendee_email) + User.bucket.set("bookinginfo-#{icaluid}", booking_info) + return true end From 0fd817cd642de15ae2be83f8587cb613467a672e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 15 Nov 2018 13:23:18 +1100 Subject: [PATCH 0955/1752] [o365] Pass contact details back on checkin --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b12fd008..5288b90b 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -830,13 +830,13 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec def check_in_attendee(owner_email:, attendee_email:, icaluid:, response_type:'Accepted') # Do whatever this client requires upon checking # VisitorEmail.deliver - + user_details = self.get_contact(owner_email:owner_email, contact_email:attendee_email) # Mark this user as checked in booking_info = User.bucket.get("bookinginfo-#{icaluid}").dup booking_info[:check_ins] ||= [] booking_info[:check_ins].push(attendee_email) User.bucket.set("bookinginfo-#{icaluid}", booking_info) - return true + return user_details end From 48877177db515821ee76646cefb667ff6467beae Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 15 Nov 2018 13:55:27 +1100 Subject: [PATCH 0956/1752] (panasonic:he50 camera) add support for basic auth --- modules/panasonic/camera/he50.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/panasonic/camera/he50.rb b/modules/panasonic/camera/he50.rb index 0695bf5d..f3c27af3 100644 --- a/modules/panasonic/camera/he50.rb +++ b/modules/panasonic/camera/he50.rb @@ -49,6 +49,8 @@ def on_update # {near: {zoom: val, pan: val, tilt: val}} @presets = setting(:presets) || {} + @username = setting(:username) + @password = setting(:password) self[:presets] = @presets.keys end @@ -392,6 +394,10 @@ def req(cmd, data, name, options = {}, &blk) else options[:name] = name end + if @password + options[:headers] ||= {} + options[:headers]['authorization'] = [@username, @password] + end request_string = "/cgi-bin/aw_ptz?cmd=%23#{cmd}#{data}&res=1" logger.debug { "requesting #{name}: #{request_string}" } get(request_string, options, &blk) From 54b7f609d7a4e14b1f5ce2231a4c0c12d380eb31 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 15 Nov 2018 13:59:27 +1100 Subject: [PATCH 0957/1752] Add setup and breakdown into booking data check --- lib/microsoft/office.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 5288b90b..e3ae393f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -487,10 +487,20 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end def extract_booking_data(booking, start_param, end_param, room_email, internal_domain=nil) + room = Orchestrator::ControlSystem.find_by_email(room_email) + # Create time objects of the start and end for easier use booking_start = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) booking_end = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) + if room.settings.key?('setup') + booking_start = booking_start - room.settings['setup'].to_i.seconds + end + + if room.settings.key?('breakdown') + booking_end = booking_end + room.settings['breakdown'].to_i.seconds + end + # Check if this means the room is unavailable booking_overlaps_start = booking_start < start_param && booking_end > start_param booking_in_between = booking_start >= start_param && booking_end <= end_param From a56be1c5375383e6be25b35787149b13291c2f2a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 15 Nov 2018 15:00:03 +1100 Subject: [PATCH 0958/1752] [o365] Fix setup and breakdown --- lib/microsoft/office.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e3ae393f..8d82950f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -493,12 +493,14 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking_start = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) booking_end = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) - if room.settings.key?('setup') - booking_start = booking_start - room.settings['setup'].to_i.seconds - end - - if room.settings.key?('breakdown') - booking_end = booking_end + room.settings['breakdown'].to_i.seconds + if room + if room.settings.key?('setup') + booking_start = booking_start - room.settings['setup'].to_i.seconds + end + + if room.settings.key?('breakdown') + booking_end = booking_end + room.settings['breakdown'].to_i.seconds + end end # Check if this means the room is unavailable From 4dcc8d028e30809b54e6e095da15b87f22b288f8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 15 Nov 2018 16:10:45 +1100 Subject: [PATCH 0959/1752] [o365] Dont add setup and breakdown to the actual response --- lib/microsoft/office.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8d82950f..fe5c4d9f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -495,18 +495,18 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d if room if room.settings.key?('setup') - booking_start = booking_start - room.settings['setup'].to_i.seconds + booking_start_with_setup = booking_start - room.settings['setup'].to_i.seconds end if room.settings.key?('breakdown') - booking_end = booking_end + room.settings['breakdown'].to_i.seconds + booking_end_with_setup = booking_end + room.settings['breakdown'].to_i.seconds end end # Check if this means the room is unavailable - booking_overlaps_start = booking_start < start_param && booking_end > start_param - booking_in_between = booking_start >= start_param && booking_end <= end_param - booking_overlaps_end = booking_start < end_param && booking_end > end_param + booking_overlaps_start = booking_start_with_setup < start_param && booking_end_with_setup > start_param + booking_in_between = booking_start_with_setup >= start_param && booking_end_with_setup <= end_param + booking_overlaps_end = booking_start_with_setup < end_param && booking_end_with_setup > end_param if booking_overlaps_start || booking_in_between || booking_overlaps_end booking['free'] = false else From c060302bc6930ebf4b744419837da27e8eaec8b4 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 15 Nov 2018 19:08:10 +1100 Subject: [PATCH 0960/1752] [o365] fix logic issue in extraction --- lib/microsoft/office.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index fe5c4d9f..0b88c314 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -496,10 +496,14 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d if room if room.settings.key?('setup') booking_start_with_setup = booking_start - room.settings['setup'].to_i.seconds + else + booking_start_with_setup = booking_start end if room.settings.key?('breakdown') booking_end_with_setup = booking_end + room.settings['breakdown'].to_i.seconds + else + booking_end_with_setup = booking_end end end From cd2b5a03e426e7b35ed635d53f0938bb137f58ec Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 15 Nov 2018 19:19:32 +1100 Subject: [PATCH 0961/1752] [o365] fix logic issue in extraction --- lib/microsoft/office.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 0b88c314..0c7f7c38 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -508,9 +508,9 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d end # Check if this means the room is unavailable - booking_overlaps_start = booking_start_with_setup < start_param && booking_end_with_setup > start_param - booking_in_between = booking_start_with_setup >= start_param && booking_end_with_setup <= end_param - booking_overlaps_end = booking_start_with_setup < end_param && booking_end_with_setup > end_param + booking_overlaps_start = (booking_start_with_setup || booking_start) < start_param && (booking_end_with_setup || booking_end) > start_param + booking_in_between = (booking_start_with_setup || booking_start) >= start_param && (booking_end_with_setup || booking_end) <= end_param + booking_overlaps_end = (booking_start_with_setup || booking_start) < end_param && (booking_end_with_setup || booking_end) > end_param if booking_overlaps_start || booking_in_between || booking_overlaps_end booking['free'] = false else From 584e56d7ced638d510526c62e5d338054fcff5de Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 15 Nov 2018 21:41:15 +1100 Subject: [PATCH 0962/1752] (aca:tracking) add support for ignoring certain users Users such as SCCM can be an issue --- modules/aca/tracking/locate_user.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/aca/tracking/locate_user.rb b/modules/aca/tracking/locate_user.rb index 5389b5ec..3e78568b 100644 --- a/modules/aca/tracking/locate_user.rb +++ b/modules/aca/tracking/locate_user.rb @@ -31,7 +31,8 @@ class Aca::Tracking::LocateUser }, ignore_hostnames: {}, accept_hostnames: {}, - temporary_macs: {} + temporary_macs: {}, + ignore_users: {} }) def on_load @@ -73,6 +74,7 @@ def on_update @accept_hosts = Set.new((setting(:accept_hostnames) || {}).values) @ignore_byod_hosts = Set.new((setting(:ignore_byod_hosts) || {}).values) @accept_byod_hosts = Set.new((setting(:accept_byod_hosts) || {}).values) + @ignore_users = Set.new((setting(:ignore_users) || {}).values.map(&:downcase)) @warnings ||= {} @filters ||= {} end @@ -111,6 +113,7 @@ def clean_up(vendor_mac) def lookup(*ips) ttl = 10.minutes.ago.to_i ips.each do |ip, login, domain, hostname| + next if @ignore_users.include?(login.downcase) perform_lookup(ip, login, domain, hostname, ttl) end @@ -121,6 +124,7 @@ def lookup(*ips) # Typically from a RADIUS server like MS Network Policy Server def associate(*macs) macs.each do |mac, login, hostname| + next if @ignore_users.include?(login.downcase) begin parts = login.split("\\") login = parts[-1] From 6baaf45075910b52be94fea69e8c74b6de0df81a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 16 Nov 2018 09:22:38 +1100 Subject: [PATCH 0963/1752] (aca:tracking) add helper methods for debugging tracking issues These will be used in a tracking dashboard --- modules/aca/tracking/locate_user.rb | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/modules/aca/tracking/locate_user.rb b/modules/aca/tracking/locate_user.rb index 3e78568b..fb4c7147 100644 --- a/modules/aca/tracking/locate_user.rb +++ b/modules/aca/tracking/locate_user.rb @@ -79,7 +79,7 @@ def on_update @filters ||= {} end - protect_method :clear_warnings, :warnings, :clean_up + protect_method :clear_warnings, :warnings, :clean_up, :macs_assigned_to, :check_ownership_of, :user_list, :remove_user # Provides a list of users and the black listed mac addresses # This allows one to update configuration of these machines @@ -93,6 +93,47 @@ def clear_filters @filters = {} end + # Returns the mac addresses assigned to the current user + def macs_assigned_to(username) + { + wired: ::Aca::Tracking::UserDevices.for_user(username).macs, + wireless: ::Aca::Tracking::UserDevices.for_user("wifi_#{username}").macs, + byod: ::Aca::Tracking::UserDevices.for_user("byod_#{username}").macs + } + end + + # Returns the username currently assigned to a MAC address + def check_ownership_of(mac_address) + ::Aca::Tracking::UserDevices.with_mac(mac_address).first&.username + end + + # Get a list of all the users in the system + def user_list + usernames = Aca::Tracking::UserDevices.by_domain.to_a.map(&:username) + usernames.map! do |name| + if name.start_with?('wifi_') || name.start_with?('byod_') + name = name.split('_', 2)[1] + end + name + end + Set.new(usernames).to_a.sort + end + + # deletes a user from the database + def remove_user(username, prefix = nil) + models = [] + if prefix + models << ::Aca::Tracking::UserDevices.for_user("#{prefix}#{username}") + else + models << ::Aca::Tracking::UserDevices.for_user(username) + models << ::Aca::Tracking::UserDevices.for_user("wifi_#{username}") + models << ::Aca::Tracking::UserDevices.for_user("byod_#{username}") + end + models.select!(&:persisted?) + models.each(&:destroy) + "removed #{models.length} entries" + end + # Removes all the references to a particular vendors mac addresses def clean_up(vendor_mac) count = 0 From 56a4bf5c7646683892418e451c48185a3dbf0ad0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 16 Nov 2018 09:23:29 +1100 Subject: [PATCH 0964/1752] (samsung:md) disconnect from device if there are command issues --- modules/samsung/displays/md_series.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 760ebb8d..543114f3 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -424,6 +424,9 @@ def do_send(command, data = [], options = {}) logger.debug { "Sending to Samsung: #{array_to_str(data)}" } - send(array_to_str(data), options) + send(array_to_str(data), options).catch do |reason| + disconnect + thread.reject(reason) + end end end From f89b0054e03f75f4b05a5901976a69c7d4e33e5c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 16 Nov 2018 12:14:41 +1100 Subject: [PATCH 0965/1752] [o365] Dont assume all bookings have info record --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 0c7f7c38..1d1fa075 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -848,7 +848,7 @@ def check_in_attendee(owner_email:, attendee_email:, icaluid:, response_type:'Ac # VisitorEmail.deliver user_details = self.get_contact(owner_email:owner_email, contact_email:attendee_email) # Mark this user as checked in - booking_info = User.bucket.get("bookinginfo-#{icaluid}").dup + booking_info = (User.bucket.get("bookinginfo-#{icaluid}", quiet: true) || {}).dup booking_info[:check_ins] ||= [] booking_info[:check_ins].push(attendee_email) User.bucket.set("bookinginfo-#{icaluid}", booking_info) From d04169d17c10537e0b3b2e9ee75ce8d58200fad6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 16 Nov 2018 16:21:31 +1100 Subject: [PATCH 0966/1752] [o365] Fix syntax --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 1d1fa075..1e3fa2e8 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -569,8 +569,8 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking_info = User.bucket.get("bookinginfo-#{booking['iCalUId']}", quiet: true) if booking_info - booking['catering'] = booking_info['catering'] if booking_info.key?('catering') - booking['parking'] = booking_info['parking'] if booking_info.key?('parking') + booking['catering'] = booking_info['catering'] if booking_info.key?(:catering) + booking['parking'] = booking_info['parking'] if booking_info.key?(:parking) end booking From 1650b433062f9001ac48fab10d13b06362579aa7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 16 Nov 2018 16:25:04 +1100 Subject: [PATCH 0967/1752] [o365] Fix syntax --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 1e3fa2e8..0ce56a6d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -569,8 +569,8 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking_info = User.bucket.get("bookinginfo-#{booking['iCalUId']}", quiet: true) if booking_info - booking['catering'] = booking_info['catering'] if booking_info.key?(:catering) - booking['parking'] = booking_info['parking'] if booking_info.key?(:parking) + booking['catering'] = booking_info[:catering] if booking_info.key?(:catering) + booking['parking'] = booking_info[:parking] if booking_info.key?(:parking) end booking From ebc4d20e8eca21a477d07c03c836bf36365dbf8d Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 16 Nov 2018 17:01:18 +1100 Subject: [PATCH 0968/1752] (cisco:switch) add a status indicator for displaying on a dashboard --- modules/cisco/switch/meraki_snmp.rb | 5 +++++ modules/cisco/switch/snooping_catalyst.rb | 4 ++++ modules/cisco/switch/snooping_catalyst_snmp.rb | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/modules/cisco/switch/meraki_snmp.rb b/modules/cisco/switch/meraki_snmp.rb index 480da674..2164ffdf 100644 --- a/modules/cisco/switch/meraki_snmp.rb +++ b/modules/cisco/switch/meraki_snmp.rb @@ -95,6 +95,8 @@ def on_update self[:level] = setting(:level) @reserve_time = setting(:reserve_time) || 0 + self[:last_successful_query] ||= 0 + @temp_last_updated = 0 end def on_unload @@ -273,6 +275,8 @@ def query_snooping_bindings (@check_interface - checked).each { |iface| remove_lookup(iface) } self[:reserved] = @reserved_interface.to_a + self[:last_successful_query] = @temp_last_updated + nil end @@ -358,6 +362,7 @@ def query_interface_status remove_interfaces.each { |iface| remove_reserved(iface) } add_interfaces.each { |iface| remove_lookup(iface) } self[:reserved] = @reserved_interface.to_a + @temp_last_updated = Time.now.to_i }.value end diff --git a/modules/cisco/switch/snooping_catalyst.rb b/modules/cisco/switch/snooping_catalyst.rb index 1c3bfa01..5193abc2 100644 --- a/modules/cisco/switch/snooping_catalyst.rb +++ b/modules/cisco/switch/snooping_catalyst.rb @@ -89,6 +89,8 @@ def on_update self[:building] = setting(:building) self[:level] = setting(:level) + self[:last_successful_query] ||= 0 + @reserve_time = setting(:reserve_time) || 0 @snooping ||= [] end @@ -256,6 +258,8 @@ def received(data, resolve, command) self[:reserved] = @reserved_interface.to_a @snooping.clear + self[:last_successful_query] = Time.now.to_i + return :success end diff --git a/modules/cisco/switch/snooping_catalyst_snmp.rb b/modules/cisco/switch/snooping_catalyst_snmp.rb index c27a0538..939ff911 100644 --- a/modules/cisco/switch/snooping_catalyst_snmp.rb +++ b/modules/cisco/switch/snooping_catalyst_snmp.rb @@ -111,6 +111,8 @@ def on_update self[:building] = setting(:building) self[:level] = setting(:level) + self[:last_successful_query] ||= 0 + @reserve_time = setting(:reserve_time) || 0 end @@ -381,6 +383,8 @@ def query_snooping_bindings (@check_interface - checked).each { |iface| remove_lookup(iface) } self[:reserved] = @reserved_interface.to_a + self[:last_successful_query] = Time.now.to_i + nil end From 7325429ea1605b1946539530ce3d21320fda5897 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 16 Nov 2018 17:02:00 +1100 Subject: [PATCH 0969/1752] (aca:tracking) expose wireless systems that are being queried for displaying on a dashboard --- modules/aca/tracking/locate_user.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/tracking/locate_user.rb b/modules/aca/tracking/locate_user.rb index fb4c7147..71b3d050 100644 --- a/modules/aca/tracking/locate_user.rb +++ b/modules/aca/tracking/locate_user.rb @@ -65,7 +65,9 @@ def on_update self[:domain_controller_posted_at] ||= 0 self[:radius_controller_posted_at] ||= 0 + self[:meraki_enabled] = @meraki_enabled self[:meraki_failed_requests] ||= 0 + self[:cmx_enabled] = @cmx_enabled self[:cmx_failed_requests] ||= 0 @temporary = Set.new((setting(:temporary_macs) || {}).values) From cce906248694dd7d826900b35b6c79b4fdf0d8d1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Nov 2018 09:09:26 +1100 Subject: [PATCH 0970/1752] [Lockers] Only get first twenty --- lib/loqit/lockers.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 75301697..1f089601 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -70,18 +70,16 @@ def list_lockers def list_lockers_detailed(start_number:nil, end_number:nil) all_lockers_detailed = [] - puts "STARTING LOCKER GET" if start_number (start_number.to_i..end_number.to_i).each do |num| all_lockers_detailed.push(self.show_locker_status(num.to_s)) end else all_lockers = self.list_lockers - all_lockers.each_with_index do |locker, ind| + all_lockers[0..19].each_with_index do |locker, ind| all_lockers_detailed.push(self.show_locker_status(locker['number'])) end end - puts "FINISHED LOCKER GET" all_lockers_detailed end From 02eb83cc386e9268da4a9a050546f52d45d97e50 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Nov 2018 11:57:06 +1100 Subject: [PATCH 0971/1752] [Lockers] Fix lockers bulk requests --- lib/loqit/lockers.rb | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 1f089601..c4c12c2d 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -56,6 +56,8 @@ def initialize( serialnumber: @serial } } + @queue = Queue.new + Thread.new { process_requests! } end def list_lockers @@ -70,19 +72,26 @@ def list_lockers def list_lockers_detailed(start_number:nil, end_number:nil) all_lockers_detailed = [] + thread = Libuv::Reactor.current + defer = thread.defer if start_number (start_number.to_i..end_number.to_i).each do |num| - all_lockers_detailed.push(self.show_locker_status(num.to_s)) + @queue << [defer, num.to_s] + all_lockers_detailed.push(defer.promise.value) end else all_lockers = self.list_lockers all_lockers[0..19].each_with_index do |locker, ind| - all_lockers_detailed.push(self.show_locker_status(locker['number'])) + @queue << [defer, locker['number']] + all_lockers_detailed.push(defer.promise.value) end end all_lockers_detailed end + def add_to_queue(locker_number) + end + def show_locker_status(locker_number) response = @client.call(:show_locker_status, message: { @@ -129,4 +138,14 @@ def customer_has_locker(user_card) JSON.parse(response) end + def process_requests! + loop do + defer, locker_num = @queue.pop + begin + defer.resolve self.show_locker_status(locker_num) + rescue => e + defer.reject e + end + end + end end \ No newline at end of file From 9618d3428904609f7940382841105b4f54a9b3e8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Nov 2018 13:08:22 +1100 Subject: [PATCH 0972/1752] [Lockers] Fix lockers bulk requests --- lib/loqit/lockers.rb | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index c4c12c2d..b2528089 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -60,6 +60,17 @@ def initialize( Thread.new { process_requests! } end + def new_request(name, *args) + thread = Libuv::Reactor.current + defer = thread.defer + @queue << [defer, name, args] + defer.promise.value + end + + def new_ruby_request(name, *args, &block) + @queue << [block, name, args] + end + def list_lockers response = @client.call(:list_lockers, message: { @@ -72,8 +83,7 @@ def list_lockers def list_lockers_detailed(start_number:nil, end_number:nil) all_lockers_detailed = [] - thread = Libuv::Reactor.current - defer = thread.defer + if start_number (start_number.to_i..end_number.to_i).each do |num| @queue << [defer, num.to_s] @@ -140,9 +150,13 @@ def customer_has_locker(user_card) def process_requests! loop do - defer, locker_num = @queue.pop + defer, name, args = @queue.pop begin - defer.resolve self.show_locker_status(locker_num) + if defer.respond_to? :call + defer.call self.__send__(name, *args) + else + defer.resolve self.__send__(name, *args) + end rescue => e defer.reject e end From f72e8a992af6550d2a1a42ba2a70ec189a10778a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Nov 2018 13:51:43 +1100 Subject: [PATCH 0973/1752] [Lockers] Fix lockers bulk requests --- lib/loqit/lockers.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index b2528089..66a471ae 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -86,14 +86,18 @@ def list_lockers_detailed(start_number:nil, end_number:nil) if start_number (start_number.to_i..end_number.to_i).each do |num| - @queue << [defer, num.to_s] - all_lockers_detailed.push(defer.promise.value) + puts "WORKING ON NUMBER: #{num}" + locker_status = new_request(show_locker_status, locker['number']) + all_lockers_detailed.push(locker_status) + sleep 0.03 end else all_lockers = self.list_lockers - all_lockers[0..19].each_with_index do |locker, ind| - @queue << [defer, locker['number']] - all_lockers_detailed.push(defer.promise.value) + all_lockers[0..19].each_with_index do |locker, ind| + puts "WORKING ON NUMBER: #{num}" + locker_status = new_request(show_locker_status, locker['number']) + all_lockers_detailed.push(locker_status) + sleep 0.03 end end all_lockers_detailed From 73aff771432818567f7b673c6527026d6823b0e2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Nov 2018 13:54:35 +1100 Subject: [PATCH 0974/1752] [Lockers] Fix lockers bulk requests --- lib/loqit/lockers.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 66a471ae..d15c32e1 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -87,7 +87,7 @@ def list_lockers_detailed(start_number:nil, end_number:nil) if start_number (start_number.to_i..end_number.to_i).each do |num| puts "WORKING ON NUMBER: #{num}" - locker_status = new_request(show_locker_status, locker['number']) + locker_status = new_request(show_locker_status, [num.to_s]) all_lockers_detailed.push(locker_status) sleep 0.03 end @@ -95,7 +95,7 @@ def list_lockers_detailed(start_number:nil, end_number:nil) all_lockers = self.list_lockers all_lockers[0..19].each_with_index do |locker, ind| puts "WORKING ON NUMBER: #{num}" - locker_status = new_request(show_locker_status, locker['number']) + locker_status = new_request(show_locker_status, [locker['number']]) all_lockers_detailed.push(locker_status) sleep 0.03 end From e9081f1d8731e065e5e794e6f122aaed7cb01b48 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Nov 2018 13:56:54 +1100 Subject: [PATCH 0975/1752] [Lockers] Fix lockers bulk requests --- lib/loqit/lockers.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index d15c32e1..01351ee6 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -87,7 +87,7 @@ def list_lockers_detailed(start_number:nil, end_number:nil) if start_number (start_number.to_i..end_number.to_i).each do |num| puts "WORKING ON NUMBER: #{num}" - locker_status = new_request(show_locker_status, [num.to_s]) + locker_status = new_request('show_locker_status', [num.to_s]) all_lockers_detailed.push(locker_status) sleep 0.03 end @@ -95,7 +95,7 @@ def list_lockers_detailed(start_number:nil, end_number:nil) all_lockers = self.list_lockers all_lockers[0..19].each_with_index do |locker, ind| puts "WORKING ON NUMBER: #{num}" - locker_status = new_request(show_locker_status, [locker['number']]) + locker_status = new_request('show_locker_status', [locker['number']]) all_lockers_detailed.push(locker_status) sleep 0.03 end From 864a96266f7cdf07f8978089e4eaba2dc1908c00 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 19 Nov 2018 15:06:42 +1100 Subject: [PATCH 0976/1752] [lockers] Add retry code thanks to steve --- lib/loqit/lockers.rb | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 01351ee6..f637bd95 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -37,12 +37,14 @@ def initialize( serial:, wsdl:, log: false, - log_level: :debug + log_level: :debug ) savon_config = { :wsdl => wsdl, - :log => log, - :log_level => log_level + :log => false, + :log_level => :debug, + :open_timeout => 1, + :read_timeout => 1 } @client = Savon.client savon_config @@ -86,18 +88,18 @@ def list_lockers_detailed(start_number:nil, end_number:nil) if start_number (start_number.to_i..end_number.to_i).each do |num| - puts "WORKING ON NUMBER: #{num}" - locker_status = new_request('show_locker_status', [num.to_s]) - all_lockers_detailed.push(locker_status) - sleep 0.03 + # puts "WORKING ON NUMBER: #{num}" + # locker_status = new_request('show_locker_status', [num.to_s]) + all_lockers_detailed.push(self.show_locker_status(num.to_s)) + # sleep 0.1 end else all_lockers = self.list_lockers all_lockers[0..19].each_with_index do |locker, ind| - puts "WORKING ON NUMBER: #{num}" - locker_status = new_request('show_locker_status', [locker['number']]) - all_lockers_detailed.push(locker_status) - sleep 0.03 + # puts "WORKING ON NUMBER: #{num}" + # locker_status = new_request('show_locker_status', [locker['number']]) + all_lockers_detailed.push(self.show_locker_status(locker['number'])) + # sleep 0.1 end end all_lockers_detailed @@ -106,7 +108,7 @@ def list_lockers_detailed(start_number:nil, end_number:nil) def add_to_queue(locker_number) end - def show_locker_status(locker_number) + def show_locker_status(locker_number, retries = 3) response = @client.call(:show_locker_status, message: { lockerNumber: locker_number, @@ -115,6 +117,12 @@ def show_locker_status(locker_number) soap_header: @header ).body[:show_locker_status_response][:return] JSON.parse(response) + rescue Net::OpenTimeout + puts "GOT FAILURE" + puts "RETRIES: #{retries}" + puts "---------------" + retries -= 1 + retry if retries > 0 end def open_locker(locker_number) From 0a9ec03968a9ea20d8064f4054aaa9db1ccc3379 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 26 Nov 2018 12:01:25 +1100 Subject: [PATCH 0977/1752] [o365] Add ignore booking functionality --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 0ce56a6d..aeec9b34 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -449,7 +449,7 @@ def delete_booking(booking_id:, mailbox:) end - def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) @@ -468,7 +468,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 is_available = true bookings.each_with_index do |booking, i| bookings[i] = extract_booking_data(booking, available_from, available_to, u_id, internal_domain) - if bookings[i]['free'] == false + if bookings[i]['free'] == false && bookings[i]['id'] != ignore_booking is_available = false end end From e8cdc3962bcb7ef8bbf6cbc4790e5261c7900947 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 27 Nov 2018 10:32:22 +1100 Subject: [PATCH 0978/1752] (aca:ping) add a basic ping driver for monitoring online status of hardware that isn't controllable --- modules/aca/ping.rb | 35 +++++++++++++++++++++++++++++++++++ modules/aca/ping_spec.rb | 8 ++++++++ 2 files changed, 43 insertions(+) create mode 100755 modules/aca/ping.rb create mode 100755 modules/aca/ping_spec.rb diff --git a/modules/aca/ping.rb b/modules/aca/ping.rb new file mode 100755 index 00000000..a97579b5 --- /dev/null +++ b/modules/aca/ping.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +module Aca; end +class Aca::Ping + # Discovery Information + udp_port 9 + descriptive_name 'Ping Device (ICMP)' + generic_name :Ping + + default_settings({ + ping_every: '2m' + }) + + def on_load + on_update + end + + def on_update + schedule.clear + schedule.every(setting(:ping_every)) { ping_device } + end + + def ping_device + ping = ::UV::Ping.new(remote_address, count: 3) + set_connected_state(ping.ping) + logger.debug { { + host: ping.ip, + pingable: ping.pingable, + warning: ping.warning, + exception: ping.exception + }.inspect } + ping.pingable + end +end diff --git a/modules/aca/ping_spec.rb b/modules/aca/ping_spec.rb new file mode 100755 index 00000000..33ba1afc --- /dev/null +++ b/modules/aca/ping_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +Orchestrator::Testing.mock_device 'Aca::Ping', ip: 'localhost' do + exec(:ping_device) + expect(result).to be(true) + expect(status[:connected]).to be(true) +end From f0f5b0b78ac0b4d9985af832911f2c6ef7986afa Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 27 Nov 2018 13:21:16 +1100 Subject: [PATCH 0979/1752] (tripleplay:xml_rpc) fix documentation link --- modules/tripleplay/xml_rpc.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/tripleplay/xml_rpc.rb b/modules/tripleplay/xml_rpc.rb index eb036fcf..c162e73e 100644 --- a/modules/tripleplay/xml_rpc.rb +++ b/modules/tripleplay/xml_rpc.rb @@ -1,6 +1,6 @@ module Tripleplay; end -# Documentation: https://aca.im/driver_docs/TriplePlay/JSON+XMLRPC+Handler+2.2.0.pdf +# Documentation: https://aca.im/driver_docs/TriplePlay/JSON-XMLRPC-Handler-2.2.0.pdf # default URL: http:// # Settings: none @@ -21,10 +21,10 @@ class Tripleplay::XmlRpc def on_load on_update end - + def on_update end - + def channel(number, box_id) logger.debug { "Changing box#{box_id} to channel #{number}" } make_req(:SelectChannel, box_id.to_i, number.to_i, name: :channel) From f2484f533a65a0d55131a984c66f377e49ce41a7 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 27 Nov 2018 13:21:44 +1100 Subject: [PATCH 0980/1752] (aca:ping) add include for discovery information support --- modules/aca/ping.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/ping.rb b/modules/aca/ping.rb index a97579b5..94f2077c 100755 --- a/modules/aca/ping.rb +++ b/modules/aca/ping.rb @@ -3,6 +3,8 @@ module Aca; end class Aca::Ping + include ::Orchestrator::Constants + # Discovery Information udp_port 9 descriptive_name 'Ping Device (ICMP)' From 8179e2067e7810dbee294a92a7cd03157c185c21 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 27 Nov 2018 13:28:23 +1100 Subject: [PATCH 0981/1752] (aca:ping) add ping_count setting --- modules/aca/ping.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/aca/ping.rb b/modules/aca/ping.rb index 94f2077c..24042569 100755 --- a/modules/aca/ping.rb +++ b/modules/aca/ping.rb @@ -11,7 +11,8 @@ class Aca::Ping generic_name :Ping default_settings({ - ping_every: '2m' + ping_every: '2m', + ping_count: 2 }) def on_load @@ -19,13 +20,15 @@ def on_load end def on_update + @ping_count = setting(:ping_count) || 2 schedule.clear - schedule.every(setting(:ping_every)) { ping_device } + schedule.every(setting(:ping_every) || '2m') { ping_device } end def ping_device - ping = ::UV::Ping.new(remote_address, count: 3) - set_connected_state(ping.ping) + ping = ::UV::Ping.new(remote_address, count: @ping_count) + ping.ping + set_connected_state(ping.pingable) logger.debug { { host: ping.ip, pingable: ping.pingable, From 4cabddabe6227ae406355781b1e94ad998c93903 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 28 Nov 2018 13:56:22 +1100 Subject: [PATCH 0982/1752] (qsys:control) Support named controls with spaces --- modules/qsc/q_sys_control.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/qsc/q_sys_control.rb b/modules/qsc/q_sys_control.rb index 66da2024..c658c4c8 100644 --- a/modules/qsc/q_sys_control.rb +++ b/modules/qsc/q_sys_control.rb @@ -96,24 +96,24 @@ def get_status(control_id, **options) def set_position(control_id, position, ramp_time = nil) if ramp_time - send("cspr #{control_id} #{position} #{ramp_time}\n", wait: false) + send("cspr \"#{control_id}\" #{position} #{ramp_time}\n", wait: false) schedule.in(ramp_time * 1000 + 200) do get_status(control_id) end else - send("csp #{control_id} #{position}\n") + send("csp \"#{control_id}\" #{position}\n") end end def set_value(control_id, value, ramp_time = nil, **options) if ramp_time options[:wait] = false - send("csvr #{control_id} #{value} #{ramp_time}\n", options) + send("csvr \"#{control_id}\" #{value} #{ramp_time}\n", options) schedule.in(ramp_time * 1000 + 200) do get_status(control_id) end else - send("csv #{control_id} #{value}\n", options) + send("csv \"#{control_id}\" #{value}\n", options) end end @@ -137,7 +137,7 @@ def set_string(control_id, text) # Used to trigger dialing etc def trigger(action) logger.debug { "Sending trigger to Qsys: ct #{action}" } - send "ct #{action}\n", wait: false + send "ct \"#{action}\"\n", wait: false end alias preset trigger @@ -181,11 +181,11 @@ def mute_toggle(mute_id, index = nil) end def snapshot(name, index, ramp_time = 1.5) - send "ssl #{name} #{index} #{ramp_time}\n", wait: false + send "ssl \"#{name}\" #{index} #{ramp_time}\n", wait: false end def save_snapshot(name, index) - send "sss #{name} #{index}\n", wait: false + send "sss \"#{name}\" #{index}\n", wait: false end From b19a29c1e91b46a10d53740c8b6db24a3432fe6c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 18:06:05 +1100 Subject: [PATCH 0983/1752] [Google] Remove syntax issues --- modules/aca/google_refresh_booking.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/google_refresh_booking.rb b/modules/aca/google_refresh_booking.rb index 19a1e589..50bc39aa 100644 --- a/modules/aca/google_refresh_booking.rb +++ b/modules/aca/google_refresh_booking.rb @@ -82,11 +82,11 @@ class Aca::GoogleRefreshBooking ews_room: 'room@email.address', # Optional EWS for creating and removing bookings - google_organiser_location: 'attendees' + google_organiser_location: 'attendees', google_client_id: '', google_secret: '', google_redirect_uri: '', - google_scope: 'https://www.googleapis.com/auth/calendar', + google_scope: 'https://www.googleapis.com/auth/calendar' # google_scope: ENV['GOOGLE_APP_SCOPE'], # google_site: ENV["GOOGLE_APP_SITE"], # google_token_url: ENV["GOOGLE_APP_TOKEN_URL"], @@ -334,7 +334,7 @@ def fetch_bookings(*args) authorization = Google::Auth::UserRefreshCredentials.new options - Calendar = Google::Apis::CalendarV3 + #Calendar = Google::Apis::CalendarV3 calendar = Calendar::CalendarService.new calendar.authorization = authorization events = calendar.list_events(system.email) From fc8511da21eb3e0e5e6dd8111a17206352c91c57 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 18:25:33 +1100 Subject: [PATCH 0984/1752] [42i] New towers lib --- lib/fortytwo/it_api.rb | 124 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 lib/fortytwo/it_api.rb diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb new file mode 100644 index 00000000..c94e3239 --- /dev/null +++ b/lib/fortytwo/it_api.rb @@ -0,0 +1,124 @@ +require 'active_support/time' +require 'uv-rays' +require 'json' + +module Fortytwo; end; + +class Fortytwo::ItApi + def initialize( + client_id:, + client_secret:, + auth_domain:, + api_domain: + ) + @api_domain = api_domain + oauth_options = { + site: auth_domain, + token_url: "#{auth_domain}token" + } + @api_client ||= OAuth2::Client.new( + client_id, + client_secret, + oauth_options + ) + end + + def api_request(request_method:, endpoint:, data:nil, query:{}, headers:nil) + headers = Hash(headers) + query = Hash(query) + # Convert our request method to a symbol and our data to a JSON string + request_method = request_method.to_sym + data = data.to_json if !data.nil? && data.class != String + + headers['Authorization'] = "Bearer #{api_token}" + + api_path = "#{@api_domain}#{endpoint}" + + log_api_request(request_method, data, query, headers, api_path) + + + api_options = {inactivity_timeout: 25000, keepalive: false} + + api = UV::HttpEndpoint.new(@api_domain, api_options) + response = api.__send__(request_method, path: api_path, headers: headers, body: data, query: query) + + start_timing = Time.now.to_i + response_value = response.value + end_timing = Time.now.to_i + return response_value + end + + def log_api_request(request_method, data, query, headers, graph_path) + STDERR.puts "--------------NEW GRAPH REQUEST------------" + STDERR.puts "#{request_method} to #{graph_path}" + STDERR.puts "Data:" + STDERR.puts data if data + STDERR.puts "Query:" + STDERR.puts query if query + STDERR.puts "Headers:" + STDERR.puts headers if headers + STDERR.puts '--------------------------------------------' + STDERR.flush + end + + def get_users(q: nil, limit: 100, org_ids:nil, emails:nil) + # If we have a query and the query has at least one space + if q && q.include?(" ") + # Split it into word tokens + queries = q.split(" ") + filter_params = [] + # For each word, create a filtering statement + queries.each do |q| + filter_params.push("(startswith(firstName,'#{q}') or startswith(lastName,'#{q}') or startswith(email,'#{q}'))") + end + # Join these filtering statements using 'or' and add accountEnabled filter + filter_param = filter_params.join(" and ") + elsif q && !q.include?(" ") + filter_param = "startswith(firstName,'#{q}') or startswith(lastName,'#{q}') or startswith(email,'#{q}')" + elsif org_ids + filter_param = "organisation/externalId in ('#{org_ids.join('\',\'')}')" + elsif emails + filter_param = "email in ('#{emails.join('\',\'')}')" + end + query_params = { + '$top': limit, + '$expand': 'organisation' + } + query_params['$filter'] = filter_param if defined? filter_param + query_params.compact! + api_request(request_method: 'get', endpoint: 'user', query: query_params) + end + + def get_orgs(q: nil, limit: 100, ids:nil) + # If we have a query and the query has at least one space + if q && q.include?(" ") + # Split it into word tokens + queries = q.split(" ") + filter_params = [] + # For each word, create a filtering statement + queries.each do |q| + filter_params.push("(startswith(name,'#{q}') or startswith(description,'#{q}') or startswith(url,'#{q}'))") + end + # Join these filtering statements using 'or' and add accountEnabled filter + filter_param = filter_params.join(" and ") + elsif q && !q.include?(" ") + filter_param = "startswith(name,'#{q}') or startswith(description,'#{q}') or startswith(url,'#{q}')" + elsif ids + filter_param = "externalId in ('#{ids.join('\',\'')}')" + end + query_params = { + '$top': limit + } + query_params['$filter'] = filter_param if defined? filter_param + query_params.compact! + api_request(request_method: 'get', endpoint: 'org', query: query_params) + end + + + protected + + def api_token + # For now just get a new token each time. In the future we will store the token and check if it expires + @api_client.client_credentials.get_token.token + end +end From 067987cdac24670d751d66b73bc8f0f8eb9522b3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 18:39:06 +1100 Subject: [PATCH 0985/1752] [42i] New towers lib --- lib/fortytwo/it_api.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index c94e3239..30a38dad 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -86,7 +86,8 @@ def get_users(q: nil, limit: 100, org_ids:nil, emails:nil) } query_params['$filter'] = filter_param if defined? filter_param query_params.compact! - api_request(request_method: 'get', endpoint: 'user', query: query_params) + response = api_request(request_method: 'get', endpoint: 'user', query: query_params) + JSON.parse(response.body)['value'] end def get_orgs(q: nil, limit: 100, ids:nil) @@ -111,7 +112,8 @@ def get_orgs(q: nil, limit: 100, ids:nil) } query_params['$filter'] = filter_param if defined? filter_param query_params.compact! - api_request(request_method: 'get', endpoint: 'org', query: query_params) + response = api_request(request_method: 'get', endpoint: 'org', query: query_params) + JSON.parse(response.body)['value'] end From c765ca33f7acba3c47da789ad64cc64d26b120ff Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 19:02:39 +1100 Subject: [PATCH 0986/1752] [o365] Add delete contact method --- lib/microsoft/office.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index aeec9b34..96206736 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -449,6 +449,16 @@ def delete_booking(booking_id:, mailbox:) end + # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_delete + def delete_contact(contact_id:, mailbox:) + endpoint = "/v1.0/users/#{mailbox}/events/#{contact_id}" + request = graph_request(request_method: 'delete', endpoint: endpoint, password: @delegated) + check_response(request) + 200 + end + + + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) @@ -649,7 +659,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil attendees = Array(attendees) # Get our room - room = Orchestrator::ControlSystem.find(room_id) + room = Orchestrator::ControlSystem.find_by_id(room_id) || Orchestrator::ControlSystem.find_by_email(room_id) if endpoint_override endpoint = "/v1.0/users/#{endpoint_override}/events" From cd7a15ccf62cd949dd38d8ba6a24b42934abff58 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 20:09:30 +1100 Subject: [PATCH 0987/1752] [o365] Addext support --- lib/microsoft/office.rb | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 96206736..77cf10b3 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -459,7 +459,7 @@ def delete_contact(contact_id:, mailbox:) - def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil, extensions:[]) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) @@ -469,15 +469,15 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 # Array of all bookings within our period if bulk - recurring_bookings = bookings_request_by_users(user_id, start_param, end_param) + recurring_bookings = bookings_request_by_users(user_id, start_param, end_param, extensions) else - recurring_bookings = bookings_request_by_user(user_id, start_param, end_param) + recurring_bookings = bookings_request_by_user(user_id, start_param, end_param, extensions) end recurring_bookings.each do |u_id, bookings| is_available = true bookings.each_with_index do |booking, i| - bookings[i] = extract_booking_data(booking, available_from, available_to, u_id, internal_domain) + bookings[i] = extract_booking_data(booking, available_from, available_to, u_id, internal_domain, extensions) if bookings[i]['free'] == false && bookings[i]['id'] != ignore_booking is_available = false end @@ -496,7 +496,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end - def extract_booking_data(booking, start_param, end_param, room_email, internal_domain=nil) + def extract_booking_data(booking, start_param, end_param, room_email, internal_domain=nil, extensions=[]) room = Orchestrator::ControlSystem.find_by_email(room_email) # Create time objects of the start and end for easier use @@ -539,6 +539,16 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['icaluid'] = booking['iCalUId'] booking['show_as'] = booking['showAs'] + if booking.key?('extensions') && !extensions.empty? + booking['extensions'].each do |ext| + if extensions.include?(ext['id']) + ext.each do |ext_key, ext_val| + booking[ext_key] = ext_val if !['@odata.type', 'id','extensionName'].include?(ext_key) + end + end + end + end + # Check whether this event has external attendees booking_has_visitors = false @@ -587,7 +597,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_calendarview - def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week)) + def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now + 1.week), extensions=[]) if user_id.class == Array user_id = user_id[0] end @@ -600,7 +610,9 @@ def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now # Build our query to only get bookings within our datetimes query_hash = {} query_hash['$top'] = "200" - + extensions.each do |ext_name| + query['$expand'] = "Extensions($filter=id eq 'Microsoft.OutlookServices.OpenTypeExtension.#{ext_name}')" + end if not start_param.nil? query_hash['startDateTime'] = start_param query_hash['endDateTime'] = end_param @@ -614,7 +626,7 @@ def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_calendarview - def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.now + 1.week)) + def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.now + 1.week), extensions=[]) # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] @@ -630,6 +642,9 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no startDateTime: start_param, endDateTime: end_param, } + extensions.each do |ext_name| + query['$expand'] = "Extensions($filter=id eq 'Microsoft.OutlookServices.OpenTypeExtension.#{ext_name}')" + end bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) check_response(bulk_response) From 67b56249ce4200ef930d4720d89eaeb77688b473 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 20:17:43 +1100 Subject: [PATCH 0988/1752] [o365] Addext support --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 77cf10b3..dc90ae01 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -541,7 +541,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d if booking.key?('extensions') && !extensions.empty? booking['extensions'].each do |ext| - if extensions.include?(ext['id']) + if extensions.include?("Microsoft.OutlookServices.OpenTypeExtension.#{ext['id']}") ext.each do |ext_key, ext_val| booking[ext_key] = ext_val if !['@odata.type', 'id','extensionName'].include?(ext_key) end From 6326fea7e49841c3153b11ec8bdb9512726a72c0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 20:19:29 +1100 Subject: [PATCH 0989/1752] [o365] Addext support --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index dc90ae01..4df73912 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -541,7 +541,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d if booking.key?('extensions') && !extensions.empty? booking['extensions'].each do |ext| - if extensions.include?("Microsoft.OutlookServices.OpenTypeExtension.#{ext['id']}") + if extensions.map {|e| "Microsoft.OutlookServices.OpenTypeExtension.#{e}"}.include?(ext['id']) ext.each do |ext_key, ext_val| booking[ext_key] = ext_val if !['@odata.type', 'id','extensionName'].include?(ext_key) end From 50554da3fc27fc09503fdcd5d5722dac9a74be23 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 20:26:28 +1100 Subject: [PATCH 0990/1752] [o365] Addext support --- lib/microsoft/office.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4df73912..0acdf0b3 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -560,7 +560,11 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['room_id'] = attendee_email.downcase else # Check if attendee is external or internal - internal_domain = ENV['INTERNAL_DOMAIN'] || internal_domain + if booking.key?('owner') + Mail::Address.new(booking.key?('owner')).domain + else + internal_domain = ENV['INTERNAL_DOMAIN'] || internal_domain + end mail_object = Mail::Address.new(attendee_email) mail_domain = mail_object.domain booking_has_visitors = true if mail_domain != internal_domain From 0641538bd55f3f5db03084c3d5da3e7db3a8c5dd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 20:28:02 +1100 Subject: [PATCH 0991/1752] [o365] Addext support --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 0acdf0b3..f14c304e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -561,7 +561,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d else # Check if attendee is external or internal if booking.key?('owner') - Mail::Address.new(booking.key?('owner')).domain + internal_domain = Mail::Address.new(booking.key?('owner')).domain else internal_domain = ENV['INTERNAL_DOMAIN'] || internal_domain end From acc8662ec194d52520a21944eb255bd3a40afca6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 20:32:21 +1100 Subject: [PATCH 0992/1752] [o365] Addext support --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f14c304e..69f12862 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -561,7 +561,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d else # Check if attendee is external or internal if booking.key?('owner') - internal_domain = Mail::Address.new(booking.key?('owner')).domain + internal_domain = Mail::Address.new(booking['owner']).domain else internal_domain = ENV['INTERNAL_DOMAIN'] || internal_domain end From b26d66cb86adcf439a026940142565d2a6e7ab24 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 20:50:34 +1100 Subject: [PATCH 0993/1752] [o365] Addext support --- lib/microsoft/office.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 69f12862..e92a624e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -757,7 +757,9 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil "@odata.type": "microsoft.graph.openTypeExtension", "extensionName": extension[:name] } - ext[extension[:key]] = extension[:value] + extension[:values].each do |ext_key, ext_value| + ext[ext_key] = ext_value + end exts.push(ext) end event[:extensions] = exts From 809521eea324580c8477d1219c24ccbfeab7350e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 29 Nov 2018 20:28:01 +1000 Subject: [PATCH 0994/1752] (cisco:ui) fix error if no bindings are defined --- modules/cisco/collaboration_endpoint/ui.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/ui.rb b/modules/cisco/collaboration_endpoint/ui.rb index 646cdbae..c3f3031e 100644 --- a/modules/cisco/collaboration_endpoint/ui.rb +++ b/modules/cisco/collaboration_endpoint/ui.rb @@ -28,8 +28,8 @@ def on_unload def on_update codec_mod = setting(:codec) || :VidConf - ui_layout = setting :cisco_ui_layout - bindings = setting :cisco_ui_bindings || {} + ui_layout = setting(:cisco_ui_layout) + bindings = setting(:cisco_ui_bindings) || {} # Allow UI layouts to be stored as JSON if ui_layout.is_a? Hash From 465dd99306a7d7eed119b887a50259600d5bac10 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 22:04:45 +1100 Subject: [PATCH 0995/1752] [o365] Read in name from metadata before organiser field --- lib/microsoft/office.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e92a624e..3204d1f6 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -582,7 +582,11 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['attendees'] = new_attendees # Get the organiser and location data - booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} + if booking.key?('owner') && booking.key?('owner_name') + booking['organizer'] = { name: booking['owner_name'], email: booking['owner']} + else + booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} + end # if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] # booking['room_id'] = booking['locations'][0]['uniqueId'].downcase # end From 8abd94136fa41e922e6baa465f6f9b6cf9442541 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 22:59:24 +1100 Subject: [PATCH 0996/1752] [o365] Read in name from metadata before organiser field --- lib/microsoft/office.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 3204d1f6..5279f14b 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -459,7 +459,7 @@ def delete_contact(contact_id:, mailbox:) - def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil, extensions:[]) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil, extensions:[], custom_query:[]) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) @@ -469,7 +469,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 # Array of all bookings within our period if bulk - recurring_bookings = bookings_request_by_users(user_id, start_param, end_param, extensions) + recurring_bookings = bookings_request_by_users(user_id, start_param, end_param, extensions, custom_query) else recurring_bookings = bookings_request_by_user(user_id, start_param, end_param, extensions) end @@ -634,7 +634,7 @@ def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_calendarview - def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.now + 1.week), extensions=[]) + def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.now + 1.week), extensions=[], custom_query:[]) # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] @@ -653,6 +653,11 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no extensions.each do |ext_name| query['$expand'] = "Extensions($filter=id eq 'Microsoft.OutlookServices.OpenTypeExtension.#{ext_name}')" end + + custom_query.each do |query_key, query_val| + query[query_key] = query_val + end + bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) check_response(bulk_response) From f59dedad13d04779b4d3667c9c2a7e0c5baa1d0c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 29 Nov 2018 23:01:03 +1100 Subject: [PATCH 0997/1752] [o365] Read in name from metadata before organiser field --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 5279f14b..b22a413a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -634,7 +634,7 @@ def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_calendarview - def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.now + 1.week), extensions=[], custom_query:[]) + def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.now + 1.week), extensions=[], custom_query=[]) # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).iso8601.split("+")[0] @@ -657,7 +657,7 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no custom_query.each do |query_key, query_val| query[query_key] = query_val end - + bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) check_response(bulk_response) From aec040b49310fc1ef90d5b8616f57f3b5a4ee96f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 30 Nov 2018 10:57:45 +1100 Subject: [PATCH 0998/1752] [Exchange Booking] Add private booking support --- modules/aca/exchange_booking.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index a7fc86ed..2180cba3 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -785,7 +785,12 @@ def todays_bookings(first=false, skype_exists=false) # Prevent connections handing with TIME_WAIT # cli.ews.connection.httpcli.reset_all - subject = item[:subject][:text] + if ["Private", "Confidential"].inculde?(meeting.sensitivity) + subject = meeting.sensitivity + else + subject = item[:subject][:text] + end + { :Start => start, :End => ending, From 1305a073365fa87352b22c604a5de36462a22b35 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 30 Nov 2018 15:08:52 +1100 Subject: [PATCH 0999/1752] [o365] Add custom location field --- lib/microsoft/office.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b22a413a..9802fd33 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -682,7 +682,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_events - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML", extensions:[]) + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML", extensions:[], location:nil) description = String(description) attendees = Array(attendees) @@ -760,6 +760,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil isOrganizer: false, attendees: attendees } + event[:location][:displayName] = location if location exts = [] extensions.each do |extension| ext = { From 21f2334967d200d65d0c21ba43484287e83d9bd2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 30 Nov 2018 16:11:18 +1100 Subject: [PATCH 1000/1752] [Exchange Bookings] Dont spell like an idiot --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 2180cba3..3658115e 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -785,7 +785,7 @@ def todays_bookings(first=false, skype_exists=false) # Prevent connections handing with TIME_WAIT # cli.ews.connection.httpcli.reset_all - if ["Private", "Confidential"].inculde?(meeting.sensitivity) + if ["Private", "Confidential"].include?(meeting.sensitivity) subject = meeting.sensitivity else subject = item[:subject][:text] From 2f366c14fb43267c0001161ac1b9e5482a2cab03 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 3 Dec 2018 15:13:40 +1100 Subject: [PATCH 1001/1752] Cisco/sx80,roomkit: add in_call status --- modules/cisco/collaboration_endpoint/room_kit.rb | 1 + modules/cisco/collaboration_endpoint/sx80.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 5220543c..78395ac6 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -38,6 +38,7 @@ def connected props[:status] == :Idle || props.include?(:ghost) end self[:calls] = calls + self[:in_call] = calls.present? end end diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 27019a70..4d765cba 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -38,6 +38,7 @@ def connected props[:status] == :Idle || props.include?(:ghost) end self[:calls] = calls + self[:in_call] = calls.present? end end From d7a06f1eec392316f78d66333d99354260de8d73 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 3 Dec 2018 15:31:22 +1100 Subject: [PATCH 1002/1752] (cisoc:ce) fix missing in_call when calls={} --- modules/cisco/collaboration_endpoint/room_kit.rb | 1 + modules/cisco/collaboration_endpoint/sx80.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 78395ac6..934e3805 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -32,6 +32,7 @@ def connected end self[:calls] = {} + self[:in_call] = false register_feedback '/Status/Call' do |call| calls = self[:calls].deep_merge call calls.reject! do |_, props| diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 4d765cba..dc7f75d5 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -32,6 +32,7 @@ def connected end self[:calls] = {} + self[:in_call] = false register_feedback '/Status/Call' do |call| calls = self[:calls].deep_merge call calls.reject! do |_, props| From c937299aa623e662ac0ea61b5e978cdadcd91eb4 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 3 Dec 2018 21:32:27 +1100 Subject: [PATCH 1003/1752] [o365] Add notes to bookings --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 9802fd33..bc0f7206 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -599,6 +599,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d if booking_info booking['catering'] = booking_info[:catering] if booking_info.key?(:catering) booking['parking'] = booking_info[:parking] if booking_info.key?(:parking) + booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) end booking From 55ff7280d93586a974f3615e27e1991de121980a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 12:05:48 +1100 Subject: [PATCH 1004/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack.rb | 48 +++++++------- modules/aca/slack_concierge.rb | 112 ++++++++++++++++----------------- 2 files changed, 74 insertions(+), 86 deletions(-) diff --git a/modules/aca/slack.rb b/modules/aca/slack.rb index 4a889dab..fd8bfc78 100644 --- a/modules/aca/slack.rb +++ b/modules/aca/slack.rb @@ -29,15 +29,11 @@ def on_unload # Message coming in from Slack API def on_message(data) - logger.debug "------------------ Message from Slack API: ------------------" - logger.debug data.inspect - logger.debug "--------------------------------------------------------------" # This should always be set as all messages from the slack client should be replies if data.thread_ts || data.ts user_id = get_user_id(data.thread_ts) || get_user_id(data.ts) - logger.debug "---------------Setting last_message_#{user_id}-----------" - logger.debug data - logger.debug "---------------------------------------------------------" + + # Assuming the user exists (it should always as they must send the first message)_ if !user_id.nil? self["last_message_#{user_id}"] = data end @@ -46,36 +42,38 @@ def on_message(data) # Message from the frontend def send_message(message_text) - logger.debug "------------------ Message from the frontend: ------------------" - logger.debug message_text.inspect - logger.debug "-----------------------------------------------------------------" user = current_user thread_id = get_thread_id(user) + # A thread exists meaning this is not the user's first message if thread_id # Post to the slack channel using the thread ID message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, username: current_user.email, thread_ts: thread_id + # This is the user's first message else + # Post to the slack channel using the thread ID message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, username: current_user.email - # logger.debug "Message from frontend:" - # logger.debug message.to_json + # Store thread id thread_id = message['message']['ts'] User.bucket.set("slack-thread-#{user.id}-#{setting(:building)}", thread_id) User.bucket.set("slack-user-#{thread_id}", user.id) - on_message(message.message) + on_message(message.message) end user.last_message_sent = Time.now.to_i * 1000 user.save! end def get_historic_messages + # Grab the thread ID of the currently logged in user user = current_user thread_id = get_thread_id(user) + # If it exists, they've sent messages before if thread_id - # Get the messages + + # We can't use the client for this for some reason that I can't remember slack_api = UV::HttpEndpoint.new("https://slack.com") req = { token: @client.token, @@ -92,6 +90,7 @@ def get_historic_messages thread_id: thread_id, messages: messages } + # Otherwise just send back nothing else { last_sent: user.last_message_sent, @@ -116,36 +115,31 @@ def get_user_id(thread_id) # Create a realtime WS connection to the Slack servers def create_websocket + # Set our token and other config options ::Slack.configure do |config| - logger.debug "Token:" - logger.debug setting(:slack_api_token) - logger.debug setting(:channel) config.token = setting(:slack_api_token) config.logger = Logger.new(STDOUT) config.logger.level = Logger::INFO fail 'Missing slack api token setting!' unless config.token end - ::Slack::RealTime.configure do |config| - config.concurrency = Slack::RealTime::Concurrency::Libuv - end - @client = ::Slack::RealTime::Client.new - - logger.debug "Created client!!" + # Use Libuv as our concurrency driver + ::Slack::RealTime.configure do |config| + config.concurrency = Slack::RealTime::Concurrency::Libuv + end + # Create the client and set the callback function when a message is received + @client = ::Slack::RealTime::Client.new @client.on :message do |data| - logger.debug "Got message!" begin #@client.typing channel: data.channel on_message(data) rescue Exception => e - logger.debug e.message - logger.debug e.backtrace end end - @client.start! - + # Start the client + @client.start! end end diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 21a7c80d..8398a7e3 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -38,26 +38,45 @@ def send_message(message_text, thread_id) message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, thread_ts: thread_id, username: 'Concierge' end - def update_last_message_read(email) - authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id - user = User.find_by_email(authority_id, email) + def update_last_message_read(email_or_thread) + authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id + user = User.find_by_email(authority_id, email_or_thread) + user = User.find(User.bucket.get("slack-user-#{email_or_thread}", quiet: true)) if user.nil? user.last_message_read = Time.now.to_i * 1000 user.save! end + def get_threads - messages = @client.web_client.channels_history({channel: setting(:channel), oldest: (Time.now - 12.months).to_i, count: 1000})['messages'] - messages.delete_if{ |message| - !((!message.key?('thread_ts') || message['thread_ts'] == message['ts']) && message['subtype'] == 'bot_message') + # Get the messages from far back (when over 1000 we need to paginate) + page_count = 1 + all_messages = @client.web_client.channels_history({channel:setting(:channel), count: 1000})['messages'] + + while (all_messages.length) == (1000 * page_count) + page_count += 1 + all_messages += @client.web_client.channels_history({channel: "CEHDN0QP5", latest: all_messages.last['ts'], count: 1000})['messages'] + end + + # Delete messages that aren't threads ((either has no thread_ts OR thread_ts == ts) AND type == bot_message) + messages = [] + all_messages.each do |message| + messages.push(message) if (!message.key?('thread_ts') || message['thread_ts'] == message['ts']) && message['subtype'] == 'bot_message' } - logger.debug "Processing messages in get_threads" + + # Output count as if this gets > 1000 we need to paginate + + # For every message, grab the user's details out of it messages.each_with_index{|message, i| + # If the message has a username associated (not a status message, etc) + # Then grab the details and put it into the message if message.key?('username') - authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id + authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id user = User.find_by_email(authority_id, messages[i]['email']) messages[i]['email'] = message['username'] messages[i]['name'] = user.name end + + # If the user sending the message exists (this should essentially always be the case) if !user.nil? messages[i]['last_sent'] = user.last_message_sent messages[i]['last_read'] = user.last_message_read @@ -65,11 +84,12 @@ def get_threads messages[i]['last_sent'] = nil messages[i]['last_read'] = nil end + # update_last_message_read(messages[i]['email']) messages[i]['replies'] = get_message(message['ts']) } - logger.debug "Finished processing messages in get_threads" - logger.debug messages[0] + + # Bind the frontend to the messages self["threads"] = messages end @@ -91,86 +111,60 @@ def get_thread(thread_id) return nil end - def update_read_time(thread_id) - user = User.find(User.bucket.get("slack-user-#{thread_id}", quiet: true)) - user.last_message_read = Time.now.to_i * 1000 - user.save! - end protected # Create a realtime WS connection to the Slack servers def create_websocket - logger.debug "Creating websocket" - + # Set our token and other config options ::Slack.configure do |config| config.token = setting(:slack_api_token) - # config.logger = logger config.logger = Logger.new(STDOUT) config.logger.level = Logger::INFO fail 'Missing slack api token setting!' unless config.token end - logger.debug "Configured slack" - + # Use Libuv as our concurrency driver ::Slack::RealTime.configure do |config| - config.concurrency = Slack::RealTime::Concurrency::Libuv + config.concurrency = Slack::RealTime::Concurrency::Libuv end - logger.debug "Configured slack concurrency" - + # Create the client and set the callback function when a message is received @client = ::Slack::RealTime::Client.new get_threads - logger.debug "Created client!!" @client.on :message do |data| - logger.debug "----------------- Got message! -----------------" - logger.debug data - logger.debug "------------------------------------------------" begin - #@client.typing channel: data.channel # Disregard if we have a subtype key and it's a reply to a message if data.key?('subtype') && data['subtype'] == 'message_replied' next end - user_email = nil - # # This is not a reply - if data.key?('thread_ts') - # if data['username'].include?('(') - # user_email = data['username'].split(' (')[1][0..-2] if data.key?('username') - # end - get_thread(data['ts']) - get_thread(data['thread_ts']) - else - logger.info "Adding thread to binding" - if data['username'].include?('(') - data['name'] = data['username'].split(' (')[0] if data.key?('username') - data['email'] = data['username'].split(' (')[1][0..-2] if data.key?('username') - # user_email = data['email'] - else - data['name'] = data['username'] - end - messages = self["threads"].dup.unshift(data) - self["threads"] = messages - # if user_email - # authority_id = Authority.find_by_domain('uat-book.internationaltowers.com').id - # user = User.find_by_email(authority_id, user_email) - # user.last_message_read = Time.now.to_i * 1000 - # user.save! - # end - logger.debug "Getting threads! " - get_threads - end - + # Ensure that it is a bot_message + if data['subtype'] == 'bot_message' + # We will likely never get thread_ts == ts + # Because when a message event happens, it's before a thread is created + if data.key?('thread_ts') + if data['thread_ts'] == data['ts'] + STDERR.puts "GOT SOMEWHERE WE DIDN'T THINK POSSIBLE!" + STDERR.puts "REWRITE CODE!" + STDERR.flush + end + self["threads"].each_with_index do |thread, i| + if thread['ts'] == data['thread_ts'] + self["threads"][i]['replies'].insert(0,data) + end + end + else + self["threads"].insert(0,data) + end + end rescue Exception => e - logger.debug e.message - logger.debug e.backtrace end end From 08dd9d4c0e5b0b33fb298c4c48e2e45c0cc14154 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 12:26:26 +1100 Subject: [PATCH 1005/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 8398a7e3..b759df1c 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -61,7 +61,7 @@ def get_threads messages = [] all_messages.each do |message| messages.push(message) if (!message.key?('thread_ts') || message['thread_ts'] == message['ts']) && message['subtype'] == 'bot_message' - } + end # Output count as if this gets > 1000 we need to paginate From 13cb6d38854edc9c8d00df6976a64f9ad4956f25 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 12:39:23 +1100 Subject: [PATCH 1006/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index b759df1c..1f076efe 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -71,8 +71,8 @@ def get_threads # Then grab the details and put it into the message if message.key?('username') authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id - user = User.find_by_email(authority_id, messages[i]['email']) - messages[i]['email'] = message['username'] + user = User.find_by_email(authority_id, message['username'] ) + messages[i]['email'] = user.email messages[i]['name'] = user.name end From 4ef3022f7242c41470bdf2eb1aeb43dbc6afb089 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 13:09:20 +1100 Subject: [PATCH 1007/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 1f076efe..f2641acd 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -139,13 +139,13 @@ def create_websocket @client.on :message do |data| begin - # Disregard if we have a subtype key and it's a reply to a message - if data.key?('subtype') && data['subtype'] == 'message_replied' - next - end - # Ensure that it is a bot_message - if data['subtype'] == 'bot_message' + logger.info "-----NEW MESSAGE RECEIVED----" + logger.info data.inspect + logger.info "-----------------------------" + + # Ensure that it is a bot_message or slack client reply + if ['bot_message', 'message_replied'].include?(data['subtype']) # We will likely never get thread_ts == ts # Because when a message event happens, it's before a thread is created if data.key?('thread_ts') @@ -156,6 +156,7 @@ def create_websocket end self["threads"].each_with_index do |thread, i| if thread['ts'] == data['thread_ts'] + self["threads"][i]['replies'] ||= [] self["threads"][i]['replies'].insert(0,data) end end From 376738a36d538f4a7a31916e7f512a0e3c9e2ce1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 13:44:05 +1100 Subject: [PATCH 1008/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f2641acd..f5443a84 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -156,11 +156,13 @@ def create_websocket end self["threads"].each_with_index do |thread, i| if thread['ts'] == data['thread_ts'] + data['email'] = data['username'] self["threads"][i]['replies'] ||= [] self["threads"][i]['replies'].insert(0,data) end end else + data['replies'] ||= [] self["threads"].insert(0,data) end end From 3c83a30158f475600422bbc97616517b18c42631 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 14:40:10 +1100 Subject: [PATCH 1009/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f5443a84..0efa2929 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -162,7 +162,7 @@ def create_websocket end end else - data['replies'] ||= [] + data['replies'] ||= {replies: []} self["threads"].insert(0,data) end end From 95f7ea2ed84293c3547621a0034e3479af4c33bd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 14:45:59 +1100 Subject: [PATCH 1010/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 0efa2929..f5443a84 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -162,7 +162,7 @@ def create_websocket end end else - data['replies'] ||= {replies: []} + data['replies'] ||= [] self["threads"].insert(0,data) end end From d3f54281b17918e3b2ea8af0154b84d3ca692172 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:01:20 +1100 Subject: [PATCH 1011/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f5443a84..21f7a9ea 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -94,7 +94,15 @@ def get_threads end def get_message(ts) - messages = @client.web_client.channels_history({channel: setting(:channel), latest: ts, inclusive: true})['messages'][0] + # Get the messages + slack_api = UV::HttpEndpoint.new("https://slack.com") + req = { + token: @client.token, + channel: setting(:channel), + thread_ts: thretsad_id + } + response = slack_api.post(path: 'https://slack.com/api/channels.replies', body: req).value + JSON.parse(response.body)['messages'] end def get_thread(thread_id) From 2a3b1820a88f59a8d716d1b5566e2cb5088998d1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:06:21 +1100 Subject: [PATCH 1012/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 21f7a9ea..8e633978 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -93,13 +93,13 @@ def get_threads self["threads"] = messages end - def get_message(ts) + def get_message(thread_id) # Get the messages slack_api = UV::HttpEndpoint.new("https://slack.com") req = { token: @client.token, channel: setting(:channel), - thread_ts: thretsad_id + thread_ts: thread_id } response = slack_api.post(path: 'https://slack.com/api/channels.replies', body: req).value JSON.parse(response.body)['messages'] From f509632b938b20b74e58314f6e395fd1784bcfce Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:11:48 +1100 Subject: [PATCH 1013/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 8e633978..c30f4d3f 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -170,7 +170,7 @@ def create_websocket end end else - data['replies'] ||= [] + data['replies'] ||= get_message(data['ts']) self["threads"].insert(0,data) end end From 670c5659f993fb2da2da11a0b57589ddb47623c2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:13:48 +1100 Subject: [PATCH 1014/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index c30f4d3f..a4f3af51 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -170,7 +170,7 @@ def create_websocket end end else - data['replies'] ||= get_message(data['ts']) + data['replies'] ||= [data] self["threads"].insert(0,data) end end From 0d3ce1f915e66a49f811a407984be8b0f8fb033a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:20:23 +1100 Subject: [PATCH 1015/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index a4f3af51..340a1438 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -171,6 +171,15 @@ def create_websocket end else data['replies'] ||= [data] + + if data['username'] != 'Concierge' + authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id + user = User.find_by_email(authority_id, data['username']) + if user + data['last_read'] = user.last_message_read + data['last_sent'] = user.last_message_sent + end + end self["threads"].insert(0,data) end end From 93fe27493ecd55385f4a29fc41d904b62f40b120 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:32:03 +1100 Subject: [PATCH 1016/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 340a1438..9a82304a 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -162,13 +162,15 @@ def create_websocket STDERR.puts "REWRITE CODE!" STDERR.flush end - self["threads"].each_with_index do |thread, i| + new_threads = self["threads"].dup + new_threads.each_with_index do |thread, i| if thread['ts'] == data['thread_ts'] data['email'] = data['username'] - self["threads"][i]['replies'] ||= [] - self["threads"][i]['replies'].insert(0,data) + new_threads[i]['replies'] ||= [] + new_threads[i]['replies'].insert(0,data) end end + self["threads"] = new_threads else data['replies'] ||= [data] @@ -180,7 +182,7 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"].insert(0,data) + self["threads"] = self["threads"].insert(0,data) end end From 1f82e7012da7f67818b5f6a670c283426105979a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:34:50 +1100 Subject: [PATCH 1017/1752] [Slack Modules] Fix syntax and logic --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 9a82304a..b9fb878c 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -182,7 +182,7 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"] = self["threads"].insert(0,data) + self["threads"] = self["threads"].dup.insert(0,data) end end From 13da08024aeb2b93845886d746e9c297950ce60f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:41:17 +1100 Subject: [PATCH 1018/1752] [Slack Modules] Attempt fix --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index b9fb878c..f0727a3e 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -172,7 +172,7 @@ def create_websocket end self["threads"] = new_threads else - data['replies'] ||= [data] + data['replies'] ||= [data.dup] if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id From 4653ddc43087b8c1f9df48a8d165dc11978970a5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 15:42:41 +1100 Subject: [PATCH 1019/1752] [Slack Modules] Attempt fix --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f0727a3e..d3194c45 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -182,7 +182,7 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"] = self["threads"].dup.insert(0,data) + self["threads"] = self["threads"].dup.insert(0,data.dup) end end From 2915aa4cdd196e97dd22b84bcadf6787e4a88aff Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 16:00:45 +1100 Subject: [PATCH 1020/1752] [Slack Modules] Attempt fix --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index d3194c45..26af9340 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -153,7 +153,7 @@ def create_websocket logger.info "-----------------------------" # Ensure that it is a bot_message or slack client reply - if ['bot_message', 'message_replied'].include?(data['subtype']) + if ['bot_message'].include?(data['subtype']) # We will likely never get thread_ts == ts # Because when a message event happens, it's before a thread is created if data.key?('thread_ts') From 24ad82197e363942c22059ee63f14c3966b09ea9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 16:05:05 +1100 Subject: [PATCH 1021/1752] [Slack Modules] Attempt fix --- modules/aca/slack_concierge.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 26af9340..dfac878d 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -165,9 +165,9 @@ def create_websocket new_threads = self["threads"].dup new_threads.each_with_index do |thread, i| if thread['ts'] == data['thread_ts'] - data['email'] = data['username'] + data['email'] = data['username'].dup new_threads[i]['replies'] ||= [] - new_threads[i]['replies'].insert(0,data) + new_threads[i]['replies'].insert(0,data.dup) end end self["threads"] = new_threads From 02cb2fece4f43e399937103058b6cfbf0eb6cc4a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 16:12:58 +1100 Subject: [PATCH 1022/1752] [Slack Modules] Attempt fix --- modules/aca/slack_concierge.rb | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index dfac878d..7f5ed3f9 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -154,25 +154,24 @@ def create_websocket # Ensure that it is a bot_message or slack client reply if ['bot_message'].include?(data['subtype']) - # We will likely never get thread_ts == ts - # Because when a message event happens, it's before a thread is created + + # If this is a reply (has a thread_ts field) if data.key?('thread_ts') - if data['thread_ts'] == data['ts'] - STDERR.puts "GOT SOMEWHERE WE DIDN'T THINK POSSIBLE!" - STDERR.puts "REWRITE CODE!" - STDERR.flush - end - new_threads = self["threads"].dup + + # Duplicate the current threads array so we don't have ref issues + new_threads = self["threads"].clone + + # Loop through the array and add user data new_threads.each_with_index do |thread, i| + # If the ID of the looped message equals the new message thread ID if thread['ts'] == data['thread_ts'] - data['email'] = data['username'].dup - new_threads[i]['replies'] ||= [] - new_threads[i]['replies'].insert(0,data.dup) + data['email'] = data['username'] + new_threads[i]['replies'].insert(0, data.clone) end end self["threads"] = new_threads else - data['replies'] ||= [data.dup] + data['replies'] ||= [data.clone] if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id @@ -182,7 +181,7 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"] = self["threads"].dup.insert(0,data.dup) + self["threads"] = self["threads"].clone.insert(0, data.clone) end end From dcbbfe00b7ac03e675281487a244c72488873a66 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 16:29:21 +1100 Subject: [PATCH 1023/1752] [Slack Modules] Attempt fix --- modules/aca/slack_concierge.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 7f5ed3f9..e0e76c0c 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -159,19 +159,20 @@ def create_websocket if data.key?('thread_ts') # Duplicate the current threads array so we don't have ref issues - new_threads = self["threads"].clone + new_threads = self["threads"].dup # Loop through the array and add user data new_threads.each_with_index do |thread, i| # If the ID of the looped message equals the new message thread ID if thread['ts'] == data['thread_ts'] data['email'] = data['username'] - new_threads[i]['replies'].insert(0, data.clone) + new_threads[i]['replies'].insert(0, data.dup.to_h) + break end end self["threads"] = new_threads else - data['replies'] ||= [data.clone] + data['replies'] ||= [data.dup.to_h] if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id @@ -181,7 +182,7 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"] = self["threads"].clone.insert(0, data.clone) + self["threads"] = self["threads"].dup.insert(0, data.dup.to_h) end end From dbcfe6a572222207f8b6cb7e83c384727af07d0f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:04:25 +1100 Subject: [PATCH 1024/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index e0e76c0c..3106fa1b 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -159,20 +159,20 @@ def create_websocket if data.key?('thread_ts') # Duplicate the current threads array so we don't have ref issues - new_threads = self["threads"].dup + new_threads = self["threads"].deep_dup # Loop through the array and add user data new_threads.each_with_index do |thread, i| # If the ID of the looped message equals the new message thread ID if thread['ts'] == data['thread_ts'] data['email'] = data['username'] - new_threads[i]['replies'].insert(0, data.dup.to_h) + new_threads[i]['replies'].insert(0, data.deep_dup.to_h) break end end self["threads"] = new_threads else - data['replies'] ||= [data.dup.to_h] + data['replies'] ||= [data.deep_dup.to_h] if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id @@ -182,7 +182,7 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"] = self["threads"].dup.insert(0, data.dup.to_h) + self["threads"] = self["threads"].deep_dup.insert(0, data.deep_dup.to_h) end end From 75c2cfbfc6b976e54352f0c43aa458c8a7f573ec Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:11:05 +1100 Subject: [PATCH 1025/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 3106fa1b..1682cb5c 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -148,15 +148,13 @@ def create_websocket begin - logger.info "-----NEW MESSAGE RECEIVED----" - logger.info data.inspect - logger.info "-----------------------------" - # Ensure that it is a bot_message or slack client reply if ['bot_message'].include?(data['subtype']) # If this is a reply (has a thread_ts field) if data.key?('thread_ts') + logger.info "!!!!! GOT INSIDE USING THREAD TS !!!!!" + logger.info "WITH TEXT #{data['text']}" # Duplicate the current threads array so we don't have ref issues new_threads = self["threads"].deep_dup @@ -172,6 +170,8 @@ def create_websocket end self["threads"] = new_threads else + logger.info "XXXXX GOT INSIDE WITHOUT THREAD TS XXXXX" + logger.info "WITH TEXT #{data['text']}" data['replies'] ||= [data.deep_dup.to_h] if data['username'] != 'Concierge' From 2ebf2c126a91a388f055f2682cf6bee275374e4e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:17:44 +1100 Subject: [PATCH 1026/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 1682cb5c..2a3c75d1 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -172,7 +172,11 @@ def create_websocket else logger.info "XXXXX GOT INSIDE WITHOUT THREAD TS XXXXX" logger.info "WITH TEXT #{data['text']}" - data['replies'] ||= [data.deep_dup.to_h] + replies_object = data.deep_dup.to_h + logger.info "REPLIES OBJECT HAS TYPE" + logger.info replies_object.class + logger.info replies_object.inspect + data['replies'] = [replies_object] if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id From 4e4a63d93fedbcdedf8fe49049de09bd6a09738d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:22:53 +1100 Subject: [PATCH 1027/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 2a3c75d1..e2323ebb 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -186,7 +186,7 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"] = self["threads"].deep_dup.insert(0, data.deep_dup.to_h) + self["threads"] = self["threads"].deep_dup.insert(0, replies_object) end end From 3ad8b65406a8fb43fa01ef7e3a397fdedbcf58cc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:25:33 +1100 Subject: [PATCH 1028/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index e2323ebb..90bcf3a1 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -186,7 +186,7 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"] = self["threads"].deep_dup.insert(0, replies_object) + self["threads"] = self["threads"].deep_dup.insert(0, data.to_h.deep_dup) end end From 173a5e6a868007f6275ae466681870c643b28fc3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:27:03 +1100 Subject: [PATCH 1029/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 90bcf3a1..41ca869a 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -164,7 +164,7 @@ def create_websocket # If the ID of the looped message equals the new message thread ID if thread['ts'] == data['thread_ts'] data['email'] = data['username'] - new_threads[i]['replies'].insert(0, data.deep_dup.to_h) + new_threads[i]['replies'].insert(0, data.to_h.deep_dup) break end end @@ -172,7 +172,7 @@ def create_websocket else logger.info "XXXXX GOT INSIDE WITHOUT THREAD TS XXXXX" logger.info "WITH TEXT #{data['text']}" - replies_object = data.deep_dup.to_h + replies_object = data.to_h.deep_dup logger.info "REPLIES OBJECT HAS TYPE" logger.info replies_object.class logger.info replies_object.inspect From 9cf224b3a6aafe29d95b07ad14058c67178eee35 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:29:21 +1100 Subject: [PATCH 1030/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 41ca869a..4cc98b5d 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -176,7 +176,7 @@ def create_websocket logger.info "REPLIES OBJECT HAS TYPE" logger.info replies_object.class logger.info replies_object.inspect - data['replies'] = [replies_object] + data['replies'] = [replies_object.class] if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id From e199f57ba58d2b6227d80172777baf125579cdcc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:32:12 +1100 Subject: [PATCH 1031/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 4cc98b5d..64866584 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -172,11 +172,11 @@ def create_websocket else logger.info "XXXXX GOT INSIDE WITHOUT THREAD TS XXXXX" logger.info "WITH TEXT #{data['text']}" - replies_object = data.to_h.deep_dup + replies_object = data.to_h.dup logger.info "REPLIES OBJECT HAS TYPE" logger.info replies_object.class logger.info replies_object.inspect - data['replies'] = [replies_object.class] + data['replies'] = [replies_object] if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id From dadbcb8ecb04e81ccceefbbbebca0ea7ba4ba8c0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:40:49 +1100 Subject: [PATCH 1032/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 64866584..f8aa723e 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -172,11 +172,20 @@ def create_websocket else logger.info "XXXXX GOT INSIDE WITHOUT THREAD TS XXXXX" logger.info "WITH TEXT #{data['text']}" - replies_object = data.to_h.dup - logger.info "REPLIES OBJECT HAS TYPE" - logger.info replies_object.class - logger.info replies_object.inspect + replies_object = { + bot_id: data['bot_id'], + channel: data['channel'], + event_ts: data['event_ts'], + subtype: data['subtype'], + team: data['team'], + text: data['text'], + ts: data['ts'], + type: data['type'], + username: data['username'] + } + logger.info replies_object.class # => Hash data['replies'] = [replies_object] + self["threads"] = self["threads"].deep_dup.insert(0, data.to_h.deep_dup) if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id @@ -186,7 +195,6 @@ def create_websocket data['last_sent'] = user.last_message_sent end end - self["threads"] = self["threads"].deep_dup.insert(0, data.to_h.deep_dup) end end From 999a69267fa46eea6699e59942e1d9ab3d985e09 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 17:42:16 +1100 Subject: [PATCH 1033/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f8aa723e..d829e7a4 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -180,6 +180,7 @@ def create_websocket team: data['team'], text: data['text'], ts: data['ts'], + tester: "AHHH TEST", type: data['type'], username: data['username'] } From 456ccbeb283a039014bc04b1a3119c1d4131e145 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 22:25:32 +1100 Subject: [PATCH 1034/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index d829e7a4..f63c5ea6 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -184,9 +184,8 @@ def create_websocket type: data['type'], username: data['username'] } - logger.info replies_object.class # => Hash data['replies'] = [replies_object] - self["threads"] = self["threads"].deep_dup.insert(0, data.to_h.deep_dup) + self["threads"] = self["threads"].to_h.deep_dup.insert(0, data.to_h.deep_dup) if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id From 7b32cd47b96803a4c60aedfddc0d66ee0e7c4365 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 22:29:42 +1100 Subject: [PATCH 1035/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f63c5ea6..bee9abd1 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -184,9 +184,7 @@ def create_websocket type: data['type'], username: data['username'] } - data['replies'] = [replies_object] - self["threads"] = self["threads"].to_h.deep_dup.insert(0, data.to_h.deep_dup) - + if data['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id user = User.find_by_email(authority_id, data['username']) @@ -195,6 +193,14 @@ def create_websocket data['last_sent'] = user.last_message_sent end end + + data['replies'] = [replies_object] + old_threads = self["threads"].deep_dup + new_message = [data.to_h] + + + + self["threads"] = new_message + old_threads end end From a2eb5b20839ba0e388fd0e9c2141d594ae6f3890 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 22:39:02 +1100 Subject: [PATCH 1036/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index bee9abd1..80854815 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -172,35 +172,22 @@ def create_websocket else logger.info "XXXXX GOT INSIDE WITHOUT THREAD TS XXXXX" logger.info "WITH TEXT #{data['text']}" - replies_object = { - bot_id: data['bot_id'], - channel: data['channel'], - event_ts: data['event_ts'], - subtype: data['subtype'], - team: data['team'], - text: data['text'], - ts: data['ts'], - tester: "AHHH TEST", - type: data['type'], - username: data['username'] - } - - if data['username'] != 'Concierge' + new_message = data.to_h + + if new_message['username'] != 'Concierge' authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id - user = User.find_by_email(authority_id, data['username']) + user = User.find_by_email(authority_id, new_message['username']) if user - data['last_read'] = user.last_message_read - data['last_sent'] = user.last_message_sent + new_message['last_read'] = user.last_message_read + new_message['last_sent'] = user.last_message_sent end end - data['replies'] = [replies_object] - old_threads = self["threads"].deep_dup - new_message = [data.to_h] - - + new_message_copy = new_message.deep_dup + new_message['replies'] = new_message_copy - self["threads"] = new_message + old_threads + new_threads = self["threads"].deep_dup + self["threads"] = new_threads.insert(0, new_message) end end From 45f2dbc5b3de2ca087ac033ca31e7c0087995109 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 22:41:08 +1100 Subject: [PATCH 1037/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 80854815..b9c84406 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -184,7 +184,7 @@ def create_websocket end new_message_copy = new_message.deep_dup - new_message['replies'] = new_message_copy + new_message['replies'] = [new_message_copy] new_threads = self["threads"].deep_dup self["threads"] = new_threads.insert(0, new_message) From 44c44459911d0a15aa939ff35716bfdd2e301d34 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 22:48:31 +1100 Subject: [PATCH 1038/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index b9c84406..463f8407 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -153,25 +153,20 @@ def create_websocket # If this is a reply (has a thread_ts field) if data.key?('thread_ts') - logger.info "!!!!! GOT INSIDE USING THREAD TS !!!!!" - logger.info "WITH TEXT #{data['text']}" - - # Duplicate the current threads array so we don't have ref issues + new_message = data.to_h new_threads = self["threads"].deep_dup # Loop through the array and add user data new_threads.each_with_index do |thread, i| # If the ID of the looped message equals the new message thread ID - if thread['ts'] == data['thread_ts'] - data['email'] = data['username'] - new_threads[i]['replies'].insert(0, data.to_h.deep_dup) + if thread['ts'] ==new_message['thread_ts'] + new_message['email'] =new_message['username'] + new_threads[i]['replies'].insert(0, new_message) break end end self["threads"] = new_threads else - logger.info "XXXXX GOT INSIDE WITHOUT THREAD TS XXXXX" - logger.info "WITH TEXT #{data['text']}" new_message = data.to_h if new_message['username'] != 'Concierge' From 9fe1aa7b0404bb521ffc3de4f0297d66759f7b69 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 23:01:28 +1100 Subject: [PATCH 1039/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 463f8407..a2ee3eaa 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -159,8 +159,8 @@ def create_websocket # Loop through the array and add user data new_threads.each_with_index do |thread, i| # If the ID of the looped message equals the new message thread ID - if thread['ts'] ==new_message['thread_ts'] - new_message['email'] =new_message['username'] + if thread['ts'] == new_message['thread_ts'] + new_message['email'] = new_message['username'] new_threads[i]['replies'].insert(0, new_message) break end @@ -178,7 +178,7 @@ def create_websocket end end - new_message_copy = new_message.deep_dup + new_message_copy = new_message.to_h new_message['replies'] = [new_message_copy] new_threads = self["threads"].deep_dup From 4ea9c460a0c3ffb87adc59377600754f6817cfbb Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 23:10:35 +1100 Subject: [PATCH 1040/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 73 ++++++++++++++++------------------ 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index a2ee3eaa..81a9907b 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -145,49 +145,44 @@ def create_websocket @client.on :message do |data| - - begin - - # Ensure that it is a bot_message or slack client reply - if ['bot_message'].include?(data['subtype']) - - # If this is a reply (has a thread_ts field) - if data.key?('thread_ts') - new_message = data.to_h - new_threads = self["threads"].deep_dup - - # Loop through the array and add user data - new_threads.each_with_index do |thread, i| - # If the ID of the looped message equals the new message thread ID - if thread['ts'] == new_message['thread_ts'] - new_message['email'] = new_message['username'] - new_threads[i]['replies'].insert(0, new_message) - break - end + # Ensure that it is a bot_message or slack client reply + if ['bot_message'].include?(data['subtype']) + + # If this is a reply (has a thread_ts field) + if data.key?('thread_ts') + new_message = data.to_h + new_threads = self["threads"].deep_dup + + # Loop through the array and add user data + new_threads.each_with_index do |thread, i| + # If the ID of the looped message equals the new message thread ID + if thread['ts'] == new_message['thread_ts'] + new_message['email'] = new_message['username'] + new_threads[i]['replies'].insert(0, new_message) + break end - self["threads"] = new_threads - else - new_message = data.to_h - - if new_message['username'] != 'Concierge' - authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id - user = User.find_by_email(authority_id, new_message['username']) - if user - new_message['last_read'] = user.last_message_read - new_message['last_sent'] = user.last_message_sent - end + end + self["threads"] = new_threads + else + new_message = data.to_h + + if new_message['username'] != 'Concierge' + authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id + user = User.find_by_email(authority_id, new_message['username']) + if user + new_message['last_read'] = user.last_message_read + new_message['last_sent'] = user.last_message_sent end + end - new_message_copy = new_message.to_h - new_message['replies'] = [new_message_copy] + new_message_copy = new_message.to_h + new_message['replies'] = [new_message_copy] - new_threads = self["threads"].deep_dup - self["threads"] = new_threads.insert(0, new_message) - end - end - - rescue Exception => e - end + new_threads = self["threads"].deep_dup + self["threads"] = new_threads.insert(0, new_message) + end + end + end @client.start! From c21abd06ef9c921c024ffe042466628a08c0f311 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 23:14:38 +1100 Subject: [PATCH 1041/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 81a9907b..7a189418 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -175,7 +175,7 @@ def create_websocket end end - new_message_copy = new_message.to_h + new_message_copy = new_message.deep_dup.to_h new_message['replies'] = [new_message_copy] new_threads = self["threads"].deep_dup From d861d8c346dd9437c335f2ac02157379101064f2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 23:28:30 +1100 Subject: [PATCH 1042/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 7a189418..e855452d 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -24,6 +24,7 @@ def on_load def on_update on_unload create_websocket + @threads = [] self[:building] = setting(:building) || :barangaroo self[:channel] = setting(:channel) || :concierge end @@ -90,7 +91,8 @@ def get_threads } # Bind the frontend to the messages - self["threads"] = messages + @threads = messages + self["threads"] = @threads.deep_dup end def get_message(thread_id) @@ -151,7 +153,7 @@ def create_websocket # If this is a reply (has a thread_ts field) if data.key?('thread_ts') new_message = data.to_h - new_threads = self["threads"].deep_dup + new_threads = @threads.deep_dup # Loop through the array and add user data new_threads.each_with_index do |thread, i| @@ -175,10 +177,10 @@ def create_websocket end end - new_message_copy = new_message.deep_dup.to_h + new_message_copy = new_message.deep_dup new_message['replies'] = [new_message_copy] - new_threads = self["threads"].deep_dup + new_threads = @threads.deep_dup self["threads"] = new_threads.insert(0, new_message) end end From dcad8a38a3661094dc743a227ee98eab366d67d9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 23:31:32 +1100 Subject: [PATCH 1043/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index e855452d..306edb98 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -164,7 +164,8 @@ def create_websocket break end end - self["threads"] = new_threads + @threads = new_threads + self["threads"] = new_threads.deep_dup else new_message = data.to_h @@ -180,8 +181,8 @@ def create_websocket new_message_copy = new_message.deep_dup new_message['replies'] = [new_message_copy] - new_threads = @threads.deep_dup - self["threads"] = new_threads.insert(0, new_message) + @threads = @threads.deep_dup.insert(0, new_message) + self["threads"] = @threads.deep_dup.insert(0, new_message) end end From 82056a133e4870296dbe0bb3ce95e27d8d9fe67b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 23:32:41 +1100 Subject: [PATCH 1044/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 306edb98..dc278ef4 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -181,7 +181,7 @@ def create_websocket new_message_copy = new_message.deep_dup new_message['replies'] = [new_message_copy] - @threads = @threads.deep_dup.insert(0, new_message) + @threads = @threads.insert(0, new_message) self["threads"] = @threads.deep_dup.insert(0, new_message) end end From 26e03be83d513b0246650dc018b183f6927cded8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 23:33:44 +1100 Subject: [PATCH 1045/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index dc278ef4..7bd263c6 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -182,7 +182,7 @@ def create_websocket new_message['replies'] = [new_message_copy] @threads = @threads.insert(0, new_message) - self["threads"] = @threads.deep_dup.insert(0, new_message) + self["threads"] = @threads.deep_dup end end From fcf797f13a4f6ac6acee9919e7def5ebde207707 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 4 Dec 2018 23:38:24 +1100 Subject: [PATCH 1046/1752] [Slack Modules] Change dup to deep_dup --- modules/aca/slack_concierge.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 7bd263c6..0d87deb7 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -161,6 +161,7 @@ def create_websocket if thread['ts'] == new_message['thread_ts'] new_message['email'] = new_message['username'] new_threads[i]['replies'].insert(0, new_message) + self["thread_#{new_message['thread_ts']}"] = new_threads[i]['replies'].dup break end end From f7a2264f16ed70f172d5ad6ec06c677ba36540b5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Dec 2018 11:17:55 +1100 Subject: [PATCH 1047/1752] [42API] Add support to grab visitors --- lib/fortytwo/it_api.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 30a38dad..aaeb0818 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -117,6 +117,21 @@ def get_orgs(q: nil, limit: 100, ids:nil) end + def get_visitors(owner_id: nil, limit: 100) + # If we have a query and the query has at least one space + query_params = { + '$top': limit + } + # # TODO:: ONCE STEPHEN GETS BACK TO US + # if owner_id + # query_params['$filter'] = + # end + # query_params['$filter'] = filter_param if defined? filter_param + response = api_request(request_method: 'get', endpoint: 'visitor', query: query_params) + JSON.parse(response.body)['value'] + end + + protected def api_token From 096ce4afec6db0b5bd1e4884eab1ab0bd7025fc8 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Dec 2018 11:42:12 +1100 Subject: [PATCH 1048/1752] [itapi] Add visitor adding and grabbing --- lib/fortytwo/it_api.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index aaeb0818..855fe5cb 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -131,6 +131,16 @@ def get_visitors(owner_id: nil, limit: 100) JSON.parse(response.body)['value'] end + def add_visitor(owner_id:, visitor_object:) + # If we have a query and the query has at least one space + query_params = { + 'bookerExternalId': owner_id + } + post_data = visitor_object + response = api_request(request_method: 'post', endpoint: 'visitor', query: query_params, body: post_data) + JSON.parse(response.body)['value'] + end + protected From 5c3273bbc74dfdef4f37ce6098ee62fae9022629 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Dec 2018 11:45:04 +1100 Subject: [PATCH 1049/1752] [itapi] Add visitor adding and grabbing --- lib/fortytwo/it_api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 855fe5cb..0e689f01 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -137,7 +137,7 @@ def add_visitor(owner_id:, visitor_object:) 'bookerExternalId': owner_id } post_data = visitor_object - response = api_request(request_method: 'post', endpoint: 'visitor', query: query_params, body: post_data) + response = api_request(request_method: 'post', endpoint: 'visitor', query: query_params, data: post_data) JSON.parse(response.body)['value'] end From 984365636f29bed2404fda5bfc5118ad1eb435ef Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Dec 2018 11:55:41 +1100 Subject: [PATCH 1050/1752] [itapi] Add visitor adding and grabbing --- lib/fortytwo/it_api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 0e689f01..7baa94a6 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -138,7 +138,7 @@ def add_visitor(owner_id:, visitor_object:) } post_data = visitor_object response = api_request(request_method: 'post', endpoint: 'visitor', query: query_params, data: post_data) - JSON.parse(response.body)['value'] + JSON.parse(response.body) end From ef4901f818b62261a18c9025f545ea82d2c3c2cc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Dec 2018 12:30:01 +1100 Subject: [PATCH 1051/1752] [itapi] Add visitor adding and grabbing --- lib/fortytwo/it_api.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 7baa94a6..0902410a 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -31,6 +31,7 @@ def api_request(request_method:, endpoint:, data:nil, query:{}, headers:nil) data = data.to_json if !data.nil? && data.class != String headers['Authorization'] = "Bearer #{api_token}" + headers['Content-Type'] = "application/json" api_path = "#{@api_domain}#{endpoint}" From 9e2c8ec0e0b29aaa9191101a28ece530049df0dc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Dec 2018 15:42:45 +1100 Subject: [PATCH 1052/1752] Add email to event body after to_json --- lib/microsoft/office.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index bc0f7206..cd51158e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -744,7 +744,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil subject: subject, body: { contentType: content_type, - content: description + content: "X!X!X!" }, start: { dateTime: start_param, @@ -811,6 +811,8 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil event = event.to_json + event.gsub!("X!X!X!",description) + request = graph_request(request_method: 'post', endpoint: endpoint, data: event, password: @delegated) check_response(request) From 90d3316462b8d8beeacb52d087212058d1670d1f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Dec 2018 18:42:10 +1100 Subject: [PATCH 1053/1752] [itapi] Add visitor adding and grabbing --- lib/microsoft/office.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index cd51158e..985fd32c 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -874,6 +874,8 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec } } if attendees + event[:attendees] ||= [] + event[:attendees].push({ emailAddress: { address: room.email, From 8e370fcb413d2bbe5c298941852a0c0e4a7d546a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 5 Dec 2018 18:44:13 +1100 Subject: [PATCH 1054/1752] [itapi] Add visitor adding and grabbing --- lib/microsoft/office.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 985fd32c..6d764b39 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -861,7 +861,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec event[:body] = { contentType: 'html', - content: description + content: "X!X!X!" } if description # Let's assume that the request has the current user and room as an attendee already @@ -884,7 +884,11 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec type: 'resource' }) - request = graph_request(request_method: 'patch', endpoint: endpoint, data: event.to_json, password: @delegated) + event = event.to_json + + event.gsub!("X!X!X!",description) + + request = graph_request(request_method: 'patch', endpoint: endpoint, data: event, password: @delegated) check_response(request) response = JSON.parse(request.body) end From 008ed39d5a05b6b75a11357462ee265928c6b519 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 5 Dec 2018 21:59:08 +1100 Subject: [PATCH 1055/1752] (cisco:switch) seperate SNMP client Allows us to execute this remotely or locally so we can offload the processing --- modules/cisco/catalyst_snmp_client.rb | 193 +++++++++++++++ .../cisco/switch/snooping_catalyst_snmp.rb | 229 ++++-------------- 2 files changed, 240 insertions(+), 182 deletions(-) create mode 100644 modules/cisco/catalyst_snmp_client.rb diff --git a/modules/cisco/catalyst_snmp_client.rb b/modules/cisco/catalyst_snmp_client.rb new file mode 100644 index 00000000..809a52a2 --- /dev/null +++ b/modules/cisco/catalyst_snmp_client.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +require 'netsnmp' + +module Cisco; end +module Cisco::Switch; + AddressType = { + 0 => :unknown, + 1 => :ipv4, + 2 => :ipv6, + 3 => :ipv4z, + 4 => :ipv6z, + 16 => :dns + }.freeze + + AcceptAddress = [:ipv4, :ipv6, :ipv4z, :ipv6z].freeze + + BindingStatus = { + 1 => :active, + 2 => :not_in_service, + 3 => :not_ready, + 4 => :create_and_go, + 5 => :create_and_wait, + 6 => :destroy + }.freeze + + # cdsBindingsEntry + EntryParts = { + '1' => :vlan, # Cisco has made this not-accessible + '2' => :mac_address, # Cisco has made this not-accessible + '3' => :addr_type, + '4' => :ip_address, + '5' => :interface, + '6' => :leased_time, # in seconds + '7' => :binding_status, # can set this to destroy to delete entry + '8' => :hostname + }.freeze + + SnoopingEntry = Struct.new(:id, *EntryParts.values) do + def address_type + AddressType[self.addr_type] + end + + def mac + self.mac_address || self.extract_vlan_and_mac.mac_address + end + + def get_vlan + self.vlan || self.extract_vlan_and_mac.vlan + end + + def ip + ip_addr = self.ip_address + return nil unless ip_addr + + case self.address_type + when :ipv4 + # DISPLAY-HINT "1d.1d.1d.1d" + # Example response: "0A B2 C4 45" + ip_addr.split(' ').map { |i| i.to_i(16).to_s }.join('.') + when :ipv6 + # DISPLAY-HINT "2x:2x:2x:2x:2x:2x:2x:2x" + # IPAddr will present the IPv6 address in it's short form + IPAddr.new(ip_addr.gsub(' ', '').scan(/..../).join(':')).to_s + end + end + + def extract_vlan_and_mac + parts = self.id.split('.') + self.vlan = parts[0].to_i + self.mac_address = parts[1..-1].map { |i| i.to_i.to_s(16).rjust(2, '0') }.join('') + self + end + end +end + +class Cisco::Switch::CatalystSNMPClient + # version: 1, + # community: 'public', + # timeout: 4 + # host: + # community: + def initialize(reactor, **snmp_settings) + @reactor = reactor + @snmp_settings = snmp_settings + + # flag to indicate if processing is occuring + @if_mappings = {} + @defer = nil + end + + def processing + @defer + end + + def promise + @defer.promise + end + + def new_client + close + @client = NETSNMP::Client.new(@snmp_settings) + end + + def close + client = @client + if @defer + @defer.promise.finally { client.close } + @defer.reject RuntimeError.new("client closed by user") + @defer = nil + else + client&.close + end + end + + # Index short name lookup + # ifName: 1.3.6.1.2.1.31.1.1.1.1.xx (where xx is the ifIndex) + def query_index_mappings + @defer = @reactor.defer + client = new_client + mappings = {} + + @reactor.work { + client.walk(oid: '1.3.6.1.2.1.31.1.1.1.1').each do |oid_code, value| + oid_code = oid_code[23..-1] + mappings[oid_code.to_i] = value.downcase + end + }.value + + @if_mappings = mappings + ensure + @defer.resolve(true) + @defer = nil + end + + # ifOperStatus: 1.3.6.1.2.1.2.2.1.8.xx == up(1), down(2), testing(3) + def query_interface_status + @defer = @reactor.defer + client = @client + interfaces_up = [] + interfaces_down = [] + if_mappings = @if_mappings + + @reactor.work { + client.walk(oid: '1.3.6.1.2.1.2.2.1.8').each do |oid_code, value| + oid_code = oid_code[20..-1] + interface = if_mappings[oid_code.to_i] + next unless interface + + case value + when 1 # up + interfaces_up << interface + when 2 # down + interfaces_down << interface + else + next + end + end + }.value + + [interfaces_down, interfaces_up] + ensure + @defer.resolve(true) + @defer = nil + end + + # A row instance contains the Mac address, IP address type, IP address, VLAN number, interface number, leased time, and status of this instance. + # http://www.oidview.com/mibs/9/CISCO-DHCP-SNOOPING-MIB.html + # http://www.snmplink.org/OnLineMIB/Cisco/index.html#1634 + def query_snooping_bindings + @defer = @reactor.defer + client = @client + entries = {} + + @reactor.work { + client.walk(oid: '1.3.6.1.4.1.9.9.380.1.4.1').each do |oid_code, value| + part, entry_id = oid_code[28..-1].split('.', 2) + next if entry_id.nil? + + entry = entries[entry_id] || ::Cisco::Switch::SnoopingEntry.new + entry.id = entry_id + entry.__send__("#{::Cisco::Switch::EntryParts[part]}=", value) + entries[entry_id] = entry + end + }.value + + entries + ensure + @defer.resolve(true) + @defer = nil + end +end diff --git a/modules/cisco/switch/snooping_catalyst_snmp.rb b/modules/cisco/switch/snooping_catalyst_snmp.rb index 939ff911..543b71e7 100644 --- a/modules/cisco/switch/snooping_catalyst_snmp.rb +++ b/modules/cisco/switch/snooping_catalyst_snmp.rb @@ -12,28 +12,9 @@ module Cisco::Switch; end ::Orchestrator::DependencyManager.load('Aca::Tracking::SwitchPort', :model, :force) ::Aca::Tracking::SwitchPort.ensure_design_document! -# Run SNMP code on an independent thread pool as SNMP is soo slow -module Cisco::ThreadPool - THREAD_POOL = ::Concurrent::ThreadPoolExecutor.new( - min_threads: ::Libuv::Reactor::LIBUV_MIN_POOL, - max_threads: ::Libuv::Reactor::LIBUV_MAX_POOL, - max_queue: ::Libuv::Reactor::LIBUV_MAX_QUEUE - ) - - def self.task - reactor = ::Libuv::Reactor.current - d = reactor.defer - THREAD_POOL.post do - begin - d.resolve(yield) - rescue Exception => e - d.reject(e) - end - end - promise = d.promise - promise - end -end +# The SNMP clients +load File.join(__dir__, '../catalyst_snmp_client.rb') +#load File.join(__dir__, '../catalyst_offloader.rb') class Cisco::Switch::SnoopingCatalystSNMP include ::Orchestrator::Constants @@ -57,7 +38,8 @@ class Cisco::Switch::SnoopingCatalystSNMP "Cisco Phone Dock": "7001b5" }, temporary_macs: {}, - discovery_polling_period: 90 + discovery_polling_period: 90, + offload_snmp: false }) def on_load @@ -100,6 +82,7 @@ def on_load end def on_update + @offload_snmp = setting(:offload_snmp) new_client if @resolved_ip @remote_address = remote_address.downcase @ignore_macs = ::Set.new((setting(:ignore_macs) || {}).values) @@ -117,12 +100,7 @@ def on_update end def on_unload - if @processing - client = @client - @processing.finally { client.close } - else - @client&.close - end + @client&.close @client = nil td = ::Aca::TrapDispatcher.instance @@ -130,7 +108,7 @@ def on_unload end def is_processing? - "IP resolved to #{@resolved_ip}\ntransport online #{!!@client}\nprocessing #{!!@processing}" + "IP resolved to #{@resolved_ip}\ntransport online #{!!@client}\nprocessing #{!!@client.processing}" end def hostname_resolution(ip) @@ -171,8 +149,8 @@ def check_link_state(pdu) end if ifIndex && state - if @processing - @processing.finally { on_trap(ifIndex, state) } + if @client.processing + @client.promise.finally { on_trap(ifIndex, state) } else on_trap(ifIndex, state) end @@ -205,102 +183,17 @@ def on_trap(ifIndex, state) self[:interfaces] = @connected_interfaces.to_a end - AddressType = { - 0 => :unknown, - 1 => :ipv4, - 2 => :ipv6, - 3 => :ipv4z, - 4 => :ipv6z, - 16 => :dns - }.freeze - - AcceptAddress = [:ipv4, :ipv6, :ipv4z, :ipv6z].freeze - - BindingStatus = { - 1 => :active, - 2 => :not_in_service, - 3 => :not_ready, - 4 => :create_and_go, - 5 => :create_and_wait, - 6 => :destroy - }.freeze - - # cdsBindingsEntry - EntryParts = { - '1' => :vlan, # Cisco has made this not-accessible - '2' => :mac_address, # Cisco has made this not-accessible - '3' => :addr_type, - '4' => :ip_address, - '5' => :interface, - '6' => :leased_time, # in seconds - '7' => :binding_status, # can set this to destroy to delete entry - '8' => :hostname - }.freeze - - SnoopingEntry = Struct.new(:id, *EntryParts.values) do - def address_type - AddressType[self.addr_type] - end - - def mac - self.mac_address || self.extract_vlan_and_mac.mac_address - end - - def get_vlan - self.vlan || self.extract_vlan_and_mac.vlan - end - - def ip - ip_addr = self.ip_address - return nil unless ip_addr - - case self.address_type - when :ipv4 - # DISPLAY-HINT "1d.1d.1d.1d" - # Example response: "0A B2 C4 45" - ip_addr.split(' ').map { |i| i.to_i(16).to_s }.join('.') - when :ipv6 - # DISPLAY-HINT "2x:2x:2x:2x:2x:2x:2x:2x" - # IPAddr will present the IPv6 address in it's short form - IPAddr.new(ip_addr.gsub(' ', '').scan(/..../).join(':')).to_s - end - end - - def extract_vlan_and_mac - parts = self.id.split('.') - self.vlan = parts[0].to_i - self.mac_address = parts[1..-1].map { |i| i.to_i.to_s(16).rjust(2, '0') }.join('') - self - end - end - # A row instance contains the Mac address, IP address type, IP address, VLAN number, interface number, leased time, and status of this instance. # http://www.oidview.com/mibs/9/CISCO-DHCP-SNOOPING-MIB.html # http://www.snmplink.org/OnLineMIB/Cisco/index.html#1634 def query_snooping_bindings return :not_ready unless @client - return :currently_processing if @processing + return :currently_processing if @client.processing logger.debug '==> extracting snooping table <==' # Walking cdsBindingsTable - client = @client - entries = {} - @processing = ::Cisco::ThreadPool.task do - client.walk(oid: '1.3.6.1.4.1.9.9.380.1.4.1').each do |oid_code, value| - part, entry_id = oid_code[28..-1].split('.', 2) - next if entry_id.nil? - - entry = entries[entry_id] || SnoopingEntry.new - entry.id = entry_id - entry.__send__("#{EntryParts[part]}=", value) - entries[entry_id] = entry - end - end - @processing.finally { - @processing = nil - client.close if client != @client - }.value + entries = @client.query_snooping_bindings # Process the bindings entries = entries.values @@ -392,93 +285,61 @@ def query_snooping_bindings # ifName: 1.3.6.1.2.1.31.1.1.1.1.xx (where xx is the ifIndex) def query_index_mappings return :not_ready unless @client - return :currently_processing if @processing + return :currently_processing if @client.processing logger.debug '==> mapping ifIndex to port names <==' @scheduled_if_query = false - client = @client - mappings = {} - @processing = ::Cisco::ThreadPool.task do - client.walk(oid: '1.3.6.1.2.1.31.1.1.1.1').each do |oid_code, value| - oid_code = oid_code[23..-1] - mappings[oid_code.to_i] = value.downcase - end + mappings = @client.query_index_mappings + logger.debug { "<== found #{mappings.length} ports ==>" } + + if mappings.empty? + @scheduled_if_query = true + else + @if_mappings = mappings end - @processing.finally { - @processing = nil - client.close if client != @client - } - @processing.then { - logger.debug { "<== found #{mappings.length} ports ==>" } - if mappings.empty? - @scheduled_if_query = true - else - @if_mappings = mappings - end - }.value end # ifOperStatus: 1.3.6.1.2.1.2.2.1.8.xx == up(1), down(2), testing(3) def query_interface_status return :not_ready unless @client - return :currently_processing if @processing + return :currently_processing if @client.processing logger.debug '==> querying interface status <==' @scheduled_status_query = false - client = @client - if_mappings = @if_mappings - remove_interfaces = [] - add_interfaces = [] - @processing = ::Cisco::ThreadPool.task do - client.walk(oid: '1.3.6.1.2.1.2.2.1.8').each do |oid_code, value| - oid_code = oid_code[20..-1] - interface = if_mappings[oid_code.to_i] - - next unless interface - - case value - when 1 # up - next if @check_interface.include?(interface) - logger.debug { "Interface Up: #{interface}" } - remove_interfaces << interface - @check_interface << interface - when 2 # down - next unless @check_interface.include?(interface) - logger.debug { "Interface Down: #{interface}" } - # We are no longer interested in this interface - @check_interface.delete(interface) - add_interfaces << interface - else - next - end - end + interfaces_down, interfaces_up = @client.query_interface_status + + interfaces_up.each do |interface| + next if @check_interface.include?(interface) + logger.debug { "Interface Up: #{interface}" } + remove_reserved(interface) + @check_interface << interface end - @processing.finally { - @processing = nil - client.close if client != @client - } - @processing.then { - logger.debug '<== finished querying interfaces ==>' - remove_interfaces.each { |iface| remove_reserved(iface) } - add_interfaces.each { |iface| remove_lookup(iface) } - self[:reserved] = @reserved_interface.to_a - }.value + + interfaces_down.each do |interface| + next unless @check_interface.include?(interface) + logger.debug { "Interface Down: #{interface}" } + # We are no longer interested in this interface + @check_interface.delete(interface) + remove_lookup(interface) + end + + logger.debug '<== finished querying interfaces ==>' + self[:reserved] = @reserved_interface.to_a end def query_connected_devices - if @processing + if @client.processing logger.debug 'Skipping device query... busy processing' return end + rebuild_client logger.debug 'Querying for connected devices' query_index_mappings if @if_mappings.empty? || @scheduled_if_query query_interface_status if @scheduled_status_query query_snooping_bindings - rebuild_client rescue => e - rebuild_client @scheduled_status_query = true raise e end @@ -518,8 +379,12 @@ def new_client end def rebuild_client - @client.close if @client && @processing.nil? - @client = NETSNMP::Client.new(@snmp_settings) + @client.close + @client = if @offload_snmp + # TODO:: obtain remote client + else + ::Cisco::Switch::CatalystSNMPClient.new(thread, @snmp_settings) + end end def received(data, resolve, command) From 0920e777fa6d40818a014cce55993d9bb723c506 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 5 Dec 2018 23:27:56 +1100 Subject: [PATCH 1056/1752] (cisco:switch) complete snmp query extraction --- modules/cisco/catalyst_snmp_client.rb | 128 ++++++++++-------- .../cisco/switch/snooping_catalyst_snmp.rb | 4 +- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/modules/cisco/catalyst_snmp_client.rb b/modules/cisco/catalyst_snmp_client.rb index 809a52a2..e6f758fa 100644 --- a/modules/cisco/catalyst_snmp_client.rb +++ b/modules/cisco/catalyst_snmp_client.rb @@ -117,77 +117,93 @@ def close # Index short name lookup # ifName: 1.3.6.1.2.1.31.1.1.1.1.xx (where xx is the ifIndex) def query_index_mappings - @defer = @reactor.defer - client = new_client - mappings = {} - - @reactor.work { - client.walk(oid: '1.3.6.1.2.1.31.1.1.1.1').each do |oid_code, value| - oid_code = oid_code[23..-1] - mappings[oid_code.to_i] = value.downcase - end - }.value + raise "processing in progress" if @defer - @if_mappings = mappings - ensure - @defer.resolve(true) - @defer = nil + client = @client || new_client + defer = @defer = @reactor.defer + + begin + mappings = {} + + @reactor.work { + client.walk(oid: '1.3.6.1.2.1.31.1.1.1.1').each do |oid_code, value| + oid_code = oid_code[23..-1] + mappings[oid_code.to_i] = value.downcase + end + }.value + + @if_mappings = mappings + ensure + defer.resolve(true) + @defer = nil if defer = @defer + end end # ifOperStatus: 1.3.6.1.2.1.2.2.1.8.xx == up(1), down(2), testing(3) def query_interface_status - @defer = @reactor.defer - client = @client - interfaces_up = [] - interfaces_down = [] - if_mappings = @if_mappings - - @reactor.work { - client.walk(oid: '1.3.6.1.2.1.2.2.1.8').each do |oid_code, value| - oid_code = oid_code[20..-1] - interface = if_mappings[oid_code.to_i] - next unless interface - - case value - when 1 # up - interfaces_up << interface - when 2 # down - interfaces_down << interface - else - next + raise "processing in progress" if @defer + + if_mappings = @if_mappings || query_index_mappings + + client = @client || new_client + defer = @defer = @reactor.defer + + begin + interfaces_up = [] + interfaces_down = [] + + @reactor.work { + client.walk(oid: '1.3.6.1.2.1.2.2.1.8').each do |oid_code, value| + oid_code = oid_code[20..-1] + interface = if_mappings[oid_code.to_i] + next unless interface + + case value + when 1 # up + interfaces_up << interface + when 2 # down + interfaces_down << interface + else + next + end end - end - }.value + }.value - [interfaces_down, interfaces_up] - ensure - @defer.resolve(true) - @defer = nil + [interfaces_down, interfaces_up] + ensure + defer.resolve(true) + @defer = nil if defer = @defer + end end # A row instance contains the Mac address, IP address type, IP address, VLAN number, interface number, leased time, and status of this instance. # http://www.oidview.com/mibs/9/CISCO-DHCP-SNOOPING-MIB.html # http://www.snmplink.org/OnLineMIB/Cisco/index.html#1634 def query_snooping_bindings - @defer = @reactor.defer - client = @client - entries = {} + raise "processing in progress" if @defer - @reactor.work { - client.walk(oid: '1.3.6.1.4.1.9.9.380.1.4.1').each do |oid_code, value| - part, entry_id = oid_code[28..-1].split('.', 2) - next if entry_id.nil? + client = @client || new_client + defer = @defer = @reactor.defer - entry = entries[entry_id] || ::Cisco::Switch::SnoopingEntry.new - entry.id = entry_id - entry.__send__("#{::Cisco::Switch::EntryParts[part]}=", value) - entries[entry_id] = entry - end - }.value + begin + entries = {} - entries - ensure - @defer.resolve(true) - @defer = nil + @reactor.work { + client.walk(oid: '1.3.6.1.4.1.9.9.380.1.4.1').each do |oid_code, value| + part, entry_id = oid_code[28..-1].split('.', 2) + next if entry_id.nil? + + entry = entries[entry_id] || ::Cisco::Switch::SnoopingEntry.new + entry.id = entry_id + entry.__send__("#{::Cisco::Switch::EntryParts[part]}=", value) + entries[entry_id] = entry + end + }.value + + entries + ensure + defer.resolve(true) + @defer = nil if defer = @defer + end end end diff --git a/modules/cisco/switch/snooping_catalyst_snmp.rb b/modules/cisco/switch/snooping_catalyst_snmp.rb index 543b71e7..4dbc8b5a 100644 --- a/modules/cisco/switch/snooping_catalyst_snmp.rb +++ b/modules/cisco/switch/snooping_catalyst_snmp.rb @@ -149,7 +149,7 @@ def check_link_state(pdu) end if ifIndex && state - if @client.processing + if @client&.processing @client.promise.finally { on_trap(ifIndex, state) } else on_trap(ifIndex, state) @@ -379,7 +379,7 @@ def new_client end def rebuild_client - @client.close + @client&.close @client = if @offload_snmp # TODO:: obtain remote client else From 491bbfc4d2a43286032c482914743836f89163f4 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 08:54:19 +1100 Subject: [PATCH 1057/1752] (cisco:snmp) add snmp offloader --- lib/tasks/offload.rake | 43 +++++ modules/cisco/catalyst_offloader.rb | 148 ++++++++++++++++++ modules/cisco/catalyst_snmp_client.rb | 8 +- .../cisco/switch/snooping_catalyst_snmp.rb | 10 +- 4 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 lib/tasks/offload.rake create mode 100644 modules/cisco/catalyst_offloader.rb diff --git a/lib/tasks/offload.rake b/lib/tasks/offload.rake new file mode 100644 index 00000000..1962f9f3 --- /dev/null +++ b/lib/tasks/offload.rake @@ -0,0 +1,43 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +# rake offload:catalyst_snmp['port_number'] + +namespace :offload do + desc 'provides a process for offloading SNMP workloads' + task(:catalyst_snmp, [:tcp_port]) do |_, args| + require 'uv-rays' + require File.join(__dir__, '../../modules/cisco/catalyst_snmp_client.rb') + require File.join(__dir__, '../../modules/cisco/catalyst_offloader.rb') + + port = args[:tcp_port].to_i + + Libuv.reactor.run do { |reactor| + reactor.tcp.bind('127.0.0.1', port) do |client| + tokeniser = ::UV::AbstractTokenizer.new(::Cisco::CatalystOffloader::ParserSettings) + snmp_client = nil + + client.progress do |data| + tokeniser.extract(data).each do |response| + begin + args = Marshal.load(response) + if args[0] == :client + snmp_client&.close + snmp_client = ::Cisco::Switch::CatalystSNMPClient.new(reactor, args[1]) + else + retval = snmp_client.__send__(*args) + if args[0].to_s.start_with? 'query' + msg = Marshal.dump(retval) + client.write("#{[msg.length].pack('V')}#{msg}") + end + end + rescue => e + STDOUT.puts "failed to marshal request\n#{e.message}\n#{e.backtrace.join("\n")}" + end + end + end + client.start_read + end + } + end +end diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb new file mode 100644 index 00000000..8abd111b --- /dev/null +++ b/modules/cisco/catalyst_offloader.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +require 'singleton' + +module Cisco; end +class Cisco::CatalystOffloader + include Singleton + + class Proxy + def initialize(reactor, snmp_settings) + @reactor = reactor + @snmp_settings = snmp_settings + @defer = nil + + @terminated = false + @connected = false + + connect + end + + ParserSettings = { + callback: lambda do |byte_str| + return false if byte_str.bytesize < 4 + length = byte_str[0...4].unpack('V')[0] + 4 + return length if byte_str.length >= length + false + end + }.freeze + + def connect(defer = @reactor.defer) + @connecting = defer.promise + @tokeniser = ::UV::AbstractTokenizer.new(ParserSettings) + + # Process response data + @connection = @reactor.tcp { |data, socket| + @tokeniser.extract(data).each do |response| + next unless @defer + begin + @defer.resolve Marshal.load(response) + rescue => e + @defer.reject e + end + end + } + + # Attempt to connect to offload process + @connection.connect('127.0.0.1', 30001) { |socket| + @connected = true + defer.resolve(true) + socket.start_read + + write(:client, @snmp_settings) + }.finally { + @defer&.reject RuntimeError.new('connection lost') + @defer = nil + + if @connected + @connected = false + defer = @reactor.defer + elsif @terminated + defer.reject RuntimeError.new('failed to connect') + end + + connect(defer) unless @terminated + } + end + + def write(*args) + raise "connection not ready" unless @connected + msg = Marshal.dump(args) + @connection.write("#{[msg.length].pack('V')}#{msg}") + end + + def disconnect + @terminated = true + @connection.close + end + + # Proxied methods: + def processing + @defer + end + + def promise + @defer.promise + end + + def new_setting(snmp_settings) + @snmp_settings = snmp_settings + @connecting.then { write(:client, snmp_settings) } + @defer&.reject RuntimeError.new('client closed by user') + @defer = nil + end + + def new_client + @connecting.then { write(:new_client) } + @defer&.reject RuntimeError.new('client closed by user') + @defer = nil + end + + def close + @connecting.then { write(:close) } + @defer&.reject RuntimeError.new('client closed by user') + @defer = nil + end + + def query_index_mappings + raise "processing in progress" if @defer + @connecting.then { write(:query_index_mappings) } + @defer = @reactor.defer + @defer.promise.value + end + + def query_interface_status + raise "processing in progress" if @defer + @connecting.then { write(:query_interface_status) } + @defer = @reactor.defer + @defer.promise.value + end + + def query_snooping_bindings + raise "processing in progress" if @defer + @connecting.then { write(:query_snooping_bindings) } + @defer = @reactor.defer + @defer.promise.value + end + end + + def initialize + Libuv::Reactor.default.schedule { start_process } if !defined?(::Rake::Task) + end + + def register(reactor, snmp_settings) + Proxy.new(reactor, snmp_settings) + end + + def start_process + # Start the rake task that will do the SNMP processing + @process = @reactor.spawn('rake', args: "offload:catalyst_snmp['30001']") + @process.finally do + STDOUT.puts "offload task closed! Restarting in 5s..." + @reactor.scheduler.in('5s') do + @process = @reactor.spawn('rake', args: "offload:catalyst_snmp['30001']") + end + end + end +end diff --git a/modules/cisco/catalyst_snmp_client.rb b/modules/cisco/catalyst_snmp_client.rb index e6f758fa..023203ff 100644 --- a/modules/cisco/catalyst_snmp_client.rb +++ b/modules/cisco/catalyst_snmp_client.rb @@ -107,7 +107,7 @@ def close client = @client if @defer @defer.promise.finally { client.close } - @defer.reject RuntimeError.new("client closed by user") + @defer.reject RuntimeError.new('client closed by user') @defer = nil else client&.close @@ -135,7 +135,7 @@ def query_index_mappings @if_mappings = mappings ensure defer.resolve(true) - @defer = nil if defer = @defer + @defer = nil if defer == @defer end end @@ -172,7 +172,7 @@ def query_interface_status [interfaces_down, interfaces_up] ensure defer.resolve(true) - @defer = nil if defer = @defer + @defer = nil if defer == @defer end end @@ -203,7 +203,7 @@ def query_snooping_bindings entries ensure defer.resolve(true) - @defer = nil if defer = @defer + @defer = nil if defer == @defer end end end diff --git a/modules/cisco/switch/snooping_catalyst_snmp.rb b/modules/cisco/switch/snooping_catalyst_snmp.rb index 4dbc8b5a..613a0f87 100644 --- a/modules/cisco/switch/snooping_catalyst_snmp.rb +++ b/modules/cisco/switch/snooping_catalyst_snmp.rb @@ -14,7 +14,7 @@ module Cisco::Switch; end # The SNMP clients load File.join(__dir__, '../catalyst_snmp_client.rb') -#load File.join(__dir__, '../catalyst_offloader.rb') +load File.join(__dir__, '../catalyst_offloader.rb') class Cisco::Switch::SnoopingCatalystSNMP include ::Orchestrator::Constants @@ -101,6 +101,7 @@ def on_update def on_unload @client&.close + @client.disconnect if @client.is_a? ::Cisco::CatalystOffloader::Proxy @client = nil td = ::Aca::TrapDispatcher.instance @@ -381,8 +382,13 @@ def new_client def rebuild_client @client&.close @client = if @offload_snmp - # TODO:: obtain remote client + if @client.is_a? ::Cisco::CatalystOffloader::Proxy + @client.new_setting(@snmp_settings) + else + ::Cisco::CatalystOffloader.instance.register(thread, @snmp_settings) + end else + @client.disconnect if @client.is_a? ::Cisco::CatalystOffloader::Proxy ::Cisco::Switch::CatalystSNMPClient.new(thread, @snmp_settings) end end From 34edb7c50a96417ae7e46f2214e15939ff5b5878 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 09:17:55 +1100 Subject: [PATCH 1058/1752] (cisco:snmp) add logger to offloader proxy --- modules/cisco/catalyst_offloader.rb | 9 ++++++--- modules/cisco/switch/snooping_catalyst_snmp.rb | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 8abd111b..ab556de7 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -8,8 +8,9 @@ class Cisco::CatalystOffloader include Singleton class Proxy - def initialize(reactor, snmp_settings) + def initialize(reactor, logger, snmp_settings) @reactor = reactor + @logger = logger @snmp_settings = snmp_settings @defer = nil @@ -46,12 +47,14 @@ def connect(defer = @reactor.defer) # Attempt to connect to offload process @connection.connect('127.0.0.1', 30001) { |socket| + @logger.debug "connected to offload task" @connected = true defer.resolve(true) socket.start_read write(:client, @snmp_settings) }.finally { + @logger.debug "disconnected from offload task" @defer&.reject RuntimeError.new('connection lost') @defer = nil @@ -131,8 +134,8 @@ def initialize Libuv::Reactor.default.schedule { start_process } if !defined?(::Rake::Task) end - def register(reactor, snmp_settings) - Proxy.new(reactor, snmp_settings) + def register(reactor, logger, snmp_settings) + Proxy.new(reactor, logger, snmp_settings) end def start_process diff --git a/modules/cisco/switch/snooping_catalyst_snmp.rb b/modules/cisco/switch/snooping_catalyst_snmp.rb index 613a0f87..1500ebef 100644 --- a/modules/cisco/switch/snooping_catalyst_snmp.rb +++ b/modules/cisco/switch/snooping_catalyst_snmp.rb @@ -331,7 +331,7 @@ def query_interface_status end def query_connected_devices - if @client.processing + if @client&.processing logger.debug 'Skipping device query... busy processing' return end @@ -385,7 +385,7 @@ def rebuild_client if @client.is_a? ::Cisco::CatalystOffloader::Proxy @client.new_setting(@snmp_settings) else - ::Cisco::CatalystOffloader.instance.register(thread, @snmp_settings) + ::Cisco::CatalystOffloader.instance.register(thread, logger, @snmp_settings) end else @client.disconnect if @client.is_a? ::Cisco::CatalystOffloader::Proxy From f2866a366a97e88714c6678b348ffb87c2b92093 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 09:22:51 +1100 Subject: [PATCH 1059/1752] (cisco:snmp) fix offload rake task --- lib/tasks/offload.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/offload.rake b/lib/tasks/offload.rake index 1962f9f3..2f32d646 100644 --- a/lib/tasks/offload.rake +++ b/lib/tasks/offload.rake @@ -12,7 +12,7 @@ namespace :offload do port = args[:tcp_port].to_i - Libuv.reactor.run do { |reactor| + Libuv.reactor.run do |reactor| reactor.tcp.bind('127.0.0.1', port) do |client| tokeniser = ::UV::AbstractTokenizer.new(::Cisco::CatalystOffloader::ParserSettings) snmp_client = nil @@ -38,6 +38,6 @@ namespace :offload do end client.start_read end - } + end end end From 650cc541b458d97d2cde0f384745939005c501df Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 09:27:59 +1100 Subject: [PATCH 1060/1752] (cisco:snmp) add debugging output --- lib/tasks/offload.rake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/tasks/offload.rake b/lib/tasks/offload.rake index 2f32d646..984a41ff 100644 --- a/lib/tasks/offload.rake +++ b/lib/tasks/offload.rake @@ -11,9 +11,11 @@ namespace :offload do require File.join(__dir__, '../../modules/cisco/catalyst_offloader.rb') port = args[:tcp_port].to_i + puts "offload catalyst snmp binding to port #{port}" Libuv.reactor.run do |reactor| - reactor.tcp.bind('127.0.0.1', port) do |client| + tcp = reactor.tcp + tcp.bind('127.0.0.1', port) do |client| tokeniser = ::UV::AbstractTokenizer.new(::Cisco::CatalystOffloader::ParserSettings) snmp_client = nil @@ -38,6 +40,11 @@ namespace :offload do end client.start_read end + tcp.catch do |e| + STDOUT.puts "failed to bind port\n#{e.message}\n#{e.backtrace.join("\n")}" + end end + + puts "offload catalyst snmp closed..." end end From e834af2054aed7aedc9d018575c442b3a1ffeae8 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 10:22:35 +1100 Subject: [PATCH 1061/1752] (cisco:snmp) fix message processing and add additional debugging output --- lib/tasks/offload.rake | 10 +++++++--- modules/cisco/catalyst_offloader.rb | 8 +++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/tasks/offload.rake b/lib/tasks/offload.rake index 984a41ff..5268c1e2 100644 --- a/lib/tasks/offload.rake +++ b/lib/tasks/offload.rake @@ -15,18 +15,20 @@ namespace :offload do Libuv.reactor.run do |reactor| tcp = reactor.tcp - tcp.bind('127.0.0.1', port) do |client| - tokeniser = ::UV::AbstractTokenizer.new(::Cisco::CatalystOffloader::ParserSettings) + tcp.bind('0.0.0.0', port) do |client| + tokeniser = ::UV::AbstractTokenizer.new(::Cisco::CatalystOffloader::Proxy::ParserSettings) snmp_client = nil client.progress do |data| tokeniser.extract(data).each do |response| begin - args = Marshal.load(response) + args = Marshal.load(response[4..-1]) if args[0] == :client + STDOUT.puts "reloading client for #{args[1][:hostname]}" snmp_client&.close snmp_client = ::Cisco::Switch::CatalystSNMPClient.new(reactor, args[1]) else + STDOUT.puts "received request #{args[0]}" retval = snmp_client.__send__(*args) if args[0].to_s.start_with? 'query' msg = Marshal.dump(retval) @@ -38,11 +40,13 @@ namespace :offload do end end end + client.enable_nodelay client.start_read end tcp.catch do |e| STDOUT.puts "failed to bind port\n#{e.message}\n#{e.backtrace.join("\n")}" end + tcp.listen(1024) end puts "offload catalyst snmp closed..." diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index ab556de7..7c01ef44 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -38,8 +38,9 @@ def connect(defer = @reactor.defer) @tokeniser.extract(data).each do |response| next unless @defer begin - @defer.resolve Marshal.load(response) + @defer.resolve Marshal.load(response[4..-1]) rescue => e + @logger.debug { "OFFLOAD: error loading response #{e.message}" } @defer.reject e end end @@ -47,14 +48,14 @@ def connect(defer = @reactor.defer) # Attempt to connect to offload process @connection.connect('127.0.0.1', 30001) { |socket| - @logger.debug "connected to offload task" + @logger.debug "OFFLOAD: connected to offload task" @connected = true defer.resolve(true) socket.start_read write(:client, @snmp_settings) }.finally { - @logger.debug "disconnected from offload task" + @logger.debug "OFFLOAD: disconnected from offload task" @defer&.reject RuntimeError.new('connection lost') @defer = nil @@ -76,6 +77,7 @@ def write(*args) end def disconnect + @logger.debug "OFFLOAD: worker termination requested" @terminated = true @connection.close end From 56f1dabaf308835d8ba556100f7d6e4f0e5f0047 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 11:33:47 +1100 Subject: [PATCH 1062/1752] (cisco:snmp) ensure client doesn't reconnect needlessly --- lib/tasks/offload.rake | 9 ++++++++- modules/cisco/catalyst_offloader.rb | 12 +++++++++++- modules/cisco/switch/snooping_catalyst_snmp.rb | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/tasks/offload.rake b/lib/tasks/offload.rake index 5268c1e2..e1e57fba 100644 --- a/lib/tasks/offload.rake +++ b/lib/tasks/offload.rake @@ -11,6 +11,7 @@ namespace :offload do require File.join(__dir__, '../../modules/cisco/catalyst_offloader.rb') port = args[:tcp_port].to_i + connected = 0 puts "offload catalyst snmp binding to port #{port}" Libuv.reactor.run do |reactor| @@ -18,13 +19,15 @@ namespace :offload do tcp.bind('0.0.0.0', port) do |client| tokeniser = ::UV::AbstractTokenizer.new(::Cisco::CatalystOffloader::Proxy::ParserSettings) snmp_client = nil + connected += 1 + STDOUT.puts "total connections: #{connected}" client.progress do |data| tokeniser.extract(data).each do |response| begin args = Marshal.load(response[4..-1]) if args[0] == :client - STDOUT.puts "reloading client for #{args[1][:hostname]}" + STDOUT.puts "reloading client with #{args[1]}" snmp_client&.close snmp_client = ::Cisco::Switch::CatalystSNMPClient.new(reactor, args[1]) else @@ -46,6 +49,10 @@ namespace :offload do tcp.catch do |e| STDOUT.puts "failed to bind port\n#{e.message}\n#{e.backtrace.join("\n")}" end + tcp.finally do + connected -= 1 + STDOUT.puts "total connections: #{connected}" + end tcp.listen(1024) end diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 7c01ef44..1baf7eff 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -66,7 +66,11 @@ def connect(defer = @reactor.defer) defer.reject RuntimeError.new('failed to connect') end - connect(defer) unless @terminated + if !@terminated + @reactor.scheduler.in(4000) do + connect(defer) unless @terminated + end + end } end @@ -115,6 +119,8 @@ def query_index_mappings @connecting.then { write(:query_index_mappings) } @defer = @reactor.defer @defer.promise.value + ensure + @defer = nil end def query_interface_status @@ -122,6 +128,8 @@ def query_interface_status @connecting.then { write(:query_interface_status) } @defer = @reactor.defer @defer.promise.value + ensure + @defer = nil end def query_snooping_bindings @@ -129,6 +137,8 @@ def query_snooping_bindings @connecting.then { write(:query_snooping_bindings) } @defer = @reactor.defer @defer.promise.value + ensure + @defer = nil end end diff --git a/modules/cisco/switch/snooping_catalyst_snmp.rb b/modules/cisco/switch/snooping_catalyst_snmp.rb index 1500ebef..77fb5baa 100644 --- a/modules/cisco/switch/snooping_catalyst_snmp.rb +++ b/modules/cisco/switch/snooping_catalyst_snmp.rb @@ -384,6 +384,7 @@ def rebuild_client @client = if @offload_snmp if @client.is_a? ::Cisco::CatalystOffloader::Proxy @client.new_setting(@snmp_settings) + @client else ::Cisco::CatalystOffloader.instance.register(thread, logger, @snmp_settings) end From eea74130ce3a99156e0324a6fd8a1997c3bbcd63 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 13:29:31 +1100 Subject: [PATCH 1063/1752] (cisco:snmp) complete offloader launch code --- lib/tasks/offload.rake | 4 +++- modules/cisco/catalyst_offloader.rb | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/tasks/offload.rake b/lib/tasks/offload.rake index e1e57fba..aa7ab507 100644 --- a/lib/tasks/offload.rake +++ b/lib/tasks/offload.rake @@ -19,6 +19,7 @@ namespace :offload do tcp.bind('0.0.0.0', port) do |client| tokeniser = ::UV::AbstractTokenizer.new(::Cisco::CatalystOffloader::Proxy::ParserSettings) snmp_client = nil + client_host = nil connected += 1 STDOUT.puts "total connections: #{connected}" @@ -28,10 +29,11 @@ namespace :offload do args = Marshal.load(response[4..-1]) if args[0] == :client STDOUT.puts "reloading client with #{args[1]}" + client_host = args[1][:host] snmp_client&.close snmp_client = ::Cisco::Switch::CatalystSNMPClient.new(reactor, args[1]) else - STDOUT.puts "received request #{args[0]}" + STDOUT.puts "received request #{client_host} #{args[0]}" retval = snmp_client.__send__(*args) if args[0].to_s.start_with? 'query' msg = Marshal.dump(retval) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 1baf7eff..659efcf3 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -142,21 +142,21 @@ def query_snooping_bindings end end - def initialize - Libuv::Reactor.default.schedule { start_process } if !defined?(::Rake::Task) - end - def register(reactor, logger, snmp_settings) + Libuv::Reactor.default.schedule { start_process } if !defined?(::Rake::Task) && @worker.nil? Proxy.new(reactor, logger, snmp_settings) end def start_process + return if @worker + # Start the rake task that will do the SNMP processing - @process = @reactor.spawn('rake', args: "offload:catalyst_snmp['30001']") - @process.finally do + @worker = @reactor.spawn('rake', args: "offload:catalyst_snmp['30001']", mode: :inherit) + @worker.finally do STDOUT.puts "offload task closed! Restarting in 5s..." @reactor.scheduler.in('5s') do - @process = @reactor.spawn('rake', args: "offload:catalyst_snmp['30001']") + @worker = nil + start_process end end end From 41aa7ea655d630832de8ebf9370212571afaa363 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 13:51:50 +1100 Subject: [PATCH 1064/1752] (cisco:snmp) complete offloader spawn --- modules/cisco/catalyst_offloader.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 659efcf3..c2668506 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -149,12 +149,13 @@ def register(reactor, logger, snmp_settings) def start_process return if @worker + reactor = Libuv::Reactor.default # Start the rake task that will do the SNMP processing - @worker = @reactor.spawn('rake', args: "offload:catalyst_snmp['30001']", mode: :inherit) + @worker = reactor.spawn('rake', args: "offload:catalyst_snmp['30001']", mode: :inherit) @worker.finally do STDOUT.puts "offload task closed! Restarting in 5s..." - @reactor.scheduler.in('5s') do + reactor.scheduler.in('5s') do @worker = nil start_process end From a874303955d14bc7633ff4ce200f90c8c5a10ef7 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 14:38:30 +1100 Subject: [PATCH 1065/1752] (cisco:snmp) fix spawn code --- modules/cisco/catalyst_offloader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index c2668506..12af69db 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -152,7 +152,7 @@ def start_process reactor = Libuv::Reactor.default # Start the rake task that will do the SNMP processing - @worker = reactor.spawn('rake', args: "offload:catalyst_snmp['30001']", mode: :inherit) + @worker = reactor.spawn('rake', args: "offload:catalyst_snmp[30001]", mode: :inherit) @worker.finally do STDOUT.puts "offload task closed! Restarting in 5s..." reactor.scheduler.in('5s') do From c5bc2a34221d1a24bac1d18d434b3a129c41cd04 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 6 Dec 2018 14:55:12 +1100 Subject: [PATCH 1066/1752] (cisco:snmp) fix failure error messages --- lib/tasks/offload.rake | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/tasks/offload.rake b/lib/tasks/offload.rake index aa7ab507..1544493c 100644 --- a/lib/tasks/offload.rake +++ b/lib/tasks/offload.rake @@ -41,19 +41,19 @@ namespace :offload do end end rescue => e - STDOUT.puts "failed to marshal request\n#{e.message}\n#{e.backtrace.join("\n")}" + STDOUT.puts "failed to marshal request\n#{e.message}\n#{e.backtrace&.join("\n")}" end end end client.enable_nodelay client.start_read + client.finally do + connected -= 1 + STDOUT.puts "total connections: #{connected}" + end end tcp.catch do |e| - STDOUT.puts "failed to bind port\n#{e.message}\n#{e.backtrace.join("\n")}" - end - tcp.finally do - connected -= 1 - STDOUT.puts "total connections: #{connected}" + STDOUT.puts "failed to bind port\n#{e.message}\n#{e.backtrace&.join("\n")}" end tcp.listen(1024) end From f3f72762b0fba88f757fe9e441ae7f2193d587ac Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 10 Dec 2018 14:27:24 +1100 Subject: [PATCH 1067/1752] [Exch Driver] Add vistitor supprt --- lib/microsoft/exchange.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index f740c43d..6a212ca8 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -214,6 +214,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. # events = @ews_client.get_item(:calendar, opts = {act_as: email}).items events.each{|event| event.get_all_properties! + internal_domain = Mail::Address.new(event.organizer.email).domain booking = {} booking[:subject] = event.subject booking[:title] = event.subject @@ -232,9 +233,11 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. booking[:room_id] = attendee.email next end + email_domain = Mail::Address.new(attendee.email).domain { name: attendee.name, - email: attendee.email + email: attendee.email, + visitor: (internal_domain == email_domain) } }.compact if event.required_attendees if @hide_all_day_bookings From 83493204509843434a4b3f02ed839ae548419b6a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 10 Dec 2018 14:27:38 +1100 Subject: [PATCH 1068/1752] [Exch Driver] Add vistitor supprt --- lib/microsoft/exchange.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 6a212ca8..29a1f64c 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -237,7 +237,8 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. { name: attendee.name, email: attendee.email, - visitor: (internal_domain == email_domain) + visitor: (internal_domain == email_domain), + organisation: attendee.email.split('@')[1..-1].join("").split('.')[0].capitalize } }.compact if event.required_attendees if @hide_all_day_bookings From d1034af67cfc49ee247d83004f09c05d9773315e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 10 Dec 2018 14:31:12 +1100 Subject: [PATCH 1069/1752] [Exch Driver] Add vistitor supprt --- lib/microsoft/exchange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/exchange.rb b/lib/microsoft/exchange.rb index 29a1f64c..c364ee95 100644 --- a/lib/microsoft/exchange.rb +++ b/lib/microsoft/exchange.rb @@ -237,7 +237,7 @@ def get_bookings(email:, start_param:DateTime.now.midnight, end_param:(DateTime. { name: attendee.name, email: attendee.email, - visitor: (internal_domain == email_domain), + visitor: (internal_domain != email_domain), organisation: attendee.email.split('@')[1..-1].join("").split('.')[0].capitalize } }.compact if event.required_attendees From 43b006fdf49005455b782bb5431988d5e0e277b5 Mon Sep 17 00:00:00 2001 From: Viv B Date: Mon, 10 Dec 2018 15:09:48 +1100 Subject: [PATCH 1070/1752] (panasonic:lcd) add polling enable setting --- modules/panasonic/lcd/protocol2.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/panasonic/lcd/protocol2.rb b/modules/panasonic/lcd/protocol2.rb index 03eefe58..f7c25247 100755 --- a/modules/panasonic/lcd/protocol2.rb +++ b/modules/panasonic/lcd/protocol2.rb @@ -48,6 +48,7 @@ def on_load def on_update @username = setting(:username) || 'dispadmin' @password = setting(:password) || '@Panasonic' + @polling_enabled = setting(:polling_enabled) end def connected @@ -150,7 +151,7 @@ def volume? def do_poll power?(priority: 0).then do - if self[:power] + if self[:power] && @polling_enabled muted? volume? end From 86547222a96bbf9a7cc9d31e5de9bd1538b18b9c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 10 Dec 2018 16:29:32 +1100 Subject: [PATCH 1071/1752] [42i API] Store client token properly --- lib/fortytwo/it_api.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 0902410a..9f55d15b 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -146,7 +146,20 @@ def add_visitor(owner_id:, visitor_object:) protected def api_token - # For now just get a new token each time. In the future we will store the token and check if it expires - @api_client.client_credentials.get_token.token + # Get the token + token = User.bucket.get("fortytwo-token", quiet: true) + if token.nil? || token[:expiry] >= Time.now.to_i + # Get a new token and save it + new_token = @api_client.client_credentials.get_token + new_token_model = { + token: new_token.token, + expiry: Time.now.to_i + new_token.expires_in, + } + User.bucket.set("fortytwo-token", new_token_model) + return new_token.token + else + # Use the existing token + token[:token] + end end end From 7af225783babe4c055ba03c929e5614d277ce078 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 11 Dec 2018 12:12:33 +1100 Subject: [PATCH 1072/1752] (cisco:offloader) recreate connection if request takes too long --- modules/cisco/catalyst_offloader.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 12af69db..549acabd 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -78,6 +78,14 @@ def write(*args) raise "connection not ready" unless @connected msg = Marshal.dump(args) @connection.write("#{[msg.length].pack('V')}#{msg}") + defer = @defer + sched = @reactor.scheduler.in(180_000) do + defer&.reject RuntimeError.new('request timeout') + @defer = nil if @defer == defer + @logger.debug "OFFLOAD: closing connection due to timeout" + @connection.close + end + defer.finally { sched.cancel } end def disconnect From 0c900649fae7b251b8807b234cc1f6b80debfd0a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 11 Dec 2018 13:29:18 +1100 Subject: [PATCH 1073/1752] (cisco:offloader) no timeout for some requests --- modules/cisco/catalyst_offloader.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 549acabd..c1893d41 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -77,8 +77,10 @@ def connect(defer = @reactor.defer) def write(*args) raise "connection not ready" unless @connected msg = Marshal.dump(args) - @connection.write("#{[msg.length].pack('V')}#{msg}") defer = @defer + promise = @connection.write("#{[msg.length].pack('V')}#{msg}") + return promise if defer.nil? + sched = @reactor.scheduler.in(180_000) do defer&.reject RuntimeError.new('request timeout') @defer = nil if @defer == defer From deae81f23f739609f5c11b12dddfdcaec68e84e1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 11 Dec 2018 13:52:19 +1100 Subject: [PATCH 1074/1752] (cisco:offloader) improve order of operations --- modules/cisco/catalyst_offloader.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index c1893d41..e51cf2ab 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -107,27 +107,27 @@ def promise def new_setting(snmp_settings) @snmp_settings = snmp_settings - @connecting.then { write(:client, snmp_settings) } @defer&.reject RuntimeError.new('client closed by user') @defer = nil + @connecting.then { write(:client, snmp_settings) } end def new_client - @connecting.then { write(:new_client) } @defer&.reject RuntimeError.new('client closed by user') @defer = nil + @connecting.then { write(:new_client) } end def close - @connecting.then { write(:close) } @defer&.reject RuntimeError.new('client closed by user') @defer = nil + @connecting.then { write(:close) } end def query_index_mappings raise "processing in progress" if @defer - @connecting.then { write(:query_index_mappings) } @defer = @reactor.defer + @connecting.then { write(:query_index_mappings) } @defer.promise.value ensure @defer = nil @@ -135,8 +135,8 @@ def query_index_mappings def query_interface_status raise "processing in progress" if @defer - @connecting.then { write(:query_interface_status) } @defer = @reactor.defer + @connecting.then { write(:query_interface_status) } @defer.promise.value ensure @defer = nil @@ -144,8 +144,8 @@ def query_interface_status def query_snooping_bindings raise "processing in progress" if @defer - @connecting.then { write(:query_snooping_bindings) } @defer = @reactor.defer + @connecting.then { write(:query_snooping_bindings) } @defer.promise.value ensure @defer = nil From a58591a84d602bcf0e7984ae9c612198f1d38f82 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 11 Dec 2018 14:04:59 +1100 Subject: [PATCH 1075/1752] (cisco:offloader) prevent timeout overlaps i.e. ensure we only disconnect from the defer that is of interest --- modules/cisco/catalyst_offloader.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index e51cf2ab..664f1815 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -82,10 +82,12 @@ def write(*args) return promise if defer.nil? sched = @reactor.scheduler.in(180_000) do - defer&.reject RuntimeError.new('request timeout') - @defer = nil if @defer == defer - @logger.debug "OFFLOAD: closing connection due to timeout" - @connection.close + if @defer == defer + defer&.reject RuntimeError.new('request timeout') + @defer = nil + @logger.debug "OFFLOAD: closing connection due to timeout" + @connection.close + end end defer.finally { sched.cancel } end From 4131267df69c3fc46099e67d4a071893083dd7cf Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 11 Dec 2018 14:18:16 +1100 Subject: [PATCH 1076/1752] (cisco:offloader) increase timeout and use new_client request --- modules/cisco/catalyst_offloader.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 664f1815..09725c0b 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -81,12 +81,12 @@ def write(*args) promise = @connection.write("#{[msg.length].pack('V')}#{msg}") return promise if defer.nil? - sched = @reactor.scheduler.in(180_000) do + sched = @reactor.scheduler.in(240_000) do if @defer == defer defer&.reject RuntimeError.new('request timeout') @defer = nil @logger.debug "OFFLOAD: closing connection due to timeout" - @connection.close + new_client end end defer.finally { sched.cancel } From 0bb96e35a09210426e0bcc9c55dda8989aaa9bf7 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 11 Dec 2018 14:59:01 +1100 Subject: [PATCH 1077/1752] (cisco:offloader) revert timeout 3min is enough time to walk the switch ports --- modules/cisco/catalyst_offloader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 09725c0b..65a76b24 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -81,7 +81,7 @@ def write(*args) promise = @connection.write("#{[msg.length].pack('V')}#{msg}") return promise if defer.nil? - sched = @reactor.scheduler.in(240_000) do + sched = @reactor.scheduler.in(180_000) do if @defer == defer defer&.reject RuntimeError.new('request timeout') @defer = nil From 02bd69cb652146f594c4c4985d2f083542b34787 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 11 Dec 2018 15:38:10 +1100 Subject: [PATCH 1078/1752] (cisco:offloader) ensure errors are propagated between the processes --- lib/tasks/offload.rake | 7 ++++++- modules/cisco/catalyst_offloader.rb | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/tasks/offload.rake b/lib/tasks/offload.rake index 1544493c..ac43bb5e 100644 --- a/lib/tasks/offload.rake +++ b/lib/tasks/offload.rake @@ -34,7 +34,12 @@ namespace :offload do snmp_client = ::Cisco::Switch::CatalystSNMPClient.new(reactor, args[1]) else STDOUT.puts "received request #{client_host} #{args[0]}" - retval = snmp_client.__send__(*args) + retval = nil + begin + retval = snmp_client.__send__(*args) + rescue => e + retval = e + end if args[0].to_s.start_with? 'query' msg = Marshal.dump(retval) client.write("#{[msg.length].pack('V')}#{msg}") diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 65a76b24..417c49c7 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -38,7 +38,12 @@ def connect(defer = @reactor.defer) @tokeniser.extract(data).each do |response| next unless @defer begin - @defer.resolve Marshal.load(response[4..-1]) + response = Marshal.load(response[4..-1]) + if response.is_a? Exception + @defer.reject response + else + @defer.resolve response + end rescue => e @logger.debug { "OFFLOAD: error loading response #{e.message}" } @defer.reject e From 8a18ac8abc80c4cc34754f69a2096b6da8201fda Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 12 Dec 2018 10:54:08 +1100 Subject: [PATCH 1079/1752] [42i] Fix conditional about token --- lib/fortytwo/it_api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 9f55d15b..3f16fb38 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -148,7 +148,7 @@ def add_visitor(owner_id:, visitor_object:) def api_token # Get the token token = User.bucket.get("fortytwo-token", quiet: true) - if token.nil? || token[:expiry] >= Time.now.to_i + if token.nil? || token[:expiry] <= Time.now.to_i # Get a new token and save it new_token = @api_client.client_credentials.get_token new_token_model = { From ece8f64cd1dee259d4785b8993e62ca32659bfad Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 12 Dec 2018 11:14:07 +1100 Subject: [PATCH 1080/1752] [42i] Fix visitor filtering --- lib/fortytwo/it_api.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 3f16fb38..3507dcea 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -123,11 +123,10 @@ def get_visitors(owner_id: nil, limit: 100) query_params = { '$top': limit } - # # TODO:: ONCE STEPHEN GETS BACK TO US - # if owner_id - # query_params['$filter'] = - # end - # query_params['$filter'] = filter_param if defined? filter_param + if owner_id + query_params['$filter'] = "Bookers/any(b:b eq '#{owner_id}')" + end + query_params['$filter'] = filter_param if defined? filter_param response = api_request(request_method: 'get', endpoint: 'visitor', query: query_params) JSON.parse(response.body)['value'] end From 1d77bb65314d9883d1e11c56aaf28a9d98280d5c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 12 Dec 2018 11:21:32 +1100 Subject: [PATCH 1081/1752] [42i] Fix visitor filtering --- lib/fortytwo/it_api.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 3507dcea..f93a7dc8 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -136,8 +136,7 @@ def add_visitor(owner_id:, visitor_object:) query_params = { 'bookerExternalId': owner_id } - post_data = visitor_object - response = api_request(request_method: 'post', endpoint: 'visitor', query: query_params, data: post_data) + response = api_request(request_method: 'post', endpoint: 'visitor', query: query_params, data: visitor_object) JSON.parse(response.body) end From 83da79c89dee50ad17c8025eb6106d462cce2f87 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 13 Dec 2018 09:37:35 +1100 Subject: [PATCH 1082/1752] (sony:visca) provide a setting for adjusting default timeouts --- modules/sony/camera/visca.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/sony/camera/visca.rb b/modules/sony/camera/visca.rb index c6e4e031..c7773249 100644 --- a/modules/sony/camera/visca.rb +++ b/modules/sony/camera/visca.rb @@ -45,16 +45,19 @@ def on_load on_update end - + def on_unload end - + def on_update + timeout = setting(:timeout) || 5000 + defaults timeout: timeout + @presets = setting(:presets) || {} self[:presets] = @presets.keys self[:invert] = setting(:invert) end - + def connected schedule.every('60s') do logger.debug "-- Polling Sony Camera" @@ -67,7 +70,7 @@ def connected end end end - + def disconnected # Disconnected will be called before connect if initial connect fails schedule.clear @@ -179,7 +182,7 @@ def joystick(pan_speed, tilt_speed) elsif pan_speed < 0 dir_hori = :left end - + dir_vert = nil if tilt_speed > 0 dir_vert = :up @@ -201,7 +204,7 @@ def joystick(pan_speed, tilt_speed) def zoom(position, focus = -1) val = in_range(position.to_i, self[:zoom_max], self[:zoom_min]) - + cmd = "\x04\x47" # Format the zoom focus values as required @@ -366,4 +369,3 @@ def received(data, resolve, command) :success end end - From dbc7a794f3197aaa351756905e90122b32db4a5e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 13 Dec 2018 11:20:34 +1100 Subject: [PATCH 1083/1752] (cisco:offloader) fix scheduler cancel --- modules/cisco/catalyst_offloader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 417c49c7..64e87212 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -94,7 +94,7 @@ def write(*args) new_client end end - defer.finally { sched.cancel } + defer.promise.finally { sched.cancel } end def disconnect From 649741192fe11a4ca5ac8573f7ef0f31dd92ee6c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 15 Dec 2018 13:21:51 +1000 Subject: [PATCH 1084/1752] (cisco:sx80,sx20,room_kit) track selfview fullscreen status --- modules/cisco/collaboration_endpoint/room_kit.rb | 1 + modules/cisco/collaboration_endpoint/sx20.rb | 1 + modules/cisco/collaboration_endpoint/sx80.rb | 3 +-- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 934e3805..4f607697 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -52,6 +52,7 @@ def connected status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals status 'Video Selfview Mode' => :selfview + status 'Video Selfview FullScreenMode' => :selfview_fullscreen status 'Video Input' => :video_input status 'Video Output' => :video_output status 'Standby State' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index 5e01812c..e1f944e0 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -48,6 +48,7 @@ def connected status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals status 'Video Selfview Mode' => :selfview + status 'Video Selfview FullScreenMode' => :selfview_fullscreen status 'Video Input' => :video_input status 'Video Output' => :video_output status 'Standby State' => :standby diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index dc7f75d5..8552e34d 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -32,14 +32,12 @@ def connected end self[:calls] = {} - self[:in_call] = false register_feedback '/Status/Call' do |call| calls = self[:calls].deep_merge call calls.reject! do |_, props| props[:status] == :Idle || props.include?(:ghost) end self[:calls] = calls - self[:in_call] = calls.present? end end @@ -52,6 +50,7 @@ def connected status 'Conference Presentation Mode' => :presentation status 'Peripherals ConnectedDevice' => :peripherals status 'Video Selfview Mode' => :selfview + status 'Video Selfview FullScreenMode' => :selfview_fullscreen status 'Video Input' => :video_input status 'Video Output' => :video_output status 'Standby State' => :standby From 08ecd39d01805afd79deccca2817768ac6ee12bf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 16 Dec 2018 20:53:54 +1100 Subject: [PATCH 1085/1752] [ITAPI] Add sms method --- lib/fortytwo/it_api.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index f93a7dc8..0677459c 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -140,6 +140,22 @@ def add_visitor(owner_id:, visitor_object:) JSON.parse(response.body) end + def send_sms(to:, body:, from:'IT Concierge') + # Change to aussie number + if to[0] == '0' + to = '+61' + to[1..-1] + end + + # If we have a query and the query has at least one space + sms_object = { + 'to': to, + 'from': from, + 'body': body + } + response = api_request(request_method: 'post', endpoint: 'visitor', data: sms_object) + JSON.parse(response.body) + end + protected From ed9ba4c2ee463236d3497d55e135a59557de2f1a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 16 Dec 2018 21:13:54 +1100 Subject: [PATCH 1086/1752] [ITAPI] Fix SMS method --- lib/fortytwo/it_api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index 0677459c..bb403565 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -152,7 +152,7 @@ def send_sms(to:, body:, from:'IT Concierge') 'from': from, 'body': body } - response = api_request(request_method: 'post', endpoint: 'visitor', data: sms_object) + response = api_request(request_method: 'post', endpoint: 'sms', data: sms_object) JSON.parse(response.body) end From a4697ce034200bae82d7c2399840bb89ffd65c77 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 16 Dec 2018 21:16:59 +1100 Subject: [PATCH 1087/1752] [ITAPI] Shorten SMS from field --- lib/fortytwo/it_api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fortytwo/it_api.rb b/lib/fortytwo/it_api.rb index bb403565..af3b8763 100644 --- a/lib/fortytwo/it_api.rb +++ b/lib/fortytwo/it_api.rb @@ -140,7 +140,7 @@ def add_visitor(owner_id:, visitor_object:) JSON.parse(response.body) end - def send_sms(to:, body:, from:'IT Concierge') + def send_sms(to:, body:, from:'Concierge') # Change to aussie number if to[0] == '0' to = '+61' + to[1..-1] From 262bddce396ed3b949bb7740a5722f790c710857 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 18 Dec 2018 15:42:05 +1100 Subject: [PATCH 1088/1752] [Slack Conc Module] Fix channel hard coding --- modules/aca/slack_concierge.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 0d87deb7..ffa26b12 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -26,7 +26,7 @@ def on_update create_websocket @threads = [] self[:building] = setting(:building) || :barangaroo - self[:channel] = setting(:channel) || :concierge + self[:`] = setting(:channel) || :concierge end def on_unload @@ -55,7 +55,7 @@ def get_threads while (all_messages.length) == (1000 * page_count) page_count += 1 - all_messages += @client.web_client.channels_history({channel: "CEHDN0QP5", latest: all_messages.last['ts'], count: 1000})['messages'] + all_messages += @client.web_client.channels_history({channel: setting(:channel), latest: all_messages.last['ts'], count: 1000})['messages'] end # Delete messages that aren't threads ((either has no thread_ts OR thread_ts == ts) AND type == bot_message) From 23991a01ccb7b7dbe0a5a7b03c57f6f2a7f6d2fd Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 18 Dec 2018 19:10:07 +1100 Subject: [PATCH 1089/1752] (haivision:makitox) init commit --- modules/haivision/makito_x.rb | 89 +++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 modules/haivision/makito_x.rb diff --git a/modules/haivision/makito_x.rb b/modules/haivision/makito_x.rb new file mode 100644 index 00000000..dc2ffcb7 --- /dev/null +++ b/modules/haivision/makito_x.rb @@ -0,0 +1,89 @@ +require 'shellwords' +require 'protocols/telnet' + +# Documentation: https://aca.im/driver_docs/Haivision/makito_x.pdf + +module Haivision; end +class Haivision::MakitoX + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + descriptive_name 'Haivision Makito X' + generic_name :Streamer + tcp_port 23 + + # Communication settings + tokenize delimiter: "\r", + wait_ready: "login:" + clear_queue_on_disconnect! + + def on_load + on_update + + # Allow more ignores + defaults max_waits: 15 + + # Implement the Telnet protocol + new_telnet_client + config before_buffering: proc { |data| + @telnet.buffer data + } + end + + def on_update + @username = setting(:username) || 'admin' + end + + def connected + @ignore_responses = true + do_send @username, wait: false, delay: 200, priority: 98 + do_send setting(:password), wait: false, delay: 200, priority: 97 + + schedule.every(100_000) { version } + end + + def disconnected + # Ensures the buffer is cleared + new_telnet_client + end + + def version + do_send('haiversion') + end + + protected + + def received(data, resolve, command) + # Ignore the command prompt + return :ignore if data.start_with?(@username) + + logger.debug { "MakitoX sent #{data}" } + + # Ignore login info + if @ignore_responses + if command + @ignore_responses = false + else + return :success + end + end + + # Grab command data + key, value = Shellwords.split(data).join('').split(':') + key = key.underscore + self[key] = value + :success + end + + def new_telnet_client + @telnet = Protocols::Telnet.new do |data| + send data, priority: 99, wait: false + end + end + + def do_send(command, options = {}) + logger.debug { "requesting #{command}" } + send @telnet.prepare(command), options + end +end From e22bf14a3a7a77132e380579df4597cbe0f63e40 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 21 Dec 2018 14:19:15 +1100 Subject: [PATCH 1090/1752] (switch:snooping_catalyst) add support for two g ethernet --- modules/cisco/switch/snooping_catalyst.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/switch/snooping_catalyst.rb b/modules/cisco/switch/snooping_catalyst.rb index 5193abc2..cea3a2c3 100644 --- a/modules/cisco/switch/snooping_catalyst.rb +++ b/modules/cisco/switch/snooping_catalyst.rb @@ -384,6 +384,6 @@ def format(mac) def normalise(interface) # Port-channel == po - interface.downcase.gsub('tengigabitethernet', 'te').gsub('gigabitethernet', 'gi').gsub('fastethernet', 'fa') + interface.downcase.gsub('tengigabitethernet', 'te').gsub('twogigabitethernet', 'tw').gsub('gigabitethernet', 'gi').gsub('fastethernet', 'fa') end end From 22f690dd544889a9b53b449a027fe6085f0ac02c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 7 Jan 2019 16:45:00 +1100 Subject: [PATCH 1091/1752] Fix syntax issue grabbing extensions --- lib/microsoft/office.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 6d764b39..06b22542 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -620,7 +620,7 @@ def bookings_request_by_user(user_id, start_param=Time.now, end_param=(Time.now query_hash = {} query_hash['$top'] = "200" extensions.each do |ext_name| - query['$expand'] = "Extensions($filter=id eq 'Microsoft.OutlookServices.OpenTypeExtension.#{ext_name}')" + query_hash['$expand'] = "Extensions($filter=id eq 'Microsoft.OutlookServices.OpenTypeExtension.#{ext_name}')" end if not start_param.nil? query_hash['startDateTime'] = start_param @@ -683,7 +683,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_events - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML", extensions:[], location:nil) + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, recurrence_end: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML", extensions:[], location:nil) description = String(description) attendees = Array(attendees) @@ -799,8 +799,9 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil daysOfWeek: [start_object.strftime("%A")] }, range: { - type: 'noEnd', - startDate: start_object.strftime("%F") + type: 'endDate', + startDate: start_object.strftime("%F"), + endDate: Time.at(recurrence_end).strftime("%F") } } end From 34efdfe7284986291a5814cdf9ac0f133f727688 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 8 Jan 2019 17:38:36 +1100 Subject: [PATCH 1092/1752] Only sub desc if exists --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 06b22542..6f0ca435 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -887,7 +887,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec event = event.to_json - event.gsub!("X!X!X!",description) + event.gsub!("X!X!X!",description) if description request = graph_request(request_method: 'patch', endpoint: endpoint, data: event, password: @delegated) check_response(request) From 16e5496d18570bc13c62b3b582856f5b6abc44bf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 11 Jan 2019 14:17:58 +1100 Subject: [PATCH 1093/1752] [o365] Add new fields to visitors for kiosk checkin --- lib/microsoft/office.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 6f0ca435..68fa7289 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -245,10 +245,10 @@ def get_contacts(owner_email:, q:nil, limit:nil) endpoint = "/v1.0/users/#{owner_email}/contacts" request = graph_request(request_method: 'get', endpoint: endpoint, query: query_params, password: @delegated) check_response(request) - return format_contacts(JSON.parse(request.body)['value']) + return format_contacts(JSON.parse(request.body)['value'], owner_email) end - def format_contacts(contacts) + def format_contacts(contacts, host) output_contacts = [] contacts.each do |contact| if contact.key?('emailAddresses') && !contact['emailAddresses'].empty? @@ -259,6 +259,9 @@ def format_contacts(contacts) output_format[:phone] = contact['businessPhones'][0] output_format[:organisation] = contact['companyName'] output_format[:email] = contact['emailAddresses'][0]['address'] + output_format[:title] = contact['title'] + output_format[:host] = host + output_format[:purpose] = "Here to see #{host}" output_contacts.push(output_format) end end From 99fb1d93ecbd1e5a2f70899a20b1611707cca40b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 14 Jan 2019 00:15:01 +1100 Subject: [PATCH 1094/1752] [o365] Add title to vis --- lib/microsoft/office.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 68fa7289..70af1ca1 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -296,9 +296,10 @@ def has_contact(owner_email:, contact_email:) end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_contacts - def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, organisation:nil, other:{}) + def add_contact(owner_email:, email:, first_name:, last_name:, phone:nil, organisation:nil, other:{}, title:"") # This data is required so add it unconditionally contact_data = { + title: title, givenName: first_name, surname: last_name, emailAddresses: [{ From 1459cb6c1529f28da14373d7645a8b3e669a8584 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 17 Jan 2019 10:50:31 +1100 Subject: [PATCH 1095/1752] [o365] Add created field to bookings --- lib/microsoft/office.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 70af1ca1..b9c90a80 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -542,6 +542,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['booking_id'] = booking['id'] booking['icaluid'] = booking['iCalUId'] booking['show_as'] = booking['showAs'] + booking['created'] = booking['createdDateTime'] if booking.key?('extensions') && !extensions.empty? booking['extensions'].each do |ext| @@ -606,6 +607,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) end + booking end From a04b86f68c8edc2110179298dda6c86b30e39830 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 22 Jan 2019 11:30:22 +1100 Subject: [PATCH 1096/1752] Fix bug in meeting cancel request --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index abf67b11..a5695d53 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -588,7 +588,7 @@ def delete_ews_booking(delete_at) self[:today].each_with_index do |booking, i| booking_start_object = Time.parse(booking[:Start]) if delete_at_object.to_i == booking_start_object.to_i - response = @client.delete_booking(booking_id: booking[:id], current_user: system) + response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) if response == 200 count += 1 self[:today].delete(i) From f6de9266de3ccf9234312f45d983d33527dae34e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 24 Jan 2019 11:49:35 +1100 Subject: [PATCH 1097/1752] Hacky fix for user domain issue in slack concierge --- modules/aca/slack_concierge.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index ffa26b12..dae9a0f8 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -40,8 +40,7 @@ def send_message(message_text, thread_id) end def update_last_message_read(email_or_thread) - authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id - user = User.find_by_email(authority_id, email_or_thread) + user = find_user(email_or_thread) user = User.find(User.bucket.get("slack-user-#{email_or_thread}", quiet: true)) if user.nil? user.last_message_read = Time.now.to_i * 1000 user.save! @@ -71,8 +70,7 @@ def get_threads # If the message has a username associated (not a status message, etc) # Then grab the details and put it into the message if message.key?('username') - authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id - user = User.find_by_email(authority_id, message['username'] ) + user = find_user(message['username']) messages[i]['email'] = user.email messages[i]['name'] = user.name end @@ -124,6 +122,16 @@ def get_thread(thread_id) protected + def find_user(email) + authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id + user = User.find_by_email(authority_id, email ) + if user.nil? + authority_id = Authority.find_by_domain(ENV['STAFF_DOMAIN']).id + user = User.find_by_email(authority_id, email ) + end + user + end + # Create a realtime WS connection to the Slack servers def create_websocket @@ -171,8 +179,7 @@ def create_websocket new_message = data.to_h if new_message['username'] != 'Concierge' - authority_id = Authority.find_by_domain(ENV['EMAIL_DOMAIN']).id - user = User.find_by_email(authority_id, new_message['username']) + user = find_user(new_message['username']) if user new_message['last_read'] = user.last_message_read new_message['last_sent'] = user.last_message_sent From a917d336089a3fea8efdc47292efc1d048e75dab Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 24 Jan 2019 14:08:10 +1100 Subject: [PATCH 1098/1752] [Zoom] Add base lib --- lib/zoom/meeting.rb | 119 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 lib/zoom/meeting.rb diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb new file mode 100644 index 00000000..114018c5 --- /dev/null +++ b/lib/zoom/meeting.rb @@ -0,0 +1,119 @@ +require 'active_support/time' +require 'uv-rays' +require 'json' +require 'jwt' + +# Key: r5xPDqu-SOa78h3cqgndFg +# Secret: cghDnHqVEeSSoPRy0oQXfBjsrmPWDqm81dNr +# Token: eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJrRUtqRVJvQlIzT0hacXQ4MGp0VVVnIn0.CJWvJmDrdIbJT7EXZtKMOhl-3Y_u-Mdzn5W34725t0U +# JWT (long expiry): eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOm51bGwsImlzcyI6InI1eFBEcXUtU09hNzhoM2NxZ25kRmciLCJleHAiOjE3NjcwMTMxNDAsImlhdCI6MTU0ODAyOTE1Nn0.P5VrccVn3GGardbqajeiILb7pGaMMThFUIlxK0Fcc_Y + + +class Zoom::Meeting + def initialize( + key:, + secret: + ) + @key = key + @secret = secret + @api_domain = 'https://api.zoom.us' + @api_path = '/v2/' + @api_full_path = "#{@api_domain}#{@api_path}" + end + + + # { + # "topic": "Test Meeting", + # "agenda": "Test the Zoom API", + # "type": 2, => Scheduled Meeting + # "start_time": "2018-02-21T16:00:00", + # "duration": 30, + # "timezone": "Australia/Sydney", + # "settings": { + # "host_video": true, + # "participant_video": true, + # "join_before_host": true, + # "mute_upon_entry": true, + # "use_pmi": true, + # "approval_type": 0, + # "audio": "both", + # "auto_recording": "none", + # "enforce_login": false + # } + # } + def create_meeting(owner_email:, start_time:, duration:, topic:, agenda:nil, countries:, timezone:'Australia/Sydney') + zoom_params = { + "topic": topic, + "type": 2, + "start_time": Time.at(start_time).iso8601, + "duration": 30, + "timezone": timezone, + "settings": { + "host_video": true, + "participant_video": true, + "join_before_host": true, + "mute_upon_entry": true, + "use_pmi": true, + "approval_type": 0, + "audio": "both", + "auto_recording": "none", + "enforce_login": false + } + } + zoom_params['agenda'] = agenda if agenda + response = api_request(request_method: 'post', endpoint: "users/#{owner_email}/meetings", data: zoom_params) + JSON.parse(reponse.body) + end + + protected + + def generate_jwt + payload = { + iss: @key, + exp: (Time.now + 259200).to_i + } + + # IMPORTANT: set nil as password parameter + token = JWT.encode payload, @secret, 'HS256' + end + + def api_request(request_method:, endpoint:, data:nil, query:{}, headers:nil) + headers = Hash(headers) + query = Hash(query) + # Convert our request method to a symbol and our data to a JSON string + request_method = request_method.to_sym + data = data.to_json if !data.nil? && data.class != String + + headers['Authorization'] = "Bearer #{api_token}" + headers['Content-Type'] = "application/json" + + api_path = "#{@api_full_path}#{endpoint}" + + log_api_request(request_method, data, query, headers, api_path) + + + api_options = {inactivity_timeout: 25000, keepalive: false} + + api = UV::HttpEndpoint.new(@api_domain, api_options) + response = api.__send__(request_method, path: api_path, headers: headers, body: data, query: query) + + start_timing = Time.now.to_i + response_value = response.value + end_timing = Time.now.to_i + return response_value + end + + def log_api_request(request_method, data, query, headers, graph_path) + STDERR.puts "--------------NEW GRAPH REQUEST------------" + STDERR.puts "#{request_method} to #{graph_path}" + STDERR.puts "Data:" + STDERR.puts data if data + STDERR.puts "Query:" + STDERR.puts query if query + STDERR.puts "Headers:" + STDERR.puts headers if headers + STDERR.puts '--------------------------------------------' + STDERR.flush + end + +end From 923317d8cc68f19f778a407977cea75d3729e7aa Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 24 Jan 2019 14:10:50 +1100 Subject: [PATCH 1099/1752] [Zoom] Add base lib --- lib/zoom/meeting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index 114018c5..c1395a92 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -7,7 +7,7 @@ # Secret: cghDnHqVEeSSoPRy0oQXfBjsrmPWDqm81dNr # Token: eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJrRUtqRVJvQlIzT0hacXQ4MGp0VVVnIn0.CJWvJmDrdIbJT7EXZtKMOhl-3Y_u-Mdzn5W34725t0U # JWT (long expiry): eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOm51bGwsImlzcyI6InI1eFBEcXUtU09hNzhoM2NxZ25kRmciLCJleHAiOjE3NjcwMTMxNDAsImlhdCI6MTU0ODAyOTE1Nn0.P5VrccVn3GGardbqajeiILb7pGaMMThFUIlxK0Fcc_Y - +module Zoom; end; class Zoom::Meeting def initialize( From 59cf068983b7064e157c82e89ffe8d0cc23781df Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 24 Jan 2019 14:14:18 +1100 Subject: [PATCH 1100/1752] [Zoom] Add base lib --- lib/zoom/meeting.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index c1395a92..66fdd73d 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -41,12 +41,12 @@ def initialize( # "enforce_login": false # } # } - def create_meeting(owner_email:, start_time:, duration:, topic:, agenda:nil, countries:, timezone:'Australia/Sydney') + def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, countries:, timezone:'Australia/Sydney') zoom_params = { "topic": topic, "type": 2, "start_time": Time.at(start_time).iso8601, - "duration": 30, + "duration": (duration || 30), "timezone": timezone, "settings": { "host_video": true, @@ -84,7 +84,7 @@ def api_request(request_method:, endpoint:, data:nil, query:{}, headers:nil) request_method = request_method.to_sym data = data.to_json if !data.nil? && data.class != String - headers['Authorization'] = "Bearer #{api_token}" + headers['Authorization'] = "Bearer #{generate_jwt}" headers['Content-Type'] = "application/json" api_path = "#{@api_full_path}#{endpoint}" From 986afb02b56995058c225ba28332afbde0e94f66 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 24 Jan 2019 14:15:05 +1100 Subject: [PATCH 1101/1752] [Zoom] Add base lib --- lib/zoom/meeting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index 66fdd73d..322e00ca 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -62,7 +62,7 @@ def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, } zoom_params['agenda'] = agenda if agenda response = api_request(request_method: 'post', endpoint: "users/#{owner_email}/meetings", data: zoom_params) - JSON.parse(reponse.body) + JSON.parse(response.body) end protected From 4e9be8e2f3fe54e3d7c45ffa0f4df43711b30a55 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 24 Jan 2019 14:25:44 +1100 Subject: [PATCH 1102/1752] [Zoom] Add base lib --- lib/zoom/meeting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index 322e00ca..75dfd940 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -41,7 +41,7 @@ def initialize( # "enforce_login": false # } # } - def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, countries:, timezone:'Australia/Sydney') + def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, countries:[], timezone:'Australia/Sydney') zoom_params = { "topic": topic, "type": 2, From d557c83a6726aee98a1fec06da04f5a0582d6f99 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 24 Jan 2019 16:52:27 +1100 Subject: [PATCH 1103/1752] [SlackC] Fix timing bug --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index dae9a0f8..92b5407c 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -182,7 +182,7 @@ def create_websocket user = find_user(new_message['username']) if user new_message['last_read'] = user.last_message_read - new_message['last_sent'] = user.last_message_sent + new_message['last_sent'] = Time.now.to_i * 1000 end end From a5ccdc1c7ff3efa21216610eeb03b95170b9df0d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 29 Jan 2019 14:21:21 +1100 Subject: [PATCH 1104/1752] [o365] Change ignore check to use icaluid --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b9c90a80..454b757b 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -482,7 +482,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 is_available = true bookings.each_with_index do |booking, i| bookings[i] = extract_booking_data(booking, available_from, available_to, u_id, internal_domain, extensions) - if bookings[i]['free'] == false && bookings[i]['id'] != ignore_booking + if bookings[i]['free'] == false && bookings[i]['icaluid'] != ignore_booking is_available = false end end From 692cac6ce70323b9f13ec5b98e0d532ce6701dcb Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 30 Jan 2019 16:40:14 +1100 Subject: [PATCH 1105/1752] (tracking:desk management) ensure latest user details are returned in case username has changed since the last connection event --- modules/aca/tracking/desk_management.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/aca/tracking/desk_management.rb b/modules/aca/tracking/desk_management.rb index 4f007363..0fba63ba 100644 --- a/modules/aca/tracking/desk_management.rb +++ b/modules/aca/tracking/desk_management.rb @@ -115,10 +115,17 @@ def desk_usage(level) # # @param desk_id [String] the unique id that represents a desk def desk_details(*desk_ids) + bucket = ::Aca::Tracking::SwitchPort.bucket desk_ids.map do |desk_id| switch_ip, port = @desk_mappings[desk_id] if switch_ip - ::Aca::Tracking::SwitchPort.find_by_id("swport-#{switch_ip}-#{port}")&.details + details = ::Aca::Tracking::SwitchPort.find_by_id("swport-#{switch_ip}-#{port}")&.details + + # We do this as the mac ownership can change without a disconnect / reconnect event + mac = details.mac + details.username = bucket.get("macuser-#{mac}", quiet: true) if mac + + details else # Check for manual checkin username = @manual_usage[desk_id] if username From 08d018e89fc6952094e4db87a05996d66b53ad23 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 30 Jan 2019 11:58:50 +0000 Subject: [PATCH 1106/1752] Fix private bookings bug --- modules/aca/office_booking.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index a5695d53..e972ed86 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -667,8 +667,8 @@ def todays_bookings(response, office_organiser_location) subject = booking['subject'] if booking.key?('sensitivity') && ['private','confidential'].include?(booking['sensitivity']) - organizer = "" - subject = "" + organizer = "Private" + subject = "Private" end results.push({ From 24b8949752add6c417c44f9204cc6bc73d1ed2ac Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 31 Jan 2019 11:00:43 +1100 Subject: [PATCH 1107/1752] (cisco:catalyst SNMP offloader) prevent rejections being lost --- modules/cisco/catalyst_offloader.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cisco/catalyst_offloader.rb b/modules/cisco/catalyst_offloader.rb index 64e87212..9c23c135 100644 --- a/modules/cisco/catalyst_offloader.rb +++ b/modules/cisco/catalyst_offloader.rb @@ -87,11 +87,14 @@ def write(*args) return promise if defer.nil? sched = @reactor.scheduler.in(180_000) do + defer.reject RuntimeError.new('request timeout') + if @defer == defer - defer&.reject RuntimeError.new('request timeout') @defer = nil @logger.debug "OFFLOAD: closing connection due to timeout" new_client + else + @logger.debug "OFFLOAD: closing connection due to timeout" end end defer.promise.finally { sched.cancel } From 805e75e508f19848b079a5b974080d2d995fdab1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 31 Jan 2019 14:56:31 +1100 Subject: [PATCH 1108/1752] o365 - Add support for multi room bookings --- lib/microsoft/office.rb | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 454b757b..bb5f1839 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -693,13 +693,20 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil description = String(description) attendees = Array(attendees) + if room_id.class != Array + room_id = [room_id] + end + # Get our room - room = Orchestrator::ControlSystem.find_by_id(room_id) || Orchestrator::ControlSystem.find_by_email(room_id) + rooms = room_id.map do |r_id| + Orchestrator::ControlSystem.find_by_id(r_id) || Orchestrator::ControlSystem.find_by_email(r_id) + end + if endpoint_override endpoint = "/v1.0/users/#{endpoint_override}/events" elsif @mailbox_location == 'room' || current_user.nil? - endpoint = "/v1.0/users/#{room.email}/events" + endpoint = "/v1.0/users/#{rooms[0].email}/events" elsif @mailbox_location == 'user' endpoint = "/v1.0/users/#{current_user[:email]}/events" end @@ -726,14 +733,16 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil } } - # Add the room as an attendee - attendees.push({ - type: "resource", - emailAddress: { - address: room.email, - name: room.name - } - }) + rooms.each do |room| + # Add the room as an attendee + attendees.push({ + type: "resource", + emailAddress: { + address: room.email, + name: room.name + } + }) + end # Add the current user as an attendee if current_user @@ -761,8 +770,8 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil timeZone: TIMEZONE_MAPPING[timezone.to_sym] }, location: { - displayName: room.name, - locationEmailAddress: room.email + displayName: rooms.map{|room| room.name}.join(" and "), + locationEmailAddress: rooms[0].id }, isOrganizer: false, attendees: attendees @@ -791,8 +800,8 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil else event[:organizer] = { emailAddress: { - address: room.email, - name: room.name + address: rooms[0].email, + name: rooms[0].name } } end From f8b0a1b6b6537898f1f70bf8523e27a4c316d6ea Mon Sep 17 00:00:00 2001 From: pkheav Date: Thu, 31 Jan 2019 16:21:39 +1100 Subject: [PATCH 1109/1752] add clear credential functions to lockers --- lib/loqit/lockers.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index f637bd95..eb6d64b7 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -37,7 +37,7 @@ def initialize( serial:, wsdl:, log: false, - log_level: :debug + log_level: :debug ) savon_config = { :wsdl => wsdl, @@ -85,7 +85,7 @@ def list_lockers def list_lockers_detailed(start_number:nil, end_number:nil) all_lockers_detailed = [] - + if start_number (start_number.to_i..end_number.to_i).each do |num| # puts "WORKING ON NUMBER: #{num}" @@ -125,7 +125,7 @@ def show_locker_status(locker_number, retries = 3) retry if retries > 0 end - def open_locker(locker_number) + def open_locker(locker_number) response = @client.call(:open_locker, message: { lockerNumber: locker_number @@ -135,7 +135,7 @@ def open_locker(locker_number) JSON.parse(response) end - def store_credentials(locker_number, user_pin_code, user_card, test_if_free=false) + def store_credentials(locker_number, user_pin_code, user_card, test_if_free=false) payload = { lockerNumber: locker_number, userPincode: user_pin_code @@ -149,7 +149,12 @@ def store_credentials(locker_number, user_pin_code, user_card, test_if_free=fals JSON.parse(response) end - def customer_has_locker(user_card) + # this is untested + def clear_credentials(locker_number) + store_credentials(locker_number, '', '') + end + + def customer_has_locker(user_card) response = @client.call(:customer_has_locker, message: { lockerNumber: locker_number, @@ -174,4 +179,4 @@ def process_requests! end end end -end \ No newline at end of file +end From ea0144dc497007fc45ae4f9d4db56ff4ce127aee Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Jan 2019 16:49:29 +0800 Subject: [PATCH 1110/1752] aca/office/booking panel: allow frontend to set title and host --- modules/aca/office_booking.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index e972ed86..c4ee83b9 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -120,6 +120,9 @@ def on_update self[:booking_hide_timeline] = setting(:booking_hide_timeline) self[:booking_endable] = setting(:booking_endable) self[:timeout] = setting(:timeout) + self[:booking_set_host] = setting(:booking_set_host) + self[:booking_set_title] = setting(:booking_set_title) + self[:booking_search_user] = setting(:booking_search_user) # Skype join button available 2min before the start of a meeting @skype_start_offset = setting(:skype_start_offset) || 120 From 481ca8d8a7c4705ea69db8c547026de1159d3552 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 1 Feb 2019 12:33:47 +1100 Subject: [PATCH 1111/1752] Remove last read message from slack --- modules/aca/slack.rb | 12 ++++-------- modules/aca/slack_concierge.rb | 34 +++++++++++++++++----------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/modules/aca/slack.rb b/modules/aca/slack.rb index fd8bfc78..54e91998 100644 --- a/modules/aca/slack.rb +++ b/modules/aca/slack.rb @@ -34,9 +34,9 @@ def on_message(data) user_id = get_user_id(data.thread_ts) || get_user_id(data.ts) # Assuming the user exists (it should always as they must send the first message)_ - if !user_id.nil? - self["last_message_#{user_id}"] = data - end + # if !user_id.nil? + # self["last_message_#{user_id}"] = data + # end end end @@ -61,7 +61,7 @@ def send_message(message_text) User.bucket.set("slack-user-#{thread_id}", user.id) on_message(message.message) end - user.last_message_sent = Time.now.to_i * 1000 + # user.last_message_sent = Time.now.to_i * 1000 user.save! end @@ -85,16 +85,12 @@ def get_historic_messages messages = JSON.parse(response.body)['messages'] { - last_sent: user.last_message_sent, - last_read: user.last_message_read, thread_id: thread_id, messages: messages } # Otherwise just send back nothing else { - last_sent: user.last_message_sent, - last_read: user.last_message_read, thread_id: nil, messages: [] } diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 92b5407c..f80bca2a 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -39,12 +39,12 @@ def send_message(message_text, thread_id) message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, thread_ts: thread_id, username: 'Concierge' end - def update_last_message_read(email_or_thread) - user = find_user(email_or_thread) - user = User.find(User.bucket.get("slack-user-#{email_or_thread}", quiet: true)) if user.nil? - user.last_message_read = Time.now.to_i * 1000 - user.save! - end + # def update_last_message_read(email_or_thread) + # user = find_user(email_or_thread) + # user = User.find(User.bucket.get("slack-user-#{email_or_thread}", quiet: true)) if user.nil? + # user.last_message_read = Time.now.to_i * 1000 + # user.save! + # end def get_threads @@ -76,13 +76,13 @@ def get_threads end # If the user sending the message exists (this should essentially always be the case) - if !user.nil? - messages[i]['last_sent'] = user.last_message_sent - messages[i]['last_read'] = user.last_message_read - else - messages[i]['last_sent'] = nil - messages[i]['last_read'] = nil - end + # if !user.nil? + # messages[i]['last_sent'] = user.last_message_sent + # messages[i]['last_read'] = user.last_message_read + # else + # messages[i]['last_sent'] = nil + # messages[i]['last_read'] = nil + # end # update_last_message_read(messages[i]['email']) messages[i]['replies'] = get_message(message['ts']) @@ -180,10 +180,10 @@ def create_websocket if new_message['username'] != 'Concierge' user = find_user(new_message['username']) - if user - new_message['last_read'] = user.last_message_read - new_message['last_sent'] = Time.now.to_i * 1000 - end + # if user + # new_message['last_read'] = user.last_message_read + # new_message['last_sent'] = Time.now.to_i * 1000 + # end end new_message_copy = new_message.deep_dup From 04263c1394b6befea1bbb19e4cb865b57f83c2a7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 1 Feb 2019 13:44:04 +1100 Subject: [PATCH 1112/1752] slack - fix order of messages on concierge --- modules/aca/slack_concierge.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index f80bca2a..cf28b5b0 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -168,7 +168,8 @@ def create_websocket # If the ID of the looped message equals the new message thread ID if thread['ts'] == new_message['thread_ts'] new_message['email'] = new_message['username'] - new_threads[i]['replies'].insert(0, new_message) + new_threads[i]['replies'].push(new_message) + new_threads[i]['latest_reply'] = new_message['ts'] self["thread_#{new_message['thread_ts']}"] = new_threads[i]['replies'].dup break end From 8c042e5256268d7ff6edb45c4f34c53baaf70ef0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 1 Feb 2019 14:43:33 +1100 Subject: [PATCH 1113/1752] Put back unread message info --- modules/aca/slack.rb | 12 ++++++++---- modules/aca/slack_concierge.rb | 34 +++++++++++++++++----------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/modules/aca/slack.rb b/modules/aca/slack.rb index 54e91998..fd8bfc78 100644 --- a/modules/aca/slack.rb +++ b/modules/aca/slack.rb @@ -34,9 +34,9 @@ def on_message(data) user_id = get_user_id(data.thread_ts) || get_user_id(data.ts) # Assuming the user exists (it should always as they must send the first message)_ - # if !user_id.nil? - # self["last_message_#{user_id}"] = data - # end + if !user_id.nil? + self["last_message_#{user_id}"] = data + end end end @@ -61,7 +61,7 @@ def send_message(message_text) User.bucket.set("slack-user-#{thread_id}", user.id) on_message(message.message) end - # user.last_message_sent = Time.now.to_i * 1000 + user.last_message_sent = Time.now.to_i * 1000 user.save! end @@ -85,12 +85,16 @@ def get_historic_messages messages = JSON.parse(response.body)['messages'] { + last_sent: user.last_message_sent, + last_read: user.last_message_read, thread_id: thread_id, messages: messages } # Otherwise just send back nothing else { + last_sent: user.last_message_sent, + last_read: user.last_message_read, thread_id: nil, messages: [] } diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index cf28b5b0..d9bdfb08 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -39,12 +39,12 @@ def send_message(message_text, thread_id) message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, thread_ts: thread_id, username: 'Concierge' end - # def update_last_message_read(email_or_thread) - # user = find_user(email_or_thread) - # user = User.find(User.bucket.get("slack-user-#{email_or_thread}", quiet: true)) if user.nil? - # user.last_message_read = Time.now.to_i * 1000 - # user.save! - # end + def update_last_message_read(email_or_thread) + user = find_user(email_or_thread) + user = User.find(User.bucket.get("slack-user-#{email_or_thread}", quiet: true)) if user.nil? + user.last_message_read = Time.now.to_i * 1000 + user.save! + end def get_threads @@ -76,13 +76,13 @@ def get_threads end # If the user sending the message exists (this should essentially always be the case) - # if !user.nil? - # messages[i]['last_sent'] = user.last_message_sent - # messages[i]['last_read'] = user.last_message_read - # else - # messages[i]['last_sent'] = nil - # messages[i]['last_read'] = nil - # end + if !user.nil? + messages[i]['last_sent'] = user.last_message_sent + messages[i]['last_read'] = user.last_message_read + else + messages[i]['last_sent'] = nil + messages[i]['last_read'] = nil + end # update_last_message_read(messages[i]['email']) messages[i]['replies'] = get_message(message['ts']) @@ -181,10 +181,10 @@ def create_websocket if new_message['username'] != 'Concierge' user = find_user(new_message['username']) - # if user - # new_message['last_read'] = user.last_message_read - # new_message['last_sent'] = Time.now.to_i * 1000 - # end + if user + new_message['last_read'] = user.last_message_read + new_message['last_sent'] = Time.now.to_i * 1000 + end end new_message_copy = new_message.deep_dup From d305523ad3fdcc73a1af32c182cafa64e6a6fff0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 1 Feb 2019 14:47:30 +1100 Subject: [PATCH 1114/1752] Put back unread message info --- modules/aca/slack_concierge.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index d9bdfb08..1debbef0 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -40,6 +40,13 @@ def send_message(message_text, thread_id) end def update_last_message_read(email_or_thread) + @threads.each do |thread| + thread['replies'].each_with_index do |reply, i| + if reply['ts'] == email_or_thread + @threads[i]['last_read'] = Time.now.to_i * 1000 + end + end + end user = find_user(email_or_thread) user = User.find(User.bucket.get("slack-user-#{email_or_thread}", quiet: true)) if user.nil? user.last_message_read = Time.now.to_i * 1000 From e9522cea0cefd0276fc9048223c75745d681be68 Mon Sep 17 00:00:00 2001 From: pkheav Date: Fri, 1 Feb 2019 16:47:32 +1100 Subject: [PATCH 1115/1752] added lockers clearing code --- lib/loqit/lockers.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index eb6d64b7..2fe9b01c 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -154,6 +154,26 @@ def clear_credentials(locker_number) store_credentials(locker_number, '', '') end + def clear_lockers + elastic_lockers ||= Elastic.new(Locker) + + date_range = { + range: { + 'doc.end_time' => { + lte: Time.now.to_i + } + } + } + + query = elastic_lockers.query + query.raw_filter(date_range) + results = elastic_lockers.search(query)[:results] + results.each do |r| + clear_credentials(r.locker_id) + # also need to delete the bookings from couchbase + end + end + def customer_has_locker(user_card) response = @client.call(:customer_has_locker, message: { From 2f31cc63207ebc3cedd7a16f5a2994cdc2cd8924 Mon Sep 17 00:00:00 2001 From: pkheav Date: Fri, 1 Feb 2019 17:12:41 +1100 Subject: [PATCH 1116/1752] set released flags of lockers to true when they are cleared --- lib/loqit/lockers.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 2fe9b01c..871deb21 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -170,7 +170,8 @@ def clear_lockers results = elastic_lockers.search(query)[:results] results.each do |r| clear_credentials(r.locker_id) - # also need to delete the bookings from couchbase + r[:released] = true + r.save! end end From 5bb25fc14ea8b0d884a3965f0743f35256eb9b29 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 3 Feb 2019 13:14:32 +1100 Subject: [PATCH 1117/1752] Fix slack issue where user isnt found --- modules/aca/slack_concierge.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 1debbef0..086944f2 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -78,6 +78,7 @@ def get_threads # Then grab the details and put it into the message if message.key?('username') user = find_user(message['username']) + next if user.nil? messages[i]['email'] = user.email messages[i]['name'] = user.name end @@ -190,7 +191,7 @@ def create_websocket user = find_user(new_message['username']) if user new_message['last_read'] = user.last_message_read - new_message['last_sent'] = Time.now.to_i * 1000 + new_message['last_sent'] = user.last_message_sent end end From 1d772401e96789641ce1a1a62911b338a23607db Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 3 Feb 2019 14:42:33 +1100 Subject: [PATCH 1118/1752] Fix slack issue where user isnt found --- modules/aca/slack_concierge.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 086944f2..095133ba 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -39,7 +39,7 @@ def send_message(message_text, thread_id) message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, thread_ts: thread_id, username: 'Concierge' end - def update_last_message_read(email_or_thread) + def (email_or_thread) @threads.each do |thread| thread['replies'].each_with_index do |reply, i| if reply['ts'] == email_or_thread @@ -67,7 +67,15 @@ def get_threads # Delete messages that aren't threads ((either has no thread_ts OR thread_ts == ts) AND type == bot_message) messages = [] all_messages.each do |message| - messages.push(message) if (!message.key?('thread_ts') || message['thread_ts'] == message['ts']) && message['subtype'] == 'bot_message' + if (!message.key?('thread_ts') || message['thread_ts'] == message['ts']) && message['subtype'] == 'bot_message' + if message.key?('username') + user = find_user(message['username']) + next if user.nil? + message['email'] = user.email + message['name'] = user.name + end + messages.push(message) + end end # Output count as if this gets > 1000 we need to paginate From 4a91ebc9cb21ae2c36e98473c73d0b28f17bc606 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 3 Feb 2019 14:54:48 +1100 Subject: [PATCH 1119/1752] Fix slack issue where user isnt found --- modules/aca/slack_concierge.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 095133ba..77fcf6fe 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -39,16 +39,16 @@ def send_message(message_text, thread_id) message = @client.web_client.chat_postMessage channel: setting(:channel), text: message_text, thread_ts: thread_id, username: 'Concierge' end - def (email_or_thread) + def update_last_message_read(thread_id) @threads.each do |thread| thread['replies'].each_with_index do |reply, i| - if reply['ts'] == email_or_thread + if reply['ts'] == thread_id @threads[i]['last_read'] = Time.now.to_i * 1000 end end end - user = find_user(email_or_thread) - user = User.find(User.bucket.get("slack-user-#{email_or_thread}", quiet: true)) if user.nil? + user = find_user(thread_id) + user = User.find(User.bucket.get("slack-user-#{thread_id}", quiet: true)) if user.nil? user.last_message_read = Time.now.to_i * 1000 user.save! end @@ -70,11 +70,12 @@ def get_threads if (!message.key?('thread_ts') || message['thread_ts'] == message['ts']) && message['subtype'] == 'bot_message' if message.key?('username') user = find_user(message['username']) - next if user.nil? - message['email'] = user.email - message['name'] = user.name + if !user.nil? && !User.bucket.get("slack-user-#{message['ts']}", quiet: true).nil? + message['email'] = user.email + message['name'] = user.name + messages.push(message) + end end - messages.push(message) end end From 835c3421cfd8fab8f429da04deda3d14a18c8dc5 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 3 Feb 2019 15:00:22 +1100 Subject: [PATCH 1120/1752] [slack conc] Fix thread read method --- modules/aca/slack_concierge.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 77fcf6fe..9be1c528 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -40,11 +40,9 @@ def send_message(message_text, thread_id) end def update_last_message_read(thread_id) - @threads.each do |thread| - thread['replies'].each_with_index do |reply, i| - if reply['ts'] == thread_id - @threads[i]['last_read'] = Time.now.to_i * 1000 - end + @threads.each_with_index do |thread, i| + if tread['ts'] == thread_id + @threads[i]['last_read'] = Time.now.to_i * 1000 end end user = find_user(thread_id) From 7e71c0e823cc2287adfeb4511d3de18c8cd7d162 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 3 Feb 2019 15:03:11 +1100 Subject: [PATCH 1121/1752] [slack conc] Fix syntax durrrr --- modules/aca/slack_concierge.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index 9be1c528..c58afa81 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -41,7 +41,7 @@ def send_message(message_text, thread_id) def update_last_message_read(thread_id) @threads.each_with_index do |thread, i| - if tread['ts'] == thread_id + if thread['ts'] == thread_id @threads[i]['last_read'] = Time.now.to_i * 1000 end end From c4fb4b58d374c22b0d9c6eda111ee849157561e2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sun, 3 Feb 2019 21:13:45 +1100 Subject: [PATCH 1122/1752] [Slack conc] dup thraeds before updating --- modules/aca/slack_concierge.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/aca/slack_concierge.rb b/modules/aca/slack_concierge.rb index c58afa81..c7b41dd5 100644 --- a/modules/aca/slack_concierge.rb +++ b/modules/aca/slack_concierge.rb @@ -42,7 +42,10 @@ def send_message(message_text, thread_id) def update_last_message_read(thread_id) @threads.each_with_index do |thread, i| if thread['ts'] == thread_id - @threads[i]['last_read'] = Time.now.to_i * 1000 + message_obj = @threads[i].dup + message_obj['last_read'] = Time.now.to_i * 1000 + @threads[i] = message_obj + self["threads"] = @threads.deep_dup end end user = find_user(thread_id) From c836dfebaa29a7fdb344284cda9b73e1176f66d7 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Mon, 4 Feb 2019 10:58:36 +1100 Subject: [PATCH 1123/1752] (samsung:displays) add software version mdc command --- modules/samsung/displays/md_series.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 03ea5a0f..0dc02131 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -98,7 +98,8 @@ def disconnected :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) + :screen_split => 0xB2, # Tri / quad split (larger panels only) + :software_version => 0x0E } COMMAND.merge!(COMMAND.invert) @@ -144,6 +145,11 @@ def unmute power(true) end + #check software version + def software_version? + do_send (:software_version) + end + INPUTS = { :vga => 0x14, # pc in manual :dvi => 0x18, @@ -393,6 +399,8 @@ def received(response, resolve, command) when :screen_split state = value[0] self[:screen_split] = state.positive? + when :software_version + self[:software_version] = value end :success From f329a4794d0c32465cd193cf31731d0f4e3239d5 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Mon, 4 Feb 2019 11:49:25 +1100 Subject: [PATCH 1124/1752] (samsung:displays) software response to string Convert array response to string on software_version response. --- modules/samsung/displays/md_series.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 0dc02131..d63a0ae5 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -400,7 +400,7 @@ def received(response, resolve, command) state = value[0] self[:screen_split] = state.positive? when :software_version - self[:software_version] = value + self[:software_version] = array_to_str(value) end :success From 8ff9b80813ca673f2436171ffa533eeb2671cd84 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 4 Feb 2019 14:37:28 +1100 Subject: [PATCH 1125/1752] remove clearing logic from lockers library --- lib/loqit/lockers.rb | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/lib/loqit/lockers.rb b/lib/loqit/lockers.rb index 871deb21..eb6d64b7 100644 --- a/lib/loqit/lockers.rb +++ b/lib/loqit/lockers.rb @@ -154,27 +154,6 @@ def clear_credentials(locker_number) store_credentials(locker_number, '', '') end - def clear_lockers - elastic_lockers ||= Elastic.new(Locker) - - date_range = { - range: { - 'doc.end_time' => { - lte: Time.now.to_i - } - } - } - - query = elastic_lockers.query - query.raw_filter(date_range) - results = elastic_lockers.search(query)[:results] - results.each do |r| - clear_credentials(r.locker_id) - r[:released] = true - r.save! - end - end - def customer_has_locker(user_card) response = @client.call(:customer_has_locker, message: { From 29b8123e78a577ddc6685b7c722f1964fe961cf5 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Tue, 5 Feb 2019 09:51:22 +1100 Subject: [PATCH 1126/1752] (samsung:displays) add custom mdc send function --- modules/samsung/displays/md_series.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index d63a0ae5..1c3b38da 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -150,6 +150,11 @@ def software_version? do_send (:software_version) end + # ability to send custom mdc commands via backoffice + def custom_mcd (command, value = "") + do_send(hex_to_byte(command).bytes[0], hex_to_byte(value).bytes) + end + INPUTS = { :vga => 0x14, # pc in manual :dvi => 0x18, From 22d3b95ca5a582144cbd122ef6a1c5f2db058705 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Tue, 5 Feb 2019 10:17:25 +1100 Subject: [PATCH 1127/1752] (samsing:displays) added serial number command --- modules/samsung/displays/md_series.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 1c3b38da..2c60aa24 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -99,7 +99,8 @@ def disconnected :eco_solution => 0xE6, # Eco options (auto power off) :auto_power => 0x33, :screen_split => 0xB2, # Tri / quad split (larger panels only) - :software_version => 0x0E + :software_version => 0x0E, + :serial_number => 0x0B } COMMAND.merge!(COMMAND.invert) @@ -150,6 +151,10 @@ 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_mcd (command, value = "") do_send(hex_to_byte(command).bytes[0], hex_to_byte(value).bytes) @@ -406,6 +411,8 @@ def received(response, resolve, command) self[:screen_split] = state.positive? when :software_version self[:software_version] = array_to_str(value) + when :serial_number + self[:serial_number] = array_to_str(value) end :success From 528ce7fae59ce29753a13e632fa1f488d94bfbd9 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Tue, 5 Feb 2019 10:25:29 +1100 Subject: [PATCH 1128/1752] (samsung:displays) amended do_send debug --- modules/samsung/displays/md_series.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 2c60aa24..a87cde17 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -461,7 +461,7 @@ def do_send(command, data = [], options = {}) data << (data.reduce(:+) & 0xFF) # Add checksum data = [0xAA] + data # Add header - logger.debug { "Sending to Samsung: #{array_to_str(data)}" } + logger.debug { "Sending to Samsung: #{byte_to_hex(array_to_str(data))}" } send(array_to_str(data), options).catch do |reason| disconnect From abbd58e3142d4d47ffb18de1617148c880185008 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 5 Feb 2019 10:57:59 +1100 Subject: [PATCH 1129/1752] [o365] Stop double logging bulk requests --- lib/microsoft/office.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index bb5f1839..90c767c5 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -133,12 +133,8 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers requests: request_array }.to_json - - log_graph_request(request_method, data, query, headers, graph_path, password, endpoints) - graph_api_options = {inactivity_timeout: 25000, keepalive: false} - if @internet_proxy proxy = URI.parse(@internet_proxy) graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } From acf2bce8bee64e1a621aaba6ac8f88215a9bff01 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Tue, 5 Feb 2019 11:32:52 +1100 Subject: [PATCH 1130/1752] (samsung:display) added ack collector --- modules/samsung/displays/md_series.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index a87cde17..6fe67de8 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -413,6 +413,8 @@ def received(response, resolve, command) self[:software_version] = array_to_str(value) when :serial_number self[:serial_number] = array_to_str(value) + else + logger.debug "Samsung responded with ACK: #{array_to_str(value)}" end :success From 7798cdf1f7fa8943bd6a4435227ed333d7372c35 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Tue, 5 Feb 2019 11:43:19 +1100 Subject: [PATCH 1131/1752] (samsung:display) show raw response value in else ack --- modules/samsung/displays/md_series.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 6fe67de8..af98d9c7 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -414,7 +414,7 @@ def received(response, resolve, command) when :serial_number self[:serial_number] = array_to_str(value) else - logger.debug "Samsung responded with ACK: #{array_to_str(value)}" + logger.debug "Samsung responded with ACK: #{value}" end :success From 6ca5ada7da06e818a2532c5736f97799028535a2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 5 Feb 2019 11:48:39 +1100 Subject: [PATCH 1132/1752] [o365] Add flag to add current user to booking --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 90c767c5..2776db84 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -685,7 +685,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_events - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, recurrence_end: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML", extensions:[], location:nil) + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, recurrence_end: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML", extensions:[], location:nil, add_current_user: true) description = String(description) attendees = Array(attendees) @@ -741,7 +741,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end # Add the current user as an attendee - if current_user + if current_user && add_current_user attendees.push({ emailAddress: { address: current_user[:email], From e380a62fe24037d0ca8e382cde287fb9be3eeacc Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Tue, 5 Feb 2019 11:50:46 +1100 Subject: [PATCH 1133/1752] (samsung:display) only log when debugging other ack --- modules/samsung/displays/md_series.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index af98d9c7..d9d7673d 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -419,7 +419,7 @@ def received(response, resolve, command) :success when :nak - logger.debug "Samsung responded with NAK: #{value}" + logger.debug { "Samsung responded with NAK: #{array_to_str(Array(value))}" } :failed # Failed response else From 77ce5f785f8f763f8974932771be075ce790d5a9 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Tue, 5 Feb 2019 12:01:14 +1100 Subject: [PATCH 1134/1752] (samsung:displays) spelt mdc wrong in method --- modules/samsung/displays/md_series.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index d9d7673d..7b4386af 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -156,7 +156,7 @@ def serial_number? end # ability to send custom mdc commands via backoffice - def custom_mcd (command, value = "") + def custom_mdc (command, value = "") do_send(hex_to_byte(command).bytes[0], hex_to_byte(value).bytes) end From b0cef11984c426bb99d39c793381230c03b3a72a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 6 Feb 2019 15:21:23 +1100 Subject: [PATCH 1135/1752] [o365] Fix conflict test for individual room --- lib/microsoft/office.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 2776db84..793fd2ba 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -466,6 +466,8 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 # Allow passing in epoch, time string or ruby Time class start_param = ensure_ruby_date(start_param).utc.iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).utc.iso8601.split("+")[0] + available_from = ensure_ruby_date(available_from).utc.iso8601.split("+")[0] + available_to = ensure_ruby_date(available_to).utc.iso8601.split("+")[0] # Array of all bookings within our period if bulk From dbd23e621ae5421f1659df8b3b7119e397e8256c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 6 Feb 2019 17:08:40 +1100 Subject: [PATCH 1136/1752] Fix recurrence check --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 793fd2ba..8cd628ba 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -714,7 +714,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end_object = ensure_ruby_date(end_param).in_time_zone(timezone) start_param = ensure_ruby_date(start_param).in_time_zone(timezone).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] - + recurrence_end = ensure_ruby_date(recurrence_end) # Add the attendees attendees.map!{|a| @@ -814,7 +814,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil range: { type: 'endDate', startDate: start_object.strftime("%F"), - endDate: Time.at(recurrence_end).strftime("%F") + endDate: recurrence_end.strftime("%F") } } end From ff2612ebbde4f29d057ea99d9c61e72b885b595f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Feb 2019 15:39:50 +1100 Subject: [PATCH 1137/1752] [O365 panel] Add support for linked rooms --- modules/aca/office_booking.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index c4ee83b9..2e4c259a 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -331,6 +331,14 @@ def order_complete def fetch_bookings(*args) # Make the request response = @client.get_bookings_by_user(user_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] + if system.settings.key?('linked_rooms') + system.settings['linked_rooms'].each do |linked_room_id| + linked_room = Orchestrator::ControlSystem.find_by_id(linked_room_id) + if linked_room + response += @client.get_bookings_by_user(user_id: linked_room.id, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] + end + end + end self[:today] = todays_bookings(response, @office_organiser_location) end From f0ac0c1e30f8f57b1d02ea29c9b05dd9f68b7d12 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Feb 2019 15:54:43 +1100 Subject: [PATCH 1138/1752] [O365 panel] Add support for linked rooms --- modules/aca/office_booking.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 2e4c259a..9d6cab87 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -331,8 +331,9 @@ def order_complete def fetch_bookings(*args) # Make the request response = @client.get_bookings_by_user(user_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] - if system.settings.key?('linked_rooms') - system.settings['linked_rooms'].each do |linked_room_id| + real_room = Orchestrator::ControlSystem.find(system.id) + if real_room.settings.key?('linked_rooms') + real_room.settings['linked_rooms'].each do |linked_room_id| linked_room = Orchestrator::ControlSystem.find_by_id(linked_room_id) if linked_room response += @client.get_bookings_by_user(user_id: linked_room.id, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] From 56770701e91eb8ee98a2f2e645305c8e6cee92f7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 7 Feb 2019 15:55:33 +1100 Subject: [PATCH 1139/1752] [O365 panel] Send email not system ID duh --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 9d6cab87..8fa42d73 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -336,7 +336,7 @@ def fetch_bookings(*args) real_room.settings['linked_rooms'].each do |linked_room_id| linked_room = Orchestrator::ControlSystem.find_by_id(linked_room_id) if linked_room - response += @client.get_bookings_by_user(user_id: linked_room.id, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] + response += @client.get_bookings_by_user(user_id: linked_room.email, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] end end end From 9a429403ad28c46d0ba9c17532b7ed33ac0d73b2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 8 Feb 2019 14:49:53 +1100 Subject: [PATCH 1140/1752] [zoom] Add new fields like password and alt host --- lib/zoom/meeting.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index 75dfd940..ebda55f7 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -41,7 +41,7 @@ def initialize( # "enforce_login": false # } # } - def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, countries:[], timezone:'Australia/Sydney') + def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, countries:[], password:nil, alternative_host:nil, timezone:'Australia/Sydney') zoom_params = { "topic": topic, "type": 2, @@ -61,6 +61,8 @@ def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, } } zoom_params['agenda'] = agenda if agenda + zoom_params['password'] = password if password + zoom_params['alternative_host'] = alternative_host if alternative_host response = api_request(request_method: 'post', endpoint: "users/#{owner_email}/meetings", data: zoom_params) JSON.parse(response.body) end From 3f487160a4d670f9506c7fd6084f28a26eb5374d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 8 Feb 2019 18:06:22 +1100 Subject: [PATCH 1141/1752] [o365] Fix booking creation response format --- lib/microsoft/office.rb | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8cd628ba..a93344e8 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -498,6 +498,108 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end + def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) + room = Orchestrator::ControlSystem.find_by_email(room_email) + + # Create time objects of the start and end for easier use + booking_start = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']) + booking_end = ActiveSupport::TimeZone.new(booking['end']['timeZone']).parse(booking['end']['dateTime']) + + if room + if room.settings.key?('setup') + booking_start_with_setup = booking_start - room.settings['setup'].to_i.seconds + else + booking_start_with_setup = booking_start + end + + if room.settings.key?('breakdown') + booking_end_with_setup = booking_end + room.settings['breakdown'].to_i.seconds + else + booking_end_with_setup = booking_end + end + end + + + # Grab the start and end in the right format for the frontend + # booking['Start'] = booking_start.utc.iso8601 + # booking['End'] = booking_end.utc.iso8601 + booking['start_epoch'] = booking_start.to_i + booking['end_epoch'] = booking_end.to_i + + # Get some data about the booking + booking['title'] = booking['subject'] + booking['booking_id'] = booking['id'] + booking['icaluid'] = booking['iCalUId'] + booking['show_as'] = booking['showAs'] + booking['created'] = booking['createdDateTime'] + + if booking.key?('extensions') && !extensions.empty? + booking['extensions'].each do |ext| + if extensions.map {|e| "Microsoft.OutlookServices.OpenTypeExtension.#{e}"}.include?(ext['id']) + ext.each do |ext_key, ext_val| + booking[ext_key] = ext_val if !['@odata.type', 'id','extensionName'].include?(ext_key) + end + end + end + end + + # Check whether this event has external attendees + booking_has_visitors = false + + # Format the attendees and save the old format + new_attendees = [] + booking['attendees'].each do |attendee| + attendee_email = attendee['emailAddress']['address'] + if attendee['type'] == 'resource' + booking['room_id'] = attendee_email.downcase + else + # Check if attendee is external or internal + if booking.key?('owner') + internal_domain = Mail::Address.new(booking['owner']).domain + else + internal_domain = ENV['INTERNAL_DOMAIN'] || internal_domain + end + mail_object = Mail::Address.new(attendee_email) + mail_domain = mail_object.domain + booking_has_visitors = true if mail_domain != internal_domain + attendee_object = { + email: attendee_email, + name: attendee['emailAddress']['name'], + visitor: (mail_domain != internal_domain), + organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize + } + new_attendees.push(attendee_object) + end + end + booking['visitors'] = booking_has_visitors + booking['old_attendees'] = booking['attendees'] + booking['attendees'] = new_attendees + + # Get the organiser and location data + if booking.key?('owner') && booking.key?('owner_name') + booking['organizer'] = { name: booking['owner_name'], email: booking['owner']} + else + booking['organizer'] = { name: booking['organizer']['emailAddress']['name'], email: booking['organizer']['emailAddress']['address']} + end + # if !booking.key?('room_id') && booking['locations'] && !booking['locations'].empty? && booking['locations'][0]['uniqueId'] + # booking['room_id'] = booking['locations'][0]['uniqueId'].downcase + # end + # booking['room_id'] = room_email + if !booking['location']['displayName'].nil? && !booking['location']['displayName'].empty? + booking['room_name'] = booking['location']['displayName'] + end + + booking_info = User.bucket.get("bookinginfo-#{booking['iCalUId']}", quiet: true) + if booking_info + booking['catering'] = booking_info[:catering] if booking_info.key?(:catering) + booking['parking'] = booking_info[:parking] if booking_info.key?(:parking) + booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) + end + + + booking + end + def extract_booking_data(booking, start_param, end_param, room_email, internal_domain=nil, extensions=[]) room = Orchestrator::ControlSystem.find_by_email(room_email) @@ -832,6 +934,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil check_response(request) response = JSON.parse(request.body) + format_booking_data(response, rooms[0].email) end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update From ac892b962b8f572a97afbff31de029d21eb732a2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 8 Feb 2019 18:12:43 +1100 Subject: [PATCH 1142/1752] [o365] Fix timezone --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a93344e8..4cb381ba 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -789,7 +789,7 @@ def get_bookings_by_room(room_id:, start_param:Time.now, end_param:(Time.now + 1 end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_post_events - def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, recurrence_end: nil, is_private: false, timezone:'Sydney', endpoint_override:nil, content_type:"HTML", extensions:[], location:nil, add_current_user: true) + def create_booking(room_id:, start_param:, end_param:, subject:, description:nil, current_user:, attendees: nil, recurrence: nil, recurrence_end: nil, is_private: false, timezone:'UTC', endpoint_override:nil, content_type:"HTML", extensions:[], location:nil, add_current_user: true) description = String(description) attendees = Array(attendees) From 2781d54bd298365e587e682be5f23d98908ef4d7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Fri, 8 Feb 2019 18:14:18 +1100 Subject: [PATCH 1143/1752] [o365] Fix timezone --- lib/microsoft/office.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 4cb381ba..11e02ece 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -14,7 +14,8 @@ class ErrorAccessDenied < Error; end class Microsoft::Office TIMEZONE_MAPPING = { "Sydney": "AUS Eastern Standard Time", - "Brisbane": "Australia/Brisbane" + "Brisbane": "Australia/Brisbane", + "UTC": "UTC" } def initialize( client_id:, From da8a964166b902b9e8738876ad86b452da6f9388 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 9 Feb 2019 15:23:43 +1100 Subject: [PATCH 1144/1752] Support formatting edit results and multi room IDs --- lib/microsoft/office.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 11e02ece..f5aa1f28 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -660,10 +660,11 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d # Format the attendees and save the old format new_attendees = [] + booking['room_id'] = [] booking['attendees'].each do |attendee| attendee_email = attendee['emailAddress']['address'] if attendee['type'] == 'resource' - booking['room_id'] = attendee_email.downcase + booking['room_id'].push(attendee_email.downcase) else # Check if attendee is external or internal if booking.key?('owner') @@ -1009,6 +1010,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec request = graph_request(request_method: 'patch', endpoint: endpoint, data: event, password: @delegated) check_response(request) response = JSON.parse(request.body) + format_booking_data(response, room.email) end def check_in_attendee(owner_email:, attendee_email:, icaluid:, response_type:'Accepted') From c3ae242fb8fb9f13e07d1752eca9e8d12ad8d690 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 9 Feb 2019 15:49:18 +1100 Subject: [PATCH 1145/1752] [o365] Temp remove multi room --- lib/microsoft/office.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f5aa1f28..94acab2f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -664,7 +664,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['attendees'].each do |attendee| attendee_email = attendee['emailAddress']['address'] if attendee['type'] == 'resource' - booking['room_id'].push(attendee_email.downcase) + booking['room_id'].push(attendee_email.downcase) else # Check if attendee is external or internal if booking.key?('owner') @@ -684,6 +684,9 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d new_attendees.push(attendee_object) end end + + # Remove me once frontend supports multi-room + booking['room_id'] = booking['room_id'][0] booking['visitors'] = booking_has_visitors booking['old_attendees'] = booking['attendees'] booking['attendees'] = new_attendees From 700e8b376b33e1b1ac3c91be505d8020c7a4052a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 9 Feb 2019 16:40:21 +1100 Subject: [PATCH 1146/1752] [zoom] Add get_user method --- lib/zoom/meeting.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index ebda55f7..9777d31c 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -67,6 +67,10 @@ def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, JSON.parse(response.body) end + def get_user(owner_email:) + response = api_request(request_method: 'get', endpoint: "users/#{owner_email}") + end + protected def generate_jwt From c1a4f1ce83d91e1916b910202da8c5d362d6962c Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 9 Feb 2019 16:45:02 +1100 Subject: [PATCH 1147/1752] [zoom] Parse get user json --- lib/zoom/meeting.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index 9777d31c..78b9610d 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -69,6 +69,7 @@ def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, def get_user(owner_email:) response = api_request(request_method: 'get', endpoint: "users/#{owner_email}") + JSON.parse(response.body) end protected From e831421cea56badb02d5dfd034b0c9998395a1b1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 9 Feb 2019 17:19:15 +1100 Subject: [PATCH 1148/1752] Add epoch helper --- lib/zoom/meeting.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index 78b9610d..ee20259d 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -42,6 +42,7 @@ def initialize( # } # } def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, countries:[], password:nil, alternative_host:nil, timezone:'Australia/Sydney') + start_time = ensure_ruby_epoch(start_time) zoom_params = { "topic": topic, "type": 2, @@ -123,4 +124,25 @@ def log_api_request(request_method, data, query, headers, graph_path) STDERR.flush end + def ensure_ruby_epoch(date) + if !(date.class == Time || date.class == DateTime) + if string_is_digits(date) + + # Convert to an integer + date = date.to_i + + # If JavaScript epoch remove milliseconds + if date.to_s.length == 13 + date /= 1000 + end + + # Convert to datetimes + date = Time.at(date) + else + date = Time.parse(date) + end + end + return date.to_i + end + end From 5a8ca398a89d80cef4c938a53375cf6fe7c70a74 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 9 Feb 2019 17:20:18 +1100 Subject: [PATCH 1149/1752] Add epoch helper --- lib/zoom/meeting.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index ee20259d..923e8d2e 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -145,4 +145,10 @@ def ensure_ruby_epoch(date) return date.to_i end + # Returns true if a string is all digits (used to check for an epoch) + def string_is_digits(string) + string = string.to_s + string.scan(/\D/).empty? + end + end From 5c5bf66423f6a45ced7c99456b0081e42a058034 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Sat, 9 Feb 2019 17:35:07 +1100 Subject: [PATCH 1150/1752] [zoom] Add support for instant meeting type --- lib/zoom/meeting.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index 923e8d2e..f3dad68e 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -41,11 +41,11 @@ def initialize( # "enforce_login": false # } # } - def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, countries:[], password:nil, alternative_host:nil, timezone:'Australia/Sydney') + def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, countries:[], password:nil, alternative_host:nil, timezone:'Australia/Sydney', type: 2) start_time = ensure_ruby_epoch(start_time) zoom_params = { "topic": topic, - "type": 2, + "type": type, "start_time": Time.at(start_time).iso8601, "duration": (duration || 30), "timezone": timezone, From 441c6a1006c8606a634a886539e896efc81cdd64 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Feb 2019 10:26:09 +1100 Subject: [PATCH 1151/1752] [EWS Panel] Add setting for canceling timeout --- modules/aca/exchange_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 3658115e..94bd7f0a 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -115,6 +115,7 @@ def on_update self[:booking_min_duration] = setting(:booking_min_duration) self[:booking_disable_future] = setting(:booking_disable_future) self[:booking_max_duration] = setting(:booking_max_duration) + self[:booking_cancel_timeout] = setting(:booking_cancel_timeout) self[:hide_all_day_bookings] = setting(:hide_all_day_bookings) self[:timeout] = setting(:timeout) self[:arrow_direction] = setting(:arrow_direction) From 47729e68d4297b8e564c74af42db71580eaa3368 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Feb 2019 11:44:47 +1100 Subject: [PATCH 1152/1752] [zoom] Fix field for alternative host --- lib/zoom/meeting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zoom/meeting.rb b/lib/zoom/meeting.rb index f3dad68e..b305caea 100644 --- a/lib/zoom/meeting.rb +++ b/lib/zoom/meeting.rb @@ -63,7 +63,7 @@ def create_meeting(owner_email:, start_time:, duration:nil, topic:, agenda:nil, } zoom_params['agenda'] = agenda if agenda zoom_params['password'] = password if password - zoom_params['alternative_host'] = alternative_host if alternative_host + zoom_params['schedule_for'] = alternative_host if alternative_host response = api_request(request_method: 'post', endpoint: "users/#{owner_email}/meetings", data: zoom_params) JSON.parse(response.body) end From 855e860b2ce9f94d0ba513b40e75ad9c9bf4a6eb Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Feb 2019 13:46:56 +1100 Subject: [PATCH 1153/1752] [Gallagher] Fix access granting and vivant code --- lib/gallagher.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/gallagher.rb b/lib/gallagher.rb index 7f570e9b..25395e25 100755 --- a/lib/gallagher.rb +++ b/lib/gallagher.rb @@ -42,7 +42,7 @@ def initialize( :wsdl => wsdl, :log => log, :log_level => log_level, - :ssl_version => :TLSv1, + :ssl_version => :TLSv1_2, :ssl_cert_file => cert_path, :ssl_ca_cert_file => ca_cert_path, :ssl_cert_key_file => key_path, @@ -59,7 +59,7 @@ def initialize( :wsdl => wsdl, :log => log, :log_level => log_level, - :ssl_version => :TLSv1, + :ssl_version => :TLSv1_2, :ssl_cert_file => cert_path, :ssl_ca_cert_file => ca_cert_path, :ssl_cert_key_file => key_path, @@ -88,7 +88,7 @@ def has_cardholder(vivant_id) 'wsdl:sessionToken': { 'web:Value': gallagher_token}, 'wsdl:id': { 'wsdl:PdfName': 'Unique ID', - 'wsdl:Value': "cardholder--#{vivant_id}" + 'wsdl:Value': vivant_id }, 'wsdl:pdfName': 'Unique ID' }) @@ -129,7 +129,7 @@ def create_card(vivant_id) 'wsdl:sessionToken': { 'web:Value': gallagher_token}, 'wsdl:id': { 'wsdl:PdfName': 'Unique ID', - 'wsdl:Value': "cardholder--#{vivant_id}" + 'wsdl:Value': vivant_id }, 'wsdl:cardType': 'C4 Base Building', 'wsdl:cardNumber': { @@ -159,7 +159,7 @@ def get_cards(vivant_id) 'wsdl:sessionToken': { 'web:Value': gallagher_token}, 'wsdl:id': { 'wsdl:PdfName': 'Unique ID', - 'wsdl:Value': "cardholder--#{vivant_id}" + 'wsdl:Value': vivant_id } }) card_list = response.body[:cif_query_cards_by_cardholder_response][:cif_query_cards_by_cardholder_result] @@ -171,18 +171,19 @@ def get_cards(vivant_id) end def has_cards(vivant_id) - return get_cards("cardholder--#{vivant_id}").empty? + return get_cards(vivant_id).empty? end def set_access(vivant_id, access_group, time_start, time_end) # Add five minutes to time start - time_start = (Time.parse(time_start) - 10.minutes).iso8601 + time_start = (Time.parse(time_start) - 10.minutes).utc.iso8601 + time_end = (Time.parse(time_end) + 10.minutes).utc.iso8601 begin response = @client.call(:cif_assign_temporary_access, message: { 'wsdl:sessionToken': { 'web:Value': gallagher_token}, 'wsdl:id': { 'wsdl:PdfName': 'Unique ID', - 'wsdl:Value': "cardholder--#{vivant_id}" + 'wsdl:Value': vivant_id }, 'wsdl:accessGroup': access_group, 'wsdl:activationTime': { @@ -194,7 +195,7 @@ def set_access(vivant_id, access_group, time_start, time_end) 'wsdl:UseDefault':false } }) - return response.body[:connect_response][:connect_result][:value] + return response.http.code rescue Savon::SOAPFault => e return e.message end From c5855c29b3fbaaa2ca2a35c618e01d9b0a159aa3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Feb 2019 15:31:44 +1100 Subject: [PATCH 1154/1752] [o365] Add support for created_from filter --- lib/microsoft/office.rb | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 94acab2f..3e600aa7 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -458,7 +458,43 @@ def delete_contact(contact_id:, mailbox:) 200 end + def get_bookings_created_from(mailboxes:, created_from:) + mailboxes = Array(mailboxes) + created_from = ensure_ruby_date(created_from).utc.iso8601 + all_endpoints = mailboxes.map do |email| + "/users/#{email}/events" + end + + slice_size = 20 + responses = [] + all_endpoints.each_slice(slice_size).with_index do |endpoints, ind| + query = { + '$top': 200, + '$filter': "createdDateTime gt #{created_from}" + } + + bulk_response = bulk_graph_request(request_method: 'get', endpoints: endpoints, query: query ) + + check_response(bulk_response) + parsed_response = JSON.parse(bulk_response.body)['responses'] + parsed_response.each do |res| + local_id = res['id'].to_i + global_id = local_id + (slice_size * ind.to_i) + res['id'] = global_id + responses.push(res) + end + end + + bookings = {} + responses.each_with_index do |res, i| + bookings[mailboxes[res['id'].to_i]] = res['body']['value'] + end + bookings + bookings.each do |email, booking| + bookings[email] = format_booking_data(booking, email) + end + end def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil, extensions:[], custom_query:[]) # The user_ids param can be passed in as a string or array but is always worked on as an array From 782055b714249fe7af90bb4afea281288e677564 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Feb 2019 15:54:25 +1100 Subject: [PATCH 1155/1752] [o365] Fix booking created from logic --- lib/microsoft/office.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 3e600aa7..1a1a7e17 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -491,8 +491,10 @@ def get_bookings_created_from(mailboxes:, created_from:) bookings[mailboxes[res['id'].to_i]] = res['body']['value'] end bookings - bookings.each do |email, booking| - bookings[email] = format_booking_data(booking, email) + bookings.each do |email, bookings| + bookings.each do |booking| + bookings[email] = format_booking_data(booking, email) + end end end From b90251ecf594ecc60103366f86459be61e8750b6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Feb 2019 15:56:40 +1100 Subject: [PATCH 1156/1752] [o365] Fix booking created from logic --- lib/microsoft/office.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 1a1a7e17..8a52e825 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -492,8 +492,8 @@ def get_bookings_created_from(mailboxes:, created_from:) end bookings bookings.each do |email, bookings| - bookings.each do |booking| - bookings[email] = format_booking_data(booking, email) + bookings.each_with_index do |booking, i| + bookings[email][i] = format_booking_data(booking, email) end end end From 4f279054c199589fb0a489cfaeb82e0e951f5149 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 11 Feb 2019 16:04:35 +1100 Subject: [PATCH 1157/1752] [o365] Fix booking created from logic --- lib/microsoft/office.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8a52e825..d17e603f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -488,14 +488,12 @@ def get_bookings_created_from(mailboxes:, created_from:) bookings = {} responses.each_with_index do |res, i| - bookings[mailboxes[res['id'].to_i]] = res['body']['value'] - end - bookings - bookings.each do |email, bookings| - bookings.each_with_index do |booking, i| - bookings[email][i] = format_booking_data(booking, email) + bookings[mailboxes[res['id'].to_i]] = [] + res['body']['value'].each do |booking| + bookings[mailboxes[res['id'].to_i]].push(format_booking_data(booking, mailboxes[res['id'].to_i])) end end + bookings end def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil, extensions:[], custom_query:[]) From 4aab8113bbf85164d96b271c593c8c7ffb148814 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 11 Feb 2019 16:14:09 +1100 Subject: [PATCH 1158/1752] add field to people_count for start_time --- lib/aca/tracking/people_count.rb | 17 ++++++++------- modules/aca/tracking/people_counter.rb | 30 +++++++++++++------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/lib/aca/tracking/people_count.rb b/lib/aca/tracking/people_count.rb index 24881639..44fbad9c 100644 --- a/lib/aca/tracking/people_count.rb +++ b/lib/aca/tracking/people_count.rb @@ -4,15 +4,16 @@ class Aca::Tracking::PeopleCount < CouchbaseOrm::Base design_document :pcount # Connection details - attribute :room_email, type: String - attribute :booking_id, type: String - attribute :system_id, type: String - attribute :capacity, type: Integer - attribute :maximum, type: Integer - attribute :average, type: Integer + attribute :room_email, type: String + attribute :booking_id, type: String + attribute :system_id, type: String + attribute :capacity, type: Integer + attribute :maximum, type: Integer + attribute :average, type: Integer attribute :median, type: Integer attribute :organiser, type: String attribute :counts, type: Array, default: lambda { [] } + attribute :start_time, type: Integer protected @@ -22,7 +23,7 @@ class Aca::Tracking::PeopleCount < CouchbaseOrm::Base view :all, emit_key: :room_email def set_id - self.id = "count-#{self.booking_id}" + self.id = "pcount-#{self.booking_id}" end -end \ No newline at end of file +end diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 210bc810..4bbfbb8d 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -37,7 +37,7 @@ def booking_changed(details) } end end - + end def get_current_booking(details) @@ -50,7 +50,7 @@ def get_current_booking(details) meeting_end = Time.at(meeting[:end_epoch]).to_i # If it's past the start time and before the end time - if start_time >= meeting_start && start_time < meeting_end + if start_time >= meeting_start && start_time < meeting_end current = meeting end end @@ -63,11 +63,11 @@ def count_changed(new_count) # Check the current meeting current = get_current_booking(self[:todays_bookings]) return if current.nil? - + logger.info "Count changed: #{new_count} and ID: #{current[:id]}" # Add the change to the dataset for that meeting - current_dataset = Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") + current_dataset = Aca::Tracking::PeopleCount.find_by_id("pcount-#{current[:id]}") if current_dataset.nil? current_dataset = create_dataset(new_count, current) logger.info "Created dataset with ID: #{current_dataset.id}" @@ -90,15 +90,15 @@ def create_dataset(count, booking) dataset = Aca::Tracking::PeopleCount.new # # Dataset attrs - # attribute :room_email, type: String - # attribute :booking_id, type: String - # attribute :system_id, type: String - # attribute :capacity, type: Integer - # attribute :maximum, type: Integer - # attribute :average, type: Integer + # attribute :room_email, type: String + # attribute :booking_id, type: String + # attribute :system_id, type: String + # attribute :capacity, type: Integer + # attribute :maximum, type: Integer + # attribute :average, type: Integer # attribute :median, type: Integer # attribute :organiser, type: String - + dataset.room_email = system.email dataset.system_id = system.id dataset.capacity = system.capacity @@ -110,15 +110,15 @@ def create_dataset(count, booking) return dataset if dataset.save! end - def calculate_average(meeting) + def calculate_average(meeting) logger.info "Calculating average for: #{meeting[:id]}" - + # Set up our holding vars durations = [] total_duration = 0 # Get the dataset - dataset = ::Aca::Tracking::PeopleCount.find_by_id("count-#{meeting[:id]}") + dataset = ::Aca::Tracking::PeopleCount.find_by_id("pcount-#{meeting[:id]}") events = dataset.counts.dup @@ -169,7 +169,7 @@ def on_update self[:todays_bookings] = [] schedule.clear logger.info "Starting booking update in 30s" - schedule.in('10s') { + schedule.in('10s') { logger.info "Grabbing bookings to update" self[:todays_bookings] = system[:Bookings][:today] booking_changed(self[:todays_bookings]) From ba557169ffa12c88e6b0e51e5db5c5a1ff1d85f7 Mon Sep 17 00:00:00 2001 From: pkheav Date: Tue, 12 Feb 2019 12:19:50 +1100 Subject: [PATCH 1159/1752] add start time to new people count records --- modules/aca/tracking/people_counter.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 4bbfbb8d..278a2623 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -98,6 +98,7 @@ def create_dataset(count, booking) # attribute :average, type: Integer # attribute :median, type: Integer # attribute :organiser, type: String + # attribute :start_time, type: Integer dataset.room_email = system.email dataset.system_id = system.id @@ -107,6 +108,7 @@ def create_dataset(count, booking) dataset.median = count dataset.booking_id = booking[:id] dataset.organiser = booking[:owner] + dataset.start_time = booking[:start] return dataset if dataset.save! end From 555ed70eca3e042409383d4edc14750ad05177dc Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 13 Feb 2019 10:59:14 +1100 Subject: [PATCH 1160/1752] revert key from pcount to count to maintain consistency with old records --- lib/aca/tracking/people_count.rb | 2 +- modules/aca/tracking/people_counter.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/aca/tracking/people_count.rb b/lib/aca/tracking/people_count.rb index 44fbad9c..8f52f8f2 100644 --- a/lib/aca/tracking/people_count.rb +++ b/lib/aca/tracking/people_count.rb @@ -23,7 +23,7 @@ class Aca::Tracking::PeopleCount < CouchbaseOrm::Base view :all, emit_key: :room_email def set_id - self.id = "pcount-#{self.booking_id}" + self.id = "count-#{self.booking_id}" end end diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 278a2623..5288f4ec 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -67,7 +67,7 @@ def count_changed(new_count) logger.info "Count changed: #{new_count} and ID: #{current[:id]}" # Add the change to the dataset for that meeting - current_dataset = Aca::Tracking::PeopleCount.find_by_id("pcount-#{current[:id]}") + current_dataset = Aca::Tracking::PeopleCount.find_by_id("count-#{current[:id]}") if current_dataset.nil? current_dataset = create_dataset(new_count, current) logger.info "Created dataset with ID: #{current_dataset.id}" @@ -120,7 +120,7 @@ def calculate_average(meeting) total_duration = 0 # Get the dataset - dataset = ::Aca::Tracking::PeopleCount.find_by_id("pcount-#{meeting[:id]}") + dataset = ::Aca::Tracking::PeopleCount.find_by_id("count-#{meeting[:id]}") events = dataset.counts.dup From c8da9790d418679461fada67d1ba8c2779a567ac Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 13 Feb 2019 12:52:04 +1100 Subject: [PATCH 1161/1752] Update office.rb add food_ordered field to booking --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index d17e603f..cf04b12d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -746,6 +746,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['catering'] = booking_info[:catering] if booking_info.key?(:catering) booking['parking'] = booking_info[:parking] if booking_info.key?(:parking) booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) + booking['food_ordered'] = booking_info[:food_ordered] if booking_info.key?(:food_ordered) end From 20d3f960511582cd526637ebafc6ea1d0f9c65cb Mon Sep 17 00:00:00 2001 From: pkheav Date: Wed, 13 Feb 2019 14:52:47 +1100 Subject: [PATCH 1162/1752] pass food_ordered to the frontend --- lib/microsoft/office.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index cf04b12d..460f2f3f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -535,7 +535,7 @@ def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1 end end - def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) + def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) room = Orchestrator::ControlSystem.find_by_email(room_email) # Create time objects of the start and end for easier use @@ -548,7 +548,7 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) else booking_start_with_setup = booking_start end - + if room.settings.key?('breakdown') booking_end_with_setup = booking_end + room.settings['breakdown'].to_i.seconds else @@ -631,6 +631,7 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) booking['catering'] = booking_info[:catering] if booking_info.key?(:catering) booking['parking'] = booking_info[:parking] if booking_info.key?(:parking) booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) + booking['food_ordered'] = booking_info[:food_ordered] if booking_info.key?(:food_ordered) end @@ -650,7 +651,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d else booking_start_with_setup = booking_start end - + if room.settings.key?('breakdown') booking_end_with_setup = booking_end + room.settings['breakdown'].to_i.seconds else @@ -700,7 +701,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['attendees'].each do |attendee| attendee_email = attendee['emailAddress']['address'] if attendee['type'] == 'resource' - booking['room_id'].push(attendee_email.downcase) + booking['room_id'].push(attendee_email.downcase) else # Check if attendee is external or internal if booking.key?('owner') @@ -801,7 +802,7 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no } extensions.each do |ext_name| query['$expand'] = "Extensions($filter=id eq 'Microsoft.OutlookServices.OpenTypeExtension.#{ext_name}')" - end + end custom_query.each do |query_key, query_val| query[query_key] = query_val @@ -840,10 +841,10 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end # Get our room - rooms = room_id.map do |r_id| + rooms = room_id.map do |r_id| Orchestrator::ControlSystem.find_by_id(r_id) || Orchestrator::ControlSystem.find_by_email(r_id) end - + if endpoint_override endpoint = "/v1.0/users/#{endpoint_override}/events" @@ -1046,7 +1047,7 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec event = event.to_json event.gsub!("X!X!X!",description) if description - + request = graph_request(request_method: 'patch', endpoint: endpoint, data: event, password: @delegated) check_response(request) response = JSON.parse(request.body) From 7f52562d295311e90b2d719e75557be19502bac7 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Feb 2019 22:17:56 +1100 Subject: [PATCH 1163/1752] [Slack Booking] Add slack booking bot --- modules/aca/slack_booking.rb | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 modules/aca/slack_booking.rb diff --git a/modules/aca/slack_booking.rb b/modules/aca/slack_booking.rb new file mode 100644 index 00000000..315dba6b --- /dev/null +++ b/modules/aca/slack_booking.rb @@ -0,0 +1,100 @@ +require 'slack-ruby-client' +require 'slack/real_time/concurrency/libuv' +require 'microsoft/office' + +# App ID: AG5KR5JSX +# Client ID: 32027075415.549671188915 +# App Secret: 899130bef0277f54f17b7f8c49309c2d +# Signing Secret: 8e4f0421de528a602903aefc9acb4524 +# Verification Token: eYEilNP8PpfLop3rjKmVE3gz +# Bot token: xoxb-32027075415-549994563445-ORN5cfzecXHfQsoa9qUD6WVM +class Aca::Slack + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + include ::Orchestrator::Security + + + descriptive_name 'Slack Connector' + generic_name :Slack + implements :logic + + def on_load + on_update + end + + def on_update + on_unload + create_websocket + self[:channel] = setting(:channel) || :concierge + end + + def on_unload + @client.stop! if @client && @client.started? + @client = nil + end + + # Message coming in from Slack API + def on_message(data) + matched_lines = [ + "free room", + "free space", + "available room", + ] + if matched_lines.include?(data['text']) + @client.web_client.chat_postMessage channel: setting(:channel), text: "The following rooms are available:", username: 'Room Bot' + rooms = Orchestrator::ControlSystem.all.to_a + rooms.each do |room| + if room.bookable && room.name.include?("SYD") + @client.web_client.chat_postMessage channel: setting(:channel), text: room.name, username: 'Room Bot' + end + end + + # @office + end + + protected + + # Create a realtime WS connection to the Slack servers + def create_websocket + + + @office = ::Microsoft::Office.new({ + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ( ENV["OFFICE_CLIENT_SECRET"] || "M6o]=6{Qi>*:?+_>|%}#_s[*/}$1}^N[.=D&>Lg--}!+{=&.*{/:|J_|%.{="), + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + service_account_email: ENV['OFFICE_ACCOUNT_EMAIL'], + service_account_password: ENV['OFFICE_ACCOUNT_PASSWORD'], + internet_proxy: ENV['INTERNET_PROXY'] + }) + + # Set our token and other config options + ::Slack.configure do |config| + config.token = setting(:slack_api_token) + config.logger = Logger.new(STDOUT) + config.logger.level = Logger::INFO + fail 'Missing slack api token setting!' unless config.token + end + + # Use Libuv as our concurrency driver + ::Slack::RealTime.configure do |config| + config.concurrency = Slack::RealTime::Concurrency::Libuv + end + + # Create the client and set the callback function when a message is received + @client = ::Slack::RealTime::Client.new + @client.on :message do |data| + begin + #@client.typing channel: data.channel + on_message(data) + rescue Exception => e + end + + end + # Start the client + @client.start! + end + +end From 8151846ccc619100d9cf0ed168f13636952ed95f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Feb 2019 22:20:32 +1100 Subject: [PATCH 1164/1752] [Slack Booking] Add slack booking bot --- modules/aca/slack_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/slack_booking.rb b/modules/aca/slack_booking.rb index 315dba6b..edc477d6 100644 --- a/modules/aca/slack_booking.rb +++ b/modules/aca/slack_booking.rb @@ -48,7 +48,7 @@ def on_message(data) @client.web_client.chat_postMessage channel: setting(:channel), text: room.name, username: 'Room Bot' end end - + end # @office end From 69e38ee4a27f7edecc089f49d73d77dea761ccca Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Feb 2019 22:27:56 +1100 Subject: [PATCH 1165/1752] [Slack Book] rename class --- modules/aca/slack_booking.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/slack_booking.rb b/modules/aca/slack_booking.rb index edc477d6..240e93dc 100644 --- a/modules/aca/slack_booking.rb +++ b/modules/aca/slack_booking.rb @@ -8,13 +8,13 @@ # Signing Secret: 8e4f0421de528a602903aefc9acb4524 # Verification Token: eYEilNP8PpfLop3rjKmVE3gz # Bot token: xoxb-32027075415-549994563445-ORN5cfzecXHfQsoa9qUD6WVM -class Aca::Slack +class Aca::SlackBooking include ::Orchestrator::Constants include ::Orchestrator::Transcoder include ::Orchestrator::Security - descriptive_name 'Slack Connector' + descriptive_name 'Slack Booking' generic_name :Slack implements :logic From 84db96ff33825423833a142d1f67c8ec5a463251 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 13 Feb 2019 22:31:45 +1100 Subject: [PATCH 1166/1752] [Slack Book] Remove office365 for now --- modules/aca/slack_booking.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/modules/aca/slack_booking.rb b/modules/aca/slack_booking.rb index 240e93dc..3e8d3e8a 100644 --- a/modules/aca/slack_booking.rb +++ b/modules/aca/slack_booking.rb @@ -58,18 +58,6 @@ def on_message(data) def create_websocket - @office = ::Microsoft::Office.new({ - client_id: ENV['OFFICE_CLIENT_ID'], - client_secret: ( ENV["OFFICE_CLIENT_SECRET"] || "M6o]=6{Qi>*:?+_>|%}#_s[*/}$1}^N[.=D&>Lg--}!+{=&.*{/:|J_|%.{="), - app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", - app_token_url: ENV["OFFICE_TOKEN_URL"], - app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", - graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", - service_account_email: ENV['OFFICE_ACCOUNT_EMAIL'], - service_account_password: ENV['OFFICE_ACCOUNT_PASSWORD'], - internet_proxy: ENV['INTERNET_PROXY'] - }) - # Set our token and other config options ::Slack.configure do |config| config.token = setting(:slack_api_token) From e6e957b3041292561df5c21e9de9b1e3eb2f0465 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 13 Feb 2019 19:41:58 +0800 Subject: [PATCH 1167/1752] lib/office graph api: support Hong Kong (HK) timezone --- lib/microsoft/office.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 460f2f3f..2346be12 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -15,6 +15,8 @@ class Microsoft::Office TIMEZONE_MAPPING = { "Sydney": "AUS Eastern Standard Time", "Brisbane": "Australia/Brisbane", + "HK": "Asia/Hong_Kong", + "Hong_Kong": "Asia/Hong_Kong", "UTC": "UTC" } def initialize( From e721b60dbd5f6937a2d7792f3ec09574b698acd6 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 13 Feb 2019 13:19:16 +0000 Subject: [PATCH 1168/1752] [Slack Booking] Remove token data --- modules/aca/slack_booking.rb | 133 ++++++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 18 deletions(-) diff --git a/modules/aca/slack_booking.rb b/modules/aca/slack_booking.rb index 3e8d3e8a..212d3968 100644 --- a/modules/aca/slack_booking.rb +++ b/modules/aca/slack_booking.rb @@ -2,12 +2,6 @@ require 'slack/real_time/concurrency/libuv' require 'microsoft/office' -# App ID: AG5KR5JSX -# Client ID: 32027075415.549671188915 -# App Secret: 899130bef0277f54f17b7f8c49309c2d -# Signing Secret: 8e4f0421de528a602903aefc9acb4524 -# Verification Token: eYEilNP8PpfLop3rjKmVE3gz -# Bot token: xoxb-32027075415-549994563445-ORN5cfzecXHfQsoa9qUD6WVM class Aca::SlackBooking include ::Orchestrator::Constants include ::Orchestrator::Transcoder @@ -33,23 +27,109 @@ def on_unload @client = nil end + def post_message(text) + @client.web_client.chat_postMessage channel: setting(:channel), text: text, username: 'Room Bot' + end + # Message coming in from Slack API def on_message(data) - matched_lines = [ - "free room", - "free space", - "available room", - ] - if matched_lines.include?(data['text']) - @client.web_client.chat_postMessage channel: setting(:channel), text: "The following rooms are available:", username: 'Room Bot' - rooms = Orchestrator::ControlSystem.all.to_a - rooms.each do |room| - if room.bookable && room.name.include?("SYD") - @client.web_client.chat_postMessage channel: setting(:channel), text: room.name, username: 'Room Bot' + STDERR.puts "GOT MESSAGE" + STDERR.puts data.inspect + logger.info data + logger.info "Current command is #{self['current_command']}" + logger.info "Current command is nil? #{self['current_command'].nil?}" + STDERR.flush + if data.key?("subtype") && data['subtype'] == 'bot_message' + logger.info "Got a bot message, stopping." + return + else + if self['current_command'].nil? + logger.info "Matching against lines" + matched_lines = [ + "free room", + "free space", + "available room", + "rooms are free" + ] + if matched_lines.any? { |s| data['text'].include?(s) } + self['current_command'] = 'duration' + post_message("How long do you need it for (in minutes)?") + return + end + end + + if self['current_command'] == 'duration' + self['duration'] = data['text'].to_i + post_message("Let me check that for you...") + + rooms = Orchestrator::ControlSystem.all.to_a + room_emails = rooms.map {|r| r.email }.compact + + all_bookings = @office.get_bookings_by_user( + user_id: room_emails, + available_from: Time.now, + available_to: Time.now + self['duration'].minutes, + start_param: Time.now, + end_param: Time.now + self['duration'].minutes, + bulk: true + ) + post_message("The following rooms are available:") + count = 0 + rooms.each_with_index do |room, i| + if room.bookable && room.name.include?("SYD") && all_bookings[room.email][:available] + post_message("#{count + 1}. #{room.name}") + count += 1 + self['available_rooms'].push(room) + end + end + post_message("If you would like to book one, please specify its number now.") + self['current_command'] = 'selection' + return + end + + if self['current_command'] == 'selection' + self['selection'] = data['text'].to_i - 1 + self['selected_room'] = self['available_rooms'][self['selection']].email + post_message("You have selected #{self['available_rooms'][self['selection']].name}") + post_message("Who would you like to invite to this room? (separated by a space)") + self['current_command'] = 'emails' + return + end + + if self['current_command'] == 'emails' + # WOAH HACKY + emails = data['text'].split(""," ").split(" ").uniq + self['emails'] = emails.map {|e| {email: e, name: e} } + self['current_command'] = 'confirm' + post_message("Would you like me to book the following room? (yes / no)") + post_message("#{self['available_rooms'][self['selection']].name} for #{self['duration']} minutes with #{emails.join(" ")}") + return + end + + if self['current_command'] == 'confirm' + if data['text'].downcase == 'yes' || data['text'].downcase == 'y' + create_params = { + room_id: self['selected_room'], + start_param: Time.now, + end_param: Time.now + self['duration'].minutes, + subject: "Slack Booking", + description: "", + current_user: nil, + attendees: self['emails'], + recurrence: nil, + recurrence_end: nil, + timezone: ENV['TIMEZONE'] || 'UTC' + } + + # if room.settings.key?('direct_book') && room.settings['direct_book'] + # create_params[:endpoint_override] = room.email + # end + + @office.create_booking(create_params) + post_message("That's booked! Enjoy your meeting.") end end end - # @office end protected @@ -57,7 +137,22 @@ def on_message(data) # Create a realtime WS connection to the Slack servers def create_websocket + @office = ::Microsoft::Office.new({ + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ( ENV["OFFICE_CLIENT_SECRET"] || "M6o]=6{Qi>*:?+_>|%}#_s[*/}$1}^N[.=D&>Lg--}!+{=&.*{/:|J_|%.{="), + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + service_account_email: ENV['OFFICE_ACCOUNT_EMAIL'], + service_account_password: ENV['OFFICE_ACCOUNT_PASSWORD'], + internet_proxy: ENV['INTERNET_PROXY'] + }) + self['available_rooms'] = [] + self['current_command'] = nil + self['selected_room'] = nil + self['duration'] = nil # Set our token and other config options ::Slack.configure do |config| config.token = setting(:slack_api_token) @@ -78,6 +173,8 @@ def create_websocket #@client.typing channel: data.channel on_message(data) rescue Exception => e + logger.info e.message + logger.info e.backtrace.inspect end end From 88caeb053f2f16df655c68ab04dd4e73154fb614 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 14 Feb 2019 10:57:13 +0800 Subject: [PATCH 1169/1752] foxtel/iq2: add Back (globalcache IR code) --- modules/foxtel/iq2.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/foxtel/iq2.rb b/modules/foxtel/iq2.rb index 6ce71cc1..0f01ea56 100644 --- a/modules/foxtel/iq2.rb +++ b/modules/foxtel/iq2.rb @@ -67,7 +67,8 @@ def channel(number) enter: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,16,6,16,6,28,6,10,6,3224', channel_up: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,22,6,10,6,10,6,3231', channel_down:'1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,10,6,22,6,10,6,16,6,3225', - guide: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,28,6,10,6,28,6,10,6,3218' + guide: '1,37000,1,1,16,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,28,6,10,6,28,6,10,6,3218', + back: '1,36000,1,1,15,10,6,10,6,22,6,10,6,16,6,22,6,22,6,10,6,10,6,10,6,22,6,16,6,22,6,22,6,10,6,10,6,28,6,3212' } # Automatically creates a callable function for each command From 8f2bf79be063021e2805005940445f5c0610f615 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 16:18:23 +1100 Subject: [PATCH 1170/1752] [o365 lib] Add timezone checking from zones and remove mapping --- lib/microsoft/office.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 2346be12..5d2c03db 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -863,6 +863,14 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] recurrence_end = ensure_ruby_date(recurrence_end) + # Get the timezone out of the room's zone if it has any + rooms[0].zones.each do |zone_id| + zone = Orchestrator::Zone.find(zone_id) + if zone.settings.key?("timezone") + timezone = zone.settings['timezone'] + end + end + # Add the attendees attendees.map!{|a| if a[:optional] @@ -1003,6 +1011,14 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec start_param = ensure_ruby_date(start_param).in_time_zone(timezone).iso8601.split("+")[0] end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] + # Get the timezone out of the room's zone if it has any + room.zones.each do |zone_id| + zone = Orchestrator::Zone.find(zone_id) + if zone.settings.key?("timezone") + timezone = zone.settings['timezone'] + end + end + event = {} event[:subject] = subject if subject From ac7d30d959de6b71c0a6e277961c70f78b53b523 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 16:35:31 +1100 Subject: [PATCH 1171/1752] [o365] remove hardcoded timezones or even env vars, use backoffice settings instead --- lib/microsoft/office.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 5d2c03db..586b9017 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -12,13 +12,13 @@ class ErrorAccessDenied < Error; end end class Microsoft::Office - TIMEZONE_MAPPING = { - "Sydney": "AUS Eastern Standard Time", - "Brisbane": "Australia/Brisbane", - "HK": "Asia/Hong_Kong", - "Hong_Kong": "Asia/Hong_Kong", - "UTC": "UTC" - } + # TIMEZONE_MAPPING = { + # "Sydney": "AUS Eastern Standard Time", + # "Brisbane": "Australia/Brisbane", + # "HK": "Asia/Hong_Kong", + # "Hong_Kong": "Asia/Hong_Kong", + # "UTC": "UTC" + # } def initialize( client_id:, client_secret:, @@ -916,11 +916,11 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil }, start: { dateTime: start_param, - timeZone: TIMEZONE_MAPPING[timezone.to_sym] + timeZone: timezone }, end: { dateTime: end_param, - timeZone: TIMEZONE_MAPPING[timezone.to_sym] + timeZone: timezone }, location: { displayName: rooms.map{|room| room.name}.join(" and "), @@ -1024,12 +1024,12 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec event[:start] = { dateTime: start_param, - timeZone: TIMEZONE_MAPPING[timezone.to_sym] + timeZone: timezone } if start_param event[:end] = { dateTime: end_param, - timeZone: TIMEZONE_MAPPING[timezone.to_sym] + timeZone: timezone } if end_param event[:location] = { From 826badd9f7eea21fb21a0bd2bc2ead91c1eb0d8e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 17:07:14 +1100 Subject: [PATCH 1172/1752] [o365 lib] New timezone refactor --- lib/microsoft/office.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 586b9017..2af7852f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -856,14 +856,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil endpoint = "/v1.0/users/#{current_user[:email]}/events" end - # Ensure our start and end params are Ruby dates and format them in Graph format - start_object = ensure_ruby_date(start_param).in_time_zone(timezone) - end_object = ensure_ruby_date(end_param).in_time_zone(timezone) - start_param = ensure_ruby_date(start_param).in_time_zone(timezone).iso8601.split("+")[0] - end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] - recurrence_end = ensure_ruby_date(recurrence_end) - - # Get the timezone out of the room's zone if it has any + # Get the timezones out of the room's zone if it has any rooms[0].zones.each do |zone_id| zone = Orchestrator::Zone.find(zone_id) if zone.settings.key?("timezone") @@ -871,6 +864,14 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end end + + # Ensure our start and end params are Ruby dates and format them in Graph format + start_object = ensure_ruby_date(start_param).in_time_zone(timezone) + end_object = ensure_ruby_date(end_param).in_time_zone(timezone) + start_param = ensure_ruby_date(start_param).in_time_zone(timezone).iso8601.split("+")[0] + end_param = ensure_ruby_date(end_param).in_time_zone(timezone).iso8601.split("+")[0] + recurrence_end = ensure_ruby_date(recurrence_end) + # Add the attendees attendees.map!{|a| if a[:optional] From df9d76e2ac70def8fb469de351f8ee3ecb631613 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 22:13:57 +1100 Subject: [PATCH 1173/1752] Deal with multiple domains --- lib/microsoft/office.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 2af7852f..bc6a1094 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -594,13 +594,13 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) else # Check if attendee is external or internal if booking.key?('owner') - internal_domain = Mail::Address.new(booking['owner']).domain + internal_domains = [Mail::Address.new(booking['owner']).domain] else - internal_domain = ENV['INTERNAL_DOMAIN'] || internal_domain + internal_domains = ENV['INTERNAL_DOMAIN'].split(",") || internal_domain end mail_object = Mail::Address.new(attendee_email) mail_domain = mail_object.domain - booking_has_visitors = true if mail_domain != internal_domain + booking_has_visitors = true if not internal_domains.include?(mail_domain) attendee_object = { email: attendee_email, name: attendee['emailAddress']['name'], From ffdc1e0227292395ca7e20989f14d29eef487b32 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 22:16:32 +1100 Subject: [PATCH 1174/1752] [o365] Deal with multiple domains --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index bc6a1094..7634360d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -604,7 +604,7 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) attendee_object = { email: attendee_email, name: attendee['emailAddress']['name'], - visitor: (mail_domain != internal_domain), + visitor: internal_domains.include?(mail_domain), organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize } new_attendees.push(attendee_object) From d78593b4738613b3dff419e873543d09ff3e6f98 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 22:17:51 +1100 Subject: [PATCH 1175/1752] [o365] Deal with multiple domains --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 7634360d..81fd472f 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -604,7 +604,7 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) attendee_object = { email: attendee_email, name: attendee['emailAddress']['name'], - visitor: internal_domains.include?(mail_domain), + visitor: !internal_domains.include?(mail_domain), organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize } new_attendees.push(attendee_object) From b018364e21124c4e3d63863add105e11605814fc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 22:20:26 +1100 Subject: [PATCH 1176/1752] [o365] Make default bookings retreival for a month --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 81fd472f..f0fb18e3 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -498,7 +498,7 @@ def get_bookings_created_from(mailboxes:, created_from:) bookings end - def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.week), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil, extensions:[], custom_query:[]) + def get_bookings_by_user(user_id:, start_param:Time.now, end_param:(Time.now + 1.month), available_from: Time.now, available_to: (Time.now + 1.hour), bulk: false, availability: true, internal_domain:nil, ignore_booking: nil, extensions:[], custom_query:[]) # The user_ids param can be passed in as a string or array but is always worked on as an array user_id = Array(user_id) From 448ed91105f4be3cc1aab7e30ff7215cc97928bc Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 22:42:00 +1100 Subject: [PATCH 1177/1752] [o365] Only add room id if the system exists --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index f0fb18e3..42a7cd0c 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -703,7 +703,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['attendees'].each do |attendee| attendee_email = attendee['emailAddress']['address'] if attendee['type'] == 'resource' - booking['room_id'].push(attendee_email.downcase) + booking['room_id'].push(attendee_email.downcase) if Orchestrator::ControlSystem.find_by_email(attendee_email.downcase) else # Check if attendee is external or internal if booking.key?('owner') From 74d8075fdd4158e1711953beb18788bc26c46853 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 14 Feb 2019 22:46:19 +1100 Subject: [PATCH 1178/1752] [o365] i code so bad --- lib/microsoft/office.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 42a7cd0c..5c8a0541 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -709,15 +709,15 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d if booking.key?('owner') internal_domain = Mail::Address.new(booking['owner']).domain else - internal_domain = ENV['INTERNAL_DOMAIN'] || internal_domain + internal_domains = ENV['INTERNAL_DOMAIN'].split(",") || internal_domain end mail_object = Mail::Address.new(attendee_email) mail_domain = mail_object.domain - booking_has_visitors = true if mail_domain != internal_domain + booking_has_visitors = true if not internal_domains.include?(mail_domain) attendee_object = { email: attendee_email, name: attendee['emailAddress']['name'], - visitor: (mail_domain != internal_domain), + visitor: !internal_domains.include?(mail_domain), organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize } new_attendees.push(attendee_object) From bc762f3270e9ce6e7afdd0427916f7772dafa9cf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 19 Feb 2019 16:48:45 +1100 Subject: [PATCH 1179/1752] o365 - fix param names on availability search --- lib/microsoft/office.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 5c8a0541..47c046dc 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -381,12 +381,12 @@ def get_available_rooms(rooms:, start_param:, end_param:) activityDomain: 'unrestricted', timeslots: [{ start: { - DateTime: start_param, + dateTime: start_param, TimeZone: 'UTC' }, end: { - DateTime: end_param, - TimeZone: 'UTC' + dateTime: end_param, + timeZone: 'UTC' } }] } From 3c06006c9963b45baaa22a0ece13cdd441a7dd62 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Feb 2019 11:34:44 +1100 Subject: [PATCH 1180/1752] [o365] Fix internal domain config for towers --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 47c046dc..9d531292 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -707,7 +707,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d else # Check if attendee is external or internal if booking.key?('owner') - internal_domain = Mail::Address.new(booking['owner']).domain + internal_domains = [Mail::Address.new(booking['owner']).domain] else internal_domains = ENV['INTERNAL_DOMAIN'].split(",") || internal_domain end From 40c56b7b3f0dd67f6e4fb3eb5b2da3e1e27f4a95 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Feb 2019 12:13:38 +1100 Subject: [PATCH 1181/1752] [o365] Fix class for email address parsing --- lib/microsoft/office.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 9d531292..7a132770 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -594,11 +594,11 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) else # Check if attendee is external or internal if booking.key?('owner') - internal_domains = [Mail::Address.new(booking['owner']).domain] + internal_domains = [ ::Mail::Address.new(booking['owner']).domain ] else internal_domains = ENV['INTERNAL_DOMAIN'].split(",") || internal_domain end - mail_object = Mail::Address.new(attendee_email) + mail_object = ::Mail::Address.new(attendee_email) mail_domain = mail_object.domain booking_has_visitors = true if not internal_domains.include?(mail_domain) attendee_object = { @@ -707,11 +707,11 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d else # Check if attendee is external or internal if booking.key?('owner') - internal_domains = [Mail::Address.new(booking['owner']).domain] + internal_domains = [::Mail::Address.new(booking['owner']).domain] else internal_domains = ENV['INTERNAL_DOMAIN'].split(",") || internal_domain end - mail_object = Mail::Address.new(attendee_email) + mail_object = ::Mail::Address.new(attendee_email) mail_domain = mail_object.domain booking_has_visitors = true if not internal_domains.include?(mail_domain) attendee_object = { From fb94eb949ce31c263ff6518f122ece71fad6c76a Mon Sep 17 00:00:00 2001 From: viv Date: Wed, 20 Feb 2019 14:30:55 +1100 Subject: [PATCH 1182/1752] Update QSC camera control --- modules/qsc/q_sys_camera.rb | 42 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/modules/qsc/q_sys_camera.rb b/modules/qsc/q_sys_camera.rb index 62441ae2..93236995 100644 --- a/modules/qsc/q_sys_camera.rb +++ b/modules/qsc/q_sys_camera.rb @@ -17,12 +17,13 @@ def on_load def on_update @mod_id = setting(:driver) || :Mixer - @component = setting(:component) + @ids = setting(:ids) + self[:no_discrete_zoom] = true end def power(state) - powered = is_affirmative?(state) - camera.mute('toggle_privacy', state, @component) + state = is_affirmative?(state) + camera.mute(@ids[:power], state) end def adjust_tilt(direction) @@ -30,12 +31,12 @@ def adjust_tilt(direction) case direction when :down - camera.mute('tilt_down', true, @component) + camera.mute(@ids[:tilt_down], true) when :up - camera.mute('tilt_up', true, @component) + camera.mute(@ids[:tilt_up], true) else # stop - camera.mute('toggle_privacy', false, @component) - camera.mute('tilt_down', false, @component) + camera.mute(@ids[:tilt_up], false) + camera.mute(@ids[:tilt_down], false) end end @@ -44,17 +45,34 @@ def adjust_pan(direction) case direction when :right - camera.mute('pan_right', true, @component) + camera.mute(@ids[:pan_right], true) when :left - camera.mute('pan_left', true, @component) + camera.mute(@ids[:pan_left], true) else # stop - camera.mute('pan_right', false, @component) - camera.mute('pan_left', false, @component) + camera.mute(@ids[:pan_right], false) + camera.mute(@ids[:pan_left], false) end end def home - camera.component_trigger(@component, 'preset_home_load') + camera.trigger(@ids[:preset_home_load]) + end + + def preset(presetName) + home + end + + def zoom(direction) + direction = direction.to_sym + case direction + when :in + camera.mute(@ids[:zoom_in], true) + when :out + camera.mute(@ids[:zoom_out], true) + else #stop + camera.mute(@ids[:zoom_in], false) + camera.mute(@ids[:zoom_out], false) + end end protected From 0d5074e1baeb6844d7aab49a48646889e077b5f1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 20 Feb 2019 22:56:21 +1100 Subject: [PATCH 1183/1752] [o365] Only output graph request logging if not in test env --- lib/microsoft/office.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 7a132770..184b2280 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -88,7 +88,9 @@ def graph_request(request_method:, endpoint:, data:nil, query:{}, headers:nil, p graph_path = "#{@graph_domain}#{endpoint}" + if Rails.env != 'test' log_graph_request(request_method, data, query, headers, graph_path, password) + end graph_api_options = {inactivity_timeout: 25000, keepalive: false} @@ -142,7 +144,10 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers proxy = URI.parse(@internet_proxy) graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } end - log_graph_request(request_method, bulk_data, query, headers, graph_path, password, endpoints) + + if Rails.env != 'test' + log_graph_request(request_method, bulk_data, query, headers, graph_path, password, endpoints) + end graph_api = UV::HttpEndpoint.new(@graph_domain, graph_api_options) response = graph_api.__send__('post', path: graph_path, headers: headers, body: bulk_data) From c75ded9f6d6f278c648122e38a383e01d3c9a057 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Feb 2019 00:36:55 +1100 Subject: [PATCH 1184/1752] [o365] Add recurrence debugging --- lib/microsoft/office.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 184b2280..564de4d9 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -966,17 +966,21 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end if recurrence + recurrence_range = { + type: 'endDate', + startDate: start_object.strftime("%F"), + endDate: recurrence_end.strftime("%F") + } + STDERR.puts "Creating recurrence with details:" + STDERR.puts recurrence_range + STDERR.flush event[:recurrence] = { pattern: { type: recurrence, interval: 1, daysOfWeek: [start_object.strftime("%A")] }, - range: { - type: 'endDate', - startDate: start_object.strftime("%F"), - endDate: recurrence_end.strftime("%F") - } + range: recurrence_range } end From a7cddac8a99e1eac10ebfdc89ca6230e0c43d838 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 21 Feb 2019 00:39:13 +1100 Subject: [PATCH 1185/1752] [o365] Add recurrence debugging --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 564de4d9..a50e8d26 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -973,6 +973,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil } STDERR.puts "Creating recurrence with details:" STDERR.puts recurrence_range + STDERR.puts "In timezone: #{timezone}" STDERR.flush event[:recurrence] = { pattern: { From eea155877a7220f39d14e9b2c4fc4c0114c55ac6 Mon Sep 17 00:00:00 2001 From: pkheav Date: Thu, 21 Feb 2019 10:55:51 +1100 Subject: [PATCH 1186/1752] save walk_in to backend --- lib/microsoft/office.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 460f2f3f..00046047 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -632,6 +632,7 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) booking['parking'] = booking_info[:parking] if booking_info.key?(:parking) booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) booking['food_ordered'] = booking_info[:food_ordered] if booking_info.key?(:food_ordered) + booking['walk_in'] = booking_info[:walk_in] if booking_info.key?(:walk_in) end From 1fac3e95b0e4b7df58da8d0740725187a6c15e56 Mon Sep 17 00:00:00 2001 From: pkheav Date: Thu, 21 Feb 2019 15:36:03 +1100 Subject: [PATCH 1187/1752] fix walk_in saving --- lib/microsoft/office.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8bbabf15..e6c98fda 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -88,7 +88,7 @@ def graph_request(request_method:, endpoint:, data:nil, query:{}, headers:nil, p graph_path = "#{@graph_domain}#{endpoint}" - if Rails.env != 'test' + if Rails.env != 'test' log_graph_request(request_method, data, query, headers, graph_path, password) end @@ -145,7 +145,7 @@ def bulk_graph_request(request_method:, endpoints:, data:nil, query:nil, headers graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } end - if Rails.env != 'test' + if Rails.env != 'test' log_graph_request(request_method, bulk_data, query, headers, graph_path, password, endpoints) end @@ -756,6 +756,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['parking'] = booking_info[:parking] if booking_info.key?(:parking) booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) booking['food_ordered'] = booking_info[:food_ordered] if booking_info.key?(:food_ordered) + booking['walk_in'] = booking_info[:walk_in] if booking_info.key?(:walk_in) end From fa582fb8a37668e8dd12ee107024dcfc1f499f00 Mon Sep 17 00:00:00 2001 From: pkheav Date: Thu, 21 Feb 2019 15:51:20 +1100 Subject: [PATCH 1188/1752] add events field --- lib/microsoft/office.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index e6c98fda..cb75dd1e 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -640,6 +640,7 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) booking['food_ordered'] = booking_info[:food_ordered] if booking_info.key?(:food_ordered) booking['walk_in'] = booking_info[:walk_in] if booking_info.key?(:walk_in) + booking['event'] = booking_info[:event] if booking_info.key?(:event) end @@ -757,6 +758,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['notes'] = booking_info[:notes] if booking_info.key?(:notes) booking['food_ordered'] = booking_info[:food_ordered] if booking_info.key?(:food_ordered) booking['walk_in'] = booking_info[:walk_in] if booking_info.key?(:walk_in) + booking['event'] = booking_info[:event] if booking_info.key?(:event) end From 81f827de045e4939750cfdf02feb30a2d92a7b35 Mon Sep 17 00:00:00 2001 From: pkheav Date: Mon, 25 Feb 2019 10:17:39 +1100 Subject: [PATCH 1189/1752] Change timezone from 'Sydney' to 'Australia/Sydney' --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index cb75dd1e..824e9579 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -1006,7 +1006,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update - def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Sydney', endpoint_override:nil) + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Australia/Sydney', endpoint_override:nil) # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find_by_email(room_id) || Orchestrator::ControlSystem.find(room_id) From 5e67a7cd7f403af15b6a20fa217b02f867c14a46 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 25 Feb 2019 10:24:46 +0800 Subject: [PATCH 1190/1752] (aca:office_booking): Booking panel settings bindings: Update to match ngx-bookings Readme.MD --- modules/aca/office_booking.rb | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 8fa42d73..b7eba9e6 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -110,19 +110,43 @@ def on_update self[:touch_enabled] = setting(:touch_enabled) || false self[:name] = self[:room_name] = setting(:room_name) || system.name + self[:name] = self[:room_name] = setting(:room_name) || system.name + self[:touch_enabled] = setting(:touch_enabled) || false + self[:arrow_direction] = setting(:arrow_direction) + self[:hearing_assistance] = setting(:hearing_assistance) + self[:timeline_start] = setting(:timeline_start) + self[:timeline_end] = setting(:timeline_end) + self[:title] = setting(:title) + self[:description] = setting(:description) + self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url + + self[:timeout] = setting(:timeout) + self[:booking_cancel_timeout] = setting(:booking_cancel_timeout) self[:booking_controls] = setting(:booking_controls) self[:booking_catering] = setting(:booking_catering) self[:booking_hide_details] = setting(:booking_hide_details) self[:booking_hide_availability] = setting(:booking_hide_availability) self[:booking_hide_user] = setting(:booking_hide_user) + self[:booking_hide_modal] = setting(:booking_hide_modal) + self[:booking_hide_title] = setting(:booking_hide_title) self[:booking_hide_description] = setting(:booking_hide_description) self[:booking_hide_timeline] = setting(:booking_hide_timeline) - self[:booking_endable] = setting(:booking_endable) - self[:timeout] = setting(:timeout) self[:booking_set_host] = setting(:booking_set_host) self[:booking_set_title] = setting(:booking_set_title) + self[:booking_set_ext] = setting(:booking_set_ext) self[:booking_search_user] = setting(:booking_search_user) + self[:booking_disable_future] = setting(:booking_disable_future) + self[:booking_min_duration] = setting(:booking_min_duration) + self[:booking_max_duration] = setting(:booking_max_duration) + self[:booking_duration_step] = setting(:booking_duration_step) + self[:booking_endable] = setting(:booking_endable) + self[:booking_ask_cancel] = setting(:booking_ask_cancel) + self[:booking_ask_end] = setting(:booking_ask_end) + self[:booking_default_title] = setting(:booking_default_title) + self[:booking_select_free] = setting(:booking_select_free) + self[:booking_hide_all] = setting(:booking_hide_all) || false + self[:hide_all] = setting(:booking_hide_all) || false # for backwards compatibility only # Skype join button available 2min before the start of a meeting @skype_start_offset = setting(:skype_start_offset) || 120 From 506e61ef337c1ff3f65a2183981aa796e94d9ec9 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 25 Feb 2019 13:45:12 +1100 Subject: [PATCH 1191/1752] (excahnge bookings) add reason for meeting cancel_meeting logs to persisted logs too for easier debugging --- modules/aca/exchange_booking.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 94bd7f0a..bb26ad31 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -384,7 +384,7 @@ def start_meeting(meeting_ref) define_setting(:last_meeting_started, meeting_ref) end - def cancel_meeting(start_time) + def cancel_meeting(start_time, reason = "timeout") task { if start_time.class == Integer delete_ews_booking (start_time / 1000).to_i @@ -394,7 +394,7 @@ def cancel_meeting(start_time) delete_ews_booking start_time.to_i end }.then(proc { |count| - logger.debug { "successfully removed #{count} bookings" } + logger.warn { "successfully removed #{count} bookings due to #{reason}" } self[:last_meeting_started] = 0 self[:meeting_pending] = 0 @@ -791,7 +791,7 @@ def todays_bookings(first=false, skype_exists=false) else subject = item[:subject][:text] end - + { :Start => start, :End => ending, From 21def4a630f12bbff1561d6c64304c89dd048a0e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 25 Feb 2019 14:17:16 +1100 Subject: [PATCH 1192/1752] [Office Driver] Add debugging for booking deletion --- modules/aca/office_booking.rb | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index b7eba9e6..dbd7277e 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -380,18 +380,24 @@ def start_meeting(meeting_ref) define_setting(:last_meeting_started, meeting_ref) end - def cancel_meeting(start_time) - if start_time.class == Integer - delete_ews_booking (start_time / 1000).to_i - else - # Converts to time object regardless of start_time being string or time object - start_time = Time.parse(start_time.to_s) - delete_ews_booking start_time.to_i - end - self[:last_meeting_started] = start_time - self[:meeting_pending] = start_time - self[:meeting_ending] = false - self[:meeting_pending_notice] = false + def cancel_meeting(start_time, reason = "timeout") + task { + if start_time.class == Integer + delete_ews_booking (start_time / 1000).to_i + else + # Converts to time object regardless of start_time being string or time object + start_time = Time.parse(start_time.to_s) + delete_ews_booking start_time.to_i + end + }.then(proc { + logger.warn { "successfully removed #{count} bookings due to #{reason}" } + self[:last_meeting_started] = start_time + self[:meeting_pending] = start_time + self[:meeting_ending] = false + self[:meeting_pending_notice] = false + fetch_bookings + true + }) end # If last meeting started !== meeting pending then From aac6d74ff238d54fad1f47251d2ccf8c869b8fc1 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 25 Feb 2019 15:24:14 +1100 Subject: [PATCH 1193/1752] [o365 driver] Fix proc in cancel --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index dbd7277e..42acf278 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -389,7 +389,7 @@ def cancel_meeting(start_time, reason = "timeout") start_time = Time.parse(start_time.to_s) delete_ews_booking start_time.to_i end - }.then(proc { + }.then(proc { |count| logger.warn { "successfully removed #{count} bookings due to #{reason}" } self[:last_meeting_started] = start_time self[:meeting_pending] = start_time From 1b45d3409f158e28b39059ea01839930e7140a23 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 27 Feb 2019 14:37:57 +1100 Subject: [PATCH 1194/1752] [0365] Put support for room_ids as an array back --- lib/microsoft/office.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 824e9579..a74c5c4b 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -731,8 +731,6 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d end end - # Remove me once frontend supports multi-room - booking['room_id'] = booking['room_id'][0] booking['visitors'] = booking_has_visitors booking['old_attendees'] = booking['attendees'] booking['attendees'] = new_attendees From b646399953c08a3505899c13808f702ae0a56ab5 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 27 Feb 2019 16:41:46 +1100 Subject: [PATCH 1195/1752] (meeting room logic) expose doors setting --- modules/aca/meeting_room.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index fcd0f0d4..40343aeb 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -25,6 +25,7 @@ def on_update self[:vc_show_pres_layout] = setting(:vc_show_pres_layout) self[:hide_vc_sources] = setting(:hide_vc_sources) self[:mics_mutes] = setting(:mics_mutes) + self[:doors] = setting(:doors) @confidence_monitor = setting(:confidence_monitor) # Get any default settings From f9563201352233673d53973bea7061e5e924b963 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 28 Feb 2019 18:36:35 +1100 Subject: [PATCH 1196/1752] (sony:cbx) add basic power on/off control --- modules/sony/display/cbx.rb | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100755 modules/sony/display/cbx.rb diff --git a/modules/sony/display/cbx.rb b/modules/sony/display/cbx.rb new file mode 100755 index 00000000..f914e360 --- /dev/null +++ b/modules/sony/display/cbx.rb @@ -0,0 +1,61 @@ +module Sony; end +module Sony::Display; end + +# Documentation: https://docs.google.com/spreadsheets/d/1F8RyaDqLYlKBT7fHJ_PT7yHwH3vVMZue4zUc5McJVYM/edit?usp=sharing + +class Sony::Display::CBX + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + tcp_port 4999 + descriptive_name 'Sony CBX Display' + generic_name :Display + + def on_load + on_update + end + + def on_update + end + + def connected + schedule.every('60s') { power? } + end + + def disconnected + schedule.clear + end + + # + # Power commands + # + def power(state, opt = nil) + if is_affirmative?(state) + send("8C 00 00 02 01 8F", hex_string: true) + logger.debug "-- sony display requested to power on" + else + send("8C 00 00 02 01 8F", hex_string: true) + logger.debug "-- sony display requested to power off" + end + + # Request status update + power? + end + + def power?(options = {}) + options[:priority] = 0 + options[:hex_string] = true + send("83 00 00 FF FF 81", options) + end + + def received(byte_str, resolve, command) # Data is default received as a string + logger.debug { "sony display sent: 0x#{byte_to_hex(byte_str)}" } + if byte_str.start_with?("\x70\x0\x0\x1") + self[:power] = true + elsif byte_str == "\x70\x0\x0\x0\x72" + self[:power] = false + end + :success + end +end From f834bf2a71574d311e56ac0df902f48c691cd74d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 28 Feb 2019 22:28:34 +1000 Subject: [PATCH 1197/1752] (cisco:sx80,room_kit) add video layout control --- modules/cisco/collaboration_endpoint/room_kit.rb | 5 +++++ modules/cisco/collaboration_endpoint/sx80.rb | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 4f607697..713b633f 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -55,6 +55,7 @@ def connected status 'Video Selfview FullScreenMode' => :selfview_fullscreen status 'Video Input' => :video_input status 'Video Output' => :video_output + status 'Video Layout LayoutFamily Local' => :video_layout status 'Standby State' => :standby command 'Audio Microphones Mute' => :mic_mute_on @@ -123,6 +124,10 @@ def mic_mute(state = On) Layout_: [:Equal, :PIP], # physical connector... SourceId_: (1..3) # ...or the logical source ID + command 'Video Layout LayoutFamily Set' => :video_layout, + LayoutFamily: [:Auto, :Equal, :Overlay, :Prominent, :Single], + Target_: [:Local, :Remote] + command 'Video Selfview Set' => :selfview, Mode_: [:On, :Off], FullScreenMode_: [:On, :Off], diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index 8552e34d..a131a7e7 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -53,6 +53,7 @@ def connected status 'Video Selfview FullScreenMode' => :selfview_fullscreen status 'Video Input' => :video_input status 'Video Output' => :video_output + status 'Video Layout LayoutFamily Local' => :video_layout status 'Standby State' => :standby command 'Audio Microphones Mute' => :mic_mute_on @@ -121,6 +122,10 @@ def mic_mute(state = On) Layout_: [:Equal, :PIP], # physical connector... SourceId_: (1..4) # ...or the logical source ID + command 'Video Layout LayoutFamily Set' => :video_layout, + LayoutFamily: [:Auto, :Equal, :Overlay, :Prominent, :Single], + Target_: [:Local, :Remote] + command 'Video Selfview Set' => :selfview, Mode_: [:On, :Off], FullScreenMode_: [:On, :Off], From a80f2085a45b65ff6baed45a6db2f075d778294c Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 28 Feb 2019 22:56:54 +1000 Subject: [PATCH 1198/1752] (cisco:room_os) fix exception when receiving malformed status response --- modules/cisco/collaboration_endpoint/room_os.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index cc9770b6..670b0ad4 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -295,7 +295,11 @@ def send_xstatus(path) :success else error = response.dig :CommandResponse, :Status - logger.error "#{error[:Reason]} (#{error[:XPath]})" + if error + logger.error "#{error[:Reason]} (#{error[:XPath]})" + else + logger.error "bad response: #{response[:CommandResponse]}" + end :abort end end From acb9d6ca5bc15ac30dbd0d305b3b1d1b203042e6 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 4 Mar 2019 14:18:52 +1100 Subject: [PATCH 1199/1752] [o365] Update bulk method to use larger page size --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index a74c5c4b..c52bfd6d 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -805,7 +805,7 @@ def bookings_request_by_users(user_ids, start_param=Time.now, end_param=(Time.no responses = [] all_endpoints.each_slice(slice_size).with_index do |endpoints, ind| query = { - '$top': 200, + '$top': 10000, startDateTime: start_param, endDateTime: end_param, } From 482c3647004b175123f3e68554319eaff71bebc9 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 11 Mar 2019 14:47:45 +1100 Subject: [PATCH 1200/1752] Update office.rb return check_ins with booking --- lib/microsoft/office.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index c52bfd6d..907135ba 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -641,6 +641,7 @@ def format_booking_data(booking, room_email, internal_domain=nil, extensions=[]) booking['food_ordered'] = booking_info[:food_ordered] if booking_info.key?(:food_ordered) booking['walk_in'] = booking_info[:walk_in] if booking_info.key?(:walk_in) booking['event'] = booking_info[:event] if booking_info.key?(:event) + booking['check_ins'] = booking_info[:check_ins] if booking_info.key?(:check_ins) end @@ -757,6 +758,7 @@ def extract_booking_data(booking, start_param, end_param, room_email, internal_d booking['food_ordered'] = booking_info[:food_ordered] if booking_info.key?(:food_ordered) booking['walk_in'] = booking_info[:walk_in] if booking_info.key?(:walk_in) booking['event'] = booking_info[:event] if booking_info.key?(:event) + booking['check_ins'] = booking_info[:check_ins] if booking_info.key?(:check_ins) end From 665efdaf44f7107103852931ae4366cdfd543882 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 20 Mar 2019 14:41:40 +1100 Subject: [PATCH 1201/1752] (aca:people count) ensure only basic objects returned --- modules/aca/tracking/people_counter.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 5288f4ec..80f932eb 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -37,7 +37,7 @@ def booking_changed(details) } end end - + nil end def get_current_booking(details) @@ -83,6 +83,7 @@ def count_changed(new_count) # Save it back current_dataset.save! + new_count end def create_dataset(count, booking) @@ -176,6 +177,7 @@ def on_update self[:todays_bookings] = system[:Bookings][:today] booking_changed(self[:todays_bookings]) } + nil end def update_state From 4e99cacf9408876cbdad9e0a92b724ca2d854544 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 20 Mar 2019 14:42:21 +1100 Subject: [PATCH 1202/1752] (philips:sicp display) ensure correct input selected --- modules/philips/display/sicp_protocol.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/philips/display/sicp_protocol.rb b/modules/philips/display/sicp_protocol.rb index 8cb3ad96..d27ec4b7 100644 --- a/modules/philips/display/sicp_protocol.rb +++ b/modules/philips/display/sicp_protocol.rb @@ -134,7 +134,7 @@ def set_power_recovery(mode) Inputs.merge!(Inputs.invert) def switch_to(input) - inp = self[:input] = input.to_sym + inp = self[:input] = input.to_s.downcase.to_sym do_send Command[:input], Inputs[inp], 0, 1, 0, name: :input end From d6cfac97ca76a5216c3e580f8cf76e6fc87553a0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 20 Mar 2019 16:32:35 +1100 Subject: [PATCH 1203/1752] prevent overlapping people count updates --- modules/aca/tracking/people_counter.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 80f932eb..6cc41523 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -58,6 +58,14 @@ def get_current_booking(details) end def count_changed(new_count) + # Prevent overlapping processing + if @processing + @was_updated = true + @adjusted_count = new_count + return new_count + end + + @processing = true new_count = 0 if new_count == -1 return if self[:todays_bookings].nil? || self[:todays_bookings].empty? # Check the current meeting @@ -84,6 +92,17 @@ def count_changed(new_count) # Save it back current_dataset.save! new_count + ensure + if @was_updated + # Prevent spamming of this function + schedule.in(3000) do + @processing = false + @was_updated = false + count_changed(@adjusted_count) + end + else + @processing = false + end end def create_dataset(count, booking) From 942231037dbc1d62ec9f1c4088bed605de27a38f Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 20 Mar 2019 17:09:04 +1100 Subject: [PATCH 1204/1752] (tracking:people counter) ensure current id and rate limit --- modules/aca/tracking/people_counter.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/aca/tracking/people_counter.rb b/modules/aca/tracking/people_counter.rb index 6cc41523..604f0e90 100644 --- a/modules/aca/tracking/people_counter.rb +++ b/modules/aca/tracking/people_counter.rb @@ -70,7 +70,7 @@ def count_changed(new_count) return if self[:todays_bookings].nil? || self[:todays_bookings].empty? # Check the current meeting current = get_current_booking(self[:todays_bookings]) - return if current.nil? + return unless current && current[:id] logger.info "Count changed: #{new_count} and ID: #{current[:id]}" @@ -93,15 +93,15 @@ def count_changed(new_count) current_dataset.save! new_count ensure - if @was_updated - # Prevent spamming of this function - schedule.in(3000) do + schedule.in(5000) do + if @was_updated + # Prevent spamming of this function @processing = false @was_updated = false count_changed(@adjusted_count) + else + @processing = false end - else - @processing = false end end From 3d5fe0589e4697eb28133405fb0e47e3308e7532 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 26 Mar 2019 17:17:28 +1100 Subject: [PATCH 1205/1752] (cisco:broadworks) XSI events manager allows processing of events coming from callcenters --- lib/cisco/broad_soft/broad_works.rb | 193 ++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100755 lib/cisco/broad_soft/broad_works.rb diff --git a/lib/cisco/broad_soft/broad_works.rb b/lib/cisco/broad_soft/broad_works.rb new file mode 100755 index 00000000..fd1e3f4a --- /dev/null +++ b/lib/cisco/broad_soft/broad_works.rb @@ -0,0 +1,193 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +=begin + +Example Usage: +bw = Cisco::BroadSoft::BroadWorks.new("xsi-events.domain.com", "user@domain.com", "Password!", proxy: ENV['HTTPS_PROXY']) + +# Processing events +Thread.new do + bw.open_channel do |event| + thread.new { bw.acknowledge(event[:event_id]) } + puts "\n\Processing! #{event}\n\n" + end +end + +# Keeping the connection alive +Thread.new do + sleep 8 + loop do + bw.heartbeat + sleep 5 + end +end + +# Registering for Automatic Call Distribution Events +bw.get_user_events "0478242466@domain.au" + +=end + +require "active_support/all" +require "securerandom" +require "nokogiri" +require "logger" +require "httpi" +require "json" + +module Cisco; end +module Cisco::BroadSoft; end + +class Cisco::BroadSoft::BroadWorks + def initialize(domain, username, password, application: "ACAEngine", proxy: nil, logger: Logger.new(STDOUT)) + @domain = domain + @username = username + @password = password + @logger = logger + @proxy = proxy + @application_id = application + + @channel_set_id = SecureRandom.uuid + end + + attr_reader :expires + attr_reader :channel_id + attr_reader :channel_set_id + + def open_channel + expires = 6.hours.to_i + + channel_xml = %( + + #{@channel_set_id} + 1 + 50 + #{expires} + ) + + conn = new_request + conn.url = "https://#{@domain}/com.broadsoft.async/com.broadsoft.xsi-events/v2.0/channel" + conn.body = channel_xml + + conn.on_body do |chunk| + event = parse_event(chunk) + yield event if event + end + + @logger.info "Channel #{@channel_set_id} requested..." + HTTPI.post(conn) + @logger.info "Channel #{@channel_set_id} closed..." + end + + def heartbeat + conn = new_request + conn.url = "https://#{@domain}/com.broadsoft.xsi-events/v2.0/channel/#{@channel_id}/heartbeat" + response = HTTPI.put(conn) + if response.code != 200 + @logger.warn "heartbeat failed\n#{response.inspect}" + end + end + + def acknowledge(event_id) + channel_xml = %( + + #{event_id} + 200 + OK + ) + + conn = new_request + conn.url = "https://#{@domain}/com.broadsoft.xsi-events/v2.0/channel/eventresponse" + conn.body = channel_xml + response = HTTPI.post(conn) + if response.code == 200 + @logger.debug "acknowledgment #{event_id}" + else + @logger.warn "acknowledgment failed\n#{response.inspect}" + end + end + + def parse_event(data) + xml = Nokogiri::XML(data) + xml.remove_namespaces! + + begin + response_type = xml.children[0].name + case response_type + when 'Channel' + @channel_id = xml.xpath("//channelId").inner_text + @expires = (xml.xpath("//expires").inner_text.to_i - 5).seconds.from_now + @logger.info "channel established #{@channel_id}" + @logger.debug "channel details:\n#{data}" + nil + when 'Event' + event_data = xml.xpath("//eventData")[0] + { + event_id: xml.xpath("//eventID").inner_text, + event_name: event_data["type"].split(':')[1], + event_data: event_data + } + else + @logger.info "recieved #{response_type} on channel" + nil + end + rescue => e + @logger.error "processing event\n#{e.message}\nevent: #{data}" + nil + end + end + + # Admin Commands: + def renew(expires_in = 6.hours.to_i) + channel_xml = %( + + #{expires_in} + ) + + conn = new_request + conn.url = "https://#{@domain}/com.broadsoft.xsi-events/v2.0/channel/#{@channel_id}" + conn.body = channel_xml + response = HTTPI.put(conn) + if response.code != 200 + @logger.warn "channel renewal failed\n#{response.inspect}" + end + end + + def close_channel + conn = new_request + conn.url = "https://#{@domain}/com.broadsoft.xsi-events/v2.0/channel/#{@channel_id}" + response = HTTPI.delete(conn) + if response.code != 200 + @logger.warn "channel close failed\n#{response.inspect}" + end + end + + def get_user_events(group, events = "Call Center Queue") + channel_xml = %( + + #{events} + #{6.hours.to_i} + #{@channel_set_id} + #{@application_id} + ) + + conn = new_request + conn.url = "https://#{@domain}/com.broadsoft.xsi-events/v2.0/user/#{group}" + conn.body = channel_xml + response = HTTPI.post(conn) + if response.code == 200 + # TODO:: extract the subscription ID + else + @logger.warn "get user events failed\n#{response.inspect}" + end + end + + def new_request + conn = HTTPI::Request.new + conn.proxy = @proxy if @proxy + conn.headers['Content-Type'] = 'application/xml' + conn.headers['Accept'] = 'application/xml; charset=UTF-8' + conn.auth.basic(@username, @password) + conn + end +end From 5f667aa569354f4e20f275d92b470c84fbae852b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 27 Mar 2019 16:27:26 +1100 Subject: [PATCH 1206/1752] (cisco:broadworks) initial signage monitor driver --- lib/cisco/broad_soft/broad_works.rb | 79 ++++++++--- modules/cisco/broad_works.rb | 200 ++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 15 deletions(-) create mode 100644 modules/cisco/broad_works.rb diff --git a/lib/cisco/broad_soft/broad_works.rb b/lib/cisco/broad_soft/broad_works.rb index fd1e3f4a..5934c2dc 100755 --- a/lib/cisco/broad_soft/broad_works.rb +++ b/lib/cisco/broad_soft/broad_works.rb @@ -9,20 +9,10 @@ # Processing events Thread.new do bw.open_channel do |event| - thread.new { bw.acknowledge(event[:event_id]) } puts "\n\Processing! #{event}\n\n" end end -# Keeping the connection alive -Thread.new do - sleep 8 - loop do - bw.heartbeat - sleep 5 - end -end - # Registering for Automatic Call Distribution Events bw.get_user_events "0478242466@domain.au" @@ -47,14 +37,22 @@ def initialize(domain, username, password, application: "ACAEngine", proxy: nil, @proxy = proxy @application_id = application + @keepalive_active = false + @channel_open = false + @heartbeat = true + @channel_set_id = SecureRandom.uuid end attr_reader :expires attr_reader :channel_id attr_reader :channel_set_id + attr_reader :channel_open def open_channel + return if @channel_open + @channel_open = true + expires = 6.hours.to_i channel_xml = %( @@ -70,13 +68,20 @@ def open_channel conn.body = channel_xml conn.on_body do |chunk| + @heartbeat = false event = parse_event(chunk) yield event if event end + keepalive + @logger.info "Channel #{@channel_set_id} requested..." HTTPI.post(conn) @logger.info "Channel #{@channel_set_id} closed..." + ensure + @channel_open = false + # notify of the disconnect + yield nil end def heartbeat @@ -84,7 +89,36 @@ def heartbeat conn.url = "https://#{@domain}/com.broadsoft.xsi-events/v2.0/channel/#{@channel_id}/heartbeat" response = HTTPI.put(conn) if response.code != 200 - @logger.warn "heartbeat failed\n#{response.inspect}" + if response.code == 404 + # Seems multiple servers handle requests and not all are aware of channels! + # This just desperately tries to hit the right server to keep the channel alive + sleep 1 + heartbeat if @heartbeat && @channel_open + else + @logger.warn "heartbeat failed\n#{response.inspect}" + end + end + end + + def keepalive + return if @keepalive_active + @keepalive_active = true + @logger.debug "heartbeat starting..." + Thread.new do + begin + loop do + @heartbeat = true + sleep 6 + break unless @channel_open + heartbeat if @heartbeat + end + rescue => e + @logger.error "error performing heartbeat: #{e.message}" + ensure + @keepalive_active = false + # Ensure this is always running if a channel is open + keepalive if @channel_open + end end end @@ -119,16 +153,24 @@ def parse_event(data) @expires = (xml.xpath("//expires").inner_text.to_i - 5).seconds.from_now @logger.info "channel established #{@channel_id}" @logger.debug "channel details:\n#{data}" - nil + { + channel_id: @channel_id, + event_name: 'new_channel' + } when 'Event' event_data = xml.xpath("//eventData")[0] - { + event = { + channel_id: xml.xpath("//channelId").inner_text, + user_id: xml.xpath("//userId").inner_text, + subscription_id: xml.xpath("//subscriptionId").inner_text, event_id: xml.xpath("//eventID").inner_text, event_name: event_data["type"].split(':')[1], event_data: event_data } + Thread.new { acknowledge(event[:event_id]) } + event else - @logger.info "recieved #{response_type} on channel" + @logger.debug "recieved #{response_type} on channel" nil end rescue => e @@ -176,9 +218,16 @@ def get_user_events(group, events = "Call Center Queue") conn.body = channel_xml response = HTTPI.post(conn) if response.code == 200 - # TODO:: extract the subscription ID + # extract the subscription ID + xml = Nokogiri::XML(response.body) + xml.remove_namespaces! + xml.xpath("//subscriptionId").inner_text + elsif response.code == 404 + sleep 1 + get_user_events(group, events) if @channel_open else @logger.warn "get user events failed\n#{response.inspect}" + raise "bad response when requesting events #{response.code}\n#{response.body}" end end diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb new file mode 100644 index 00000000..c3b5914b --- /dev/null +++ b/modules/cisco/broad_works.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +module Cisco; end + +::Orchestrator::DependencyManager.load('Cisco::BroadSoft::BroadWorks', :model, :force) + +class Cisco::BroadWorks + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + include ::Orchestrator::Security + + descriptive_name 'Cisco BroadWorks Call Center Management' + generic_name :CallCenter + + # Discovery Information + implements :service + keepalive false # HTTP keepalive + + default_settings({ + username: :cisco, + password: :cisco, + events_domain: 'xsi-events.endpoint.com', + proxy: 'http://proxy.com:8080', + callcenters: {"phone_number@domain.com" => "Other Matters"} + }) + + def on_load + HTTPI.log = false + @terminated = false + + # TODO:: Load todays stats from the database if they exist + + + on_update + end + + def on_update + @username = setting(:username) + @password = setting(:password) + @domain = setting(:events_domain) + @proxy = setting(:proxy) || ENV["HTTPS_PROXY"] || ENV["https_proxy"] + @callcenters = setting(:callcenters) || {} + + # "sub_id" => "callcenter id" + @subscription_lookup = {} + + @poll_sched&.cancel + @reset_sched&.cancel + + # Every 2 min + @poll_sched = schedule.every(120_000) do + @callcenters.each_key { |id| get_call_count(id) } + end + # Every day at 5am + @reset_sched = schedule.cron("0 5 * * *") { reset_stats } + + connect + end + + def reset_stats + # "callcenter id" => count + @queued_calls = {} + @abandoned_calls = {} + + # "callcenter id" => Array(wait_times) + @calls_taken = {} + end + + def on_unload + @terminated = true + @bw.close_channel + end + + def connect + return if @terminated + + @bw.close_channel if @bw + reactor = self.thread + callcenters = @callcenters + + @bw = Cisco::BroadSoft::BroadWorks.new(@domain, @username, @password, proxy: @proxy) + bw = @bw + Thread.new do + bw.open_channel do |event| + reactor.schedule { process_event(event) } + end + end + nil + end + + SHOULD_UPDATE = Set.new(['ACDCallAddedEvent', 'ACDCallAbandonedEvent', 'CallReleasedEvent', 'ACDCallStrandedEvent', 'ACDCallAnsweredByAgentEvent']) + + def process_event(event) + # Check if the connection was terminated + if event.nil? + if !@terminated + schedule.in(1000) do + @bw = nil + connect + end + end + return + end + + # Lookup the callcenter in question + call_center_id = @subscription_lookup[event[:subscription_id]] + + # Otherwise process the event + case event[:event_name] + when 'new_channel' + monitor_callcenters + when 'ACDCallAddedEvent' + count = @queued_calls[call_center_id] + @queued_calls[call_center_id] = count.to_i + 1 + when 'ACDCallAbandonedEvent' + count = @abandoned_calls[call_center_id] + @abandoned_calls[call_center_id] = count.to_i + 1 + + count = @queued_calls[call_center_id] + @queued_calls[call_center_id] = count.to_i - 1 + when 'CallReleasedEvent', 'ACDCallStrandedEvent' + # Not entirely sure when this happens + count = @queued_calls[call_center_id] + @queued_calls[call_center_id] = count.to_i - 1 + when 'ACDCallAnsweredByAgentEvent' + # TODO:: Call answered by "0278143573@det.nsw.edu.au" + # Possibly we can monitor the time this conversion goes for? + count = @queued_calls[call_center_id] + @queued_calls[call_center_id] = count.to_i - 1 + + # Extract wait time... + event_data = event[:event_data] + start_time = event_data.xpath("//addTime").inner_text.to_i + answered_time = event_data.xpath("//removeTime").inner_text.to_i + wait_time = answered_time - start_time + + wait_times = @calls_taken[call_center_id] || [] + wait_times << wait_time + @calls_taken[call_center_id] = wait_times + else + logger.debug { "ignoring event #{event[:event_name]}" } + end + + if SHOULD_UPDATE.include?(event[:event_name]) + update_stats + end + end + + def monitor_callcenters + # Reset the lookups + @subscription_lookup = {} + @queued_calls = {} + + bw = @bw + reactor = self.thread + @callcenters.each do |id, name| + # Run the request on the thread pool + retries = 0 + begin + task { + sub_id = bw.get_user_events(id) + + # Ensure the mapping is maintained + reactor.schedule do + if bw.channel_open + @subscription_lookup[sub_id] = id + get_call_count(id) + end + end + }.value + + break unless bw.channel_open + rescue => e + logger.error "monitoring callcenter #{name}\n#{e.message}" + break unless bw.channel_open + retries += 1 + retry unless retries > 3 + end + end + end + + # Non-event related calls + def get_call_count(call_center_id) + get('/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls') do |data, resolve, command| + if data.status == 200 + xml = Nokogiri::XML(data.body) + xml.remove_namespaces! + @queued_calls[call_center_id] = xml.xpath("//queueEntry").length + else + logger.warn "failed to retrieve active calls for #{@callcenters[call_center_id]} - response code: #{data&.status}" + end + :success + end + end + + def update_stats + queues = {} + end +end From b3c24d30ed7d9a43307e16c229b65d5496711acf Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 12:01:40 +1100 Subject: [PATCH 1207/1752] Add new office library structure --- lib/microsoft/office/client.rb | 337 ++++++++++++++++++++++++++++++++ lib/microsoft/office/contact.rb | 26 +++ lib/microsoft/office/event.rb | 98 ++++++++++ lib/microsoft/office/events.rb | 304 ++++++++++++++++++++++++++++ lib/microsoft/office/model.rb | 43 ++++ lib/microsoft/office/user.rb | 28 +++ 6 files changed, 836 insertions(+) create mode 100644 lib/microsoft/office/client.rb create mode 100644 lib/microsoft/office/contact.rb create mode 100644 lib/microsoft/office/event.rb create mode 100644 lib/microsoft/office/events.rb create mode 100644 lib/microsoft/office/model.rb create mode 100644 lib/microsoft/office/user.rb diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb new file mode 100644 index 00000000..978177b4 --- /dev/null +++ b/lib/microsoft/office/client.rb @@ -0,0 +1,337 @@ +require 'active_support/time' +require 'microsoft/officenew' +require 'microsoft/office/model' +require 'microsoft/office/user' +require 'microsoft/office/contact' +require 'microsoft/office/event' +module Microsoft + class Error < StandardError + class ResourceNotFound < Error; end + class InvalidAuthenticationToken < Error; end + class BadRequest < Error; end + class ErrorInvalidIdMalformed < Error; end + class ErrorAccessDenied < Error; end + end +end + +class Microsoft::Officenew; end + +## +# This class provides a client to interface between Microsoft Graph API and ACA Engine. Instances of this class are +# primarily only used for: +# - +class Microsoft::Officenew::Client + + ## + # Initialize the client for making requests to the Office365 API. + # @param [String] client_id The client ID of the application registered in the application portal or Azure + # @param [String] client_secret The client secret of the application registered in the application portal or Azure + # @param [String] app_site The site in which to send auth requests. This is usually "https://login.microsoftonline.com" + # @param [String] app_token_url The token URL in which to send token requests + # @param [String] app_scope The oauth scope to pass to token requests. This is usually "https://graph.microsoft.com/.default" + # @param [String] graph_domain The domain to pass requests to Graph API. This is usually "https://graph.microsoft.com" + def initialize( + client_id:, + client_secret:, + app_site:, + app_token_url:, + app_scope:, + graph_domain: + ) + @client_id = client_id + @client_secret = client_secret + @app_site = app_site + @app_token_url = app_token_url + @app_scope = app_scope + @graph_domain = graph_domain + oauth_options = { site: @app_site, token_url: @app_token_url } + oauth_options[:connection_opts] = { proxy: @internet_proxy } if @internet_proxy + @graph_client ||= OAuth2::Client.new( + @client_id, + @client_secret, + oauth_options + ) + end + + ## + # Retrieve a list of users stored in Office365 + # + # @param q [String] The query param which filters all users without a name or email matching this string + # @param limit [String] The maximum number of users to return + def get_users(q: nil, limit: nil) + + # If we have a query and the query has at least one space + if q && q.include?(" ") + # Split it into word tokens + queries = q.split(" ") + filter_params = [] + + # For each word, create a filtering statement + queries.each do |q| + filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))") + end + + # Join these filtering statements using 'or' and add accountEnabled filter + filter_param = "(accountEnabled eq true) and #{filter_params.join(" and ")}" + else + # Or just add the space-less query to be checked for each field + filter_param = "(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))" if q + end + + # If we have no query then still only grab enabled accounts + filter_param = "accountEnabled eq true" if q.nil? + + # Put our params together and make the request + query_params = { + '$filter': filter_param, + '$top': limit + }.compact + endpoint = "/v1.0/users" + request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) + check_response(request) + + # Return the parsed user data + JSON.parse(request.body)['value'].map {|u| Microsoft::Officenew::User.new(client: self, user: u)} + end + + ## + # Retrieve a list of contacts for some passed in mailbox + # + # @param mailbox [String] The mailbox email of which we want to grab the contacts for + # @param q [String] The query param which filters all contacts without a name or email matching this string + # @param limit [String] The maximum number of contacts to return + def get_contacts(mailbox:, q:nil, limit:nil) + query_params = { '$top': (limit || 1000) } + query_params['$filter'] = "startswith(displayName, '#{q}') or startswith(givenName, '#{q}') or startswith(surname, '#{q}') or emailAddresses/any(a:a/address eq '#{q}')" if q + endpoint = "/v1.0/users/#{mailbox}/contacts" + request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) + check_response(request) + JSON.parse(request.body)['value'].map {|c| Microsoft::Officenew::Contact.new(client: self, contact: c)} + end + + + ## + # For every mailbox (email) passed in, this method will grab all the bookings and, if + # requested, return the availability of the mailboxes for some time range. + # + # @param mailboxes [Array] An array of mailbox emails to pull bookings from. These are generally rooms but could be users. + # @option options [Integer] :created_from Get all the bookings created after this seconds epoch + # @option options [Integer] :start_param Get all the bookings that occurr between this seconds epoch and end_param + # @option options [Integer] :end_param Get all the bookings that occurr between this seconds epoch and start_param + # @option options [Integer] :available_from If bookings exist between this seconds epoch and available_to then the room is market as not available + # @option options [Integer] :available_to If bookings exist between this seconds epoch and available_from then the room is market as not available + # @option options [Array] :ignore_bookings An array of icaluids for bookings which are ignored if they fall within the available_from and to time range + # @option options [String] :extension_name The name of the extension list to retreive in O365. This probably doesn't need to change + # @return [Hash] A hash of room emails to availability and bookings fields, eg: + # @example + # An example response: + # { + # 'room1@example.com' => { + # available: false, + # bookings: [ + # { + # subject: 'Blah', + # start_epoch: 1552974994 + # ... + # }, { + # subject: 'Foo', + # start_epoch: 1552816751 + # ... + # } + # ] + # }, + # 'room2@example.com' => { + # available: false, + # bookings: [{ + # subject: 'Blah', + # start_epoch: 1552974994 + # }] + # ... + # } + # } + def get_bookings(mailboxes:, options:{}) + default_options = { + created_from: nil, + start_param: nil, + end_param: nil, + available_from: nil, + available_to: nil, + ignore_bookings: [], + extension_name: "Com.Acaprojects.Extensions" + } + # Merge in our default options with those passed in + options = options.reverse_merge(default_options) + + # We should always have an array of mailboxes + mailboxes = Array(mailboxes) + + # We need at least one of these params + if options[:start_param].nil? && options[:available_from].nil? && options[:created_from].nil? + raise "either start_param, available_from or created_from is required" + end + + # If there is no params to get bookings for, set those based on availability params + if options[:available_from].present? && options[:start_param].nil? + options[:start_param] = options[:available_from] + options[:end_param] = options[:available_to] + end + + # If we are using created_from then we cannot use the calendarView and must use events + endpoint = ( options[:created_from].nil? ? "calendarView" : "events" ) + + # Get all of the endpoints for our bulk request + all_endpoints = mailboxes.map do |email| + "/users/#{email}/#{endpoint}" + end + + # This is currently the max amount of queries per bulk request + slice_size = 20 + responses = [] + all_endpoints.each_slice(slice_size).with_index do |endpoints, ind| + query = { + '$top': 10000 + } + # Add the relevant query params + query[:startDateTime] = graph_date(options[:start_param]) if options[:start_param] + query[:endDateTime] = graph_date(options[:end_param]) if options[:end_param] + query[:'$filter'] = "createdDateTime gt #{created_from}" if options[:created_from] + query[:'$expand'] = "extensions($filter=id eq '#{options[:extension_name]}')" if options[:extension_name] + + # Make the request, check the repsonse then parse it + bulk_response = graph_request(request_method: 'get', endpoints: endpoints, query: query, bulk: true) + check_response(bulk_response) + parsed_response = JSON.parse(bulk_response.body)['responses'] + + # Ensure that we reaggregate the bulk requests correctly + parsed_response.each do |res| + local_id = res['id'].to_i + global_id = local_id + (slice_size * ind.to_i) + res['id'] = global_id + responses.push(res) + end + end + + # Arrange our bookings by room + bookings_by_room = {} + responses.each_with_index do |res, i| + bookings = res['body']['value'] + is_available = true + # Go through each booking and extract more info from it + bookings.each_with_index do |booking, i| + # # Check if the bookings fall inside the availability window + # booking = check_availability(booking, options[:available_from], options[:available_to], mailboxes[res['id']]) + # # Alias a bunch of fields we use to follow our naming convention + # booking = alias_fields(booking) + # # Set the attendees as internal or external and alias their fields too + # bookings[i] = set_attendees(booking) + # if bookings[i]['free'] == false && !options[:ignore_bookings].include?(bookings[i]['icaluid']) + # is_available = false + # end + + + bookings[i] = Microsoft::Officenew::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]) + end + bookings_by_room[mailboxes[res['id'].to_i]] = {available: is_available, bookings: bookings} + end + bookings_by_room + end + + + protected + + ## + # Passes back either a stored bearer token for Graph API that has yet to expire or + # grabs a new token and stores it along with the expiry date. + def graph_token + # Check if we have a token in couchbase + token = User.bucket.get("office-token", quiet: true) + + # If we don't have a token + if token.nil? || token[:expiry] <= Time.now.to_i + # Get a new token with the passed in scope + new_token = @graph_client.client_credentials.get_token({ + :scope => @app_scope + }) + # Save both the token and the expiry details + new_token_model = { + token: new_token.token, + expiry: Time.now.to_i + new_token.expires_in, + } + User.bucket.set("office-token", new_token_model) + return new_token.token + else + # Otherwise, use the existing token + token[:token] + end + end + + ## + # The helper method that abstracts calls to graph API. This method allows for both single requests and + # bulk requests using the $batch endpoint. + def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, bulk: false) + if bulk + uv_request_method = :post + graph_path = "#{@graph_domain}/v1.0/$batch" + query_string = "?#{query.map { |k,v| "#{k}=#{v}" }.join('&')}" + data = { + requests: endpoints.each_with_index.map { |endpoint, i| { id: i, method: request_method.upcase, url: "#{endpoint}#{query_string}" } } + } + query = {} + else + uv_request_method = request_method.to_sym + graph_path = "#{@graph_domain}#{endpoints[0]}" + end + + headers['Authorization'] = "Bearer #{graph_token}" + headers['Content-Type'] = ENV['GRAPH_CONTENT_TYPE'] || "application/json" + headers['Prefer'] = ENV['GRAPH_PREFER'] || 'outlook.timezone="Australia/Sydney"' + + log_graph_request(request_method, data, query, headers, graph_path, endpoints) + + graph_api = UV::HttpEndpoint.new(@graph_domain, { inactivity_timeout: 25000, keepalive: false }) + response = graph_api.__send__(uv_request_method, path: graph_path, headers: headers, body: data.to_json, query: query) + + response.value + end + + def graph_date(date) + Time.at(date).utc.iso8601.split("+")[0] + end + + def log_graph_request(request_method, data, query, headers, graph_path, endpoints=nil) + STDERR.puts "--------------NEW GRAPH REQUEST------------" + STDERR.puts "#{request_method} to #{graph_path}" + STDERR.puts "Data:" + STDERR.puts data if data + STDERR.puts "Query:" + STDERR.puts query if query + STDERR.puts "Headers:" + STDERR.puts headers if headers + STDERR.puts "Endpoints:" + STDERR.puts endpoints if endpoints + STDERR.puts '--------------------------------------------' + STDERR.flush + end + + def check_response(response) + puts "CHECKING RESPONSE" + puts response.body if response.body + case response.status + when 200, 201, 204 + return + when 400 + if response['error']['code'] == 'ErrorInvalidIdMalformed' + raise Microsoft::Error::ErrorInvalidIdMalformed.new(response.body) + else + raise Microsoft::Error::BadRequest.new(response.body) + end + when 401 + raise Microsoft::Error::InvalidAuthenticationToken.new(response.body) + when 403 + raise Microsoft::Error::ErrorAccessDenied.new(response.body) + when 404 + raise Microsoft::Error::ResourceNotFound.new(response.body) + end + end + +end \ No newline at end of file diff --git a/lib/microsoft/office/contact.rb b/lib/microsoft/office/contact.rb new file mode 100644 index 00000000..50ab6d2f --- /dev/null +++ b/lib/microsoft/office/contact.rb @@ -0,0 +1,26 @@ +class Microsoft::Officenew::Contact < Microsoft::Officenew::Model + + ALIAS_FIELDS = { + 'id' => 'id', + 'title' => 'title', + 'mobilePhone' => 'phone', + 'displayName' => 'name', + 'personalNotes' => 'notes', + 'emailAddresses' => { 0 => { 'address' => 'email' } } + } + + NEW_FIELDS = {} + + hash_to_reduced_array(ALIAS_FIELDS).each do |field| + define_method field do + @contact[field] + end + end + + def initialize(client:, contact:) + @client = client + @contact = create_aliases(contact, ALIAS_FIELDS, NEW_FIELDS, self) + end + + attr_accessor :contact +end diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb new file mode 100644 index 00000000..9ad6fe33 --- /dev/null +++ b/lib/microsoft/office/event.rb @@ -0,0 +1,98 @@ +class Microsoft::Officenew::Event < Microsoft::Officenew::Model + + # These are the fields which we just want to alias or pull out of the O365 model without any processing + ALIAS_FIELDS = { + 'start' => 'start', + 'end' => 'end', + 'subject' => 'subject', + 'attendees' => 'old_attendees', + 'iCalUId' => 'icaluid', + 'showAs' => 'show_as', + 'createdDateTime' => 'created' + } + + NEW_FIELDS = [ + { new_key: 'start_epoch', method: 'datestring_to_epoch', model_params:['start'] }, + { new_key: 'end_epoch', method: 'datestring_to_epoch', model_params:['end'] }, + { new_key: 'room_ids', method: 'set_room_id', model_params:['attendees'] }, + { new_key: 'attendees', method: 'format_attendees', model_params:['attendees', 'organizer'] }, + { new_key: 'is_free', method: 'check_availability', model_params:['start', 'end'], passed_params: ['available_to', 'available_from'] } + ] + + (hash_to_reduced_array(ALIAS_FIELDS) + NEW_FIELDS.map{|v| v[:new_key]}).each do |field| + define_method field do + @event[field] + end + end + + def initialize(client:, event:, available_to:nil, available_from:nil) + @client = client + @available_to = available_to + @available_from = available_from + @event = create_aliases(event, ALIAS_FIELDS, NEW_FIELDS, self) + end + + def get_contacts + @client.get_contacts(mailbox: @event['mail']) + end + + attr_accessor :event, :available_to, :available_from + + protected + + def datestring_to_epoch(date_object) + ActiveSupport::TimeZone.new(date_object['timeZone']).parse(date_object['dateTime']).to_i + end + + def set_room_id(attendees) + room_ids = [] + attendees.each do |attendee| + attendee_email = attendee['emailAddress']['address'] + # If this attendee is a room resource + if attendee['type'] == 'resource' + room_ids.push(attendee_email) + end + end + room_ids + end + + def format_attendees(attendees, organizer) + internal_domains = [::Mail::Address.new(organizer['emailAddress']['address']).domain] + new_attendees = [] + attendees.each do |attendee| + attendee_email = attendee['emailAddress']['address'] + + # Compare the domain of this attendee's email against the internal domain + mail_object = ::Mail::Address.new(attendee_email) + mail_domain = mail_object.domain + is_visitor = !(internal_domains.map{|d| + d.downcase + }.include?(mail_domain.downcase)) + + # Alias the attendee fields, mark whether this is a visitor and pull organisation details from the email + attendee_object = { + email: attendee_email, + name: attendee['emailAddress']['name'], + visitor: is_visitor, + organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize + } + new_attendees.push(attendee_object) + end + new_attendees + end + + def check_availability(start_param, end_param, available_from, available_to) + booking_start = ActiveSupport::TimeZone.new(start_param['timeZone']).parse(start_param['dateTime']).to_i + booking_end = ActiveSupport::TimeZone.new(end_param['timeZone']).parse(end_param['dateTime']).to_i + + # Check if this means the room is unavailable + booking_overlaps_start = booking_start < available_from && booking_end > available_from + booking_in_between = booking_start >= available_from && booking_end <= available_to + booking_overlaps_end = booking_start < available_to && booking_end > available_to + if booking_overlaps_start || booking_in_between || booking_overlaps_end + return false + else + return true + end + end +end \ No newline at end of file diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb new file mode 100644 index 00000000..88195961 --- /dev/null +++ b/lib/microsoft/office/events.rb @@ -0,0 +1,304 @@ +module Microsoft::Officenew::Events + + + ## + # For every mailbox (email) passed in, this method will grab all the bookings and, if + # requested, return the availability of the mailboxes for some time range. + # + # @param mailboxes [Array] An array of mailbox emails to pull bookings from. These are generally rooms but could be users. + # @option options [Integer] :created_from Get all the bookings created after this seconds epoch + # @option options [Integer] :start_param Get all the bookings that occurr between this seconds epoch and end_param + # @option options [Integer] :end_param Get all the bookings that occurr between this seconds epoch and start_param + # @option options [Integer] :available_from If bookings exist between this seconds epoch and available_to then the room is market as not available + # @option options [Integer] :available_to If bookings exist between this seconds epoch and available_from then the room is market as not available + # @option options [Array] :ignore_bookings An array of icaluids for bookings which are ignored if they fall within the available_from and to time range + # @option options [String] :extension_name The name of the extension list to retreive in O365. This probably doesn't need to change + # @return [Hash] A hash of room emails to availability and bookings fields, eg: + # @example + # An example response: + # { + # 'room1@example.com' => { + # available: false, + # bookings: [ + # { + # subject: 'Blah', + # start_epoch: 1552974994 + # ... + # }, { + # subject: 'Foo', + # start_epoch: 1552816751 + # ... + # } + # ] + # }, + # 'room2@example.com' => { + # available: false, + # bookings: [{ + # subject: 'Blah', + # start_epoch: 1552974994 + # }] + # ... + # } + # } + def get_bookings(mailboxes:, options:{}) + default_options = { + created_from: nil, + start_param: nil, + end_param: nil, + available_from: nil, + available_to: nil, + ignore_bookings: [], + extension_name: "Com.Acaprojects.Extensions" + } + # Merge in our default options with those passed in + options = options.reverse_merge(default_options) + + # We should always have an array of mailboxes + mailboxes = Array(mailboxes) + + # We need at least one of these params + if options[:start_param].nil? && options[:available_from].nil? && options[:created_from].nil? + raise "either start_param, available_from or created_from is required" + end + + # If there is no params to get bookings for, set those based on availability params + if options[:available_from].present? && options[:start_param].nil? + options[:start_param] = options[:available_from] + options[:end_param] = options[:available_to] + end + + # If we are using created_from then we cannot use the calendarView and must use events + endpoint = ( options[:created_from].nil? ? "calendarView" : "events" ) + + # Get all of the endpoints for our bulk request + all_endpoints = mailboxes.map do |email| + "/users/#{email}/#{endpoint}" + end + + # This is currently the max amount of queries per bulk request + slice_size = 20 + responses = [] + all_endpoints.each_slice(slice_size).with_index do |endpoints, ind| + query = { + '$top': 10000 + } + # Add the relevant query params + query[:startDateTime] = graph_date(options[:start_param]) if options[:start_param] + query[:endDateTime] = graph_date(options[:end_param]) if options[:end_param] + query[:'$filter'] = "createdDateTime gt #{created_from}" if options[:created_from] + query[:'$expand'] = "extensions($filter=id eq '#{options[:extension_name]}')" if options[:extension_name] + + # Make the request, check the repsonse then parse it + bulk_response = graph_request(request_method: 'get', endpoints: endpoints, query: query, bulk: true) + check_response(bulk_response) + parsed_response = JSON.parse(bulk_response.body)['responses'] + + # Ensure that we reaggregate the bulk requests correctly + parsed_response.each do |res| + local_id = res['id'].to_i + global_id = local_id + (slice_size * ind.to_i) + res['id'] = global_id + responses.push(res) + end + end + + # Arrange our bookings by room + bookings_by_room = {} + responses.each_with_index do |res, i| + bookings = res['body']['value'] + is_available = true + # Go through each booking and extract more info from it + bookings.each_with_index do |booking, i| + # # Check if the bookings fall inside the availability window + # booking = check_availability(booking, options[:available_from], options[:available_to], mailboxes[res['id']]) + # # Alias a bunch of fields we use to follow our naming convention + # booking = alias_fields(booking) + # # Set the attendees as internal or external and alias their fields too + # bookings[i] = set_attendees(booking) + # if bookings[i]['free'] == false && !options[:ignore_bookings].include?(bookings[i]['icaluid']) + # is_available = false + # end + + + bookings[i] = Microsoft::Officenew::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]) + end + bookings_by_room[mailboxes[res['id'].to_i]] = {available: is_available, bookings: bookings} + end + bookings_by_room + end + + + def create_booking(mailbox:, start_param:, end_param:, options: {}) + default_options = { + room_emails: [], + subject: "Meeting", + description: nil, + organizer_name: nil, + organizer_email: mailbox, + attendees: [], + recurrence_type: nil, + recurrence_end: nil, + is_private: false, + timezone: 'UTC', + extensions: {}, + location: nil + } + # Merge in our default options with those passed in + options = options.reverse_merge(default_options) + + # Turn our array of room emails into an array of rooms (ControlSystems) + rooms = room_emails.map { |r_id| Orchestrator::ControlSystem.find_by_email(r_id) } + + # Get the timezones out of the room's zone if it has any + timezone = get_timezone(rooms[0]) if rooms.present? + + # Create the JSON body for our event + event_json = create_event_json( + subject: options[:subject], + body: options[:description], + rooms: rooms, + start_param: start_param, + end_param: end_param, + timezone: (timezone || "UTC"), + location: options[:location], + attendees: options[:attendees], + organizer_name: options[:organizer_name], + organizer_email: options[:organizer_email], + recurrence_type: options[:recurrence_type], + recurrence_end: options[:recurrence_end], + is_private: options[:is_private] + ) + + # Make the request and check the response + request = graph_request(request_method: 'post', endpoint: "/v1.0/users/#{mailbox}/events", data: event_json) + check_response(request) + + end + + def update_booking(booking_id:, mailbox:, options: {}) + default_options = { + start_param: nil, + end_param: nil, + room_emails: [], + subject: "Meeting", + description: nil, + organizer_name: nil, + organizer_email: mailbox, + attendees: [], + recurrence_type: nil, + recurrence_end: nil, + is_private: false, + timezone: 'UTC', + extensions: {}, + location: nil + } + # Merge in our default options with those passed in + options = options.reverse_merge(default_options) + + # Turn our array of room emails into an array of rooms (ControlSystems) + rooms = room_emails.map { |r_id| Orchestrator::ControlSystem.find_by_email(r_id) } + + # Get the timezones out of the room's zone if it has any + timezone = get_timezone(rooms[0]) if rooms.present? + + # Create the JSON body for our event + event_json = create_event_json( + subject: options[:subject], + body: options[:description], + rooms: rooms, + start_param: options[:start_param], + end_param: options[:end_param], + timezone: (timezone || "UTC"), + location: options[:location], + attendees: options[:attendees], + organizer_name: options[:organizer_name], + organizer_email: options[:organizer_email], + recurrence_type: options[:recurrence_type], + recurrence_end: options[:recurrence_end], + is_private: options[:is_private] + ) + + # Make the request and check the response + request = graph_request(request_method: 'patch', endpoint: "/v1.0/users/#{mailbox}/events/#{booking_id}", data: event_json) + check_response(request) + request + end + + protected + + def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer_email: nil, recurrence_type: nil, recurrence_end: nil, is_private: false) + # Put the attendees into the MS Graph expeceted format + attendees.map! do |a| + attendee_type = ( a[:optional] ? "optional" : "required" ) + { emailAddress: { address: a[:email], name: a[:name] }, type: attendee_type } + end + + # Add each room to the attendees array + rooms.each do |room| + attendees.push({ type: "resource", emailAddress: { address: room.email, name: room.name } }) + end + + # If we have rooms then build the location from that, otherwise use the passed in value + event_location = rooms.map{ |room| room.name }.join(" and ") + event_location = ( event_location.present? ? event_location : location ) + + event_json = {} + event_json[:subject] = subject + event_json[:attendees] = attendees + event_json[:sensitivity] = ( is_private ? "private" : "normal" ) + + event_json[:body] = { + contentType: "HTML", + content: (body || "") + } + + event_json[:start] = { + dateTime: start_param, + timeZone: timezone + } if start_param + + event_json[:end] = { + dateTime: end_param, + timeZone: timezone + } if end_param + + event_json[:location] = { + displayName: location + } if location + + event_json[:organizer] = { + emailAddress: { + address: organizer_email, + name: organizer_name + } + } if organizer + + event[:recurrence] = { + pattern: { + type: recurrence_type, + interval: 1, + daysOfWeek: [epoch_in_timezone(start_param, timezone).strftime("%A")] + }, + range: { + type: 'endDate', + startDate: epoch_in_timezone(start_param, timezone).strftime("%F"), + endDate: epoch_in_timezone(recurrence_end, timezone).strftime("%F") + } + } if recurrence_type + + event_json.reject!{|k,v| v.nil?} + end + + + def get_timezone(room) + timezone = nil + room.zones.each do |zone_id| + zone = Orchestrator::Zone.find(zone_id) + if zone.settings.key?("timezone") + timezone = zone.settings['timezone'] + end + end + timezone + end + +end diff --git a/lib/microsoft/office/model.rb b/lib/microsoft/office/model.rb new file mode 100644 index 00000000..adec7608 --- /dev/null +++ b/lib/microsoft/office/model.rb @@ -0,0 +1,43 @@ +class Microsoft::Officenew::Model + + def create_aliases(object, alias_fields, new_fields, model) + aliased_contact = object.each_with_object({}) do |(k,v), h| + if alias_fields.keys.include?(k) + new_field = alias_reduction(k, alias_fields[k], v) + h[new_field[:key]] = new_field[:value] + end + end + new_fields.each do |field| + aliased_contact[field[:new_key]] = model.__send__( + field[:method], + *((field[:model_params] || []).map{|p| object[p.to_s]}), + *((field[:passed_params] || []).map{|p| self.__send__(p) + })) + end + aliased_contact + end + + # If the alias_field value is a string, then use the string, otherwise dig deeper using the hash + def alias_reduction(alias_key, alias_value, object_value) + # If it's a string, return it and use this as the new key + return {key: alias_value, value: object_value} if alias_value.class == String + new_alias_key = alias_value.keys[0] + new_alias_key + alias_reduction(new_alias_key, alias_value[new_alias_key], object_value[new_alias_key]) + end + + # This method takes a hash which has either strings as values or hashes that can be reduced to strings + def self.hash_to_reduced_array(hash_object) + reduced = [] + hash_object.each do |k,v| + reduced.push(reduce_hash(v)) + end + reduced + end + + def self.reduce_hash(value) + return value if value.class == String + reduce_hash(value[value.keys[0]]) + end + +end diff --git a/lib/microsoft/office/user.rb b/lib/microsoft/office/user.rb new file mode 100644 index 00000000..9058e55f --- /dev/null +++ b/lib/microsoft/office/user.rb @@ -0,0 +1,28 @@ +class Microsoft::Officenew::User < Microsoft::Officenew::Model + + ALIAS_FIELDS = { + 'id' => 'id', + 'mobilePhone' => 'phone', + 'displayName' => 'name', + 'mail' => 'email' + } + + NEW_FIELDS = {} + + hash_to_reduced_array(ALIAS_FIELDS).each do |field| + define_method field do + @user[field] + end + end + + def initialize(client:, user:) + @client = client + @user = create_aliases(user, ALIAS_FIELDS, NEW_FIELDS, self) + end + + def get_contacts + @client.get_contacts(mailbox: @user['mail']) + end + + attr_accessor :user +end \ No newline at end of file From 0f783cb22aa6ae81379c290497116b380ca585af Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 12:20:14 +1100 Subject: [PATCH 1208/1752] Move request methods to modules --- lib/microsoft/office/client.rb | 193 ++----------------------------- lib/microsoft/office/contacts.rb | 16 +++ lib/microsoft/office/event.rb | 4 - lib/microsoft/office/events.rb | 71 ++++++++++-- lib/microsoft/office/users.rb | 42 +++++++ 5 files changed, 129 insertions(+), 197 deletions(-) create mode 100644 lib/microsoft/office/contacts.rb create mode 100644 lib/microsoft/office/users.rb diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index 978177b4..82eb4942 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -2,8 +2,11 @@ require 'microsoft/officenew' require 'microsoft/office/model' require 'microsoft/office/user' +require 'microsoft/office/users' require 'microsoft/office/contact' +require 'microsoft/office/contacts' require 'microsoft/office/event' +require 'microsoft/office/events' module Microsoft class Error < StandardError class ResourceNotFound < Error; end @@ -21,6 +24,9 @@ class Microsoft::Officenew; end # primarily only used for: # - class Microsoft::Officenew::Client + include Microsoft::Officenew::Events + include Microsoft::Officenew::Users + include Microsoft::Officenew::Contacts ## # Initialize the client for making requests to the Office365 API. @@ -53,189 +59,6 @@ def initialize( ) end - ## - # Retrieve a list of users stored in Office365 - # - # @param q [String] The query param which filters all users without a name or email matching this string - # @param limit [String] The maximum number of users to return - def get_users(q: nil, limit: nil) - - # If we have a query and the query has at least one space - if q && q.include?(" ") - # Split it into word tokens - queries = q.split(" ") - filter_params = [] - - # For each word, create a filtering statement - queries.each do |q| - filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))") - end - - # Join these filtering statements using 'or' and add accountEnabled filter - filter_param = "(accountEnabled eq true) and #{filter_params.join(" and ")}" - else - # Or just add the space-less query to be checked for each field - filter_param = "(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))" if q - end - - # If we have no query then still only grab enabled accounts - filter_param = "accountEnabled eq true" if q.nil? - - # Put our params together and make the request - query_params = { - '$filter': filter_param, - '$top': limit - }.compact - endpoint = "/v1.0/users" - request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) - check_response(request) - - # Return the parsed user data - JSON.parse(request.body)['value'].map {|u| Microsoft::Officenew::User.new(client: self, user: u)} - end - - ## - # Retrieve a list of contacts for some passed in mailbox - # - # @param mailbox [String] The mailbox email of which we want to grab the contacts for - # @param q [String] The query param which filters all contacts without a name or email matching this string - # @param limit [String] The maximum number of contacts to return - def get_contacts(mailbox:, q:nil, limit:nil) - query_params = { '$top': (limit || 1000) } - query_params['$filter'] = "startswith(displayName, '#{q}') or startswith(givenName, '#{q}') or startswith(surname, '#{q}') or emailAddresses/any(a:a/address eq '#{q}')" if q - endpoint = "/v1.0/users/#{mailbox}/contacts" - request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) - check_response(request) - JSON.parse(request.body)['value'].map {|c| Microsoft::Officenew::Contact.new(client: self, contact: c)} - end - - - ## - # For every mailbox (email) passed in, this method will grab all the bookings and, if - # requested, return the availability of the mailboxes for some time range. - # - # @param mailboxes [Array] An array of mailbox emails to pull bookings from. These are generally rooms but could be users. - # @option options [Integer] :created_from Get all the bookings created after this seconds epoch - # @option options [Integer] :start_param Get all the bookings that occurr between this seconds epoch and end_param - # @option options [Integer] :end_param Get all the bookings that occurr between this seconds epoch and start_param - # @option options [Integer] :available_from If bookings exist between this seconds epoch and available_to then the room is market as not available - # @option options [Integer] :available_to If bookings exist between this seconds epoch and available_from then the room is market as not available - # @option options [Array] :ignore_bookings An array of icaluids for bookings which are ignored if they fall within the available_from and to time range - # @option options [String] :extension_name The name of the extension list to retreive in O365. This probably doesn't need to change - # @return [Hash] A hash of room emails to availability and bookings fields, eg: - # @example - # An example response: - # { - # 'room1@example.com' => { - # available: false, - # bookings: [ - # { - # subject: 'Blah', - # start_epoch: 1552974994 - # ... - # }, { - # subject: 'Foo', - # start_epoch: 1552816751 - # ... - # } - # ] - # }, - # 'room2@example.com' => { - # available: false, - # bookings: [{ - # subject: 'Blah', - # start_epoch: 1552974994 - # }] - # ... - # } - # } - def get_bookings(mailboxes:, options:{}) - default_options = { - created_from: nil, - start_param: nil, - end_param: nil, - available_from: nil, - available_to: nil, - ignore_bookings: [], - extension_name: "Com.Acaprojects.Extensions" - } - # Merge in our default options with those passed in - options = options.reverse_merge(default_options) - - # We should always have an array of mailboxes - mailboxes = Array(mailboxes) - - # We need at least one of these params - if options[:start_param].nil? && options[:available_from].nil? && options[:created_from].nil? - raise "either start_param, available_from or created_from is required" - end - - # If there is no params to get bookings for, set those based on availability params - if options[:available_from].present? && options[:start_param].nil? - options[:start_param] = options[:available_from] - options[:end_param] = options[:available_to] - end - - # If we are using created_from then we cannot use the calendarView and must use events - endpoint = ( options[:created_from].nil? ? "calendarView" : "events" ) - - # Get all of the endpoints for our bulk request - all_endpoints = mailboxes.map do |email| - "/users/#{email}/#{endpoint}" - end - - # This is currently the max amount of queries per bulk request - slice_size = 20 - responses = [] - all_endpoints.each_slice(slice_size).with_index do |endpoints, ind| - query = { - '$top': 10000 - } - # Add the relevant query params - query[:startDateTime] = graph_date(options[:start_param]) if options[:start_param] - query[:endDateTime] = graph_date(options[:end_param]) if options[:end_param] - query[:'$filter'] = "createdDateTime gt #{created_from}" if options[:created_from] - query[:'$expand'] = "extensions($filter=id eq '#{options[:extension_name]}')" if options[:extension_name] - - # Make the request, check the repsonse then parse it - bulk_response = graph_request(request_method: 'get', endpoints: endpoints, query: query, bulk: true) - check_response(bulk_response) - parsed_response = JSON.parse(bulk_response.body)['responses'] - - # Ensure that we reaggregate the bulk requests correctly - parsed_response.each do |res| - local_id = res['id'].to_i - global_id = local_id + (slice_size * ind.to_i) - res['id'] = global_id - responses.push(res) - end - end - - # Arrange our bookings by room - bookings_by_room = {} - responses.each_with_index do |res, i| - bookings = res['body']['value'] - is_available = true - # Go through each booking and extract more info from it - bookings.each_with_index do |booking, i| - # # Check if the bookings fall inside the availability window - # booking = check_availability(booking, options[:available_from], options[:available_to], mailboxes[res['id']]) - # # Alias a bunch of fields we use to follow our naming convention - # booking = alias_fields(booking) - # # Set the attendees as internal or external and alias their fields too - # bookings[i] = set_attendees(booking) - # if bookings[i]['free'] == false && !options[:ignore_bookings].include?(bookings[i]['icaluid']) - # is_available = false - # end - - - bookings[i] = Microsoft::Officenew::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]) - end - bookings_by_room[mailboxes[res['id'].to_i]] = {available: is_available, bookings: bookings} - end - bookings_by_room - end - protected @@ -269,6 +92,10 @@ def graph_token # The helper method that abstracts calls to graph API. This method allows for both single requests and # bulk requests using the $batch endpoint. def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, bulk: false) + puts "HIT GRAPH REQUEST" + puts "DATA IS" + puts data + puts '------' if bulk uv_request_method = :post graph_path = "#{@graph_domain}/v1.0/$batch" diff --git a/lib/microsoft/office/contacts.rb b/lib/microsoft/office/contacts.rb new file mode 100644 index 00000000..603e4aaa --- /dev/null +++ b/lib/microsoft/office/contacts.rb @@ -0,0 +1,16 @@ +module Microsoft::Officenew::Contacts + ## + # Retrieve a list of contacts for some passed in mailbox + # + # @param mailbox [String] The mailbox email of which we want to grab the contacts for + # @param q [String] The query param which filters all contacts without a name or email matching this string + # @param limit [String] The maximum number of contacts to return + def get_contacts(mailbox:, q:nil, limit:nil) + query_params = { '$top': (limit || 1000) } + query_params['$filter'] = "startswith(displayName, '#{q}') or startswith(givenName, '#{q}') or startswith(surname, '#{q}') or emailAddresses/any(a:a/address eq '#{q}')" if q + endpoint = "/v1.0/users/#{mailbox}/contacts" + request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) + check_response(request) + JSON.parse(request.body)['value'].map {|c| Microsoft::Officenew::Contact.new(client: self, contact: c)} + end +end \ No newline at end of file diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index 9ad6fe33..0b6887c8 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -32,10 +32,6 @@ def initialize(client:, event:, available_to:nil, available_from:nil) @event = create_aliases(event, ALIAS_FIELDS, NEW_FIELDS, self) end - def get_contacts - @client.get_contacts(mailbox: @event['mail']) - end - attr_accessor :event, :available_to, :available_from protected diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index 88195961..550c15fe 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -1,6 +1,4 @@ module Microsoft::Officenew::Events - - ## # For every mailbox (email) passed in, this method will grab all the bookings and, if # requested, return the availability of the mailboxes for some time range. @@ -128,6 +126,25 @@ def get_bookings(mailboxes:, options:{}) end + ## + # Create an Office365 event in the mailbox passed in. This may have rooms and other + # attendees associated with it and thus create events in other mailboxes also. + # + # @param mailbox [String] The mailbox email in which the event is to be created. This could be a user or a room though is generally a user + # @param start_param [Integer] A seconds epoch which denotes the start of the booking + # @param end_param [Integer] A seconds epoch which denotes the end of the booking + # @option options [Array] :room_emails An array of room resource emails to be added to the booking + # @option options [String] :subject A subject for the booking + # @option options [String] :description A description to be added to the body of the event + # @option options [String] :organizer_name The name of the organizer + # @option options [String] :organizer_email The email of the organizer + # @option options [Array] :attendees An array of attendees to add in the form { name: , email: } + # @option options [String] :recurrence_type The type of recurrence if used. Can be 'daily', 'weekly' or 'monthly' + # @option options [Integer] :recurrence_end A seconds epoch denoting the final day of recurrence + # @option options [Boolean] :is_private Whether to mark the booking as private or just normal + # @option options [String] :timezone The timezone of the booking. This will be overridden by a timezone in the room's settings + # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking + # @option options [String] :location The location field to set. This will not be used if a room is passed in def create_booking(mailbox:, start_param:, end_param:, options: {}) default_options = { room_emails: [], @@ -147,7 +164,7 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) options = options.reverse_merge(default_options) # Turn our array of room emails into an array of rooms (ControlSystems) - rooms = room_emails.map { |r_id| Orchestrator::ControlSystem.find_by_email(r_id) } + rooms = options[:room_emails].map { |r_id| Orchestrator::ControlSystem.find_by_email(r_id) } # Get the timezones out of the room's zone if it has any timezone = get_timezone(rooms[0]) if rooms.present? @@ -170,11 +187,34 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) ) # Make the request and check the response - request = graph_request(request_method: 'post', endpoint: "/v1.0/users/#{mailbox}/events", data: event_json) + puts "ABOUT TO MAKE GRAPH REQUEST TO CREATE BOOKING" + puts "DATA IS " + puts event_json + puts '-----' + request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}/events"], data: event_json) check_response(request) end + ## + # Update an Office365 event with the relevant booking ID and in the mailbox passed in. + # + # @param booking_id [String] The ID of the booking which is to be updated + # @param mailbox [String] The mailbox email in which the event is to be created. This could be a user or a room though is generally a user + # @option options [Integer] :start_param A seconds epoch which denotes the start of the booking + # @option options [Integer] :end_param A seconds epoch which denotes the end of the booking + # @option options [Array] :room_emails An array of room resource emails to be added to the booking + # @option options [String] :subject A subject for the booking + # @option options [String] :description A description to be added to the body of the event + # @option options [String] :organizer_name The name of the organizer + # @option options [String] :organizer_email The email of the organizer + # @option options [Array] :attendees An array of attendees to add in the form { name: , email: } + # @option options [String] :recurrence_type The type of recurrence if used. Can be 'daily', 'weekly' or 'monthly' + # @option options [Integer] :recurrence_end A seconds epoch denoting the final day of recurrence + # @option options [Boolean] :is_private Whether to mark the booking as private or just normal + # @option options [String] :timezone The timezone of the booking. This will be overridden by a timezone in the room's settings + # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking + # @option options [String] :location The location field to set. This will not be used if a room is passed in def update_booking(booking_id:, mailbox:, options: {}) default_options = { start_param: nil, @@ -219,14 +259,14 @@ def update_booking(booking_id:, mailbox:, options: {}) ) # Make the request and check the response - request = graph_request(request_method: 'patch', endpoint: "/v1.0/users/#{mailbox}/events/#{booking_id}", data: event_json) + request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}/events/#{booking_id}"], data: event_json) check_response(request) request end protected - def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer_email: nil, recurrence_type: nil, recurrence_end: nil, is_private: false) + def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer_email: nil, recurrence_type: nil, recurrence_end: nil, extensions: {}, is_private: false) # Put the attendees into the MS Graph expeceted format attendees.map! do |a| attendee_type = ( a[:optional] ? "optional" : "required" ) @@ -253,12 +293,12 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } event_json[:start] = { - dateTime: start_param, + dateTime: ActiveSupport::TimeZone.new(timezone).at(start_param), timeZone: timezone } if start_param event_json[:end] = { - dateTime: end_param, + dateTime: ActiveSupport::TimeZone.new(timezone).at(end_param), timeZone: timezone } if end_param @@ -269,9 +309,19 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, event_json[:organizer] = { emailAddress: { address: organizer_email, - name: organizer_name + name: (organizer_name || organizer_email) } - } if organizer + } if organizer_email + + + ext = { + "@odata.type": "microsoft.graph.openTypeExtension", + "extensionName": "Com.Acaprojects.Extensions" + } + extensions.each do |ext_key, ext_value| + ext[ext_key] = ext_value + end + event[:extensions] = exts event[:recurrence] = { pattern: { @@ -287,6 +337,7 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } if recurrence_type event_json.reject!{|k,v| v.nil?} + event_json end diff --git a/lib/microsoft/office/users.rb b/lib/microsoft/office/users.rb new file mode 100644 index 00000000..e36c8d79 --- /dev/null +++ b/lib/microsoft/office/users.rb @@ -0,0 +1,42 @@ +module Microsoft::Officenew::Users + ## + # Retrieve a list of users stored in Office365 + # + # @param q [String] The query param which filters all users without a name or email matching this string + # @param limit [String] The maximum number of users to return + def get_users(q: nil, limit: nil) + + # If we have a query and the query has at least one space + if q && q.include?(" ") + # Split it into word tokens + queries = q.split(" ") + filter_params = [] + + # For each word, create a filtering statement + queries.each do |q| + filter_params.push("(startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))") + end + + # Join these filtering statements using 'or' and add accountEnabled filter + filter_param = "(accountEnabled eq true) and #{filter_params.join(" and ")}" + else + # Or just add the space-less query to be checked for each field + filter_param = "(accountEnabled eq true) and (startswith(displayName,'#{q}') or startswith(givenName,'#{q}') or startswith(surname,'#{q}') or startswith(mail,'#{q}'))" if q + end + + # If we have no query then still only grab enabled accounts + filter_param = "accountEnabled eq true" if q.nil? + + # Put our params together and make the request + query_params = { + '$filter': filter_param, + '$top': limit + }.compact + endpoint = "/v1.0/users" + request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) + check_response(request) + + # Return the parsed user data + JSON.parse(request.body)['value'].map {|u| Microsoft::Officenew::User.new(client: self, user: u)} + end +end \ No newline at end of file From c857cd08a41121735f7851801e97465947373f31 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 15:42:48 +1100 Subject: [PATCH 1209/1752] Change some params for booking retreival to fit convention --- lib/microsoft/office/events.rb | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index 550c15fe..9649f6c2 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -40,9 +40,9 @@ module Microsoft::Officenew::Events # } def get_bookings(mailboxes:, options:{}) default_options = { - created_from: nil, - start_param: nil, - end_param: nil, + created_from: nil, + bookings_from: nil, + bookings_to: nil, available_from: nil, available_to: nil, ignore_bookings: [], @@ -55,14 +55,17 @@ def get_bookings(mailboxes:, options:{}) mailboxes = Array(mailboxes) # We need at least one of these params - if options[:start_param].nil? && options[:available_from].nil? && options[:created_from].nil? - raise "either start_param, available_from or created_from is required" + if options[:bookings_from].nil? && options[:available_from].nil? && options[:created_from].nil? + raise "either bookings_from, available_from or created_from is required" end - # If there is no params to get bookings for, set those based on availability params - if options[:available_from].present? && options[:start_param].nil? - options[:start_param] = options[:available_from] - options[:end_param] = options[:available_to] + # If there is no params to get bookings for, set those based on availability params and vice versa + if options[:available_from].present? && options[:bookings_from].nil? + options[:bookings_from] = options[:available_from] + options[:bookings_to] = options[:available_to] + elsif options[:bookings_from].present? && options[:available_from].nil? + options[:available_from] = options[:bookings_from] + options[:available_to] = options[:bookings_to] end # If we are using created_from then we cannot use the calendarView and must use events @@ -81,8 +84,8 @@ def get_bookings(mailboxes:, options:{}) '$top': 10000 } # Add the relevant query params - query[:startDateTime] = graph_date(options[:start_param]) if options[:start_param] - query[:endDateTime] = graph_date(options[:end_param]) if options[:end_param] + query[:startDateTime] = graph_date(options[:bookings_from]) if options[:bookings_from] + query[:endDateTime] = graph_date(options[:bookings_to]) if options[:bookings_to] query[:'$filter'] = "createdDateTime gt #{created_from}" if options[:created_from] query[:'$expand'] = "extensions($filter=id eq '#{options[:extension_name]}')" if options[:extension_name] From 0c48446793106ce1aa998691302441d6edb0f067 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 15:43:25 +1100 Subject: [PATCH 1210/1752] Initiate the module in one outer file --- lib/microsoft/officenew.rb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/microsoft/officenew.rb diff --git a/lib/microsoft/officenew.rb b/lib/microsoft/officenew.rb new file mode 100644 index 00000000..b96c292f --- /dev/null +++ b/lib/microsoft/officenew.rb @@ -0,0 +1 @@ +class Microsoft::Officenew; end \ No newline at end of file From 600ccba57127a5b1e55a5eaf46568d400879df57 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 16:23:48 +1100 Subject: [PATCH 1211/1752] Override to_json for o365 model --- lib/microsoft/office/user.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/microsoft/office/user.rb b/lib/microsoft/office/user.rb index 9058e55f..1d4c75c6 100644 --- a/lib/microsoft/office/user.rb +++ b/lib/microsoft/office/user.rb @@ -24,5 +24,10 @@ def get_contacts @client.get_contacts(mailbox: @user['mail']) end + def to_json(options) + super(:only => :user) + end + + attr_accessor :user end \ No newline at end of file From 8b6556f54ceaae99609496edf952baa90f45ea0d Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 16:28:49 +1100 Subject: [PATCH 1212/1752] Just return user property from class in o365 user lib methods --- lib/microsoft/office/users.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office/users.rb b/lib/microsoft/office/users.rb index e36c8d79..27d7c2cc 100644 --- a/lib/microsoft/office/users.rb +++ b/lib/microsoft/office/users.rb @@ -37,6 +37,6 @@ def get_users(q: nil, limit: nil) check_response(request) # Return the parsed user data - JSON.parse(request.body)['value'].map {|u| Microsoft::Officenew::User.new(client: self, user: u)} + JSON.parse(request.body)['value'].map {|u| Microsoft::Officenew::User.new(client: self, user: u).user} end end \ No newline at end of file From c4367ca71ee396666b00aab9bb4936b129e5cdab Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 28 Mar 2019 16:53:43 +1100 Subject: [PATCH 1213/1752] (cisco:broad_works) add support for tracking call ended events Can now track: No. Calls in Queue No. Calls taken Av. Speed of Answer Longest Wait time Calls abandoned Average talk time Agents currently on a call --- lib/cisco/broad_soft/broad_works.rb | 10 ++- modules/cisco/broad_works.rb | 132 +++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 5 deletions(-) diff --git a/lib/cisco/broad_soft/broad_works.rb b/lib/cisco/broad_soft/broad_works.rb index 5934c2dc..a9ca8719 100755 --- a/lib/cisco/broad_soft/broad_works.rb +++ b/lib/cisco/broad_soft/broad_works.rb @@ -42,6 +42,8 @@ def initialize(domain, username, password, application: "ACAEngine", proxy: nil, @heartbeat = true @channel_set_id = SecureRandom.uuid + + @buffer = String.new end attr_reader :expires @@ -92,7 +94,7 @@ def heartbeat if response.code == 404 # Seems multiple servers handle requests and not all are aware of channels! # This just desperately tries to hit the right server to keep the channel alive - sleep 1 + sleep 0.7 heartbeat if @heartbeat && @channel_open else @logger.warn "heartbeat failed\n#{response.inspect}" @@ -142,7 +144,13 @@ def acknowledge(event_id) end def parse_event(data) + @buffer << data + data = @buffer + + # Check if valid XML with our current buffer xml = Nokogiri::XML(data) + return unless xml.errors.empty? + @buffer = String.new xml.remove_namespaces! begin diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index c3b5914b..ad9b2dd7 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -30,7 +30,7 @@ def on_load @terminated = false # TODO:: Load todays stats from the database if they exist - + reset_stats on_update end @@ -43,7 +43,9 @@ def on_update @callcenters = setting(:callcenters) || {} # "sub_id" => "callcenter id" - @subscription_lookup = {} + @subscription_lookup ||= {} + # "call_id" => {user: user_id, center: "callcenter id", time: 435643} + @call_tracking ||= {} @poll_sched&.cancel @reset_sched&.cancel @@ -51,7 +53,9 @@ def on_update # Every 2 min @poll_sched = schedule.every(120_000) do @callcenters.each_key { |id| get_call_count(id) } + check_call_state end + # Every day at 5am @reset_sched = schedule.cron("0 5 * * *") { reset_stats } @@ -65,6 +69,13 @@ def reset_stats # "callcenter id" => Array(wait_times) @calls_taken = {} + + # "callcenter id" => Array(talk_times) + @talk_time = {} + + # We reset the call tracker + # Probably not needed but might accumulate calls over time + @call_tracking = {} end def on_unload @@ -89,11 +100,12 @@ def connect nil end - SHOULD_UPDATE = Set.new(['ACDCallAddedEvent', 'ACDCallAbandonedEvent', 'CallReleasedEvent', 'ACDCallStrandedEvent', 'ACDCallAnsweredByAgentEvent']) + SHOULD_UPDATE = Set.new(['ACDCallAddedEvent', 'ACDCallAbandonedEvent', 'CallReleasedEvent', 'ACDCallStrandedEvent', 'ACDCallAnsweredByAgentEvent', 'CallReleasedEvent']) def process_event(event) # Check if the connection was terminated if event.nil? + logger.debug { "Connection closed! Reconnecting: #{!@terminated}" } if !@terminated schedule.in(1000) do @bw = nil @@ -138,6 +150,38 @@ def process_event(event) wait_times = @calls_taken[call_center_id] || [] wait_times << wait_time @calls_taken[call_center_id] = wait_times + when 'ACDWhisperStartedEvent' + # The agent has decided to accept the call. + event_data = event[:event_data] + user_id = event_data.xpath("//answeringUserId").inner_text + call_id = event_data.xpath("//answeringCallId").inner_text + + @call_tracking[call_id] = { + user: user_id, + center: call_center_id, + time: Time.now.to_i + } + + logger.debug { "tracking call #{call_id} handled by #{user_id}" } + task { bw.get_user_events(user_id, "Basic Call") } + when 'CallReleasedEvent' + event_data = event[:event_data] + call_id = event_data.xpath("//callId").inner_text + call_details = @call_tracking.delete call_id + + logger.debug { "call #{call_id} ended, was tracked: #{!!call_details}" } + + if call_details + call_center_id = call_details[:center] + + answered_time = event_data.xpath("//answerTime").inner_text.to_i + released_time = event_data.xpath("//releaseTime").inner_text.to_i + + talk_time = released_time - answered_time + talk_times = @talk_time[call_center_id] || [] + talk_times << talk_time + @talk_time[call_center_id] = talk_times + end else logger.debug { "ignoring event #{event[:event_name]}" } end @@ -182,7 +226,7 @@ def monitor_callcenters # Non-event related calls def get_call_count(call_center_id) - get('/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls') do |data, resolve, command| + get('/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls', name: "call_center_#{call_center_id}_calls") do |data, resolve, command| if data.status == 200 xml = Nokogiri::XML(data.body) xml.remove_namespaces! @@ -196,5 +240,85 @@ def get_call_count(call_center_id) def update_stats queues = {} + total_abandoned = 0 + all_calls = [] + all_times = [] + + # Summarise current calls + num_on_call = {} + @call_tracking.each do |call_id, details| + center_id = details[:center] + count = num_on_call[center_id] || 0 + count += 1 + num_on_call[center_id] = count + end + + # Build a summary of each DC + @callcenters.each do |id, name| + calls = Array(@calls_taken[id]) + abandoned = @abandoned_calls[id].to_i + total_abandoned += abandoned + all_calls.concat calls + + times = Array(@talk_time[id]) + all_times.concat times + + details = { + queue_length: @queued_calls[id].to_i, + abandoned: abandoned, + total_calls: calls.size, + # Time in milliseconds + average_wait: calls.reduce(:+) / calls.size, + max_wait: calls.max.to_i, + + average_talk: times.reduce(:+) / times.size, + on_calls: num_on_call[id].to_i + } + + queues[name] = details + end + + # Expose the state + self[:queues] = queues + self[:total_abandoned] = total_abandoned + + # TODO:: confirm if this is longest in the day or in the current queue? + self[:longest_wait] = all_calls.max + self[:longest_talk] = all_times.max + end + + # Ensure the calls that are active are still active: + def check_call_state + # This doesn't update stats like average wait times as would only typically occur + # when someone hangs up really fast as the events in the system are delays by about 10 seconds + @call_tracking.each do |call_id, details| + current_calls(details[:user]).then do |call_ids| + if !call_ids.include?(call_id) + count = details[:check_failed].to_i + count += 1 + if count == 2 + logger.debug { "call tracking failed for #{call_id} with user #{details[:user]}" } + @call_tracking.delete(call_id) + else + details[:check_failed] = count + end + end + end + end + end + + def current_calls(user_id) + # i.e. an event isn't missed: /com.broadsoft.xsi-actions/v2.0/user//calls + # Returns an object with multiple callhalf-722:0 + get('/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls', name: "current_#{user_id}_calls") do |data, resolve, command| + if data.status == 200 + xml = Nokogiri::XML(data.body) + xml.remove_namespaces! + xml.xpath("//callId").map(&:inner_text) + else + logger.warn "failed to retrieve active calls for user #{user_id} - response code: #{data&.status}" + :abort + end + end end end From 357775e7a94547a61ab54da474243e7586832840 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 17:00:46 +1100 Subject: [PATCH 1214/1752] Return only event property of event class --- lib/microsoft/office/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index 9649f6c2..b49b8b9c 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -121,7 +121,7 @@ def get_bookings(mailboxes:, options:{}) # end - bookings[i] = Microsoft::Officenew::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]) + bookings[i] = Microsoft::Officenew::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]).event end bookings_by_room[mailboxes[res['id'].to_i]] = {available: is_available, bookings: bookings} end From eee8fe2c20a8d9e2ea037f3b1099dfbdf559fd38 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 17:03:48 +1100 Subject: [PATCH 1215/1752] Remove debugging --- lib/microsoft/office/client.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index 82eb4942..c5b1b0da 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -92,10 +92,6 @@ def graph_token # The helper method that abstracts calls to graph API. This method allows for both single requests and # bulk requests using the $batch endpoint. def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, bulk: false) - puts "HIT GRAPH REQUEST" - puts "DATA IS" - puts data - puts '------' if bulk uv_request_method = :post graph_path = "#{@graph_domain}/v1.0/$batch" From 21f91ce779e0eb61252555fcc5ed594915d61c4e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 28 Mar 2019 17:04:15 +1100 Subject: [PATCH 1216/1752] Remove debugging --- lib/microsoft/office/client.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index c5b1b0da..7d7e2346 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -137,8 +137,6 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint end def check_response(response) - puts "CHECKING RESPONSE" - puts response.body if response.body case response.status when 200, 201, 204 return From b6452ede69e63109a27e2f3d690d2e92555b4640 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 29 Mar 2019 04:17:17 +0000 Subject: [PATCH 1217/1752] Fix little bugs and issues with e2e testing --- lib/microsoft/office/client.rb | 4 +-- lib/microsoft/office/contacts.rb | 45 ++++++++++++++++++++++++++++++++ lib/microsoft/office/event.rb | 9 +++++++ lib/microsoft/office/events.rb | 34 +++++++++++------------- 4 files changed, 71 insertions(+), 21 deletions(-) diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index 7d7e2346..645eef1b 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -118,14 +118,14 @@ def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, b end def graph_date(date) - Time.at(date).utc.iso8601.split("+")[0] + Time.at(date.to_i).utc.iso8601.split("+")[0] end def log_graph_request(request_method, data, query, headers, graph_path, endpoints=nil) STDERR.puts "--------------NEW GRAPH REQUEST------------" STDERR.puts "#{request_method} to #{graph_path}" STDERR.puts "Data:" - STDERR.puts data if data + STDERR.puts data.to_json if data STDERR.puts "Query:" STDERR.puts query if query STDERR.puts "Headers:" diff --git a/lib/microsoft/office/contacts.rb b/lib/microsoft/office/contacts.rb index 603e4aaa..fa288599 100644 --- a/lib/microsoft/office/contacts.rb +++ b/lib/microsoft/office/contacts.rb @@ -13,4 +13,49 @@ def get_contacts(mailbox:, q:nil, limit:nil) check_response(request) JSON.parse(request.body)['value'].map {|c| Microsoft::Officenew::Contact.new(client: self, contact: c)} end + + ## + # Add a new contact to the passed in mailbox. + # + # @param mailbox [String] The mailbox email to which we will save the new contact + # @param email [String] The email of the new contact + # @param first_name [String] The first name of the new contact + # @param last_name [String] The last name of the new contact + # @option options [String] :phone The phone number of the new contact + # @option options [String] :organisation The organisation of the new contact + # @option options [String] :title The title of the new contact + def create_contact(mailbox:, email:, first_name:, last_name:, options:{}) + default_options = { + phone: nil, + organisation: nil, + title: nil, + other: {} + } + # Merge in our default options with those passed in + options = options.reverse_merge(default_options) + + # This data is required so add it unconditionally + contact_data = { + givenName: first_name, + surname: last_name, + emailAddresses: [{ + address: email, + name: "#{first_name} #{last_name}" + }] + } + + # Only add these fields if passed in + contact_data[:title] = options[:title] if options[:title] + contact_data[:businessPhones] = [ options[:phone] ] if options[:phone] + contact_data[:companyName] = options[:organisation] if options[:organisation] + + # Add any fields that we haven't specified explicitly + options[:other].each do |field, value| + contact_data[field.to_sym] = value + end + + # Make the request and return the result + request = graph_request(request_method: 'post', endpoint: "/v1.0/users/#{mailbox}/contacts", data: contact_data) + check_response(request) + J end \ No newline at end of file diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index 0b6887c8..cc80fa8b 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -2,6 +2,7 @@ class Microsoft::Officenew::Event < Microsoft::Officenew::Model # These are the fields which we just want to alias or pull out of the O365 model without any processing ALIAS_FIELDS = { + 'id' => 'id', 'start' => 'start', 'end' => 'end', 'subject' => 'subject', @@ -16,6 +17,7 @@ class Microsoft::Officenew::Event < Microsoft::Officenew::Model { new_key: 'end_epoch', method: 'datestring_to_epoch', model_params:['end'] }, { new_key: 'room_ids', method: 'set_room_id', model_params:['attendees'] }, { new_key: 'attendees', method: 'format_attendees', model_params:['attendees', 'organizer'] }, + { new_key: 'organizer', method: 'set_organizer', model_params:['organizer'] }, { new_key: 'is_free', method: 'check_availability', model_params:['start', 'end'], passed_params: ['available_to', 'available_from'] } ] @@ -52,6 +54,10 @@ def set_room_id(attendees) room_ids end + def set_organizer(organizer) + { name: organizer['emailAddress']['name'], email: organizer['emailAddress']['address']} + end + def format_attendees(attendees, organizer) internal_domains = [::Mail::Address.new(organizer['emailAddress']['address']).domain] new_attendees = [] @@ -78,6 +84,9 @@ def format_attendees(attendees, organizer) end def check_availability(start_param, end_param, available_from, available_to) + # If there's no availability required just return nil + return true if available_from.nil? + booking_start = ActiveSupport::TimeZone.new(start_param['timeZone']).parse(start_param['dateTime']).to_i booking_end = ActiveSupport::TimeZone.new(end_param['timeZone']).parse(end_param['dateTime']).to_i diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index b49b8b9c..a1584f13 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -153,11 +153,9 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) room_emails: [], subject: "Meeting", description: nil, - organizer_name: nil, - organizer_email: mailbox, + organizer: { name: nil, email: mailbox }, attendees: [], - recurrence_type: nil, - recurrence_end: nil, + recurrence: nil, is_private: false, timezone: 'UTC', extensions: {}, @@ -182,10 +180,8 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) timezone: (timezone || "UTC"), location: options[:location], attendees: options[:attendees], - organizer_name: options[:organizer_name], - organizer_email: options[:organizer_email], - recurrence_type: options[:recurrence_type], - recurrence_end: options[:recurrence_end], + organizer: options[:organizer], + recurrence: options[:recurrence], is_private: options[:is_private] ) @@ -196,7 +192,7 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) puts '-----' request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}/events"], data: event_json) check_response(request) - + Microsoft::Officenew::Event.new(client: self, event: JSON.parse(request.body)).event end ## @@ -264,12 +260,12 @@ def update_booking(booking_id:, mailbox:, options: {}) # Make the request and check the response request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}/events/#{booking_id}"], data: event_json) check_response(request) - request + Microsoft::Officenew::Event.new(client: self, event: JSON.parse(request.body)).event end protected - def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer_email: nil, recurrence_type: nil, recurrence_end: nil, extensions: {}, is_private: false) + def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) # Put the attendees into the MS Graph expeceted format attendees.map! do |a| attendee_type = ( a[:optional] ? "optional" : "required" ) @@ -311,10 +307,10 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, event_json[:organizer] = { emailAddress: { - address: organizer_email, - name: (organizer_name || organizer_email) + address: organizer[:email], + name: organizer[:email] || organizer[:email] } - } if organizer_email + } if organizer ext = { @@ -324,20 +320,20 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, extensions.each do |ext_key, ext_value| ext[ext_key] = ext_value end - event[:extensions] = exts + event_json[:extensions] = [ext] - event[:recurrence] = { + event_json[:recurrence] = { pattern: { - type: recurrence_type, + type: recurrence[:type], interval: 1, daysOfWeek: [epoch_in_timezone(start_param, timezone).strftime("%A")] }, range: { type: 'endDate', startDate: epoch_in_timezone(start_param, timezone).strftime("%F"), - endDate: epoch_in_timezone(recurrence_end, timezone).strftime("%F") + endDate: epoch_in_timezone(recurrence[:end], timezone).strftime("%F") } - } if recurrence_type + } if recurrence event_json.reject!{|k,v| v.nil?} event_json From e6ff78e395946f131d4345ee4a48bbbc1e922d1f Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 2 Apr 2019 19:03:53 +1000 Subject: [PATCH 1218/1752] (cisco:ce) support removing camera presets --- modules/cisco/collaboration_endpoint/sx20.rb | 2 ++ modules/cisco/collaboration_endpoint/sx80.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/sx20.rb b/modules/cisco/collaboration_endpoint/sx20.rb index e1f944e0..c7b536e1 100644 --- a/modules/cisco/collaboration_endpoint/sx20.rb +++ b/modules/cisco/collaboration_endpoint/sx20.rb @@ -97,6 +97,8 @@ def mic_mute(state = On) Name_: String, TakeSnapshot_: [true, false], DefaultPosition_: [true, false] + command 'Camera Preset Remove' => :camera_remove_preset, + PresetId: (1..35) command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..2), diff --git a/modules/cisco/collaboration_endpoint/sx80.rb b/modules/cisco/collaboration_endpoint/sx80.rb index a131a7e7..169da279 100644 --- a/modules/cisco/collaboration_endpoint/sx80.rb +++ b/modules/cisco/collaboration_endpoint/sx80.rb @@ -103,6 +103,8 @@ def mic_mute(state = On) Name_: String, TakeSnapshot_: [true, false], DefaultPosition_: [true, false] + command 'Camera Preset Remove' => :camera_remove_preset, + PresetId: (1..35) command 'Camera PositionReset' => :camera_position_reset, CameraId: (1..7), From d4aec2a67524fab31791aade147386345b083fa0 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 3 Apr 2019 12:19:07 +1100 Subject: [PATCH 1219/1752] [o365] Enable extensions in booking update --- lib/microsoft/office.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 907135ba..efbcb71c 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -1006,7 +1006,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update - def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Australia/Sydney', endpoint_override:nil) + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Australia/Sydney', endpoint_override:nil, extensions:nil) # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find_by_email(room_id) || Orchestrator::ControlSystem.find(room_id) @@ -1077,6 +1077,20 @@ def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subjec type: 'resource' }) + exts = [] + extensions.each do |extension| + ext = { + "@odata.type": "microsoft.graph.openTypeExtension", + "extensionName": extension[:name] + } + extension[:values].each do |ext_key, ext_value| + ext[ext_key] = ext_value + end + exts.push(ext) + end + event[:extensions] = exts + + event = event.to_json event.gsub!("X!X!X!",description) if description From 6308a630d81886d7232147c2e4bff2068935ec49 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 3 Apr 2019 01:47:52 +0000 Subject: [PATCH 1220/1752] [o365] Fix syntax issue in events --- lib/microsoft/office/contacts.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office/contacts.rb b/lib/microsoft/office/contacts.rb index fa288599..b9ebeef9 100644 --- a/lib/microsoft/office/contacts.rb +++ b/lib/microsoft/office/contacts.rb @@ -57,5 +57,5 @@ def create_contact(mailbox:, email:, first_name:, last_name:, options:{}) # Make the request and return the result request = graph_request(request_method: 'post', endpoint: "/v1.0/users/#{mailbox}/contacts", data: contact_data) check_response(request) - J + end end \ No newline at end of file From 7a8597bd755214579997c9dd66f6bb07012e50d2 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 3 Apr 2019 12:48:24 +1100 Subject: [PATCH 1221/1752] Add office365 spec --- spec/libs/office_spec.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 spec/libs/office_spec.rb diff --git a/spec/libs/office_spec.rb b/spec/libs/office_spec.rb new file mode 100644 index 00000000..4b7ff05f --- /dev/null +++ b/spec/libs/office_spec.rb @@ -0,0 +1,26 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'rails' +require 'uv-rays' +require 'microsoft/officenew' + + +describe "office365 library" do + before :each do + @office_credentials = { + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ENV["OFFICE_CLIENT_SECRET"], + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com" + } + end + + it "should initialize with client application details" do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + expect(@office).not_to be_nil + end + +end From e60067bd58581f676a7fcbfff57a0be84fcf1fc2 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Apr 2019 00:05:27 +0000 Subject: [PATCH 1222/1752] Allow for passing of procs to get and save token --- lib/microsoft/office/client.rb | 13 +++-- lib/microsoft/office/event.rb | 21 ++++++-- lib/microsoft/office/events.rb | 92 +++++++++++++++++----------------- lib/microsoft/officenew.rb | 1 + 4 files changed, 75 insertions(+), 52 deletions(-) diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index 645eef1b..c2b28acb 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -1,4 +1,5 @@ require 'active_support/time' +require 'oauth2' require 'microsoft/officenew' require 'microsoft/office/model' require 'microsoft/office/user' @@ -42,7 +43,9 @@ def initialize( app_site:, app_token_url:, app_scope:, - graph_domain: + graph_domain:, + save_token:, + get_token: ) @client_id = client_id @client_secret = client_secret @@ -50,6 +53,8 @@ def initialize( @app_token_url = app_token_url @app_scope = app_scope @graph_domain = graph_domain + @get_token = get_token + @save_token = save_token oauth_options = { site: @app_site, token_url: @app_token_url } oauth_options[:connection_opts] = { proxy: @internet_proxy } if @internet_proxy @graph_client ||= OAuth2::Client.new( @@ -67,7 +72,8 @@ def initialize( # grabs a new token and stores it along with the expiry date. def graph_token # Check if we have a token in couchbase - token = User.bucket.get("office-token", quiet: true) + # token = User.bucket.get("office-token", quiet: true) + token = @get_token.call # If we don't have a token if token.nil? || token[:expiry] <= Time.now.to_i @@ -80,7 +86,8 @@ def graph_token token: new_token.token, expiry: Time.now.to_i + new_token.expires_in, } - User.bucket.set("office-token", new_token_model) + @save_token.call(new_token_model) + # User.bucket.set("office-token", new_token_model) return new_token.token else # Otherwise, use the existing token diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index cc80fa8b..e270f1df 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -1,3 +1,4 @@ +require 'mail' class Microsoft::Officenew::Event < Microsoft::Officenew::Model # These are the fields which we just want to alias or pull out of the O365 model without any processing @@ -6,6 +7,7 @@ class Microsoft::Officenew::Event < Microsoft::Officenew::Model 'start' => 'start', 'end' => 'end', 'subject' => 'subject', + 'body' => {'content' => 'body'}, 'attendees' => 'old_attendees', 'iCalUId' => 'icaluid', 'showAs' => 'show_as', @@ -15,7 +17,7 @@ class Microsoft::Officenew::Event < Microsoft::Officenew::Model NEW_FIELDS = [ { new_key: 'start_epoch', method: 'datestring_to_epoch', model_params:['start'] }, { new_key: 'end_epoch', method: 'datestring_to_epoch', model_params:['end'] }, - { new_key: 'room_ids', method: 'set_room_id', model_params:['attendees'] }, + { new_key: 'room_emails', method: 'set_room_id', model_params:['attendees'] }, { new_key: 'attendees', method: 'format_attendees', model_params:['attendees', 'organizer'] }, { new_key: 'organizer', method: 'set_organizer', model_params:['organizer'] }, { new_key: 'is_free', method: 'check_availability', model_params:['start', 'end'], passed_params: ['available_to', 'available_from'] } @@ -31,7 +33,7 @@ def initialize(client:, event:, available_to:nil, available_from:nil) @client = client @available_to = available_to @available_from = available_from - @event = create_aliases(event, ALIAS_FIELDS, NEW_FIELDS, self) + @event = get_extensions(create_aliases(event, ALIAS_FIELDS, NEW_FIELDS, self), event) end attr_accessor :event, :available_to, :available_from @@ -42,6 +44,19 @@ def datestring_to_epoch(date_object) ActiveSupport::TimeZone.new(date_object['timeZone']).parse(date_object['dateTime']).to_i end + def get_extensions(new_event, old_event) + if old_event.key?('extensions') + old_event['extensions'].each do |ext| + if ext['id'] == "Microsoft.OutlookServices.OpenTypeExtension.Com.Acaprojects.Extensions" + ext.each do |ext_key, ext_val| + new_event[ext_key] = ext_val if !['@odata.type', '@odata.context', 'id','extensionName'].include?(ext_key) + end + end + end + end + new_event + end + def set_room_id(attendees) room_ids = [] attendees.each do |attendee| @@ -78,7 +93,7 @@ def format_attendees(attendees, organizer) visitor: is_visitor, organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize } - new_attendees.push(attendee_object) + new_attendees.push(attendee_object) if attendee['type'] != 'resource' end new_attendees end diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index a1584f13..c9eaba6c 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -150,7 +150,7 @@ def get_bookings(mailboxes:, options:{}) # @option options [String] :location The location field to set. This will not be used if a room is passed in def create_booking(mailbox:, start_param:, end_param:, options: {}) default_options = { - room_emails: [], + rooms: [], subject: "Meeting", description: nil, organizer: { name: nil, email: mailbox }, @@ -164,32 +164,23 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) # Merge in our default options with those passed in options = options.reverse_merge(default_options) - # Turn our array of room emails into an array of rooms (ControlSystems) - rooms = options[:room_emails].map { |r_id| Orchestrator::ControlSystem.find_by_email(r_id) } - - # Get the timezones out of the room's zone if it has any - timezone = get_timezone(rooms[0]) if rooms.present? - # Create the JSON body for our event event_json = create_event_json( subject: options[:subject], body: options[:description], - rooms: rooms, + rooms: options[:rooms], start_param: start_param, end_param: end_param, - timezone: (timezone || "UTC"), + timezone: options[:timezone], location: options[:location], - attendees: options[:attendees], + attendees: options[:attendees].dup, organizer: options[:organizer], recurrence: options[:recurrence], + extensions: options[:extensions], is_private: options[:is_private] ) # Make the request and check the response - puts "ABOUT TO MAKE GRAPH REQUEST TO CREATE BOOKING" - puts "DATA IS " - puts event_json - puts '-----' request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}/events"], data: event_json) check_response(request) Microsoft::Officenew::Event.new(client: self, event: JSON.parse(request.body)).event @@ -218,14 +209,12 @@ def update_booking(booking_id:, mailbox:, options: {}) default_options = { start_param: nil, end_param: nil, - room_emails: [], + rooms: [], subject: "Meeting", description: nil, - organizer_name: nil, - organizer_email: mailbox, + organizer: { name: nil, email: mailbox }, attendees: [], - recurrence_type: nil, - recurrence_end: nil, + recurrence: nil, is_private: false, timezone: 'UTC', extensions: {}, @@ -234,33 +223,55 @@ def update_booking(booking_id:, mailbox:, options: {}) # Merge in our default options with those passed in options = options.reverse_merge(default_options) - # Turn our array of room emails into an array of rooms (ControlSystems) - rooms = room_emails.map { |r_id| Orchestrator::ControlSystem.find_by_email(r_id) } - - # Get the timezones out of the room's zone if it has any - timezone = get_timezone(rooms[0]) if rooms.present? - # Create the JSON body for our event event_json = create_event_json( subject: options[:subject], body: options[:description], - rooms: rooms, + rooms: options[:rooms], start_param: options[:start_param], end_param: options[:end_param], - timezone: (timezone || "UTC"), + timezone: options[:timezone], location: options[:location], - attendees: options[:attendees], - organizer_name: options[:organizer_name], - organizer_email: options[:organizer_email], - recurrence_type: options[:recurrence_type], - recurrence_end: options[:recurrence_end], + attendees: options[:attendees].dup, + organizer: options[:organizer], + recurrence: options[:recurrence], + extensions: options[:extensions], is_private: options[:is_private] ) + # If extensions exist we must make a separate request to add them + if options[:extensions].present? + options[:extensions] = options[:extensions].dup + options[:extensions]["@odata.type"] = "microsoft.graph.openTypeExtension" + options[:extensions]["extensionName"] = "Com.Acaprojects.Extensions" + request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}/events/#{booking_id}/extensions/Microsoft.OutlookServices.OpenTypeExtension.Com.Acaprojects.Extensions"], data: options[:extensions]) + check_response(request) + ext_data = JSON.parse(request.body) + end + puts "GOT EXT DATA:" + puts ext_data.inspect + # Make the request and check the response request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}/events/#{booking_id}"], data: event_json) check_response(request) - Microsoft::Officenew::Event.new(client: self, event: JSON.parse(request.body)).event + puts "----" + puts "EVENT WITHOUT EXT:" + puts JSON.parse(request.body) + puts "EVENT WITH EXT" + puts JSON.parse(request.body).merge({extensions: [ext_data]}) + Microsoft::Officenew::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event + end + + ## + # Delete a booking from the passed in mailbox. + # + # @param mailbox [String] The mailbox email which contains the booking to delete + # @param booking_id [String] The ID of the booking to be deleted + def delete_booking(mailbox:, booking_id:) + endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" + request = graph_request(request_method: 'delete', endpoints: [endpoint]) + check_response(request) + 200 end protected @@ -274,11 +285,11 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, # Add each room to the attendees array rooms.each do |room| - attendees.push({ type: "resource", emailAddress: { address: room.email, name: room.name } }) + attendees.push({ type: "resource", emailAddress: { address: room[:email], name: room[:name] } }) end # If we have rooms then build the location from that, otherwise use the passed in value - event_location = rooms.map{ |room| room.name }.join(" and ") + event_location = rooms.map{ |room| room[:name] }.join(" and ") event_location = ( event_location.present? ? event_location : location ) event_json = {} @@ -340,15 +351,4 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, end - def get_timezone(room) - timezone = nil - room.zones.each do |zone_id| - zone = Orchestrator::Zone.find(zone_id) - if zone.settings.key?("timezone") - timezone = zone.settings['timezone'] - end - end - timezone - end - end diff --git a/lib/microsoft/officenew.rb b/lib/microsoft/officenew.rb index b96c292f..aa7978a5 100644 --- a/lib/microsoft/officenew.rb +++ b/lib/microsoft/officenew.rb @@ -1 +1,2 @@ +module Microsoft; end class Microsoft::Officenew; end \ No newline at end of file From e9c5db227b3766cb982e0eb5295bd82275f68d30 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Apr 2019 00:23:47 +0000 Subject: [PATCH 1223/1752] Add tests for events --- spec/libs/microsoft/office/events/create.rb | 128 ++++++++++++++++++++ spec/libs/microsoft/office/events/update.rb | 123 +++++++++++++++++++ spec/libs/office_spec.rb | 26 ---- 3 files changed, 251 insertions(+), 26 deletions(-) create mode 100644 spec/libs/microsoft/office/events/create.rb create mode 100644 spec/libs/microsoft/office/events/update.rb delete mode 100644 spec/libs/office_spec.rb diff --git a/spec/libs/microsoft/office/events/create.rb b/spec/libs/microsoft/office/events/create.rb new file mode 100644 index 00000000..9dc3b9dc --- /dev/null +++ b/spec/libs/microsoft/office/events/create.rb @@ -0,0 +1,128 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'rails' +require 'uv-rays' +require 'microsoft/office/client' + + +describe "office365 events" do + before :each do + @office_credentials = { + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ENV["OFFICE_CLIENT_SECRET"], + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + save_token: Proc.new{ |token| @token = token }, + get_token: Proc.new{ nil } + } + @start_time = Time.now + @end_time = @start_time + 30.minutes + @title = "Test Booking" + @body = "Test Body" + @attendees = [{name: "Cram Creeves", email: "reeves.cameron@gmail.com"}, {name: "Cam Reeves", email: "cam@acaprojects.com"}] + @extensions = { test_ext: 123 } + @rooms = [{ email: 'testroom1@acaprojects.com', name: "Test Room" }] + @booking_body = { + mailbox: "cam@acaprojects.com", + start_param: @start_time.to_i, + end_param: @end_time.to_i, + options: { + rooms: @rooms, + subject: @title, + description: @body, + attendees: @attendees, + extensions: @extensions + } + } + end + + it "should initialize with client application details" do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + expect(@office).not_to be_nil + end + + it 'should create events in o365 at the passed in time' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + booking = nil + reactor.run { + booking = @office.create_booking(@booking_body) + } + expect(booking['start_epoch']).to eq(@start_time.to_i) + reactor.run { + @office.delete_booking(mailbox: booking['organizer'][:email], booking_id: booking['id']) + } + end + + it 'should create events in o365 with the passed in attendees' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + booking = nil + reactor.run { + booking = @office.create_booking(@booking_body) + } + booking['attendees'].each do |attendee| + expect(@attendees.map{ |a| a[:email] }).to include(attendee[:email]) + end + + reactor.run { + @office.delete_booking(mailbox: booking['organizer'][:email], booking_id: booking['id']) + } + end + + it 'should create events in o365 containing the passed in rooms' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + booking = nil + reactor.run { + booking = @office.create_booking(@booking_body) + } + expect(booking['room_emails'].map{ |e| e.downcase }.sort).to eq(@rooms.map{|r| r[:email].downcase}.sort) + + reactor.run { + @office.delete_booking(mailbox: booking['organizer'][:email], booking_id: booking['id']) + } + end + + it 'should create events in o365 with the passed in title' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + booking = nil + reactor.run { + booking = @office.create_booking(@booking_body) + } + expect(booking['subject']).to eq(@title) + + reactor.run { + @office.delete_booking(mailbox: booking['organizer'][:email], booking_id: booking['id']) + } + end + + it 'should create events in o365 with the body as the passed in description' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + booking = nil + reactor.run { + booking = @office.create_booking(@booking_body) + } + expect(booking['body']).to include(@body) + + reactor.run { + @office.delete_booking(mailbox: booking['organizer'][:email], booking_id: booking['id']) + } + end + + it 'should create events in o365 with any passed in extensions at the root of the event' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + booking = nil + reactor.run { + booking = @office.create_booking(@booking_body) + } + @extensions.each do |ext_key, ext_value| + expect(booking[ext_key.to_s]).to eq(ext_value) + end + + reactor.run { + @office.delete_booking(mailbox: booking['organizer'][:email], booking_id: booking['id']) + } + end + +end diff --git a/spec/libs/microsoft/office/events/update.rb b/spec/libs/microsoft/office/events/update.rb new file mode 100644 index 00000000..0b3f42b3 --- /dev/null +++ b/spec/libs/microsoft/office/events/update.rb @@ -0,0 +1,123 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'rails' +require 'uv-rays' +require 'microsoft/office/client' + + +describe "office365 events" do + before :each do + @office_credentials = { + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ENV["OFFICE_CLIENT_SECRET"], + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + save_token: Proc.new{ |token| @token = token }, + get_token: Proc.new{ nil } + } + @start_time = Time.now + @end_time = @start_time + 30.minutes + @subject = "Test Booking" + @body = "Test Body" + @attendees = [{name: "Cram Creeves", email: "reeves.cameron@gmail.com"}, {name: "Cam Reeves", email: "cam@acaprojects.com"}] + @extensions = { test_ext: 'NOT UPDATED' } + @rooms = [{ email: 'testroom1@acaprojects.com', name: "Test Room" }] + @booking_body = { + mailbox: "cam@acaprojects.com", + start_param: @start_time.to_i, + end_param: @end_time.to_i, + options: { + rooms: @rooms, + subject: @subject, + description: @body, + attendees: @attendees, + extensions: @extensions + } + } + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @booking = nil + reactor.run { + @booking = @office.create_booking(@booking_body) + } + + puts "--------------------- Original booking ---------------------" + puts @booking + puts "--------------------" + @new_start_time = Time.now + 1.day + @new_end_time = @new_start_time + 30.minutes + @new_subject = "Updated Test Booking" + @new_body = "Updated Test Body" + @new_attendees = [{name: "Atomic Creeves", email: "atomic778@gmail.com"}] + @new_extensions = { updated_test_ext: 'UPDATED' } + @new_rooms = [{ email: 'testroom2@acaprojects.com', name: "New Room" }] + @update_body = { + booking_id: @booking['id'], + mailbox: "cam@acaprojects.com", + options: { + start_param: @new_start_time.to_i, + end_param: @new_end_time.to_i, + rooms: @new_rooms, + subject: @new_subject, + description: @new_body, + attendees: @new_attendees, + extensions: @new_extensions + } + } + end + + it 'should return updated events with the passed in time' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + updated_booking = nil + reactor.run { + updated_booking = @office.update_booking(@update_body) + } + expect(updated_booking['start_epoch']).to eq(@new_start_time.to_i) + reactor.run { + @office.delete_booking(mailbox: updated_booking['organizer'][:email], booking_id: updated_booking['id']) + } + end + + it 'should return updated events with the passed in attendees' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + updated_booking = nil + reactor.run { + updated_booking = @office.update_booking(@update_body) + } + updated_booking['attendees'].each do |attendee| + expect(@new_attendees.map{ |a| a[:email] }).to include(attendee[:email]) + end + reactor.run { + @office.delete_booking(mailbox: updated_booking['organizer'][:email], booking_id: updated_booking['id']) + } + end + it 'should return updated events with the passed in subject' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + updated_booking = nil + reactor.run { + updated_booking = @office.update_booking(@update_body) + } + + expect(updated_booking['subject']).to eq(@new_subject) + + reactor.run { + @office.delete_booking(mailbox: updated_booking['organizer'][:email], booking_id: updated_booking['id']) + } + end + + it 'should return updated events with the passed in time' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + updated_booking = nil + reactor.run { + updated_booking = @office.update_booking(@update_body) + } + @new_extensions.each do |ext_key, ext_value| + expect(updated_booking[ext_key.to_s]).to eq(ext_value) + end + reactor.run { + @office.delete_booking(mailbox: updated_booking['organizer'][:email], booking_id: updated_booking['id']) + } + end +end diff --git a/spec/libs/office_spec.rb b/spec/libs/office_spec.rb deleted file mode 100644 index 4b7ff05f..00000000 --- a/spec/libs/office_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# encoding: ASCII-8BIT -# frozen_string_literal: true - -require 'rails' -require 'uv-rays' -require 'microsoft/officenew' - - -describe "office365 library" do - before :each do - @office_credentials = { - client_id: ENV['OFFICE_CLIENT_ID'], - client_secret: ENV["OFFICE_CLIENT_SECRET"], - app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", - app_token_url: ENV["OFFICE_TOKEN_URL"], - app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", - graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com" - } - end - - it "should initialize with client application details" do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) - expect(@office).not_to be_nil - end - -end From 43594c957980e4b84b56fc40448d80cad7a58ac3 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Apr 2019 05:12:08 +0000 Subject: [PATCH 1224/1752] Remove debugging and add default proc --- lib/microsoft/office/client.rb | 4 ++-- lib/microsoft/office/event.rb | 2 +- lib/microsoft/office/events.rb | 20 ++------------------ 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index c2b28acb..73856397 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -44,8 +44,8 @@ def initialize( app_token_url:, app_scope:, graph_domain:, - save_token:, - get_token: + save_token: Proc.new{ |token| User.bucket.set("office-token", token) }, + get_token: Proc.new{ User.bucket.get("office-token", quiet: true) } ) @client_id = client_id @client_secret = client_secret diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index e270f1df..8f3e58a9 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -20,7 +20,7 @@ class Microsoft::Officenew::Event < Microsoft::Officenew::Model { new_key: 'room_emails', method: 'set_room_id', model_params:['attendees'] }, { new_key: 'attendees', method: 'format_attendees', model_params:['attendees', 'organizer'] }, { new_key: 'organizer', method: 'set_organizer', model_params:['organizer'] }, - { new_key: 'is_free', method: 'check_availability', model_params:['start', 'end'], passed_params: ['available_to', 'available_from'] } + { new_key: 'is_free', method: 'check_availability', model_params:['start', 'end'], passed_params: ['available_from', 'available_to'] } ] (hash_to_reduced_array(ALIAS_FIELDS) + NEW_FIELDS.map{|v| v[:new_key]}).each do |field| diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index c9eaba6c..972dc936 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -110,18 +110,8 @@ def get_bookings(mailboxes:, options:{}) is_available = true # Go through each booking and extract more info from it bookings.each_with_index do |booking, i| - # # Check if the bookings fall inside the availability window - # booking = check_availability(booking, options[:available_from], options[:available_to], mailboxes[res['id']]) - # # Alias a bunch of fields we use to follow our naming convention - # booking = alias_fields(booking) - # # Set the attendees as internal or external and alias their fields too - # bookings[i] = set_attendees(booking) - # if bookings[i]['free'] == false && !options[:ignore_bookings].include?(bookings[i]['icaluid']) - # is_available = false - # end - - bookings[i] = Microsoft::Officenew::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]).event + is_available = false if !bookings[i]['is_free'] && !options[:ignore_bookings].include?(bookings[i]['id']) end bookings_by_room[mailboxes[res['id'].to_i]] = {available: is_available, bookings: bookings} end @@ -248,17 +238,11 @@ def update_booking(booking_id:, mailbox:, options: {}) check_response(request) ext_data = JSON.parse(request.body) end - puts "GOT EXT DATA:" - puts ext_data.inspect # Make the request and check the response request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}/events/#{booking_id}"], data: event_json) check_response(request) - puts "----" - puts "EVENT WITHOUT EXT:" - puts JSON.parse(request.body) - puts "EVENT WITH EXT" - puts JSON.parse(request.body).merge({extensions: [ext_data]}) + Microsoft::Officenew::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event end From 6f55b8ff45db0db8b6635985abc4e37c9f5f0449 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Apr 2019 05:13:56 +0000 Subject: [PATCH 1225/1752] Add more event specs --- spec/libs/microsoft/office/events/create.rb | 2 +- spec/libs/microsoft/office/events/delete.rb | 57 ++++++++++ spec/libs/microsoft/office/events/read.rb | 113 ++++++++++++++++++++ spec/libs/microsoft/office/events/update.rb | 2 +- 4 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 spec/libs/microsoft/office/events/delete.rb create mode 100644 spec/libs/microsoft/office/events/read.rb diff --git a/spec/libs/microsoft/office/events/create.rb b/spec/libs/microsoft/office/events/create.rb index 9dc3b9dc..ff6390c3 100644 --- a/spec/libs/microsoft/office/events/create.rb +++ b/spec/libs/microsoft/office/events/create.rb @@ -6,7 +6,7 @@ require 'microsoft/office/client' -describe "office365 events" do +describe "office365 event creation" do before :each do @office_credentials = { client_id: ENV['OFFICE_CLIENT_ID'], diff --git a/spec/libs/microsoft/office/events/delete.rb b/spec/libs/microsoft/office/events/delete.rb new file mode 100644 index 00000000..55d79664 --- /dev/null +++ b/spec/libs/microsoft/office/events/delete.rb @@ -0,0 +1,57 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'rails' +require 'uv-rays' +require 'microsoft/office/client' + + +describe "office365 event reading" do + before :each do + @office_credentials = { + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ENV["OFFICE_CLIENT_SECRET"], + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + save_token: Proc.new{ |token| @token = token }, + get_token: Proc.new{ nil } + } + @mailbox = "cam@acaprojects.com" + @start_time = Time.now + @end_time = @start_time + 30.minutes + @subject = "Test Booking #{(rand * 10000).to_i}" + @body = "Test Body" + @attendees = [{name: "Cram Creeves", email: "reeves.cameron@gmail.com"}, {name: "Cam Reeves", email: "cam@acaprojects.com"}] + @extensions = { test_ext: 'NOT UPDATED' } + @rooms = [{ email: 'testroom1@acaprojects.com', name: "Test Room" }] + @booking_body = { + mailbox: @mailbox, + start_param: @start_time.to_i, + end_param: @end_time.to_i, + options: { + rooms: @rooms, + subject: @subject, + description: @body, + attendees: @attendees, + extensions: @extensions + } + } + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @booking = nil + reactor.run { + @booking = @office.create_booking(@booking_body) + } + end + + it 'should return 200 when an event is successfully deleted' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + repsonse = nil + reactor.run { + repsonse = @office.delete_booking(mailbox: @mailbox, booking_id: @booking['id']) + } + expect(repsonse).to eq(200) + end + +end diff --git a/spec/libs/microsoft/office/events/read.rb b/spec/libs/microsoft/office/events/read.rb new file mode 100644 index 00000000..df5f1663 --- /dev/null +++ b/spec/libs/microsoft/office/events/read.rb @@ -0,0 +1,113 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'rails' +require 'uv-rays' +require 'microsoft/office/client' + + +describe "office365 event reading" do + before :each do + @office_credentials = { + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ENV["OFFICE_CLIENT_SECRET"], + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + save_token: Proc.new{ |token| @token = token }, + get_token: Proc.new{ nil } + } + @mailbox = "cam@acaprojects.com" + @start_time = Time.now + @end_time = @start_time + 30.minutes + @subject = "Test Booking #{(rand * 10000).to_i}" + @body = "Test Body" + @attendees = [{name: "Cram Creeves", email: "reeves.cameron@gmail.com"}, {name: "Cam Reeves", email: "cam@acaprojects.com"}] + @extensions = { test_ext: 'NOT UPDATED' } + @rooms = [{ email: 'testroom1@acaprojects.com', name: "Test Room" }] + @booking_body = { + mailbox: @mailbox, + start_param: @start_time.to_i, + end_param: @end_time.to_i, + options: { + rooms: @rooms, + subject: @subject, + description: @body, + attendees: @attendees, + extensions: @extensions + } + } + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @booking = nil + reactor.run { + @booking = @office.create_booking(@booking_body) + } + end + + it 'should return events within the passed in time range INCLUSIVE of start and end time' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + bookings = nil + reactor.run { + bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { bookings_from: @start_time.to_i, bookings_to: @end_time.to_i }) + } + expect(bookings.keys.map{|k| k.to_s.downcase }).to include(@mailbox.downcase) + expect(bookings[@mailbox][:bookings].map{|b| b['subject'] }).to include(@subject) + reactor.run { + @office.delete_booking(mailbox: @booking['organizer'][:email], booking_id: @booking['id']) + } + end + + it 'should not return events outside the passed in time range' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + bookings = nil + reactor.run { + bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { bookings_from: (@start_time + 2.hours).to_i, bookings_to: (@end_time + 3.hours).to_i }) + } + expect(bookings[@mailbox][:bookings].map{|b| b['subject'] }).not_to include(@subject) + reactor.run { + @office.delete_booking(mailbox: @booking['organizer'][:email], booking_id: @booking['id']) + } + end + + it 'should return the room as unavailable when checking availability in the time range' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + bookings = nil + reactor.run { + bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { available_from: @start_time.to_i, available_to: @end_time.to_i }) + } + expect(bookings[@mailbox][:available]).to eq(false) + reactor.run { + @office.delete_booking(mailbox: @booking['organizer'][:email], booking_id: @booking['id']) + } + end + + it 'should return the room as available if the conflicting booking ID is passed to the ignore_bookings param' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + bookings = nil + reactor.run { + bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { available_from: @start_time.to_i, available_to: @end_time.to_i, ignore_bookings: [@booking['id']] }) + } + expect(bookings[@mailbox][:available]).to eq(true) + reactor.run { + @office.delete_booking(mailbox: @booking['organizer'][:email], booking_id: @booking['id']) + } + end + + it 'should return events with their extension data at the root of the event' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + bookings = nil + reactor.run { + bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { bookings_from: @start_time.to_i, bookings_to: @end_time.to_i }) + } + booking = bookings[@mailbox][:bookings].select{|b| b['subject'] == @subject}[0] + @extensions.each do |ext_key, ext_value| + expect(booking[ext_key.to_s]).to eq(ext_value) + end + reactor.run { + @office.delete_booking(mailbox: @booking['organizer'][:email], booking_id: @booking['id']) + } + end + + +end diff --git a/spec/libs/microsoft/office/events/update.rb b/spec/libs/microsoft/office/events/update.rb index 0b3f42b3..a06b6ff4 100644 --- a/spec/libs/microsoft/office/events/update.rb +++ b/spec/libs/microsoft/office/events/update.rb @@ -6,7 +6,7 @@ require 'microsoft/office/client' -describe "office365 events" do +describe "office365 event updating" do before :each do @office_credentials = { client_id: ENV['OFFICE_CLIENT_ID'], From a846b7abec296bd591637dd67837c244487bcb50 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Apr 2019 06:25:55 +0000 Subject: [PATCH 1226/1752] Pass back contact details inside class --- lib/microsoft/office/contacts.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office/contacts.rb b/lib/microsoft/office/contacts.rb index b9ebeef9..4d842f67 100644 --- a/lib/microsoft/office/contacts.rb +++ b/lib/microsoft/office/contacts.rb @@ -11,7 +11,7 @@ def get_contacts(mailbox:, q:nil, limit:nil) endpoint = "/v1.0/users/#{mailbox}/contacts" request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) check_response(request) - JSON.parse(request.body)['value'].map {|c| Microsoft::Officenew::Contact.new(client: self, contact: c)} + JSON.parse(request.body)['value'].map {|c| Microsoft::Officenew::Contact.new(client: self, contact: c).contact} end ## @@ -55,7 +55,21 @@ def create_contact(mailbox:, email:, first_name:, last_name:, options:{}) end # Make the request and return the result - request = graph_request(request_method: 'post', endpoint: "/v1.0/users/#{mailbox}/contacts", data: contact_data) + request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}/contacts"], data: contact_data) check_response(request) + Microsoft::Officenew::Contact.new(client: self, contact: JSON.parse(request.body)).contact end + + ## + # Delete a new contact from the passed in mailbox. + # + # @param mailbox [String] The mailbox email which contains the contact to delete + # @param contact_id [String] The ID of the contact to be deleted + def delete_contact(mailbox:, contact_id:) + endpoint = "/v1.0/users/#{mailbox}/contacts/#{contact_id}" + request = graph_request(request_method: 'delete', endpoints: [endpoint]) + check_response(request) + 200 + end + end \ No newline at end of file From dddb629b635236171d22d05004b20416e2c8ebff Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Apr 2019 06:26:06 +0000 Subject: [PATCH 1227/1752] Add specs for contacts and users --- spec/libs/microsoft/office/contacts/create.rb | 41 ++++++++++++++++ spec/libs/microsoft/office/contacts/read.rb | 48 +++++++++++++++++++ spec/libs/microsoft/office/users/read.rb | 46 ++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 spec/libs/microsoft/office/contacts/create.rb create mode 100644 spec/libs/microsoft/office/contacts/read.rb create mode 100644 spec/libs/microsoft/office/users/read.rb diff --git a/spec/libs/microsoft/office/contacts/create.rb b/spec/libs/microsoft/office/contacts/create.rb new file mode 100644 index 00000000..ecdc6e8b --- /dev/null +++ b/spec/libs/microsoft/office/contacts/create.rb @@ -0,0 +1,41 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'rails' +require 'uv-rays' +require 'microsoft/office/client' + + +describe "office365 contact creation" do + before :each do + @office_credentials = { + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ENV["OFFICE_CLIENT_SECRET"], + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + save_token: Proc.new{ |token| @token = token }, + get_token: Proc.new{ nil } + } + @mailbox = 'cam@acaprojects.com' + @first_name = "Joe" + @last_name = "Smith" + @email = 'joe@fakeemail.com' + @organisation = "Company inc" + @title = "Mr" + @phone = "0404851331" + end + + it "should create a contact with corresponding details" do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + contact = nil + reactor.run { + contact = @office.create_contact(mailbox: @mailbox, email: @email, first_name: @first_name, last_name: @last_name, options:{ title: @title, phone: @phone, organisation: @organisation }) + } + expect(contact['email']).to eq(@email) + reactor.run { + @office.delete_contact(mailbox: @mailbox, contact_id: contact['id']) + } + end +end diff --git a/spec/libs/microsoft/office/contacts/read.rb b/spec/libs/microsoft/office/contacts/read.rb new file mode 100644 index 00000000..1376e749 --- /dev/null +++ b/spec/libs/microsoft/office/contacts/read.rb @@ -0,0 +1,48 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'rails' +require 'uv-rays' +require 'microsoft/office/client' + + +describe "office365 contact reading" do + before :each do + @office_credentials = { + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ENV["OFFICE_CLIENT_SECRET"], + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + save_token: Proc.new{ |token| @token = token }, + get_token: Proc.new{ nil } + } + @mailbox = 'cam@acaprojects.com' + @first_name = "Joe" + @last_name = "Smith" + @email = 'joe@fakeemail.com' + @organisation = "Company inc" + @title = "Mr" + @phone = "0404851331" + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @contact = nil + reactor.run { + @contact = @office.create_contact(mailbox: @mailbox, email: @email, first_name: @first_name, last_name: @last_name, options:{ title: @title, phone: @phone, organisation: @organisation }) + } + end + + after :each do + reactor.run { + @office.delete_contact(mailbox: @mailbox, contact_id: @contact['id']) + } + end + + it "should respond with the contacts of the passed in mailbox" do + contacts = nil + reactor.run { + contacts = @office.get_contacts(mailbox: @mailbox) + } + expect(contacts.map{|c| c['email']}).to include(@email) + end +end diff --git a/spec/libs/microsoft/office/users/read.rb b/spec/libs/microsoft/office/users/read.rb new file mode 100644 index 00000000..b1ca0a9a --- /dev/null +++ b/spec/libs/microsoft/office/users/read.rb @@ -0,0 +1,46 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'rails' +require 'uv-rays' +require 'microsoft/office/client' + + +describe "office365 user reading" do + before :each do + @office_credentials = { + client_id: ENV['OFFICE_CLIENT_ID'], + client_secret: ENV["OFFICE_CLIENT_SECRET"], + app_site: ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: ENV["OFFICE_TOKEN_URL"], + app_scope: ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + save_token: Proc.new{ |token| @token = token }, + get_token: Proc.new{ nil } + } + @query = "ca" + @email = "cam@acaprojects.com" + end + + it 'should return all users with name or email matching query string' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + users = nil + reactor.run { + users = @office.get_users(q: @query) + } + users.each do |user| + expect(user['name'].downcase + user['email'].downcase).to include(@query) + end + end + + it 'should return one user if an email is passed' do + @office = ::Microsoft::Officenew::Client.new(@office_credentials) + users = nil + reactor.run { + users = @office.get_users(q: @email) + } + expect(users.length).to eq(1) + expect(users[0]['email'].downcase).to eq(@email) + end + +end From 9fb110c1573e4c72d9dae1c0114405402eaa0760 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 17:53:17 +0800 Subject: [PATCH 1228/1752] pjlink: add untested pjlink projector driver --- modules/pjlink/pjlink.rb | 148 ++++++++++++++++++++++++++++++++++ modules/pjlink/pjlink_spec.rb | 103 +++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 modules/pjlink/pjlink.rb create mode 100644 modules/pjlink/pjlink_spec.rb diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb new file mode 100644 index 00000000..7aaba879 --- /dev/null +++ b/modules/pjlink/pjlink.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +module Pjlink; end +module Pjlink::Pjlink; end + +# Documentation: https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf + +class Pjlink::Projector::Pjlink + include ::Orchestrator::Constants + + # Discovery Information + tcp_port 4352 + descriptive_name 'Projector with Pjlink control' + generic_name :Display + + # Communication settings + # 24bytes with header however we'll ignore the footer + tokenize indicator: "%1", delimiter: "\x0D" + + def on_load + self[:volume_min] = 0 + self[:volume_max] = 100 + end + + def connected + poll + schedule.every('5s') { poll } + end + + def disconnected + # Stop polling + schedule.clear + end + + INPUTS = { + hdmi: '31', + hdmi2: '32', + hdmi3: '33', + vga: '11', + vga2: '12', + vga3: '13', + usb: '41', + network: '51' + } + LOOKUP_INPUTS = INPUTS.invert + + def switch_to(input) + logger.debug { "requesting to switch to: #{input}" } + do_send(COMMANDS[:input], INPUTS[input]) + input? + end + + def power(state, _ = nil) + do_send(COMMANDS[:power], state ? '1' : '0')) + end + + def mute(state = true) + do_send(COMMANDS[:mute], state ? '31' : '30') + end + def unmute + mute false + end + + def video_mute(state = true) + do_send(COMMANDS[:mute], state ? '11' : '10') + end + def video_unmute + mute false + end + + def audio_mute(state = true) + do_send(COMMANDS[:mute], state ? '21' : '20') + end + def audio_unmute + mute false + end + + def poll + power? do + if self[:power] + input? + mute? + volume? + end + end + end + + protected + + def received(data, resolve, command) + logger.debug { "sent: #{data}" } + cmd, param = parse_response(data).values_at(:cmd, :param) + + return :abort if param =~ /^ERR/ + return :success if param == 'OK' + + update_status(cmd, param) + return :success + end + + + COMMANDS = { + power: 'POWR', + mute: 'AVMT', + input: 'INPT', + error_status: 'ERST', + lamp: 'LAMP', + name: 'NAME1' + } + LOOKUP_COMMANDS = COMMANDS.invert + + COMMANDS.each do |name, pj_cmd| + define_method "#{name}?" do + do_query pj_cmd + end + end + + def do_query(command, **options) + cmd = "%1#{command} ?\x0D" + send(cmd, options) + end + + def do_send(command, parameter, **options) + cmd = "%1#{command} #{parameter}\x0D" + send(cmd, options) + end + # 0123456789 + # e.g. %1NAME=Test Projector + def parse_response(byte_str) + { + cmd: COMMANDS[byte_str[2..5]], + param: byte_str[7..-1] + } + end + + # Update module state based on device feedback + def update_status(cmd, param) + case cmd.to_sym + when :power, :mute, :audio_mute, :video_mute + self[cmd] = param == '1' + when :volume + self[:volume] = param.to_i + when :input + self[:input] = LOOKUP_INPUTS[param] + end + end +end diff --git a/modules/pjlink/pjlink_spec.rb b/modules/pjlink/pjlink_spec.rb new file mode 100644 index 00000000..f4980d1e --- /dev/null +++ b/modules/pjlink/pjlink_spec.rb @@ -0,0 +1,103 @@ +# encoding: ASCII-8BIT + +Orchestrator::Testing.mock_device 'Pjlink::Pjlink' do + exec(:power?) + .should_send("%1POWR ?\x0D") + .responds("%1POWR=1\x0D") + expect(status[:power]).to be(true) + + exec(:power, true) + .should_send("%1POWR 1\x0D") + .responds("%1POWR=OK\x0D") + expect(status[:power]).to be(true) + + exec(:power, false) + .should_send("%1POWR 0\x0D") + .responds("%1POWR=OK\x0D") + expect(status[:power]).to be(false) + + exec(:input?) + .should_send("%1INPT ?\x0D") + .responds("%1INPT=31\x0D") + expect(status[:input]).to be(:hdmi) + + exec(:switch_to, :hdmi) + .should_send("%1INPT 31\x0D") + .responds("%1INPT=OK\x0D") + expect(status[:input]).to be(:hdmi) + + exec(:switch_to, :hdmi2) + .should_send("%1INPT 32\x0D") + .responds("%1INPT=OK\x0D") + expect(status[:input]).to be(:hdmi2) + + exec(:switch_to, :hdmi3) + .should_send("%1INPT 33\x0D") + .responds("%1INPT=OK\x0D") + expect(status[:input]).to be(:hdmi3) + + exec(:switch_to, :rgb) + .should_send("%1INPT 11\x0D") + .responds("%1INPT=OK\x0D") + expect(status[:input]).to be(:rgb) + + exec(:switch_to, :storage) + .should_send("%1INPT 41\x0D") + .responds("%1INPT=OK\x0D") + expect(status[:input]).to be(:storage) + + exec(:switch_to, :network) + .should_send("%1INPT 51\x0D") + .responds("%1INPT=OK\x0D") + expect(status[:input]).to be(:network) + + exec(:mute?) + .should_send("%1AVMT ?\x0D") + .responds("%AVMT=30\x0D") + expect(status[:mute]).to be(false) + + exec(:mute, false) + .should_send("%1AVMT 30\x0D") + .responds("%AVMT=OK\x0D") + expect(status[:mute]).to be(false) + + exec(:mute, true) + .should_send("%1AVMT 31\x0D") + .responds("%AVMT=OK\x0D") + expect(status[:mute]).to be(true) + + exec(:video_mute, false) + .should_send("%1AVMT 10\x0D") + .responds("%AVMT=OK\x0D") + expect(status[:video_mute]).to be(false) + + exec(:video_mute, true) + .should_send("%1AVMT 11\x0D") + .responds("%AVMT=OK\x0D") + expect(status[:video_mute]).to be(true) + + exec(:audio_mute, false) + .should_send("%1AVMT 20\x0D") + .responds("%AVMT=OK\x0D") + expect(status[:audio_mute]).to be(false) + + exec(:audio_mute, true) + .should_send("%1AVMT 21\x0D") + .responds("%AVMT=OK\x0D") + expect(status[:audio_mute]).to be(true) + + exec(:error_status?) + .should_send("%1ERST ?\x0D") + .responds("%ERST=000000\x0D") + expect(status[:errors]).to be(nil) + + exec(:lamp?) + .should_send("%1LAMP ?\x0D") + .responds("%LAMP=12345 1\x0D") + expect(status[:lamp_hours]).to be(12345) + + exec(:name?) + .should_send("%1NAME ?\x0D") + .responds("%NAME=Test Projector\x0D") + expect(status[:name]).to be("Test Projector") +end From 9a9ddf10d5c15c5e0f7038fb395e56c3ecfbfdef Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:05:17 +0800 Subject: [PATCH 1229/1752] pjlink: syntax --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 7aaba879..a557918d 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -52,7 +52,7 @@ def switch_to(input) end def power(state, _ = nil) - do_send(COMMANDS[:power], state ? '1' : '0')) + do_send(COMMANDS[:power], state ? '1' : '0') end def mute(state = true) From 6f77ea80cadebab5cf5180658b1c90bdc396c3ee Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:06:17 +0800 Subject: [PATCH 1230/1752] pjlink: syntax of class name --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index a557918d..5fac226d 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -6,7 +6,7 @@ module Pjlink::Pjlink; end # Documentation: https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf -class Pjlink::Projector::Pjlink +class Pjlink::Pjlink include ::Orchestrator::Constants # Discovery Information From 3fe0de89655ec561393a9c21a05b68e9bafc532c Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:08:05 +0800 Subject: [PATCH 1231/1752] pjlink: syntax of class --- modules/pjlink/pjlink.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 5fac226d..005345e9 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true # encoding: ASCII-8BIT - module Pjlink; end -module Pjlink::Pjlink; end # Documentation: https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf From befc7493f86f50715695b267e547b5d6bb3422db Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:13:36 +0800 Subject: [PATCH 1232/1752] pjlink/response: fix parsing --- modules/pjlink/pjlink.rb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 005345e9..c34e0719 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -86,18 +86,6 @@ def poll protected - def received(data, resolve, command) - logger.debug { "sent: #{data}" } - cmd, param = parse_response(data).values_at(:cmd, :param) - - return :abort if param =~ /^ERR/ - return :success if param == 'OK' - - update_status(cmd, param) - return :success - end - - COMMANDS = { power: 'POWR', mute: 'AVMT', @@ -123,12 +111,24 @@ def do_send(command, parameter, **options) cmd = "%1#{command} #{parameter}\x0D" send(cmd, options) end + + def received(data, resolve, command) + logger.debug { "sent: #{data}" } + cmd, param = parse_response(data).values_at(:cmd, :param) + + return :abort if param =~ /^ERR/ + return :success if param == 'OK' + + update_status(cmd, param) + return :success + end + # 0123456789 - # e.g. %1NAME=Test Projector + # e.g. NAME=Test Projector def parse_response(byte_str) { - cmd: COMMANDS[byte_str[2..5]], - param: byte_str[7..-1] + cmd: COMMANDS[byte_str[0..4]], + param: byte_str[5..-1] } end From 130c9966dc23efe78254135e7d4b4e1798f28864 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:17:32 +0800 Subject: [PATCH 1233/1752] pjlink/response: fix lookup --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index c34e0719..856d6a1a 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -127,7 +127,7 @@ def received(data, resolve, command) # e.g. NAME=Test Projector def parse_response(byte_str) { - cmd: COMMANDS[byte_str[0..4]], + cmd: LOOKUP_COMMANDS[byte_str[0..4]], param: byte_str[5..-1] } end From b730343017ab17dada27da4390c6954e4ceac0bd Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:22:09 +0800 Subject: [PATCH 1234/1752] pjlink/response: fix parsing --- modules/pjlink/pjlink.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 856d6a1a..c5837ee9 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -120,6 +120,7 @@ def received(data, resolve, command) return :success if param == 'OK' update_status(cmd, param) + logger.debug "cmd: #{cmd}, param: #{param}" return :success end @@ -127,7 +128,7 @@ def received(data, resolve, command) # e.g. NAME=Test Projector def parse_response(byte_str) { - cmd: LOOKUP_COMMANDS[byte_str[0..4]], + cmd: LOOKUP_COMMANDS[byte_str[0..4]].to_s, param: byte_str[5..-1] } end From 4f996b247c72334e152f78450df78b0effa21082 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:27:31 +0800 Subject: [PATCH 1235/1752] pjlink/response: fx parsing --- modules/pjlink/pjlink.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index c5837ee9..d27b415d 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -128,8 +128,9 @@ def received(data, resolve, command) # e.g. NAME=Test Projector def parse_response(byte_str) { - cmd: LOOKUP_COMMANDS[byte_str[0..4]].to_s, - param: byte_str[5..-1] + split = byte_str.split('=') + cmd: LOOKUP_COMMANDS[split[0]].to_s, + param: LOOKUP_COMMANDS[split[1]].to_s, } end From 1838a2141562420e1c80cf6b3092111671e67061 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:28:09 +0800 Subject: [PATCH 1236/1752] pjlink/response: syntax --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index d27b415d..71124827 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -130,7 +130,7 @@ def parse_response(byte_str) { split = byte_str.split('=') cmd: LOOKUP_COMMANDS[split[0]].to_s, - param: LOOKUP_COMMANDS[split[1]].to_s, + param: LOOKUP_COMMANDS[split[1]].to_s } end From b3f61a402a6d343be32d1bf2b8e5806c4b65840d Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:28:48 +0800 Subject: [PATCH 1237/1752] pjlink/response: syntax --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 71124827..a90e4bb7 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -127,8 +127,8 @@ def received(data, resolve, command) # 0123456789 # e.g. NAME=Test Projector def parse_response(byte_str) + split = byte_str.split('=') { - split = byte_str.split('=') cmd: LOOKUP_COMMANDS[split[0]].to_s, param: LOOKUP_COMMANDS[split[1]].to_s } From aa1078657f996384348a4abfe09c75c40eb0b6ce Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:32:04 +0800 Subject: [PATCH 1238/1752] pjlink/response: logic --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index a90e4bb7..fb5af591 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -130,7 +130,7 @@ def parse_response(byte_str) split = byte_str.split('=') { cmd: LOOKUP_COMMANDS[split[0]].to_s, - param: LOOKUP_COMMANDS[split[1]].to_s + param: split[1] } end From b0f4e78a96b0a44dae0fe36ca53fc71784a6c879 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:45:34 +0800 Subject: [PATCH 1239/1752] pjlink: expose query commands; support warming/cooling power status --- modules/pjlink/pjlink.rb | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index fb5af591..fd62cd80 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -75,17 +75,14 @@ def audio_unmute end def poll - power? do - if self[:power] - input? - mute? - volume? - end - end + power? + if self[:power] { + input? + mute? + volume? + } end - protected - COMMANDS = { power: 'POWR', mute: 'AVMT', @@ -97,11 +94,14 @@ def poll LOOKUP_COMMANDS = COMMANDS.invert COMMANDS.each do |name, pj_cmd| - define_method "#{name}?" do + define_method :"#{name}?" do do_query pj_cmd end end + + protected + def do_query(command, **options) cmd = "%1#{command} ?\x0D" send(cmd, options) @@ -137,7 +137,19 @@ def parse_response(byte_str) # Update module state based on device feedback def update_status(cmd, param) case cmd.to_sym - when :power, :mute, :audio_mute, :video_mute + when :power, + case param + when 0 + self[:power] = false + self[:power_status] = 'off' + when 1 + self[:power] = true + self[:power_status] = 'on' + when 2 + self[:power_status] = 'cooling' + when 3 + self[:power_status] = 'warming' + when :mute, :audio_mute, :video_mute self[cmd] = param == '1' when :volume self[:volume] = param.to_i From e22e2e79f2222b2065a3b2d0a2122ea38a6bbd7f Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:46:55 +0800 Subject: [PATCH 1240/1752] pjlink/response: syntax --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index fd62cd80..4d7ec6cb 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -99,7 +99,6 @@ def poll end end - protected def do_query(command, **options) @@ -149,6 +148,7 @@ def update_status(cmd, param) self[:power_status] = 'cooling' when 3 self[:power_status] = 'warming' + end when :mute, :audio_mute, :video_mute self[cmd] = param == '1' when :volume From 49cad6cb276b3358fe0fa76e085f6ae873cc8d0f Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:48:25 +0800 Subject: [PATCH 1241/1752] pjlink/query: fix syntax --- modules/pjlink/pjlink.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 4d7ec6cb..365541df 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -75,12 +75,13 @@ def audio_unmute end def poll - power? - if self[:power] { - input? - mute? - volume? - } + power? do + if self[:power] + input? + mute? + volume? + end + end end COMMANDS = { From 4b4cf2ba5d78fd2e53a64c35ff8c9602e3e06b57 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 18:57:26 +0800 Subject: [PATCH 1242/1752] pjlink/input: fix syntax --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 365541df..a4e04a68 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -45,7 +45,7 @@ def disconnected def switch_to(input) logger.debug { "requesting to switch to: #{input}" } - do_send(COMMANDS[:input], INPUTS[input]) + do_send(COMMANDS[:input], INPUTS[input.to_sym]) input? end From 8480a9d365c05585e2fba7994da237d06b99beb1 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:04:40 +0800 Subject: [PATCH 1243/1752] pjlink/poll: remove volume (doesn't exist); add debug --- modules/pjlink/pjlink.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index a4e04a68..5cfb582e 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -79,7 +79,8 @@ def poll if self[:power] input? mute? - volume? + lamp? + error_status? end end end @@ -103,12 +104,14 @@ def poll protected def do_query(command, **options) - cmd = "%1#{command} ?\x0D" send(cmd, options) + cmd = "%1#{command} ?\x0D" + logger.debug "sending query to projector: #{cmd}" end def do_send(command, parameter, **options) cmd = "%1#{command} #{parameter}\x0D" + logger.debug "sending command to projector: #{cmd}" send(cmd, options) end From 5c96c6e670a535430252618e6b1f58f0cc116098 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:05:49 +0800 Subject: [PATCH 1244/1752] pjlink/query: syntax --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 5cfb582e..d0f270b1 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -104,9 +104,9 @@ def poll protected def do_query(command, **options) - send(cmd, options) cmd = "%1#{command} ?\x0D" logger.debug "sending query to projector: #{cmd}" + send(cmd, options) end def do_send(command, parameter, **options) From f6903ac90e6983b30d51f2ce08bfa0f2361de9d5 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:10:45 +0800 Subject: [PATCH 1245/1752] pjlink/response/power: fix parsing --- modules/pjlink/pjlink.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index d0f270b1..0aa463e8 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -142,15 +142,15 @@ def update_status(cmd, param) case cmd.to_sym when :power, case param - when 0 + when '0' self[:power] = false self[:power_status] = 'off' - when 1 + when '1' self[:power] = true self[:power_status] = 'on' - when 2 + when '2' self[:power_status] = 'cooling' - when 3 + when '3' self[:power_status] = 'warming' end when :mute, :audio_mute, :video_mute From 345544dac0cebf938be14543b3b08acc805f49c6 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:29:08 +0800 Subject: [PATCH 1246/1752] pjlink/response/power: syntax --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 0aa463e8..15b2a8e5 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -140,7 +140,7 @@ def parse_response(byte_str) # Update module state based on device feedback def update_status(cmd, param) case cmd.to_sym - when :power, + when :power case param when '0' self[:power] = false From e83f4fff07ec24a6974e22d13865626fb49cbafc Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:30:19 +0800 Subject: [PATCH 1247/1752] pjlin/on_load: remove volume range --- modules/pjlink/pjlink.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 15b2a8e5..c2f48b36 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -17,8 +17,6 @@ class Pjlink::Pjlink tokenize indicator: "%1", delimiter: "\x0D" def on_load - self[:volume_min] = 0 - self[:volume_max] = 100 end def connected From 4a91116f609d4e35139324fa3c73d2ccb0f82bba Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:33:04 +0800 Subject: [PATCH 1248/1752] pjlink/poll: all status even when powered off --- modules/pjlink/pjlink.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index c2f48b36..79641cba 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -73,14 +73,11 @@ def audio_unmute end def poll - power? do - if self[:power] - input? - mute? - lamp? - error_status? - end - end + power? + input? + mute? + lamp? + error_status? end COMMANDS = { From f5d3446d653348f2d308944ca98104ef835ad68e Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:34:07 +0800 Subject: [PATCH 1249/1752] pjlink/poll: every 10s, not 5s --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 79641cba..57c77453 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -21,7 +21,7 @@ def on_load def connected poll - schedule.every('5s') { poll } + schedule.every('10s') { poll } end def disconnected From 191a02868befbb96ec0e485ba6409e3aa71601b4 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:51:12 +0800 Subject: [PATCH 1250/1752] pjlink/response: add error hash --- modules/pjlink/pjlink.rb | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 57c77453..24d98ce4 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -151,9 +151,28 @@ def update_status(cmd, param) when :mute, :audio_mute, :video_mute self[cmd] = param == '1' when :volume - self[:volume] = param.to_i + self[cmd] = param.to_i when :input - self[:input] = LOOKUP_INPUTS[param] + self[cmd] = LOOKUP_INPUTS[param] + when :lamp + split = param.split(' ') + self[:lamp_hours] = split[0].to_i + self[:lamp_status] = split[1] == '1' ? 'on' : 'off' + when :error_status + E = { + 0: :none, + 1: :warning, + 2: :error + } + fan, lamp, temperature, cover_open, filter, other = param.scan /\w/ + self[:errors] = { + fan: E[fan], + lamp: E[lamp], + temperature: E[temperature], + cover_open: E[cover_open], + filter: E[filter], + other: E[other] + } end end end From 3bb0a810cbe9ff4c8ea8466b821d0012d58599c0 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:53:33 +0800 Subject: [PATCH 1251/1752] pjlink/response/errors: syntax --- modules/pjlink/pjlink.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 24d98ce4..31730d75 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -160,9 +160,9 @@ def update_status(cmd, param) self[:lamp_status] = split[1] == '1' ? 'on' : 'off' when :error_status E = { - 0: :none, - 1: :warning, - 2: :error + '0': :none, + '1': :warning, + '2': :error } fan, lamp, temperature, cover_open, filter, other = param.scan /\w/ self[:errors] = { From 5ac3795fd6500df6d693a404e925a4cb6822d45e Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:55:39 +0800 Subject: [PATCH 1252/1752] pjlink/response/errors: syntax --- modules/pjlink/pjlink.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 31730d75..3c76a00c 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -159,19 +159,19 @@ def update_status(cmd, param) self[:lamp_hours] = split[0].to_i self[:lamp_status] = split[1] == '1' ? 'on' : 'off' when :error_status - E = { + e = { '0': :none, '1': :warning, '2': :error } fan, lamp, temperature, cover_open, filter, other = param.scan /\w/ self[:errors] = { - fan: E[fan], - lamp: E[lamp], - temperature: E[temperature], - cover_open: E[cover_open], - filter: E[filter], - other: E[other] + fan: e[fan], + lamp: e[lamp], + temperature: e[temperature], + cover_open: e[cover_open], + filter: e[filter], + other: e[other] } end end From 37e6723df625f76cff6b4db47b22cb0e70a80822 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 19:58:30 +0800 Subject: [PATCH 1253/1752] pjlink/response/errors: syntax --- modules/pjlink/pjlink.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 3c76a00c..34d33a06 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -160,9 +160,9 @@ def update_status(cmd, param) self[:lamp_status] = split[1] == '1' ? 'on' : 'off' when :error_status e = { - '0': :none, - '1': :warning, - '2': :error + "0": :none, + "1": :warning, + "2": :error } fan, lamp, temperature, cover_open, filter, other = param.scan /\w/ self[:errors] = { From db25c1c33154c53b2cb18713a192e8997c79934c Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 17 Apr 2019 20:02:04 +0800 Subject: [PATCH 1254/1752] pjlink/response/errors: fix hash lookup --- modules/pjlink/pjlink.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 34d33a06..5d97c101 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -166,12 +166,12 @@ def update_status(cmd, param) } fan, lamp, temperature, cover_open, filter, other = param.scan /\w/ self[:errors] = { - fan: e[fan], - lamp: e[lamp], - temperature: e[temperature], - cover_open: e[cover_open], - filter: e[filter], - other: e[other] + fan: e[fan.to_sym], + lamp: e[lamp.to_sym], + temperature: e[temperature.to_sym], + cover_open: e[cover_open.to_sym], + filter: e[filter.to_sym], + other: e[other.to_sym] } end end From d16fd17fbba92d034ce08c71dfa9905a46443fda Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 18 Apr 2019 12:43:31 +0800 Subject: [PATCH 1255/1752] pjlink/response/errors: neater mapping --- modules/pjlink/pjlink.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 5d97c101..bcc45484 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -164,14 +164,14 @@ def update_status(cmd, param) "1": :warning, "2": :error } - fan, lamp, temperature, cover_open, filter, other = param.scan /\w/ + fan, lamp, temperature, cover_open, filter, other = param.chars.map {|c| c.to_sym} self[:errors] = { - fan: e[fan.to_sym], - lamp: e[lamp.to_sym], - temperature: e[temperature.to_sym], - cover_open: e[cover_open.to_sym], - filter: e[filter.to_sym], - other: e[other.to_sym] + fan: e[fan], + lamp: e[lamp], + temperature: e[temperature], + cover_open: e[cover_open], + filter: e[filter], + other: e[other] } end end From b94285c34958edaf138d7708bb6b1ef527bff029 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 18 Apr 2019 12:48:13 +0800 Subject: [PATCH 1256/1752] pjlink/response: param compare to integers --- modules/pjlink/pjlink.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index bcc45484..7c30ec80 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -149,7 +149,7 @@ def update_status(cmd, param) self[:power_status] = 'warming' end when :mute, :audio_mute, :video_mute - self[cmd] = param == '1' + self[cmd] = param.to_i == 1 when :volume self[cmd] = param.to_i when :input @@ -157,7 +157,7 @@ def update_status(cmd, param) when :lamp split = param.split(' ') self[:lamp_hours] = split[0].to_i - self[:lamp_status] = split[1] == '1' ? 'on' : 'off' + self[:lamp_status] = split[1].to_i == 1 ? 'on' : 'off' when :error_status e = { "0": :none, From e251d3e5eb47fd640a1c6f959ac826d72d013353 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 18 Apr 2019 15:38:11 +0800 Subject: [PATCH 1257/1752] office: fetch after create, so panel shows new booking immediately --- modules/aca/office_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 42acf278..fd6cd053 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -459,6 +459,7 @@ def create_meeting(options) logger.debug { "successfully created booking: #{id}" } + fetch_bookings "Ok" end From 0bbe67c275f0e3c916472d2044ae9e48e30c55ac Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 18 Apr 2019 16:10:52 +0800 Subject: [PATCH 1258/1752] pjlink/poll: only poll others if power=true --- modules/pjlink/pjlink.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 7c30ec80..bcb305d8 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -73,11 +73,14 @@ def audio_unmute end def poll - power? - input? - mute? - lamp? - error_status? + power?.finally do + if self[:power] { + input? + mute? + lamp? + error_status? + } + end end COMMANDS = { From a0a4c5b62ef1a9172744f03afd7987abd6d53d62 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 18 Apr 2019 16:32:39 +0800 Subject: [PATCH 1259/1752] pjlink/poll: fix syntax --- modules/pjlink/pjlink.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index bcb305d8..1744ab25 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -74,12 +74,12 @@ def audio_unmute def poll power?.finally do - if self[:power] { + if self[:power] then input? mute? lamp? error_status? - } + end end end From e1e79d98df8236b9ec04eeefca2659ff524b1a3e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 23 Apr 2019 11:29:30 +1000 Subject: [PATCH 1260/1752] [o365 panel] Add current user to booking creation to change mailbox used --- modules/aca/office_booking.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index fd6cd053..29d1bc20 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -459,7 +459,6 @@ def create_meeting(options) logger.debug { "successfully created booking: #{id}" } - fetch_bookings "Ok" end @@ -612,7 +611,7 @@ def make_office_booking(user_email: nil, subject: 'On the spot booking', room_em # Make the request # response = office_api.post(path: "#{domain}#{endpoint}", body: booking_data, headers: headers).value - response = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: nil) + response = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: {email: organizer, name: organizer}) STDERR.puts "BOOKING SIP CREATE RESPONSE:" STDERR.puts response.inspect STDERR.puts response['id'] From 43cee39a91e431174e5f839ce48995b6b77bdb4f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 23 Apr 2019 12:52:13 +1000 Subject: [PATCH 1261/1752] Add new fields --- lib/microsoft/office/contact.rb | 1 + lib/microsoft/office/user.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office/contact.rb b/lib/microsoft/office/contact.rb index 50ab6d2f..92d72265 100644 --- a/lib/microsoft/office/contact.rb +++ b/lib/microsoft/office/contact.rb @@ -4,6 +4,7 @@ class Microsoft::Officenew::Contact < Microsoft::Officenew::Model 'id' => 'id', 'title' => 'title', 'mobilePhone' => 'phone', + 'companyName' => 'organization', 'displayName' => 'name', 'personalNotes' => 'notes', 'emailAddresses' => { 0 => { 'address' => 'email' } } diff --git a/lib/microsoft/office/user.rb b/lib/microsoft/office/user.rb index 1d4c75c6..55c24ede 100644 --- a/lib/microsoft/office/user.rb +++ b/lib/microsoft/office/user.rb @@ -4,7 +4,8 @@ class Microsoft::Officenew::User < Microsoft::Officenew::Model 'id' => 'id', 'mobilePhone' => 'phone', 'displayName' => 'name', - 'mail' => 'email' + 'mail' => 'email', + 'jobTitle' => 'role' } NEW_FIELDS = {} From d9d42b36a69c8cc5bde0f8cf46dad2439fc43428 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 23 Apr 2019 23:10:00 +0800 Subject: [PATCH 1262/1752] pjlink: add power/input TARGETs --- modules/pjlink/pjlink.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 1744ab25..a2b6f173 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -43,11 +43,13 @@ def disconnected def switch_to(input) logger.debug { "requesting to switch to: #{input}" } + self[:input_target] = INPUTS[input.to_sym] do_send(COMMANDS[:input], INPUTS[input.to_sym]) input? end - def power(state, _ = nil) + def power(state = true, _ = nil) + self[:power_target] = state do_send(COMMANDS[:power], state ? '1' : '0') end @@ -80,6 +82,8 @@ def poll lamp? error_status? end + power(self[:power_target]) if self[:power_target] && (self[:power] != self[:power_target]) + switch_to(self[:input_target]) if self[:input_target] && (self[:input] != self[:input_target]) end end From 5e7b692430f897456cc6e31f8911548d7f802b2d Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 24 Apr 2019 13:16:47 +0800 Subject: [PATCH 1263/1752] pjlink/input_target: fix --- modules/pjlink/pjlink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index a2b6f173..4ff55dc6 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -43,7 +43,7 @@ def disconnected def switch_to(input) logger.debug { "requesting to switch to: #{input}" } - self[:input_target] = INPUTS[input.to_sym] + self[:input_target] = input.to_sym do_send(COMMANDS[:input], INPUTS[input.to_sym]) input? end From 35ade2698f5c9203a4c190666194317a34c8c5f9 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 24 Apr 2019 16:05:20 +1000 Subject: [PATCH 1264/1752] Add older room_id field to events --- lib/microsoft/office/event.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index 8f3e58a9..230dc662 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -18,6 +18,7 @@ class Microsoft::Officenew::Event < Microsoft::Officenew::Model { new_key: 'start_epoch', method: 'datestring_to_epoch', model_params:['start'] }, { new_key: 'end_epoch', method: 'datestring_to_epoch', model_params:['end'] }, { new_key: 'room_emails', method: 'set_room_id', model_params:['attendees'] }, + { new_key: 'room_id', method: 'set_room_id', model_params:['attendees'] }, # Remove this once frontend is updated { new_key: 'attendees', method: 'format_attendees', model_params:['attendees', 'organizer'] }, { new_key: 'organizer', method: 'set_organizer', model_params:['organizer'] }, { new_key: 'is_free', method: 'check_availability', model_params:['start', 'end'], passed_params: ['available_from', 'available_to'] } From 71a9f1bcd7186ff52306902fea816a22424ca65b Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 24 Apr 2019 16:14:50 +1000 Subject: [PATCH 1265/1752] Alias attendee visitor field to external --- lib/microsoft/office/event.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index 230dc662..91805231 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -92,6 +92,7 @@ def format_attendees(attendees, organizer) email: attendee_email, name: attendee['emailAddress']['name'], visitor: is_visitor, + external: is_visitor, organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize } new_attendees.push(attendee_object) if attendee['type'] != 'resource' From 02bbf36f7e10ad9ec1fd1f3a3a476175ef06a219 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 24 Apr 2019 17:25:26 +0800 Subject: [PATCH 1266/1752] pjlink: disable power_target. name commands --- modules/pjlink/pjlink.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 4ff55dc6..9be53ee1 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -44,31 +44,31 @@ def disconnected def switch_to(input) logger.debug { "requesting to switch to: #{input}" } self[:input_target] = input.to_sym - do_send(COMMANDS[:input], INPUTS[input.to_sym]) + do_send(COMMANDS[:input], INPUTS[input.to_sym], name: :input) input? end def power(state = true, _ = nil) - self[:power_target] = state - do_send(COMMANDS[:power], state ? '1' : '0') + #self[:power_target] = state + do_send(COMMANDS[:power], state ? '1' : '0', retries: 10, name: :power) end def mute(state = true) - do_send(COMMANDS[:mute], state ? '31' : '30') + do_send(COMMANDS[:mute], state ? '31' : '30', name: :mute) end def unmute mute false end def video_mute(state = true) - do_send(COMMANDS[:mute], state ? '11' : '10') + do_send(COMMANDS[:mute], state ? '11' : '10', name: :video_mute) end def video_unmute mute false end def audio_mute(state = true) - do_send(COMMANDS[:mute], state ? '21' : '20') + do_send(COMMANDS[:mute], state ? '21' : '20', name: :audio_mute) end def audio_unmute mute false @@ -82,7 +82,7 @@ def poll lamp? error_status? end - power(self[:power_target]) if self[:power_target] && (self[:power] != self[:power_target]) + #power(self[:power_target]) if self[:power_target] && (self[:power] != self[:power_target]) switch_to(self[:input_target]) if self[:input_target] && (self[:input] != self[:input_target]) end end From ecd65c10ab9e84cf8be375c321cecc00147209f9 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 25 Apr 2019 18:37:34 +0800 Subject: [PATCH 1267/1752] lib/office: add decline_meeting() it results in an email notification to the host, which delete_booking() does not do. --- lib/microsoft/office.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index efbcb71c..8295d6d7 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -456,6 +456,14 @@ def delete_booking(booking_id:, mailbox:) 200 end + # https://docs.microsoft.com/en-us/graph/api/event-decline?view=graph-rest-1.0 + def decline_meeting(booking_id:, mailbox:, comment: ) + endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}/decline" + comment = 'The booking was removed from this room as the host did not Start the meeting via the booking panel' if comment.nil? + request = graph_request(request_method: 'post', endpoint: endpoint, password: @delegated, data: {comment: comment}) + check_response(request) + 200 + end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_delete def delete_contact(contact_id:, mailbox:) From 65fa832b09b242072a6f956c0397508eb83955b4 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 25 Apr 2019 18:41:06 +0800 Subject: [PATCH 1268/1752] booking_panel/o365/delete: DECLINE instead of delete, resulting in an email notification --- modules/aca/office_booking.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 29d1bc20..c1d1fe27 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -123,6 +123,7 @@ def on_update self[:timeout] = setting(:timeout) self[:booking_cancel_timeout] = setting(:booking_cancel_timeout) + self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) self[:booking_controls] = setting(:booking_controls) self[:booking_catering] = setting(:booking_catering) self[:booking_hide_details] = setting(:booking_hide_details) @@ -630,7 +631,7 @@ def delete_ews_booking(delete_at) self[:today].each_with_index do |booking, i| booking_start_object = Time.parse(booking[:Start]) if delete_at_object.to_i == booking_start_object.to_i - response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_cancel_email_message]) if response == 200 count += 1 self[:today].delete(i) From f581e5af4b954121158cc9d977d42c360a5071eb Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 28 Apr 2019 09:13:22 +1000 Subject: [PATCH 1269/1752] (toshiba:e-series displays) provide hard power off options --- modules/toshiba/display/e_series.rb | 59 +++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/modules/toshiba/display/e_series.rb b/modules/toshiba/display/e_series.rb index bbbfbe8c..3efb1819 100644 --- a/modules/toshiba/display/e_series.rb +++ b/modules/toshiba/display/e_series.rb @@ -1,7 +1,7 @@ module Toshiba; end module Toshiba::Display; end -# Documentation: https://aca.im/driver_docs/Toshiba/ESeries1+RS232+v8.pdf +# Documentation: https://aca.im/driver_docs/Toshiba/ESeries1%20RS232%20v8.pdf class Toshiba::Display::ESeries include ::Orchestrator::Constants @@ -50,7 +50,9 @@ def disconnected def power(state) + self[:power_stable] = false promise = if is_affirmative?(state) + hard(On) if self[:hard_off] self[:power_target] = self[:power] = true do_send([0x6B, 0xD9, 0x01, 0x00, 0x20, 0x30, 0x01, 0x00], name: :power) else @@ -62,6 +64,42 @@ def power(state) promise end + def hard(state) + # Results in colour bars + if is_affirmative?(state) + self[:power_stable] = true + self[:hard_off] = false + schedule.in(30000) { + do_send([0xFE, 0xD2, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00]) + self[:power_stable] = false + self[:power_target] = false + } + do_send([0xBA, 0xD2, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00], name: :hard_off) + else + self[:power_stable] = false + self[:power_target] = false + self[:hard_off] = true + do_send([0x2A, 0xD3, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00], name: :hard_off) + end + end + + def monitor(state) + if is_affirmative?(state) + self[:monitor] = true + do_send([0x19, 0xD3, 0x02, 0x00, 0x00, 0x60, 0x02, 0x00], name: :monitor) + else + self[:monitor] = false + do_send([0x19, 0xD3, 0x02, 0x00, 0x00, 0x60, 0x01, 0x00], name: :monitor) + end + end + + def hard_off? + do_send([0x19, 0xD3, 0x02, 0x00, 0x00, 0x60, 0x00, 0x00], { + name: :hard_off_query, + priority: 0 + }) + end + def power?(options = {}, &block) options[:emit] = block if block_given? options[:name] = :power_query @@ -140,6 +178,7 @@ def volume(value, **options) def do_poll + hard_off? power? do if self[:power] audio_mute? @@ -190,7 +229,7 @@ def received(data, resolve, command) # Buffer data if required if command && (@buffer.length > 0 || data[0] == "\x1D") @buffer << data - + if @buffer.length >= 3 data = @buffer[0..2] @buffer = String.new(@buffer[3..-1]) @@ -210,7 +249,7 @@ def received(data, resolve, command) return :success elsif data.length == 1 # We have an unknown response code - return :abort + return :abort end if command @@ -232,6 +271,20 @@ def received(data, resolve, command) end when :power_query self[:power] = data == "\x1D\0\1" + if !self[:power_stable] && self[:power] != self[:power_target] + power(self[:power_target]) + else + self[:power_stable] = true + end + when :hard_off_query + # Power false == hard off true + self[:hard_off] = data == "\x1D\0\0" + self[:power] = !self[:hard_off] + if !self[:power_stable] && self[:power] != self[:power_target] + power(self[:power_target]) + else + self[:power_stable] = true + end end end From de96f6e2321a9e1cca3520ea5d6c314333e42304 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 29 Apr 2019 11:44:50 +1000 Subject: [PATCH 1270/1752] (toshiba:eseries display) disable power check displays always report that they are offline --- modules/toshiba/display/e_series.rb | 30 ++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/modules/toshiba/display/e_series.rb b/modules/toshiba/display/e_series.rb index 3efb1819..e8a2ac19 100644 --- a/modules/toshiba/display/e_series.rb +++ b/modules/toshiba/display/e_series.rb @@ -69,10 +69,10 @@ def hard(state) if is_affirmative?(state) self[:power_stable] = true self[:hard_off] = false - schedule.in(30000) { + schedule.in(50000) { do_send([0xFE, 0xD2, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00]) self[:power_stable] = false - self[:power_target] = false + self[:power_target] = true } do_send([0xBA, 0xD2, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00], name: :hard_off) else @@ -83,6 +83,10 @@ def hard(state) end end + def remove_colour_bars + do_send([0xFE, 0xD2, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00]) + end + def monitor(state) if is_affirmative?(state) self[:monitor] = true @@ -178,7 +182,6 @@ def volume(value, **options) def do_poll - hard_off? power? do if self[:power] audio_mute? @@ -272,26 +275,27 @@ def received(data, resolve, command) when :power_query self[:power] = data == "\x1D\0\1" if !self[:power_stable] && self[:power] != self[:power_target] - power(self[:power_target]) + # We won't play around with forced state as feedback is too unreliable + # power(self[:power_target]) else self[:power_stable] = true end - when :hard_off_query + # Toshibas report this as always being off. So need to rely on internal state + #when :hard_off_query # Power false == hard off true - self[:hard_off] = data == "\x1D\0\0" - self[:power] = !self[:hard_off] - if !self[:power_stable] && self[:power] != self[:power_target] - power(self[:power_target]) - else - self[:power_stable] = true - end + #self[:hard_off] = data == "\x1D\0\0" + #self[:power] = !self[:hard_off] + #if !self[:power_stable] && self[:power] != self[:power_target] + # power(self[:power_target]) + #else + # self[:power_stable] = true + #end end end :success end - PREFIX = [0xBE, 0xEF, 0x03, 0x06, 0x00] def do_send(cmd, options = {}) data = PREFIX + cmd From 235d515891df1778b79dbda8b0793860180a5a04 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 9 May 2019 11:06:15 +1000 Subject: [PATCH 1271/1752] (samsung:display) add schedule support for powering on and off So that wake on lan is not required --- modules/samsung/displays/md_series.rb | 37 ++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 7b4386af..8708f4f3 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -158,7 +158,33 @@ def serial_number? # 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 + end + + def set_timer + # set the time on the display + time_cmd = 0xA7 + time_request = [] + 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 + # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 40 input HDMI holiday apply + custom_mdc "A4", "03-2D-01 01 03-1E-01 01 01 80 01 80 28 21 01" + end INPUTS = { :vga => 0x14, # pc in manual @@ -416,15 +442,14 @@ def received(response, resolve, command) else logger.debug "Samsung responded with ACK: #{value}" end - :success + byte_to_hex(response) when :nak - logger.debug { "Samsung responded with NAK: #{array_to_str(Array(value))}" } + logger.warn "Samsung responded with NAK: #{byte_to_hex(Array(value))}" :failed # Failed response - else - logger.debug "Samsung aborted with: #{byte_to_hex(array_to_str(value))}" - :abort # unknown result + logger.warn "Samsung aborted with: #{byte_to_hex(Array(value))}" + byte_to_hex(response) end end From 009d56ba387ffb69930e9527e5611de44bfe3fa7 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 9 May 2019 12:20:32 +1000 Subject: [PATCH 1272/1752] (aca:meeting) add support for touch panel support emails with context of room, current booking and booking owner --- modules/aca/exchange_booking.rb | 15 ++++++++++++++ modules/aca/meeting_room.rb | 36 ++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index bb26ad31..684c6496 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -525,6 +525,21 @@ def extend_meeting end end + def send_email(title, body, to) + task { + cli = Viewpoint::EWSClient.new(*@ews_creds) + opts = {} + + if @use_act_as + opts[:act_as] = @ews_room if @ews_room + else + cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room + end + + cli.send_message subject: title, body: body, to_recipients: to + } + end + protected diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index 40343aeb..9c391f0c 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -26,6 +26,7 @@ def on_update self[:hide_vc_sources] = setting(:hide_vc_sources) self[:mics_mutes] = setting(:mics_mutes) self[:doors] = setting(:doors) + self[:help_options] = setting(:help_options) @confidence_monitor = setting(:confidence_monitor) # Get any default settings @@ -244,6 +245,31 @@ def on_update vc_sources end + def request_help(issue) + now = Time.now.to_i + booking = system[:Bookings] + todays = booking[:today] + + current = nil + Time.zone = "UTC" + todays.each do |booking| + starting = Time.zone.parse(booking[:Start]).to_i + ending = Time.zone.parse(booking[:End]).to_i + if now >= starting && now < ending + current = booking + break; + end + end + + if current + message = "Issue in #{self[:name]}\n#{current[:owner]} requires help with #{issue}" + else + message = "Issue in #{self[:name]}\nUser requires help with #{issue}" + end + + # help_email: ["array.of@.email.addresses"] + booking.send_email("Issue in #{self[:name]}", message, setting(:help_email)) + end # # SOURCE SWITCHING @@ -513,7 +539,7 @@ def switch_mode(mode_name, from_join = false, booting: false) mode_outs = mode[:outputs] || {} difference = mode_outs.keys - default_outs.keys - if mode[:outputs_clobber] + if mode[:outputs_clobber] self[:outputs] = ActiveSupport::HashWithIndifferentAccess.new.deep_merge(mode_outs) else self[:outputs] = ActiveSupport::HashWithIndifferentAccess.new.deep_merge(mode_outs.merge(default_outs)) @@ -755,7 +781,7 @@ def shutdown_actual(scheduled_shutdown = false) end switch_mode(@defaults[:shutdown_mode]) if @defaults[:shutdown_mode] - + self[:vc_content_source] = nil shutdown_vc @@ -885,13 +911,13 @@ def shutdown_actual(scheduled_shutdown = false) # # MISC FUNCTIONS # - + def shutdown_vc vidconf = system[:VidConf] vidconf.call('disconnect') vc_mute true end - + def init_vc system[:VidConf].clear_search_results system[:VidConf].wake @@ -953,7 +979,7 @@ def select_camera(source, input, output = nil) inp = src[:input] out = src[:output] system[:Switcher].switch({inp => out}) if inp && out - + # Enable or disable Cisco Speakertrack for this camera speaker_track_setting = src[:auto_camera] # true/false/nil. When nil, no command is sent system[:VidConf].speaker_track(speaker_track_setting) unless speaker_track_setting.nil? From 7a7a51132d8d544178f1c3cee0b59b2c49b42c34 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 10 May 2019 14:36:27 +0800 Subject: [PATCH 1273/1752] pjlink: add `power_target_enabled` --- modules/pjlink/pjlink.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/pjlink/pjlink.rb b/modules/pjlink/pjlink.rb index 9be53ee1..c14ba923 100644 --- a/modules/pjlink/pjlink.rb +++ b/modules/pjlink/pjlink.rb @@ -19,6 +19,10 @@ class Pjlink::Pjlink def on_load end + def on_update + self[:power_target_enabled] = setting(:power_target_enabled) || true + end + def connected poll schedule.every('10s') { poll } @@ -49,7 +53,7 @@ def switch_to(input) end def power(state = true, _ = nil) - #self[:power_target] = state + self[:power_target] = state if self[:power_target_enabled] do_send(COMMANDS[:power], state ? '1' : '0', retries: 10, name: :power) end @@ -82,7 +86,7 @@ def poll lamp? error_status? end - #power(self[:power_target]) if self[:power_target] && (self[:power] != self[:power_target]) + power(self[:power_target]) if self[:power_target_enabled] && !self[:power_target].nil? && (self[:power] != self[:power_target]) switch_to(self[:input_target]) if self[:input_target] && (self[:input] != self[:input_target]) end end From 206a6e59bd6a451a9bf4f6ca91e16e96beca4673 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 14 May 2019 16:44:26 +1000 Subject: [PATCH 1274/1752] (messsage media:sms) add sms sending support --- modules/message_media/sms.rb | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 modules/message_media/sms.rb diff --git a/modules/message_media/sms.rb b/modules/message_media/sms.rb new file mode 100644 index 00000000..24045ea6 --- /dev/null +++ b/modules/message_media/sms.rb @@ -0,0 +1,45 @@ +module MessageMedia; end + +# Documentation: https://developers.messagemedia.com/code/messages-api-documentation/ + +class MessageMedia::SMS + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :service + descriptive_name 'MessageMedia SMS service' + generic_name :SMS + + # HTTP keepalive + keepalive false + + def on_load + on_update + end + + def on_update + # NOTE:: base URI https://api.messagemedia.com + @username = setting(:username) + @password = setting(:password) + end + + def sms(text, numbers) + text = text.to_s + numbers = Array(numbers).map do |number| + { + content: text, + destination_number: number.to_s, + format: 'SMS' + } + end + + post('/v1/messages', body: { + messages: numbers + }.to_json, headers: { + 'Authorization' => [@username, @password], + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + }) + end +end From ea8440bf5976a331bb561f2f06a919b205520229 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 14 May 2019 17:26:35 +1000 Subject: [PATCH 1275/1752] (messsage media:sms) add source number support --- modules/message_media/sms.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/message_media/sms.rb b/modules/message_media/sms.rb index 24045ea6..29800b6b 100644 --- a/modules/message_media/sms.rb +++ b/modules/message_media/sms.rb @@ -24,14 +24,19 @@ def on_update @password = setting(:password) end - def sms(text, numbers) + def sms(text, numbers, source = nil) text = text.to_s numbers = Array(numbers).map do |number| - { + message = { content: text, destination_number: number.to_s, format: 'SMS' } + if source + message[:source_number] = source.to_s + message[:source_number_type] = "ALPHANUMERIC" + end + message end post('/v1/messages', body: { @@ -42,4 +47,12 @@ def sms(text, numbers) 'Accept' => 'application/json' }) end + + def received(data, resolve, command) + if data.status == 202 + :success + else + :retry + end + end end From 9ec3924c7dc22738d013de31e47e8bf52ad7de68 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 May 2019 12:54:44 +1000 Subject: [PATCH 1276/1752] (samsung:display) configure volume to be 15 40 was too loud --- modules/samsung/displays/md_series.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index 8708f4f3..d2b9ed6d 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -182,8 +182,8 @@ def set_timer time_request << ampm do_send time_cmd, time_request - # on 03:45 am enabled off 03:30 am enabled on-everyday ignore manual off-everyday ignore manual volume 40 input HDMI holiday apply - custom_mdc "A4", "03-2D-01 01 03-1E-01 01 01 80 01 80 28 21 01" + # 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 01 03-1E-01 01 01 80 01 80 0f 21 01" end INPUTS = { From 5def11bbb693bea2a855e5be8496bc799549bd35 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 17 May 2019 08:31:51 +1000 Subject: [PATCH 1277/1752] (samsung:displays) provide options to disable power off timers --- modules/samsung/displays/md_series.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index d2b9ed6d..a6c5e1da 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -160,7 +160,7 @@ def custom_mdc (command, value = "") do_send(hex_to_byte(command).bytes[0], hex_to_byte(value).bytes) end - def set_timer + def set_timer(enable = true, volume = 0) # set the time on the display time_cmd = 0xA7 time_request = [] @@ -182,8 +182,11 @@ def set_timer time_request << ampm do_send time_cmd, time_request + + state = is_affirmative?(enabled) ? '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 01 03-1E-01 01 01 80 01 80 0f 21 01" + custom_mdc "A4", "03-2D-01 #{state} 03-1E-01 #{state} 01 80 01 80 #{vol} 21 01" end INPUTS = { From 6910788c4313dd3e31ae330fe61cc36bf88e58fe Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 19 May 2019 21:01:59 +1000 Subject: [PATCH 1278/1752] (extron:sw) add driver for Extron SW series switchers --- modules/extron/switcher/sw.rb | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 modules/extron/switcher/sw.rb diff --git a/modules/extron/switcher/sw.rb b/modules/extron/switcher/sw.rb new file mode 100644 index 00000000..dce9adeb --- /dev/null +++ b/modules/extron/switcher/sw.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +load File.expand_path('../base.rb', File.dirname(__FILE__)) + +module Extron::Switcher; end + +class Extron::Switcher::Sw < Extron::Base + descriptive_name 'Extron Switcher SW' + generic_name :Switcher + + def switch_to(input) + do_send "#{input}!" + end + + def mute_video(state = true) + val = is_affirmative?(state) ? 1 : 0 + do_send "#{val}B" + end + + def unmute_video + mute_video false + end + + def mute_audio(state = true) + val = is_affirmative?(state) ? 1 : 0 + do_send "#{val}Z" + end + + def unmute_audio + mute_audio false + end + + def received(data, resolve, command) + logger.debug { "Extron switcher sent #{data}" } + + if data =~ /Login/i + device_ready + else + response, _, param = data.partition(/(?=\d)/) + + case response.to_sym + when :In + self[:input] = param.to_i + when :Vmt + self[:video_muted] = param > '0' + when :Amt + self[:audio_muted] = param > '0' + when :Sig + param.split.each_with_index do |state, idx| + self[:"input_#{idx + 1}_sync"] = state == '1' + end + when :Hdcp + param.split.each_with_index do |state, idx| + self[:"input_#{idx + 1}_hdcp"] = state == '1' + end + when :E + code = param.to_i + logger.warn(ERROR[code] || "Unknown device error (#{code})") + return :failed + else + logger.info("Unhandled device response (#{data})") + end + end + + :success + end + + + ERRORS = { + 1 => 'Invalid input channel (out of range)', + 6 => 'Invalid input during auto-input switching', + 10 => 'Invalid command', + 13 => 'Invalid value (out of range)' + }.freeze +end From d0f869c9d989c1a63616f8fc6dc23b8bf59d1082 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 19 May 2019 21:26:43 +1000 Subject: [PATCH 1279/1752] (extron:sw) fix incorrect constant name --- modules/extron/switcher/sw.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/extron/switcher/sw.rb b/modules/extron/switcher/sw.rb index dce9adeb..8809b6eb 100644 --- a/modules/extron/switcher/sw.rb +++ b/modules/extron/switcher/sw.rb @@ -66,7 +66,7 @@ def received(data, resolve, command) end - ERRORS = { + ERROR = { 1 => 'Invalid input channel (out of range)', 6 => 'Invalid input during auto-input switching', 10 => 'Invalid command', From d2cebc8a987266204188006e7a9b5a932801b4c1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 19 May 2019 21:38:57 +1000 Subject: [PATCH 1280/1752] (extron:sw) query sync and hdcp on connect --- modules/extron/switcher/sw.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/extron/switcher/sw.rb b/modules/extron/switcher/sw.rb index 8809b6eb..06a135f8 100644 --- a/modules/extron/switcher/sw.rb +++ b/modules/extron/switcher/sw.rb @@ -30,6 +30,20 @@ def unmute_audio mute_audio false end + def query_sync + do_send "\eLS" + end + + def query_hdcp + do_send "\eHDCP" + end + + def connected + super + query_sync + query_hdcp + end + def received(data, resolve, command) logger.debug { "Extron switcher sent #{data}" } @@ -53,6 +67,8 @@ def received(data, resolve, command) param.split.each_with_index do |state, idx| self[:"input_#{idx + 1}_hdcp"] = state == '1' end + when :Ver + # Firmware query response from poll - nothing to do... when :E code = param.to_i logger.warn(ERROR[code] || "Unknown device error (#{code})") From c2ce8735293d70190237976f5dbd17a96667f8b2 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sun, 19 May 2019 21:51:38 +1000 Subject: [PATCH 1281/1752] (extron:sw) remove unsupported hdcp query, neaten up sync monitoring --- modules/extron/switcher/sw.rb | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/modules/extron/switcher/sw.rb b/modules/extron/switcher/sw.rb index 06a135f8..56f27ef4 100644 --- a/modules/extron/switcher/sw.rb +++ b/modules/extron/switcher/sw.rb @@ -34,14 +34,9 @@ def query_sync do_send "\eLS" end - def query_hdcp - do_send "\eHDCP" - end - def connected super query_sync - query_hdcp end def received(data, resolve, command) @@ -60,21 +55,19 @@ def received(data, resolve, command) when :Amt self[:audio_muted] = param > '0' when :Sig - param.split.each_with_index do |state, idx| + inputs, outputs = param.split('*') + inputs.split.each_with_index do |state, idx| self[:"input_#{idx + 1}_sync"] = state == '1' end - when :Hdcp - param.split.each_with_index do |state, idx| - self[:"input_#{idx + 1}_hdcp"] = state == '1' + outputs.split.each_with_index do |state, idx| + self[:"output_#{idx + 1}_sync"] = state == '1' end - when :Ver - # Firmware query response from poll - nothing to do... when :E code = param.to_i logger.warn(ERROR[code] || "Unknown device error (#{code})") return :failed else - logger.info("Unhandled device response (#{data})") + # Unhandled device response - safe to ignore end end From 4526f641c6cf8724a7dcc1e86b5158c3ebdb2506 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 20 May 2019 10:34:28 +1000 Subject: [PATCH 1282/1752] initial commit --- modules/mediasite/modules.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 modules/mediasite/modules.rb diff --git a/modules/mediasite/modules.rb b/modules/mediasite/modules.rb new file mode 100644 index 00000000..83997b4a --- /dev/null +++ b/modules/mediasite/modules.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'net/http' + +module Mediasite; end + +class Mediasite::Module + descriptive_name 'Mediasite' + generic_name :Recorder + implements :logic + + def on_load + on_update + end + + def on_update +=begin + ret = Net::HTTP.get(URI.parse('https://www.meethue.com/api/nupnp')) + parsed = JSON.parse(ret) # parse the JSON string into a usable hash table + ip_address = parsed[0]['internalipaddress'] + @url = "http://#{ip_address}/api/#{setting(:api_key)}/sensors/7" + logger.debug { "url is #{@url}" } +=end + end + +end From bf740f0da12f17281a3763528715ec8dbe5fdaaa Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 20 May 2019 15:01:23 +1000 Subject: [PATCH 1283/1752] (extron:sw) fix issue with parsing of async response --- modules/extron/switcher/sw.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/extron/switcher/sw.rb b/modules/extron/switcher/sw.rb index 56f27ef4..6300e2b6 100644 --- a/modules/extron/switcher/sw.rb +++ b/modules/extron/switcher/sw.rb @@ -47,7 +47,7 @@ def received(data, resolve, command) else response, _, param = data.partition(/(?=\d)/) - case response.to_sym + case response.strip.to_sym when :In self[:input] = param.to_i when :Vmt From 76dbee98d8cbb77736cfd73bc3bc95a3a55c2333 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 21 May 2019 10:27:35 +1000 Subject: [PATCH 1284/1752] (cisco:ce) chunk large multiline body commands --- .../cisco/collaboration_endpoint/room_os.rb | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/modules/cisco/collaboration_endpoint/room_os.rb b/modules/cisco/collaboration_endpoint/room_os.rb index 670b0ad4..282363e6 100644 --- a/modules/cisco/collaboration_endpoint/room_os.rb +++ b/modules/cisco/collaboration_endpoint/room_os.rb @@ -377,15 +377,25 @@ def init_connection def do_send(command, multiline_body = nil, **options) request_id = generate_request_uuid - if multiline_body - multiline_body += "\n" unless multiline_body.end_with? "\n" - multiline_body << ".\n" - end - - request = "#{command} | resultId=\"#{request_id}\"\n#{multiline_body}" + request = "#{command} | resultId=\"#{request_id}\"\n" logger.debug { "-> #{request}" } + if multiline_body + # Command header + send request, wait: false + + # Multiline body + # Note: somewhere in the comms stack outgoing commands appear to be + # truncated around 4Kb. + multiline_body.scan(/.{1,4000}/).each do |chunk| + send chunk, wait: false + end + + # Multiline body terminator + request = "\n.\n" + end + send request, **options do |response, defer, cmd| received response, defer, cmd do |json| if json[:ResultId] != request_id From 7b5bb34d7ec623e21a1a1970d9f579f9ac35ee48 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 21 May 2019 11:34:41 +1000 Subject: [PATCH 1285/1752] (samsung:display) fix variable name --- modules/samsung/displays/md_series.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index a6c5e1da..f62e72ab 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -183,7 +183,7 @@ def set_timer(enable = true, volume = 0) do_send time_cmd, time_request - state = is_affirmative?(enabled) ? '01' : '00' + 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" From 9341e0721e2f0e452dc600c2fb6bab62902ae4b7 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 21 May 2019 11:37:05 +1000 Subject: [PATCH 1286/1752] (aca:exchange bookings) don't delete meetings starting in the past Also if a deleted meeting is re-created then it is auto-accepted --- modules/aca/exchange_booking.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 684c6496..6ed45729 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -387,19 +387,17 @@ def start_meeting(meeting_ref) def cancel_meeting(start_time, reason = "timeout") task { if start_time.class == Integer - delete_ews_booking (start_time / 1000).to_i + start_time = (start_time / 1000).to_i + delete_ews_booking start_time else # Converts to time object regardless of start_time being string or time object - start_time = Time.parse(start_time.to_s) - delete_ews_booking start_time.to_i + start_time = Time.parse(start_time.to_s).to_i + delete_ews_booking start_time end }.then(proc { |count| logger.warn { "successfully removed #{count} bookings due to #{reason}" } - self[:last_meeting_started] = 0 - self[:meeting_pending] = 0 - self[:meeting_ending] = false - self[:meeting_pending_notice] = false + start_meeting(start_time * 1000) fetch_bookings true @@ -660,6 +658,12 @@ def make_ews_booking(user_email: nil, subject: 'On the spot booking', room_email def delete_ews_booking(delete_at) now = Time.now + timeout = delete_at + (self[:booking_cancel_timeout] || self[:timeout]) + 120 + if now.to_i > timeout + start_meeting(delete_at * 1000) + return 0 + end + if @timezone start = now.in_time_zone(@timezone).midnight ending = now.in_time_zone(@timezone).tomorrow.midnight From 9edbaf2eaea8b08ee3ca575429a4bc7fd34c2317 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 21 May 2019 11:42:21 +1000 Subject: [PATCH 1287/1752] add username/password settings and add basic auth --- modules/mediasite/modules.rb | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/modules/mediasite/modules.rb b/modules/mediasite/modules.rb index 83997b4a..538777ae 100644 --- a/modules/mediasite/modules.rb +++ b/modules/mediasite/modules.rb @@ -5,22 +5,26 @@ module Mediasite; end class Mediasite::Module - descriptive_name 'Mediasite' - generic_name :Recorder - implements :logic + descriptive_name 'Mediasite' + generic_name :Recorder + implements :logic - def on_load - on_update - end + default_settings({ + url: 'https://alex-dev.deakin.edu.au/Mediasite/', + username: 'acaprojects', + password: 'WtjtvB439cXdZ4Z3' + }) - def on_update -=begin - ret = Net::HTTP.get(URI.parse('https://www.meethue.com/api/nupnp')) - parsed = JSON.parse(ret) # parse the JSON string into a usable hash table - ip_address = parsed[0]['internalipaddress'] - @url = "http://#{ip_address}/api/#{setting(:api_key)}/sensors/7" - logger.debug { "url is #{@url}" } -=end - end + def on_load + on_update + end + + def on_update + uri = URI.parse(setting(:url)) + request = Net::HTTP::GET.new(URI.parse(uri)) + request.basic_auth(setting(:username), setting(:password)) + http = Net::HTTP.new(uri.host, uri.port) + response = http.request(request) + end end From 242b1f02dc940a26fd67c852308fc803f9ddf568 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 21 May 2019 12:45:09 +1000 Subject: [PATCH 1288/1752] (pexip:management api) init commit --- modules/pexip/management.rb | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 modules/pexip/management.rb diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb new file mode 100644 index 00000000..85707ee6 --- /dev/null +++ b/modules/pexip/management.rb @@ -0,0 +1,84 @@ +module Pexip; end + +# Documentation: https://docs.pexip.com/api_manage/api_configuration.htm#create_vmr + +class Pexip::Management + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :service + descriptive_name 'Pexip Management API' + generic_name :Meeting + + # HTTP keepalive + keepalive false + + def on_load + on_update + end + + def on_update + # NOTE:: base URI https://pexip.company.com + @username = setting(:username) + @password = setting(:password) + end + + MeetingTypes = ["conference", "lecture", "two_stage_dialing", "test_call"] + def new_meeting(name, type = "conference", pin: rand(9999), **options) + type = type.to_s.strip.downcase + raise "unknown meeting type" unless MeetingTypes.include?(type) + + post('/api/admin/configuration/v1/conference/', body: { + name: name.to_s, + service_type: type, + pin: pin.to_s.rjust(4, '0') + }.merge(options).to_json, headers: { + 'Authorization' => [@username, @password], + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + }) do |data| + if (200...300).include?(data.status) + get_meeting URI(data['location']).path + else + :retry + end + end + end + + def get_meeting(meeting) + meeting = "/api/admin/configuration/v1/conference/#{meeting}/" if !meeting.to_s.include?("/") + + get(meeting).path, headers: { + 'Authorization' => [@username, @password], + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + }) do |data| + case data.status + when (200...300) + JSON.parse(data.body, symbolize_names: true) + when 404 + :abort + else + :retry + end + end + end + + def end_meeting(meeting_id) + delete("/api/admin/configuration/v1/conference/#{meeting_id}/", headers: { + 'Authorization' => [@username, @password], + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + }) do |data| + case data.status + when (200...300) + :success + when 404 + :success + else + :retry + end + end + end +end From 066148165c081bea956d0a9453a61feddd8abc83 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 21 May 2019 12:46:55 +1000 Subject: [PATCH 1289/1752] (pexip:management api) allow meeting id or resource path --- modules/pexip/management.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 85707ee6..92f82653 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -47,7 +47,7 @@ def new_meeting(name, type = "conference", pin: rand(9999), **options) end def get_meeting(meeting) - meeting = "/api/admin/configuration/v1/conference/#{meeting}/" if !meeting.to_s.include?("/") + meeting = "/api/admin/configuration/v1/conference/#{meeting}/" unless meeting.to_s.include?("/") get(meeting).path, headers: { 'Authorization' => [@username, @password], @@ -65,8 +65,10 @@ def get_meeting(meeting) end end - def end_meeting(meeting_id) - delete("/api/admin/configuration/v1/conference/#{meeting_id}/", headers: { + def end_meeting(meeting) + meeting = "/api/admin/configuration/v1/conference/#{meeting}/" unless meeting.to_s.include?("/") + + delete(meeting, headers: { 'Authorization' => [@username, @password], 'Content-Type' => 'application/json', 'Accept' => 'application/json' From 2e5b34d4caee1b50473792d807a827ba9defd8ff Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 21 May 2019 14:03:22 +1000 Subject: [PATCH 1290/1752] add recorder states mapping --- modules/mediasite/modules.rb | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/modules/mediasite/modules.rb b/modules/mediasite/modules.rb index 538777ae..58fe281b 100644 --- a/modules/mediasite/modules.rb +++ b/modules/mediasite/modules.rb @@ -9,22 +9,49 @@ class Mediasite::Module generic_name :Recorder implements :logic - default_settings({ + default_settings( url: 'https://alex-dev.deakin.edu.au/Mediasite/', username: 'acaprojects', password: 'WtjtvB439cXdZ4Z3' - }) + ) def on_load - on_update end def on_update - uri = URI.parse(setting(:url)) + end + + def request(url) + uri = URI.parse(url) request = Net::HTTP::GET.new(URI.parse(uri)) request.basic_auth(setting(:username), setting(:password)) http = Net::HTTP.new(uri.host, uri.port) response = http.request(request) end + + # https://alex.deakin.edu.au/mediasite/api/v1/$metadata#Rooms + # GET /api/v1/Room + # GET /api/v1/Rooms('id') + def get_rooms + request(url + '/api/v1/Room') + end + + STATES = { + 'Unknown' => 'Offline', + 'Idle' => 'Idle', + 'Busy' => 'Recording', + 'RecordStart' => 'Recording', + 'Recording' => 'Recording', + 'RecordEnd' => 'Recording', + 'Pausing' => 'Recording', + 'Paused' => 'Recording', + 'Resuming' => 'Recording', + 'OpeningSession' => 'Recording', + 'ConfiguringDevices' => 'Idle' + }.freeze + + # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. + def state + end end From 7c865ef1348f76ebd8ce427102597317573aa70e Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 21 May 2019 15:24:34 +1000 Subject: [PATCH 1291/1752] add placeholders for required functions --- modules/mediasite/modules.rb | 55 ++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/modules/mediasite/modules.rb b/modules/mediasite/modules.rb index 58fe281b..400d90fc 100644 --- a/modules/mediasite/modules.rb +++ b/modules/mediasite/modules.rb @@ -12,7 +12,9 @@ class Mediasite::Module default_settings( url: 'https://alex-dev.deakin.edu.au/Mediasite/', username: 'acaprojects', - password: 'WtjtvB439cXdZ4Z3' + password: 'WtjtvB439cXdZ4Z3', + update_every: 1 + # actual_room_name: setting to override room name to search when they mediasite room names don't match up wtih backoffice system names ) def on_load @@ -20,23 +22,41 @@ def on_load end def on_update + schedule.clear + self[:room_name] = setting(:actual_room_name) || '' end - def request(url) + def start + schedule.every("#{setting(:update_every)}m") do + state + end + end + + def get_request(url) uri = URI.parse(url) request = Net::HTTP::GET.new(URI.parse(uri)) request.basic_auth(setting(:username), setting(:password)) http = Net::HTTP.new(uri.host, uri.port) - response = http.request(request) + http.request(request) + end + + def post_request(url) + uri = URI.parse(url) + request = Net::HTTP::POST.new(URI.parse(uri)) + request.basic_auth(setting(:username), setting(:password)) + http = Net::HTTP.new(uri.host, uri.port) + http.request(request) end # https://alex.deakin.edu.au/mediasite/api/v1/$metadata#Rooms # GET /api/v1/Room # GET /api/v1/Rooms('id') def get_rooms - request(url + '/api/v1/Room') + get_request(url + '/api/v1/Room') end + + # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. STATES = { 'Unknown' => 'Offline', 'Idle' => 'Idle', @@ -51,7 +71,30 @@ def get_rooms 'ConfiguringDevices' => 'Idle' }.freeze - # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. - def state + # GET /api/v1/Recorders('id')/Status + def state(id) + response = request(url + "/api/v1/Recorders('#{id}')/Status") + self[:previous_state] = self[:state] + self[:state] = STATES[response] + end + +=begin +POST /api/v1/CatchDevices('id')/Start +POST /api/v1/CatchDevices('id')/Stop +POST /api/v1/CatchDevices('id')/Pause +POST /api/v1/CatchDevices('id')/Resume + +POST /api/v1/Recorders('id')/Start +POST /api/v1/Recorders('id')/Stop +POST /api/v1/Recorders('id')/Pause +POST /api/v1/Recorders('id')/Resume +=end + def pause + end + + def resume + end + + def stop end end From 9bd236fc79c927586c037f2bb76b74078779fb7d Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 21 May 2019 15:48:40 +1000 Subject: [PATCH 1292/1752] add info for other required states --- modules/mediasite/modules.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/mediasite/modules.rb b/modules/mediasite/modules.rb index 400d90fc..16d19808 100644 --- a/modules/mediasite/modules.rb +++ b/modules/mediasite/modules.rb @@ -32,6 +32,9 @@ def start end end + def poll + end + def get_request(url) uri = URI.parse(url) request = Net::HTTP::GET.new(URI.parse(uri)) @@ -78,6 +81,18 @@ def state(id) self[:state] = STATES[response] end +=begin +GET /api/v1/Recorders('id')/CurrentPresentationMetadata +Metadata for the current recording including title, start datetime, and if a schedule is available, linked modules and presenter names. +Title - The title of the recording. +Presenters – A list of presenters associated with the recording. +Live – Boolean value indicating that it is also being live streamed. +Dual – Boolean indicating that 2 or more video inputs are being used. +GET /api/v1/Recorders('id')/TimeRemaining +Time Remaining – For scheduled recordings, a mechanism to show how long until the current recording completes. (Discussion with UX team required re whether they would prefer XXX seconds or mm:ss format.) +Basic volume level of current recording. This may be obtained either via the Mediasite API or via QSC. Further discussion is required to ensure an efficient implementation. Refer to Potential Constrains section below. +=end + =begin POST /api/v1/CatchDevices('id')/Start POST /api/v1/CatchDevices('id')/Stop From c8b12781524fe0b10ed946505963776219a8f767 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 21 May 2019 16:38:16 +1000 Subject: [PATCH 1293/1752] include orchestrator constants so driver can be discovered properly --- modules/mediasite/modules.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/mediasite/modules.rb b/modules/mediasite/modules.rb index 16d19808..9b7a861a 100644 --- a/modules/mediasite/modules.rb +++ b/modules/mediasite/modules.rb @@ -5,7 +5,9 @@ module Mediasite; end class Mediasite::Module - descriptive_name 'Mediasite' + include ::Orchestrator::Constants + + descriptive_name 'Mediasite Recorder' generic_name :Recorder implements :logic From 5463578921242f2bf8d05660199bb096f07dfad9 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 22 May 2019 12:59:43 +1000 Subject: [PATCH 1294/1752] (tracking) improve logging for temporary MAC addresses Also adds an extra check when assigning MAC addresses to a user to prevent temporary MAC address assignment --- lib/aca/tracking/switch_port.rb | 2 +- lib/aca/tracking/user_devices.rb | 5 +++++ modules/cisco/switch/meraki_snmp.rb | 2 +- modules/cisco/switch/snooping_catalyst.rb | 2 +- modules/cisco/switch/snooping_catalyst_snmp.rb | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/aca/tracking/switch_port.rb b/lib/aca/tracking/switch_port.rb index 92c55ec9..118e9e28 100644 --- a/lib/aca/tracking/switch_port.rb +++ b/lib/aca/tracking/switch_port.rb @@ -186,7 +186,7 @@ def disconnected(temporary: 0) # Block the MAC address from being discovered for a period of time begin - self.class.set("temporarily_block_mac-#{self.mac_address}", ttl: temporary) + self.class.set("temporarily_block_mac-#{mac}", ttl: (temporary * 2)) ::Aca::Tracking::UserDevices.with_mac(mac).each do |user| user.remove(mac) end diff --git a/lib/aca/tracking/user_devices.rb b/lib/aca/tracking/user_devices.rb index e347c217..ce985e6f 100644 --- a/lib/aca/tracking/user_devices.rb +++ b/lib/aca/tracking/user_devices.rb @@ -21,6 +21,11 @@ def add(mac) .reject { |u| u.id == self.id } .each { |u| u.remove(mac) } + if self.class.bucket.get("temporarily_block_mac-#{mac}", quiet: true) + remove(mac) + return + end + self.class.bucket.set("macuser-#{mac}", username) return if self.macs.include?(mac) diff --git a/modules/cisco/switch/meraki_snmp.rb b/modules/cisco/switch/meraki_snmp.rb index 2164ffdf..db9d66b0 100644 --- a/modules/cisco/switch/meraki_snmp.rb +++ b/modules/cisco/switch/meraki_snmp.rb @@ -437,7 +437,7 @@ def remove_lookup(interface) # Need to create a database entry for the MAC with a TTL mac = model.mac_address temporary = if (mac && @temporary.include?(mac[0..5])) - logger.debug { "removing temporary MAC for #{model.username} with #{model.mac_address} at #{model.desk_id}" } + logger.info { "removing temporary MAC for #{model.username} with #{model.mac_address} at #{model.desk_id}" } @polling_period else 0 diff --git a/modules/cisco/switch/snooping_catalyst.rb b/modules/cisco/switch/snooping_catalyst.rb index cea3a2c3..6f3eea14 100644 --- a/modules/cisco/switch/snooping_catalyst.rb +++ b/modules/cisco/switch/snooping_catalyst.rb @@ -336,7 +336,7 @@ def remove_lookup(interface) # Need to create a database entry for the MAC with a TTL mac = model.mac_address temporary = if (mac && @temporary.include?(mac[0..5])) - logger.debug { "removing temporary MAC for #{model.username} with #{model.mac_address} at #{model.desk_id}" } + logger.info { "removing temporary MAC for #{model.username} with #{model.mac_address} at #{model.desk_id}" } @polling_period else 0 diff --git a/modules/cisco/switch/snooping_catalyst_snmp.rb b/modules/cisco/switch/snooping_catalyst_snmp.rb index 77fb5baa..e7233459 100644 --- a/modules/cisco/switch/snooping_catalyst_snmp.rb +++ b/modules/cisco/switch/snooping_catalyst_snmp.rb @@ -410,7 +410,7 @@ def remove_lookup(interface) # Need to create a database entry for the MAC with a TTL mac = model.mac_address temporary = if (mac && @temporary.include?(mac[0..5])) - logger.debug { "removing temporary MAC for #{model.username} with #{model.mac_address} at #{model.desk_id}" } + logger.info { "removing temporary MAC for #{model.username} with #{model.mac_address} at #{model.desk_id}" } @polling_period else 0 From 9aeea0aa25393ec4b9db473afddef7268aa0e301 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 22 May 2019 14:07:35 +1000 Subject: [PATCH 1295/1752] add api key to http request --- modules/mediasite/modules.rb | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/modules/mediasite/modules.rb b/modules/mediasite/modules.rb index 9b7a861a..b1d18725 100644 --- a/modules/mediasite/modules.rb +++ b/modules/mediasite/modules.rb @@ -15,6 +15,7 @@ class Mediasite::Module url: 'https://alex-dev.deakin.edu.au/Mediasite/', username: 'acaprojects', password: 'WtjtvB439cXdZ4Z3', + api_key: '6c6f13e0-bab0-4b74-a3fb-1b1ee866ffb8', update_every: 1 # actual_room_name: setting to override room name to search when they mediasite room names don't match up wtih backoffice system names ) @@ -38,19 +39,25 @@ def poll end def get_request(url) - uri = URI.parse(url) - request = Net::HTTP::GET.new(URI.parse(uri)) - request.basic_auth(setting(:username), setting(:password)) - http = Net::HTTP.new(uri.host, uri.port) - http.request(request) + uri = URI('https://alex-dev.deakin.edu.au/Mediasite/api/v1/Rooms') + req = Net::HTTP::Get.new(uri) + req.basic_auth('acaprojects', 'WtjtvB439cXdZ4Z3') + req['sfapikey'] = api_key + http = Net::HTTP.new(uri.hostname, uri.port) + http.use_ssl = true + res = http.request(req) + res.body end def post_request(url) - uri = URI.parse(url) - request = Net::HTTP::POST.new(URI.parse(uri)) - request.basic_auth(setting(:username), setting(:password)) - http = Net::HTTP.new(uri.host, uri.port) - http.request(request) + uri = URI('https://alex-dev.deakin.edu.au/Mediasite/api/v1/Rooms') + req = Net::HTTP::Post.new(uri) + req.basic_auth('acaprojects', 'WtjtvB439cXdZ4Z3') + req['sfapikey'] = api_key + http = Net::HTTP.new(uri.hostname, uri.port) + http.use_ssl = true + res = http.request(req) + res.body end # https://alex.deakin.edu.au/mediasite/api/v1/$metadata#Rooms From a81b55aaf0a97db36d9fe958f021c86a86096cd0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 22 May 2019 14:10:07 +1000 Subject: [PATCH 1296/1752] (LDAP:users) extracts users from LDAP and provides methods for very quickly searching for someone --- modules/ldap/users.rb | 177 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 modules/ldap/users.rb diff --git a/modules/ldap/users.rb b/modules/ldap/users.rb new file mode 100644 index 00000000..1637cf17 --- /dev/null +++ b/modules/ldap/users.rb @@ -0,0 +1,177 @@ +require 'net/ldap' + +module LDAP; end +class LDAP::Users + include ::Orchestrator::Constants + + descriptive_name 'LDAP User Listings' + generic_name :Users + implements :logic + + # For encrypted auth use port: 636 + # using auth_method: simple_tls + default_settings({ + username: "cn=read-only-admin,dc=example,dc=com", + password: "password", + host: "ldap.forumsys.com", + port: 389, + auth_method: "simple", + tree_base: "dc=example,dc=com", + attributes: ['dn', 'givenName', 'sN', 'mail', 'memberOf', 'telephoneNumber'], + + # Fields that should be searchable + query: ['sN', 'givenName'], + + # Every day at 3am + fetch_every: "0 3 * * *" + }) + + def on_load + on_update + end + + def on_update + @username = setting(:username) + @password = setting(:password) + @host = setting(:host) + @port = setting(:port) + @auth_method = (setting(:auth_method) || :simple).to_sym + @tree_base = setting(:tree_base) + @attributes = setting(:attributes) + + @users ||= {} + @query = Array(setting(:query)).map { |attr| attr.to_s.downcase.to_sym } + @query.each { |attr| @users[attr] ||= [] } + + schedule.clear + cron = setting(:fetch_every) + if cron + schedule.cron(cron) { fetch_user_list } + + # Fetch the list of users of not otherwise known + schedule.in('30s') { fetch_user_list } if self[:user_count].nil? + end + end + + def find_user(query) + results = [] + @query.each do |attr| + array = @users[attr] + # binary search returning the index of the element + i = (0...array.size).bsearch { |i| array[i][attr].start_with?(query) } + next unless i + + # Run backwards looking for earlier matches + loop do + i -= 1 + element = array[i] + if element && element[attr].start_with?(query) + else + break + end + end + + # Add up to 20 matching results + count = 0 + loop do + i += 1 + element = array[i] + if element && element[attr].start_with?(query) + results << element + else + break + end + + count += 1 + break if count > 20 + end + end + + # Sort the results + attr = @query[0] + results.uniq.sort { |a, b| a[attr] <=> b[attr] } + end + + # 400_000 users equates to about 200MB of data + def fetch_user_list + return if @fetching + self[:fetching] = true + + users ||= {} + @query.each { |attr| users[attr] = [] } + + results = task do + ldap_con = Net::LDAP.new({ + host: @host, + port: @port, + auth: { + method: @auth_method, + username: @username, + password: @password + } + }) + op_filter = Net::LDAP::Filter.eq("objectClass", "person") + ldap_con.search({ + base: @tree_base, + filter: op_filter, + attributes: @attributes, + # Improve memory usage + return_result: false + }) do |entry| + logger.debug { "processing #{entry.inspect}" } + + # Grab the raw data + hash = entry.instance_variable_get(:@myhash) + + # Build a nice version of data + user = {} + hash.each do |key, value| + if value.is_a?(Array) && value.size == 1 + user[key] = value[0] + else + user[key] = value + end + end + + # Insert the user in a sorted array for lookup later + @query.each do |attr| + next unless user[attr] + user[attr] = user[attr].downcase + insort(attr, users[attr], user) + end + end + + nil + end + + # Wait for the users to be fetched + results.value + + @users = users + self[:user_count] = users[@query[0]].size + ensure + self[:fetching] = false + end + + private + + def insort(attr, array, item, lo = 0, hi = array.size) + index = bisect_right(attr, array, item, lo, hi) + array.insert(index, item) + end + + def bisect_right(attr, array, item, lo = 0, hi = array.size) + raise ArgumentError, "lo must be non-negative" if lo < 0 + + while lo < hi + mid = (lo + hi) / 2 + if item[attr] < array[mid][attr] + hi = mid + else + lo = mid + 1 + end + end + + lo + end +end From ae3f37f47f5ff47ac97d6354157eabc07baa7bb9 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 22 May 2019 15:12:11 +1000 Subject: [PATCH 1297/1752] (ldap:users) add encryption support --- modules/ldap/users.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/ldap/users.rb b/modules/ldap/users.rb index 1637cf17..ef827845 100644 --- a/modules/ldap/users.rb +++ b/modules/ldap/users.rb @@ -16,6 +16,7 @@ class LDAP::Users host: "ldap.forumsys.com", port: 389, auth_method: "simple", + encryption: nil, tree_base: "dc=example,dc=com", attributes: ['dn', 'givenName', 'sN', 'mail', 'memberOf', 'telephoneNumber'], @@ -36,6 +37,7 @@ def on_update @host = setting(:host) @port = setting(:port) @auth_method = (setting(:auth_method) || :simple).to_sym + @encryption = setting(:encryption) @tree_base = setting(:tree_base) @attributes = setting(:attributes) @@ -101,7 +103,8 @@ def fetch_user_list @query.each { |attr| users[attr] = [] } results = task do - ldap_con = Net::LDAP.new({ + opts = { + force_no_page: true, host: @host, port: @port, auth: { @@ -109,7 +112,18 @@ def fetch_user_list username: @username, password: @password } - }) + } + + if @encryption + opts[:encryption] = { + method: @encryption.to_sym, + tls_options: { + verify_mode: OpenSSL::SSL::VERIFY_NONE + } + } + end + + ldap_con = Net::LDAP.new(opts) op_filter = Net::LDAP::Filter.eq("objectClass", "person") ldap_con.search({ base: @tree_base, From 91459d4a6eed67fd70a0eee8df250859bb4fa23e Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 22 May 2019 15:57:27 +1000 Subject: [PATCH 1298/1752] add code to get system/room name --- modules/mediasite/modules.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/mediasite/modules.rb b/modules/mediasite/modules.rb index b1d18725..6b7634d4 100644 --- a/modules/mediasite/modules.rb +++ b/modules/mediasite/modules.rb @@ -13,8 +13,8 @@ class Mediasite::Module default_settings( url: 'https://alex-dev.deakin.edu.au/Mediasite/', - username: 'acaprojects', - password: 'WtjtvB439cXdZ4Z3', + username: 'testapi', + password: 'jJ6nP28PE8rr', api_key: '6c6f13e0-bab0-4b74-a3fb-1b1ee866ffb8', update_every: 1 # actual_room_name: setting to override room name to search when they mediasite room names don't match up wtih backoffice system names @@ -26,7 +26,7 @@ def on_load def on_update schedule.clear - self[:room_name] = setting(:actual_room_name) || '' + self[:room_name] = setting(:actual_room_name) || system.name end def start @@ -67,7 +67,6 @@ def get_rooms get_request(url + '/api/v1/Room') end - # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. STATES = { 'Unknown' => 'Offline', From 6c0d203b7daf8058d38c10e6d0d2e5b48d5dd2b4 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 22 May 2019 16:06:57 +1000 Subject: [PATCH 1299/1752] rename modules.rb to module.rb --- modules/mediasite/{modules.rb => module.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/mediasite/{modules.rb => module.rb} (100%) diff --git a/modules/mediasite/modules.rb b/modules/mediasite/module.rb similarity index 100% rename from modules/mediasite/modules.rb rename to modules/mediasite/module.rb From f4a0289910e7a79e8ea298c8e3e5fe9eac8bdb8a Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 22 May 2019 16:37:20 +1000 Subject: [PATCH 1300/1752] add functionality to get device for the room --- modules/mediasite/module.rb | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 6b7634d4..64dcb121 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -26,7 +26,20 @@ def on_load def on_update schedule.clear - self[:room_name] = setting(:actual_room_name) || system.name + self[:room_name] = room_name + end + + def room_name + setting(:actual_room_name) || system.name + end + + def get_device + res = get_request('api/v1/Rooms?$top=1000') + res['value'].each { |room| + if room['name'] == room_name + return room['DeviceConfigurations']['DeviceId'] + end + } end def start @@ -39,25 +52,23 @@ def poll end def get_request(url) - uri = URI('https://alex-dev.deakin.edu.au/Mediasite/api/v1/Rooms') + uri = URI(setting(:url) + url) req = Net::HTTP::Get.new(uri) req.basic_auth('acaprojects', 'WtjtvB439cXdZ4Z3') req['sfapikey'] = api_key http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true - res = http.request(req) - res.body + http.request(req).body end def post_request(url) - uri = URI('https://alex-dev.deakin.edu.au/Mediasite/api/v1/Rooms') + uri = URI(setting(:url) + url) req = Net::HTTP::Post.new(uri) req.basic_auth('acaprojects', 'WtjtvB439cXdZ4Z3') req['sfapikey'] = api_key http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true - res = http.request(req) - res.body + http.request(req).body end # https://alex.deakin.edu.au/mediasite/api/v1/$metadata#Rooms From ac8740ed2ec4a06519a0b79290e48946b8c652b8 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 22 May 2019 17:02:38 +1000 Subject: [PATCH 1301/1752] add post requests for pause, resume and stop --- modules/mediasite/module.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 64dcb121..a073e39a 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -34,10 +34,12 @@ def room_name end def get_device + # Use $top=1000 to ensure that all rooms are returned from the api res = get_request('api/v1/Rooms?$top=1000') res['value'].each { |room| if room['name'] == room_name - return room['DeviceConfigurations']['DeviceId'] + self[:device_id] = room['DeviceConfigurations']['DeviceId'] + break end } end @@ -124,11 +126,20 @@ def state(id) POST /api/v1/Recorders('id')/Resume =end def pause + if self[:device_id] + post_request("/api/v1/Recorders('#{self[:device_id]}')/Pause") + end end def resume + if self[:device_id] + post_request("/api/v1/Recorders('#{self[:device_id]}')/Resume") + end end def stop + if self[:device_id] + post_request("/api/v1/Recorders('#{self[:device_id]}')/Stop") + end end end From 921d3c7e32f0823d6fde17f71d126ffa0d350ec8 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 10:42:42 +1000 Subject: [PATCH 1302/1752] add api_key as a setting --- modules/mediasite/module.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index a073e39a..0c44a2f1 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -57,7 +57,7 @@ def get_request(url) uri = URI(setting(:url) + url) req = Net::HTTP::Get.new(uri) req.basic_auth('acaprojects', 'WtjtvB439cXdZ4Z3') - req['sfapikey'] = api_key + req['sfapikey'] = setting(:api_key) http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true http.request(req).body @@ -67,19 +67,12 @@ def post_request(url) uri = URI(setting(:url) + url) req = Net::HTTP::Post.new(uri) req.basic_auth('acaprojects', 'WtjtvB439cXdZ4Z3') - req['sfapikey'] = api_key + req['sfapikey'] = setting(:api_key) http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true http.request(req).body end - # https://alex.deakin.edu.au/mediasite/api/v1/$metadata#Rooms - # GET /api/v1/Room - # GET /api/v1/Rooms('id') - def get_rooms - get_request(url + '/api/v1/Room') - end - # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. STATES = { 'Unknown' => 'Offline', @@ -97,7 +90,7 @@ def get_rooms # GET /api/v1/Recorders('id')/Status def state(id) - response = request(url + "/api/v1/Recorders('#{id}')/Status") + response = request(url + "/api/v1/Recorders('#{self[:device_id]}')/Status") self[:previous_state] = self[:state] self[:state] = STATES[response] end From e90422604bb9bdceaa5e786d924e8304c9842e92 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 11:05:20 +1000 Subject: [PATCH 1303/1752] remove credentials --- modules/mediasite/module.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 0c44a2f1..83b0d0b8 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -12,12 +12,12 @@ class Mediasite::Module implements :logic default_settings( - url: 'https://alex-dev.deakin.edu.au/Mediasite/', - username: 'testapi', - password: 'jJ6nP28PE8rr', - api_key: '6c6f13e0-bab0-4b74-a3fb-1b1ee866ffb8', - update_every: 1 + # url: 'https://alex-dev.deakin.edu.au/Mediasite/' # api url endpoint + # username: + # password: + # api_key: # sfapikey # actual_room_name: setting to override room name to search when they mediasite room names don't match up wtih backoffice system names + update_every: 1 ) def on_load From 3093e0f9119d99b1986daeb45ba0f1f83617780f Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 11:38:18 +1000 Subject: [PATCH 1304/1752] fix obtaining device id --- modules/mediasite/module.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 83b0d0b8..28e85763 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -37,8 +37,8 @@ def get_device # Use $top=1000 to ensure that all rooms are returned from the api res = get_request('api/v1/Rooms?$top=1000') res['value'].each { |room| - if room['name'] == room_name - self[:device_id] = room['DeviceConfigurations']['DeviceId'] + if room['Name'] == room_name + self[:device_id] = room['DeviceConfigurations'][0]['DeviceId'] break end } From 5995aff35f01e76a64bf7b6dd283f071b6ab351d Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 11:50:21 +1000 Subject: [PATCH 1305/1752] use settings for username and password --- modules/mediasite/module.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 28e85763..482133d5 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -56,7 +56,7 @@ def poll def get_request(url) uri = URI(setting(:url) + url) req = Net::HTTP::Get.new(uri) - req.basic_auth('acaprojects', 'WtjtvB439cXdZ4Z3') + req.basic_auth(setting(:username), setting(:password)) req['sfapikey'] = setting(:api_key) http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true @@ -66,7 +66,7 @@ def get_request(url) def post_request(url) uri = URI(setting(:url) + url) req = Net::HTTP::Post.new(uri) - req.basic_auth('acaprojects', 'WtjtvB439cXdZ4Z3') + req.basic_auth(setting(:username), setting(:password)) req['sfapikey'] = setting(:api_key) http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true From e821d88fe71e8b1afbb00d3e7402dc9caa948fb3 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 11:52:16 +1000 Subject: [PATCH 1306/1752] parse JSON response --- modules/mediasite/module.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 482133d5..35f89918 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'net/http' +require 'json' module Mediasite; end @@ -60,7 +61,7 @@ def get_request(url) req['sfapikey'] = setting(:api_key) http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true - http.request(req).body + JSON.parse(http.request(req).body) end def post_request(url) @@ -70,7 +71,7 @@ def post_request(url) req['sfapikey'] = setting(:api_key) http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true - http.request(req).body + JSON.parse(http.request(req).body) end # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. From b5e2684f1c0ab604170a89557c07b14c698723ef Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 12:24:46 +1000 Subject: [PATCH 1307/1752] add function to get device --- modules/mediasite/module.rb | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 35f89918..b6582838 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -34,17 +34,6 @@ def room_name setting(:actual_room_name) || system.name end - def get_device - # Use $top=1000 to ensure that all rooms are returned from the api - res = get_request('api/v1/Rooms?$top=1000') - res['value'].each { |room| - if room['Name'] == room_name - self[:device_id] = room['DeviceConfigurations'][0]['DeviceId'] - break - end - } - end - def start schedule.every("#{setting(:update_every)}m") do state @@ -64,6 +53,21 @@ def get_request(url) JSON.parse(http.request(req).body) end + def get_device_id + # Use $top=1000 to ensure that all rooms are returned from the api + res = get_request('api/v1/Rooms?$top=1000') + res['value'].each { |room| + if room['Name'] == room_name + self[:device_id] = room['DeviceConfigurations'][0]['DeviceId'] + break + end + } + end + + def get_device + get_request("api/v1/Recorders('#{self[:device_id]}')") + end + def post_request(url) uri = URI(setting(:url) + url) req = Net::HTTP::Post.new(uri) From cf4bfdf575d4faece5844ffa393fe43babe8201a Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 12:27:27 +1000 Subject: [PATCH 1308/1752] get device id on start up --- modules/mediasite/module.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index b6582838..f66beed5 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -28,6 +28,7 @@ def on_load def on_update schedule.clear self[:room_name] = room_name + self[:device_id] = get_device_id end def room_name @@ -58,8 +59,7 @@ def get_device_id res = get_request('api/v1/Rooms?$top=1000') res['value'].each { |room| if room['Name'] == room_name - self[:device_id] = room['DeviceConfigurations'][0]['DeviceId'] - break + return room['DeviceConfigurations'][0]['DeviceId'] end } end From d566c6df750414b84c8c8239308f90fb2f120af2 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 12:29:14 +1000 Subject: [PATCH 1309/1752] add debuggin code --- modules/mediasite/module.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index f66beed5..866a1c0e 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -65,7 +65,9 @@ def get_device_id end def get_device - get_request("api/v1/Recorders('#{self[:device_id]}')") + res = get_request("api/v1/Recorders('#{self[:device_id]}')") + logger.debug res + res end def post_request(url) From 0ed4792a4642c250bcdc763298150829bb0e6dbd Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 12:33:20 +1000 Subject: [PATCH 1310/1752] output request urls to backoffice --- modules/mediasite/module.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 866a1c0e..0e102f77 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -45,7 +45,9 @@ def poll end def get_request(url) - uri = URI(setting(:url) + url) + req_url = setting(:url) + url + logger.debug(req_url) + uri = URI(req_url) req = Net::HTTP::Get.new(uri) req.basic_auth(setting(:username), setting(:password)) req['sfapikey'] = setting(:api_key) @@ -71,7 +73,9 @@ def get_device end def post_request(url) - uri = URI(setting(:url) + url) + req_url = setting(:url) + url + logger.debug(req_url) + uri = URI(req_url) req = Net::HTTP::Post.new(uri) req.basic_auth(setting(:username), setting(:password)) req['sfapikey'] = setting(:api_key) From 68582e808b2831076f1155f90fe8418b3842b3ef Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 13:07:24 +1000 Subject: [PATCH 1311/1752] remove useless id param to get state --- modules/mediasite/module.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 0e102f77..8aed932f 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -100,10 +100,10 @@ def post_request(url) }.freeze # GET /api/v1/Recorders('id')/Status - def state(id) - response = request(url + "/api/v1/Recorders('#{self[:device_id]}')/Status") + def state + res = request(url + "/api/v1/Recorders('#{self[:device_id]}')/Status") self[:previous_state] = self[:state] - self[:state] = STATES[response] + self[:state] = STATES[res['RecorderState']] end =begin From ddd3e60222c705238762da57fa5ab122c792617f Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 14:15:13 +1000 Subject: [PATCH 1312/1752] add more info to recorder state --- modules/mediasite/module.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 8aed932f..26e24884 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -101,9 +101,18 @@ def post_request(url) # GET /api/v1/Recorders('id')/Status def state - res = request(url + "/api/v1/Recorders('#{self[:device_id]}')/Status") + res = get_request("/api/v1/Recorders('#{self[:device_id]}')/Status") self[:previous_state] = self[:state] self[:state] = STATES[res['RecorderState']] + res = get_request("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata") + self[:title] = res['Title'] + self[:presenters] = res['Presenters'] + self[:live] = false # TODO: found out how to know if recording is being live streamed + res = get_request("/api/v1/Recorders('#{self[:device_id]}')/ActiveInputs") + self[:dual] = res['ActiveInputs'].size >= 2 + res = get_request("/api/v1/Recorders('#{self[:device_id]}')/TimeRemaining") + self[:time_remaining] = res['SecondsRemaining'] + self[:volume] = 0 end =begin From cb4859a2b6791e9e88febe1759f5821f0bd52d44 Mon Sep 17 00:00:00 2001 From: Viv B Date: Thu, 23 May 2019 15:13:38 +1000 Subject: [PATCH 1313/1752] (aca:exchangebooking) fix setting of booking owner --- modules/aca/exchange_booking.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 6ed45729..4c17d6c5 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -807,15 +807,17 @@ def todays_bookings(first=false, skype_exists=false) if ["Private", "Confidential"].include?(meeting.sensitivity) subject = meeting.sensitivity + booking_owner = "Private" else subject = item[:subject][:text] + booking_owner = item[:organizer][:elems][0][:mailbox][:elems][0][:name][:text] end { :Start => start, :End => ending, :Subject => subject, - :owner => item[:organizer][:elems][0][:mailbox][:elems][0][:name][:text], + :owner => booking_owner, :setup => 0, :breakdown => 0, :start_epoch => real_start.to_i, From f70cd7b88b379cc05cf94a5dd809a417a2a5ebb9 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 23 May 2019 15:14:08 +1000 Subject: [PATCH 1314/1752] add polling of state --- modules/mediasite/module.rb | 56 ++++++++++--------------------------- 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 26e24884..947757ae 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -18,7 +18,7 @@ class Mediasite::Module # password: # api_key: # sfapikey # actual_room_name: setting to override room name to search when they mediasite room names don't match up wtih backoffice system names - update_every: 1 + update_every: 5 ) def on_load @@ -29,21 +29,19 @@ def on_update schedule.clear self[:room_name] = room_name self[:device_id] = get_device_id + poll end def room_name setting(:actual_room_name) || system.name end - def start - schedule.every("#{setting(:update_every)}m") do + def poll + schedule.every("#{setting(:update_every)}s") do state end end - def poll - end - def get_request(url) req_url = setting(:url) + url logger.debug(req_url) @@ -88,18 +86,17 @@ def post_request(url) STATES = { 'Unknown' => 'Offline', 'Idle' => 'Idle', - 'Busy' => 'Recording', + 'Busy' => 'Idle', 'RecordStart' => 'Recording', 'Recording' => 'Recording', 'RecordEnd' => 'Recording', - 'Pausing' => 'Recording', - 'Paused' => 'Recording', + 'Pausing' => 'Paused', + 'Paused' => 'Paused', 'Resuming' => 'Recording', 'OpeningSession' => 'Recording', 'ConfiguringDevices' => 'Idle' }.freeze - # GET /api/v1/Recorders('id')/Status def state res = get_request("/api/v1/Recorders('#{self[:device_id]}')/Status") self[:previous_state] = self[:state] @@ -115,44 +112,19 @@ def state self[:volume] = 0 end -=begin -GET /api/v1/Recorders('id')/CurrentPresentationMetadata -Metadata for the current recording including title, start datetime, and if a schedule is available, linked modules and presenter names. -Title - The title of the recording. -Presenters – A list of presenters associated with the recording. -Live – Boolean value indicating that it is also being live streamed. -Dual – Boolean indicating that 2 or more video inputs are being used. -GET /api/v1/Recorders('id')/TimeRemaining -Time Remaining – For scheduled recordings, a mechanism to show how long until the current recording completes. (Discussion with UX team required re whether they would prefer XXX seconds or mm:ss format.) -Basic volume level of current recording. This may be obtained either via the Mediasite API or via QSC. Further discussion is required to ensure an efficient implementation. Refer to Potential Constrains section below. -=end - -=begin -POST /api/v1/CatchDevices('id')/Start -POST /api/v1/CatchDevices('id')/Stop -POST /api/v1/CatchDevices('id')/Pause -POST /api/v1/CatchDevices('id')/Resume - -POST /api/v1/Recorders('id')/Start -POST /api/v1/Recorders('id')/Stop -POST /api/v1/Recorders('id')/Pause -POST /api/v1/Recorders('id')/Resume -=end + def start + post_request("/api/v1/Recorders('#{self[:device_id]}')/Start") + end + def pause - if self[:device_id] - post_request("/api/v1/Recorders('#{self[:device_id]}')/Pause") - end + post_request("/api/v1/Recorders('#{self[:device_id]}')/Pause") end def resume - if self[:device_id] - post_request("/api/v1/Recorders('#{self[:device_id]}')/Resume") - end + post_request("/api/v1/Recorders('#{self[:device_id]}')/Resume") end def stop - if self[:device_id] - post_request("/api/v1/Recorders('#{self[:device_id]}')/Stop") - end + post_request("/api/v1/Recorders('#{self[:device_id]}')/Stop") end end From bfd45df2512e6df4ebf21262cde6c1cfdec17783 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 27 Sep 2018 22:47:29 +1000 Subject: [PATCH 1315/1752] (cisco:switch snmp) update polling timing Spreads instances across a 60 second (rather than 5s) randomised period and changes behaviour to allow 60s between polls rather than poll every 60s. --- modules/cisco/switch/snooping_catalyst_snmp.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/cisco/switch/snooping_catalyst_snmp.rb b/modules/cisco/switch/snooping_catalyst_snmp.rb index e7233459..99fd3c90 100644 --- a/modules/cisco/switch/snooping_catalyst_snmp.rb +++ b/modules/cisco/switch/snooping_catalyst_snmp.rb @@ -366,9 +366,24 @@ def new_client query_connected_devices end + # FIXME: move this concept into uv-rays to enable a scheduled action + # that reschedules at the tail end of the previous run, rather than + # static intervals. + repeat = lambda do |initial_delay: nil, interval:, &action| + repeat_action = proc do + begin + action.call + rescue => e + logger.print_error e, 'in repeat_action' + end + schedule.in(interval, &repeat_action) + end + schedule.in(initial_delay || interval, &repeat_action) + end + # Connected device polling (in case a trap was dropped by the network) # Also expires any desk reservations every 1min - schedule.every(57000 + rand(5000)) do + repeat.call(initial_delay: rand(60000), interval: '60s') do query_connected_devices check_reservations if @reserve_time > 0 end From 35e3e436e226dac710236eac2afcf41bb3d091b1 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 May 2019 14:15:21 +1000 Subject: [PATCH 1316/1752] update repo name in readme --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 732f02bb..d7587cb0 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ -# ACAEngine Modules +# ACAEngine Drivers -Physical device drivers, service integrations and modular logic for [ACAEngine](https://github.com/acaprojects/ruby-engine). +Physical device drivers, service integrations and modular logic for [ACAEngine](https://docs.acaengine.com). --- -If you are getting started head on over to https://developer.acaprojects.com/ to get setup. - For those using these modules, you may find the [docs](https://acaprojects.github.io/aca-device-modules/) of use. Please read the [contributor guide](.github/CONTRIBUTING.md) for project guidelines, or check [support](.github/SUPPORT.md) if you're lost. From fc187b6814efcf04eeeb5f0db2676bb4e7d0b8cf Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 28 May 2019 14:51:40 +1000 Subject: [PATCH 1317/1752] (vagrant) add config for dev-env --- .gitignore | 1 + Vagrantfile | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 Vagrantfile diff --git a/.gitignore b/.gitignore index 20a2c510..ba91ba2a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Gemfile.lock .yardoc doc +.vagrant diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..b08fab3e --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Local path to the drivers repo that you would like to use. +DRIVERS_PATH = './' + +# Local path to the front-ends to have available. +WWW_PATH = nil + +Vagrant.configure('2') do |config| + config.vm.define 'ACAEngine' + + config.vm.box = 'acaengine/dev-env' + + config.vm.synced_folder DRIVERS_PATH, '/etc/aca/aca-device-modules' \ + unless DRIVERS_PATH.nil? + + config.vm.synced_folder WWW_PATH, '/etc/aca/www' \ + unless WWW_PATH.nil? +end From 2e04c796400445c92dde0ce4793f7d53e1567c4e Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 May 2019 15:54:07 +1000 Subject: [PATCH 1318/1752] add function to determine if presentation is live --- modules/mediasite/module.rb | 48 ++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 947757ae..16db13a9 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -43,7 +43,7 @@ def poll end def get_request(url) - req_url = setting(:url) + url + req_url = url logger.debug(req_url) uri = URI(req_url) req = Net::HTTP::Get.new(uri) @@ -54,9 +54,13 @@ def get_request(url) JSON.parse(http.request(req).body) end + def create_url(url) + setting(:url) + url + end + def get_device_id # Use $top=1000 to ensure that all rooms are returned from the api - res = get_request('api/v1/Rooms?$top=1000') + res = get_request(create_url('api/v1/Rooms?$top=1000')) res['value'].each { |room| if room['Name'] == room_name return room['DeviceConfigurations'][0]['DeviceId'] @@ -64,12 +68,6 @@ def get_device_id } end - def get_device - res = get_request("api/v1/Recorders('#{self[:device_id]}')") - logger.debug res - res - end - def post_request(url) req_url = setting(:url) + url logger.debug(req_url) @@ -98,18 +96,40 @@ def post_request(url) }.freeze def state - res = get_request("/api/v1/Recorders('#{self[:device_id]}')/Status") + res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/Status")) self[:previous_state] = self[:state] self[:state] = STATES[res['RecorderState']] - res = get_request("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata") + + res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata")) self[:title] = res['Title'] self[:presenters] = res['Presenters'] - self[:live] = false # TODO: found out how to know if recording is being live streamed - res = get_request("/api/v1/Recorders('#{self[:device_id]}')/ActiveInputs") + + # TODO: found out how to know if recording is being live streamed + self[:live] = live? + + res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/ActiveInputs")) self[:dual] = res['ActiveInputs'].size >= 2 - res = get_request("/api/v1/Recorders('#{self[:device_id]}')/TimeRemaining") + + res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/TimeRemaining")) self[:time_remaining] = res['SecondsRemaining'] - self[:volume] = 0 + + self[:volume] = 0 # TODO: + end + + def live? + live = false + res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/ScheduledRecordingTimes")) + res['value'].each { |schedule| + current_time = ActiveSupport::TimeZone.new('UTC').now + start_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['StartTime']) + end_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['EndTime']) + if start_time <= current_time && current_time <= end_time + presentation = get_request(schedule['ScheduleLink'] + "/Presentations") + live = presentation['value']['Status'] == 'Live' + break; + end + } + live end def start From e07f0251e3ed3874edc11a0c1deb22780ca73941 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 May 2019 16:36:34 +1000 Subject: [PATCH 1319/1752] fix for obtaining live status of presentation --- modules/mediasite/module.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 16db13a9..5a0c4791 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -124,8 +124,8 @@ def live? start_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['StartTime']) end_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['EndTime']) if start_time <= current_time && current_time <= end_time - presentation = get_request(schedule['ScheduleLink'] + "/Presentations") - live = presentation['value']['Status'] == 'Live' + presentation = get_request(schedule['ScheduleLink'] + '/Presentations') + live = presentation['value'][0]['Status'] == 'Live' break; end } From 31f870c04eacc6c1df90883dba41b030a34912b0 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 May 2019 16:56:14 +1000 Subject: [PATCH 1320/1752] fix api path to obtain device id --- modules/mediasite/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 5a0c4791..10b9b5c8 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -60,7 +60,7 @@ def create_url(url) def get_device_id # Use $top=1000 to ensure that all rooms are returned from the api - res = get_request(create_url('api/v1/Rooms?$top=1000')) + res = get_request(create_url('/api/v1/Rooms?$top=1000')) res['value'].each { |room| if room['Name'] == room_name return room['DeviceConfigurations'][0]['DeviceId'] From 38f6bc73f0e1d3c8ea9d94afa1986aaa43a3f302 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 29 May 2019 17:05:24 +1000 Subject: [PATCH 1321/1752] poll state on driver loadup --- modules/mediasite/module.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 10b9b5c8..6b30287f 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -37,6 +37,7 @@ def room_name end def poll + state schedule.every("#{setting(:update_every)}s") do state end From c935be040e30dd367c28c122e7bc188e1d38952c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 30 May 2019 10:39:31 +1000 Subject: [PATCH 1322/1752] return test device id --- modules/mediasite/module.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 6b30287f..eadea453 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -61,6 +61,7 @@ def create_url(url) def get_device_id # Use $top=1000 to ensure that all rooms are returned from the api + return '6fde77f222494df1a4a1b988c7982b814e ' res = get_request(create_url('/api/v1/Rooms?$top=1000')) res['value'].each { |room| if room['Name'] == room_name From dd60cbcc24be0aba70899d5eaae06a32b201599b Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 30 May 2019 10:42:38 +1000 Subject: [PATCH 1323/1752] fix test device id --- modules/mediasite/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index eadea453..6679c306 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -61,7 +61,7 @@ def create_url(url) def get_device_id # Use $top=1000 to ensure that all rooms are returned from the api - return '6fde77f222494df1a4a1b988c7982b814e ' + return '6fde77f222494df1a4a1b988c7982b814e' # only for testing res = get_request(create_url('/api/v1/Rooms?$top=1000')) res['value'].each { |room| if room['Name'] == room_name From 3ef994f388347e27479ad7def31956a7f1b13fa0 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 30 May 2019 10:49:24 +1000 Subject: [PATCH 1324/1752] add ability to set recorder id manually --- modules/mediasite/module.rb | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 6679c306..340d4a8e 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -18,6 +18,7 @@ class Mediasite::Module # password: # api_key: # sfapikey # actual_room_name: setting to override room name to search when they mediasite room names don't match up wtih backoffice system names + # recorder_id: # set recorder id manually if needed update_every: 5 ) @@ -61,13 +62,19 @@ def create_url(url) def get_device_id # Use $top=1000 to ensure that all rooms are returned from the api - return '6fde77f222494df1a4a1b988c7982b814e' # only for testing - res = get_request(create_url('/api/v1/Rooms?$top=1000')) - res['value'].each { |room| - if room['Name'] == room_name - return room['DeviceConfigurations'][0]['DeviceId'] - end - } + device_id = '' + if setting(:recorder_id) + device_id = setting(:recorder_id) + else + res = get_request(create_url('/api/v1/Rooms?$top=1000')) + res['value'].each { |room| + if room['Name'] == room_name + device_id = room['DeviceConfigurations'][0]['DeviceId'] + break + end + } + end + return device_id end def post_request(url) From 57a867029a6625eddf2df00aeec8ec3aac28cbb0 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 30 May 2019 15:09:24 +1000 Subject: [PATCH 1325/1752] fix post request to do nothing with response --- modules/mediasite/module.rb | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 340d4a8e..a5d9716e 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -56,6 +56,17 @@ def get_request(url) JSON.parse(http.request(req).body) end + def post_request(url) + req_url = setting(:url) + url + uri = URI(req_url) + req = Net::HTTP::Post.new(uri) + req.basic_auth(setting(:username), setting(:password)) + req['sfapikey'] = setting(:api_key) + http = Net::HTTP.new(uri.hostname, uri.port) + http.use_ssl = true + http.request(req) + end + def create_url(url) setting(:url) + url end @@ -77,18 +88,6 @@ def get_device_id return device_id end - def post_request(url) - req_url = setting(:url) + url - logger.debug(req_url) - uri = URI(req_url) - req = Net::HTTP::Post.new(uri) - req.basic_auth(setting(:username), setting(:password)) - req['sfapikey'] = setting(:api_key) - http = Net::HTTP.new(uri.hostname, uri.port) - http.use_ssl = true - JSON.parse(http.request(req).body) - end - # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. STATES = { 'Unknown' => 'Offline', From 5e38b8dff3b26040670e9d3f230b60204ebb2e0f Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 30 May 2019 15:16:20 +1000 Subject: [PATCH 1326/1752] add documentation link and return success on successful post command --- modules/mediasite/module.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index a5d9716e..f00ff6f5 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# Documentation: https://alex.deakin.edu.au/mediasite/api/v1/$metadata#top + require 'net/http' require 'json' @@ -65,6 +67,7 @@ def post_request(url) http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = true http.request(req) + :success end def create_url(url) @@ -85,7 +88,7 @@ def get_device_id end } end - return device_id + device_id end # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. From 19ece96a5f23e4381ed77dc5764d5fb9ad62dce1 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 30 May 2019 15:37:19 +1000 Subject: [PATCH 1327/1752] add comments --- modules/mediasite/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index f00ff6f5..768d8155 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -21,7 +21,7 @@ class Mediasite::Module # api_key: # sfapikey # actual_room_name: setting to override room name to search when they mediasite room names don't match up wtih backoffice system names # recorder_id: # set recorder id manually if needed - update_every: 5 + update_every: 5 # number of seconds to poll recorder state ) def on_load From e9d9511a7ba5e199913f7195ec6b6a7658fa3e80 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 31 May 2019 11:40:36 +1000 Subject: [PATCH 1328/1752] change module name to Capture --- modules/mediasite/module.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 768d8155..5fffa152 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -11,7 +11,7 @@ class Mediasite::Module include ::Orchestrator::Constants descriptive_name 'Mediasite Recorder' - generic_name :Recorder + generic_name :Capture implements :logic default_settings( @@ -112,6 +112,7 @@ def state self[:state] = STATES[res['RecorderState']] res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata")) + self[:current] = res['Title'] self[:title] = res['Title'] self[:presenters] = res['Presenters'] From 898610826b4023bbdf1ab488386bf251810b0e61 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 3 Jun 2019 12:01:41 +1000 Subject: [PATCH 1329/1752] (aca:router) fix module nil check --- modules/aca/router.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/router.rb b/modules/aca/router.rb index b3b4593f..5acc6c6b 100644 --- a/modules/aca/router.rb +++ b/modules/aca/router.rb @@ -326,7 +326,7 @@ def needs_activation?(edge, force: false) if mod.nil? && single_source fail_with['already on correct input'] \ - if edge.nx1? && mod && mod[:input] == edge.input && !force + if edge.nx1? && !mod.nil? && mod[:input] == edge.input && !force fail_with['has an incompatible api, but only a single input defined'] \ if edge.nx1? && !mod.respond_to?(:switch_to) && single_source From b7f5f3765502b9db76bfe121d68286f3406acf75 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 3 Jun 2019 12:16:00 +1000 Subject: [PATCH 1330/1752] remove debugging output --- modules/mediasite/module.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 5fffa152..e2566c59 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -48,7 +48,7 @@ def poll def get_request(url) req_url = url - logger.debug(req_url) + #logger.debug(req_url) uri = URI(req_url) req = Net::HTTP::Get.new(uri) req.basic_auth(setting(:username), setting(:password)) @@ -112,7 +112,10 @@ def state self[:state] = STATES[res['RecorderState']] res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata")) - self[:current] = res['Title'] + self[:current] = { + 'start_time' => Time.now, + 'state' => STATES[res['RecorderState']] + } self[:title] = res['Title'] self[:presenters] = res['Presenters'] @@ -138,7 +141,7 @@ def live? if start_time <= current_time && current_time <= end_time presentation = get_request(schedule['ScheduleLink'] + '/Presentations') live = presentation['value'][0]['Status'] == 'Live' - break; + break end } live From 2da4830afc83c1d869c247d75b359beee33ef286 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 3 Jun 2019 16:11:17 +1000 Subject: [PATCH 1331/1752] Update documentation link --- modules/mediasite/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index e2566c59..a4fafcca 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Documentation: https://alex.deakin.edu.au/mediasite/api/v1/$metadata#top +# Documnetation: https://docs.google.com/document/d/18EjSfVSg5FTe0rMsc-Yygb8obcOp7tm5BNmz9OGj21o/edit?usp=sharing require 'net/http' require 'json' From 2b6f081bc8bbc8a95fe7d233e078b83d05c09710 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 3 Jun 2019 17:06:38 +1000 Subject: [PATCH 1332/1752] (messagemedia:sms) fix proxy support --- modules/message_media/sms.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/message_media/sms.rb b/modules/message_media/sms.rb index 29800b6b..a75231de 100644 --- a/modules/message_media/sms.rb +++ b/modules/message_media/sms.rb @@ -22,6 +22,15 @@ def on_update # NOTE:: base URI https://api.messagemedia.com @username = setting(:username) @password = setting(:password) + proxy = setting(:proxy) + if proxy + config({ + proxy: { + host: proxy[:host], + port: proxy[:port] + } + }) + end end def sms(text, numbers, source = nil) From 1c4b97b511d53eaa75d668c0410125b546c15d45 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 3 Jun 2019 17:14:02 +1000 Subject: [PATCH 1333/1752] (pexip:management api) add proxy support and fix name --- modules/message_media/sms.rb | 4 ++-- modules/pexip/management.rb | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/message_media/sms.rb b/modules/message_media/sms.rb index a75231de..a88b8f06 100644 --- a/modules/message_media/sms.rb +++ b/modules/message_media/sms.rb @@ -8,8 +8,8 @@ class MessageMedia::SMS # Discovery Information implements :service - descriptive_name 'MessageMedia SMS service' - generic_name :SMS + descriptive_name 'Pexip Management API' + generic_name :Pexip # HTTP keepalive keepalive false diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 92f82653..464fea6e 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -22,6 +22,15 @@ def on_update # NOTE:: base URI https://pexip.company.com @username = setting(:username) @password = setting(:password) + proxy = setting(:proxy) + if proxy + config({ + proxy: { + host: proxy[:host], + port: proxy[:port] + } + }) + end end MeetingTypes = ["conference", "lecture", "two_stage_dialing", "test_call"] From 3b8e82ff4ddae0b13bb0872d0b4faa6ca2f7dfc4 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 3 Jun 2019 17:31:46 +1000 Subject: [PATCH 1334/1752] (messagemedia:sms) revert name --- modules/message_media/sms.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/message_media/sms.rb b/modules/message_media/sms.rb index a88b8f06..a75231de 100644 --- a/modules/message_media/sms.rb +++ b/modules/message_media/sms.rb @@ -8,8 +8,8 @@ class MessageMedia::SMS # Discovery Information implements :service - descriptive_name 'Pexip Management API' - generic_name :Pexip + descriptive_name 'MessageMedia SMS service' + generic_name :SMS # HTTP keepalive keepalive false From 6d17f4a5d2624ce872649877d609aa4f44628bf0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 3 Jun 2019 17:33:55 +1000 Subject: [PATCH 1335/1752] (pexip:management) fix get meeting --- modules/pexip/management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 464fea6e..d6fb68a7 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -58,7 +58,7 @@ def new_meeting(name, type = "conference", pin: rand(9999), **options) def get_meeting(meeting) meeting = "/api/admin/configuration/v1/conference/#{meeting}/" unless meeting.to_s.include?("/") - get(meeting).path, headers: { + get(meeting, headers: { 'Authorization' => [@username, @password], 'Content-Type' => 'application/json', 'Accept' => 'application/json' From 3fa3b882d261f532064d580a86e7966b2b861c6e Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 4 Jun 2019 10:25:37 +1000 Subject: [PATCH 1336/1752] change recorder states to match frontend --- modules/mediasite/module.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index a4fafcca..7dd9ff11 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -94,16 +94,16 @@ def get_device_id # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. STATES = { 'Unknown' => 'Offline', - 'Idle' => 'Idle', - 'Busy' => 'Idle', - 'RecordStart' => 'Recording', - 'Recording' => 'Recording', - 'RecordEnd' => 'Recording', - 'Pausing' => 'Paused', - 'Paused' => 'Paused', - 'Resuming' => 'Recording', - 'OpeningSession' => 'Recording', - 'ConfiguringDevices' => 'Idle' + 'Idle' => 'stop', + 'Busy' => 'stop', + 'RecordStart' => 'active', + 'Recording' => 'active', + 'RecordEnd' => 'active', + 'Pausing' => 'paused', + 'Paused' => 'paused', + 'Resuming' => 'active', + 'OpeningSession' => 'active', + 'ConfiguringDevices' => 'stop' }.freeze def state From 18680e54d020de2bae26320515adbda7a45c25cd Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 4 Jun 2019 14:26:05 +1000 Subject: [PATCH 1337/1752] fix typo --- modules/mediasite/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 7dd9ff11..6f6c7964 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Documnetation: https://docs.google.com/document/d/18EjSfVSg5FTe0rMsc-Yygb8obcOp7tm5BNmz9OGj21o/edit?usp=sharing +# Documentation: https://docs.google.com/document/d/18EjSfVSg5FTe0rMsc-Yygb8obcOp7tm5BNmz9OGj21o/edit?usp=sharing require 'net/http' require 'json' From 24788400375ba5428b85c7c3c584565e261bbce3 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 5 Jun 2019 10:56:21 +1000 Subject: [PATCH 1338/1752] lutron lighting specs pass --- modules/lutron/lighting.rb | 2 +- modules/lutron/lighting_spec.rb | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/lutron/lighting.rb b/modules/lutron/lighting.rb index 42cbcab6..b7534822 100644 --- a/modules/lutron/lighting.rb +++ b/modules/lutron/lighting.rb @@ -217,4 +217,4 @@ def send_query(*command, **options) logger.debug { "Requesting: #{cmd}" } send("#{cmd}\r\n", options) end -end \ No newline at end of file +end diff --git a/modules/lutron/lighting_spec.rb b/modules/lutron/lighting_spec.rb index 5e77c2f8..bae08f0b 100644 --- a/modules/lutron/lighting_spec.rb +++ b/modules/lutron/lighting_spec.rb @@ -1,15 +1,15 @@ -Orchestrator::Testing.mock_device 'Lutron::Lighting' do +EngineSpec.mock_device "Lutron::Lighting" do # Module waits for this text to become ready - transmit 'login: ' + transmit "login: " should_send "nwk\r\n" transmit "connection established\r\n" - wait 110 + sleep 110.milliseconds # Perform actions exec(:scene?, 1) - .should_send("?AREA,1,6\r\n") - .responds("~AREA,1,6,2\r\n") + should_send("?AREA,1,6\r\n") + responds("~AREA,1,6,2\r\n") expect(status[:area1]).to be(2) transmit "~DEVICE,1,6,9,1\r\n" @@ -24,13 +24,13 @@ transmit "~SHADEGRP,26,1,100.00\r\n" expect(status[:shadegrp26_level]).to be(100.00) - wait 110 + sleep 110.milliseconds exec(:scene, 1, 3) - .should_send("#AREA,1,6,3\r\n") - .responds("\r\n") + should_send("#AREA,1,6,3\r\n") + responds("\r\n") - wait 110 + sleep 110.milliseconds should_send("?AREA,1,6\r\n") transmit "~AREA,1,6,3\r\n" From 9b456076cb6a2a86b524972b27681663e9936884 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 5 Jun 2019 11:24:13 +1000 Subject: [PATCH 1339/1752] Revert "lutron lighting specs pass" This reverts commit 24788400375ba5428b85c7c3c584565e261bbce3. --- modules/lutron/lighting.rb | 2 +- modules/lutron/lighting_spec.rb | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/lutron/lighting.rb b/modules/lutron/lighting.rb index b7534822..42cbcab6 100644 --- a/modules/lutron/lighting.rb +++ b/modules/lutron/lighting.rb @@ -217,4 +217,4 @@ def send_query(*command, **options) logger.debug { "Requesting: #{cmd}" } send("#{cmd}\r\n", options) end -end +end \ No newline at end of file diff --git a/modules/lutron/lighting_spec.rb b/modules/lutron/lighting_spec.rb index bae08f0b..5e77c2f8 100644 --- a/modules/lutron/lighting_spec.rb +++ b/modules/lutron/lighting_spec.rb @@ -1,15 +1,15 @@ -EngineSpec.mock_device "Lutron::Lighting" do +Orchestrator::Testing.mock_device 'Lutron::Lighting' do # Module waits for this text to become ready - transmit "login: " + transmit 'login: ' should_send "nwk\r\n" transmit "connection established\r\n" - sleep 110.milliseconds + wait 110 # Perform actions exec(:scene?, 1) - should_send("?AREA,1,6\r\n") - responds("~AREA,1,6,2\r\n") + .should_send("?AREA,1,6\r\n") + .responds("~AREA,1,6,2\r\n") expect(status[:area1]).to be(2) transmit "~DEVICE,1,6,9,1\r\n" @@ -24,13 +24,13 @@ transmit "~SHADEGRP,26,1,100.00\r\n" expect(status[:shadegrp26_level]).to be(100.00) - sleep 110.milliseconds + wait 110 exec(:scene, 1, 3) - should_send("#AREA,1,6,3\r\n") - responds("\r\n") + .should_send("#AREA,1,6,3\r\n") + .responds("\r\n") - sleep 110.milliseconds + wait 110 should_send("?AREA,1,6\r\n") transmit "~AREA,1,6,3\r\n" From 73633a00f3a7d3ba1b25d3691823e7266c8efd66 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Jun 2019 12:04:41 +1000 Subject: [PATCH 1340/1752] fix getting state info --- modules/mediasite/module.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 6f6c7964..2c99e159 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -110,12 +110,12 @@ def state res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/Status")) self[:previous_state] = self[:state] self[:state] = STATES[res['RecorderState']] - - res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata")) self[:current] = { 'start_time' => Time.now, 'state' => STATES[res['RecorderState']] } + + res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata")) self[:title] = res['Title'] self[:presenters] = res['Presenters'] From 3abcf456b9088532cc99d63995c94486f697ee4d Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Jun 2019 12:08:40 +1000 Subject: [PATCH 1341/1752] return actual recorder state as well --- modules/mediasite/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 2c99e159..bcc2183d 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -109,7 +109,7 @@ def get_device_id def state res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/Status")) self[:previous_state] = self[:state] - self[:state] = STATES[res['RecorderState']] + self[:state] = res['RecorderState'] self[:current] = { 'start_time' => Time.now, 'state' => STATES[res['RecorderState']] From 0b909fb692b0e4727e0a41feac670bec465d0de9 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Jun 2019 12:11:23 +1000 Subject: [PATCH 1342/1752] instantly poll state after a command --- modules/mediasite/module.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index bcc2183d..0feb14da 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -149,17 +149,21 @@ def live? def start post_request("/api/v1/Recorders('#{self[:device_id]}')/Start") + state end def pause post_request("/api/v1/Recorders('#{self[:device_id]}')/Pause") + state end def resume post_request("/api/v1/Recorders('#{self[:device_id]}')/Resume") + state end def stop post_request("/api/v1/Recorders('#{self[:device_id]}')/Stop") + state end end From eeba14e6a481085a25a529e328005ef7803c4794 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Jun 2019 12:18:52 +1000 Subject: [PATCH 1343/1752] turn debugging output back on --- modules/mediasite/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 0feb14da..e204f12b 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -48,7 +48,7 @@ def poll def get_request(url) req_url = url - #logger.debug(req_url) + logger.debug(req_url) uri = URI(req_url) req = Net::HTTP::Get.new(uri) req.basic_auth(setting(:username), setting(:password)) From 7726b80942baee3521a39af7b72b1f43bea557bf Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 7 Jun 2019 12:39:04 +1000 Subject: [PATCH 1344/1752] get actual current time for recordings --- modules/mediasite/module.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index e204f12b..828a4f03 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -110,10 +110,6 @@ def state res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/Status")) self[:previous_state] = self[:state] self[:state] = res['RecorderState'] - self[:current] = { - 'start_time' => Time.now, - 'state' => STATES[res['RecorderState']] - } res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata")) self[:title] = res['Title'] @@ -141,6 +137,10 @@ def live? if start_time <= current_time && current_time <= end_time presentation = get_request(schedule['ScheduleLink'] + '/Presentations') live = presentation['value'][0]['Status'] == 'Live' + self[:current] = { + 'state' => STATES[self[:state]], + 'start_time' => start_time.in_time_zone('Sydney') + } break end } From 08eaf5b0ce66007297d67bc57b022799f18c0c69 Mon Sep 17 00:00:00 2001 From: viv Date: Wed, 12 Jun 2019 14:41:03 +1000 Subject: [PATCH 1345/1752] replace :stable_state with :power_target.nil? --- modules/lumens/dc192.rb | 655 ++++++++++++++++++++-------------------- 1 file changed, 325 insertions(+), 330 deletions(-) diff --git a/modules/lumens/dc192.rb b/modules/lumens/dc192.rb index c88b4e9f..d65525ca 100644 --- a/modules/lumens/dc192.rb +++ b/modules/lumens/dc192.rb @@ -3,7 +3,7 @@ module Lumens; end # Documentation: https://aca.im/driver_docs/Lumens/DC192%20Protocol.pdf class Lumens::Dc192 - include ::Orchestrator::Constants + include ::Orchestrator::Constants include ::Orchestrator::Transcoder @@ -18,332 +18,327 @@ class Lumens::Dc192 wait_response retries: 8 - def on_load - self[:zoom_max] = 855 - self[:zoom_min] = 0 - self[:stable_state] = true - end - - def on_unload - end - - def on_update - end - - - - def connected - do_poll - schedule.every('60s') do - logger.debug "-- Polling Lumens DC Series Visualiser" - do_poll - end - end - - def disconnected - schedule.clear - end - - - - - COMMANDS = { - :zoom_stop => 0x10, # p1 = 0x00 - :zoom_start => 0x11, # p1 (00/01:Tele/Wide) - :zoom_direct => 0x13, # p1-LowByte, p2 highbyte (0~620) - :lamp => 0xC1, # p1 (00/01:Off/On) - :power => 0xB1, # p1 (00/01:Off/On) - :sharp => 0xA7, # p1 (00/01/02:Photo/Text/Gray) only photo or text - :auto_focus => 0xA3, # p1 = 0x01 - :frozen => 0x2C, # p1 (00/01:Off/On) - - 0x10 => :zoom_stop, - 0x11 => :zoom_start, - 0x13 => :zoom_direct, - 0xC1 => :lamp, - 0xB1 => :power, - 0xA7 => :sharp, - 0xA3 => :auto_focus, - 0x2C => :frozen, - - # Status response codes: - 0x78 => :frozen, - 0x51 => :sharp, - 0x50 => :lamp, - 0x60 => :zoom_direct, - 0xB7 => :system_status - } - - - def power(state) - state = is_affirmative?(state) - self[:power_target] = state - power? do - if state && !self[:power] # Request to power on if off - self[:stable_state] = false - do_send([COMMANDS[:power], 0x01], :timeout => 15000, :delay_on_receive => 5000, :name => :power) - - elsif !state && self[:power] # Request to power off if on - self[:stable_state] = false - do_send([COMMANDS[:power], 0x00], :timeout => 15000, :delay_on_receive => 5000, :name => :power) - self[:frozen] = false - end - end - end - - def power?(options = {}, &block) - options[:emit] = block - do_send(STATUS_CODE[:system_status], options) - end - - def zoom_in - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:zoom_start]) - end - - def zoom_out - return if self[:frozen] - cancel_focus - do_send([COMMANDS[:zoom_start], 0x01]) - end - - def zoom_stop - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:zoom_stop]) - end - - def zoom(position) - return if self[:frozen] - cancel_focus - position = position.to_i - - position = in_range(position, self[:zoom_max]) - - - low = position & 0xFF - high = (position >> 8) & 0xFF - - do_send([COMMANDS[:zoom_direct], low, high], {:name => :zoom}) - end - - - def lamp(power) - return if self[:frozen] - power = is_affirmative?(power) - - if power - do_send([COMMANDS[:lamp], 0x01], {:name => :lamp}) - else - do_send([COMMANDS[:lamp], 0x00], {:name => :lamp}) - end - end - - - def sharp(state) - return if self[:frozen] - state = is_affirmative?(state) - - if state - do_send([COMMANDS[:sharp], 0x01], {:name => :sharp}) - else - do_send([COMMANDS[:sharp], 0x00], {:name => :sharp}) - end - end - - - def frozen(state) - state = is_affirmative?(state) - - if state - do_send([COMMANDS[:frozen], 0x01], {:name => :frozen}) - else - do_send([COMMANDS[:frozen], 0x00], {:name => :frozen}) - end - end - - - def auto_focus - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:auto_focus], :timeout => 8000, :name => :auto_focus) - end - - - def reset - return if self[:frozen] - cancel_focus - power(On) - - RESET_CODES.each_value do |value| - do_send(value) - end - - sharp(Off) - frozen(Off) - lamp(On) - zoom(0) - end - - - def received(data, reesolve, command) - logger.debug "Lumens sent #{byte_to_hex(data)}" - - - data = str_to_array(data) - - - # - # Process response - # - logger.debug "command was #{COMMANDS[data[0]]}" - case COMMANDS[data[0]] - when :zoom_stop - # - # A 3 second delay for zoom status and auto focus - # - zoom_status - delay_focus - when :zoom_direct - self[:zoom] = data[1] + (data[2] << 8) - delay_focus if COMMANDS[:zoom_direct] == data[0] # then 3 second delay for auto focus - when :lamp - self[:lamp] = data[1] == 0x01 - when :power - self[:power] = data[1] == 0x01 - if (self[:power] != self[:power_target]) && !self[:stable_state] - power(self[:power_target]) - logger.debug "Lumens state == unstable - power resp" - else - self[:stable_state] = true - self[:zoom] = self[:zoom_min] unless self[:power] - end - when :sharp - self[:sharp] = data[1] == 0x01 - when :frozen - self[:frozen] = data[1] == 0x01 - when :system_status - self[:power] = data[2] == 0x01 - if (self[:power] != self[:power_target]) && !self[:stable_state] - power(self[:power_target]) - logger.debug "Lumens state == unstable - status" - else - self[:stable_state] = true - self[:zoom] = self[:zoom_min] unless self[:power] - end - # ready = data[1] == 0x01 - end - - - # - # Check for error - # => We check afterwards as power for instance may be on when we call on - # => The power status is sent as on with a NAK as the command did nothing - # - if data[3] != 0x00 && (!!!self[:frozen]) - case data[3] - when 0x01 - logger.error "Lumens NAK error" - when 0x10 - logger.error "Lumens IGNORE error" - if command.present? - command[:delay_on_receive] = 2000 # update the command - return :abort # retry the command - # - # TODO:: Call system_status(0) and check for ready every second until the command will go through - # - end - else - logger.warn "Lumens unknown error code #{data[3]}" - end - - logger.error "Error on #{byte_to_hex(command[:data])}" unless command.nil? - return :abort - end - - - return :success - end - - - - private - - - def delay_focus - @focus_timer.cancel unless @focus_timer.nil? - @focus_timer = schedule.in('4s') do - auto_focus - end - end - - def cancel_focus - @focus_timer.cancel unless @focus_timer.nil? - end - - - - RESET_CODES = { - :OSD => [0x4B, 0x00], # p1 (00/01:Off/On) on screen display - :digital_zoom => [0x40, 0x00], # p1 (00/01:Disable/Enable) - :language => [0x38, 0x00], # p1 == 00 (english) - :colour => [0xA7, 0x00], # p1 (00/01:Photo/Gray) - :mode => [0xA9, 0x00], # P1 (00/01/02/03:Normal/Slide/Film/Microscope) - :logo => [0x47, 0x00], # p1 (00/01:Off/On) - :source => [0x3A, 0x00], # p1 (00/01:Live/PC) used for reset - :slideshow => [0x04, 0x00] # p1 (00/01:Off/On) -- NAKs - } - - - STATUS_CODE = { - :frozen_status => 0x78, # p1 = 0x00 - :sharp_status => 0x51, # p1 = 0x00 - :lamp_status => 0x50, # p1 = 0x00 - :zoom_status => 0x60, # p1 = 0x00 - :system_status => 0xB7 - } - # - # Automatically creates a callable function for each command - # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html - # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html - # - STATUS_CODE.each_key do |command| - define_method command do |*args| - priority = 99 - if args.length > 0 - priority = args[0] - end - - do_send(STATUS_CODE[command], {:priority => priority, :wait => true}) # Status polling is a low priority - end - end - - - def do_poll - power?(:priority => 99) do - if self[:power] == On - frozen_status - if not self[:frozen] - zoom_status - lamp_status - sharp_status - end - end - end - end - - - def do_send(command, options = {}) - #logger.debug "-- GlobalCache, sending: #{command}" - command = [command] unless command.is_a?(Array) - while command.length < 4 - command << 0x00 - end - - command = [0xA0] + command + [0xAF] - logger.debug "requesting #{byte_to_hex(command)}" - - send(command, options) - end -end \ No newline at end of file + def on_load + self[:zoom_max] = 855 + self[:zoom_min] = 0 + end + + def on_unload + end + + def on_update + end + + + + def connected + do_poll + schedule.every('60s') do + logger.debug "-- Polling Lumens DC Series Visualiser" + do_poll + end + end + + def disconnected + schedule.clear + end + + + + + COMMANDS = { + :zoom_stop => 0x10, # p1 = 0x00 + :zoom_start => 0x11, # p1 (00/01:Tele/Wide) + :zoom_direct => 0x13, # p1-LowByte, p2 highbyte (0~620) + :lamp => 0xC1, # p1 (00/01:Off/On) + :power => 0xB1, # p1 (00/01:Off/On) + :sharp => 0xA7, # p1 (00/01/02:Photo/Text/Gray) only photo or text + :auto_focus => 0xA3, # p1 = 0x01 + :frozen => 0x2C, # p1 (00/01:Off/On) + + 0x10 => :zoom_stop, + 0x11 => :zoom_start, + 0x13 => :zoom_direct, + 0xC1 => :lamp, + 0xB1 => :power, + 0xA7 => :sharp, + 0xA3 => :auto_focus, + 0x2C => :frozen, + + # Status response codes: + 0x78 => :frozen, + 0x51 => :sharp, + 0x50 => :lamp, + 0x60 => :zoom_direct, + 0xB7 => :system_status + } + + + def power(state) + state = is_affirmative?(state) + self[:power_target] = state + power? do + if state && !self[:power] # Request to power on if off + do_send([COMMANDS[:power], 0x01], :timeout => 15000, :delay_on_receive => 5000, :name => :power) + + elsif !state && self[:power] # Request to power off if on + do_send([COMMANDS[:power], 0x00], :timeout => 15000, :delay_on_receive => 5000, :name => :power) + self[:frozen] = false + end + end + end + + def power?(options = {}, &block) + options[:emit] = block + do_send(STATUS_CODE[:system_status], options) + end + + def zoom_in + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:zoom_start]) + end + + def zoom_out + return if self[:frozen] + cancel_focus + do_send([COMMANDS[:zoom_start], 0x01]) + end + + def zoom_stop + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:zoom_stop]) + end + + def zoom(position) + return if self[:frozen] + cancel_focus + position = position.to_i + + position = in_range(position, self[:zoom_max]) + + + low = position & 0xFF + high = (position >> 8) & 0xFF + + do_send([COMMANDS[:zoom_direct], low, high], {:name => :zoom}) + end + + + def lamp(power) + return if self[:frozen] + power = is_affirmative?(power) + + if power + do_send([COMMANDS[:lamp], 0x01], {:name => :lamp}) + else + do_send([COMMANDS[:lamp], 0x00], {:name => :lamp}) + end + end + + + def sharp(state) + return if self[:frozen] + state = is_affirmative?(state) + + if state + do_send([COMMANDS[:sharp], 0x01], {:name => :sharp}) + else + do_send([COMMANDS[:sharp], 0x00], {:name => :sharp}) + end + end + + + def frozen(state) + state = is_affirmative?(state) + + if state + do_send([COMMANDS[:frozen], 0x01], {:name => :frozen}) + else + do_send([COMMANDS[:frozen], 0x00], {:name => :frozen}) + end + end + + + def auto_focus + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:auto_focus], :timeout => 8000, :name => :auto_focus) + end + + + def reset + return if self[:frozen] + cancel_focus + power(On) + + RESET_CODES.each_value do |value| + do_send(value) + end + + sharp(Off) + frozen(Off) + lamp(On) + zoom(0) + end + + + def received(data, reesolve, command) + logger.debug "Lumens sent #{byte_to_hex(data)}" + + + data = str_to_array(data) + + + # + # Process response + # + logger.debug "command was #{COMMANDS[data[0]]}" + case COMMANDS[data[0]] + when :zoom_stop + # + # A 3 second delay for zoom status and auto focus + # + zoom_status + delay_focus + when :zoom_direct + self[:zoom] = data[1] + (data[2] << 8) + delay_focus if COMMANDS[:zoom_direct] == data[0] # then 3 second delay for auto focus + when :lamp + self[:lamp] = data[1] == 0x01 + when :power + self[:power] = data[1] == 0x01 + if (self[:power] != self[:power_target]) && !self[:power_target].nil? + power(self[:power_target]) + logger.debug "Lumens state == unstable - power resp" + else + self[:zoom] = self[:zoom_min] unless self[:power] + end + when :sharp + self[:sharp] = data[1] == 0x01 + when :frozen + self[:frozen] = data[1] == 0x01 + when :system_status + self[:power] = data[2] == 0x01 + if (self[:power] != self[:power_target]) && !self[:power_target].nil? + power(self[:power_target]) + logger.debug "Lumens state == unstable - status" + else + self[:zoom] = self[:zoom_min] unless self[:power] + end + # ready = data[1] == 0x01 + end + + + # + # Check for error + # => We check afterwards as power for instance may be on when we call on + # => The power status is sent as on with a NAK as the command did nothing + # + if data[3] != 0x00 && (!!!self[:frozen]) + case data[3] + when 0x01 + logger.error "Lumens NAK error" + when 0x10 + logger.error "Lumens IGNORE error" + if command.present? + command[:delay_on_receive] = 2000 # update the command + return :abort # retry the command + # + # TODO:: Call system_status(0) and check for ready every second until the command will go through + # + end + else + logger.warn "Lumens unknown error code #{data[3]}" + end + + logger.error "Error on #{byte_to_hex(command[:data])}" unless command.nil? + return :abort + end + + + return :success + end + + + + private + + + def delay_focus + @focus_timer.cancel unless @focus_timer.nil? + @focus_timer = schedule.in('4s') do + auto_focus + end + end + + def cancel_focus + @focus_timer.cancel unless @focus_timer.nil? + end + + + + RESET_CODES = { + :OSD => [0x4B, 0x00], # p1 (00/01:Off/On) on screen display + :digital_zoom => [0x40, 0x00], # p1 (00/01:Disable/Enable) + :language => [0x38, 0x00], # p1 == 00 (english) + :colour => [0xA7, 0x00], # p1 (00/01:Photo/Gray) + :mode => [0xA9, 0x00], # P1 (00/01/02/03:Normal/Slide/Film/Microscope) + :logo => [0x47, 0x00], # p1 (00/01:Off/On) + :source => [0x3A, 0x00], # p1 (00/01:Live/PC) used for reset + :slideshow => [0x04, 0x00] # p1 (00/01:Off/On) -- NAKs + } + + + STATUS_CODE = { + :frozen_status => 0x78, # p1 = 0x00 + :sharp_status => 0x51, # p1 = 0x00 + :lamp_status => 0x50, # p1 = 0x00 + :zoom_status => 0x60, # p1 = 0x00 + :system_status => 0xB7 + } + # + # Automatically creates a callable function for each command + # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html + # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html + # + STATUS_CODE.each_key do |command| + define_method command do |*args| + priority = 99 + if args.length > 0 + priority = args[0] + end + + do_send(STATUS_CODE[command], {:priority => priority, :wait => true}) # Status polling is a low priority + end + end + + + def do_poll + power?(:priority => 99) do + if self[:power] == On + frozen_status + if not self[:frozen] + zoom_status + lamp_status + sharp_status + end + end + end + end + + + def do_send(command, options = {}) + #logger.debug "-- GlobalCache, sending: #{command}" + command = [command] unless command.is_a?(Array) + while command.length < 4 + command << 0x00 + end + + command = [0xA0] + command + [0xAF] + logger.debug "requesting #{byte_to_hex(command)}" + + send(command, options) + end +end From 99b86a1cc9d1ad50eee28b82b066670407b94447 Mon Sep 17 00:00:00 2001 From: viv Date: Wed, 12 Jun 2019 14:41:03 +1000 Subject: [PATCH 1346/1752] replace :stable_state with :power_target.nil? --- modules/lumens/dc192.rb | 655 ++++++++++++++++++++-------------------- 1 file changed, 325 insertions(+), 330 deletions(-) diff --git a/modules/lumens/dc192.rb b/modules/lumens/dc192.rb index c88b4e9f..d65525ca 100644 --- a/modules/lumens/dc192.rb +++ b/modules/lumens/dc192.rb @@ -3,7 +3,7 @@ module Lumens; end # Documentation: https://aca.im/driver_docs/Lumens/DC192%20Protocol.pdf class Lumens::Dc192 - include ::Orchestrator::Constants + include ::Orchestrator::Constants include ::Orchestrator::Transcoder @@ -18,332 +18,327 @@ class Lumens::Dc192 wait_response retries: 8 - def on_load - self[:zoom_max] = 855 - self[:zoom_min] = 0 - self[:stable_state] = true - end - - def on_unload - end - - def on_update - end - - - - def connected - do_poll - schedule.every('60s') do - logger.debug "-- Polling Lumens DC Series Visualiser" - do_poll - end - end - - def disconnected - schedule.clear - end - - - - - COMMANDS = { - :zoom_stop => 0x10, # p1 = 0x00 - :zoom_start => 0x11, # p1 (00/01:Tele/Wide) - :zoom_direct => 0x13, # p1-LowByte, p2 highbyte (0~620) - :lamp => 0xC1, # p1 (00/01:Off/On) - :power => 0xB1, # p1 (00/01:Off/On) - :sharp => 0xA7, # p1 (00/01/02:Photo/Text/Gray) only photo or text - :auto_focus => 0xA3, # p1 = 0x01 - :frozen => 0x2C, # p1 (00/01:Off/On) - - 0x10 => :zoom_stop, - 0x11 => :zoom_start, - 0x13 => :zoom_direct, - 0xC1 => :lamp, - 0xB1 => :power, - 0xA7 => :sharp, - 0xA3 => :auto_focus, - 0x2C => :frozen, - - # Status response codes: - 0x78 => :frozen, - 0x51 => :sharp, - 0x50 => :lamp, - 0x60 => :zoom_direct, - 0xB7 => :system_status - } - - - def power(state) - state = is_affirmative?(state) - self[:power_target] = state - power? do - if state && !self[:power] # Request to power on if off - self[:stable_state] = false - do_send([COMMANDS[:power], 0x01], :timeout => 15000, :delay_on_receive => 5000, :name => :power) - - elsif !state && self[:power] # Request to power off if on - self[:stable_state] = false - do_send([COMMANDS[:power], 0x00], :timeout => 15000, :delay_on_receive => 5000, :name => :power) - self[:frozen] = false - end - end - end - - def power?(options = {}, &block) - options[:emit] = block - do_send(STATUS_CODE[:system_status], options) - end - - def zoom_in - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:zoom_start]) - end - - def zoom_out - return if self[:frozen] - cancel_focus - do_send([COMMANDS[:zoom_start], 0x01]) - end - - def zoom_stop - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:zoom_stop]) - end - - def zoom(position) - return if self[:frozen] - cancel_focus - position = position.to_i - - position = in_range(position, self[:zoom_max]) - - - low = position & 0xFF - high = (position >> 8) & 0xFF - - do_send([COMMANDS[:zoom_direct], low, high], {:name => :zoom}) - end - - - def lamp(power) - return if self[:frozen] - power = is_affirmative?(power) - - if power - do_send([COMMANDS[:lamp], 0x01], {:name => :lamp}) - else - do_send([COMMANDS[:lamp], 0x00], {:name => :lamp}) - end - end - - - def sharp(state) - return if self[:frozen] - state = is_affirmative?(state) - - if state - do_send([COMMANDS[:sharp], 0x01], {:name => :sharp}) - else - do_send([COMMANDS[:sharp], 0x00], {:name => :sharp}) - end - end - - - def frozen(state) - state = is_affirmative?(state) - - if state - do_send([COMMANDS[:frozen], 0x01], {:name => :frozen}) - else - do_send([COMMANDS[:frozen], 0x00], {:name => :frozen}) - end - end - - - def auto_focus - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:auto_focus], :timeout => 8000, :name => :auto_focus) - end - - - def reset - return if self[:frozen] - cancel_focus - power(On) - - RESET_CODES.each_value do |value| - do_send(value) - end - - sharp(Off) - frozen(Off) - lamp(On) - zoom(0) - end - - - def received(data, reesolve, command) - logger.debug "Lumens sent #{byte_to_hex(data)}" - - - data = str_to_array(data) - - - # - # Process response - # - logger.debug "command was #{COMMANDS[data[0]]}" - case COMMANDS[data[0]] - when :zoom_stop - # - # A 3 second delay for zoom status and auto focus - # - zoom_status - delay_focus - when :zoom_direct - self[:zoom] = data[1] + (data[2] << 8) - delay_focus if COMMANDS[:zoom_direct] == data[0] # then 3 second delay for auto focus - when :lamp - self[:lamp] = data[1] == 0x01 - when :power - self[:power] = data[1] == 0x01 - if (self[:power] != self[:power_target]) && !self[:stable_state] - power(self[:power_target]) - logger.debug "Lumens state == unstable - power resp" - else - self[:stable_state] = true - self[:zoom] = self[:zoom_min] unless self[:power] - end - when :sharp - self[:sharp] = data[1] == 0x01 - when :frozen - self[:frozen] = data[1] == 0x01 - when :system_status - self[:power] = data[2] == 0x01 - if (self[:power] != self[:power_target]) && !self[:stable_state] - power(self[:power_target]) - logger.debug "Lumens state == unstable - status" - else - self[:stable_state] = true - self[:zoom] = self[:zoom_min] unless self[:power] - end - # ready = data[1] == 0x01 - end - - - # - # Check for error - # => We check afterwards as power for instance may be on when we call on - # => The power status is sent as on with a NAK as the command did nothing - # - if data[3] != 0x00 && (!!!self[:frozen]) - case data[3] - when 0x01 - logger.error "Lumens NAK error" - when 0x10 - logger.error "Lumens IGNORE error" - if command.present? - command[:delay_on_receive] = 2000 # update the command - return :abort # retry the command - # - # TODO:: Call system_status(0) and check for ready every second until the command will go through - # - end - else - logger.warn "Lumens unknown error code #{data[3]}" - end - - logger.error "Error on #{byte_to_hex(command[:data])}" unless command.nil? - return :abort - end - - - return :success - end - - - - private - - - def delay_focus - @focus_timer.cancel unless @focus_timer.nil? - @focus_timer = schedule.in('4s') do - auto_focus - end - end - - def cancel_focus - @focus_timer.cancel unless @focus_timer.nil? - end - - - - RESET_CODES = { - :OSD => [0x4B, 0x00], # p1 (00/01:Off/On) on screen display - :digital_zoom => [0x40, 0x00], # p1 (00/01:Disable/Enable) - :language => [0x38, 0x00], # p1 == 00 (english) - :colour => [0xA7, 0x00], # p1 (00/01:Photo/Gray) - :mode => [0xA9, 0x00], # P1 (00/01/02/03:Normal/Slide/Film/Microscope) - :logo => [0x47, 0x00], # p1 (00/01:Off/On) - :source => [0x3A, 0x00], # p1 (00/01:Live/PC) used for reset - :slideshow => [0x04, 0x00] # p1 (00/01:Off/On) -- NAKs - } - - - STATUS_CODE = { - :frozen_status => 0x78, # p1 = 0x00 - :sharp_status => 0x51, # p1 = 0x00 - :lamp_status => 0x50, # p1 = 0x00 - :zoom_status => 0x60, # p1 = 0x00 - :system_status => 0xB7 - } - # - # Automatically creates a callable function for each command - # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html - # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html - # - STATUS_CODE.each_key do |command| - define_method command do |*args| - priority = 99 - if args.length > 0 - priority = args[0] - end - - do_send(STATUS_CODE[command], {:priority => priority, :wait => true}) # Status polling is a low priority - end - end - - - def do_poll - power?(:priority => 99) do - if self[:power] == On - frozen_status - if not self[:frozen] - zoom_status - lamp_status - sharp_status - end - end - end - end - - - def do_send(command, options = {}) - #logger.debug "-- GlobalCache, sending: #{command}" - command = [command] unless command.is_a?(Array) - while command.length < 4 - command << 0x00 - end - - command = [0xA0] + command + [0xAF] - logger.debug "requesting #{byte_to_hex(command)}" - - send(command, options) - end -end \ No newline at end of file + def on_load + self[:zoom_max] = 855 + self[:zoom_min] = 0 + end + + def on_unload + end + + def on_update + end + + + + def connected + do_poll + schedule.every('60s') do + logger.debug "-- Polling Lumens DC Series Visualiser" + do_poll + end + end + + def disconnected + schedule.clear + end + + + + + COMMANDS = { + :zoom_stop => 0x10, # p1 = 0x00 + :zoom_start => 0x11, # p1 (00/01:Tele/Wide) + :zoom_direct => 0x13, # p1-LowByte, p2 highbyte (0~620) + :lamp => 0xC1, # p1 (00/01:Off/On) + :power => 0xB1, # p1 (00/01:Off/On) + :sharp => 0xA7, # p1 (00/01/02:Photo/Text/Gray) only photo or text + :auto_focus => 0xA3, # p1 = 0x01 + :frozen => 0x2C, # p1 (00/01:Off/On) + + 0x10 => :zoom_stop, + 0x11 => :zoom_start, + 0x13 => :zoom_direct, + 0xC1 => :lamp, + 0xB1 => :power, + 0xA7 => :sharp, + 0xA3 => :auto_focus, + 0x2C => :frozen, + + # Status response codes: + 0x78 => :frozen, + 0x51 => :sharp, + 0x50 => :lamp, + 0x60 => :zoom_direct, + 0xB7 => :system_status + } + + + def power(state) + state = is_affirmative?(state) + self[:power_target] = state + power? do + if state && !self[:power] # Request to power on if off + do_send([COMMANDS[:power], 0x01], :timeout => 15000, :delay_on_receive => 5000, :name => :power) + + elsif !state && self[:power] # Request to power off if on + do_send([COMMANDS[:power], 0x00], :timeout => 15000, :delay_on_receive => 5000, :name => :power) + self[:frozen] = false + end + end + end + + def power?(options = {}, &block) + options[:emit] = block + do_send(STATUS_CODE[:system_status], options) + end + + def zoom_in + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:zoom_start]) + end + + def zoom_out + return if self[:frozen] + cancel_focus + do_send([COMMANDS[:zoom_start], 0x01]) + end + + def zoom_stop + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:zoom_stop]) + end + + def zoom(position) + return if self[:frozen] + cancel_focus + position = position.to_i + + position = in_range(position, self[:zoom_max]) + + + low = position & 0xFF + high = (position >> 8) & 0xFF + + do_send([COMMANDS[:zoom_direct], low, high], {:name => :zoom}) + end + + + def lamp(power) + return if self[:frozen] + power = is_affirmative?(power) + + if power + do_send([COMMANDS[:lamp], 0x01], {:name => :lamp}) + else + do_send([COMMANDS[:lamp], 0x00], {:name => :lamp}) + end + end + + + def sharp(state) + return if self[:frozen] + state = is_affirmative?(state) + + if state + do_send([COMMANDS[:sharp], 0x01], {:name => :sharp}) + else + do_send([COMMANDS[:sharp], 0x00], {:name => :sharp}) + end + end + + + def frozen(state) + state = is_affirmative?(state) + + if state + do_send([COMMANDS[:frozen], 0x01], {:name => :frozen}) + else + do_send([COMMANDS[:frozen], 0x00], {:name => :frozen}) + end + end + + + def auto_focus + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:auto_focus], :timeout => 8000, :name => :auto_focus) + end + + + def reset + return if self[:frozen] + cancel_focus + power(On) + + RESET_CODES.each_value do |value| + do_send(value) + end + + sharp(Off) + frozen(Off) + lamp(On) + zoom(0) + end + + + def received(data, reesolve, command) + logger.debug "Lumens sent #{byte_to_hex(data)}" + + + data = str_to_array(data) + + + # + # Process response + # + logger.debug "command was #{COMMANDS[data[0]]}" + case COMMANDS[data[0]] + when :zoom_stop + # + # A 3 second delay for zoom status and auto focus + # + zoom_status + delay_focus + when :zoom_direct + self[:zoom] = data[1] + (data[2] << 8) + delay_focus if COMMANDS[:zoom_direct] == data[0] # then 3 second delay for auto focus + when :lamp + self[:lamp] = data[1] == 0x01 + when :power + self[:power] = data[1] == 0x01 + if (self[:power] != self[:power_target]) && !self[:power_target].nil? + power(self[:power_target]) + logger.debug "Lumens state == unstable - power resp" + else + self[:zoom] = self[:zoom_min] unless self[:power] + end + when :sharp + self[:sharp] = data[1] == 0x01 + when :frozen + self[:frozen] = data[1] == 0x01 + when :system_status + self[:power] = data[2] == 0x01 + if (self[:power] != self[:power_target]) && !self[:power_target].nil? + power(self[:power_target]) + logger.debug "Lumens state == unstable - status" + else + self[:zoom] = self[:zoom_min] unless self[:power] + end + # ready = data[1] == 0x01 + end + + + # + # Check for error + # => We check afterwards as power for instance may be on when we call on + # => The power status is sent as on with a NAK as the command did nothing + # + if data[3] != 0x00 && (!!!self[:frozen]) + case data[3] + when 0x01 + logger.error "Lumens NAK error" + when 0x10 + logger.error "Lumens IGNORE error" + if command.present? + command[:delay_on_receive] = 2000 # update the command + return :abort # retry the command + # + # TODO:: Call system_status(0) and check for ready every second until the command will go through + # + end + else + logger.warn "Lumens unknown error code #{data[3]}" + end + + logger.error "Error on #{byte_to_hex(command[:data])}" unless command.nil? + return :abort + end + + + return :success + end + + + + private + + + def delay_focus + @focus_timer.cancel unless @focus_timer.nil? + @focus_timer = schedule.in('4s') do + auto_focus + end + end + + def cancel_focus + @focus_timer.cancel unless @focus_timer.nil? + end + + + + RESET_CODES = { + :OSD => [0x4B, 0x00], # p1 (00/01:Off/On) on screen display + :digital_zoom => [0x40, 0x00], # p1 (00/01:Disable/Enable) + :language => [0x38, 0x00], # p1 == 00 (english) + :colour => [0xA7, 0x00], # p1 (00/01:Photo/Gray) + :mode => [0xA9, 0x00], # P1 (00/01/02/03:Normal/Slide/Film/Microscope) + :logo => [0x47, 0x00], # p1 (00/01:Off/On) + :source => [0x3A, 0x00], # p1 (00/01:Live/PC) used for reset + :slideshow => [0x04, 0x00] # p1 (00/01:Off/On) -- NAKs + } + + + STATUS_CODE = { + :frozen_status => 0x78, # p1 = 0x00 + :sharp_status => 0x51, # p1 = 0x00 + :lamp_status => 0x50, # p1 = 0x00 + :zoom_status => 0x60, # p1 = 0x00 + :system_status => 0xB7 + } + # + # Automatically creates a callable function for each command + # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html + # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html + # + STATUS_CODE.each_key do |command| + define_method command do |*args| + priority = 99 + if args.length > 0 + priority = args[0] + end + + do_send(STATUS_CODE[command], {:priority => priority, :wait => true}) # Status polling is a low priority + end + end + + + def do_poll + power?(:priority => 99) do + if self[:power] == On + frozen_status + if not self[:frozen] + zoom_status + lamp_status + sharp_status + end + end + end + end + + + def do_send(command, options = {}) + #logger.debug "-- GlobalCache, sending: #{command}" + command = [command] unless command.is_a?(Array) + while command.length < 4 + command << 0x00 + end + + command = [0xA0] + command + [0xAF] + logger.debug "requesting #{byte_to_hex(command)}" + + send(command, options) + end +end From 28ca7a4827632655d4a8521f56c57b2dd4640553 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 17 Jun 2019 08:23:02 +0000 Subject: [PATCH 1347/1752] o365/RBP: strip back to basic functions for readability --- modules/aca/office_booking.rb | 741 ++++++++-------------------------- 1 file changed, 158 insertions(+), 583 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index c1d1fe27..bd93255e 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -1,102 +1,27 @@ # encoding: ASCII-8BIT - require 'faraday' require 'uv-rays' require 'microsoft/office' Faraday.default_adapter = :libuv -# For rounding up to the nearest 15min -# See: http://stackoverflow.com/questions/449271/how-to-round-a-time-down-to-the-nearest-15-minutes-in-ruby -class ActiveSupport::TimeWithZone - def ceil(seconds = 60) - return self if seconds.zero? - Time.at(((self - self.utc_offset).to_f / seconds).ceil * seconds).in_time_zone + self.utc_offset - end -end - - module Aca; end - -# NOTE:: Requires Settings: -# ======================== -# room_alias: 'rs.au.syd.L16Aitken', -# building: 'DP3', -# level: '16' - class Aca::OfficeBooking include ::Orchestrator::Constants - EMAIL_CACHE = ::Concurrent::Map.new - CAN_LDAP = begin - require 'net/ldap' - true - rescue LoadError - false - end - CAN_OFFICE = begin - require 'oauth2' - true - rescue LoadError - false - end - - - descriptive_name 'Office365 Room Bookings' + descriptive_name 'Office365 Room Booking Panel Logic' generic_name :Bookings implements :logic + # Constants that the Room Booking Panel UI (ngx-bookings) will use + RBP_AUTOCANCEL_TRIGGERED = 'pending timeout' + RBP_STOP_PRESSED = 'user cancelled' # The room we are interested in default_settings({ update_every: '2m', - - # Moved to System or Zone Setting - # cancel_meeting_after: 900 - - # Card reader IDs if we want to listen for swipe events - card_readers: ['reader_id_1', 'reader_id_2'], - - # Optional LDAP creds for looking up emails - ldap_creds: { - host: 'ldap.org.com', - port: 636, - encryption: { - method: :simple_tls, - tls_options: { - verify_mode: 0 - } - }, - auth: { - method: :simple, - username: 'service account', - password: 'password' - } - }, - tree_base: "ou=User,ou=Accounts,dc=org,dc=com", - - # Optional EWS for creating and removing bookings - ews_creds: [ - 'https://company.com/EWS/Exchange.asmx', - 'service account', - 'password', - { http_opts: { ssl_verify_mode: 0 } } - ], - ews_room: 'room@email.address', - - # Optional EWS for creating and removing bookings - office_organiser_location: 'attendees' - # office_client_id: ENV["OFFICE_APP_CLIENT_ID"], - # office_secret: ENV["OFFICE_APP_CLIENT_SECRET"], - # office_scope: ENV['OFFICE_APP_SCOPE'], - # office_site: ENV["OFFICE_APP_SITE"], - # office_token_url: ENV["OFFICE_APP_TOKEN_URL"], - # office_options: { - # site: ENV["OFFICE_APP_SITE"], - # token_url: ENV["OFFICE_APP_TOKEN_URL"] - # }, - # office_room: 'room@email.address' + booking_cancel_email_message: 'The Stop button was presseed on the room booking panel', + booking_timeout_email_message: 'The Start button was not pressed on the room booking panel' }) - def on_load on_update end @@ -108,8 +33,6 @@ def on_update self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false - self[:name] = self[:room_name] = setting(:room_name) || system.name - self[:name] = self[:room_name] = setting(:room_name) || system.name self[:touch_enabled] = setting(:touch_enabled) || false self[:arrow_direction] = setting(:arrow_direction) @@ -124,6 +47,7 @@ def on_update self[:timeout] = setting(:timeout) self[:booking_cancel_timeout] = setting(:booking_cancel_timeout) self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) + self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) self[:booking_controls] = setting(:booking_controls) self[:booking_catering] = setting(:booking_catering) self[:booking_hide_details] = setting(:booking_hide_details) @@ -149,230 +73,78 @@ def on_update self[:booking_hide_all] = setting(:booking_hide_all) || false self[:hide_all] = setting(:booking_hide_all) || false # for backwards compatibility only - # Skype join button available 2min before the start of a meeting - @skype_start_offset = setting(:skype_start_offset) || 120 - @skype_check_offset = setting(:skype_check_offset) || 380 # 5min + 20 seconds - - # Skype join button not available in the last 8min of a meeting - @skype_end_offset = setting(:skype_end_offset) || 480 - @force_skype_extract = setting(:force_skype_extract) - - # Because restarting the modules results in a 'swipe' of the last read card - ignore_first_swipe = true - - # Is there catering available for this room? - self[:catering] = setting(:catering_system_id) - if self[:catering] - self[:menu] = setting(:menu) - end - - # Do we want to look up the users email address? - if CAN_LDAP - @ldap_creds = setting(:ldap_creds) - if @ldap_creds - encrypt = @ldap_creds[:encryption] - encrypt[:method] = encrypt[:method].to_sym if encrypt && encrypt[:method] - @tree_base = setting(:tree_base) - @ldap_user = @ldap_creds.delete :auth - end - else - logger.warn "net/ldap gem not available" if setting(:ldap_creds) - end - - # Do we want to use exchange web services to manage bookings - if CAN_OFFICE - logger.debug "Setting OFFICE" - @office_organiser_location = setting(:office_organiser_location) - @office_client_id = setting(:office_client_id) - @office_secret = setting(:office_secret) - @office_scope = setting(:office_scope) - @office_site = setting(:office_site) - @office_token_url = setting(:office_token_url) - @office_options = setting(:office_options) - @office_user_email = setting(:office_user_email) - @office_user_password = setting(:office_user_password) - @office_delegated = setting(:office_delegated) - @office_room = (setting(:office_room) || system.email) - # supports: SMTP, PSMTP, SID, UPN (user principle name) - # NOTE:: Using UPN we might be able to remove the LDAP requirement - @office_connect_type = (setting(:office_connect_type) || :SMTP).to_sym - @timezone = setting(:room_timezone) - - @client = ::Microsoft::Office.new({ - client_id: @office_client_id || ENV['OFFICE_CLIENT_ID'], - client_secret: @office_secret || ENV["OFFICE_CLIENT_SECRET"], - app_site: @office_site || ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", - app_token_url: @office_token_url || ENV["OFFICE_TOKEN_URL"], - app_scope: @office_scope || ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", - graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", - service_account_email: @office_user_password || ENV['OFFICE_ACCOUNT_EMAIL'], - service_account_password: @office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], - internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'] - }) - else - logger.warn "oauth2 gem not available" if setting(:office_creds) - end - - # Load the last known values (persisted to the DB) - self[:waiter_status] = (setting(:waiter_status) || :idle).to_sym - self[:waiter_call] = self[:waiter_status] != :idle - - self[:catering_status] = setting(:last_catering_status) || {} - self[:order_status] = :idle + logger.debug "Setting OFFICE" + @office_client_id = setting(:office_client_id) + @office_secret = setting(:office_secret) + @office_scope = setting(:office_scope) + @office_site = setting(:office_site) + @office_token_url = setting(:office_token_url) + @office_options = setting(:office_options) + @office_user_email = setting(:office_user_email) + @office_user_password = setting(:office_user_password) + @office_delegated = setting(:office_delegated) + @office_room = (setting(:office_room) || system.email) + @office_connect_type = (setting(:office_connect_type) || :SMTP).to_sym # supports: SMTP, PSMTP, SID, UPN (user principle name) + @timezone = setting(:room_timezone) + + @client = ::Microsoft::Office.new({ + client_id: @office_client_id || ENV['OFFICE_CLIENT_ID'], + client_secret: @office_secret || ENV["OFFICE_CLIENT_SECRET"], + app_site: @office_site || ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: @office_token_url || ENV["OFFICE_TOKEN_URL"], + app_scope: @office_scope || ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + service_account_email: @office_user_password || ENV['OFFICE_ACCOUNT_EMAIL'], + service_account_password: @office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], + internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'] + }) self[:last_meeting_started] = setting(:last_meeting_started) self[:cancel_meeting_after] = setting(:cancel_meeting_after) - - # unsubscribe to all swipe IDs if any are subscribed - if @subs.present? - @subs.each do |sub| - unsubscribe(sub) - end - - @subs = nil - end - - # Are there any swipe card integrations - if system.exists? :Security - readers = setting(:card_readers) - if readers.present? - security = system[:Security] - - readers = readers.is_a?(Array) ? readers : [readers] - sys = system - @subs = [] - readers.each do |id| - @subs << sys.subscribe(:Security, 1, id.to_s) do |notice| - if ignore_first_swipe - ignore_first_swipe = false - else - swipe_occured(notice.value) - end - end - end - end - end - fetch_bookings schedule.clear schedule.every(setting(:update_every) || '5m') { fetch_bookings } end - - def set_light_status(status) - lightbar = system[:StatusLight] - return if lightbar.nil? - - case status.to_sym - when :unavailable - lightbar.colour(:red) - when :available - lightbar.colour(:green) - when :pending - lightbar.colour(:orange) - else - lightbar.colour(:off) - end - end - - - # ====================================== - # Waiter call information - # ====================================== - def waiter_call(state) - status = is_affirmative?(state) - - self[:waiter_call] = status - - # Used to highlight the service button - if status - self[:waiter_status] = :pending - else - self[:waiter_status] = :idle - end - - define_setting(:waiter_status, self[:waiter_status]) - end - - def call_acknowledged - self[:waiter_status] = :accepted - define_setting(:waiter_status, self[:waiter_status]) + def fetch_bookings(*args) + response = @client.get_bookings_by_user(user_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] + self[:today] = expose_bookings(response) end - - # ====================================== - # Catering Management - # ====================================== - def catering_status(details) - self[:catering_status] = details - - # We'll turn off the green light on the waiter call button - if self[:waiter_status] != :idle && details[:progress] == 'visited' - self[:waiter_call] = false - self[:waiter_status] = :idle - define_setting(:waiter_status, self[:waiter_status]) + def create_meeting(options) + # Check that the required params exist + required_fields = ["start", "end"] + check = required_fields - options.keys + if check != [] + # There are missing required fields + logger.info "Required fields missing: #{check}" + raise "missing required fields: #{check}" end - define_setting(:last_catering_status, details) - end - - def commit_order(order_details) - self[:order_status] = :pending - status = self[:catering_status] - - if status && status[:progress] == 'visited' - status = status.dup - status[:progress] = 'cleaned' - self[:catering_status] = status - end + logger.debug "RBP>#{@office_room}>CREATE>INPUT:\n #{options}" + req_params = {} + req_params[:room_email] = @office_room + req_params[:organizer] = options.dig(:host, :email) || @office_room + req_params[:subject] = options[:title] + req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.to_i + req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.to_i - if self[:catering] - sys = system - @oid ||= 1 - systems(self[:catering])[:Orders].add_order({ - id: "#{sys.id}_#{@oid}", - created_at: Time.now.to_i, - room_id: sys.id, - room_name: sys.name, - order: order_details - }) + # TODO:: Catch error for booking failure + begin + id = create_o365_booking req_params + rescue Exception => e + logger.debug e.message + logger.debug e.backtrace.inspect + raise e end - end - - def order_accepted - self[:order_status] = :accepted - end - - def order_complete - self[:order_status] = :idle - end - - - - # ====================================== - # ROOM BOOKINGS: - # ====================================== - def fetch_bookings(*args) - # Make the request - response = @client.get_bookings_by_user(user_id: @office_room, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] - real_room = Orchestrator::ControlSystem.find(system.id) - if real_room.settings.key?('linked_rooms') - real_room.settings['linked_rooms'].each do |linked_room_id| - linked_room = Orchestrator::ControlSystem.find_by_id(linked_room_id) - if linked_room - response += @client.get_bookings_by_user(user_id: linked_room.email, start_param: Time.now.midnight, end_param: Time.now.tomorrow.midnight)[:bookings] - end - end + logger.debug { "successfully created booking: #{id}" } + schedule.in('2s') do + fetch_bookings end - self[:today] = todays_bookings(response, @office_organiser_location) + "Ok" end - - # ====================================== - # Meeting Helper Functions - # ====================================== - def start_meeting(meeting_ref) self[:last_meeting_started] = meeting_ref self[:meeting_pending] = meeting_ref @@ -381,24 +153,44 @@ def start_meeting(meeting_ref) define_setting(:last_meeting_started, meeting_ref) end - def cancel_meeting(start_time, reason = "timeout") - task { - if start_time.class == Integer - delete_ews_booking (start_time / 1000).to_i - else - # Converts to time object regardless of start_time being string or time object - start_time = Time.parse(start_time.to_s) - delete_ews_booking start_time.to_i + def cancel_meeting(start_time, reason = "unknown reason") + now = Time.now.to_i + start_epoch = Time.parse(start_time).to_i + ms_epoch = start_epoch * 1000 + too_early_to_cancel = now < start_epoch + too_late_to_cancel = now > start_epoch + (self[:booking_cancel_timeout] || self[:timeout]) + 180 # allow up to 3mins of slippage, in case endpoint is not NTP synced + bookings_to_cancel = bookings_with_start_time(start_epoch) + + if bookings_to_cancel == 1 + if reason == RBP_STOP_PRESSED + delete_o365_booking(start_epoch, reason) + elsif reason == RBP_AUTOCANCEL_TRIGGERED + if !too_early_to_cancel && !too_late_to_cancel + delete_o365_booking(start_epoch, reason) + else + logger.warn { "RBP>#{@office_room}>CANCEL>TOO_EARLY: Booking not cancelled with start time #{start_time}" } if too_early_to_cancel + logger.warn { "RBP>#{@office_room}>CANCEL>TOO_LATE: Booking not cancelled with start time #{start_time}" } if too_late_to_cancel + end + else # an unsupported reason, just cancel the booking and add support to this driver. + logger.error { "RBP>#{@office_room}>CANCEL>UNKNOWN_REASON: Cancelled booking with unknown reason, with start time #{start_time}" } + delete_o365_booking(start_epoch, reason) end - }.then(proc { |count| - logger.warn { "successfully removed #{count} bookings due to #{reason}" } - self[:last_meeting_started] = start_time - self[:meeting_pending] = start_time - self[:meeting_ending] = false - self[:meeting_pending_notice] = false - fetch_bookings - true - }) + else + logger.warn { "RBP>#{@office_room}>CANCEL>CLASH: No bookings cancelled as Multiple bookings (#{bookings_to_cancel}) were found with same start time #{start_time}" } if bookings_to_cancel > 1 + logger.warn { "RBP>#{@office_room}>CANCEL>NOT_FOUND: Could not find booking to cancel with start time #{start_time}" } if bookings_to_cancel == 0 + end + + self[:last_meeting_started] = ms_epoch + self[:meeting_pending] = ms_epoch + self[:meeting_ending] = false + self[:meeting_pending_notice] = false + true + end + + def bookings_with_start_time(start_epoch) + return 0 unless self[:today] + booking_start_times = self[:today]&.map { |b| Time.parse(b[:Start]).to_i } + return booking_start_times.count(start_epoch) end # If last meeting started !== meeting pending then @@ -425,292 +217,79 @@ def set_end_meeting_warning(meeting_ref = nil, extendable = false) def clear_end_meeting_warning self[:meeting_ending] = self[:last_meeting_started] end - # --------- - - def create_meeting(options) - # Check that the required params exist - required_fields = ["start", "end"] - check = required_fields - options.keys - if check != [] - # There are missing required fields - logger.info "Required fields missing: #{check}" - raise "missing required fields: #{check}" - end - - logger.debug "Passed Room options: --------------------" - logger.debug options - logger.debug options.to_json - - req_params = {} - req_params[:room_email] = @ews_room - req_params[:organizer] = options[:user_email] - req_params[:subject] = options[:title] - req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.to_i - req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.to_i - - - # TODO:: Catch error for booking failure - begin - id = make_office_booking req_params - rescue Exception => e - logger.debug e.message - logger.debug e.backtrace.inspect - raise e - end - - - logger.debug { "successfully created booking: #{id}" } - "Ok" - end - protected - - def swipe_occured(info) - # Update the user details - @last_swipe_at = Time.now.to_i - self[:fullname] = "#{info[:firstname]} #{info[:lastname]}" - self[:username] = info[:staff_id] - email = nil - - if self[:username] && @ldap_creds - email = EMAIL_CACHE[self[:username]] - if email - set_email(email) - logger.debug { "email #{email} found in cache" } - else - # Cache username here as self[:username] might change while we - # looking up the previous username - username = self[:username] - - logger.debug { "looking up email for #{username} - #{self[:fullname]}" } - task { - ldap_lookup_email username - }.then do |email| - if email - logger.debug { "email #{email} found in LDAP" } - EMAIL_CACHE[username] = email - set_email(email) - else - logger.warn "no email found in LDAP for #{username}" - set_email nil - end - end - end - else - logger.warn "no staff ID for user #{self[:fullname]}" - set_email nil - end - end - - def set_email(email) - self[:email] = email - self[:swiped] += 1 - end - - # ==================================== - # LDAP lookup to occur in worker thread - # ==================================== - def ldap_lookup_email(username) - email = EMAIL_CACHE[username] - return email if email - - ldap = Net::LDAP.new @ldap_creds - ldap.authenticate @ldap_user[:username], @ldap_user[:password] if @ldap_user - - login_filter = Net::LDAP::Filter.eq('sAMAccountName', username) - object_filter = Net::LDAP::Filter.eq('objectClass', '*') - treebase = @tree_base - search_attributes = ['mail'] - - email = nil - ldap.bind - ldap.search({ - base: treebase, - filter: object_filter & login_filter, - attributes: search_attributes - }) do |entry| - email = get_attr(entry, 'mail') - end - - # Returns email as a promise - EMAIL_CACHE[username] = email - email - end - - def get_attr(entry, attr_name) - if attr_name != "" && attr_name != nil - entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] - end - end - # ==================================== - - - # ======================================= - # EWS Requests to occur in a worker thread - # ======================================= - def make_office_booking(user_email: nil, subject: 'On the spot booking', room_email:, start_time:, end_time:, organizer:) - - STDERR.puts organizer - logger.info organizer - - STDERR.puts organizer.class - logger.info organizer.class - - STDERR.puts organizer.nil? - logger.info organizer.nil? - - STDERR.flush - - booking_data = { - subject: subject, - start: { dateTime: start_time, timeZone: "UTC" }, - end: { dateTime: end_time, timeZone: "UTC" }, - location: { displayName: @office_room, locationEmailAddress: @office_room }, - attendees: [ emailAddress: { address: organizer, name: "User"}] + def create_o365_booking(user_email: nil, subject: 'On the spot booking', room_email:, start_time:, end_time:, organizer:) + new_booking = { + subject: subject, + start: { dateTime: start_time, timeZone: "UTC" }, + end: { dateTime: end_time, timeZone: "UTC" }, + location: { displayName: @office_room, locationEmailAddress: @office_room }, + attendees: organizer ? [ emailAddress: { address: organizer, name: "User"}] : [] } - - if organizer.nil? - booking_data[:attendees] = [] + booking_json = new_booking.to_json + logger.debug "RBP>#{@office_room}>CREATE:\n #{booking_json}" + begin + result = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: {email: organizer, name: organizer}) + rescue => e + logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e}\nResponse:\n#{result}" + else + logger.debug "RBP>#{@office_room}>CREATE>SUCCESS:\n #{result}" end - - booking_data = booking_data.to_json - - logger.debug "Creating booking:" - logger.debug booking_data - - # client = OAuth2::Client.new(@office_client_id, @office_secret, {site: @office_site, token_url: @office_token_url}) - - # begin - # access_token = client.client_credentials.get_token({ - # :scope => @office_scope - # # :client_secret => ENV["OFFICE_APP_CLIENT_SECRET"], - # # :client_id => ENV["OFFICE_APP_CLIENT_ID"] - # }).token - # rescue Exception => e - # logger.debug e.message - # logger.debug e.backtrace.inspect - # raise e - # end - - - # # Set out domain, endpoint and content type - # domain = 'https://graph.microsoft.com' - # host = 'graph.microsoft.com' - # endpoint = "/v1.0/users/#{@office_room}/events" - # content_type = 'application/json;odata.metadata=minimal;odata.streaming=true' - - # # Create the request URI and config - # office_api = UV::HttpEndpoint.new(domain, tls_options: {host_name: host}) - # headers = { - # 'Authorization' => "Bearer #{access_token}", - # 'Content-Type' => content_type - # } - - # Make the request - - # response = office_api.post(path: "#{domain}#{endpoint}", body: booking_data, headers: headers).value - response = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: {email: organizer, name: organizer}) - STDERR.puts "BOOKING SIP CREATE RESPONSE:" - STDERR.puts response.inspect - STDERR.puts response['id'] - STDERR.flush - - id = response['id'] - - # Return the booking IDs - id + result['id'] end - def delete_ews_booking(delete_at) - count = 0 - delete_at_object = Time.at(delete_at) - if self[:today] - self[:today].each_with_index do |booking, i| - booking_start_object = Time.parse(booking[:Start]) - if delete_at_object.to_i == booking_start_object.to_i + def delete_o365_booking(delete_start_epoch, reason) + bookings_deleted = 0 + return bookings_deleted unless self[:today] # Exist if no bookings + delete_start_time = Time.at(delete_start_epoch) + + self[:today].each_with_index do |booking, i| + booking_start_epoch = Time.parse(booking[:Start]).to_i + if booking[:isAllDay] + logger.warn { "RBP>#{@office_room}>CANCEL>ALL_DAY: An All Day booking was NOT deleted, with start time #{delete_start_epoch}" } + elsif booking[:email] == @office_room # Bookings owned by the room need to be deleted, not declined + # INSERT DELETE BOOKING HERE (not desired for AIA) + logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: A booking owned by the room was NOT deleted, with start time #{delete_start_epoch}" } + elsif booking_start_epoch == delete_start_epoch + # Decline the meeting, with the appropriate message to the booker + case reason + when RBP_AUTOCANCEL_TRIGGERED + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_timeout_email_message]) + when RBP_STOP_PRESSED response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_cancel_email_message]) - if response == 200 - count += 1 - self[:today].delete(i) - end + else + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: "The booking was cancelled due to \"#{reason}\" ") + end + logger.warn { "RBP>#{@office_room}>CANCEL>SUCCESS: Declined booking due to \"#{reason}\", with start time #{delete_start_epoch}" } + if response == 200 + bookings_deleted += 1 + # self[:today].delete_at(i) This does not seem to notify the websocket, so call fetch_bookings instead + fetch_bookings end end end - # Return the number of meetings removed - count + bookings_deleted end - def todays_bookings(response, office_organiser_location) + def expose_bookings(bookings) results = [] + bookings.each{ |booking| + tz = ActiveSupport::TimeZone.new(booking['start']['timeZone']) # in tz database format: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + start_utc = tz.parse(booking['start']['dateTime']).utc # output looks like: "2019-05-21 15:50:00 UTC" + end_utc = tz.parse(booking['end']['dateTime']).utc + start_time = start_utc.iso8601 # output looks like: "2019-05-21T15:50:00Z+08:00" + end_time = end_utc.iso8601 - set_skype_url = true if @force_skype_extract - now_int = Time.now.to_i - - response.each{ |booking| - if booking['start'].key?("timeZone") - real_start = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['start']['dateTime']).utc - real_end = ActiveSupport::TimeZone.new(booking['start']['timeZone']).parse(booking['end']['dateTime']).utc - start_time = real_start.iso8601 - end_time = real_end.iso8601 - else - # TODO:: this needs review. Does MS ever respond without a timeZone? - real_start = Time.parse(booking['start']['dateTime']).utc - real_end = Time.parse(booking['end']['dateTime']).utc - start_time = real_start.iso8601[0..18] + 'Z' - end_time = real_end.iso8601[0..18] + 'Z' - end - - #logger.debug { "\n\ninspecting booking:\n#{booking.inspect}\n\n" } - # Get body data: booking['body'].values.join("") - - # Extract the skype meeting URL - if set_skype_url - start_integer = real_start.to_i - @skype_check_offset - join_integer = real_start.to_i - @skype_start_offset - end_integer = real_end.to_i - @skype_end_offset - - if now_int > start_integer && now_int < end_integer && booking['body'] - body = booking['body'].values.join('') - match = body.match(/\"pexip\:\/\/(.+?)\"/) - if match - set_skype_url = false - self[:pexip_meeting_uid] = start_integer - self[:pexip_meeting_address] = match[1] - else - links = URI.extract(body).select { |url| url.start_with?('https://meet.lync') } - if links[0].present? - if now_int > join_integer - self[:can_join_skype_meeting] = true - self[:skype_meeting_pending] = true - else - self[:skype_meeting_pending] = true - end - set_skype_url = false - self[:skype_meeting_address] = links[0] - end - end - end - end - - if office_organiser_location == 'attendees' - # Grab the first attendee - if booking.key?('attendees') && !booking['attendees'].empty? - organizer = booking['attendees'][0]['name'] - else - organizer = "" - end - else - # Grab the organiser - organizer = booking['organizer']['name'] - end + name = booking.dig('organizer','name') || booking.dig('attendees',0,'name') + email = booking.dig('organizer','email') || booking.dig('attendees',0,'email') subject = booking['subject'] - if booking.key?('sensitivity') && ['private','confidential'].include?(booking['sensitivity']) - organizer = "Private" + if ['private','confidential'].include?(booking['sensitivity']) + name = "Private" subject = "Private" end @@ -719,17 +298,13 @@ def todays_bookings(response, office_organiser_location) :End => end_time, :Subject => subject, :id => booking['id'], - :owner => organizer + :icaluid => booking['icaluid'], + :owner => name, + :email => email, + :organizer => {:name => name, :email => email}, + :isAllDay => booking['isAllDay'] }) } - results end - - def log(msg) - STDERR.puts msg - logger.info msg - STDERR.flush - end - # ======================================= -end +end \ No newline at end of file From 013609221554be9687b3a3fed1a6f59e76f5deda Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 17 Jun 2019 08:34:37 +0000 Subject: [PATCH 1348/1752] o365/RBP: bookings owned by room can be deleted --- modules/aca/office_booking.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index bd93255e..c0e02562 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -251,8 +251,8 @@ def delete_o365_booking(delete_start_epoch, reason) if booking[:isAllDay] logger.warn { "RBP>#{@office_room}>CANCEL>ALL_DAY: An All Day booking was NOT deleted, with start time #{delete_start_epoch}" } elsif booking[:email] == @office_room # Bookings owned by the room need to be deleted, not declined - # INSERT DELETE BOOKING HERE (not desired for AIA) - logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: A booking owned by the room was NOT deleted, with start time #{delete_start_epoch}" } + response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) + logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: A booking owned by the room was deleted, with start time #{delete_start_epoch}" } elsif booking_start_epoch == delete_start_epoch # Decline the meeting, with the appropriate message to the booker case reason From e2f915bd1f7213ca37391bf60d8034a3d6cbb112 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 18 Jun 2019 12:16:45 +1000 Subject: [PATCH 1349/1752] (lib:cisco cmx) the CMX v2 API has changed again... why even bother with the versioning cisco? --- lib/cisco/cmx.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cisco/cmx.rb b/lib/cisco/cmx.rb index e1594fdb..de981bb7 100755 --- a/lib/cisco/cmx.rb +++ b/lib/cisco/cmx.rb @@ -42,7 +42,7 @@ def locate(user: nil, ip: nil, mac: nil, sort: 'lastLocatedTime:DESC') def perform(query) resp = @host.get(path: @path, headers: @headers, query: query).value - return nil if resp.status == 204 + return nil if (200...300).include?(resp.status) raise "request failed #{resp.status}\n#{resp.body}" unless (200...300).include?(resp.status) locations = JSON.parse(resp.body, symbolize_names: true) From a1bb7109092049a94479adfd73241f27335432d0 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 18 Jun 2019 07:58:15 +0000 Subject: [PATCH 1350/1752] o365/RBP: Remove deprecated status/settings bindings: name,timeout,hide_all --- modules/aca/office_booking.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index c0e02562..ddd91f61 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -33,8 +33,6 @@ def on_update self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false - self[:name] = self[:room_name] = setting(:room_name) || system.name - self[:touch_enabled] = setting(:touch_enabled) || false self[:arrow_direction] = setting(:arrow_direction) self[:hearing_assistance] = setting(:hearing_assistance) self[:timeline_start] = setting(:timeline_start) @@ -44,7 +42,6 @@ def on_update self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url - self[:timeout] = setting(:timeout) self[:booking_cancel_timeout] = setting(:booking_cancel_timeout) self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) @@ -71,7 +68,6 @@ def on_update self[:booking_default_title] = setting(:booking_default_title) self[:booking_select_free] = setting(:booking_select_free) self[:booking_hide_all] = setting(:booking_hide_all) || false - self[:hide_all] = setting(:booking_hide_all) || false # for backwards compatibility only logger.debug "Setting OFFICE" @office_client_id = setting(:office_client_id) From cf22f40cf556e1e01f84afeb759277ed700ec4a1 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 18 Jun 2019 08:39:35 +0000 Subject: [PATCH 1351/1752] o365/RBP: bookings_cancel_timeout format to (e.g.) '1m2s' instead of '62' --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index ddd91f61..42077ea6 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -42,7 +42,7 @@ def on_update self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url - self[:booking_cancel_timeout] = setting(:booking_cancel_timeout) + self[:booking_cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) self[:booking_controls] = setting(:booking_controls) From bbf0e3b03186a6f6c256ecfb1874a37063c145af Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 18 Jun 2019 08:48:48 +0000 Subject: [PATCH 1352/1752] o365/RBP: remove unused vars --- modules/aca/office_booking.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 42077ea6..88fcef46 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -75,13 +75,9 @@ def on_update @office_scope = setting(:office_scope) @office_site = setting(:office_site) @office_token_url = setting(:office_token_url) - @office_options = setting(:office_options) @office_user_email = setting(:office_user_email) @office_user_password = setting(:office_user_password) - @office_delegated = setting(:office_delegated) @office_room = (setting(:office_room) || system.email) - @office_connect_type = (setting(:office_connect_type) || :SMTP).to_sym # supports: SMTP, PSMTP, SID, UPN (user principle name) - @timezone = setting(:room_timezone) @client = ::Microsoft::Office.new({ client_id: @office_client_id || ENV['OFFICE_CLIENT_ID'], From a776ab06bac5d79c9f90019700327a0f086976e3 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 18 Jun 2019 08:49:51 +0000 Subject: [PATCH 1353/1752] o365/RBP: fix o365 client account email --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 88fcef46..9e41d4dd 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -86,7 +86,7 @@ def on_update app_token_url: @office_token_url || ENV["OFFICE_TOKEN_URL"], app_scope: @office_scope || ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", - service_account_email: @office_user_password || ENV['OFFICE_ACCOUNT_EMAIL'], + service_account_email: @office_user_email || ENV['OFFICE_ACCOUNT_EMAIL'], service_account_password: @office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'] }) From 67f9856f1492347463c9f0703b46c50886a43600 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 18 Jun 2019 08:58:15 +0000 Subject: [PATCH 1354/1752] o365/RBP: limit scope of office client vars; rename http proxy var --- modules/aca/office_booking.rb | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 9e41d4dd..540209b7 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -70,25 +70,26 @@ def on_update self[:booking_hide_all] = setting(:booking_hide_all) || false logger.debug "Setting OFFICE" - @office_client_id = setting(:office_client_id) - @office_secret = setting(:office_secret) - @office_scope = setting(:office_scope) - @office_site = setting(:office_site) - @office_token_url = setting(:office_token_url) - @office_user_email = setting(:office_user_email) - @office_user_password = setting(:office_user_password) + office_client_id = setting(:office_client_id) + office_secret = setting(:office_secret) + office_scope = setting(:office_scope) + office_site = setting(:office_site) + office_token_url = setting(:office_token_url) + office_user_email = setting(:office_user_email) + office_user_password = setting(:office_user_password) @office_room = (setting(:office_room) || system.email) + office_https_proxy = setting(:office_https_proxy) @client = ::Microsoft::Office.new({ - client_id: @office_client_id || ENV['OFFICE_CLIENT_ID'], - client_secret: @office_secret || ENV["OFFICE_CLIENT_SECRET"], - app_site: @office_site || ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", - app_token_url: @office_token_url || ENV["OFFICE_TOKEN_URL"], - app_scope: @office_scope || ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", - graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", - service_account_email: @office_user_email || ENV['OFFICE_ACCOUNT_EMAIL'], - service_account_password: @office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], - internet_proxy: @internet_proxy || ENV['INTERNET_PROXY'] + client_id: office_client_id || ENV['OFFICE_CLIENT_ID'], + client_secret: office_secret || ENV["OFFICE_CLIENT_SECRET"], + app_site: office_site || ENV["OFFICE_SITE"] || "https://login.microsoftonline.com", + app_token_url: office_token_url || ENV["OFFICE_TOKEN_URL"], + app_scope: office_scope || ENV['OFFICE_SCOPE'] || "https://graph.microsoft.com/.default", + graph_domain: ENV['GRAPH_DOMAIN'] || "https://graph.microsoft.com", + service_account_email: office_user_email || ENV['OFFICE_ACCOUNT_EMAIL'], + service_account_password: office_user_password || ENV['OFFICE_ACCOUNT_PASSWORD'], + internet_proxy: office_https_proxy || ENV['INTERNET_PROXY'] }) self[:last_meeting_started] = setting(:last_meeting_started) From d75780cd75c50a5f5a0ab1c2e32246b607c5d03e Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 18 Jun 2019 09:01:52 +0000 Subject: [PATCH 1355/1752] o365/RBP: improve log output for o365 client init --- modules/aca/office_booking.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 540209b7..28aaf2b7 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -69,7 +69,6 @@ def on_update self[:booking_select_free] = setting(:booking_select_free) self[:booking_hide_all] = setting(:booking_hide_all) || false - logger.debug "Setting OFFICE" office_client_id = setting(:office_client_id) office_secret = setting(:office_secret) office_scope = setting(:office_scope) @@ -80,6 +79,8 @@ def on_update @office_room = (setting(:office_room) || system.email) office_https_proxy = setting(:office_https_proxy) + logger.debug "RBP>#{@office_room}>INIT: Instantiating o365 Graph API client" + @client = ::Microsoft::Office.new({ client_id: office_client_id || ENV['OFFICE_CLIENT_ID'], client_secret: office_secret || ENV["OFFICE_CLIENT_SECRET"], From fb8c2970ef14dc3995a73a8038bf2191d052be73 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 18 Jun 2019 09:58:54 +0000 Subject: [PATCH 1356/1752] o365/RBP: reinstant "room_name" (required by ngx-bookings), remove "title" (avoid confusion, not used) --- modules/aca/office_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 28aaf2b7..8509a643 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -31,13 +31,13 @@ def on_update @last_swipe_at = 0 @use_act_as = setting(:use_act_as) + self[:room_name] = setting(:room_name) || system.name self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false self[:arrow_direction] = setting(:arrow_direction) self[:hearing_assistance] = setting(:hearing_assistance) self[:timeline_start] = setting(:timeline_start) self[:timeline_end] = setting(:timeline_end) - self[:title] = setting(:title) self[:description] = setting(:description) self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url From 008fd78b1297e0e98990b20785e4a57644f23f8f Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 25 Jun 2019 08:16:25 +1000 Subject: [PATCH 1357/1752] (cisco:broadworks) add virtual system --- modules/cisco/broad_works_virtual.ts | 80 ++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 modules/cisco/broad_works_virtual.ts diff --git a/modules/cisco/broad_works_virtual.ts b/modules/cisco/broad_works_virtual.ts new file mode 100644 index 00000000..26219c58 --- /dev/null +++ b/modules/cisco/broad_works_virtual.ts @@ -0,0 +1,80 @@ + +window.control.systems['sys-call-center'] = { + CallCenter: [{ + queues: { + "Police": { + queue_length: 1, + abandoned: 0, + total_calls: 8, + // Asssuming seconds here + average_wait: 12, + max_wait: 36, + average_talk: 540, + on_calls: 1 + }, + "Injury": { + queue_length: 2, + abandoned: 1, + total_calls: 16, + // Asssuming seconds here + average_wait: 24, + max_wait: 91, + average_talk: 238, + on_calls: 2 + }, + "Incident": { + queue_length: 0, + abandoned: 0, + total_calls: 1, + // Asssuming seconds here + average_wait: 5, + max_wait: 5, + average_talk: 49, + on_calls: 0 + }, + "Specialist Support": { + queue_length: 0, + abandoned: 0, + total_calls: 1, + // Asssuming seconds here + average_wait: 5, + max_wait: 5, + average_talk: 49, + on_calls: 0 + }, + "E-Safety": { + queue_length: 0, + abandoned: 0, + total_calls: 1, + // Asssuming seconds here + average_wait: 5, + max_wait: 5, + average_talk: 49, + on_calls: 1 + }, + "Inquires": { + queue_length: 0, + abandoned: 0, + total_calls: 2, + // Asssuming seconds here + average_wait: 7, + max_wait: 8, + average_talk: 180, + on_calls: 1 + }, + "Media": { + queue_length: 0, + abandoned: 0, + total_calls: 1, + // Asssuming seconds here + average_wait: 5, + max_wait: 5, + average_talk: 49, + on_calls: 0 + } + }, + total_abandoned: 3, + longest_wait: 125, + longest_talk: 945 + }] +}; From 21fddcf4f5068d915ae6a63543de6c75dab146c9 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 25 Jun 2019 08:31:06 +0000 Subject: [PATCH 1358/1752] o365/client: add default app site, scope, domain --- lib/microsoft/office/client.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index 73856397..c1f5a57e 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -40,10 +40,10 @@ class Microsoft::Officenew::Client def initialize( client_id:, client_secret:, - app_site:, app_token_url:, - app_scope:, - graph_domain:, + app_site: "https://login.microsoftonline.com", + app_scope: "https://graph.microsoft.com/.default", + graph_domain: "https://graph.microsoft.com", save_token: Proc.new{ |token| User.bucket.set("office-token", token) }, get_token: Proc.new{ User.bucket.get("office-token", quiet: true) } ) From 1eed8e52177537b48ed4f3d2957c2f2eb29cee10 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 27 Jun 2019 08:34:22 +0000 Subject: [PATCH 1359/1752] o365/RBP: REPLACE Old driver! Use new Office library --- modules/aca/o365_booking_panel.rb | 300 ++++++++++++++++++++++++++++++ modules/aca/office_booking.rb | 2 +- 2 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 modules/aca/o365_booking_panel.rb diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb new file mode 100644 index 00000000..25810067 --- /dev/null +++ b/modules/aca/o365_booking_panel.rb @@ -0,0 +1,300 @@ +# encoding: ASCII-8BIT +require 'faraday' +require 'uv-rays' +require 'microsoft/officenew' +require 'microsoft/office/client' +Faraday.default_adapter = :libuv + +module Aca; end +class Aca::O365BookingPanel + include ::Orchestrator::Constants + descriptive_name 'Office365 Room Booking Panel Logic' + generic_name :Bookings + implements :logic + + # Constants that the Room Booking Panel UI (ngx-bookings) will use + RBP_AUTOCANCEL_TRIGGERED = 'pending timeout' + RBP_STOP_PRESSED = 'user cancelled' + + # The room we are interested in + default_settings({ + update_every: '2m', + booking_cancel_email_message: 'The Stop button was presseed on the room booking panel', + booking_timeout_email_message: 'The Start button was not pressed on the room booking panel' + }) + + def on_load + on_update + end + + def on_update + self[:swiped] ||= 0 + @last_swipe_at = 0 + @use_act_as = setting(:use_act_as) + + self[:room_name] = setting(:room_name) || system.name + self[:hide_all] = setting(:hide_all) || false + self[:touch_enabled] = setting(:touch_enabled) || false + self[:arrow_direction] = setting(:arrow_direction) + self[:hearing_assistance] = setting(:hearing_assistance) + self[:timeline_start] = setting(:timeline_start) + self[:timeline_end] = setting(:timeline_end) + self[:description] = setting(:description) + self[:icon] = setting(:icon) + self[:control_url] = setting(:booking_control_url) || system.config.support_url + + self[:booking_cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' + self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) + self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) + self[:booking_controls] = setting(:booking_controls) + self[:booking_catering] = setting(:booking_catering) + self[:booking_hide_details] = setting(:booking_hide_details) + self[:booking_hide_availability] = setting(:booking_hide_availability) + self[:booking_hide_user] = setting(:booking_hide_user) + self[:booking_hide_modal] = setting(:booking_hide_modal) + self[:booking_hide_title] = setting(:booking_hide_title) + self[:booking_hide_description] = setting(:booking_hide_description) + self[:booking_hide_timeline] = setting(:booking_hide_timeline) + self[:booking_set_host] = setting(:booking_set_host) + self[:booking_set_title] = setting(:booking_set_title) + self[:booking_set_ext] = setting(:booking_set_ext) + self[:booking_search_user] = setting(:booking_search_user) + self[:booking_disable_future] = setting(:booking_disable_future) + self[:booking_min_duration] = setting(:booking_min_duration) + self[:booking_max_duration] = setting(:booking_max_duration) + self[:booking_duration_step] = setting(:booking_duration_step) + self[:booking_endable] = setting(:booking_endable) + self[:booking_ask_cancel] = setting(:booking_ask_cancel) + self[:booking_ask_end] = setting(:booking_ask_end) + self[:booking_default_title] = setting(:booking_default_title) + self[:booking_select_free] = setting(:booking_select_free) + self[:booking_hide_all] = setting(:booking_hide_all) || false + + office_client_id = setting(:office_client_id) + office_secret = setting(:office_secret) + office_scope = setting(:office_scope) + office_site = setting(:office_site) + office_token_url = setting(:office_token_url) + office_user_email = setting(:office_user_email) + office_user_password = setting(:office_user_password) + @office_room = (setting(:office_room) || system.email) + office_https_proxy = setting(:office_https_proxy) + + logger.debug "RBP>#{@office_room}>INIT: Instantiating o365 Graph API client" + + @client = ::Microsoft::Officenew::Client.new({ + client_id: office_client_id || ENV['OFFICE_CLIENT_ID'], + client_secret: office_secret || ENV["OFFICE_CLIENT_SECRET"], + app_token_url: office_token_url || ENV["OFFICE_TOKEN_URL"] + }) + + self[:last_meeting_started] = setting(:last_meeting_started) + self[:cancel_meeting_after] = setting(:cancel_meeting_after) + + fetch_bookings + schedule.clear + schedule.every(setting(:update_every) || '5m') { fetch_bookings } + end + + def fetch_bookings(*args) + response = @client.get_bookings(mailboxes: [@office_room], options: {bookings_from: Time.now.midnight, bookings_to: Time.now.tomorrow.midnight}).dig(@office_room, :bookings) + puts "==============================\n#{response}\n===============================" + self[:today] = expose_bookings(response) + end + + def create_meeting(options) + # Check that the required params exist + required_fields = ["start", "end"] + check = required_fields - options.keys + if check != [] + # There are missing required fields + logger.info "Required fields missing: #{check}" + raise "missing required fields: #{check}" + end + + logger.debug "RBP>#{@office_room}>CREATE>INPUT:\n #{options}" + req_params = {} + req_params[:room_email] = @office_room + req_params[:organizer] = options.dig(:host, :email) || @office_room + req_params[:subject] = options[:title] + req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.to_i + req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.to_i + + # TODO:: Catch error for booking failure + begin + id = create_o365_booking req_params + rescue Exception => e + logger.debug e.message + logger.debug e.backtrace.inspect + raise e + end + logger.debug { "successfully created booking: #{id}" } + schedule.in('2s') do + fetch_bookings + end + "Ok" + end + + def start_meeting(meeting_ref) + self[:last_meeting_started] = meeting_ref + self[:meeting_pending] = meeting_ref + self[:meeting_ending] = false + self[:meeting_pending_notice] = false + define_setting(:last_meeting_started, meeting_ref) + end + + def cancel_meeting(start_time, reason = "unknown reason") + now = Time.now.to_i + start_epoch = Time.parse(start_time).to_i + ms_epoch = start_epoch * 1000 + too_early_to_cancel = now < start_epoch + too_late_to_cancel = now > start_epoch + (self[:booking_cancel_timeout] || self[:timeout]) + 180 # allow up to 3mins of slippage, in case endpoint is not NTP synced + bookings_to_cancel = bookings_with_start_time(start_epoch) + + if bookings_to_cancel == 1 + if reason == RBP_STOP_PRESSED + delete_o365_booking(start_epoch, reason) + elsif reason == RBP_AUTOCANCEL_TRIGGERED + if !too_early_to_cancel && !too_late_to_cancel + delete_o365_booking(start_epoch, reason) + else + logger.warn { "RBP>#{@office_room}>CANCEL>TOO_EARLY: Booking not cancelled with start time #{start_time}" } if too_early_to_cancel + logger.warn { "RBP>#{@office_room}>CANCEL>TOO_LATE: Booking not cancelled with start time #{start_time}" } if too_late_to_cancel + end + else # an unsupported reason, just cancel the booking and add support to this driver. + logger.error { "RBP>#{@office_room}>CANCEL>UNKNOWN_REASON: Cancelled booking with unknown reason, with start time #{start_time}" } + delete_o365_booking(start_epoch, reason) + end + else + logger.warn { "RBP>#{@office_room}>CANCEL>CLASH: No bookings cancelled as Multiple bookings (#{bookings_to_cancel}) were found with same start time #{start_time}" } if bookings_to_cancel > 1 + logger.warn { "RBP>#{@office_room}>CANCEL>NOT_FOUND: Could not find booking to cancel with start time #{start_time}" } if bookings_to_cancel == 0 + end + + self[:last_meeting_started] = ms_epoch + self[:meeting_pending] = ms_epoch + self[:meeting_ending] = false + self[:meeting_pending_notice] = false + true + end + + def bookings_with_start_time(start_epoch) + return 0 unless self[:today] + booking_start_times = self[:today]&.map { |b| Time.parse(b[:Start]).to_i } + return booking_start_times.count(start_epoch) + end + + # If last meeting started !== meeting pending then + # we'll show a warning on the in room touch panel + def set_meeting_pending(meeting_ref) + self[:meeting_ending] = false + self[:meeting_pending] = meeting_ref + self[:meeting_pending_notice] = true + end + + # Meeting ending warning indicator + # (When meeting_ending !== last_meeting_started then the warning hasn't been cleared) + # The warning is only displayed when meeting_ending === true + def set_end_meeting_warning(meeting_ref = nil, extendable = false) + if self[:last_meeting_started].nil? || self[:meeting_ending] != (meeting_ref || self[:last_meeting_started]) + self[:meeting_ending] = true + + # Allows meeting ending warnings in all rooms + self[:last_meeting_started] = meeting_ref if meeting_ref + self[:meeting_canbe_extended] = extendable + end + end + + def clear_end_meeting_warning + self[:meeting_ending] = self[:last_meeting_started] + end + + protected + + def create_o365_booking(user_email: nil, subject: 'On the spot booking', room_email:, start_time:, end_time:, organizer:) + new_booking = { + subject: subject, + start: { dateTime: start_time, timeZone: "UTC" }, + end: { dateTime: end_time, timeZone: "UTC" }, + location: { displayName: @office_room, locationEmailAddress: @office_room }, + attendees: organizer ? [ emailAddress: { address: organizer, name: "User"}] : [] + } + + booking_json = new_booking.to_json + logger.debug "RBP>#{@office_room}>CREATE:\n #{booking_json}" + begin + result = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: {email: organizer, name: organizer}) + rescue => e + logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e}\nResponse:\n#{result}" + else + logger.debug "RBP>#{@office_room}>CREATE>SUCCESS:\n #{result}" + end + result['id'] + end + + def delete_o365_booking(delete_start_epoch, reason) + bookings_deleted = 0 + return bookings_deleted unless self[:today] # Exist if no bookings + delete_start_time = Time.at(delete_start_epoch) + + self[:today].each_with_index do |booking, i| + booking_start_epoch = Time.parse(booking[:Start]).to_i + if booking[:isAllDay] + logger.warn { "RBP>#{@office_room}>CANCEL>ALL_DAY: An All Day booking was NOT deleted, with start time #{delete_start_epoch}" } + elsif booking[:email] == @office_room # Bookings owned by the room need to be deleted, not declined + response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) + logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: A booking owned by the room was deleted, with start time #{delete_start_epoch}" } + elsif booking_start_epoch == delete_start_epoch + # Decline the meeting, with the appropriate message to the booker + case reason + when RBP_AUTOCANCEL_TRIGGERED + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_timeout_email_message]) + when RBP_STOP_PRESSED + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_cancel_email_message]) + else + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: "The booking was cancelled due to \"#{reason}\" ") + end + logger.warn { "RBP>#{@office_room}>CANCEL>SUCCESS: Declined booking due to \"#{reason}\", with start time #{delete_start_epoch}" } + if response == 200 + bookings_deleted += 1 + # self[:today].delete_at(i) This does not seem to notify the websocket, so call fetch_bookings instead + fetch_bookings + end + end + end + # Return the number of meetings removed + bookings_deleted + end + + def expose_bookings(bookings) + results = [] + bookings.each{ |booking| + tz = ActiveSupport::TimeZone.new(booking['start']['timeZone']) # in tz database format: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + start_utc = tz.parse(booking['start']['dateTime']).utc # output looks like: "2019-05-21 15:50:00 UTC" + end_utc = tz.parse(booking['end']['dateTime']).utc + start_time = start_utc.iso8601 # output looks like: "2019-05-21T15:50:00Z+08:00" + end_time = end_utc.iso8601 + + name = booking.dig('organizer','name') || booking.dig('attendees',0,'name') + email = booking.dig('organizer','email') || booking.dig('attendees',0,'email') + + subject = booking['subject'] + if ['private','confidential'].include?(booking['sensitivity']) + name = "Private" + subject = "Private" + end + + results.push({ + :Start => start_time, + :End => end_time, + :Subject => subject, + :id => booking['id'], + :icaluid => booking['icaluid'], + :owner => name, + :email => email, + :organizer => {:name => name, :email => email}, + :isAllDay => booking['isAllDay'] + }) + } + results + end +end \ No newline at end of file diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 8509a643..19781da7 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -7,7 +7,7 @@ module Aca; end class Aca::OfficeBooking include ::Orchestrator::Constants - descriptive_name 'Office365 Room Booking Panel Logic' + descriptive_name 'Deprecated Office365 Room Booking Panel Logic' generic_name :Bookings implements :logic From 36f8e05d8716787dd06b0a74c5fb6337dbae3303 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 27 Jun 2019 08:44:07 +0000 Subject: [PATCH 1360/1752] o365/RBP: Fix fetch bookings --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 25810067..9003922d 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -97,7 +97,7 @@ def on_update end def fetch_bookings(*args) - response = @client.get_bookings(mailboxes: [@office_room], options: {bookings_from: Time.now.midnight, bookings_to: Time.now.tomorrow.midnight}).dig(@office_room, :bookings) + response = @client.get_bookings(mailboxes: [@office_room], options: {bookings_from: Time.now.midnight.to_i, bookings_to: Time.now.tomorrow.midnight.to_i}).dig(@office_room, :bookings) puts "==============================\n#{response}\n===============================" self[:today] = expose_bookings(response) end From 8c9ea97a17fe9d65b23cf42f3b1cf034e1196cf9 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 27 Jun 2019 10:23:11 +0000 Subject: [PATCH 1361/1752] o365/RBP/fetch: remove debug output --- modules/aca/o365_booking_panel.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 9003922d..8f0e1f04 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -98,7 +98,6 @@ def on_update def fetch_bookings(*args) response = @client.get_bookings(mailboxes: [@office_room], options: {bookings_from: Time.now.midnight.to_i, bookings_to: Time.now.tomorrow.midnight.to_i}).dig(@office_room, :bookings) - puts "==============================\n#{response}\n===============================" self[:today] = expose_bookings(response) end From ae20fcfa59e5f419f487e7268ecef306e70572d3 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 28 Jun 2019 14:56:04 +1000 Subject: [PATCH 1362/1752] Delete dc192.rb --- modules/lumens/dc192.rb | 344 ---------------------------------------- 1 file changed, 344 deletions(-) delete mode 100644 modules/lumens/dc192.rb diff --git a/modules/lumens/dc192.rb b/modules/lumens/dc192.rb deleted file mode 100644 index d65525ca..00000000 --- a/modules/lumens/dc192.rb +++ /dev/null @@ -1,344 +0,0 @@ -module Lumens; end - -# Documentation: https://aca.im/driver_docs/Lumens/DC192%20Protocol.pdf - -class Lumens::Dc192 - include ::Orchestrator::Constants - include ::Orchestrator::Transcoder - - - # Discovery Information - implements :device - descriptive_name 'Lumens Visualiser DC192' - generic_name :Visualiser - - # Communication settings - tokenize delimiter: "\xAF", indicator: "\xA0" - delay between_sends: 300 - wait_response retries: 8 - - - def on_load - self[:zoom_max] = 855 - self[:zoom_min] = 0 - end - - def on_unload - end - - def on_update - end - - - - def connected - do_poll - schedule.every('60s') do - logger.debug "-- Polling Lumens DC Series Visualiser" - do_poll - end - end - - def disconnected - schedule.clear - end - - - - - COMMANDS = { - :zoom_stop => 0x10, # p1 = 0x00 - :zoom_start => 0x11, # p1 (00/01:Tele/Wide) - :zoom_direct => 0x13, # p1-LowByte, p2 highbyte (0~620) - :lamp => 0xC1, # p1 (00/01:Off/On) - :power => 0xB1, # p1 (00/01:Off/On) - :sharp => 0xA7, # p1 (00/01/02:Photo/Text/Gray) only photo or text - :auto_focus => 0xA3, # p1 = 0x01 - :frozen => 0x2C, # p1 (00/01:Off/On) - - 0x10 => :zoom_stop, - 0x11 => :zoom_start, - 0x13 => :zoom_direct, - 0xC1 => :lamp, - 0xB1 => :power, - 0xA7 => :sharp, - 0xA3 => :auto_focus, - 0x2C => :frozen, - - # Status response codes: - 0x78 => :frozen, - 0x51 => :sharp, - 0x50 => :lamp, - 0x60 => :zoom_direct, - 0xB7 => :system_status - } - - - def power(state) - state = is_affirmative?(state) - self[:power_target] = state - power? do - if state && !self[:power] # Request to power on if off - do_send([COMMANDS[:power], 0x01], :timeout => 15000, :delay_on_receive => 5000, :name => :power) - - elsif !state && self[:power] # Request to power off if on - do_send([COMMANDS[:power], 0x00], :timeout => 15000, :delay_on_receive => 5000, :name => :power) - self[:frozen] = false - end - end - end - - def power?(options = {}, &block) - options[:emit] = block - do_send(STATUS_CODE[:system_status], options) - end - - def zoom_in - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:zoom_start]) - end - - def zoom_out - return if self[:frozen] - cancel_focus - do_send([COMMANDS[:zoom_start], 0x01]) - end - - def zoom_stop - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:zoom_stop]) - end - - def zoom(position) - return if self[:frozen] - cancel_focus - position = position.to_i - - position = in_range(position, self[:zoom_max]) - - - low = position & 0xFF - high = (position >> 8) & 0xFF - - do_send([COMMANDS[:zoom_direct], low, high], {:name => :zoom}) - end - - - def lamp(power) - return if self[:frozen] - power = is_affirmative?(power) - - if power - do_send([COMMANDS[:lamp], 0x01], {:name => :lamp}) - else - do_send([COMMANDS[:lamp], 0x00], {:name => :lamp}) - end - end - - - def sharp(state) - return if self[:frozen] - state = is_affirmative?(state) - - if state - do_send([COMMANDS[:sharp], 0x01], {:name => :sharp}) - else - do_send([COMMANDS[:sharp], 0x00], {:name => :sharp}) - end - end - - - def frozen(state) - state = is_affirmative?(state) - - if state - do_send([COMMANDS[:frozen], 0x01], {:name => :frozen}) - else - do_send([COMMANDS[:frozen], 0x00], {:name => :frozen}) - end - end - - - def auto_focus - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:auto_focus], :timeout => 8000, :name => :auto_focus) - end - - - def reset - return if self[:frozen] - cancel_focus - power(On) - - RESET_CODES.each_value do |value| - do_send(value) - end - - sharp(Off) - frozen(Off) - lamp(On) - zoom(0) - end - - - def received(data, reesolve, command) - logger.debug "Lumens sent #{byte_to_hex(data)}" - - - data = str_to_array(data) - - - # - # Process response - # - logger.debug "command was #{COMMANDS[data[0]]}" - case COMMANDS[data[0]] - when :zoom_stop - # - # A 3 second delay for zoom status and auto focus - # - zoom_status - delay_focus - when :zoom_direct - self[:zoom] = data[1] + (data[2] << 8) - delay_focus if COMMANDS[:zoom_direct] == data[0] # then 3 second delay for auto focus - when :lamp - self[:lamp] = data[1] == 0x01 - when :power - self[:power] = data[1] == 0x01 - if (self[:power] != self[:power_target]) && !self[:power_target].nil? - power(self[:power_target]) - logger.debug "Lumens state == unstable - power resp" - else - self[:zoom] = self[:zoom_min] unless self[:power] - end - when :sharp - self[:sharp] = data[1] == 0x01 - when :frozen - self[:frozen] = data[1] == 0x01 - when :system_status - self[:power] = data[2] == 0x01 - if (self[:power] != self[:power_target]) && !self[:power_target].nil? - power(self[:power_target]) - logger.debug "Lumens state == unstable - status" - else - self[:zoom] = self[:zoom_min] unless self[:power] - end - # ready = data[1] == 0x01 - end - - - # - # Check for error - # => We check afterwards as power for instance may be on when we call on - # => The power status is sent as on with a NAK as the command did nothing - # - if data[3] != 0x00 && (!!!self[:frozen]) - case data[3] - when 0x01 - logger.error "Lumens NAK error" - when 0x10 - logger.error "Lumens IGNORE error" - if command.present? - command[:delay_on_receive] = 2000 # update the command - return :abort # retry the command - # - # TODO:: Call system_status(0) and check for ready every second until the command will go through - # - end - else - logger.warn "Lumens unknown error code #{data[3]}" - end - - logger.error "Error on #{byte_to_hex(command[:data])}" unless command.nil? - return :abort - end - - - return :success - end - - - - private - - - def delay_focus - @focus_timer.cancel unless @focus_timer.nil? - @focus_timer = schedule.in('4s') do - auto_focus - end - end - - def cancel_focus - @focus_timer.cancel unless @focus_timer.nil? - end - - - - RESET_CODES = { - :OSD => [0x4B, 0x00], # p1 (00/01:Off/On) on screen display - :digital_zoom => [0x40, 0x00], # p1 (00/01:Disable/Enable) - :language => [0x38, 0x00], # p1 == 00 (english) - :colour => [0xA7, 0x00], # p1 (00/01:Photo/Gray) - :mode => [0xA9, 0x00], # P1 (00/01/02/03:Normal/Slide/Film/Microscope) - :logo => [0x47, 0x00], # p1 (00/01:Off/On) - :source => [0x3A, 0x00], # p1 (00/01:Live/PC) used for reset - :slideshow => [0x04, 0x00] # p1 (00/01:Off/On) -- NAKs - } - - - STATUS_CODE = { - :frozen_status => 0x78, # p1 = 0x00 - :sharp_status => 0x51, # p1 = 0x00 - :lamp_status => 0x50, # p1 = 0x00 - :zoom_status => 0x60, # p1 = 0x00 - :system_status => 0xB7 - } - # - # Automatically creates a callable function for each command - # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html - # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html - # - STATUS_CODE.each_key do |command| - define_method command do |*args| - priority = 99 - if args.length > 0 - priority = args[0] - end - - do_send(STATUS_CODE[command], {:priority => priority, :wait => true}) # Status polling is a low priority - end - end - - - def do_poll - power?(:priority => 99) do - if self[:power] == On - frozen_status - if not self[:frozen] - zoom_status - lamp_status - sharp_status - end - end - end - end - - - def do_send(command, options = {}) - #logger.debug "-- GlobalCache, sending: #{command}" - command = [command] unless command.is_a?(Array) - while command.length < 4 - command << 0x00 - end - - command = [0xA0] + command + [0xAF] - logger.debug "requesting #{byte_to_hex(command)}" - - send(command, options) - end -end From ce164e7ac21a8a17199d57285fb97d3af6a3297c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 28 Jun 2019 15:00:16 +1000 Subject: [PATCH 1363/1752] Create dc192.rb --- modules/lumens/dc192.rb | 344 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 modules/lumens/dc192.rb diff --git a/modules/lumens/dc192.rb b/modules/lumens/dc192.rb new file mode 100644 index 00000000..d65525ca --- /dev/null +++ b/modules/lumens/dc192.rb @@ -0,0 +1,344 @@ +module Lumens; end + +# Documentation: https://aca.im/driver_docs/Lumens/DC192%20Protocol.pdf + +class Lumens::Dc192 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + + # Discovery Information + implements :device + descriptive_name 'Lumens Visualiser DC192' + generic_name :Visualiser + + # Communication settings + tokenize delimiter: "\xAF", indicator: "\xA0" + delay between_sends: 300 + wait_response retries: 8 + + + def on_load + self[:zoom_max] = 855 + self[:zoom_min] = 0 + end + + def on_unload + end + + def on_update + end + + + + def connected + do_poll + schedule.every('60s') do + logger.debug "-- Polling Lumens DC Series Visualiser" + do_poll + end + end + + def disconnected + schedule.clear + end + + + + + COMMANDS = { + :zoom_stop => 0x10, # p1 = 0x00 + :zoom_start => 0x11, # p1 (00/01:Tele/Wide) + :zoom_direct => 0x13, # p1-LowByte, p2 highbyte (0~620) + :lamp => 0xC1, # p1 (00/01:Off/On) + :power => 0xB1, # p1 (00/01:Off/On) + :sharp => 0xA7, # p1 (00/01/02:Photo/Text/Gray) only photo or text + :auto_focus => 0xA3, # p1 = 0x01 + :frozen => 0x2C, # p1 (00/01:Off/On) + + 0x10 => :zoom_stop, + 0x11 => :zoom_start, + 0x13 => :zoom_direct, + 0xC1 => :lamp, + 0xB1 => :power, + 0xA7 => :sharp, + 0xA3 => :auto_focus, + 0x2C => :frozen, + + # Status response codes: + 0x78 => :frozen, + 0x51 => :sharp, + 0x50 => :lamp, + 0x60 => :zoom_direct, + 0xB7 => :system_status + } + + + def power(state) + state = is_affirmative?(state) + self[:power_target] = state + power? do + if state && !self[:power] # Request to power on if off + do_send([COMMANDS[:power], 0x01], :timeout => 15000, :delay_on_receive => 5000, :name => :power) + + elsif !state && self[:power] # Request to power off if on + do_send([COMMANDS[:power], 0x00], :timeout => 15000, :delay_on_receive => 5000, :name => :power) + self[:frozen] = false + end + end + end + + def power?(options = {}, &block) + options[:emit] = block + do_send(STATUS_CODE[:system_status], options) + end + + def zoom_in + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:zoom_start]) + end + + def zoom_out + return if self[:frozen] + cancel_focus + do_send([COMMANDS[:zoom_start], 0x01]) + end + + def zoom_stop + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:zoom_stop]) + end + + def zoom(position) + return if self[:frozen] + cancel_focus + position = position.to_i + + position = in_range(position, self[:zoom_max]) + + + low = position & 0xFF + high = (position >> 8) & 0xFF + + do_send([COMMANDS[:zoom_direct], low, high], {:name => :zoom}) + end + + + def lamp(power) + return if self[:frozen] + power = is_affirmative?(power) + + if power + do_send([COMMANDS[:lamp], 0x01], {:name => :lamp}) + else + do_send([COMMANDS[:lamp], 0x00], {:name => :lamp}) + end + end + + + def sharp(state) + return if self[:frozen] + state = is_affirmative?(state) + + if state + do_send([COMMANDS[:sharp], 0x01], {:name => :sharp}) + else + do_send([COMMANDS[:sharp], 0x00], {:name => :sharp}) + end + end + + + def frozen(state) + state = is_affirmative?(state) + + if state + do_send([COMMANDS[:frozen], 0x01], {:name => :frozen}) + else + do_send([COMMANDS[:frozen], 0x00], {:name => :frozen}) + end + end + + + def auto_focus + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:auto_focus], :timeout => 8000, :name => :auto_focus) + end + + + def reset + return if self[:frozen] + cancel_focus + power(On) + + RESET_CODES.each_value do |value| + do_send(value) + end + + sharp(Off) + frozen(Off) + lamp(On) + zoom(0) + end + + + def received(data, reesolve, command) + logger.debug "Lumens sent #{byte_to_hex(data)}" + + + data = str_to_array(data) + + + # + # Process response + # + logger.debug "command was #{COMMANDS[data[0]]}" + case COMMANDS[data[0]] + when :zoom_stop + # + # A 3 second delay for zoom status and auto focus + # + zoom_status + delay_focus + when :zoom_direct + self[:zoom] = data[1] + (data[2] << 8) + delay_focus if COMMANDS[:zoom_direct] == data[0] # then 3 second delay for auto focus + when :lamp + self[:lamp] = data[1] == 0x01 + when :power + self[:power] = data[1] == 0x01 + if (self[:power] != self[:power_target]) && !self[:power_target].nil? + power(self[:power_target]) + logger.debug "Lumens state == unstable - power resp" + else + self[:zoom] = self[:zoom_min] unless self[:power] + end + when :sharp + self[:sharp] = data[1] == 0x01 + when :frozen + self[:frozen] = data[1] == 0x01 + when :system_status + self[:power] = data[2] == 0x01 + if (self[:power] != self[:power_target]) && !self[:power_target].nil? + power(self[:power_target]) + logger.debug "Lumens state == unstable - status" + else + self[:zoom] = self[:zoom_min] unless self[:power] + end + # ready = data[1] == 0x01 + end + + + # + # Check for error + # => We check afterwards as power for instance may be on when we call on + # => The power status is sent as on with a NAK as the command did nothing + # + if data[3] != 0x00 && (!!!self[:frozen]) + case data[3] + when 0x01 + logger.error "Lumens NAK error" + when 0x10 + logger.error "Lumens IGNORE error" + if command.present? + command[:delay_on_receive] = 2000 # update the command + return :abort # retry the command + # + # TODO:: Call system_status(0) and check for ready every second until the command will go through + # + end + else + logger.warn "Lumens unknown error code #{data[3]}" + end + + logger.error "Error on #{byte_to_hex(command[:data])}" unless command.nil? + return :abort + end + + + return :success + end + + + + private + + + def delay_focus + @focus_timer.cancel unless @focus_timer.nil? + @focus_timer = schedule.in('4s') do + auto_focus + end + end + + def cancel_focus + @focus_timer.cancel unless @focus_timer.nil? + end + + + + RESET_CODES = { + :OSD => [0x4B, 0x00], # p1 (00/01:Off/On) on screen display + :digital_zoom => [0x40, 0x00], # p1 (00/01:Disable/Enable) + :language => [0x38, 0x00], # p1 == 00 (english) + :colour => [0xA7, 0x00], # p1 (00/01:Photo/Gray) + :mode => [0xA9, 0x00], # P1 (00/01/02/03:Normal/Slide/Film/Microscope) + :logo => [0x47, 0x00], # p1 (00/01:Off/On) + :source => [0x3A, 0x00], # p1 (00/01:Live/PC) used for reset + :slideshow => [0x04, 0x00] # p1 (00/01:Off/On) -- NAKs + } + + + STATUS_CODE = { + :frozen_status => 0x78, # p1 = 0x00 + :sharp_status => 0x51, # p1 = 0x00 + :lamp_status => 0x50, # p1 = 0x00 + :zoom_status => 0x60, # p1 = 0x00 + :system_status => 0xB7 + } + # + # Automatically creates a callable function for each command + # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html + # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html + # + STATUS_CODE.each_key do |command| + define_method command do |*args| + priority = 99 + if args.length > 0 + priority = args[0] + end + + do_send(STATUS_CODE[command], {:priority => priority, :wait => true}) # Status polling is a low priority + end + end + + + def do_poll + power?(:priority => 99) do + if self[:power] == On + frozen_status + if not self[:frozen] + zoom_status + lamp_status + sharp_status + end + end + end + end + + + def do_send(command, options = {}) + #logger.debug "-- GlobalCache, sending: #{command}" + command = [command] unless command.is_a?(Array) + while command.length < 4 + command << 0x00 + end + + command = [0xA0] + command + [0xAF] + logger.debug "requesting #{byte_to_hex(command)}" + + send(command, options) + end +end From b503ab349aec8970527de4008b1b1bd49220d540 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 2 Jul 2019 12:10:09 +1000 Subject: [PATCH 1364/1752] (aca:tracking) add support for manual desk management Marking areas as reserved etc --- modules/aca/tracking/desk_management.rb | 137 +++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/modules/aca/tracking/desk_management.rb b/modules/aca/tracking/desk_management.rb index 75960ab1..15784405 100644 --- a/modules/aca/tracking/desk_management.rb +++ b/modules/aca/tracking/desk_management.rb @@ -10,8 +10,6 @@ module Aca::Tracking; end class Aca::Tracking::DeskManagement include ::Orchestrator::Constants - - descriptive_name 'ACA Desk Management' generic_name :DeskManagement implements :logic @@ -75,6 +73,25 @@ def on_update @user_identifier = setting(:user_identifier) || :login_name @timezone = setting(:timezone) || 'UTC' + # { "level_id": ["desk_id"] } + out_of_order = setting(:out_of_order) || {} + @out_of_order = {} + out_of_order.each do |level_id, desks| + self["#{level_id}:out_of_order"] = desks + @out_of_order[level_id.to_s] = Set.new(desks) + end + + # { "level_id": {"desk_id": {reservation details}} } + @reservations = setting(:reservations) || {} + @reservations.each do |level_id, reservations| + self["#{level_id}:reservations"] = reservations.values + end + + @occupied = setting(:occupied) || {} + @occupied.each do |level_id, occupied| + self["#{level_id}:occupied"] = occupied.values + end + # { "switch_ip": { "port_id": "desk_id" } } @switch_mappings = setting(:mappings) || {} @desk_mappings = {} @@ -106,6 +123,122 @@ def on_update subscribe_disconnect end + # ======================== + # MANUAL DESK MANAGEMENT + # ======================== + def reserve_desks(tag, desks, level_id) + level_id = level_id.to_s + reservations = @reservations[level_id] || {} + time = Time.now.to_i + + # Grab the name of the user who reserved these desks + user = current_user + username = user.__send__(@user_identifier) if user + + # For each desk id passed to the function, create the reservation object + # (This adds desks to the list of reserved desks - versus overriding anything already reserved) + Array(desks).each do |desk| + reservations[desk_id] = { + connected_at: time, + connected: true, + reserved: true, + reserved_for: tag, + username: username, + desk_id: desk_id + } + end + + # Update the status variables + @reservations[level_id] = reservations + self["#{level_id}:reservations"] = reservations.values + + # Save to DB + define_setting(:reservations, @reservations) + true + end + + def checkin(level_id, desks, user_id) + level_id = level_id.to_s + occupied = @occupied[level_id] || {} + time = Time.now.to_i + + # Grab the name of the user who reserved these desks + user = current_user + username = user.__send__(@user_identifier) if user + + # For each desk id passed to the function, create the reservation object + # (This adds desks to the list of reserved desks - versus overriding anything already reserved) + Array(desks).each do |desk| + occupied[desk_id] = { + connected_at: time, + connected: true, + reserved: true, + reserved_for: user_id, + username: username, + desk_id: desk_id + } + end + + # Update the status variables + @occupied[level_id] = occupied + self["#{level_id}:occupied"] = occupied.values + + # Save to DB + define_setting(:occupied, @occupied) + true + end + + def reset_desks(desks, user_id) + # Update tracking variables + Array(desks).each do |desk| + @out_of_order.each { |level, set| set.delete(desk) } + @reservations.each { |level, hash| hash.delete(desk) } + @occupied.each { |level, hash| hash.delete(desk) } + end + + # Update exposed state + @out_of_order.each do |level_id, desks| + self["#{level_id}:out_of_order"] = desks.to_a + end + + @reservations.each do |level_id, reservations| + self["#{level_id}:reservations"] = reservations.values + end + + @occupied.each do |level_id, occupied| + self["#{level_id}:occupied"] = occupied.values + end + + # Save details to the database + savable = {} + @out_of_order.each { |level, set_desks| savable[level] = set_desks.to_a } + define_setting(:out_of_order, savable) + define_setting(:reservations, @reservations) + define_setting(:occupied, @occupied) + true + end + + def set_out_of_order(level_id, desks) + level_id = level_id.to_s + + # Merge these desks into the existing out of order desks + out_of_order_desks = @out_of_order[level_id] || Set.new + out_of_order_desks.merge desks + + # Expose the list of desks to the ["id1", "id2", ...] to the front end + @out_of_order[level_id] = out_of_order_desks + self["#{level_id}:out_of_order"] = out_of_order_desks + + # Save details to the database (can't save sets as JSON) + savable = {} + @out_of_order.each { |level, set_desks| savable[level] = set_desks.to_a } + define_setting(:out_of_order, savable) + true + end + # ======================= + # END DESK MANAGEMENT + # ======================= + # Grab the list of desk ids in use on a floor # # @param level [String] the level id of the floor From c0615369b7502930be22d35ab2920c91fce15450 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 2 Jul 2019 15:58:37 +0800 Subject: [PATCH 1365/1752] o365/RBP/deletion: Fix logic error causing ALL bookings to be deleted instead of the target (matching start time) --- modules/aca/o365_booking_panel.rb | 36 ++++++++++++++++--------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 8f0e1f04..759ee55f 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -239,24 +239,26 @@ def delete_o365_booking(delete_start_epoch, reason) booking_start_epoch = Time.parse(booking[:Start]).to_i if booking[:isAllDay] logger.warn { "RBP>#{@office_room}>CANCEL>ALL_DAY: An All Day booking was NOT deleted, with start time #{delete_start_epoch}" } - elsif booking[:email] == @office_room # Bookings owned by the room need to be deleted, not declined - response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) - logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: A booking owned by the room was deleted, with start time #{delete_start_epoch}" } elsif booking_start_epoch == delete_start_epoch - # Decline the meeting, with the appropriate message to the booker - case reason - when RBP_AUTOCANCEL_TRIGGERED - response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_timeout_email_message]) - when RBP_STOP_PRESSED - response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_cancel_email_message]) + if booking[:email] == @office_room # Bookings owned by the room need to be deleted, not declined + response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) + logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: A booking owned by the room was deleted, with start time #{delete_start_epoch}" } else - response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: "The booking was cancelled due to \"#{reason}\" ") - end - logger.warn { "RBP>#{@office_room}>CANCEL>SUCCESS: Declined booking due to \"#{reason}\", with start time #{delete_start_epoch}" } - if response == 200 - bookings_deleted += 1 - # self[:today].delete_at(i) This does not seem to notify the websocket, so call fetch_bookings instead - fetch_bookings + # Decline the meeting, with the appropriate message to the booker + case reason + when RBP_AUTOCANCEL_TRIGGERED + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_timeout_email_message]) + when RBP_STOP_PRESSED + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_cancel_email_message]) + else + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: "The booking was cancelled due to \"#{reason}\" ") + end + logger.warn { "RBP>#{@office_room}>CANCEL>SUCCESS: Declined booking due to \"#{reason}\", with start time #{delete_start_epoch}" } + if response == 200 + bookings_deleted += 1 + # self[:today].delete_at(i) This does not seem to notify the websocket, so call fetch_bookings instead + fetch_bookings + end end end end @@ -296,4 +298,4 @@ def expose_bookings(bookings) } results end -end \ No newline at end of file +end From 21e0a8bc7cccef9b3517919bb4a213c78f57cf82 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 3 Jul 2019 13:59:04 +0800 Subject: [PATCH 1366/1752] o365/lib/event: add DECLINE meeting function --- lib/microsoft/office/events.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index 972dc936..4f438f63 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -257,7 +257,20 @@ def delete_booking(mailbox:, booking_id:) check_response(request) 200 end - + + ## + # Decline a meeting + # + # @param mailbox [String] The mailbox email which contains the booking to delete + # @param booking_id [String] The ID of the booking to be deleted + # @param comment [String] An optional message that will be included in the body of the automated email that will be sent to the host of the meeting + def delete_booking(mailbox:, booking_id:, comment: '') + endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}/decline" + request = graph_request(request_method: 'post', endpoints: [endpoint], data: {comment: comment}) + check_response(request) + 200 + end + protected def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) From c917d84ff521328a9613d5c9037b210be0e59898 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 3 Jul 2019 14:27:20 +0800 Subject: [PATCH 1367/1752] o365/lib/events/decline: fix func name --- lib/microsoft/office/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index 4f438f63..aa3e8b19 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -264,7 +264,7 @@ def delete_booking(mailbox:, booking_id:) # @param mailbox [String] The mailbox email which contains the booking to delete # @param booking_id [String] The ID of the booking to be deleted # @param comment [String] An optional message that will be included in the body of the automated email that will be sent to the host of the meeting - def delete_booking(mailbox:, booking_id:, comment: '') + def decline_meeting(mailbox:, booking_id:, comment: '') endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}/decline" request = graph_request(request_method: 'post', endpoints: [endpoint], data: {comment: comment}) check_response(request) From e9719966cc82a939b362c56930d9cfed0d7611e7 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 4 Jul 2019 07:54:49 +0000 Subject: [PATCH 1368/1752] o365/RBP/cancel: don't require setting booking_cancel_timeout --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 759ee55f..929ac020 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -147,7 +147,7 @@ def cancel_meeting(start_time, reason = "unknown reason") start_epoch = Time.parse(start_time).to_i ms_epoch = start_epoch * 1000 too_early_to_cancel = now < start_epoch - too_late_to_cancel = now > start_epoch + (self[:booking_cancel_timeout] || self[:timeout]) + 180 # allow up to 3mins of slippage, in case endpoint is not NTP synced + too_late_to_cancel = self[:booking_cancel_timeout] ? (now > (start_epoch + self[:booking_cancel_timeout] + 180)) : false # "180": allow up to 3mins of slippage, in case endpoint is not NTP synced bookings_to_cancel = bookings_with_start_time(start_epoch) if bookings_to_cancel == 1 From dfc016c4342c632ca256b05ea36ae41571c829ad Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 5 Jul 2019 13:27:31 +1000 Subject: [PATCH 1369/1752] (cisco:broadworks) fix call center interpolation --- modules/cisco/broad_works.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index ad9b2dd7..073d0fec 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -226,7 +226,7 @@ def monitor_callcenters # Non-event related calls def get_call_count(call_center_id) - get('/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls', name: "call_center_#{call_center_id}_calls") do |data, resolve, command| + get("/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls", name: "call_center_#{call_center_id}_calls") do |data, resolve, command| if data.status == 200 xml = Nokogiri::XML(data.body) xml.remove_namespaces! @@ -310,7 +310,7 @@ def check_call_state def current_calls(user_id) # i.e. an event isn't missed: /com.broadsoft.xsi-actions/v2.0/user//calls # Returns an object with multiple callhalf-722:0 - get('/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls', name: "current_#{user_id}_calls") do |data, resolve, command| + get("/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls", name: "current_#{user_id}_calls") do |data, resolve, command| if data.status == 200 xml = Nokogiri::XML(data.body) xml.remove_namespaces! From 8995ff5d85cb9954a426b1ef21bb6a8ee7ddb4a0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 5 Jul 2019 13:33:33 +1000 Subject: [PATCH 1370/1752] (cisco:broadworks) update virtual system --- modules/cisco/broad_works_virtual.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/modules/cisco/broad_works_virtual.ts b/modules/cisco/broad_works_virtual.ts index 26219c58..ed014941 100644 --- a/modules/cisco/broad_works_virtual.ts +++ b/modules/cisco/broad_works_virtual.ts @@ -1,6 +1,23 @@ window.control.systems['sys-call-center'] = { CallCenter: [{ + total_abandoned: 3, + longest_wait: 125, + longest_talk: 945, + achievements: [ + { + "text": "Nicki is amazing, she got stuff done", + "icon": "star" + }, + { + "text": "Nicki is amazing, she got stuff done", + "icon": "trophy" + }, + { + "text": "Nicki is amazing, she got stuff done", + "icon": "phone" + } + ], queues: { "Police": { queue_length: 1, @@ -72,9 +89,6 @@ window.control.systems['sys-call-center'] = { average_talk: 49, on_calls: 0 } - }, - total_abandoned: 3, - longest_wait: 125, - longest_talk: 945 + } }] }; From 67427b7b2d0f0e552c572151f1b07bb959999908 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 5 Jul 2019 12:33:25 +0000 Subject: [PATCH 1371/1752] o365/lib/events/get: avoid error when bookings is nil --- lib/microsoft/office/events.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index aa3e8b19..73b27852 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -108,6 +108,7 @@ def get_bookings(mailboxes:, options:{}) responses.each_with_index do |res, i| bookings = res['body']['value'] is_available = true + next unless bookings # Go through each booking and extract more info from it bookings.each_with_index do |booking, i| bookings[i] = Microsoft::Officenew::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]).event From 769d00b9359c00adb71ce4ebd35341d76db0fe17 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 5 Jul 2019 12:34:24 +0000 Subject: [PATCH 1372/1752] o365/RBP: fix showing booking organisers email --- modules/aca/o365_booking_panel.rb | 4 ++-- modules/aca/office_booking.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 929ac020..732bee4e 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -275,8 +275,8 @@ def expose_bookings(bookings) start_time = start_utc.iso8601 # output looks like: "2019-05-21T15:50:00Z+08:00" end_time = end_utc.iso8601 - name = booking.dig('organizer','name') || booking.dig('attendees',0,'name') - email = booking.dig('organizer','email') || booking.dig('attendees',0,'email') + name = booking.dig('organizer',:name) || booking.dig('attendees',0,'name') + email = booking.dig('organizer',:email) || booking.dig('attendees',0,'email') subject = booking['subject'] if ['private','confidential'].include?(booking['sensitivity']) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 19781da7..92916ae5 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -278,8 +278,8 @@ def expose_bookings(bookings) start_time = start_utc.iso8601 # output looks like: "2019-05-21T15:50:00Z+08:00" end_time = end_utc.iso8601 - name = booking.dig('organizer','name') || booking.dig('attendees',0,'name') - email = booking.dig('organizer','email') || booking.dig('attendees',0,'email') + name = booking.dig('organizer',:name) || booking.dig('attendees',0,'name') + email = booking.dig('organizer',:email) || booking.dig('attendees',0,'email') subject = booking['subject'] if ['private','confidential'].include?(booking['sensitivity']) From 431af029657756242bcc74448ac83128c3291776 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 10 Jul 2019 08:32:36 +0100 Subject: [PATCH 1373/1752] (cisco:broadworks) add proxy and auth to all requests --- modules/cisco/broad_works.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index 073d0fec..2fa226d5 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -90,7 +90,7 @@ def connect reactor = self.thread callcenters = @callcenters - @bw = Cisco::BroadSoft::BroadWorks.new(@domain, @username, @password, proxy: @proxy) + @bw = Cisco::BroadSoft::BroadWorks.new(@domain, @username, @password, proxy: @proxy, logger: logger) bw = @bw Thread.new do bw.open_channel do |event| @@ -224,9 +224,23 @@ def monitor_callcenters end end + def get_proxy_details + if @proxy + proxy = URI.parse @proxy + { + host: proxy.host, + port: proxy.port + } + end + end + # Non-event related calls def get_call_count(call_center_id) - get("/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls", name: "call_center_#{call_center_id}_calls") do |data, resolve, command| + get("/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls", + name: "call_center_#{call_center_id}_calls", + proxy: get_proxy_details, + headers: {Authorization: [@username, @password]} + ) do |data, resolve, command| if data.status == 200 xml = Nokogiri::XML(data.body) xml.remove_namespaces! @@ -310,7 +324,11 @@ def check_call_state def current_calls(user_id) # i.e. an event isn't missed: /com.broadsoft.xsi-actions/v2.0/user//calls # Returns an object with multiple callhalf-722:0 - get("/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls", name: "current_#{user_id}_calls") do |data, resolve, command| + get("/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls", + name: "current_#{user_id}_calls", + proxy: get_proxy_details, + headers: {Authorization: [@username, @password]} + ) do |data, resolve, command| if data.status == 200 xml = Nokogiri::XML(data.body) xml.remove_namespaces! From 012a593b5afbc7baede9a91e69b92c17363ec154 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 11 Jul 2019 00:09:44 +0100 Subject: [PATCH 1374/1752] o365/RBP/creat_booking: adjust params to suit new o365 lib --- modules/aca/o365_booking_panel.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 732bee4e..6ea9fde1 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -114,7 +114,7 @@ def create_meeting(options) logger.debug "RBP>#{@office_room}>CREATE>INPUT:\n #{options}" req_params = {} req_params[:room_email] = @office_room - req_params[:organizer] = options.dig(:host, :email) || @office_room + req_params[:organizer] = { name: options.dig(:host, :name) || 'Anonymous', email: options.dig(:host, :email) || @office_room } req_params[:subject] = options[:title] req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.to_i req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.to_i @@ -221,7 +221,7 @@ def create_o365_booking(user_email: nil, subject: 'On the spot booking', room_em booking_json = new_booking.to_json logger.debug "RBP>#{@office_room}>CREATE:\n #{booking_json}" begin - result = @client.create_booking(room_id: system.id, start_param: start_time, end_param: end_time, subject: subject, current_user: {email: organizer, name: organizer}) + result = @client.create_booking(mailbox: organizer[:email], start_param: start_time, end_param: end_time, options: {subject: subject, attendees: [system.email]}) rescue => e logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e}\nResponse:\n#{result}" else From d7c3e4ffb35f7c1421a4278899c8cd742a51a4e0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 15 Jul 2019 09:33:59 +0100 Subject: [PATCH 1375/1752] (cisco:broadworks) ensure stats build when no calls --- modules/cisco/broad_works.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index 2fa226d5..f445073b 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -282,10 +282,10 @@ def update_stats abandoned: abandoned, total_calls: calls.size, # Time in milliseconds - average_wait: calls.reduce(:+) / calls.size, + average_wait: (calls.reduce(:+) || 0) / calls.size, max_wait: calls.max.to_i, - average_talk: times.reduce(:+) / times.size, + average_talk: (times.reduce(:+) || 0) / times.size, on_calls: num_on_call[id].to_i } From 2d5fa5c67ee6e8cae33ddbee279bf3c1e12672bd Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 15 Jul 2019 09:41:49 +0100 Subject: [PATCH 1376/1752] (cisco:broadworks) prevent divide by 0 --- modules/cisco/broad_works.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index f445073b..00fc1f45 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -282,10 +282,10 @@ def update_stats abandoned: abandoned, total_calls: calls.size, # Time in milliseconds - average_wait: (calls.reduce(:+) || 0) / calls.size, + average_wait: (calls.reduce(:+) || 0) / [1, calls.size].max, max_wait: calls.max.to_i, - average_talk: (times.reduce(:+) || 0) / times.size, + average_talk: (times.reduce(:+) || 0) / [1, times.size].max, on_calls: num_on_call[id].to_i } From 46906bcb59a254c62d96a9e6a2426a66b2049011 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 16 Jul 2019 12:37:30 +1000 Subject: [PATCH 1377/1752] don't error if mediasite api errors --- modules/mediasite/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 828a4f03..789e252f 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -119,7 +119,7 @@ def state self[:live] = live? res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/ActiveInputs")) - self[:dual] = res['ActiveInputs'].size >= 2 + self[:dual] = res['ActiveInputs'].size >= 2 if res['ActiveInputs'] res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/TimeRemaining")) self[:time_remaining] = res['SecondsRemaining'] From f2b0b745ec47316c8ee700108befb4c35623bc9d Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 17 Jul 2019 10:14:40 +0100 Subject: [PATCH 1378/1752] (cisco:broadworks) fix getting user call details --- modules/cisco/broad_works.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index 00fc1f45..5f3302cd 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -117,6 +117,7 @@ def process_event(event) # Lookup the callcenter in question call_center_id = @subscription_lookup[event[:subscription_id]] + logger.debug { "processing event #{event[:event_name]}" } # Otherwise process the event case event[:event_name] @@ -162,14 +163,14 @@ def process_event(event) time: Time.now.to_i } - logger.debug { "tracking call #{call_id} handled by #{user_id}" } + logger.info { "tracking call #{call_id} handled by #{user_id}" } task { bw.get_user_events(user_id, "Basic Call") } when 'CallReleasedEvent' event_data = event[:event_data] call_id = event_data.xpath("//callId").inner_text call_details = @call_tracking.delete call_id - logger.debug { "call #{call_id} ended, was tracked: #{!!call_details}" } + logger.info { "call #{call_id} ended, was tracked: #{!!call_details}" } if call_details call_center_id = call_details[:center] @@ -183,7 +184,7 @@ def process_event(event) @talk_time[call_center_id] = talk_times end else - logger.debug { "ignoring event #{event[:event_name]}" } + logger.info { "ignoring event #{event[:event_name]}" } end if SHOULD_UPDATE.include?(event[:event_name]) @@ -324,7 +325,7 @@ def check_call_state def current_calls(user_id) # i.e. an event isn't missed: /com.broadsoft.xsi-actions/v2.0/user//calls # Returns an object with multiple callhalf-722:0 - get("/com.broadsoft.xsi-actions/v2.0/callcenter/#{call_center_id}/calls", + get("/com.broadsoft.xsi-actions/v2.0/user/#{user_id}/calls", name: "current_#{user_id}_calls", proxy: get_proxy_details, headers: {Authorization: [@username, @password]} From 3607af612be7c8d64bd5bb4f7a661bd70404973b Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Thu, 18 Jul 2019 14:02:14 +1000 Subject: [PATCH 1379/1752] (nec:display) added additional inputs Added HDMI2/3, USB and Tuner inputs. --- modules/nec/display/all.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/nec/display/all.rb b/modules/nec/display/all.rb index 7475d59c..dd9848cd 100644 --- a/modules/nec/display/all.rb +++ b/modules/nec/display/all.rb @@ -134,14 +134,17 @@ def power?(options = {}, &block) :video1 => 5, :video2 => 6, :svideo => 7, - + :tuner => 9, :tv => 10, :dvd1 => 12, :option => 13, :dvd2 => 14, :display_port => 15, + :hdmi => 17, + :hdmi2 => 18, + :hdmi3 => 130, + :usb => 135 - :hdmi => 17 } INPUTS.merge!(INPUTS.invert) From 52a87f11700c46a6292472ea22fca5f41280b65f Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 19 Jul 2019 00:48:55 +0100 Subject: [PATCH 1380/1752] (aca:tracking) expose reservation group used for determining if the user can reserve a group of desks --- modules/aca/tracking/desk_management.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/aca/tracking/desk_management.rb b/modules/aca/tracking/desk_management.rb index 15784405..a7f3485a 100644 --- a/modules/aca/tracking/desk_management.rb +++ b/modules/aca/tracking/desk_management.rb @@ -25,7 +25,9 @@ class Aca::Tracking::DeskManagement desk_hold_time: 5.minutes.to_i, desk_reserve_time: 2.hours.to_i, manual_reserve_time: 2.hours.to_i, - user_identifier: :login_name # in user model + user_identifier: :login_name, # in user model + # Group name of users allowed to reserve desks + reservation_group: 'administrators' }) def on_load @@ -70,6 +72,7 @@ def on_update self[:hold_time] = setting(:desk_hold_time) || 5.minutes.to_i self[:reserve_time] = @desk_reserve_time = setting(:desk_reserve_time) || 2.hours.to_i self[:manual_reserve_time] = @manual_reserve_time = setting(:manual_reserve_time) || 2.hours.to_i + self[:reservation_group] = setting(:reservation_group) || '' @user_identifier = setting(:user_identifier) || :login_name @timezone = setting(:timezone) || 'UTC' From 92874791c4e0fc85708c210aaf4418edafa1cabf Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 22 Jul 2019 10:25:45 +1000 Subject: [PATCH 1381/1752] Create hue.rb --- modules/philips/hue.rb | 181 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 modules/philips/hue.rb diff --git a/modules/philips/hue.rb b/modules/philips/hue.rb new file mode 100644 index 00000000..745986d7 --- /dev/null +++ b/modules/philips/hue.rb @@ -0,0 +1,181 @@ +require 'net/http' +require 'rubygems' +require 'json' +require 'uv-rays' +require 'libuv' +require 'microsoft/exchange' + +module Philips; end + +class Philips::Hue + include ::Orchestrator::Constants + + descriptive_name 'Philips Hue Motion Sensor' + generic_name :Sensor + implements :logic + + default_settings({ + api_key: 'RKIKef3WgZIQk9FUlMZ2qPqrrwAaTIV3mzetNI2I', + threshold: 10, # number of seconds for testing, number of minutes for real life + minimum_booking_duration: 1 # in minutes + }) + + def on_load + on_update + end + + def on_update + stop + ret = Net::HTTP.get(URI.parse('https://www.meethue.com/api/nupnp')) + parsed = JSON.parse(ret) # parse the JSON string into a usable hash table + ip_address = parsed[0]['internalipaddress'] + @url = "http://#{ip_address}/api/#{setting(:api_key)}/sensors/7" + logger.debug { "url is #{@url}" } + @ews = ::Microsoft::Exchange.new({ + ews_url: 'https://outlook.office365.com/ews/Exchange.asmx', + service_account_email: 'cam@acaprojects.com', + service_account_password: 'Aca1783808' + }) + end + + # Every x amount of time, check if presence is true + def booking_has_presence(booking) + start_time = DateTime.parse(Time.at(booking[:start_date].to_i / 1000).to_s) + end_time = DateTime.parse(Time.at(booking[:end_date].to_i / 1000).to_s) + logger.debug { + "Creating a scheduled task for #{booking[:title]} starting at #{start_time} and ending at #{end_time}" + } + @scheduled_bookings[booking[:title]] = schedule.at(@ews.ensure_ruby_date(start_time)) do + logger.debug { "Starting check for #{booking[:title]}" } + Thread.new do + noshow = false + analytics = 0 + # can schedule a check here to make sure the booking is still valid + ::Libuv::Reactor.new.run do |reactor| + ref = reactor.scheduler.every('5s') do + if DateTime.now <= start_time + setting(:threshold).seconds + if has_presence + analytics = 1 + ref.cancel + else + logger.debug { "Waiting for booking to show up" } + end + # check for this condition first incase of booking duration < threshold + elsif DateTime.now > end_time + analytics = 2 + ref.cancel + elsif DateTime.now > start_time + setting(:threshold).seconds + # cancel the booking since its after the threshold + logger.debug { "Cancelled booking, waiting for walk-in" } + + if !noshow + noshow = true # set the noshow flag so this only happens once + #@ews.cancel_booking + end + + if has_presence + ref.cancel + create_walkin_booking + analytics = 3 + end + end + end + end + + logger.debug { "Analytics is #{analytics}" } + case analytics + when 0 + logger.debug { "Error" } + when 1 + logger.debug { "Original booking showed up" } + when 2 + logger.debug { "No show and no walkin" } + when 3 + logger.debug { "Walkin" } + end + + @scheduled_bookings.delete(booking[:title]) + end + end + end + + # Return true when detected and false when not + def has_presence + ret = Net::HTTP.get(URI.parse(@url)) # get sensor information in JSON format from api + parsed = JSON.parse(ret) # parse the JSON string into a usable hash table + presence = parsed['state']['presence'] + end + + def current_booking + logger.debug { "Checking for new bookings" } + @bookings = get_bookings + if @bookings.length > 0 && !(@scheduled_bookings.include?(@bookings[0][:title])) + booking_has_presence(@bookings[0]) + end + end + + # Get a list of bookings + def get_bookings + curr = [] + today = DateTime.now.to_date + + @ews.get_bookings(email: "cam@acaprojects.com", use_act_as: true).each { |e| + start_date = DateTime.parse(Time.at(e[:start_date].to_i / 1000).to_s) + start_day = start_date.to_date + + if start_day.to_s == today.to_s # these are today's events + if start_date > DateTime.now # show only future events + curr.push(e) + end + end + } + return curr + end + +=begin + Creates a walkin booking ensuring it does not overlap with the next booking + Default duration for walk in bookings has been set to 30 minutes + If next booking is within 30 minutes, walk in booking end time will be set to 1 minute before the next booking's start time +=end + def create_walkin_booking + @bookings = get_bookings + + end_time = DateTime.now + setting(:minimum_booking_duration).minutes + if(@bookings.length > 0) + start_date = DateTime.parse(Time.at(@bookings[0][:start_date].to_i / 1000).to_s) + logger.debug { "Next booking #{@bookings[0][:title]} starts at #{start_date}" } + if end_time > start_date + end_time = start_date - 1.minutes + end + end +=begin + @ews.create_booking({ + room_email: "cam@acaprojects.com", + start_param: DateTime.now, + end_param: end_time, + subject: "Walkin", + current_user: #TODO + }) +=end + logger.debug { "Creating a booking starting at #{DateTime.now} and ending at #{end_time}" } + end + + # check for all bookings now and schedule the check for every future 7am + def start + current_booking + + schedule.every("#{setting(:minimum_booking_duration)}m") do + current_booking + end + end + + def stop + schedule.clear + @scheduled_bookings = {} + logger.debug { "Cleared schedule" } + end + + def send_email + + end +end From 8149e1d9577bedeaf323c3c73f3f75f08859b15b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 22 Jul 2019 16:06:39 +1000 Subject: [PATCH 1382/1752] (aca:tracking) reservation functions send hashes --- modules/aca/tracking/desk_management.rb | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/modules/aca/tracking/desk_management.rb b/modules/aca/tracking/desk_management.rb index a7f3485a..9e2c6bc7 100644 --- a/modules/aca/tracking/desk_management.rb +++ b/modules/aca/tracking/desk_management.rb @@ -129,7 +129,13 @@ def on_update # ======================== # MANUAL DESK MANAGEMENT # ======================== - def reserve_desks(tag, desks, level_id) + def reserve_desks(args) + tag = args["tag"] + desks = args["desks"] + level_id = args["levelId"] + + raise ArgumentError.new("missing one of tag, desks or levelId") unless tag && desks && level_id + level_id = level_id.to_s reservations = @reservations[level_id] || {} time = Time.now.to_i @@ -140,7 +146,7 @@ def reserve_desks(tag, desks, level_id) # For each desk id passed to the function, create the reservation object # (This adds desks to the list of reserved desks - versus overriding anything already reserved) - Array(desks).each do |desk| + Array(desks).each do |desk_id| reservations[desk_id] = { connected_at: time, connected: true, @@ -160,6 +166,7 @@ def reserve_desks(tag, desks, level_id) true end + # TODO:: remove this function def checkin(level_id, desks, user_id) level_id = level_id.to_s occupied = @occupied[level_id] || {} @@ -171,7 +178,7 @@ def checkin(level_id, desks, user_id) # For each desk id passed to the function, create the reservation object # (This adds desks to the list of reserved desks - versus overriding anything already reserved) - Array(desks).each do |desk| + Array(desks).each do |desk_id| occupied[desk_id] = { connected_at: time, connected: true, @@ -191,7 +198,12 @@ def checkin(level_id, desks, user_id) true end - def reset_desks(desks, user_id) + def reset_desks(args) + desks = args["desks"] + user_id = args["userId"] + + raise ArgumentError.new("missing one of desks or userId") unless desks && user_id + # Update tracking variables Array(desks).each do |desk| @out_of_order.each { |level, set| set.delete(desk) } @@ -222,6 +234,11 @@ def reset_desks(desks, user_id) end def set_out_of_order(level_id, desks) + level_id = args["levelId"] + desks = args["desks"] + + raise ArgumentError.new("missing one of desks or levelId") unless desks && level_id + level_id = level_id.to_s # Merge these desks into the existing out of order desks From ab6c5c0f6efe8cc8195fa1a742da628047ce952e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 22 Jul 2019 16:07:24 +1000 Subject: [PATCH 1383/1752] (aca:tracking) reservation functions send hashes --- modules/aca/tracking/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/tracking/desk_management.rb b/modules/aca/tracking/desk_management.rb index 9e2c6bc7..1842b7d0 100644 --- a/modules/aca/tracking/desk_management.rb +++ b/modules/aca/tracking/desk_management.rb @@ -233,7 +233,7 @@ def reset_desks(args) true end - def set_out_of_order(level_id, desks) + def set_out_of_order(args) level_id = args["levelId"] desks = args["desks"] From ca44334cffcc96da6264c2fe473bd8a42613721e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 22 Jul 2019 16:17:25 +1000 Subject: [PATCH 1384/1752] (aca:tracking) fix out of order desks --- modules/aca/tracking/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/tracking/desk_management.rb b/modules/aca/tracking/desk_management.rb index 1842b7d0..4d794af8 100644 --- a/modules/aca/tracking/desk_management.rb +++ b/modules/aca/tracking/desk_management.rb @@ -247,7 +247,7 @@ def set_out_of_order(args) # Expose the list of desks to the ["id1", "id2", ...] to the front end @out_of_order[level_id] = out_of_order_desks - self["#{level_id}:out_of_order"] = out_of_order_desks + self["#{level_id}:out_of_order"] = out_of_order_desks.to_a # Save details to the database (can't save sets as JSON) savable = {} From 5782d41c031413dcb717ea35ca8648d0371598e1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 23 Jul 2019 09:22:35 +1000 Subject: [PATCH 1385/1752] (cisco:broadworks) add achievements --- modules/cisco/broad_works.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index 5f3302cd..b0739d9e 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -41,6 +41,7 @@ def on_update @domain = setting(:events_domain) @proxy = setting(:proxy) || ENV["HTTPS_PROXY"] || ENV["https_proxy"] @callcenters = setting(:callcenters) || {} + self[:achievements] = setting(:achievements) || [] # "sub_id" => "callcenter id" @subscription_lookup ||= {} From 88be210fd9cc62b69832817b98ada70ce37eabd8 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 23 Jul 2019 10:32:32 +1000 Subject: [PATCH 1386/1752] (cisco:broadworks) don't reset stats on load --- modules/cisco/broad_works.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index b0739d9e..1762baf8 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -30,7 +30,7 @@ def on_load @terminated = false # TODO:: Load todays stats from the database if they exist - reset_stats + # reset_stats (we don't want to reset these) on_update end From 7162c4e49f4725f33bb8c64b4b331b46486f1be2 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 23 Jul 2019 14:02:53 +1000 Subject: [PATCH 1387/1752] (cisco:broadworks) track user events on reload --- modules/cisco/broad_works.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index 1762baf8..ca89bc17 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -101,7 +101,7 @@ def connect nil end - SHOULD_UPDATE = Set.new(['ACDCallAddedEvent', 'ACDCallAbandonedEvent', 'CallReleasedEvent', 'ACDCallStrandedEvent', 'ACDCallAnsweredByAgentEvent', 'CallReleasedEvent']) + SHOULD_UPDATE = Set.new(['ACDCallAddedEvent', 'ACDCallAbandonedEvent', 'ACDCallReleasedEvent', 'ACDCallStrandedEvent', 'ACDCallAnsweredByAgentEvent', 'CallReleasedEvent']) def process_event(event) # Check if the connection was terminated @@ -133,7 +133,7 @@ def process_event(event) count = @queued_calls[call_center_id] @queued_calls[call_center_id] = count.to_i - 1 - when 'CallReleasedEvent', 'ACDCallStrandedEvent' + when 'ACDCallStrandedEvent' # Not entirely sure when this happens count = @queued_calls[call_center_id] @queued_calls[call_center_id] = count.to_i - 1 @@ -164,9 +164,10 @@ def process_event(event) time: Time.now.to_i } + bw = @bw logger.info { "tracking call #{call_id} handled by #{user_id}" } task { bw.get_user_events(user_id, "Basic Call") } - when 'CallReleasedEvent' + when 'CallReleasedEvent', 'ACDCallReleasedEvent' event_data = event[:event_data] call_id = event_data.xpath("//callId").inner_text call_details = @call_tracking.delete call_id @@ -224,6 +225,19 @@ def monitor_callcenters retry unless retries > 3 end end + + user_ids = [] + @call_tracking.each do |key, value| + user_ids << value[:user] + end + return if user_ids.empty? + begin + task { + user_ids.each { |id| bw.get_user_events(id, "Basic Call") } + }.value + rescue => e + logger.error "monitoring users\n#{e.message}" + end end def get_proxy_details From c2ea6731f405e5ad406a643766fc5abd5b2d7d74 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 24 Jul 2019 17:59:12 +0800 Subject: [PATCH 1388/1752] o365/RBP/logging: Show backtrace for booking creation errors --- modules/aca/o365_booking_panel.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 6ea9fde1..c9590b33 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -123,8 +123,8 @@ def create_meeting(options) begin id = create_o365_booking req_params rescue Exception => e - logger.debug e.message - logger.debug e.backtrace.inspect + logger.error e.message + logger.error e.backtrace.join("\n") raise e end logger.debug { "successfully created booking: #{id}" } @@ -223,7 +223,7 @@ def create_o365_booking(user_email: nil, subject: 'On the spot booking', room_em begin result = @client.create_booking(mailbox: organizer[:email], start_param: start_time, end_param: end_time, options: {subject: subject, attendees: [system.email]}) rescue => e - logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e}\nResponse:\n#{result}" + logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e.message}\n#{e.backtrace.join("\n")}" else logger.debug "RBP>#{@office_room}>CREATE>SUCCESS:\n #{result}" end From f63b143b3331e699d0c04a7b71d128f2acbf6169 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 24 Jul 2019 19:22:41 +0800 Subject: [PATCH 1389/1752] lib/o365/events/documentation: fix doc for rooms param, add links to MS graph api documentation --- lib/microsoft/office/events.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index 73b27852..2409adbe 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -2,7 +2,8 @@ module Microsoft::Officenew::Events ## # For every mailbox (email) passed in, this method will grab all the bookings and, if # requested, return the availability of the mailboxes for some time range. - # + # https://docs.microsoft.com/en-us/graph/api/user-list-events?view=graph-rest-1.0&tabs=http + # # @param mailboxes [Array] An array of mailbox emails to pull bookings from. These are generally rooms but could be users. # @option options [Integer] :created_from Get all the bookings created after this seconds epoch # @option options [Integer] :start_param Get all the bookings that occurr between this seconds epoch and end_param @@ -123,11 +124,12 @@ def get_bookings(mailboxes:, options:{}) ## # Create an Office365 event in the mailbox passed in. This may have rooms and other # attendees associated with it and thus create events in other mailboxes also. - # + # https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-1.0&tabs=http + # # @param mailbox [String] The mailbox email in which the event is to be created. This could be a user or a room though is generally a user # @param start_param [Integer] A seconds epoch which denotes the start of the booking # @param end_param [Integer] A seconds epoch which denotes the end of the booking - # @option options [Array] :room_emails An array of room resource emails to be added to the booking + # @option options [Array] :rooms An array of room resource emails to be added to the booking. They will get added to attendees[] with "type: resource" # @option options [String] :subject A subject for the booking # @option options [String] :description A description to be added to the body of the event # @option options [String] :organizer_name The name of the organizer @@ -184,7 +186,7 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) # @param mailbox [String] The mailbox email in which the event is to be created. This could be a user or a room though is generally a user # @option options [Integer] :start_param A seconds epoch which denotes the start of the booking # @option options [Integer] :end_param A seconds epoch which denotes the end of the booking - # @option options [Array] :room_emails An array of room resource emails to be added to the booking + # @option options [Array] :rooms An array of room resource emails to be added to the booking. They will get added to attendees[] with "type: resource" # @option options [String] :subject A subject for the booking # @option options [String] :description A description to be added to the body of the event # @option options [String] :organizer_name The name of the organizer From 9c626312268782b9991faa3caa8ba54103e4f456 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 26 Jul 2019 01:29:14 +0800 Subject: [PATCH 1390/1752] o365/lib: Fix timezone of created bookings (https://stackoverflow.com/a/47096366/1186445) --- lib/microsoft/office/client.rb | 3 ++- lib/microsoft/office/events.rb | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index c1f5a57e..7a8ef9ac 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -114,7 +114,7 @@ def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, b headers['Authorization'] = "Bearer #{graph_token}" headers['Content-Type'] = ENV['GRAPH_CONTENT_TYPE'] || "application/json" - headers['Prefer'] = ENV['GRAPH_PREFER'] || 'outlook.timezone="Australia/Sydney"' + headers['Prefer'] = ENV['GRAPH_PREFER'] || "outlook.timezone=\"#{ENV['TZ']}\"" log_graph_request(request_method, data, query, headers, graph_path, endpoints) @@ -144,6 +144,7 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint end def check_response(response) + STDERR.puts "GRAPH API Response:\n #{response}" case response.status when 200, 201, 204 return diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office/events.rb index 2409adbe..0d3bbca4 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office/events.rb @@ -303,12 +303,12 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } event_json[:start] = { - dateTime: ActiveSupport::TimeZone.new(timezone).at(start_param), + dateTime: ActiveSupport::TimeZone.new(timezone).at(start_param).strftime('%FT%R'), timeZone: timezone } if start_param event_json[:end] = { - dateTime: ActiveSupport::TimeZone.new(timezone).at(end_param), + dateTime: ActiveSupport::TimeZone.new(timezone).at(end_param).strftime('%FT%R'), timeZone: timezone } if end_param From 7f127766e28d7a5d523da0e4b7481a2b64a957ff Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 26 Jul 2019 01:42:06 +0800 Subject: [PATCH 1391/1752] o365/lib/debug: Only output debug info if rails env is dev --- lib/microsoft/office/client.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office/client.rb index 7a8ef9ac..ea356d2d 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office/client.rb @@ -129,6 +129,7 @@ def graph_date(date) end def log_graph_request(request_method, data, query, headers, graph_path, endpoints=nil) + return unless ENV['RAILS_ENV'] == "development" STDERR.puts "--------------NEW GRAPH REQUEST------------" STDERR.puts "#{request_method} to #{graph_path}" STDERR.puts "Data:" @@ -144,7 +145,7 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint end def check_response(response) - STDERR.puts "GRAPH API Response:\n #{response}" + STDOUT.puts "GRAPH API Response:\n #{response}" if ENV['RAILS_ENV'] == "development" case response.status when 200, 201, 204 return From 7b935ac7c2a41ad1fec346edc6c810ecc022d2c3 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 26 Jul 2019 15:16:58 +0800 Subject: [PATCH 1392/1752] o365/RBP/create: fix params for new o365 lib. delete: tidy logic --- modules/aca/o365_booking_panel.rb | 158 +++++++++++++++--------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index c9590b33..83cb8058 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -66,7 +66,7 @@ def on_update self[:booking_endable] = setting(:booking_endable) self[:booking_ask_cancel] = setting(:booking_ask_cancel) self[:booking_ask_end] = setting(:booking_ask_end) - self[:booking_default_title] = setting(:booking_default_title) + self[:booking_default_title] = setting(:booking_default_title) || "On the spot booking" self[:booking_select_free] = setting(:booking_select_free) self[:booking_hide_all] = setting(:booking_hide_all) || false @@ -91,6 +91,7 @@ def on_update self[:last_meeting_started] = setting(:last_meeting_started) self[:cancel_meeting_after] = setting(:cancel_meeting_after) + self[:today] = [] fetch_bookings schedule.clear schedule.every(setting(:update_every) || '5m') { fetch_bookings } @@ -101,35 +102,34 @@ def fetch_bookings(*args) self[:today] = expose_bookings(response) end - def create_meeting(options) - # Check that the required params exist + def create_meeting(params) required_fields = ["start", "end"] - check = required_fields - options.keys + check = required_fields - params.keys if check != [] - # There are missing required fields - logger.info "Required fields missing: #{check}" - raise "missing required fields: #{check}" + logger.debug "Required fields missing: #{check}" + raise "Required fields missing: #{check}" end - logger.debug "RBP>#{@office_room}>CREATE>INPUT:\n #{options}" - req_params = {} - req_params[:room_email] = @office_room - req_params[:organizer] = { name: options.dig(:host, :name) || 'Anonymous', email: options.dig(:host, :email) || @office_room } - req_params[:subject] = options[:title] - req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.to_i - req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.to_i - - # TODO:: Catch error for booking failure + logger.debug "RBP>#{@office_room}>CREATE>INPUT:\n #{params}" begin - id = create_o365_booking req_params + result = @client.create_booking( + mailbox: params.dig(:host, :email) || @office_room, + start_param: epoch(params[:start]), + end_param: epoch(params[:end]), + options: { + subject: params[:title] || setting(:booking_default_title), + attendees: [ {email: @office_room, type: "resource"} ], + timezone: ENV['TZ'] + } + ) rescue Exception => e - logger.error e.message - logger.error e.backtrace.join("\n") + logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e.message}\n#{e.backtrace.join("\n")}" raise e - end - logger.debug { "successfully created booking: #{id}" } - schedule.in('2s') do - fetch_bookings + else + logger.debug { "RBP>#{@office_room}>CREATE>SUCCESS:\n #{result}" } + schedule.in('2s') do + fetch_bookings + end end "Ok" end @@ -150,23 +150,28 @@ def cancel_meeting(start_time, reason = "unknown reason") too_late_to_cancel = self[:booking_cancel_timeout] ? (now > (start_epoch + self[:booking_cancel_timeout] + 180)) : false # "180": allow up to 3mins of slippage, in case endpoint is not NTP synced bookings_to_cancel = bookings_with_start_time(start_epoch) - if bookings_to_cancel == 1 - if reason == RBP_STOP_PRESSED - delete_o365_booking(start_epoch, reason) - elsif reason == RBP_AUTOCANCEL_TRIGGERED - if !too_early_to_cancel && !too_late_to_cancel - delete_o365_booking(start_epoch, reason) - else - logger.warn { "RBP>#{@office_room}>CANCEL>TOO_EARLY: Booking not cancelled with start time #{start_time}" } if too_early_to_cancel - logger.warn { "RBP>#{@office_room}>CANCEL>TOO_LATE: Booking not cancelled with start time #{start_time}" } if too_late_to_cancel - end - else # an unsupported reason, just cancel the booking and add support to this driver. - logger.error { "RBP>#{@office_room}>CANCEL>UNKNOWN_REASON: Cancelled booking with unknown reason, with start time #{start_time}" } + if bookings_to_cancel > 1 + logger.warn { "RBP>#{@office_room}>CANCEL>CLASH: No bookings cancelled as Multiple bookings (#{bookings_to_cancel}) were found with same start time #{start_time}" } + return + end + if bookings_to_cancel == 0 + logger.warn { "RBP>#{@office_room}>CANCEL>NOT_FOUND: Could not find booking to cancel with start time #{start_time}" } + return + end + + case reason + when RBP_STOP_PRESSED + delete_o365_booking(start_epoch, reason) + when RBP_AUTOCANCEL_TRIGGERED + if !too_early_to_cancel && !too_late_to_cancel delete_o365_booking(start_epoch, reason) + else + logger.warn { "RBP>#{@office_room}>CANCEL>TOO_EARLY: Booking NOT cancelled with start time #{start_time}" } if too_early_to_cancel + logger.warn { "RBP>#{@office_room}>CANCEL>TOO_LATE: Booking NOT cancelled with start time #{start_time}" } if too_late_to_cancel end - else - logger.warn { "RBP>#{@office_room}>CANCEL>CLASH: No bookings cancelled as Multiple bookings (#{bookings_to_cancel}) were found with same start time #{start_time}" } if bookings_to_cancel > 1 - logger.warn { "RBP>#{@office_room}>CANCEL>NOT_FOUND: Could not find booking to cancel with start time #{start_time}" } if bookings_to_cancel == 0 + else # an unsupported reason, just cancel the booking and add support to this driver. + logger.error { "RBP>#{@office_room}>CANCEL>UNKNOWN_REASON: Cancelled booking with unknown reason, with start time #{start_time}" } + delete_o365_booking(start_epoch, reason) end self[:last_meeting_started] = ms_epoch @@ -207,63 +212,58 @@ def clear_end_meeting_warning self[:meeting_ending] = self[:last_meeting_started] end + + protected - def create_o365_booking(user_email: nil, subject: 'On the spot booking', room_email:, start_time:, end_time:, organizer:) - new_booking = { - subject: subject, - start: { dateTime: start_time, timeZone: "UTC" }, - end: { dateTime: end_time, timeZone: "UTC" }, - location: { displayName: @office_room, locationEmailAddress: @office_room }, - attendees: organizer ? [ emailAddress: { address: organizer, name: "User"}] : [] - } + # convert an unknown epoch type (s, ms, micros) to s (seconds) epoch + def epoch(input) + case input.digits.count + when 1..12 #(s is typically 10 digits) + input + when 13..15 #(ms is typically 13 digits) + input/1000 + else + input/1000000 + end + end - booking_json = new_booking.to_json - logger.debug "RBP>#{@office_room}>CREATE:\n #{booking_json}" - begin - result = @client.create_booking(mailbox: organizer[:email], start_param: start_time, end_param: end_time, options: {subject: subject, attendees: [system.email]}) - rescue => e - logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e.message}\n#{e.backtrace.join("\n")}" + def delete_or_decline(booking, comment = nil) + if booking[:email] == @office_room + logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: Deleting booking owned by the room, with start time #{booking[:Start]}" } + response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) # Bookings owned by the room need to be deleted, instead of declined else - logger.debug "RBP>#{@office_room}>CREATE>SUCCESS:\n #{result}" + logger.warn { "RBP>#{@office_room}>CANCEL>SUCCESS: Declining booking, with start time #{booking[:Start]}" } + response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: comment) end - result['id'] end def delete_o365_booking(delete_start_epoch, reason) bookings_deleted = 0 - return bookings_deleted unless self[:today] # Exist if no bookings delete_start_time = Time.at(delete_start_epoch) + # Find a booking with a matching start time to delete self[:today].each_with_index do |booking, i| - booking_start_epoch = Time.parse(booking[:Start]).to_i + booking_start_epoch = Time.parse(booking[:Start]).to_i + next if booking_start_epoch != delete_start_epoch if booking[:isAllDay] logger.warn { "RBP>#{@office_room}>CANCEL>ALL_DAY: An All Day booking was NOT deleted, with start time #{delete_start_epoch}" } - elsif booking_start_epoch == delete_start_epoch - if booking[:email] == @office_room # Bookings owned by the room need to be deleted, not declined - response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) - logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: A booking owned by the room was deleted, with start time #{delete_start_epoch}" } - else - # Decline the meeting, with the appropriate message to the booker - case reason - when RBP_AUTOCANCEL_TRIGGERED - response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_timeout_email_message]) - when RBP_STOP_PRESSED - response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: self[:booking_cancel_email_message]) - else - response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: "The booking was cancelled due to \"#{reason}\" ") - end - logger.warn { "RBP>#{@office_room}>CANCEL>SUCCESS: Declined booking due to \"#{reason}\", with start time #{delete_start_epoch}" } - if response == 200 - bookings_deleted += 1 - # self[:today].delete_at(i) This does not seem to notify the websocket, so call fetch_bookings instead - fetch_bookings - end - end + next + end + + case reason + when RBP_AUTOCANCEL_TRIGGERED + response = delete_or_decline(booking, self[:booking_timeout_email_message]) + when RBP_STOP_PRESSED + response = delete_or_decline(booking, self[:booking_cancel_email_message]) + else + response = delete_or_decline(booking, "The booking was cancelled due to \"#{reason}\"") + end + if response.between?(200,204) + bookings_deleted += 1 + fetch_bookings # self[:today].delete_at(i) This does not seem to notify the websocket, so call fetch_bookings instead end end - # Return the number of meetings removed - bookings_deleted end def expose_bookings(bookings) From ff80400ffe0c6925e5e1b299baa36c653b94ab4a Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 26 Jul 2019 23:28:20 +0800 Subject: [PATCH 1393/1752] o365/lib/event/update: fix error on update due to nil.each (extensions) --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index 8295d6d7..b838680a 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -1014,7 +1014,7 @@ def create_booking(room_id:, start_param:, end_param:, subject:, description:nil end # https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/event_update - def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Australia/Sydney', endpoint_override:nil, extensions:nil) + def update_booking(booking_id:, room_id:, start_param:nil, end_param:nil, subject:nil, description:nil, attendees:nil, current_user:nil, timezone:'Australia/Sydney', endpoint_override:nil, extensions:[]) # We will always need a room and endpoint passed in room = Orchestrator::ControlSystem.find_by_email(room_id) || Orchestrator::ControlSystem.find(room_id) From 93570902e23eab4cd7ddeee7d16e0a974f02c980 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 29 Jul 2019 22:24:07 +1000 Subject: [PATCH 1394/1752] (cisco:broadworks) save current stats in the database to recover from any outages or reloads --- modules/cisco/broad_works.rb | 61 +++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index ca89bc17..9de62322 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -29,8 +29,14 @@ def on_load HTTPI.log = false @terminated = false - # TODO:: Load todays stats from the database if they exist - # reset_stats (we don't want to reset these) + last_known = setting(:last_known_stats) || { + stats: {}, + longest_wait: 0, + longest_talk: 0 + } + @saved_stats = last_known[:stats] + @saved_longest_wait = last_known[:longest_wait] + @saved_longest_talk = last_known[:longest_talk] on_update end @@ -77,6 +83,16 @@ def reset_stats # We reset the call tracker # Probably not needed but might accumulate calls over time @call_tracking = {} + + @saved_stats = {} + @saved_longest_wait = 0 + @saved_longest_talk = 0 + define_setting(:last_known_stats, { + stats: {}, + longest_wait: 0, + longest_talk: 0 + }) + true end def on_unload @@ -287,7 +303,7 @@ def update_stats @callcenters.each do |id, name| calls = Array(@calls_taken[id]) abandoned = @abandoned_calls[id].to_i - total_abandoned += abandoned + # total_abandoned += abandoned all_calls.concat calls times = Array(@talk_time[id]) @@ -308,13 +324,48 @@ def update_stats queues[name] = details end + # saved details + @saved_stats.each do |name, details| + queue = queues[name] + next unless queue + + # the .to_i handles nil cases + total_calls = details[:total_calls].to_i + queue[:total_calls].to_i + if details[:average_wait] + # the .to_i handles nil cases + avg = (queue[:average_wait].to_i * queue[:total_calls].to_i + details[:average_wait].to_i * details[:total_calls].to_i) / total_calls + queue[:average_wait] = avg + end + if details[:average_talk] + avg = (queue[:average_talk].to_i * queue[:total_calls].to_i + details[:average_talk].to_i * details[:total_calls].to_i) / total_calls + queue[:average_wait] = avg + end + + queue[:abandoned] += details[:abandoned] || 0 + queue[:total_calls] = total_calls + queue[:max_wait] = [queue[:max_wait], details[:max_wait] || 0].max + end + + # calculate abandoned + queues.each do |name, details| + total_abandoned += details[:abandoned] || 0 + end + # Expose the state self[:queues] = queues self[:total_abandoned] = total_abandoned # TODO:: confirm if this is longest in the day or in the current queue? - self[:longest_wait] = all_calls.max - self[:longest_talk] = all_times.max + self[:longest_wait] = [all_calls.max, @saved_longest_wait || 0].max + self[:longest_talk] = [all_times.max, @saved_longest_talk || 0].max + + # save the details in the database + define_setting(:last_known_stats, { + stats: queues, + longest_wait: self[:longest_wait], + longest_talk: self[:longest_talk] + }) + true end # Ensure the calls that are active are still active: From a49af0fe2ed68497f2e916c56e618aae0d522675 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 30 Jul 2019 09:47:35 +1000 Subject: [PATCH 1395/1752] (cisco:broadworks) ensure variables are initialized --- modules/cisco/broad_works.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index 9de62322..82390104 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -54,6 +54,12 @@ def on_update # "call_id" => {user: user_id, center: "callcenter id", time: 435643} @call_tracking ||= {} + @queued_calls ||= {} + @abandoned_calls ||= {} + @calls_taken ||= {} + @talk_time ||= {} + @call_tracking ||= {} + @poll_sched&.cancel @reset_sched&.cancel @@ -356,8 +362,8 @@ def update_stats self[:total_abandoned] = total_abandoned # TODO:: confirm if this is longest in the day or in the current queue? - self[:longest_wait] = [all_calls.max, @saved_longest_wait || 0].max - self[:longest_talk] = [all_times.max, @saved_longest_talk || 0].max + self[:longest_wait] = [all_calls.max.to_i, @saved_longest_wait || 0].max + self[:longest_talk] = [all_times.max.to_i, @saved_longest_talk || 0].max # save the details in the database define_setting(:last_known_stats, { From c775e38345f43232cf01ee246ad66115ba6a5646 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 31 Jul 2019 15:51:31 +0800 Subject: [PATCH 1396/1752] o365/RBP: re-add timeout setting, which re-enabled "Pending" (orange) status for ngx-bookings frontend --- modules/aca/o365_booking_panel.rb | 1 + modules/aca/office_booking.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 83cb8058..55c8a868 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -43,6 +43,7 @@ def on_update self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url + self[:timeout] = setting(:timeout) self[:booking_cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 92916ae5..25b7facb 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -42,6 +42,7 @@ def on_update self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url + self[:timeout] = setting(:timeout) self[:booking_cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) From 4dddb4e24e0fa360204ea9eacc274b4a5477deaa Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 31 Jul 2019 16:01:12 +0800 Subject: [PATCH 1397/1752] o365/RBP: remove unused vars. Init status today (bookings array) to [] on load --- modules/aca/o365_booking_panel.rb | 12 ++---------- modules/aca/office_booking.rb | 5 +---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 55c8a868..e0bf3beb 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -24,14 +24,11 @@ class Aca::O365BookingPanel }) def on_load + self[:today] = [] on_update end def on_update - self[:swiped] ||= 0 - @last_swipe_at = 0 - @use_act_as = setting(:use_act_as) - self[:room_name] = setting(:room_name) || system.name self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false @@ -73,13 +70,9 @@ def on_update office_client_id = setting(:office_client_id) office_secret = setting(:office_secret) - office_scope = setting(:office_scope) - office_site = setting(:office_site) office_token_url = setting(:office_token_url) - office_user_email = setting(:office_user_email) - office_user_password = setting(:office_user_password) @office_room = (setting(:office_room) || system.email) - office_https_proxy = setting(:office_https_proxy) + #office_https_proxy = setting(:office_https_proxy) logger.debug "RBP>#{@office_room}>INIT: Instantiating o365 Graph API client" @@ -92,7 +85,6 @@ def on_update self[:last_meeting_started] = setting(:last_meeting_started) self[:cancel_meeting_after] = setting(:cancel_meeting_after) - self[:today] = [] fetch_bookings schedule.clear schedule.every(setting(:update_every) || '5m') { fetch_bookings } diff --git a/modules/aca/office_booking.rb b/modules/aca/office_booking.rb index 25b7facb..ceee6192 100644 --- a/modules/aca/office_booking.rb +++ b/modules/aca/office_booking.rb @@ -23,14 +23,11 @@ class Aca::OfficeBooking }) def on_load + self[:today] = [] on_update end def on_update - self[:swiped] ||= 0 - @last_swipe_at = 0 - @use_act_as = setting(:use_act_as) - self[:room_name] = setting(:room_name) || system.name self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false From d1156c0760cc131fc889e617a96d01348d622ec2 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 29 Jul 2019 06:12:02 +0000 Subject: [PATCH 1398/1752] o365/lib/event/fields: Add many more fields to the event object, including type, recurrence, isallday, originalstarttimezone, changekey --- lib/microsoft/office/event.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index 91805231..011581fc 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -11,7 +11,19 @@ class Microsoft::Officenew::Event < Microsoft::Officenew::Model 'attendees' => 'old_attendees', 'iCalUId' => 'icaluid', 'showAs' => 'show_as', - 'createdDateTime' => 'created' + 'isCancelled' => 'isCancelled', + 'isAllDay' => 'isAllDay', + 'sensitivity' => 'sensitivity', + 'location' => 'location', + 'locations' => 'locations', + 'recurrence' => 'recurrence', + 'seriesMasterId' => 'seriesMasterId', + 'type' => 'type', + 'createdDateTime' => 'created', + 'changeKey' => 'changeKey', + 'lastModifiedDateTime' => 'lastModifiedDateTime', + 'originalStartTimeZone' => 'originalStartTimeZone', + 'originalEndTimeZone' => 'originalEndTimeZone' } NEW_FIELDS = [ From 17849f4e9523ba8b8ca2e709e39f780773042860 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 1 Aug 2019 14:40:51 +0800 Subject: [PATCH 1399/1752] Vagrantfile/dev-env: VM synced folder from aca-device-modules to ruby-engine-drivers --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index b08fab3e..92464386 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -11,7 +11,7 @@ Vagrant.configure('2') do |config| config.vm.box = 'acaengine/dev-env' - config.vm.synced_folder DRIVERS_PATH, '/etc/aca/aca-device-modules' \ + config.vm.synced_folder DRIVERS_PATH, '/etc/aca/ruby-engine-drivers' \ unless DRIVERS_PATH.nil? config.vm.synced_folder WWW_PATH, '/etc/aca/www' \ From 12d8b527bc16a5f4be1d3f18b0892b7f64929b2c Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 8 Aug 2019 16:36:31 +0800 Subject: [PATCH 1400/1752] EWS>RBP: Simplify: Remove card swipe, skype, ldap, catering --- modules/aca/exchange_booking.rb | 475 +++----------------------------- 1 file changed, 39 insertions(+), 436 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 4c17d6c5..39ff2af0 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -1,31 +1,12 @@ -# For rounding up to the nearest 15min -# See: http://stackoverflow.com/questions/449271/how-to-round-a-time-down-to-the-nearest-15-minutes-in-ruby -class ActiveSupport::TimeWithZone - def ceil(seconds = 60) - return self if seconds.zero? - Time.at(((self - self.utc_offset).to_f / seconds).ceil * seconds).in_time_zone + self.utc_offset - end -end - require 'microsoft/exchange' module Aca; end - -# NOTE:: Requires Settings: -# ======================== -# room_alias: 'rs.au.syd.L16Aitken', -# building: 'DP3', -# level: '16' - class Aca::ExchangeBooking include ::Orchestrator::Constants - EMAIL_CACHE = ::Concurrent::Map.new - CAN_LDAP = begin - require 'net/ldap' - true - rescue LoadError - false - end + descriptive_name 'MS Exchange (EWS) Room Booking Panel Logic' + generic_name :Bookings + implements :logic + CAN_EWS = begin require 'viewpoint2' true @@ -39,41 +20,9 @@ class Aca::ExchangeBooking false end - - descriptive_name 'Exchange Room Bookings' - generic_name :Bookings - implements :logic - - # The room we are interested in default_settings({ update_every: '2m', - - # Moved to System or Zone Setting - # cancel_meeting_after: 900 - - # Card reader IDs if we want to listen for swipe events - card_readers: ['reader_id_1', 'reader_id_2'], - - # Optional LDAP creds for looking up emails - ldap_creds: { - host: 'ldap.org.com', - port: 636, - encryption: { - method: :simple_tls, - tls_options: { - verify_mode: 0 - } - }, - auth: { - method: :simple, - username: 'service account', - password: 'password' - } - }, - tree_base: "ou=User,ou=Accounts,dc=org,dc=com", - - # Optional EWS for creating and removing bookings ews_creds: [ 'https://company.com/EWS/Exchange.asmx', 'service account', @@ -85,63 +34,55 @@ class Aca::ExchangeBooking def on_load + self[:today] = [] on_update end def on_update - self[:swiped] ||= 0 - @last_swipe_at = 0 @use_act_as = setting(:use_act_as) - + self[:room_name] = setting(:room_name) || system.name self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false - self[:name] = self[:room_name] = setting(:room_name) || system.name - self[:description] = setting(:description) || nil - self[:title] = setting(:title) || nil - self[:timeout] = setting(:timeout) || false - self[:booking_endable] = setting(:booking_endable) || false - self[:booking_ask_end] = setting(:booking_ask_end) || false - + self[:arrow_direction] = setting(:arrow_direction) + self[:hearing_assistance] = setting(:hearing_assistance) + self[:timeline_start] = setting(:timeline_start) + self[:timeline_end] = setting(:timeline_end) + self[:description] = setting(:description) + self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url + + self[:timeout] = setting(:timeout) + self[:booking_cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' + self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) + self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) self[:booking_controls] = setting(:booking_controls) self[:booking_catering] = setting(:booking_catering) self[:booking_hide_details] = setting(:booking_hide_details) self[:booking_hide_availability] = setting(:booking_hide_availability) self[:booking_hide_user] = setting(:booking_hide_user) + self[:booking_hide_modal] = setting(:booking_hide_modal) + self[:booking_hide_title] = setting(:booking_hide_title) self[:booking_hide_description] = setting(:booking_hide_description) self[:booking_hide_timeline] = setting(:booking_hide_timeline) - self[:last_meeting_started] = setting(:last_meeting_started) - self[:cancel_meeting_after] = setting(:cancel_meeting_after) - self[:booking_min_duration] = setting(:booking_min_duration) + self[:booking_set_host] = setting(:booking_set_host) + self[:booking_set_title] = setting(:booking_set_title) + self[:booking_set_ext] = setting(:booking_set_ext) + self[:booking_search_user] = setting(:booking_search_user) self[:booking_disable_future] = setting(:booking_disable_future) + self[:booking_min_duration] = setting(:booking_min_duration) self[:booking_max_duration] = setting(:booking_max_duration) - self[:booking_cancel_timeout] = setting(:booking_cancel_timeout) - self[:hide_all_day_bookings] = setting(:hide_all_day_bookings) - self[:timeout] = setting(:timeout) - self[:arrow_direction] = setting(:arrow_direction) - self[:icon] = setting(:icon) + self[:booking_duration_step] = setting(:booking_duration_step) + self[:booking_endable] = setting(:booking_endable) + self[:booking_ask_cancel] = setting(:booking_ask_cancel) + self[:booking_ask_end] = setting(:booking_ask_end) + self[:booking_default_title] = setting(:booking_default_title) || "On the spot booking" + self[:booking_select_free] = setting(:booking_select_free) + self[:booking_hide_all] = setting(:booking_hide_all) || false @hide_all_day_bookings = Boolean(setting(:hide_all_day_bookings)) @check_meeting_ending = setting(:check_meeting_ending) # seconds before meeting ending @extend_meeting_by = setting(:extend_meeting_by) || 15.minutes.to_i - # Skype join button available 2min before the start of a meeting - @skype_start_offset = setting(:skype_start_offset) || 120 - @skype_check_offset = setting(:skype_check_offset) || 380 # 5min + 20 seconds - - # Skype join button not available in the last 8min of a meeting - @skype_end_offset = setting(:skype_end_offset) || 480 - @force_skype_extract = setting(:force_skype_extract) - - # Because restarting the modules results in a 'swipe' of the last read card - ignore_first_swipe = true - - # Is there catering available for this room? - self[:catering] = setting(:catering_system_id) - if self[:catering] - self[:menu] = setting(:menu) - end - # Do we want to look up the users email address? if CAN_LDAP @ldap_creds = setting(:ldap_creds) @@ -167,47 +108,9 @@ def on_update logger.warn "viewpoint gem not available" if setting(:ews_creds) end - # Load the last known values (persisted to the DB) - self[:waiter_status] = (setting(:waiter_status) || :idle).to_sym - self[:waiter_call] = self[:waiter_status] != :idle - - self[:catering_status] = setting(:last_catering_status) || {} - self[:order_status] = :idle - self[:last_meeting_started] = setting(:last_meeting_started) self[:cancel_meeting_after] = setting(:cancel_meeting_after) - - # unsubscribe to all swipe IDs if any are subscribed - if @subs.present? - @subs.each do |sub| - unsubscribe(sub) - end - - @subs = nil - end - - # Are there any swipe card integrations - if system.exists? :Security - readers = setting(:card_readers) - if readers.present? - security = system[:Security] - - readers = readers.is_a?(Array) ? readers : [readers] - sys = system - @subs = [] - readers.each do |id| - @subs << sys.subscribe(:Security, 1, id.to_s) do |notice| - if ignore_first_swipe - ignore_first_swipe = false - else - swipe_occured(notice.value) - end - end - end - end - end - schedule.clear schedule.in(rand(10000)) { fetch_bookings(true) @@ -218,151 +121,14 @@ def on_update end - def set_light_status(status) - lightbar = system[:StatusLight] - return if lightbar.nil? - - case status.to_sym - when :unavailable - lightbar.colour(:red) - when :available - lightbar.colour(:green) - when :pending - lightbar.colour(:orange) - else - lightbar.colour(:off) - end - end - - def directory_search(q, limit: 30) - # Ensure only a single search is occuring at a time - if @dir_search - @dir_search = q - return - end - - ews = ::Microsoft::Exchange.new({ - ews_url: ENV['EWS_URL'] || 'https://outlook.office365.com/ews/Exchange.asmx', - service_account_email: ENV['OFFICE_ACCOUNT_EMAIL'], - service_account_password: ENV['OFFICE_ACCOUNT_PASSWORD'], - internet_proxy: ENV['INTERNET_PROXY'] - }) - - @dir_search = q - self[:searching] = true - begin - # sip_spd:Auto sip_num:email@address.com - entries = [] - task { ews.get_users(q: q, limit: limit) }.value.each do |entry| - phone = entry['phone'] - - entries << entry - entries << ({ - name: entry['name'], - phone: phone.gsub(/\D+/, '') - }) if phone - end - - # Ensure the results are unique and pushed to the client - entries[0][:id] = rand(10000) if entries.length > 0 - self[:directory] = entries - rescue => e - logger.print_error e, 'searching directory' - self[:directory] = [] - end - - # Update the search if a change was requested while a search was occuring - if @dir_search != q - q = @dir_search - thread.next_tick { directory_search(q, limit) } - else - self[:searching] = false - end - - @dir_search = nil - end - - # ====================================== - # Waiter call information - # ====================================== - def waiter_call(state) - status = is_affirmative?(state) - - self[:waiter_call] = status - - # Used to highlight the service button - if status - self[:waiter_status] = :pending - else - self[:waiter_status] = :idle - end - - define_setting(:waiter_status, self[:waiter_status]) - end - - def call_acknowledged - self[:waiter_status] = :accepted - define_setting(:waiter_status, self[:waiter_status]) - end - - - # ====================================== - # Catering Management - # ====================================== - def catering_status(details) - self[:catering_status] = details - - # We'll turn off the green light on the waiter call button - if self[:waiter_status] != :idle && details[:progress] == 'visited' - self[:waiter_call] = false - self[:waiter_status] = :idle - define_setting(:waiter_status, self[:waiter_status]) - end - - define_setting(:last_catering_status, details) - end - - def commit_order(order_details) - self[:order_status] = :pending - status = self[:catering_status] - - if status && status[:progress] == 'visited' - status = status.dup - status[:progress] = 'cleaned' - self[:catering_status] = status - end - - if self[:catering] - sys = system - @oid ||= 1 - systems(self[:catering])[:Orders].add_order({ - id: "#{sys.id}_#{@oid}", - created_at: Time.now.to_i, - room_id: sys.id, - room_name: sys.name, - order: order_details - }) - end - end - - def order_accepted - self[:order_status] = :accepted - end - - def order_complete - self[:order_status] = :idle - end - - # ====================================== # ROOM BOOKINGS: # ====================================== def fetch_bookings(first=false) logger.debug { "looking up todays emails for #{@ews_room}" } - skype_exists = system.exists?(:Skype) task { - todays_bookings(first, skype_exists) + todays_bookings(first) }.then(proc { |bookings| self[:today] = bookings if @check_meeting_ending @@ -449,29 +215,10 @@ def create_meeting(options) req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.iso8601.chop req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.iso8601.chop - task { - username = options[:user] - if username.present? - - user_email = ldap_lookup_email(username) - if user_email - req_params[:user_email] = user_email - make_ews_booking req_params - else - raise "couldn't find user: #{username}" - end - - else - make_ews_booking req_params - end - }.then(proc { |id| - fetch_bookings - logger.debug { "successfully created booking: #{id}" } - "Ok" - }, proc { |error| - logger.print_error error, 'creating ad hoc booking' - thread.reject error # propogate the error - }) + make_ews_booking req_params + fetch_bookings + logger.debug { "successfully created booking: #{id}" } + "Ok" end def should_notify? @@ -542,86 +289,6 @@ def send_email(title, body, to) protected - def swipe_occured(info) - # Update the user details - @last_swipe_at = Time.now.to_i - self[:fullname] = "#{info[:firstname]} #{info[:lastname]}" - self[:username] = info[:staff_id] - email = nil - - if self[:username] && @ldap_creds - email = EMAIL_CACHE[self[:username]] - if email - set_email(email) - logger.debug { "email #{email} found in cache" } - else - # Cache username here as self[:username] might change while we - # looking up the previous username - username = self[:username] - - logger.debug { "looking up email for #{username} - #{self[:fullname]}" } - task { - ldap_lookup_email username - }.then do |email| - if email - logger.debug { "email #{email} found in LDAP" } - EMAIL_CACHE[username] = email - set_email(email) - else - logger.warn "no email found in LDAP for #{username}" - set_email nil - end - end - end - else - logger.warn "no staff ID for user #{self[:fullname]}" - set_email nil - end - end - - def set_email(email) - self[:email] = email - self[:swiped] += 1 - end - - # ==================================== - # LDAP lookup to occur in worker thread - # ==================================== - def ldap_lookup_email(username) - email = EMAIL_CACHE[username] - return email if email - - ldap = Net::LDAP.new @ldap_creds - ldap.authenticate @ldap_user[:username], @ldap_user[:password] if @ldap_user - - login_filter = Net::LDAP::Filter.eq('sAMAccountName', username) - object_filter = Net::LDAP::Filter.eq('objectClass', '*') - treebase = @tree_base - search_attributes = ['mail'] - - email = nil - ldap.bind - ldap.search({ - base: treebase, - filter: object_filter & login_filter, - attributes: search_attributes - }) do |entry| - email = get_attr(entry, 'mail') - end - - # Returns email as a promise - EMAIL_CACHE[username] = email - email - end - - def get_attr(entry, attr_name) - if attr_name != "" && attr_name != nil - entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] - end - end - # ==================================== - - # ======================================= # EWS Requests to occur in a worker thread # ======================================= @@ -701,12 +368,10 @@ def delete_ews_booking(delete_at) count += 1 end end - - # Return the number of meetings removed count end - def todays_bookings(first=false, skype_exists=false) + def todays_bookings(first=false) now = Time.now if @timezone start = now.in_time_zone(@timezone).midnight @@ -730,8 +395,6 @@ def todays_bookings(first=false, skype_exists=false) items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) end - set_skype_url = skype_exists - set_skype_url = true if @force_skype_extract now_int = now.to_i # items.select! { |booking| !booking.cancelled? } @@ -744,54 +407,7 @@ def todays_bookings(first=false, skype_exists=false) real_end = Time.parse(ending) # Extract the skype meeting URL - if set_skype_url - start_integer = real_start.to_i - @skype_check_offset - join_integer = real_start.to_i - @skype_start_offset - end_integer = real_end.to_i - @skype_end_offset - - if now_int > start_integer && now_int < end_integer - if first - self[:last_meeting_started] = start_integer * 1000 - end - meeting.get_all_properties! - - if meeting.body - match = meeting.body.match(/\"pexip\:\/\/(.+?)\"/) - if match - set_skype_url = false - self[:pexip_meeting_uid] = start_integer - self[:pexip_meeting_address] = match[1] - else - links = URI.extract(meeting.body).select { |url| url.start_with?('https://meet.lync') } - if links.empty? - # Lync: - # Skype: - body_parts = meeting.body.split('OutJoinLink"') - if body_parts.length > 1 - links = body_parts[-1].split('"').select { |link| link.start_with?('https://') } - end - end - - if links[0].present? - if now_int > join_integer - self[:can_join_skype_meeting] = true - self[:skype_meeting_pending] = true - else - self[:skype_meeting_pending] = true - end - set_skype_url = false - self[:skype_meeting_address] = links[0] - system[:Skype].set_uri(links[0]) if skype_exists - end - end - end - end - - if @timezone - start = real_start.in_time_zone(@timezone).iso8601[0..18] - ending = real_end.in_time_zone(@timezone).iso8601[0..18] - end - elsif @timezone + if @timezone start = Time.parse(start).in_time_zone(@timezone).iso8601[0..18] ending = Time.parse(ending).in_time_zone(@timezone).iso8601[0..18] end @@ -802,9 +418,6 @@ def todays_bookings(first=false, skype_exists=false) next if Time.parse(ending) - Time.parse(start) > 86399 end - # Prevent connections handing with TIME_WAIT - # cli.ews.connection.httpcli.reset_all - if ["Private", "Confidential"].include?(meeting.sensitivity) subject = meeting.sensitivity booking_owner = "Private" @@ -825,15 +438,5 @@ def todays_bookings(first=false, skype_exists=false) } end results.compact! - - if set_skype_url - self[:pexip_meeting_address] = nil - self[:can_join_skype_meeting] = false - self[:skype_meeting_pending] = false - system[:Skype].set_uri(nil) if skype_exists - end - - results end - # ======================================= -end +end \ No newline at end of file From 383d8c4d97bb968558ffcc9647da18aa56873a72 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 8 Aug 2019 16:58:08 +0800 Subject: [PATCH 1401/1752] EWS>RBP: Simplify settings for ews credentials --- modules/aca/exchange_booking.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 39ff2af0..b886bec9 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -23,16 +23,13 @@ class Aca::ExchangeBooking # The room we are interested in default_settings({ update_every: '2m', - ews_creds: [ - 'https://company.com/EWS/Exchange.asmx', - 'service account', - 'password', - { http_opts: { ssl_verify_mode: 0 } } - ], - ews_room: 'room@email.address' + ews_url: 'https://example.com/EWS/Exchange.asmx', + ews_user: 'service_account', + ews_password: 'service account password', + booking_cancel_email_message: 'The Stop button was pressed on the room booking panel', + booking_timeout_email_message: 'The Start button was not pressed on the room booking panel' }) - def on_load self[:today] = [] on_update @@ -98,6 +95,13 @@ def on_update # Do we want to use exchange web services to manage bookings if CAN_EWS + + office_client_id = setting(:office_client_id) + office_secret = setting(:office_secret) + office_token_url = setting(:office_token_url) + @office_room = (setting(:office_room) || system.email) + + @ews_creds = setting(:ews_creds) @ews_room = (setting(:ews_room) || system.email) if @ews_creds # supports: SMTP, PSMTP, SID, UPN (user principle name) From 22cb9dd97d6905d74144ce30854d691dd29b3eb2 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 8 Aug 2019 17:14:17 +0800 Subject: [PATCH 1402/1752] EWS>RBP: use simplified ews credentials --- modules/aca/exchange_booking.rb | 40 +++++++++------------------------ 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index b886bec9..9c4e7769 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -24,7 +24,7 @@ class Aca::ExchangeBooking default_settings({ update_every: '2m', ews_url: 'https://example.com/EWS/Exchange.asmx', - ews_user: 'service_account', + ews_username: 'service_account', ews_password: 'service account password', booking_cancel_email_message: 'The Stop button was pressed on the room booking panel', booking_timeout_email_message: 'The Start button was not pressed on the room booking panel' @@ -80,36 +80,18 @@ def on_update @check_meeting_ending = setting(:check_meeting_ending) # seconds before meeting ending @extend_meeting_by = setting(:extend_meeting_by) || 15.minutes.to_i - # Do we want to look up the users email address? - if CAN_LDAP - @ldap_creds = setting(:ldap_creds) - if @ldap_creds - encrypt = @ldap_creds[:encryption] - encrypt[:method] = encrypt[:method].to_sym if encrypt && encrypt[:method] - @tree_base = setting(:tree_base) - @ldap_user = @ldap_creds.delete :auth - end - else - logger.warn "net/ldap gem not available" if setting(:ldap_creds) - end - - # Do we want to use exchange web services to manage bookings if CAN_EWS - - office_client_id = setting(:office_client_id) - office_secret = setting(:office_secret) - office_token_url = setting(:office_token_url) - @office_room = (setting(:office_room) || system.email) - - - @ews_creds = setting(:ews_creds) - @ews_room = (setting(:ews_room) || system.email) if @ews_creds - # supports: SMTP, PSMTP, SID, UPN (user principle name) - # NOTE:: Using UPN we might be able to remove the LDAP requirement - @ews_connect_type = (setting(:ews_connect_type) || :SMTP).to_sym - @timezone = setting(:room_timezone) + @ews_creds = [ + setting(:ews_url), + setting(:ews_username), + setting(:ews_password), + { http_opts: { ssl_verify_mode: 0 } } + ] + @ews_room = setting(:room_mailbox) || system.email + @ews_connect_type = (setting(:ews_connect_type) || :SMTP).to_sym # supports: SMTP, PSMTP, SID, UPN + @timezone = setting(:timezone) || ENV['TZ'] else - logger.warn "viewpoint gem not available" if setting(:ews_creds) + logger.error "Viewpoint gem not available" end self[:last_meeting_started] = setting(:last_meeting_started) From d1185187cc6f78806bf27519389d6bc0581a62b6 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 8 Aug 2019 18:12:02 +0800 Subject: [PATCH 1403/1752] EWS>RBP>cleanup: slightly clearer func/var names --- modules/aca/exchange_booking.rb | 171 +++++++++++++++----------------- 1 file changed, 78 insertions(+), 93 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 9c4e7769..94694787 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -36,7 +36,10 @@ def on_load end def on_update - @use_act_as = setting(:use_act_as) + # Set to true if the EWS service account does not have access to directly read room mailboxes, but has access to impersonate room mailboxes + # https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/impersonation-and-ews-in-exchange + @ews_impersonate_room = setting(:ews_impersonate_room) || setting(:use_act_as) + self[:room_name] = setting(:room_name) || system.name self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false @@ -75,6 +78,10 @@ def on_update self[:booking_default_title] = setting(:booking_default_title) || "On the spot booking" self[:booking_select_free] = setting(:booking_select_free) self[:booking_hide_all] = setting(:booking_hide_all) || false + + self[:last_meeting_started] = setting(:last_meeting_started) + self[:cancel_meeting_after] = setting(:cancel_meeting_after) + @hide_all_day_bookings = Boolean(setting(:hide_all_day_bookings)) @check_meeting_ending = setting(:check_meeting_ending) # seconds before meeting ending @@ -87,47 +94,30 @@ def on_update setting(:ews_password), { http_opts: { ssl_verify_mode: 0 } } ] - @ews_room = setting(:room_mailbox) || system.email + @room_mailbox = setting(:room_mailbox) || system.email @ews_connect_type = (setting(:ews_connect_type) || :SMTP).to_sym # supports: SMTP, PSMTP, SID, UPN @timezone = setting(:timezone) || ENV['TZ'] else logger.error "Viewpoint gem not available" end - self[:last_meeting_started] = setting(:last_meeting_started) - self[:cancel_meeting_after] = setting(:cancel_meeting_after) - schedule.clear - schedule.in(rand(10000)) { - fetch_bookings(true) - } - schedule.every((setting(:update_every) || 120000).to_i + rand(10000)) { - fetch_bookings - } + schedule.in(rand(10000)) { fetch_bookings } + fetch_interval = UV::Scheduler.parse_duration(setting(:update_every)) + rand(10000) + schedule.every(fetch_interval) { fetch_bookings } end - - - # ====================================== - # ROOM BOOKINGS: - # ====================================== - def fetch_bookings(first=false) - logger.debug { "looking up todays emails for #{@ews_room}" } + def fetch_bookings + raise "RBP>#{system.id} (#{system.name})>: Room mailbox not configured" unless @room_mailbox + logger.debug { "looking up todays emails for #{@room_mailbox}" } task { - todays_bookings(first) + todays_bookings }.then(proc { |bookings| self[:today] = bookings - if @check_meeting_ending - should_notify? - end + meeting_extendable? if @check_meeting_ending }, proc { |e| logger.print_error(e, 'error fetching bookings') }) end - - # ====================================== - # Meeting Helper Functions - # ====================================== - def start_meeting(meeting_ref) self[:last_meeting_started] = meeting_ref self[:meeting_pending] = meeting_ref @@ -142,15 +132,12 @@ def cancel_meeting(start_time, reason = "timeout") start_time = (start_time / 1000).to_i delete_ews_booking start_time else - # Converts to time object regardless of start_time being string or time object start_time = Time.parse(start_time.to_s).to_i delete_ews_booking start_time end }.then(proc { |count| logger.warn { "successfully removed #{count} bookings due to #{reason}" } - start_meeting(start_time * 1000) - fetch_bookings true }, proc { |error| @@ -183,67 +170,30 @@ def set_end_meeting_warning(meeting_ref = nil, extendable = false) def clear_end_meeting_warning self[:meeting_ending] = self[:last_meeting_started] end - # --------- def create_meeting(options) + raise "RBP>#{system.id} (#{system.name})>: Room mailbox not configured" unless @room_mailbox # Check that the required params exist required_fields = [:start, :end] check = required_fields - options.keys.collect(&:to_sym) if check != [] # There are missing required fields - logger.info "Required fields missing: #{check}" + logger.error "Required fields missing: #{check}" raise "missing required fields: #{check}" end req_params = {} - req_params[:room_email] = @ews_room + req_params[:room_email] = @room_mailbox req_params[:subject] = options[:title] req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.iso8601.chop req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.iso8601.chop make_ews_booking req_params - fetch_bookings - logger.debug { "successfully created booking: #{id}" } - "Ok" - end - - def should_notify? - bookings = self[:today] - return unless bookings.present? - now = Time.now.to_i - - current = nil - pending = nil - found = false - - bookings.sort! { |a, b| a[:end_epoch] <=> b[:end_epoch] } - bookings.each do |booking| - starting = booking[:start_epoch] - ending = booking[:end_epoch] - - if starting < now && ending > now - found = true - current = ending - @current_meeting_title = booking[:Subject] - elsif found - pending = starting - break - end - end - - if !current - self[:meeting_canbe_extended] = false - return - end - - check_start = current - @check_meeting_ending - check_extend = current + @extend_meeting_by - - if now >= check_start && (pending.nil? || pending >= check_extend) - self[:meeting_canbe_extended] = current - else - self[:meeting_canbe_extended] = false + logger.info { "successfully created booking: #{id}" } + schedule.in('2s') do + fetch_bookings end + "Ok" end def extend_meeting @@ -257,14 +207,15 @@ def extend_meeting end def send_email(title, body, to) + raise "RBP>#{system.id} (#{system.name})>: Room mailbox not configured" unless @room_mailbox task { cli = Viewpoint::EWSClient.new(*@ews_creds) opts = {} - if @use_act_as - opts[:act_as] = @ews_room if @ews_room + if @ews_impersonate_room + opts[:act_as] = @room_mailbox else - cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room + cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @room_mailbox) end cli.send_message subject: title, body: body, to_recipients: to @@ -278,7 +229,7 @@ def send_email(title, body, to) # ======================================= # EWS Requests to occur in a worker thread # ======================================= - def make_ews_booking(user_email: nil, subject: 'On the spot booking', room_email:, start_time:, end_time:) + def make_ews_booking(user_email: nil, subject: self[:booking_default_title], room_email:, start_time:, end_time:) user_email ||= self[:email] # if swipe card used booking = { @@ -296,10 +247,10 @@ def make_ews_booking(user_email: nil, subject: 'On the spot booking', room_email cli = Viewpoint::EWSClient.new(*@ews_creds) opts = {} - if @use_act_as - opts[:act_as] = @ews_room if @ews_room + if @ews_impersonate_room + opts[:act_as] = @room_mailbox else - cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room + cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @room_mailbox) end folder = cli.get_folder(:calendar, opts) @@ -329,17 +280,14 @@ def delete_ews_booking(delete_at) cli = Viewpoint::EWSClient.new(*@ews_creds) - if @use_act_as - # TODO:: think this line can be removed?? - # delete_at = Time.parse(delete_at.to_s).to_i - + if @ews_impersonate_room opts = {} - opts[:act_as] = @ews_room if @ews_room + opts[:act_as] = @room_mailbox folder = cli.get_folder(:calendar, opts) items = folder.items({:calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) else - cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room + cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @room_mailbox) items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) end @@ -357,7 +305,7 @@ def delete_ews_booking(delete_at) count end - def todays_bookings(first=false) + def todays_bookings now = Time.now if @timezone start = now.in_time_zone(@timezone).midnight @@ -370,20 +318,19 @@ def todays_bookings(first=false) cli = Viewpoint::EWSClient.new(*@ews_creds) - if @use_act_as + if @ews_impersonate_room opts = {} - opts[:act_as] = @ews_room if @ews_room + opts[:act_as] = @room_mailbox folder = cli.get_folder(:calendar, opts) items = folder.items({:calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) else - cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @ews_room) if @ews_room + cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @room_mailbox) items = cli.find_items({:folder_id => :calendar, :calendar_view => {:start_date => start.utc.iso8601, :end_date => ending.utc.iso8601}}) end now_int = now.to_i - # items.select! { |booking| !booking.cancelled? } results = items.collect do |meeting| item = meeting.ews_item start = item[:start][:text] @@ -392,7 +339,6 @@ def todays_bookings(first=false) real_start = Time.parse(start) real_end = Time.parse(ending) - # Extract the skype meeting URL if @timezone start = Time.parse(start).in_time_zone(@timezone).iso8601[0..18] ending = Time.parse(ending).in_time_zone(@timezone).iso8601[0..18] @@ -425,4 +371,43 @@ def todays_bookings(first=false) end results.compact! end + + def meeting_extendable? + bookings = self[:today] + return unless bookings.present? + now = Time.now.to_i + + current = nil + pending = nil + found = false + + bookings.sort! { |a, b| a[:end_epoch] <=> b[:end_epoch] } + bookings.each do |booking| + starting = booking[:start_epoch] + ending = booking[:end_epoch] + + if starting < now && ending > now + found = true + current = ending + @current_meeting_title = booking[:Subject] + elsif found + pending = starting + break + end + end + + if !current + self[:meeting_canbe_extended] = false + return + end + + check_start = current - @check_meeting_ending + check_extend = current + @extend_meeting_by + + if now >= check_start && (pending.nil? || pending >= check_extend) + self[:meeting_canbe_extended] = current + else + self[:meeting_canbe_extended] = false + end + end end \ No newline at end of file From faca93766cb19651f78e5c8fa660d8521e09156d Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 8 Aug 2019 18:15:31 +0800 Subject: [PATCH 1404/1752] EWS/RBP: default to o365 ews url --- modules/aca/exchange_booking.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 94694787..858a26ac 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -23,7 +23,7 @@ class Aca::ExchangeBooking # The room we are interested in default_settings({ update_every: '2m', - ews_url: 'https://example.com/EWS/Exchange.asmx', + ews_url: 'https://outlook.office365.com/EWS/Exchange.asmx', ews_username: 'service_account', ews_password: 'service account password', booking_cancel_email_message: 'The Stop button was pressed on the room booking panel', From 7b03753fd8af83413ba09e64531ae817bc1140ec Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 8 Aug 2019 18:58:08 +0800 Subject: [PATCH 1405/1752] EWS/RBP/create: fix simple bugs in create and fetch --- modules/aca/exchange_booking.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 858a26ac..2d4482e7 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -188,7 +188,7 @@ def create_meeting(options) req_params[:start_time] = Time.at(options[:start].to_i / 1000).utc.iso8601.chop req_params[:end_time] = Time.at(options[:end].to_i / 1000).utc.iso8601.chop - make_ews_booking req_params + id = make_ews_booking req_params logger.info { "successfully created booking: #{id}" } schedule.in('2s') do fetch_bookings @@ -370,6 +370,7 @@ def todays_bookings } end results.compact! + results end def meeting_extendable? From 95f6122b5c41ce14d53824d7d2d1999a6a588d6a Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 9 Aug 2019 17:16:06 +0800 Subject: [PATCH 1406/1752] EWS/RBP: add :id to booking --- modules/aca/exchange_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 2d4482e7..77f17d42 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -359,6 +359,7 @@ def todays_bookings end { + :id => item.dig(:item_id,:attribs,:id), :Start => start, :End => ending, :Subject => subject, From b0d669672c18d4f31783bffb5fae7756565d3299 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 9 Aug 2019 17:36:31 +0800 Subject: [PATCH 1407/1752] EWS/RBP: booking start/end string now includes timezone --- modules/aca/exchange_booking.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 77f17d42..65bc3152 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -317,7 +317,6 @@ def todays_bookings cli = Viewpoint::EWSClient.new(*@ews_creds) - if @ews_impersonate_room opts = {} opts[:act_as] = @room_mailbox @@ -340,8 +339,8 @@ def todays_bookings real_end = Time.parse(ending) if @timezone - start = Time.parse(start).in_time_zone(@timezone).iso8601[0..18] - ending = Time.parse(ending).in_time_zone(@timezone).iso8601[0..18] + start = Time.parse(start).in_time_zone(@timezone) + ending = Time.parse(ending).in_time_zone(@timezone) end logger.debug { item.inspect } From 22eeecfeb7900369e6eac0635be753a651f151e8 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 9 Aug 2019 17:38:52 +0800 Subject: [PATCH 1408/1752] EWS/RBP: booking start/end string: ensure iso8601 format --- modules/aca/exchange_booking.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 65bc3152..b9c92c75 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -339,8 +339,8 @@ def todays_bookings real_end = Time.parse(ending) if @timezone - start = Time.parse(start).in_time_zone(@timezone) - ending = Time.parse(ending).in_time_zone(@timezone) + start = Time.parse(start).in_time_zone(@timezone).iso8601 + ending = Time.parse(ending).in_time_zone(@timezone).iso8601 end logger.debug { item.inspect } From 24ca59d76ab07a558d7c745ecc197458b148a4c5 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 9 Aug 2019 18:32:32 +0800 Subject: [PATCH 1409/1752] EWS/RBP: temporarily set booking.icaluid to id --- modules/aca/exchange_booking.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index b9c92c75..734d6834 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -359,6 +359,7 @@ def todays_bookings { :id => item.dig(:item_id,:attribs,:id), + :icaluid => item.dig(:item_id,:attribs,:id), :Start => start, :End => ending, :Subject => subject, From a9560529f6d9a1f7e14b3da693d92b93da880ec0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 14 Aug 2019 14:21:59 +1000 Subject: [PATCH 1410/1752] (aca:meeting) merge doors in mode switch if defined --- modules/aca/meeting_room.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index 9c391f0c..d3988018 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -578,6 +578,7 @@ def switch_mode(mode_name, from_join = false, booting: false) # Update the mic mappings self[:mics] = mode[:mics] || setting(:mics) + self[:doors] = mode[:doors] || setting(:doors) # Update the preview mappings self[:has_preview] = mode[:has_preview] || setting(:has_preview) From b809073396f11b98a4056332de3f65b3ceed5a68 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 15 Aug 2019 09:57:33 +1000 Subject: [PATCH 1411/1752] (pexip:management) pexip to return meeting ID --- modules/pexip/management.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index d6fb68a7..956bc0a2 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -19,6 +19,10 @@ def on_load end def on_update + defaults({ + timeout: 10_000 + }) + # NOTE:: base URI https://pexip.company.com @username = setting(:username) @password = setting(:password) @@ -34,21 +38,25 @@ def on_update end MeetingTypes = ["conference", "lecture", "two_stage_dialing", "test_call"] - def new_meeting(name, type = "conference", pin: rand(9999), **options) + def new_meeting(name = nil, type = "conference", pin: rand(9999), **options) type = type.to_s.strip.downcase raise "unknown meeting type" unless MeetingTypes.include?(type) + conf_alias = SecureRandom.uuid + name ||= conf_alias + post('/api/admin/configuration/v1/conference/', body: { name: name.to_s, service_type: type, - pin: pin.to_s.rjust(4, '0') + pin: pin.to_s.rjust(4, '0'), + aliases: [{"alias" => conf_alias}] }.merge(options).to_json, headers: { 'Authorization' => [@username, @password], 'Content-Type' => 'application/json', 'Accept' => 'application/json' }) do |data| if (200...300).include?(data.status) - get_meeting URI(data['location']).path + URI(data['Location']).path.split("/").reject(&:empty?)[-1] else :retry end From 307e335a13c0d57412c4b12722298e663d72e1c5 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 21 Aug 2019 20:52:25 +1000 Subject: [PATCH 1412/1752] (pexip:management) track and clean up old meetings --- modules/pexip/management.rb | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 956bc0a2..39bb06fb 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -23,6 +23,12 @@ def on_update timeout: 10_000 }) + # fallback if meetings are not ended correctly + @vmr_ids ||= setting(:vmr_ids) || {} + clean_up_after = setting(:clean_up_after) || 24.hours.to_i + schedule.clear + schedule.every("30m") { cleanup_meetings(clean_up_after) } + # NOTE:: base URI https://pexip.company.com @username = setting(:username) @password = setting(:password) @@ -38,11 +44,11 @@ def on_update end MeetingTypes = ["conference", "lecture", "two_stage_dialing", "test_call"] - def new_meeting(name = nil, type = "conference", pin: rand(9999), **options) + def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(9999), **options) type = type.to_s.strip.downcase raise "unknown meeting type" unless MeetingTypes.include?(type) - conf_alias = SecureRandom.uuid + conf_alias ||= SecureRandom.uuid name ||= conf_alias post('/api/admin/configuration/v1/conference/', body: { @@ -56,7 +62,10 @@ def new_meeting(name = nil, type = "conference", pin: rand(9999), **options) 'Accept' => 'application/json' }) do |data| if (200...300).include?(data.status) - URI(data['Location']).path.split("/").reject(&:empty?)[-1] + vmr_id = URI(data['Location']).path.split("/").reject(&:empty?)[-1] + @vmr_ids[vmr_id] = Time.now.to_i + define_setting(:vmr_ids, @vmr_ids) + vmr_id else :retry end @@ -82,7 +91,7 @@ def get_meeting(meeting) end end - def end_meeting(meeting) + def end_meeting(meeting, update_ids = true) meeting = "/api/admin/configuration/v1/conference/#{meeting}/" unless meeting.to_s.include?("/") delete(meeting, headers: { @@ -92,12 +101,28 @@ def end_meeting(meeting) }) do |data| case data.status when (200...300) + define_setting(:vmr_ids, @vmr_ids) if update_ids && @vmr_ids.delete(meeting.to_s) :success when 404 + define_setting(:vmr_ids, @vmr_ids) if update_ids && @vmr_ids.delete(meeting.to_s) :success else :retry end end end + + def cleanup_meetings(older_than) + time = Time.now.to_i + delete = [] + @vmr_ids.each do |id, created| + delete << id if (created + older_than) >= time + end + promises = delete.map { |id| end_meeting(id, false) } + thread.all(*promises).then do + delete.each { |id| @vmr_ids.delete(id) } + define_setting(:vmr_ids, @vmr_ids) + end + nil + end end From 6c5f47051eafc78967665bb152f5dcc3e92717c6 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 22 Aug 2019 23:21:06 +1000 Subject: [PATCH 1413/1752] (cisco:catalyst) use mac address table for accuracy cross references link state and mac address table with the snooping table to determine port -> MAC -> IP mappings --- modules/cisco/switch/snooping_catalyst.rb | 50 ++++++++++++++++++----- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/modules/cisco/switch/snooping_catalyst.rb b/modules/cisco/switch/snooping_catalyst.rb index 6f3eea14..7ded6fd0 100644 --- a/modules/cisco/switch/snooping_catalyst.rb +++ b/modules/cisco/switch/snooping_catalyst.rb @@ -79,6 +79,9 @@ def on_load end def on_update + @temp_interface_macs ||= {} + @interface_macs ||= {} + @remote_address = remote_address.downcase @ignore_macs = ::Set.new((setting(:ignore_macs) || {}).values) @temporary = ::Set.new((setting(:temporary_macs) || {}).values) @@ -118,13 +121,26 @@ def query_interface_status do_send 'show interfaces status' end + def query_mac_addresses + @temp_interface_macs.clear + do_send 'show mac address-table' + end + def query_connected_devices logger.debug { "Querying for connected devices" } query_interface_status.then do schedule.in(3000) do - p = query_snooping_bindings - p.then { schedule.in(10000) { check_reservations } } if @reserve_time > 0 + query_mac_addresses.then do + schedule.in(3000) do + p = query_snooping_bindings + p.then { schedule.in(10000) { check_reservations; nil }; nil } if @reserve_time > 0 + nil + end + nil + end + nil end + nil end nil end @@ -154,6 +170,19 @@ def received(data, resolve, command) return :success end + # Interface MAC Address detection + # 33 e4b9.7aa5.aa7f STATIC Gi3/0/8 + # 10 f4db.e618.10a4 DYNAMIC Te2/0/40 + if data =~ /STATIC|DYNAMIC/ + parts = data.split(/\s+/).reject(&:empty?) + mac = format(parts[1]) + interface = normalise(parts[-1]) + + @temp_interface_macs[interface] = mac if mac && interface + + return :success + end + # Interface change detection # 07-Aug-2014 17:28:26 %LINK-I-Up: gi2 # 07-Aug-2014 17:28:31 %STP-W-PORTSTATUS: gi2: STP status Forwarding @@ -189,18 +218,19 @@ def received(data, resolve, command) logger.debug { "Processing #{@snooping.length} bindings" } checked = Set.new - checked_interfaces = Set.new + @interface_macs = @temp_interface_macs unless @temp_interface_macs.empty? + #checked_interfaces = Set.new # Newest lease first - @snooping.sort! { |a, b| b[0] <=> a[0] } + # @snooping.sort! { |a, b| b[0] <=> a[0] } # NOTE:: Same as snooping_catalyst_snmp.rb # Ignore any duplicates @snooping.each do |lease, mac, ip, interface| - next if checked_interfaces.include?(interface) - checked_interfaces << interface - checked << interface + next unless @check_interface.include?(interface) + next unless @interface_macs[interface] == mac + checked << interface iface = self[interface] || ::Aca::Tracking::StaticDetails.new if iface.ip != ip || iface.mac != mac @@ -252,9 +282,9 @@ def received(data, resolve, command) end end - @connected_interfaces = checked - self[:interfaces] = checked.to_a - (@check_interface - checked).each { |iface| remove_lookup(iface) } + # @interface_macs + @connected_interfaces = @check_interface + self[:interfaces] = @connected_interfaces.to_a self[:reserved] = @reserved_interface.to_a @snooping.clear From 0d2b80be31246bde9fffc09b4e785d88dca10db7 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 27 Aug 2019 14:24:43 +0800 Subject: [PATCH 1414/1752] o365/lib/event/format/attendees: dont error on attendees without an @ (safe navigators for upcase/capitalize) --- lib/microsoft/office/event.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index 011581fc..3c76ac50 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -94,10 +94,10 @@ def format_attendees(attendees, organizer) # Compare the domain of this attendee's email against the internal domain mail_object = ::Mail::Address.new(attendee_email) - mail_domain = mail_object.domain + mail_domain = mail_object.domain&.downcase is_visitor = !(internal_domains.map{|d| d.downcase - }.include?(mail_domain.downcase)) + }.include?(mail_domain)) # Alias the attendee fields, mark whether this is a visitor and pull organisation details from the email attendee_object = { @@ -105,7 +105,7 @@ def format_attendees(attendees, organizer) name: attendee['emailAddress']['name'], visitor: is_visitor, external: is_visitor, - organisation: attendee_email.split('@')[1..-1].join("").split('.')[0].capitalize + organisation: attendee_email.split('@')[1..-1].join("").split('.')[0]&.capitalize } new_attendees.push(attendee_object) if attendee['type'] != 'resource' end From ee05c2694e58aa2328d4c1486d2b9165e2239521 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 28 Aug 2019 18:07:14 +0800 Subject: [PATCH 1415/1752] o365/RBP/settings/defaults: add o365 keys to guide user --- modules/aca/o365_booking_panel.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index e0bf3beb..6b4d5ce6 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -20,7 +20,10 @@ class Aca::O365BookingPanel default_settings({ update_every: '2m', booking_cancel_email_message: 'The Stop button was presseed on the room booking panel', - booking_timeout_email_message: 'The Start button was not pressed on the room booking panel' + booking_timeout_email_message: 'The Start button was not pressed on the room booking panel', + office_client_id: "enter client ID", + office_secret: "enter client secret", + office_token_url: "/tenant_name_or_ID.onMicrosoft.com/oauth2/v2.0/token" }) def on_load From dee272173de53b7e126d04fcb4d4dcd4eec5314c Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 28 Aug 2019 18:27:35 +0800 Subject: [PATCH 1416/1752] o365/RBP/settings: User simply enters tenant name or ID instead of full token url --- modules/aca/o365_booking_panel.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 6b4d5ce6..e6197eb4 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -23,7 +23,7 @@ class Aca::O365BookingPanel booking_timeout_email_message: 'The Start button was not pressed on the room booking panel', office_client_id: "enter client ID", office_secret: "enter client secret", - office_token_url: "/tenant_name_or_ID.onMicrosoft.com/oauth2/v2.0/token" + office_token: "tenant_name_or_ID.onMicrosoft.com" }) def on_load @@ -71,18 +71,19 @@ def on_update self[:booking_select_free] = setting(:booking_select_free) self[:booking_hide_all] = setting(:booking_hide_all) || false - office_client_id = setting(:office_client_id) - office_secret = setting(:office_secret) - office_token_url = setting(:office_token_url) + office_client_id = setting(:office_client_id) || ENV['OFFICE_CLIENT_ID'] + office_secret = setting(:office_secret) || ENV["OFFICE_CLIENT_SECRET"] + office_token_path = setting(:office_token_path) || "/oauth2/v2.0/token" + office_token_url = setting(:office_token_url) || ENV["OFFICE_TOKEN_URL"] || "/" + setting(:office_token) + office_token_path @office_room = (setting(:office_room) || system.email) #office_https_proxy = setting(:office_https_proxy) logger.debug "RBP>#{@office_room}>INIT: Instantiating o365 Graph API client" @client = ::Microsoft::Officenew::Client.new({ - client_id: office_client_id || ENV['OFFICE_CLIENT_ID'], - client_secret: office_secret || ENV["OFFICE_CLIENT_SECRET"], - app_token_url: office_token_url || ENV["OFFICE_TOKEN_URL"] + client_id: office_client_id, + client_secret: office_secret, + app_token_url: office_token_url }) self[:last_meeting_started] = setting(:last_meeting_started) From 5f5c8e00f39c71f1989b777d7886b5a59b7e439c Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 28 Aug 2019 18:28:55 +0800 Subject: [PATCH 1417/1752] o365/RBP/settings: rename office_token to office_tenant, as it makes more sense --- modules/aca/o365_booking_panel.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index e6197eb4..a7a9b420 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -23,7 +23,7 @@ class Aca::O365BookingPanel booking_timeout_email_message: 'The Start button was not pressed on the room booking panel', office_client_id: "enter client ID", office_secret: "enter client secret", - office_token: "tenant_name_or_ID.onMicrosoft.com" + office_tenant: "tenant_name_or_ID.onMicrosoft.com" }) def on_load @@ -74,7 +74,7 @@ def on_update office_client_id = setting(:office_client_id) || ENV['OFFICE_CLIENT_ID'] office_secret = setting(:office_secret) || ENV["OFFICE_CLIENT_SECRET"] office_token_path = setting(:office_token_path) || "/oauth2/v2.0/token" - office_token_url = setting(:office_token_url) || ENV["OFFICE_TOKEN_URL"] || "/" + setting(:office_token) + office_token_path + office_token_url = setting(:office_token_url) || ENV["OFFICE_TOKEN_URL"] || "/" + setting(:office_tenant) + office_token_path @office_room = (setting(:office_room) || system.email) #office_https_proxy = setting(:office_https_proxy) From 7b673d2a607976330d3e0678fe2138366ef01be7 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 30 Aug 2019 17:06:05 +0800 Subject: [PATCH 1418/1752] o365/lib/event/extensions: safe navigate to extension.id --- lib/microsoft/office/event.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office/event.rb index 3c76ac50..5eb8a4ef 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office/event.rb @@ -60,7 +60,7 @@ def datestring_to_epoch(date_object) def get_extensions(new_event, old_event) if old_event.key?('extensions') old_event['extensions'].each do |ext| - if ext['id'] == "Microsoft.OutlookServices.OpenTypeExtension.Com.Acaprojects.Extensions" + if ext&.dig('id') == "Microsoft.OutlookServices.OpenTypeExtension.Com.Acaprojects.Extensions" ext.each do |ext_key, ext_val| new_event[ext_key] = ext_val if !['@odata.type', '@odata.context', 'id','extensionName'].include?(ext_key) end From 24ba1e0851eeb7b59d7d0e535283a3fdccf81767 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Sep 2019 17:02:56 +0800 Subject: [PATCH 1419/1752] o365/lib/check_response: return success code --- lib/microsoft/office.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office.rb b/lib/microsoft/office.rb index b838680a..702df6be 100644 --- a/lib/microsoft/office.rb +++ b/lib/microsoft/office.rb @@ -178,7 +178,7 @@ def log_graph_request(request_method, data, query, headers, graph_path, password def check_response(response) case response.status when 200, 201, 204 - return + return response.status when 400 if response['error']['code'] == 'ErrorInvalidIdMalformed' raise Microsoft::Error::ErrorInvalidIdMalformed.new(response.body) From af324e3af8f8ab7e50c9c18c09111a1232ba30a8 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Sep 2019 17:04:02 +0800 Subject: [PATCH 1420/1752] o365/lib: Support Calendars (create/delete/list) --- lib/microsoft/office/calendars.rb | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 lib/microsoft/office/calendars.rb diff --git a/lib/microsoft/office/calendars.rb b/lib/microsoft/office/calendars.rb new file mode 100644 index 00000000..2c0cc58a --- /dev/null +++ b/lib/microsoft/office/calendars.rb @@ -0,0 +1,77 @@ +module Microsoft::Officenew::Calendars + ## + # CRUD for MS Graph API Calendars + # https://docs.microsoft.com/en-us/graph/api/resources/calendar?view=graph-rest-1.0 + # + # @param mailbox [String] The mailbox where the calendars will be created/read + # @param limit [String] The maximum number of calendars to return + def list_calendars(mailbox:, calendargroup_id:nil, limit:nil) + query_params = { '$top': (limit || 99) } + case calendargroup_id + when nil + endpoint = "/v1.0/users/#{mailbox}/calendars" + when 'default' + endpoint = "/v1.0/users/#{mailbox}/calendarGroup/calendars" + else + endpoint = "/v1.0/users/#{mailbox}/calendarGroups/#{calendargroup_id}/calendars" + end + request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) + check_response(request) + JSON.parse(request.body)['value'] + end + + def list_calendargroups(mailbox:, limit:nil) + query_params = { '$top': (limit || 99) } + endpoint = "/v1.0/users/#{mailbox}/calendarGroups" + request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) + check_response(request) + JSON.parse(request.body)['value'] + end + + # Add a new calendar to the passed in mailbox. + # @param name [String] The name for any new calendar/group being created + # @param calendargroup_id [String] Optional: The ID of the calendargroup inside which to create the calendar + def create_calendar(mailbox:, name:, calendargroup_id: nil) + case calendargroup_id + when nil + endpoint = "/v1.0/users/#{mailbox}/calendars" + when 'default' + endpoint = "/v1.0/users/#{mailbox}/calendarGroup/calendars" + else + endpoint = "/v1.0/users/#{mailbox}/calendarGroups/#{calendargroup_id}/calendars" + end + request = graph_request(request_method: 'post', endpoints: [endpoint], data: {name: name}) + check_response(request) + JSON.parse(request.body)['value'] + end + + def create_calendargroup(mailbox:, name:) + endpoint = "/v1.0/users/#{mailbox}/calendarGroups" + request = graph_request(request_method: 'post', endpoints: [endpoint], data: {name: name}) + check_response(request) + JSON.parse(request.body)['value'] + end + + + # Delete a calendar from the passed in mailbox. + # @param id [String] The ID of the calendar to be deleted + # @param calendargroup_id [String] Optional: The ID of the calendargroup in which to locate the calendar + def delete_calendar(mailbox:, id:, calendargroup_id: nil) + case calendargroup_id + when nil + endpoint = "/v1.0/users/#{mailbox}/calendars/#{id}" + when 'default' + endpoint = "/v1.0/users/#{mailbox}/calendarGroup/calendars/#{id}" + else + endpoint = "/v1.0/users/#{mailbox}/calendarGroups/#{calendargroup_id}/calendars/#{id}" + end + request = graph_request(request_method: 'delete', endpoints: [endpoint]) + check_response(request) + end + + def delete_calendargroup(mailbox:, id:) + endpoint = "/v1.0/users/#{mailbox}/calendarGroups/#{id}" + request = graph_request(request_method: 'delete', endpoints: [endpoint]) + check_response(request) + end +end \ No newline at end of file From becdda99329c3891e9f1249603bfcaa8cd8074c1 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Sep 2019 18:28:27 +0800 Subject: [PATCH 1421/1752] o365/lib: RENAME officenew to office2 --- lib/microsoft/office2.rb | 2 ++ .../{office => office2}/calendars.rb | 2 +- lib/microsoft/{office => office2}/client.rb | 27 ++++++++++--------- lib/microsoft/{office => office2}/contact.rb | 2 +- lib/microsoft/{office => office2}/contacts.rb | 6 ++--- lib/microsoft/{office => office2}/event.rb | 2 +- lib/microsoft/{office => office2}/events.rb | 8 +++--- lib/microsoft/{office => office2}/model.rb | 2 +- lib/microsoft/{office => office2}/user.rb | 2 +- lib/microsoft/{office => office2}/users.rb | 4 +-- lib/microsoft/officenew.rb | 2 -- modules/aca/o365_booking_panel.rb | 6 ++--- spec/libs/microsoft/office/contacts/create.rb | 2 +- spec/libs/microsoft/office/contacts/read.rb | 2 +- spec/libs/microsoft/office/events/create.rb | 14 +++++----- spec/libs/microsoft/office/events/delete.rb | 4 +-- spec/libs/microsoft/office/events/read.rb | 12 ++++----- spec/libs/microsoft/office/events/update.rb | 10 +++---- spec/libs/microsoft/office/users/read.rb | 4 +-- 19 files changed, 57 insertions(+), 56 deletions(-) create mode 100644 lib/microsoft/office2.rb rename lib/microsoft/{office => office2}/calendars.rb (98%) rename lib/microsoft/{office => office2}/client.rb (92%) rename lib/microsoft/{office => office2}/contact.rb (90%) rename lib/microsoft/{office => office2}/contacts.rb (94%) rename lib/microsoft/{office => office2}/event.rb (98%) rename lib/microsoft/{office => office2}/events.rb (97%) rename lib/microsoft/{office => office2}/model.rb (97%) rename lib/microsoft/{office => office2}/user.rb (91%) rename lib/microsoft/{office => office2}/users.rb (95%) delete mode 100644 lib/microsoft/officenew.rb diff --git a/lib/microsoft/office2.rb b/lib/microsoft/office2.rb new file mode 100644 index 00000000..4b91dff4 --- /dev/null +++ b/lib/microsoft/office2.rb @@ -0,0 +1,2 @@ +module Microsoft; end +class Microsoft::Office2; end \ No newline at end of file diff --git a/lib/microsoft/office/calendars.rb b/lib/microsoft/office2/calendars.rb similarity index 98% rename from lib/microsoft/office/calendars.rb rename to lib/microsoft/office2/calendars.rb index 2c0cc58a..07f0ce14 100644 --- a/lib/microsoft/office/calendars.rb +++ b/lib/microsoft/office2/calendars.rb @@ -1,4 +1,4 @@ -module Microsoft::Officenew::Calendars +module Microsoft::Office2::Calendars ## # CRUD for MS Graph API Calendars # https://docs.microsoft.com/en-us/graph/api/resources/calendar?view=graph-rest-1.0 diff --git a/lib/microsoft/office/client.rb b/lib/microsoft/office2/client.rb similarity index 92% rename from lib/microsoft/office/client.rb rename to lib/microsoft/office2/client.rb index ea356d2d..2ff572b0 100644 --- a/lib/microsoft/office/client.rb +++ b/lib/microsoft/office2/client.rb @@ -1,13 +1,14 @@ require 'active_support/time' require 'oauth2' -require 'microsoft/officenew' -require 'microsoft/office/model' -require 'microsoft/office/user' -require 'microsoft/office/users' -require 'microsoft/office/contact' -require 'microsoft/office/contacts' -require 'microsoft/office/event' -require 'microsoft/office/events' +require 'microsoft/office2' +require 'microsoft/office2/model' +require 'microsoft/office2/user' +require 'microsoft/office2/users' +require 'microsoft/office2/contact' +require 'microsoft/office2/contacts' +require 'microsoft/office2/event' +require 'microsoft/office2/events' +require 'microsoft/office2/calendars' module Microsoft class Error < StandardError class ResourceNotFound < Error; end @@ -18,16 +19,16 @@ class ErrorAccessDenied < Error; end end end -class Microsoft::Officenew; end +class Microsoft::Office2; end ## # This class provides a client to interface between Microsoft Graph API and ACA Engine. Instances of this class are # primarily only used for: # - -class Microsoft::Officenew::Client - include Microsoft::Officenew::Events - include Microsoft::Officenew::Users - include Microsoft::Officenew::Contacts +class Microsoft::Office2::Client + include Microsoft::Office2::Events + include Microsoft::Office2::Users + include Microsoft::Office2::Contacts ## # Initialize the client for making requests to the Office365 API. diff --git a/lib/microsoft/office/contact.rb b/lib/microsoft/office2/contact.rb similarity index 90% rename from lib/microsoft/office/contact.rb rename to lib/microsoft/office2/contact.rb index 92d72265..e7bad8b8 100644 --- a/lib/microsoft/office/contact.rb +++ b/lib/microsoft/office2/contact.rb @@ -1,4 +1,4 @@ -class Microsoft::Officenew::Contact < Microsoft::Officenew::Model +class Microsoft::Office2::Contact < Microsoft::Office2::Model ALIAS_FIELDS = { 'id' => 'id', diff --git a/lib/microsoft/office/contacts.rb b/lib/microsoft/office2/contacts.rb similarity index 94% rename from lib/microsoft/office/contacts.rb rename to lib/microsoft/office2/contacts.rb index 4d842f67..41f2391a 100644 --- a/lib/microsoft/office/contacts.rb +++ b/lib/microsoft/office2/contacts.rb @@ -1,4 +1,4 @@ -module Microsoft::Officenew::Contacts +module Microsoft::Office2::Contacts ## # Retrieve a list of contacts for some passed in mailbox # @@ -11,7 +11,7 @@ def get_contacts(mailbox:, q:nil, limit:nil) endpoint = "/v1.0/users/#{mailbox}/contacts" request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) check_response(request) - JSON.parse(request.body)['value'].map {|c| Microsoft::Officenew::Contact.new(client: self, contact: c).contact} + JSON.parse(request.body)['value'].map {|c| Microsoft::Office2::Contact.new(client: self, contact: c).contact} end ## @@ -57,7 +57,7 @@ def create_contact(mailbox:, email:, first_name:, last_name:, options:{}) # Make the request and return the result request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}/contacts"], data: contact_data) check_response(request) - Microsoft::Officenew::Contact.new(client: self, contact: JSON.parse(request.body)).contact + Microsoft::Office2::Contact.new(client: self, contact: JSON.parse(request.body)).contact end ## diff --git a/lib/microsoft/office/event.rb b/lib/microsoft/office2/event.rb similarity index 98% rename from lib/microsoft/office/event.rb rename to lib/microsoft/office2/event.rb index 3c76ac50..deababe0 100644 --- a/lib/microsoft/office/event.rb +++ b/lib/microsoft/office2/event.rb @@ -1,5 +1,5 @@ require 'mail' -class Microsoft::Officenew::Event < Microsoft::Officenew::Model +class Microsoft::Office2::Event < Microsoft::Office2::Model # These are the fields which we just want to alias or pull out of the O365 model without any processing ALIAS_FIELDS = { diff --git a/lib/microsoft/office/events.rb b/lib/microsoft/office2/events.rb similarity index 97% rename from lib/microsoft/office/events.rb rename to lib/microsoft/office2/events.rb index 0d3bbca4..5eeae0ac 100644 --- a/lib/microsoft/office/events.rb +++ b/lib/microsoft/office2/events.rb @@ -1,4 +1,4 @@ -module Microsoft::Officenew::Events +module Microsoft::Office2::Events ## # For every mailbox (email) passed in, this method will grab all the bookings and, if # requested, return the availability of the mailboxes for some time range. @@ -112,7 +112,7 @@ def get_bookings(mailboxes:, options:{}) next unless bookings # Go through each booking and extract more info from it bookings.each_with_index do |booking, i| - bookings[i] = Microsoft::Officenew::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]).event + bookings[i] = Microsoft::Office2::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]).event is_available = false if !bookings[i]['is_free'] && !options[:ignore_bookings].include?(bookings[i]['id']) end bookings_by_room[mailboxes[res['id'].to_i]] = {available: is_available, bookings: bookings} @@ -176,7 +176,7 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) # Make the request and check the response request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}/events"], data: event_json) check_response(request) - Microsoft::Officenew::Event.new(client: self, event: JSON.parse(request.body)).event + Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body)).event end ## @@ -246,7 +246,7 @@ def update_booking(booking_id:, mailbox:, options: {}) request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}/events/#{booking_id}"], data: event_json) check_response(request) - Microsoft::Officenew::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event + Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event end ## diff --git a/lib/microsoft/office/model.rb b/lib/microsoft/office2/model.rb similarity index 97% rename from lib/microsoft/office/model.rb rename to lib/microsoft/office2/model.rb index adec7608..4bc33bc8 100644 --- a/lib/microsoft/office/model.rb +++ b/lib/microsoft/office2/model.rb @@ -1,4 +1,4 @@ -class Microsoft::Officenew::Model +class Microsoft::Office2::Model def create_aliases(object, alias_fields, new_fields, model) aliased_contact = object.each_with_object({}) do |(k,v), h| diff --git a/lib/microsoft/office/user.rb b/lib/microsoft/office2/user.rb similarity index 91% rename from lib/microsoft/office/user.rb rename to lib/microsoft/office2/user.rb index 55c24ede..8248fbd6 100644 --- a/lib/microsoft/office/user.rb +++ b/lib/microsoft/office2/user.rb @@ -1,4 +1,4 @@ -class Microsoft::Officenew::User < Microsoft::Officenew::Model +class Microsoft::Office2::User < Microsoft::Office2::Model ALIAS_FIELDS = { 'id' => 'id', diff --git a/lib/microsoft/office/users.rb b/lib/microsoft/office2/users.rb similarity index 95% rename from lib/microsoft/office/users.rb rename to lib/microsoft/office2/users.rb index 27d7c2cc..b34e907a 100644 --- a/lib/microsoft/office/users.rb +++ b/lib/microsoft/office2/users.rb @@ -1,4 +1,4 @@ -module Microsoft::Officenew::Users +module Microsoft::Office2::Users ## # Retrieve a list of users stored in Office365 # @@ -37,6 +37,6 @@ def get_users(q: nil, limit: nil) check_response(request) # Return the parsed user data - JSON.parse(request.body)['value'].map {|u| Microsoft::Officenew::User.new(client: self, user: u).user} + JSON.parse(request.body)['value'].map {|u| Microsoft::Office2::User.new(client: self, user: u).user} end end \ No newline at end of file diff --git a/lib/microsoft/officenew.rb b/lib/microsoft/officenew.rb deleted file mode 100644 index aa7978a5..00000000 --- a/lib/microsoft/officenew.rb +++ /dev/null @@ -1,2 +0,0 @@ -module Microsoft; end -class Microsoft::Officenew; end \ No newline at end of file diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index e0bf3beb..1f21b66c 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -1,8 +1,8 @@ # encoding: ASCII-8BIT require 'faraday' require 'uv-rays' -require 'microsoft/officenew' -require 'microsoft/office/client' +require 'microsoft/office2' +require 'microsoft/office2/client' Faraday.default_adapter = :libuv module Aca; end @@ -76,7 +76,7 @@ def on_update logger.debug "RBP>#{@office_room}>INIT: Instantiating o365 Graph API client" - @client = ::Microsoft::Officenew::Client.new({ + @client = ::Microsoft::Office2::Client.new({ client_id: office_client_id || ENV['OFFICE_CLIENT_ID'], client_secret: office_secret || ENV["OFFICE_CLIENT_SECRET"], app_token_url: office_token_url || ENV["OFFICE_TOKEN_URL"] diff --git a/spec/libs/microsoft/office/contacts/create.rb b/spec/libs/microsoft/office/contacts/create.rb index ecdc6e8b..5131cbab 100644 --- a/spec/libs/microsoft/office/contacts/create.rb +++ b/spec/libs/microsoft/office/contacts/create.rb @@ -28,7 +28,7 @@ end it "should create a contact with corresponding details" do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) contact = nil reactor.run { contact = @office.create_contact(mailbox: @mailbox, email: @email, first_name: @first_name, last_name: @last_name, options:{ title: @title, phone: @phone, organisation: @organisation }) diff --git a/spec/libs/microsoft/office/contacts/read.rb b/spec/libs/microsoft/office/contacts/read.rb index 1376e749..54e12fa0 100644 --- a/spec/libs/microsoft/office/contacts/read.rb +++ b/spec/libs/microsoft/office/contacts/read.rb @@ -25,7 +25,7 @@ @organisation = "Company inc" @title = "Mr" @phone = "0404851331" - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) @contact = nil reactor.run { @contact = @office.create_contact(mailbox: @mailbox, email: @email, first_name: @first_name, last_name: @last_name, options:{ title: @title, phone: @phone, organisation: @organisation }) diff --git a/spec/libs/microsoft/office/events/create.rb b/spec/libs/microsoft/office/events/create.rb index ff6390c3..dbee2175 100644 --- a/spec/libs/microsoft/office/events/create.rb +++ b/spec/libs/microsoft/office/events/create.rb @@ -40,12 +40,12 @@ end it "should initialize with client application details" do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) expect(@office).not_to be_nil end it 'should create events in o365 at the passed in time' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) booking = nil reactor.run { booking = @office.create_booking(@booking_body) @@ -57,7 +57,7 @@ end it 'should create events in o365 with the passed in attendees' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) booking = nil reactor.run { booking = @office.create_booking(@booking_body) @@ -72,7 +72,7 @@ end it 'should create events in o365 containing the passed in rooms' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) booking = nil reactor.run { booking = @office.create_booking(@booking_body) @@ -85,7 +85,7 @@ end it 'should create events in o365 with the passed in title' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) booking = nil reactor.run { booking = @office.create_booking(@booking_body) @@ -98,7 +98,7 @@ end it 'should create events in o365 with the body as the passed in description' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) booking = nil reactor.run { booking = @office.create_booking(@booking_body) @@ -111,7 +111,7 @@ end it 'should create events in o365 with any passed in extensions at the root of the event' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) booking = nil reactor.run { booking = @office.create_booking(@booking_body) diff --git a/spec/libs/microsoft/office/events/delete.rb b/spec/libs/microsoft/office/events/delete.rb index 55d79664..37f21ff7 100644 --- a/spec/libs/microsoft/office/events/delete.rb +++ b/spec/libs/microsoft/office/events/delete.rb @@ -38,7 +38,7 @@ extensions: @extensions } } - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) @booking = nil reactor.run { @booking = @office.create_booking(@booking_body) @@ -46,7 +46,7 @@ end it 'should return 200 when an event is successfully deleted' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) repsonse = nil reactor.run { repsonse = @office.delete_booking(mailbox: @mailbox, booking_id: @booking['id']) diff --git a/spec/libs/microsoft/office/events/read.rb b/spec/libs/microsoft/office/events/read.rb index df5f1663..4d12e785 100644 --- a/spec/libs/microsoft/office/events/read.rb +++ b/spec/libs/microsoft/office/events/read.rb @@ -38,7 +38,7 @@ extensions: @extensions } } - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) @booking = nil reactor.run { @booking = @office.create_booking(@booking_body) @@ -46,7 +46,7 @@ end it 'should return events within the passed in time range INCLUSIVE of start and end time' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) bookings = nil reactor.run { bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { bookings_from: @start_time.to_i, bookings_to: @end_time.to_i }) @@ -59,7 +59,7 @@ end it 'should not return events outside the passed in time range' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) bookings = nil reactor.run { bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { bookings_from: (@start_time + 2.hours).to_i, bookings_to: (@end_time + 3.hours).to_i }) @@ -71,7 +71,7 @@ end it 'should return the room as unavailable when checking availability in the time range' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) bookings = nil reactor.run { bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { available_from: @start_time.to_i, available_to: @end_time.to_i }) @@ -83,7 +83,7 @@ end it 'should return the room as available if the conflicting booking ID is passed to the ignore_bookings param' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) bookings = nil reactor.run { bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { available_from: @start_time.to_i, available_to: @end_time.to_i, ignore_bookings: [@booking['id']] }) @@ -95,7 +95,7 @@ end it 'should return events with their extension data at the root of the event' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) bookings = nil reactor.run { bookings = @office.get_bookings(mailboxes: [ @mailbox ], options: { bookings_from: @start_time.to_i, bookings_to: @end_time.to_i }) diff --git a/spec/libs/microsoft/office/events/update.rb b/spec/libs/microsoft/office/events/update.rb index a06b6ff4..b25c1948 100644 --- a/spec/libs/microsoft/office/events/update.rb +++ b/spec/libs/microsoft/office/events/update.rb @@ -37,7 +37,7 @@ extensions: @extensions } } - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) @booking = nil reactor.run { @booking = @office.create_booking(@booking_body) @@ -69,7 +69,7 @@ end it 'should return updated events with the passed in time' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) updated_booking = nil reactor.run { updated_booking = @office.update_booking(@update_body) @@ -81,7 +81,7 @@ end it 'should return updated events with the passed in attendees' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) updated_booking = nil reactor.run { updated_booking = @office.update_booking(@update_body) @@ -94,7 +94,7 @@ } end it 'should return updated events with the passed in subject' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) updated_booking = nil reactor.run { updated_booking = @office.update_booking(@update_body) @@ -108,7 +108,7 @@ end it 'should return updated events with the passed in time' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) updated_booking = nil reactor.run { updated_booking = @office.update_booking(@update_body) diff --git a/spec/libs/microsoft/office/users/read.rb b/spec/libs/microsoft/office/users/read.rb index b1ca0a9a..a99fbc4a 100644 --- a/spec/libs/microsoft/office/users/read.rb +++ b/spec/libs/microsoft/office/users/read.rb @@ -23,7 +23,7 @@ end it 'should return all users with name or email matching query string' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) users = nil reactor.run { users = @office.get_users(q: @query) @@ -34,7 +34,7 @@ end it 'should return one user if an email is passed' do - @office = ::Microsoft::Officenew::Client.new(@office_credentials) + @office = ::Microsoft::Office2::Client.new(@office_credentials) users = nil reactor.run { users = @office.get_users(q: @email) From 465aab15b8077170ffb56a41ae7c60ff4e0d11b0 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 4 Sep 2019 16:25:52 +0800 Subject: [PATCH 1422/1752] office2/events: allow optional calendarID and calendargroup --- lib/microsoft/office2/events.rb | 37 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 5eeae0ac..e3c43b8f 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -39,7 +39,7 @@ module Microsoft::Office2::Events # ... # } # } - def get_bookings(mailboxes:, options:{}) + def get_bookings(mailboxes:, calendargroup_id: nil, calendar_id: nil, options:{}) default_options = { created_from: nil, bookings_from: nil, @@ -69,13 +69,8 @@ def get_bookings(mailboxes:, options:{}) options[:available_to] = options[:bookings_to] end - # If we are using created_from then we cannot use the calendarView and must use events - endpoint = ( options[:created_from].nil? ? "calendarView" : "events" ) - - # Get all of the endpoints for our bulk request - all_endpoints = mailboxes.map do |email| - "/users/#{email}/#{endpoint}" - end + calendar_endpoint = calendar_path(calendargroup_id, calendar_id) + (options[:created_from] ? "/events" : "/calendarView") # If we are using created_from then we cannot use the calendarView and must use events + all_endpoints = mailboxes.map { |email| "/users/#{email}#{calendar_endpoint}" } # This is currently the max amount of queries per bulk request slice_size = 20 @@ -141,7 +136,7 @@ def get_bookings(mailboxes:, options:{}) # @option options [String] :timezone The timezone of the booking. This will be overridden by a timezone in the room's settings # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking # @option options [String] :location The location field to set. This will not be used if a room is passed in - def create_booking(mailbox:, start_param:, end_param:, options: {}) + def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, calendar_id: nil, options: {}) default_options = { rooms: [], subject: "Meeting", @@ -172,9 +167,9 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) extensions: options[:extensions], is_private: options[:is_private] ) - + # Make the request and check the response - request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}/events"], data: event_json) + request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events"], data: event_json) check_response(request) Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body)).event end @@ -198,7 +193,7 @@ def create_booking(mailbox:, start_param:, end_param:, options: {}) # @option options [String] :timezone The timezone of the booking. This will be overridden by a timezone in the room's settings # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking # @option options [String] :location The location field to set. This will not be used if a room is passed in - def update_booking(booking_id:, mailbox:, options: {}) + def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: nil, options: {}) default_options = { start_param: nil, end_param: nil, @@ -243,7 +238,7 @@ def update_booking(booking_id:, mailbox:, options: {}) end # Make the request and check the response - request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}/events/#{booking_id}"], data: event_json) + request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}"], data: event_json) check_response(request) Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event @@ -254,8 +249,8 @@ def update_booking(booking_id:, mailbox:, options: {}) # # @param mailbox [String] The mailbox email which contains the booking to delete # @param booking_id [String] The ID of the booking to be deleted - def delete_booking(mailbox:, booking_id:) - endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}" + def delete_booking(mailbox:, booking_id:, calendargroup_id: nil, calendar_id: nil) + endpoint = "/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}" request = graph_request(request_method: 'delete', endpoints: [endpoint]) check_response(request) 200 @@ -267,8 +262,8 @@ def delete_booking(mailbox:, booking_id:) # @param mailbox [String] The mailbox email which contains the booking to delete # @param booking_id [String] The ID of the booking to be deleted # @param comment [String] An optional message that will be included in the body of the automated email that will be sent to the host of the meeting - def decline_meeting(mailbox:, booking_id:, comment: '') - endpoint = "/v1.0/users/#{mailbox}/events/#{booking_id}/decline" + def decline_meeting(mailbox:, booking_id:, comment: '', calendargroup_id: nil, calendar_id: nil) + endpoint = "/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}/decline" request = graph_request(request_method: 'post', endpoints: [endpoint], data: {comment: comment}) check_response(request) 200 @@ -276,6 +271,12 @@ def decline_meeting(mailbox:, booking_id:, comment: '') protected + def calendar_path(calendargroup_id, calendar_id) + result = "" + result += "/calendarGroups/#{calendargroup_id}" if calendargroup_id + result += "/calendars/#{calendar_id}" if calendar_id + end + def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) # Put the attendees into the MS Graph expeceted format attendees.map! do |a| @@ -349,6 +350,4 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, event_json.reject!{|k,v| v.nil?} event_json end - - end From b542d890b6119ff6df50024cbceed5ce72cfcc53 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 4 Sep 2019 17:18:26 +0800 Subject: [PATCH 1423/1752] EWS/RBP/Impersonation: Fix miselading variable name --- modules/aca/exchange_booking.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index 734d6834..cea027aa 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -36,9 +36,11 @@ def on_load end def on_update - # Set to true if the EWS service account does not have access to directly read room mailboxes, but has access to impersonate room mailboxes + # Set to true if the EWS service account has access to directly read room mailboxes. + # If left as the default (false), the driver will attempt to use Impersonation access to read the room mailbox # https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/impersonation-and-ews-in-exchange - @ews_impersonate_room = setting(:ews_impersonate_room) || setting(:use_act_as) + # https://www.rubydoc.info/github/zenchild/Viewpoint/Viewpoint%2FEWS%2FFolderAccessors:get_folder + @ews_delegate_accesss = setting(:ews_delegate_accesss) || setting(:use_act_as) self[:room_name] = setting(:room_name) || system.name self[:hide_all] = setting(:hide_all) || false @@ -212,7 +214,7 @@ def send_email(title, body, to) cli = Viewpoint::EWSClient.new(*@ews_creds) opts = {} - if @ews_impersonate_room + if @ews_delegate_accesss opts[:act_as] = @room_mailbox else cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @room_mailbox) @@ -247,7 +249,7 @@ def make_ews_booking(user_email: nil, subject: self[:booking_default_title], roo cli = Viewpoint::EWSClient.new(*@ews_creds) opts = {} - if @ews_impersonate_room + if @ews_delegate_accesss opts[:act_as] = @room_mailbox else cli.set_impersonation(Viewpoint::EWS::ConnectingSID[@ews_connect_type], @room_mailbox) @@ -280,7 +282,7 @@ def delete_ews_booking(delete_at) cli = Viewpoint::EWSClient.new(*@ews_creds) - if @ews_impersonate_room + if @ews_delegate_accesss opts = {} opts[:act_as] = @room_mailbox @@ -317,7 +319,7 @@ def todays_bookings cli = Viewpoint::EWSClient.new(*@ews_creds) - if @ews_impersonate_room + if @ews_delegate_accesss opts = {} opts[:act_as] = @room_mailbox @@ -412,4 +414,4 @@ def meeting_extendable? self[:meeting_canbe_extended] = false end end -end \ No newline at end of file +end From 9648868056ea1f9fe61292db902477ab5f1e1b76 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 5 Sep 2019 13:43:54 +1000 Subject: [PATCH 1424/1752] (cisco:broadworks) ensure call count never goes below 0 --- modules/cisco/broad_works.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index 82390104..b9df6708 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -153,17 +153,17 @@ def process_event(event) count = @abandoned_calls[call_center_id] @abandoned_calls[call_center_id] = count.to_i + 1 - count = @queued_calls[call_center_id] - @queued_calls[call_center_id] = count.to_i - 1 + count = @queued_calls[call_center_id].to_i - 1 + @queued_calls[call_center_id] = count < 0 ? 0 : count when 'ACDCallStrandedEvent' # Not entirely sure when this happens - count = @queued_calls[call_center_id] - @queued_calls[call_center_id] = count.to_i - 1 + count = @queued_calls[call_center_id].to_i - 1 + @queued_calls[call_center_id] = count < 0 ? 0 : count when 'ACDCallAnsweredByAgentEvent' # TODO:: Call answered by "0278143573@det.nsw.edu.au" # Possibly we can monitor the time this conversion goes for? - count = @queued_calls[call_center_id] - @queued_calls[call_center_id] = count.to_i - 1 + count = @queued_calls[call_center_id].to_i - 1 + @queued_calls[call_center_id] = count < 0 ? 0 : count # Extract wait time... event_data = event[:event_data] From 132b6746155eab8cfb1a5e5b6da41d54ab8f91c6 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 6 Sep 2019 15:40:36 +0800 Subject: [PATCH 1425/1752] office2>client: support https_proxy --- lib/microsoft/office2/client.rb | 4 +++- lib/microsoft/office2/events.rb | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 2ff572b0..05c20f0b 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -45,6 +45,7 @@ def initialize( app_site: "https://login.microsoftonline.com", app_scope: "https://graph.microsoft.com/.default", graph_domain: "https://graph.microsoft.com", + https_proxy: nil, save_token: Proc.new{ |token| User.bucket.set("office-token", token) }, get_token: Proc.new{ User.bucket.get("office-token", quiet: true) } ) @@ -56,8 +57,9 @@ def initialize( @graph_domain = graph_domain @get_token = get_token @save_token = save_token + @https_proxy = https_proxy oauth_options = { site: @app_site, token_url: @app_token_url } - oauth_options[:connection_opts] = { proxy: @internet_proxy } if @internet_proxy + oauth_options[:connection_opts] = { proxy: @https_proxy } if @https_proxy @graph_client ||= OAuth2::Client.new( @client_id, @client_secret, diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index e3c43b8f..52b41fbb 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -275,6 +275,7 @@ def calendar_path(calendargroup_id, calendar_id) result = "" result += "/calendarGroups/#{calendargroup_id}" if calendargroup_id result += "/calendars/#{calendar_id}" if calendar_id + result end def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) From 5708a0bea5358c3a009a240f57012d77ae7a9e7e Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 6 Sep 2019 20:58:46 +0800 Subject: [PATCH 1426/1752] office2>client: add Calendars; support 409 error --- lib/microsoft/office2/client.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 05c20f0b..0029a87f 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -29,6 +29,7 @@ class Microsoft::Office2::Client include Microsoft::Office2::Events include Microsoft::Office2::Users include Microsoft::Office2::Contacts + include Microsoft::Office2::Calendars ## # Initialize the client for making requests to the Office365 API. @@ -164,6 +165,8 @@ def check_response(response) raise Microsoft::Error::ErrorAccessDenied.new(response.body) when 404 raise Microsoft::Error::ResourceNotFound.new(response.body) + when 409 + raise Microsoft::Error::ErrorFolderExists.new(response.body) end end From b9bbdeaa6071c1f4af893389766d3d772162efd5 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 6 Sep 2019 20:59:24 +0800 Subject: [PATCH 1427/1752] office2>calendars>list: support name search/match --- lib/microsoft/office2/calendars.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office2/calendars.rb b/lib/microsoft/office2/calendars.rb index 07f0ce14..66a11368 100644 --- a/lib/microsoft/office2/calendars.rb +++ b/lib/microsoft/office2/calendars.rb @@ -5,7 +5,7 @@ module Microsoft::Office2::Calendars # # @param mailbox [String] The mailbox where the calendars will be created/read # @param limit [String] The maximum number of calendars to return - def list_calendars(mailbox:, calendargroup_id:nil, limit:nil) + def list_calendars(mailbox:, calendargroup_id:nil, match:nil, search:nil, limit:nil) query_params = { '$top': (limit || 99) } case calendargroup_id when nil @@ -15,6 +15,13 @@ def list_calendars(mailbox:, calendargroup_id:nil, limit:nil) else endpoint = "/v1.0/users/#{mailbox}/calendarGroups/#{calendargroup_id}/calendars" end + + if match + query_params['$filter'] = "name eq '#{match}'" + elsif search + query_params['$filter'] = "startswith(name,'#{search}')" + end + request = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) check_response(request) JSON.parse(request.body)['value'] From e27faba8d2f18356d0d585c7948acb4cdb0b24de Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 10 Sep 2019 09:31:09 +1000 Subject: [PATCH 1428/1752] (cisco:broadworks) add support for defining achievements --- modules/cisco/broad_works.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index b9df6708..8133762a 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -75,6 +75,12 @@ def on_update connect end + def set_achievements(achievements) + achievements ||= [] + define_setting(:achievements, achievements) + self[:achievements] = achievements + end + def reset_stats # "callcenter id" => count @queued_calls = {} From 1e89fa3036b8744f9243d460811a9451eae85b3e Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 11 Sep 2019 23:50:22 +0800 Subject: [PATCH 1429/1752] desk_sensor>Pressac: add websocket driver and DeskManagement logic (for staff app) --- modules/pressac/desk_management.rb | 65 ++++++++++++ modules/pressac/sensors/example_output.txt | 14 +++ modules/pressac/sensors/ws_protocol.rb | 118 +++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 modules/pressac/desk_management.rb create mode 100644 modules/pressac/sensors/example_output.txt create mode 100644 modules/pressac/sensors/ws_protocol.rb diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb new file mode 100644 index 00000000..ee776438 --- /dev/null +++ b/modules/pressac/desk_management.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +# Designed to work with Pressac Desk sensors (Pressac::Sensors::WsProtocol) and ACA staff app frontend +module Pressac; end +class ::Pressac::DeskManagement + include ::Orchestrator::Constants + + descriptive_name 'Pressac Desk Bindings for ACA apps' + generic_name :DeskManagement + implements :logic + + default_settings({ + sensor_to_zone_mappings: { + "Sensors_1" => ["zone-xxx"], + "Sensors_2" => ["zone-xxx", "zone-zzz"] + }, + sensor_name_to_desk_mappings: { + "Note" => "This mapping is optional. If not present, the sensor NAME will be used and must match SVG map IDs", + "Desk01" => "table-SYD.2.17.A", + "Desk03" => "table-SYD.2.17.B" + } + }) + + def on_load + system.load_complete do + begin + on_update + rescue => e + logger.print_error e + end + end + end + + def on_update + @sensors = setting('sensor_to_zone_mappings') || {} + @desks = setting('sensor_name_to_desk_mappings') || {} + + @subscriptions ||= [] + @subscriptions.each { |ref| unsubscribe(ref) } + @subscriptions.clear + + @sensors.each do |sensor,zones| + # Populate our initial status with the current data from all given Sensors + zones.each do |zone| + busy_desks = self[zone] ||= [] + all_desks = self[zone + ":desk_ids"] ||= [] + #self[zone:desk_ids] is an array of all desks in this zone (zones may have multiple sensors) + self[zone + ":desk_ids"] = all_desks | system[sensor][:all_desks].map{|d| @desks[d] || d} + #self[zone] is an array of all occupied desks in this zone + self[zone] = (self[zone] | system[sensor][:busy_desks].map{|d| @desks[d] || d}) - system[sensor][:free_desks].map{|d| @desks[d] || d} + end + # Subscribe to live updates from the sensors + device,index = sensor.split('_') + @subscriptions << system.subscribe(device, index.to_i, :busy_desks) do |notification| + new_busy_desks = notification.value.map{|d| @desks[d]} + new_free_desks = system[sensor][:free_desks].map{|d| @desks[d] || d} || [] + zones.each { |zone| self[zone] = (self[zone] | new_busy_desks) - new_free_desks } + zones.each { |zone| self[zone + ":desk_ids"] = (self[zone + ":desk_ids"] | new_busy_desks) - new_free_desks } + end + end + end + + protected + +end diff --git a/modules/pressac/sensors/example_output.txt b/modules/pressac/sensors/example_output.txt new file mode 100644 index 00000000..e2804e6f --- /dev/null +++ b/modules/pressac/sensors/example_output.txt @@ -0,0 +1,14 @@ +Desk sensor: +{ + "deviceid":"050B1EE8", + "devicename":"Desk01", + "dbm":"-53", + "devicetype":"Under-Desk-Sensor", + "motionDetected":"false", + "supplyVoltage":"3.26" +} + +Environment sensor: +{ + "deviceid":"05189BE4","devicename":"PEnvironSensor-02","dbm":"-44","devicetype":"CO2-Temperature-and-Humidity","concentration":"1740","temperature":"27.2","humidity":"55" +} \ No newline at end of file diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb new file mode 100644 index 00000000..74b8f7ca --- /dev/null +++ b/modules/pressac/sensors/ws_protocol.rb @@ -0,0 +1,118 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'protocols/websocket' +require 'set' + +module Pressac; end +module Pressac::Sensors; end + +# Data flow: +# ========== +# Pressac desk / environment / other sensor modules wirelessly connect to +# Pressac Smart Gateway (https://www.pressac.com/smart-gateway/) uses AMQP or MQTT protocol to push data to MS Azure IOT Hub via internet +# Local Node-RED docker container (default hostname: node-red) connects to the same Azure IOT hub via AMQP over websocket (using "Azure IoT Hub Receiver" https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) +# Engine module (instance of this driver) connects to Node-RED via websockets. Typically ws://node-red:1880/ws/pressac/ + +class Pressac::Sensors::WsProtocol + include ::Orchestrator::Constants + + descriptive_name 'Pressac Sensors via NR websocket' + generic_name :Sensors + tcp_port 1880 + wait_response false + default_settings({ + websocket_path: '/ws/pressac/', + }) + + def on_load + on_update + self[:desks] = {} + self[:busy_desks] = [] + self[:free_desks] = [] + self[:all_desks] = [] + self[:environment] = {} + @busy_desks = Set.new + @free_desks = Set.new + end + + # Called after dependency reload and settings updates + def on_update + @ws_path = setting('websocket_path') + end + + def connected + new_websocket_client + end + + def disconnected + end + + protected + + def new_websocket_client + @ws = Protocols::Websocket.new(self, "ws://#{remote_address + @ws_path}") # Node that id is optional and only required if there are to be multiple endpoints under the /ws/press/ + @ws.start + end + + def received(data, resolve, command) + @ws.parse(data) + :success + rescue => e + logger.print_error(e, 'parsing websocket data') + disconnect + :abort + end + + # ==================== + # Websocket callbacks: + # ==================== + + # websocket ready + def on_open + logger.debug { "Websocket connected" } + end + + def on_message(raw_string) + logger.debug { "received: #{raw_string}" } + sensor = JSON.parse(raw_string, symbolize_names: true) + + case sensor[:devicetype] + when 'Under-Desk-Sensor' + id = sensor[:devicename] + occupied = sensor[:motionDetected] == "true" + if occupied + @busy_desks.add(id) + @free_desks.delete(id) + else + @busy_desks.delete(id) + @free_desks.add(id) + end + self[:busy_desks] = @busy_desks.to_a + self[:free_desks] = @free_desks.to_a + self[:all_desks] = self[:all_desks] | [id] + self[:desks][id] = { + motion: occupied, + voltage: sensor[:supplyVoltage], + id: sensor[:deviceid] + } + when 'CO2-Temperature-and-Humidity' + self[:environment][sensor[:devicename]] = { + temp: sensor[:temperature], + humidity: sensor[:humidity], + concentration: sensor[:concentration], + dbm: sensor[:dbm], + id: sensor[:deviceid] + } + end + end + + # connection is closing + def on_close(event) + logger.debug { "Websocket closing... #{event.code} #{event.reason}" } + end + + def on_error(error) + logger.warn "Websocket error: #{error.message}" + end +end \ No newline at end of file From ff0bb57fb83f343eb28fb19860316465480640f3 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 12 Sep 2019 00:21:27 +0800 Subject: [PATCH 1430/1752] pressac>desk_mgmt: fix logic in determining desks[] --- modules/pressac/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index ee776438..729425c8 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -55,7 +55,7 @@ def on_update new_busy_desks = notification.value.map{|d| @desks[d]} new_free_desks = system[sensor][:free_desks].map{|d| @desks[d] || d} || [] zones.each { |zone| self[zone] = (self[zone] | new_busy_desks) - new_free_desks } - zones.each { |zone| self[zone + ":desk_ids"] = (self[zone + ":desk_ids"] | new_busy_desks) - new_free_desks } + zones.each { |zone| self[zone + ":desk_ids"] = (self[zone + ":desk_ids"] | new_busy_desks) } end end end From ecff4c73d9298c94e4c7c80eb211650974a75ca3 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 12 Sep 2019 12:45:33 +1000 Subject: [PATCH 1431/1752] (cisco:catalyst) a device is only connected if it has a MAC --- modules/cisco/switch/snooping_catalyst.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cisco/switch/snooping_catalyst.rb b/modules/cisco/switch/snooping_catalyst.rb index 7ded6fd0..567e194c 100644 --- a/modules/cisco/switch/snooping_catalyst.rb +++ b/modules/cisco/switch/snooping_catalyst.rb @@ -283,7 +283,7 @@ def received(data, resolve, command) end # @interface_macs - @connected_interfaces = @check_interface + @connected_interfaces = @check_interface & @interface_macs.keys self[:interfaces] = @connected_interfaces.to_a self[:reserved] = @reserved_interface.to_a @snooping.clear From a3887c566daeefb35af38d6a31fc03ada2a63ae5 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 12 Sep 2019 12:46:10 +1000 Subject: [PATCH 1432/1752] (pexip:management) don't expire all meetings allows for pooling --- modules/pexip/management.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 39bb06fb..82ed7ce5 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -44,7 +44,7 @@ def on_update end MeetingTypes = ["conference", "lecture", "two_stage_dialing", "test_call"] - def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(9999), **options) + def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(9999), expire: true, **options) type = type.to_s.strip.downcase raise "unknown meeting type" unless MeetingTypes.include?(type) @@ -63,8 +63,10 @@ def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(999 }) do |data| if (200...300).include?(data.status) vmr_id = URI(data['Location']).path.split("/").reject(&:empty?)[-1] - @vmr_ids[vmr_id] = Time.now.to_i - define_setting(:vmr_ids, @vmr_ids) + if expire + @vmr_ids[vmr_id] = Time.now.to_i + define_setting(:vmr_ids, @vmr_ids) + end vmr_id else :retry @@ -72,6 +74,11 @@ def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(999 end end + def add_meeting_to_expire(vmr_id) + @vmr_ids[vmr_id] = Time.now.to_i + define_setting(:vmr_ids, @vmr_ids) + end + def get_meeting(meeting) meeting = "/api/admin/configuration/v1/conference/#{meeting}/" unless meeting.to_s.include?("/") From 7633cae46ffbf19accf3aa8da4ce0eb136fc7cb0 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 12 Sep 2019 14:07:07 +0800 Subject: [PATCH 1433/1752] pressac>desk_mgmt: use Sensors[:all_desks] --- modules/pressac/desk_management.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 729425c8..5d55e01c 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -52,10 +52,14 @@ def on_update # Subscribe to live updates from the sensors device,index = sensor.split('_') @subscriptions << system.subscribe(device, index.to_i, :busy_desks) do |notification| - new_busy_desks = notification.value.map{|d| @desks[d]} - new_free_desks = system[sensor][:free_desks].map{|d| @desks[d] || d} || [] + new_busy_desks = notification.value.map{|d| @desks[d] || d} + new_free_desks = system[sensor][:free_desks].map{|d| @desks[d] || d} || [] + all_sensors_desks = system[sensor][:all_desks].map{|d| @desks[d] || d} + puts "new busy desks: #{new_busy_desks}" + puts "new free desks: #{new_free_desks}" + puts "all sensor's desks: #{all_sensors_desks}" zones.each { |zone| self[zone] = (self[zone] | new_busy_desks) - new_free_desks } - zones.each { |zone| self[zone + ":desk_ids"] = (self[zone + ":desk_ids"] | new_busy_desks) } + zones.each { |zone| self[zone + ":desk_ids"] = (self[zone + ":desk_ids"] | all_sensors_desks) } end end end From 449f5ef3ef8725b6e4f32b27eec0908b7ee3736c Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 12 Sep 2019 12:17:17 +0000 Subject: [PATCH 1434/1752] pressac>desk_mgmt: major refactor --- modules/pressac/desk_management.rb | 60 +++++++++++++++++++----------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 5d55e01c..ae286676 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -33,37 +33,55 @@ def on_load def on_update @sensors = setting('sensor_to_zone_mappings') || {} - @desks = setting('sensor_name_to_desk_mappings') || {} + @desk_ids = setting('sensor_name_to_desk_mappings') || {} @subscriptions ||= [] @subscriptions.each { |ref| unsubscribe(ref) } @subscriptions.clear - + + # Initialize all zone status variables to [] or 0, but keep existing values if they exist (||=) + all_zone_ids = @sensors.values.flatten.compact.uniq + all_zone_ids.each do |z| + self[z] ||= [] # occupied (busy) desk ids in this zone + self[z+':desk_ids'] ||= [] # all desk ids in this zone + self[z+':occupied_count'] ||= 0 + self[z+':desk_count'] ||= 0 + end + @sensors.each do |sensor,zones| - # Populate our initial status with the current data from all given Sensors zones.each do |zone| - busy_desks = self[zone] ||= [] - all_desks = self[zone + ":desk_ids"] ||= [] - #self[zone:desk_ids] is an array of all desks in this zone (zones may have multiple sensors) - self[zone + ":desk_ids"] = all_desks | system[sensor][:all_desks].map{|d| @desks[d] || d} - #self[zone] is an array of all occupied desks in this zone - self[zone] = (self[zone] | system[sensor][:busy_desks].map{|d| @desks[d] || d}) - system[sensor][:free_desks].map{|d| @desks[d] || d} - end - # Subscribe to live updates from the sensors - device,index = sensor.split('_') - @subscriptions << system.subscribe(device, index.to_i, :busy_desks) do |notification| - new_busy_desks = notification.value.map{|d| @desks[d] || d} - new_free_desks = system[sensor][:free_desks].map{|d| @desks[d] || d} || [] - all_sensors_desks = system[sensor][:all_desks].map{|d| @desks[d] || d} - puts "new busy desks: #{new_busy_desks}" - puts "new free desks: #{new_free_desks}" - puts "all sensor's desks: #{all_sensors_desks}" - zones.each { |zone| self[zone] = (self[zone] | new_busy_desks) - new_free_desks } - zones.each { |zone| self[zone + ":desk_ids"] = (self[zone + ":desk_ids"] | all_sensors_desks) } + # Populate our initial status with the current data from all given Sensors + update_zone(zone, sensor) + + # Subscribe to live updates from the sensors + device,index = sensor.split('_') + @subscriptions << system.subscribe(device, index.to_i, :free_desks) do |notification| + update_zone(zone, sensor) + end end end end + # Update one zone with the current data from ONE sensor (there may be multiple sensors serving a zone) + def update_zone(zone, sensor) + # The below values reflect just this ONE sensor, not neccesarily the whole zone + all_desks = id system[sensor][:all_desks] + busy_desks = id system[sensor][:busy_desks] + free_desks = all_desks - busy_desks + + # add the desks from this sensor to the other sensors in the zone + self[zone+':desk_ids'] = self[zone] | all_desks + self[zone] = (self[zone] | busy_desks) - free_desks + + self[zone+':occupied_count'] = self[zone].count + self[zone+':desk_count'] = self[zone+':desk_ids'].count + end + + protected + def id(array) + return [] if array.nil? + array.map { |i| @desk_ids[i] || i } + end end From f9f92ac272711789da3be8fa81bf85bc18712740 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 12 Sep 2019 20:19:04 +0800 Subject: [PATCH 1435/1752] pressac>desk_mgmt: major refactor --- modules/pressac/desk_management.rb | 58 ++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 729425c8..89e4a506 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -33,33 +33,55 @@ def on_load def on_update @sensors = setting('sensor_to_zone_mappings') || {} - @desks = setting('sensor_name_to_desk_mappings') || {} + @desk_ids = setting('sensor_name_to_desk_mappings') || {} @subscriptions ||= [] @subscriptions.each { |ref| unsubscribe(ref) } @subscriptions.clear - + + # Initialize all zone status variables to [] or 0, but keep existing values if they exist (||=) + all_zone_ids = @sensors.values.flatten.compact.uniq + all_zone_ids.each do |z| + self[z] ||= [] # occupied (busy) desk ids in this zone + self[z+':desk_ids'] ||= [] # all desk ids in this zone + self[z+':occupied_count'] ||= 0 + self[z+':desk_count'] ||= 0 + end + @sensors.each do |sensor,zones| - # Populate our initial status with the current data from all given Sensors zones.each do |zone| - busy_desks = self[zone] ||= [] - all_desks = self[zone + ":desk_ids"] ||= [] - #self[zone:desk_ids] is an array of all desks in this zone (zones may have multiple sensors) - self[zone + ":desk_ids"] = all_desks | system[sensor][:all_desks].map{|d| @desks[d] || d} - #self[zone] is an array of all occupied desks in this zone - self[zone] = (self[zone] | system[sensor][:busy_desks].map{|d| @desks[d] || d}) - system[sensor][:free_desks].map{|d| @desks[d] || d} - end - # Subscribe to live updates from the sensors - device,index = sensor.split('_') - @subscriptions << system.subscribe(device, index.to_i, :busy_desks) do |notification| - new_busy_desks = notification.value.map{|d| @desks[d]} - new_free_desks = system[sensor][:free_desks].map{|d| @desks[d] || d} || [] - zones.each { |zone| self[zone] = (self[zone] | new_busy_desks) - new_free_desks } - zones.each { |zone| self[zone + ":desk_ids"] = (self[zone + ":desk_ids"] | new_busy_desks) } + # Populate our initial status with the current data from all given Sensors + update_zone(zone, sensor) + + # Subscribe to live updates from the sensors + device,index = sensor.split('_') + @subscriptions << system.subscribe(device, index.to_i, :free_desks) do |notification| + update_zone(zone, sensor) + end end end end + # Update one zone with the current data from ONE sensor (there may be multiple sensors serving a zone) + def update_zone(zone, sensor) + # The below values reflect just this ONE sensor, not neccesarily the whole zone + all_desks = id system[sensor][:all_desks] + busy_desks = id system[sensor][:busy_desks] + free_desks = all_desks - busy_desks + + # add the desks from this sensor to the other sensors in the zone + self[zone+':desk_ids'] = self[zone] | all_desks + self[zone] = (self[zone] | busy_desks) - free_desks + + self[zone+':occupied_count'] = self[zone].count + self[zone+':desk_count'] = self[zone+':desk_ids'].count + end + + protected -end + def id(array) + return [] if array.nil? + array.map { |i| @desk_ids[i] || i } + end +end \ No newline at end of file From 0622ed21e936f2530800984f768fc04568cdd26e Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 12 Sep 2019 20:53:19 +0800 Subject: [PATCH 1436/1752] pressac>desk_sensor>example_txt: make example output easier to read --- modules/pressac/sensors/example_output.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/pressac/sensors/example_output.txt b/modules/pressac/sensors/example_output.txt index e2804e6f..bc8065bd 100644 --- a/modules/pressac/sensors/example_output.txt +++ b/modules/pressac/sensors/example_output.txt @@ -10,5 +10,10 @@ Desk sensor: Environment sensor: { - "deviceid":"05189BE4","devicename":"PEnvironSensor-02","dbm":"-44","devicetype":"CO2-Temperature-and-Humidity","concentration":"1740","temperature":"27.2","humidity":"55" + "deviceid":"05189BE4", + "devicename":"PEnvironSensor-02","dbm":"-44", + "devicetype":"CO2-Temperature-and-Humidity", + "concentration":"1740", + "temperature":"27.2", + "humidity":"55" } \ No newline at end of file From 63d37713cf1d531084b42ffa7979c079a93e6b64 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 12 Sep 2019 20:57:39 +0800 Subject: [PATCH 1437/1752] pressac>desk_sensor: refactor on_load --- modules/pressac/sensors/ws_protocol.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 74b8f7ca..19c0293a 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -26,7 +26,6 @@ class Pressac::Sensors::WsProtocol }) def on_load - on_update self[:desks] = {} self[:busy_desks] = [] self[:free_desks] = [] @@ -34,6 +33,8 @@ def on_load self[:environment] = {} @busy_desks = Set.new @free_desks = Set.new + + on_update end # Called after dependency reload and settings updates From 8707ba113dfa633f9fd14b959ca1a57f7b3d090b Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 12 Sep 2019 14:40:32 +0000 Subject: [PATCH 1438/1752] pressac>desk_sensor: remember status if restarted --- modules/pressac/sensors/ws_protocol.rb | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 19c0293a..c934bbb6 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -26,13 +26,14 @@ class Pressac::Sensors::WsProtocol }) def on_load - self[:desks] = {} - self[:busy_desks] = [] - self[:free_desks] = [] - self[:all_desks] = [] - self[:environment] = {} - @busy_desks = Set.new - @free_desks = Set.new + status = setting(:status) || {} + self[:desk] = status[:desk] || {} # A hash of all desk names to their sensor values: { desk_name: {data: value, ..}, .. } + self[:busy_desks] = status[:busy_desks] || [] # Array of desk names + self[:free_desks] = status[:free_desks] || [] + self[:all_desks] = status[:all_desks] || [] + self[:environment] = {} # Environment sensor values (temp, humidity) + @busy_desks = self[:busy_desks].to_set + @free_desks = self[:free_desks].to_set on_update end @@ -49,6 +50,7 @@ def connected def disconnected end + protected def new_websocket_client @@ -92,7 +94,7 @@ def on_message(raw_string) self[:busy_desks] = @busy_desks.to_a self[:free_desks] = @free_desks.to_a self[:all_desks] = self[:all_desks] | [id] - self[:desks][id] = { + self[:desk][id] = { motion: occupied, voltage: sensor[:supplyVoltage], id: sensor[:deviceid] @@ -106,6 +108,15 @@ def on_message(raw_string) id: sensor[:deviceid] } end + + # Save the current status to database, so that it can retrieved when engine restarts + status = { + busy_desks: self[:busy_desks], + free_desks: self[:free_desks], + all_desks: self[:all_desks], + desk: self[:desk] + } + define_setting(:status, status) end # connection is closing From b8bb175e1c7f5cc5230bfb8f8e5a0160d1439d60 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 13 Sep 2019 12:47:34 +1000 Subject: [PATCH 1439/1752] (pexip:management) fix removal of old meetings --- modules/pexip/management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 82ed7ce5..01a9ad1e 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -123,7 +123,7 @@ def cleanup_meetings(older_than) time = Time.now.to_i delete = [] @vmr_ids.each do |id, created| - delete << id if (created + older_than) >= time + delete << id if (created + older_than) <= time end promises = delete.map { |id| end_meeting(id, false) } thread.all(*promises).then do From 1b5e8ae184a8c31f43e782edbc9eda7ea9c0bbac Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 13 Sep 2019 08:53:56 +0000 Subject: [PATCH 1440/1752] Pressac>Desk_mgmt: add desk usage() details(), last_updated --- modules/pressac/desk_management.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index ae286676..5bce885f 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -75,8 +75,22 @@ def update_zone(zone, sensor) self[zone+':occupied_count'] = self[zone].count self[zone+':desk_count'] = self[zone+':desk_ids'].count + self[last_update] = Time.now.in_time_zone($TZ).to_s end + # Grab the list of desk ids in use on a floor + # + # @param level [String] the level id of the floor + def desk_usage(zone) + self[zone] || [] + end + + + # Since this driver cannot know which user is at which desk, just return nil + # @param desk_id [String] the unique id that represents a desk + def desk_details(*desk_ids) + nil + end protected From 17de4b597f5aee7cd606899d8ae2a03fbdaae46a Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 13 Sep 2019 08:59:02 +0000 Subject: [PATCH 1441/1752] Pressac>Desk_sensor: add :last_update"" --- modules/pressac/desk_management.rb | 2 +- modules/pressac/sensors/ws_protocol.rb | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 5bce885f..773a2d47 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -75,7 +75,7 @@ def update_zone(zone, sensor) self[zone+':occupied_count'] = self[zone].count self[zone+':desk_count'] = self[zone+':desk_ids'].count - self[last_update] = Time.now.in_time_zone($TZ).to_s + self[:last_update] = Time.now.in_time_zone($TZ).to_s end # Grab the list of desk ids in use on a floor diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index c934bbb6..a7e8c0f0 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -31,6 +31,7 @@ def on_load self[:busy_desks] = status[:busy_desks] || [] # Array of desk names self[:free_desks] = status[:free_desks] || [] self[:all_desks] = status[:all_desks] || [] + self[:last_update] = status[:last_update] || "Never" self[:environment] = {} # Environment sensor values (temp, humidity) @busy_desks = self[:busy_desks].to_set @free_desks = self[:free_desks].to_set @@ -109,13 +110,15 @@ def on_message(raw_string) } end + self[:last_update] = Time.now.in_time_zone($TZ).to_s # Save the current status to database, so that it can retrieved when engine restarts status = { - busy_desks: self[:busy_desks], - free_desks: self[:free_desks], - all_desks: self[:all_desks], - desk: self[:desk] - } + busy_desks: self[:busy_desks], + free_desks: self[:free_desks], + all_desks: self[:all_desks], + desk: self[:desk] + last_update: self[:last_update], + } define_setting(:status, status) end From 1203c12c0827ca497d9698fa54c53b884bca2851 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 13 Sep 2019 17:05:23 +0800 Subject: [PATCH 1442/1752] Pressac>Desk_sensor: syntax for status[last_update] --- modules/pressac/sensors/ws_protocol.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index a7e8c0f0..9d9e6e88 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -116,8 +116,8 @@ def on_message(raw_string) busy_desks: self[:busy_desks], free_desks: self[:free_desks], all_desks: self[:all_desks], - desk: self[:desk] - last_update: self[:last_update], + desk: self[:desk], + last_update: self[:last_update] } define_setting(:status, status) end From 346f344b198dda3f1ad4309936950f7f3d81aafc Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 16 Sep 2019 17:42:39 +0800 Subject: [PATCH 1443/1752] office2>calendars>create: fix return (was nil) --- lib/microsoft/office2/calendars.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/calendars.rb b/lib/microsoft/office2/calendars.rb index 66a11368..d9d57412 100644 --- a/lib/microsoft/office2/calendars.rb +++ b/lib/microsoft/office2/calendars.rb @@ -49,14 +49,14 @@ def create_calendar(mailbox:, name:, calendargroup_id: nil) end request = graph_request(request_method: 'post', endpoints: [endpoint], data: {name: name}) check_response(request) - JSON.parse(request.body)['value'] + JSON.parse(request.body) end def create_calendargroup(mailbox:, name:) endpoint = "/v1.0/users/#{mailbox}/calendarGroups" request = graph_request(request_method: 'post', endpoints: [endpoint], data: {name: name}) check_response(request) - JSON.parse(request.body)['value'] + JSON.parse(request.body) end From 637d85ff90d2dc57e5407b8b758fd1946331eed0 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Tue, 17 Sep 2019 10:00:58 +1000 Subject: [PATCH 1444/1752] (office2:events) fix nil return --- lib/microsoft/office2/events.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index e3c43b8f..52b41fbb 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -275,6 +275,7 @@ def calendar_path(calendargroup_id, calendar_id) result = "" result += "/calendarGroups/#{calendargroup_id}" if calendargroup_id result += "/calendars/#{calendar_id}" if calendar_id + result end def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) From 662e41943de148a904b52e80e6d129220cc529a8 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Tue, 17 Sep 2019 10:20:47 +1000 Subject: [PATCH 1445/1752] mediasite driver fixes --- modules/mediasite/module.rb | 49 ++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 789e252f..d934b20a 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -93,9 +93,9 @@ def get_device_id # State tracking of recording appliance. While there are numerous recorder states (currently 11 different states), we wish to present these as a simplified state set: Offline, Idle, Recording, Paused. STATES = { - 'Unknown' => 'Offline', + 'Unknown' => 'offline', 'Idle' => 'stop', - 'Busy' => 'stop', + 'Busy' => 'offline', 'RecordStart' => 'active', 'Recording' => 'active', 'RecordEnd' => 'active', @@ -103,7 +103,7 @@ def get_device_id 'Paused' => 'paused', 'Resuming' => 'active', 'OpeningSession' => 'active', - 'ConfiguringDevices' => 'stop' + 'ConfiguringDevices' => 'offline' }.freeze def state @@ -111,6 +111,15 @@ def state self[:previous_state] = self[:state] self[:state] = res['RecorderState'] + if STATES[self[:state]] == 'active' || STATES[self[:state]] == 'paused' + self[:current] = { + 'state' => STATES[self[:state]], + 'start_time' => '' + } + else + self[:current] = nil + end + res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/CurrentPresentationMetadata")) self[:title] = res['Title'] self[:presenters] = res['Presenters'] @@ -128,23 +137,23 @@ def state end def live? - live = false - res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/ScheduledRecordingTimes")) - res['value'].each { |schedule| - current_time = ActiveSupport::TimeZone.new('UTC').now - start_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['StartTime']) - end_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['EndTime']) - if start_time <= current_time && current_time <= end_time - presentation = get_request(schedule['ScheduleLink'] + '/Presentations') - live = presentation['value'][0]['Status'] == 'Live' - self[:current] = { - 'state' => STATES[self[:state]], - 'start_time' => start_time.in_time_zone('Sydney') - } - break - end - } - live + live = self[:state] == 'Recording' + res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/ScheduledRecordingTimes")) + res['value'].each do |schedule| + current_time = ActiveSupport::TimeZone.new('UTC').now + start_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['StartTime']) + end_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['EndTime']) + if start_time <= current_time && current_time <= end_time + presentation = get_request(schedule['ScheduleLink'] + '/Presentations') + live = presentation['value'][0]['Status'] == 'Live' + self[:current] = { + 'state' => STATES[self[:state]], + 'start_time' => start_time.in_time_zone('Sydney') + } + break + end + end + live end def start From d15d9eaf1095953bbd805627fe0aa307b7b3f894 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 17 Sep 2019 15:12:48 +1000 Subject: [PATCH 1446/1752] (cisco:broadworks) add a news section --- modules/cisco/broad_works.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/cisco/broad_works.rb b/modules/cisco/broad_works.rb index 8133762a..f790b4fd 100644 --- a/modules/cisco/broad_works.rb +++ b/modules/cisco/broad_works.rb @@ -48,6 +48,7 @@ def on_update @proxy = setting(:proxy) || ENV["HTTPS_PROXY"] || ENV["https_proxy"] @callcenters = setting(:callcenters) || {} self[:achievements] = setting(:achievements) || [] + self[:news] = setting(:news) || [] # "sub_id" => "callcenter id" @subscription_lookup ||= {} @@ -81,6 +82,12 @@ def set_achievements(achievements) self[:achievements] = achievements end + def set_news(news) + news ||= [] + define_setting(:news, news) + self[:news] = news + end + def reset_stats # "callcenter id" => count @queued_calls = {} From aa086736d32ac1b61988933dba741ead88ffe23d Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 18 Sep 2019 11:12:20 +1000 Subject: [PATCH 1447/1752] Update module.rb --- modules/mediasite/module.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index d934b20a..fa1ee44a 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -146,10 +146,6 @@ def live? if start_time <= current_time && current_time <= end_time presentation = get_request(schedule['ScheduleLink'] + '/Presentations') live = presentation['value'][0]['Status'] == 'Live' - self[:current] = { - 'state' => STATES[self[:state]], - 'start_time' => start_time.in_time_zone('Sydney') - } break end end From 51bd7c8521e0ca9490a0d7318429e7d51c20aa71 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 19 Sep 2019 15:07:15 +1000 Subject: [PATCH 1448/1752] (cisco:room_kit) add webview support --- modules/cisco/collaboration_endpoint/room_kit.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/cisco/collaboration_endpoint/room_kit.rb b/modules/cisco/collaboration_endpoint/room_kit.rb index 713b633f..5eaa8d8f 100644 --- a/modules/cisco/collaboration_endpoint/room_kit.rb +++ b/modules/cisco/collaboration_endpoint/room_kit.rb @@ -172,6 +172,10 @@ def speaker_track(state = On) Instance_: (1..6), PresentationSource_: (1..2) + command 'UserInterface WebView Display' => :webview_display, + url: String + command 'UserInterface WebView Clear' => :webview_clear + # Provide compatabilty with the router module for activating presentation. def switch_to(input) if [0, nil, :none, 'none', :blank, 'blank'].include? input From 77763dbf0f382fc83ec3448ad0bdef7477c2db76 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Sat, 21 Sep 2019 11:20:49 +1000 Subject: [PATCH 1449/1752] (docs) fix api docs link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7587cb0..9e156155 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,6 @@ Physical device drivers, service integrations and modular logic for [ACAEngine]( --- -For those using these modules, you may find the [docs](https://acaprojects.github.io/aca-device-modules/) of use. +For those using these drivers, you may find the [API docs](https://acaprojects.github.io/ruby-engine-drivers/) of use. Please read the [contributor guide](.github/CONTRIBUTING.md) for project guidelines, or check [support](.github/SUPPORT.md) if you're lost. From 9c5b0ad2b6cd96d44226d8b26c06de6c61732a82 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 24 Sep 2019 22:36:32 +1000 Subject: [PATCH 1450/1752] (cisco:tele_presence) extract complete phonebook --- .../cisco/tele_presence/sx_series_common.rb | 78 +++++++++++++++---- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 79c3b5ea..302f42d8 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -6,13 +6,13 @@ def on_load self[:presentation] = :none on_update end - + def on_update @corporate_dir = setting(:use_corporate_directory) || false @default_source = setting(:presentation) || 3 @count = 0 end - + def connected super @@ -33,7 +33,7 @@ def connected end end end - + def disconnected super @@ -114,17 +114,56 @@ def mute_status :ContactType => :Contact, :SearchField => :Name } - def search(text, opts = {}) + def search(text, opts = {}, **options) opts[:PhonebookType] ||= 'Corporate' if @corporate_dir opts = SearchDefaults.merge(opts) - opts[:SearchString] = text - command(:phonebook, :search, params(opts), name: :phonebook, max_waits: 400) + opts[:SearchString] = text if text + command(:phonebook, :search, params(opts), **{name: :phonebook, max_waits: 100}.merge(options)) end - + def clear_search_results self[:search_results] = [] end + def extract_phonebook + # [{ + # type: "folder", + # id: "c_12", + # name: "Name of the Folder", + # contents: [] + # }, ...] + @complete_phonebook = [] + @current_folder = @complete_phonebook + @folder_queue = [] + + search(nil, { + ContactType: "Any", + Limit: 1000 + }, name: :extract, max_waits: 10000).then do + loop do + @results.each do |entry| + @current_folder << entry + @folder_queue << entry if entry[:folder_id] + end + break if @folder_queue.empty? + + folder = @folder_queue.shift + @current_folder = folder[:contents] + + begin + search(nil, { + ContactType: "Any", + FolderId: folder[:folder_id], + Limit: 1000 + }, name: :extract, max_waits: 10000).value + rescue => error + logger.print_error error, 'extracting phonebook data' + end + end + + self[:complete_phonebook] = @complete_phonebook + end + end # Options include: auto, custom, equal, fullscreen, overlay, presentationlargespeaker, presentationsmallspeaker, prominent, single, speaker_full def layout(mode, target = :local) @@ -201,11 +240,11 @@ def video_output_mode(video_mode) }), name: :video_output_mode) self[:video_output_mode] = video_mode end - + def video_output_mode? status 'Video Monitors' end - + # ==================== # END Common functions # ==================== @@ -258,7 +297,7 @@ def far_end_camera(action, call_id = @last_call_id) command :FarEndControl, :Camera, :Move, "CallId:#{call_id} Value:#{req}" end end - + def wake(**options) command :Standby, :Deactivate, params(options) end @@ -276,7 +315,7 @@ def sleep_time(delay = 1) def sleep(**options) command :Standby, :Activate, params(options) end - + ResponseType = { '**' => :complete, '*r' => :results, @@ -300,7 +339,8 @@ def received(data, resolve, command) @search_count += 1 @results[0][:count] = @search_count end - self[:search_results] = @results + + self[:search_results] = @results if command[:name] != :extract elsif @call_status @call_status[:id] = @last_call_id self[:call_status] = @call_status @@ -359,6 +399,17 @@ def process_results(result) @results = [] end + when 'Folder' + contact = @results[result[3].to_i - 1] + if contact.nil? + contact = { + contents: [] + } + @results << contact + end + entry = result[4].chop + contact[entry.underscore.to_sym] = result[5] + when 'Contact' contact = @results[result[3].to_i - 1] if contact.nil? @@ -413,7 +464,7 @@ def process_status(result) end end when :video - case result[2] + case result[2] when 'Monitors:' self[:video_output_mode] = result[3] when 'Selfview' @@ -430,4 +481,3 @@ def process_status(result) :ignore end end - From 81c6e5182880d2e4c0d2bb460802b7bbdb4e83ec Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 24 Sep 2019 22:59:57 +1000 Subject: [PATCH 1451/1752] (cisco:tele_presence) ensure the phonebook is present and kept up to date --- modules/cisco/tele_presence/sx_series_common.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 302f42d8..ad426eeb 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -11,6 +11,11 @@ def on_update @corporate_dir = setting(:use_corporate_directory) || false @default_source = setting(:presentation) || 3 @count = 0 + + @dir_sync&.cancel + @dir_sync = schedule.cron("0 5 * * *") { extract_phonebook } + + extract_phonebook if self[:connected] && !self[:complete_phonebook].present? end def connected From b99335ce5fbdb63e8b248922f4d549780831d9be Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 1 Oct 2019 01:57:11 +0800 Subject: [PATCH 1452/1752] feature(events/GET/icaluid): add GET booking by icaluid --- lib/microsoft/office2/events.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 52b41fbb..6d9d6cba 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -115,6 +115,14 @@ def get_bookings(mailboxes:, calendargroup_id: nil, calendar_id: nil, options:{} bookings_by_room end + # Return a booking with a matching icaluid + def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_id: nil) + query_params['$filter'] = "iCalUId eq '#{icaluid}'" + endpoint = "/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events" + response = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) + check_response(response) + JSON.parse(response.body)&.dig('value', 0) + end ## # Create an Office365 event in the mailbox passed in. This may have rooms and other From 3c9e33917e8ae81586bc32b0a25da3fa67febcec Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 1 Oct 2019 02:23:59 +0800 Subject: [PATCH 1453/1752] fix(events/GET/by_icaluid): fix syntax --- lib/microsoft/office2/events.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 6d9d6cba..8de88834 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -117,7 +117,7 @@ def get_bookings(mailboxes:, calendargroup_id: nil, calendar_id: nil, options:{} # Return a booking with a matching icaluid def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_id: nil) - query_params['$filter'] = "iCalUId eq '#{icaluid}'" + query_params = {'$filter': "iCalUId eq '#{icaluid}'" } endpoint = "/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events" response = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) check_response(response) @@ -208,7 +208,6 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni rooms: [], subject: "Meeting", description: nil, - organizer: { name: nil, email: mailbox }, attendees: [], recurrence: nil, is_private: false, @@ -229,7 +228,6 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni timezone: options[:timezone], location: options[:location], attendees: options[:attendees].dup, - organizer: options[:organizer], recurrence: options[:recurrence], extensions: options[:extensions], is_private: options[:is_private] @@ -240,7 +238,7 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni options[:extensions] = options[:extensions].dup options[:extensions]["@odata.type"] = "microsoft.graph.openTypeExtension" options[:extensions]["extensionName"] = "Com.Acaprojects.Extensions" - request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}/events/#{booking_id}/extensions/Microsoft.OutlookServices.OpenTypeExtension.Com.Acaprojects.Extensions"], data: options[:extensions]) + request = graph_request(request_method: 'put', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}/extensions/Microsoft.OutlookServices.OpenTypeExtension.Com.Acaprojects.Extensions"], data: options[:extensions]) check_response(request) ext_data = JSON.parse(request.body) end From 35c2b64581c40a91a4da2003c01a9596c450732f Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 3 Oct 2019 22:00:57 +0800 Subject: [PATCH 1454/1752] feature(office2/user/GET): Add get user --- lib/microsoft/office2/users.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office2/users.rb b/lib/microsoft/office2/users.rb index b34e907a..dfdec529 100644 --- a/lib/microsoft/office2/users.rb +++ b/lib/microsoft/office2/users.rb @@ -1,4 +1,14 @@ module Microsoft::Office2::Users + # Retrieve the properties and relationships of a user object. + # https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http + # @param id [String] The user id or email (user principle name) + def get_user(id: ) + response = graph_request(request_method: 'get', endpoints: ["/v1.0/users/#{id}"]) + check_response(response) + u = JSON.parse(request.body) + Microsoft::Office2::User.new(client: self, user: u).user + end + ## # Retrieve a list of users stored in Office365 # @@ -39,4 +49,4 @@ def get_users(q: nil, limit: nil) # Return the parsed user data JSON.parse(request.body)['value'].map {|u| Microsoft::Office2::User.new(client: self, user: u).user} end -end \ No newline at end of file +end From 34ee79f2d89068cfe9cfae276fa04b1a09e54d60 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 3 Oct 2019 22:11:18 +0800 Subject: [PATCH 1455/1752] feature(office2/user/GET/params): Allow selection or returned user properties --- lib/microsoft/office2/users.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/users.rb b/lib/microsoft/office2/users.rb index dfdec529..d910486a 100644 --- a/lib/microsoft/office2/users.rb +++ b/lib/microsoft/office2/users.rb @@ -2,8 +2,10 @@ module Microsoft::Office2::Users # Retrieve the properties and relationships of a user object. # https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http # @param id [String] The user id or email (user principle name) - def get_user(id: ) - response = graph_request(request_method: 'get', endpoints: ["/v1.0/users/#{id}"]) + # @param select [String] comma seperated string of property names to include in the response instead of the default set + def get_user(id:, select: nil) + query_params = { '$select': select }.compact + response = graph_request(request_method: 'get', endpoints: ["/v1.0/users/#{id}"], query: query_params) check_response(response) u = JSON.parse(request.body) Microsoft::Office2::User.new(client: self, user: u).user From ee925d4c0e734b5c2666d53f6589d3d5402337e4 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 3 Oct 2019 23:21:25 +0800 Subject: [PATCH 1456/1752] fix(office2/user/GET): fix var name 'response' --- lib/microsoft/office2/users.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/users.rb b/lib/microsoft/office2/users.rb index d910486a..b56f5cd3 100644 --- a/lib/microsoft/office2/users.rb +++ b/lib/microsoft/office2/users.rb @@ -7,7 +7,7 @@ def get_user(id:, select: nil) query_params = { '$select': select }.compact response = graph_request(request_method: 'get', endpoints: ["/v1.0/users/#{id}"], query: query_params) check_response(response) - u = JSON.parse(request.body) + u = JSON.parse(response.body) Microsoft::Office2::User.new(client: self, user: u).user end From 0714fd354283c3d53df500272f5006e845281f83 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 7 Oct 2019 10:04:19 +1100 Subject: [PATCH 1457/1752] fix(cisco:camera): update to latest protocol version --- .../cisco/tele_presence/sx_camera_common.rb | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/modules/cisco/tele_presence/sx_camera_common.rb b/modules/cisco/tele_presence/sx_camera_common.rb index bc27998f..69d791b3 100644 --- a/modules/cisco/tele_presence/sx_camera_common.rb +++ b/modules/cisco/tele_presence/sx_camera_common.rb @@ -27,7 +27,7 @@ def on_load on_update end - + def on_update @presets = setting(:presets) || {} self[:presets] = @presets.keys @@ -35,7 +35,7 @@ def on_update @index = setting(:camera_index) || 1 self[:camera_index] = @index end - + def connected self[:power] = true @@ -47,7 +47,7 @@ def connected do_poll end end - + def disconnected self[:power] = false @@ -117,7 +117,7 @@ def tilt(value) def zoom(position) val = in_range(position.to_i, self[:zoom_max], self[:zoom_min]) - command('Camera PositionSet', params({ + command('Cameras Camera PositionSet', params({ :CameraId => @index, :Zoom => val }), name: :zoom).then do @@ -159,7 +159,7 @@ def joystick(pan_speed, tilt_speed) end else options[:retries] = 0 - + # Calculate direction dir_hori = :stop if pan_speed > 0 @@ -167,7 +167,7 @@ def joystick(pan_speed, tilt_speed) elsif pan_speed < 0 dir_hori = :left end - + dir_vert = :stop if tilt_speed > 0 dir_vert = :up @@ -251,7 +251,7 @@ def recall_position(number) def save_position(number) number = in_range(number, 15, 1) - + command('Camera Preset Store', params({ :CameraId => @index, :PresetId => number @@ -265,28 +265,28 @@ def save_position(number) # --------------- def connected? - status "Camera #{@index} Connected", priority: 0, name: :connected? + status "Cameras Camera #{@index} Connected", priority: 0, name: :connected? end def pantilt? - status "Camera #{@index} Position Pan", priority: 0, name: :pan? - status "Camera #{@index} Position Tilt", priority: 0, name: :tilt? + status "Cameras Camera #{@index} Position Pan", priority: 0, name: :pan? + status "Cameras Camera #{@index} Position Tilt", priority: 0, name: :tilt? end def zoom? - status "Camera #{@index} Position Zoom", priority: 0, name: :zoom? + status "Cameras Camera #{@index} Position Zoom", priority: 0, name: :zoom? end def manufacturer? - status "Camera #{@index} Manufacturer", priority: 0, name: :manufacturer? + status "Cameras Camera #{@index} Manufacturer", priority: 0, name: :manufacturer? end def model? - status "Camera #{@index} Model", priority: 0, name: :model? + status "Cameras Camera #{@index} Model", priority: 0, name: :model? end def flipped? - status "Camera #{@index} Flip", priority: 0, name: :flipped? + status "Cameras Camera #{@index} Flip", priority: 0, name: :flipped? end @@ -299,7 +299,7 @@ def do_poll end end - + IsResponse = '*s'.freeze IsComplete = '**'.freeze def received(data, resolve, command) @@ -316,12 +316,12 @@ def received(data, resolve, command) end if result[0] == IsResponse - type = result[3].downcase.gsub(':', '').to_sym + type = result[4].downcase.gsub(':', '').to_sym case type when :position # Tilt: Pan: Zoom: etc so we massage to our desired status variables - self[result[4].downcase.gsub(':', '').to_sym] = result[-1].to_i + self[result[5].downcase.gsub(':', '').to_sym] = result[-1].to_i when :connected self[:connected] = result[-1].downcase == 'true' when :model @@ -335,7 +335,7 @@ def received(data, resolve, command) return :ignore end - + return :success end end From 44f9a40fe0c69041b07e06960d5f5d2ec88b4335 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 7 Oct 2019 10:50:05 +1100 Subject: [PATCH 1458/1752] feat(pexip:management): add pstn tag so meetings can dial phone numbers --- modules/pexip/management.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 01a9ad1e..e7ee643f 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -44,7 +44,7 @@ def on_update end MeetingTypes = ["conference", "lecture", "two_stage_dialing", "test_call"] - def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(9999), expire: true, **options) + def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(9999), expire: true, tag: 'pstn', **options) type = type.to_s.strip.downcase raise "unknown meeting type" unless MeetingTypes.include?(type) @@ -55,7 +55,8 @@ def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(999 name: name.to_s, service_type: type, pin: pin.to_s.rjust(4, '0'), - aliases: [{"alias" => conf_alias}] + aliases: [{"alias" => conf_alias}], + tag: tag }.merge(options).to_json, headers: { 'Authorization' => [@username, @password], 'Content-Type' => 'application/json', From ba7e2f9748de61ae853ee0fcc6d19b59208a479b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 9 Oct 2019 10:59:34 +1100 Subject: [PATCH 1459/1752] fix(cisco:camera): new zoom max value --- modules/cisco/tele_presence/sx_camera_common.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_camera_common.rb b/modules/cisco/tele_presence/sx_camera_common.rb index 69d791b3..47d358de 100644 --- a/modules/cisco/tele_presence/sx_camera_common.rb +++ b/modules/cisco/tele_presence/sx_camera_common.rb @@ -20,7 +20,7 @@ def on_load self[:tilt_min] = -2500 # Down self[:tilt_center] = 0 - self[:zoom_max] = 8500 # 65535 + self[:zoom_max] = 11800 self[:zoom_min] = 0 super @@ -31,6 +31,7 @@ def on_load def on_update @presets = setting(:presets) || {} self[:presets] = @presets.keys + self[:zoom_max] = 11800 @index = setting(:camera_index) || 1 self[:camera_index] = @index From a569eb6455756397118665d9519f030e3ac24a05 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 9 Oct 2019 19:09:03 +1100 Subject: [PATCH 1460/1752] feature(pressac>desk-sensor>WS): update protocol to match latest firmware output --- modules/pressac/sensors/ws_protocol.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 9d9e6e88..a495466d 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -81,9 +81,9 @@ def on_message(raw_string) logger.debug { "received: #{raw_string}" } sensor = JSON.parse(raw_string, symbolize_names: true) - case sensor[:devicetype] + case sensor[:deviceType] when 'Under-Desk-Sensor' - id = sensor[:devicename] + id = sensor[:deviceName] occupied = sensor[:motionDetected] == "true" if occupied @busy_desks.add(id) @@ -96,9 +96,12 @@ def on_message(raw_string) self[:free_desks] = @free_desks.to_a self[:all_desks] = self[:all_desks] | [id] self[:desk][id] = { + id: sensor[:deviceId], motion: occupied, - voltage: sensor[:supplyVoltage], - id: sensor[:deviceid] + voltage: sensor[:supplyVoltage][:value], + gatewayName: sensor[:gatewayName], + location: sensor[:location], + timestamp: sensor[:timestamp] } when 'CO2-Temperature-and-Humidity' self[:environment][sensor[:devicename]] = { From bed0532cd677bd632e6a3540e54a073d5c35f7c7 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 9 Oct 2019 19:49:03 +1100 Subject: [PATCH 1461/1752] fix(pressac/desk_sensor): update protocol for new firmware, ensure motion is bool not string. --- modules/pressac/sensors/ws_protocol.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index a495466d..422c11b1 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -84,7 +84,7 @@ def on_message(raw_string) case sensor[:deviceType] when 'Under-Desk-Sensor' id = sensor[:deviceName] - occupied = sensor[:motionDetected] == "true" + occupied = sensor[:motionDetected] == true if occupied @busy_desks.add(id) @free_desks.delete(id) From c8bedc8d6b49faed49c7d6bae184966098cdf2ba Mon Sep 17 00:00:00 2001 From: Viv B Date: Thu, 10 Oct 2019 13:08:42 +1100 Subject: [PATCH 1462/1752] fix(mediasite module): run requests in thread pool --- modules/mediasite/module.rb | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index fa1ee44a..b650d601 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -49,24 +49,30 @@ def poll def get_request(url) req_url = url logger.debug(req_url) - uri = URI(req_url) - req = Net::HTTP::Get.new(uri) - req.basic_auth(setting(:username), setting(:password)) - req['sfapikey'] = setting(:api_key) - http = Net::HTTP.new(uri.hostname, uri.port) - http.use_ssl = true - JSON.parse(http.request(req).body) + + task { + uri = URI(req_url) + req = Net::HTTP::Get.new(uri) + req.basic_auth(setting(:username), setting(:password)) + req['sfapikey'] = setting(:api_key) + http = Net::HTTP.new(uri.hostname, uri.port) + http.use_ssl = true + JSON.parse(http.request(req).body) + }.value end def post_request(url) req_url = setting(:url) + url - uri = URI(req_url) - req = Net::HTTP::Post.new(uri) - req.basic_auth(setting(:username), setting(:password)) - req['sfapikey'] = setting(:api_key) - http = Net::HTTP.new(uri.hostname, uri.port) - http.use_ssl = true - http.request(req) + + task { + uri = URI(req_url) + req = Net::HTTP::Post.new(uri) + req.basic_auth(setting(:username), setting(:password)) + req['sfapikey'] = setting(:api_key) + http = Net::HTTP.new(uri.hostname, uri.port) + http.use_ssl = true + http.request(req) + }.value :success end From 5fc6515cae330eb288f9cfc952159bbe68f14bd4 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 14 Oct 2019 12:05:22 +0800 Subject: [PATCH 1463/1752] feature(office/logs): New env vars for enabling/disabling log output --- lib/microsoft/office2/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 0029a87f..d86f3b07 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -133,7 +133,7 @@ def graph_date(date) end def log_graph_request(request_method, data, query, headers, graph_path, endpoints=nil) - return unless ENV['RAILS_ENV'] == "development" + return unless ENV['O365_LOG_REQUESTS'] STDERR.puts "--------------NEW GRAPH REQUEST------------" STDERR.puts "#{request_method} to #{graph_path}" STDERR.puts "Data:" @@ -149,7 +149,7 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint end def check_response(response) - STDOUT.puts "GRAPH API Response:\n #{response}" if ENV['RAILS_ENV'] == "development" + STDOUT.puts "GRAPH API Response:\n #{response}" if ENV['O365_LOG_RESPONSE'] case response.status when 200, 201, 204 return From cb35f2138ffb42b43282c6599b625bb86ba8127c Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 14 Oct 2019 20:21:54 +1100 Subject: [PATCH 1464/1752] feature(pressc/desk_sensors): collate sensors by gateway name --- modules/pressac/sensors/ws_protocol.rb | 27 +++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 422c11b1..b74ae324 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -83,26 +83,27 @@ def on_message(raw_string) case sensor[:deviceType] when 'Under-Desk-Sensor' - id = sensor[:deviceName] - occupied = sensor[:motionDetected] == true + sensor_name = sensor[:deviceName] + gateway = sensor[:gatewayName] + occupied = sensor[:motionDetected] == true if occupied - @busy_desks.add(id) - @free_desks.delete(id) + @busy_desks.add(sensor_name) + @free_desks.delete(sensor_name) else - @busy_desks.delete(id) - @free_desks.add(id) + @busy_desks.delete(sensor_name) + @free_desks.add(sensor_name) end self[:busy_desks] = @busy_desks.to_a self[:free_desks] = @free_desks.to_a - self[:all_desks] = self[:all_desks] | [id] - self[:desk][id] = { + self[:all_desks] = self[:all_desks] | [sensor_name] + self[:gateways][gateway][sensor_name] = { id: sensor[:deviceId], motion: occupied, voltage: sensor[:supplyVoltage][:value], - gatewayName: sensor[:gatewayName], location: sensor[:location], - timestamp: sensor[:timestamp] - } + timestamp: sensor[:timestamp], + gateway: gateway + } if gateway when 'CO2-Temperature-and-Humidity' self[:environment][sensor[:devicename]] = { temp: sensor[:temperature], @@ -119,9 +120,9 @@ def on_message(raw_string) busy_desks: self[:busy_desks], free_desks: self[:free_desks], all_desks: self[:all_desks], - desk: self[:desk], + gateways: self[:gateways], last_update: self[:last_update] - } + } define_setting(:status, status) end From 58f4f221bc4b7ad6fdf5b25db2511b56c9ef952d Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 15 Oct 2019 00:51:07 +1100 Subject: [PATCH 1465/1752] feature(pressac/desk_sensors): collate sensors by gateway name --- modules/pressac/desk_management.rb | 97 ++++++++++++++++---------- modules/pressac/sensors/ws_protocol.rb | 12 ++-- 2 files changed, 66 insertions(+), 43 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 773a2d47..37f43b53 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -10,11 +10,12 @@ class ::Pressac::DeskManagement implements :logic default_settings({ - sensor_to_zone_mappings: { - "Sensors_1" => ["zone-xxx"], - "Sensors_2" => ["zone-xxx", "zone-zzz"] + iot_hub_device: "Websocket_1", + zone_to_gateway_mappings: { + "zone-xxx" => ["pressac_gateway_name_1"], + "zone-zzz" => ["pressac_gateway_name_2", "pressac_gateway_name_3"] }, - sensor_name_to_desk_mappings: { + sensor_to_desk_mappings: { "Note" => "This mapping is optional. If not present, the sensor NAME will be used and must match SVG map IDs", "Desk01" => "table-SYD.2.17.A", "Desk03" => "table-SYD.2.17.B" @@ -32,68 +33,90 @@ def on_load end def on_update - @sensors = setting('sensor_to_zone_mappings') || {} - @desk_ids = setting('sensor_name_to_desk_mappings') || {} - @subscriptions ||= [] @subscriptions.each { |ref| unsubscribe(ref) } @subscriptions.clear - # Initialize all zone status variables to [] or 0, but keep existing values if they exist (||=) - all_zone_ids = @sensors.values.flatten.compact.uniq - all_zone_ids.each do |z| - self[z] ||= [] # occupied (busy) desk ids in this zone - self[z+':desk_ids'] ||= [] # all desk ids in this zone - self[z+':occupied_count'] ||= 0 - self[z+':desk_count'] ||= 0 + @hub = setting('iot_hub_device') || "Websocket_1" + @zones = setting('zone_to_gateway_mappings') || {} + @desk_ids = setting('sensor_to_desk_mappings') || {} + + # Initialize desk tracking variables to [] or 0, but keep existing values if they exist (||=) + @desks_pending_busy ||= {} + @desks_pending_free ||= {} + + @zones.keys.each do |zone_id| + self[zone_id] ||= [] # occupied (busy) desk ids in this zone + self[zone_id+':desk_ids'] ||= [] # all desk ids in this zone + self[zone_id+':occupied_count'] ||= 0 + self[zone_id+':desk_count'] ||= 0 end - @sensors.each do |sensor,zones| - zones.each do |zone| - # Populate our initial status with the current data from all given Sensors - update_zone(zone, sensor) - - # Subscribe to live updates from the sensors - device,index = sensor.split('_') - @subscriptions << system.subscribe(device, index.to_i, :free_desks) do |notification| - update_zone(zone, sensor) + @zones.each do |zone,gateways| + gateways.each do |gateway| + # Populate our initial status with the current data from all known sensors + update_zone(zone, gateway) + end + end + + # Subscribe to live updates from each gateway + device,index = @hub.split('_') + @zones.each do |zone,gateways| + gateways.each do |gateway| + @subscriptions << system.subscribe(device, index.to_i, gateway) do |notification| + update_zone(zone, gateway, notification) end end end end - # Update one zone with the current data from ONE sensor (there may be multiple sensors serving a zone) - def update_zone(zone, sensor) - # The below values reflect just this ONE sensor, not neccesarily the whole zone - all_desks = id system[sensor][:all_desks] - busy_desks = id system[sensor][:busy_desks] + # Update one zone with the current data from one gateway + def update_zone(zone, gateway, notification=nil) + # The below values reflect just this ONE gateway, not neccesarily the whole zone + all_desks = id system[@hub][gateway][:all_desks] + busy_desks = id system[@hub][gateway][:busy_desks] free_desks = all_desks - busy_desks - # add the desks from this sensor to the other sensors in the zone - self[zone+':desk_ids'] = self[zone] | all_desks - self[zone] = (self[zone] | busy_desks) - free_desks + # add the desks from this sensor to the zone's list and count of all ids + self[zone+':desk_ids'] = self[zone] | all_desks + self[zone+':desk_count'] = self[zone+':desk_ids'].count + self[:last_update] = Time.now.in_time_zone($TZ).to_s + # determine desks that changed state, and track time of change + previously_free = notification.old_value + previously_busy = all_desks - previously_free + newly_free = free_desks - previously_free + newly_busy = busy_desks - previously_busy + logger.debug "Newly free: #{newly_free}\nNewly busy: #{newly_busy}" + + newly_free.each { |d| desks_pending_free[d] ||= Time.now.to_i; desks_pending_busy.delete(d) } + newly_busy.each { |d| desks_pending_busy[d] ||= Time.now.to_i; desks_pending_free.delete(d) } + self[:newly_free] = newly_free + self[:newly_busy] = newly_busy + end + + def expose_desk_status(zone, busy_desks, free_desks) + # Finally, let dependant apps know that that these desks have changed state + self[zone] = (self[zone] | busy_desks) - free_desks self[zone+':occupied_count'] = self[zone].count - self[zone+':desk_count'] = self[zone+':desk_ids'].count - self[:last_update] = Time.now.in_time_zone($TZ).to_s end - # Grab the list of desk ids in use on a floor - # - # @param level [String] the level id of the floor + + # @param zone [String] the engine zone id def desk_usage(zone) self[zone] || [] end - # Since this driver cannot know which user is at which desk, just return nil # @param desk_id [String] the unique id that represents a desk def desk_details(*desk_ids) nil end + protected + # Transform an array of Sensor Names to SVG Map IDs, IF the user has specified a mapping in settings(sensor_to_desk_mappings) def id(array) return [] if array.nil? array.map { |i| @desk_ids[i] || i } diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index b74ae324..9f74f1ec 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -17,8 +17,8 @@ module Pressac::Sensors; end class Pressac::Sensors::WsProtocol include ::Orchestrator::Constants - descriptive_name 'Pressac Sensors via NR websocket' - generic_name :Sensors + descriptive_name 'Pressac Sensors via websocket (local Node-RED)' + generic_name :Websocket tcp_port 1880 wait_response false default_settings({ @@ -27,7 +27,7 @@ class Pressac::Sensors::WsProtocol def on_load status = setting(:status) || {} - self[:desk] = status[:desk] || {} # A hash of all desk names to their sensor values: { desk_name: {data: value, ..}, .. } + #self[:gateways] = status[:gateways] || {} # A hash of all gateway names => sensor names => {data: value, ..} self[:busy_desks] = status[:busy_desks] || [] # Array of desk names self[:free_desks] = status[:free_desks] || [] self[:all_desks] = status[:all_desks] || [] @@ -83,8 +83,8 @@ def on_message(raw_string) case sensor[:deviceType] when 'Under-Desk-Sensor' - sensor_name = sensor[:deviceName] - gateway = sensor[:gatewayName] + sensor_name = sensor[:deviceName].to_sym + gateway = sensor[:gatewayName].to_sym occupied = sensor[:motionDetected] == true if occupied @busy_desks.add(sensor_name) @@ -96,7 +96,7 @@ def on_message(raw_string) self[:busy_desks] = @busy_desks.to_a self[:free_desks] = @free_desks.to_a self[:all_desks] = self[:all_desks] | [sensor_name] - self[:gateways][gateway][sensor_name] = { + self[gateway][sensor_name] = { id: sensor[:deviceId], motion: occupied, voltage: sensor[:supplyVoltage][:value], From a5fcb90595a72d4bbcc20a47ad3f78200ac3c356 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 14 Oct 2019 23:48:23 +0800 Subject: [PATCH 1466/1752] feature(move gateway status under one key; use gateway: epoch for DeskManagement subscription notification) --- modules/pressac/sensors/ws_protocol.rb | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 9f74f1ec..657a10f7 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -27,7 +27,7 @@ class Pressac::Sensors::WsProtocol def on_load status = setting(:status) || {} - #self[:gateways] = status[:gateways] || {} # A hash of all gateway names => sensor names => {data: value, ..} + self[:gateways] = status[:gateways] || {} # A hash of all gateway names => sensor names => {data: value, ..} self[:busy_desks] = status[:busy_desks] || [] # Array of desk names self[:free_desks] = status[:free_desks] || [] self[:all_desks] = status[:all_desks] || [] @@ -96,14 +96,18 @@ def on_message(raw_string) self[:busy_desks] = @busy_desks.to_a self[:free_desks] = @free_desks.to_a self[:all_desks] = self[:all_desks] | [sensor_name] - self[gateway][sensor_name] = { - id: sensor[:deviceId], - motion: occupied, - voltage: sensor[:supplyVoltage][:value], - location: sensor[:location], - timestamp: sensor[:timestamp], - gateway: gateway - } if gateway + if gateway + self[:gateways][gateway] ||= {} + self[:gateways][gateway][sensor_name] = { + id: sensor[:deviceId], + motion: occupied, + voltage: sensor[:supplyVoltage][:value], + location: sensor[:location], + timestamp: sensor[:timestamp], + gateway: gateway + } + self[gateway] = Time.now.to_i + end when 'CO2-Temperature-and-Humidity' self[:environment][sensor[:devicename]] = { temp: sensor[:temperature], From bd706e0681bd2798e196628d5ce1d57622cbc82e Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 14 Oct 2019 23:52:29 +0800 Subject: [PATCH 1467/1752] fix(pressac/desk_mgmt/notifications): WIP for determine which desks newly free/busy --- modules/pressac/desk_management.rb | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 37f43b53..e97ad313 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -55,7 +55,7 @@ def on_update @zones.each do |zone,gateways| gateways.each do |gateway| # Populate our initial status with the current data from all known sensors - update_zone(zone, gateway) + update_zone(zone, gateway.to_sym) end end @@ -63,36 +63,42 @@ def on_update device,index = @hub.split('_') @zones.each do |zone,gateways| gateways.each do |gateway| - @subscriptions << system.subscribe(device, index.to_i, gateway) do |notification| - update_zone(zone, gateway, notification) + @subscriptions << system.subscribe(device, index.to_i, gateway.to_sym) do |notification| + update_zone(zone, gateway.to_sym) end end end end # Update one zone with the current data from one gateway - def update_zone(zone, gateway, notification=nil) + def update_zone(zone, gateway) # The below values reflect just this ONE gateway, not neccesarily the whole zone - all_desks = id system[@hub][gateway][:all_desks] - busy_desks = id system[@hub][gateway][:busy_desks] + begin + gateway_data = system[@hub][:gateways][gateway] || {} + rescue + gateway_data = {} + end + logger.debug "#{zone}: #{gateway_data}" + all_desks = id gateway_data[:all_desks] + busy_desks = id gateway_data[:busy_desks] free_desks = all_desks - busy_desks - # add the desks from this sensor to the zone's list and count of all ids - self[zone+':desk_ids'] = self[zone] | all_desks - self[zone+':desk_count'] = self[zone+':desk_ids'].count - self[:last_update] = Time.now.in_time_zone($TZ).to_s - # determine desks that changed state, and track time of change - previously_free = notification.old_value + # determine desks that changed state by comparing to current status (which has not yet been updated) + #previously_free = self[zone] - free_desks previously_busy = all_desks - previously_free newly_free = free_desks - previously_free newly_busy = busy_desks - previously_busy logger.debug "Newly free: #{newly_free}\nNewly busy: #{newly_busy}" + newly_free.each { |d| desks_pending_free[d] ||= Time.now.to_i; desks_pending_busy.delete(d) } newly_busy.each { |d| desks_pending_busy[d] ||= Time.now.to_i; desks_pending_free.delete(d) } self[:newly_free] = newly_free self[:newly_busy] = newly_busy + self[zone+':desk_ids'] = self[zone] | all_desks + self[zone+':desk_count'] = self[zone+':desk_ids'].count + self[:last_update] = Time.now.in_time_zone($TZ).to_s end def expose_desk_status(zone, busy_desks, free_desks) From 929b694f8828335b1420bc48bedead0c0fe84a9f Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 15 Oct 2019 03:05:16 +1100 Subject: [PATCH 1468/1752] fix(pressac/desks): move gateway status to root and use signal_status to notify of change --- modules/pressac/sensors/ws_protocol.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 657a10f7..c820161c 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -27,7 +27,6 @@ class Pressac::Sensors::WsProtocol def on_load status = setting(:status) || {} - self[:gateways] = status[:gateways] || {} # A hash of all gateway names => sensor names => {data: value, ..} self[:busy_desks] = status[:busy_desks] || [] # Array of desk names self[:free_desks] = status[:free_desks] || [] self[:all_desks] = status[:all_desks] || [] @@ -97,8 +96,8 @@ def on_message(raw_string) self[:free_desks] = @free_desks.to_a self[:all_desks] = self[:all_desks] | [sensor_name] if gateway - self[:gateways][gateway] ||= {} - self[:gateways][gateway][sensor_name] = { + self[gateway] ||= {} + self[gateway][sensor_name] = { id: sensor[:deviceId], motion: occupied, voltage: sensor[:supplyVoltage][:value], @@ -106,7 +105,7 @@ def on_message(raw_string) timestamp: sensor[:timestamp], gateway: gateway } - self[gateway] = Time.now.to_i + signal_status(gateway) end when 'CO2-Temperature-and-Humidity' self[:environment][sensor[:devicename]] = { @@ -124,7 +123,6 @@ def on_message(raw_string) busy_desks: self[:busy_desks], free_desks: self[:free_desks], all_desks: self[:all_desks], - gateways: self[:gateways], last_update: self[:last_update] } define_setting(:status, status) From 1a3455fabc477ac8d617dd697866d550d9c985c3 Mon Sep 17 00:00:00 2001 From: Viv B Date: Tue, 15 Oct 2019 14:27:37 +1100 Subject: [PATCH 1469/1752] Create smart_board.rb feat(smart_board): add smart_board from deakin --- modules/smart_tech/smart_board.rb | 268 ++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 modules/smart_tech/smart_board.rb diff --git a/modules/smart_tech/smart_board.rb b/modules/smart_tech/smart_board.rb new file mode 100644 index 00000000..1d8f8d37 --- /dev/null +++ b/modules/smart_tech/smart_board.rb @@ -0,0 +1,268 @@ +module SmartTech; end +# Copyright Deakin University, 2018 +# Documentation: http://www.smarttech.com/en/kb/171164 +# Panel requires 19200,8,N,1 + +class SmartTech::SmartBoard + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :device + descriptive_name 'Smart Board 7000' + generic_name :Display + + # Communication settings +# tokenize delimiter: "\x0D" #Other SMARTboard docs (for different models) specify CR. Docs for 7000 does not specify. + delay between_sends: 300 + wait_response timeout: 5000, retries: 3 + + #The SMARTboard allows for multiple panels daisy-chained. This driver assumes it is controlling one panel only. + #Panel uses 19200,8,N,1. When passing through another device for IP->RS232 transport (e.g. Atlona OmniStream or AMX SVSi decoder), make sure + #the transport device has its serial port configured correctly. + + #The SMARTboard has numerous power states: ON, READY, STANDBY, POWERSAVE, UPDATEON, UPDATEREADY + #This driver uses ON and READY. Other states may caus the NIC to drop offline. + #Changes in power state must go via the ON state. I.e. The SMARTboard will not change from STANDBY to POWERSAVE, etc. + + #Real world tests were performed with panel fw v2.0.134.0. Some observations: + # - The panel will sometimes drop space characters before processing the command. This causes the panels command parsing to reject the command. + # - The panel Rx buffer can be flodded with commands. Subsequent commands will be queueued then processed either as seperate commands or one "large corrupt command". + # - Rx buffer overflow can cause the panel comms to lock up, requiring a hard power-cycle of the panel. + + def on_load + end + + def on_unload + end + + def on_update + @previous_volume_level ||= 40 + serial? + firmware? + partnumber? +# self[:off_level] = "ignore this" #deprecated. + end + + def connected + do_poll + serial? + firmware? + partnumber? + + schedule.every('10s') do + do_poll + end + end + + def disconnected + schedule.clear + end + + + def power(options = {}, state) #20180927 - Added the options parameter to the function parameters. + if is_affirmative?(self[:firmware_updating]) + logger.debug {"-- Smart Board panel is updating firmware. Power(#{state}) request ignored."} + else + state = is_affirmative?(state) + self[:power_target] = state + power? do + options[:name] = :set_power #Name the message so it overrides other messages of the same name. + options[:priority] = 20 + if state && !self[:power] #If we want ON and power is not ON + do_send("set powerstate=on",options) + elsif !state && self[:power] #If we want OFF and power is ON + do_send("set powerstate=ready",options) + end + end + end + end + + def power?(options = {}, &block) + options[:emit] = block unless block.nil? + options[:name] = :power_query + if !options.include?(:priority) + options[:priority] = 5 + end + do_send("get powerstate", options) + end + + + def volume(level) + @previous_volume_level = self[:volume] = level + do_send("set volume=#{level}") + end + def volume?(options = {}, &block) + options[:emit] = block unless block.nil? + options[:name] = :get_volume + options[:priority] = 5 + do_send("get volume", options) + end + + # Audio mute + def mute_audio(state = true) + level = if is_affirmative?(state) + 0 + elsif @previous_volume_level == 0 + 30 + else + @previous_volume_level + end + volume(level) + end + alias_method :mute, :mute_audio + + def unmute_audio + mute_audio(false) + end + alias_method :unmute, :unmute_audio + + def video_freeze(state) + state = is_affirmative?(state) + if state + do_send("set videofreeze=on",:priority => 20) + else + do_send("set videofreeze=off",:priority => 20) + end + end + def video_freeze?(options = {}, &block) + options[:emit] = block unless block.nil? + options[:name] = :get_volume + options[:priority] = 5 + do_send("get videofreeze", options) + end + + def serial?(options = {}, &block) + options[:emit] = block unless block.nil? + options[:name] = :get_serial + options[:priority] = 15 + do_send("get serialnum", options) + end + def firmware?(options = {}, &block) + options[:emit] = block unless block.nil? + options[:name] = :get_firmware + options[:priority] = 16 + do_send("get fwversion", options) + end + def partnumber?(options = {}, &block) + options[:emit] = block unless block.nil? + options[:name] = :get_partnum + options[:priority] = 17 + do_send("get partnum", options) + end + + # + # Input selection + # + INPUTS = { + :hdmi => 'hdmi1', + :hdmi2 => 'hdmi2', + :dp => 'dp1', + :vga => 'vga1', + :ops => 'ops1' #whiteboard + } + # INPUTS.merge!(INPUTS.invert) + + def switch_to(input) + input = input.to_sym + return unless INPUTS.has_key? input + + do_send("set input=#{INPUTS[input]}") + logger.debug {"-- Told SMARTboard to switch to input: #{input}"} + end + def input?(options = {}, &block) + options[:name] = :get_input + options[:priority] = 5 + do_send("get input", options) + end + + def received(data, resolve, command) + logger.debug "SMARTboard sent: #{data}" + + #Get requests are formatted as: get + #Set requests are formatted as: set= + #Responses to both are formatted as: = + #Responses to bad commands are formatted as: invalid cmd= + #Commands/updates initiated from the panel are prepended with '#' + #Replies from the panel are wrapped with \r\n and \r\n> (telnet prompt). We strip these before parsing actual reply. + data = data.strip #Remove outer whitespace (only front with our data) + data.gsub!("\r\n>",'') #Remove trailing whitespace and prompt + + panelInitiated = (data[0] == '#') #Check for user-initiated commands. + if panelInitiated + data = data[1..-1] #Drop the leading '#'. + end + + data = data.split(/=/) #Split the response on the '=' character. +# logger.debug "PostSplit: #{data}" + + case data[0].to_sym + when :powerstate +# if panelInitiated +# logger.debug "I see what you did there." #This works, and is left here as a demo of how to determine what triggered the reply. +# end + + #If data[1] contains "UPDATE", fw update is in progress. This can occure regardless of on/off state, so we track power and fwupdate independently. + #We store the states as boolean values since the power() function uses boolean logic when deciding whether to send ON/OFF commands. + case data[1].to_sym + when :on, :updateon + self[:power] = true + when :ready, :standby, :powersave, :updateready + self[:power] = false + else + self[:power] = :unknown + end + case data[1].to_sym + when :updateon, :updateready + self[:firmware_updating] = true + when :on, :ready, :standby, :powersave + self[:firmware_updating] = false + else + self[:firmware_updating] = :unknown + end + #end of powerstate parsing + + when :input + self[:input] = data[1] + when :volume + vol = self[:volume] = data[1].to_i + self[:mute] = vol == 0 + when :videofreeze + case data[1].to_sym + when :on + self[:video_freeze] = true + when :off + self[:video_freeze] = false + end + + when :serialnum + self[:serial] = data[1] + when :fwversion + self[:firmware] = data[1] + when :partnum + self[:partnumber] = data[1] + + when :"invalid cmd" + logger.debug "ACA SMARTboard driver does not understand that response. Sad face." + end #end of case + + return :success + end + +private + + def do_poll + power?(:priority => 0) do + if self[:power] == true + video_freeze? + input? + end + end + end + + def do_send(command, options = {}) + logger.debug "requesting #{command}" + command = "#{command}\r" + send(command, options) + end +end From 5298961d552c9e5bf30c1979d0a054c7dab29958 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 05:54:16 +0800 Subject: [PATCH 1470/1752] fix(pressc/desks): new strategy for notify; add 1m schedule for checking desk status --- modules/pressac/desk_management.rb | 105 +++++++++++++++++-------- modules/pressac/sensors/ws_protocol.rb | 87 +++++++++++++------- 2 files changed, 132 insertions(+), 60 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index e97ad313..eefa1cae 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -11,6 +11,8 @@ class ::Pressac::DeskManagement default_settings({ iot_hub_device: "Websocket_1", + delay_until_shown_as_busy: "0m", + delay_until_shown_as_free: "0m", zone_to_gateway_mappings: { "zone-xxx" => ["pressac_gateway_name_1"], "zone-zzz" => ["pressac_gateway_name_2", "pressac_gateway_name_3"] @@ -40,6 +42,9 @@ def on_update @hub = setting('iot_hub_device') || "Websocket_1" @zones = setting('zone_to_gateway_mappings') || {} @desk_ids = setting('sensor_to_desk_mappings') || {} + # convert '1m2s' to '62' + @busy_delay = UV::Scheduler.parse_duration(setting('delaty_until_shown_as_busy') || '0m') / 1000 + @free_delay = UV::Scheduler.parse_duration(setting('delaty_until_shown_as_free') || '0m') / 1000 # Initialize desk tracking variables to [] or 0, but keep existing values if they exist (||=) @desks_pending_busy ||= {} @@ -64,63 +69,101 @@ def on_update @zones.each do |zone,gateways| gateways.each do |gateway| @subscriptions << system.subscribe(device, index.to_i, gateway.to_sym) do |notification| - update_zone(zone, gateway.to_sym) + update_desk(notification) end end end + schedule.clear + schedule.every('1m') { determine_desk_status } end + # @param zone [String] the engine zone id + def desk_usage(zone) + self[zone] || [] + end + + # Since this driver cannot know which user is at which desk, just return nil + # @param desk_id [String] the unique id that represents a desk + def desk_details(*desk_ids) + nil + end + + + protected + # Update one zone with the current data from one gateway def update_zone(zone, gateway) # The below values reflect just this ONE gateway, not neccesarily the whole zone begin - gateway_data = system[@hub][:gateways][gateway] || {} + gateway_data = system[@hub][:gateways][gateway] rescue gateway_data = {} end logger.debug "#{zone}: #{gateway_data}" - all_desks = id gateway_data[:all_desks] - busy_desks = id gateway_data[:busy_desks] - free_desks = all_desks - busy_desks + busy_desks = id gateway_data[:busy_desks] + free_desks = id gateway_data[:free_desks] + all_desks = id gateway_data[:all_desks] - # determine desks that changed state by comparing to current status (which has not yet been updated) - #previously_free = self[zone] - free_desks - previously_busy = all_desks - previously_free - newly_free = free_desks - previously_free - newly_busy = busy_desks - previously_busy - logger.debug "Newly free: #{newly_free}\nNewly busy: #{newly_busy}" - - - newly_free.each { |d| desks_pending_free[d] ||= Time.now.to_i; desks_pending_busy.delete(d) } - newly_busy.each { |d| desks_pending_busy[d] ||= Time.now.to_i; desks_pending_free.delete(d) } - self[:newly_free] = newly_free - self[:newly_busy] = newly_busy self[zone+':desk_ids'] = self[zone] | all_desks self[zone+':desk_count'] = self[zone+':desk_ids'].count self[:last_update] = Time.now.in_time_zone($TZ).to_s end - def expose_desk_status(zone, busy_desks, free_desks) - # Finally, let dependant apps know that that these desks have changed state - self[zone] = (self[zone] | busy_desks) - free_desks - self[zone+':occupied_count'] = self[zone].count + # Update desks_pending_busy/free hashes with a single sensor's data recieved from a notification + def update_desk(notification) + current_state = notification.value + previous_state = notification.old_value + # sample_state = { + # id: string, + # name: string, + # motion: bool, + # voltage: string, + # location: string, + # timestamp: string, + # gateway: string } + d = id [notification.value[:name]] + + if current_state[:motion] && !previous_state[:motion] + @desks_pending_busy[d] ||= { timestamp: Time.now.to_i, gateway: d[:gateway]} + @desks_pending_free.delete(d) + elsif !current_state[:motion] && previous_state[:motion] + @desks_pending_free[d] ||= { timestamp: Time.now.to_i, gateway: d[:gateway]} + @desks_pending_busy.delete(d) + end + + self[zone+':desk_ids'] = self[zone] | [d] + self[zone+':desk_count'] = self[zone+':desk_ids'].count + self[:last_update] = Time.now.in_time_zone($TZ).to_s end - - # @param zone [String] the engine zone id - def desk_usage(zone) - self[zone] || [] + def determine_desk_status + now = Time.now.to_i + @desks_pending_busy.each do |desk,sensor| + if sensor[:timestamp] + @busy_delay > now + expose_desk_status(desk, which_zone(sensor[:gateway]), true) + @desks_pending_busy.delete(desk) + end + end + @desks_pending_free.each do |desk,sensor| + if sensor[:timestamp] + @free_delay > now + expose_desk_status(desk, which_zone(sensor[:gateway]), false) + @desks_pending_free.delete(desk) + end + end end - # Since this driver cannot know which user is at which desk, just return nil - # @param desk_id [String] the unique id that represents a desk - def desk_details(*desk_ids) - nil + def expose_desk_status(desk_name, zone, occupied) + self[zone] = occupied ? (self[zone] | [desk_name]) : (self[zone] - desk_name) + self[zone+':occupied_count'] = self[zone].count end - - protected + # return the (first) zone that a gateway is in + def which_zone(gateway) + @zones&.each do |z,gateways| + return z if gateways.include? gateway + end + end # Transform an array of Sensor Names to SVG Map IDs, IF the user has specified a mapping in settings(sensor_to_desk_mappings) def id(array) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index c820161c..cc5b8001 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -27,13 +27,13 @@ class Pressac::Sensors::WsProtocol def on_load status = setting(:status) || {} - self[:busy_desks] = status[:busy_desks] || [] # Array of desk names - self[:free_desks] = status[:free_desks] || [] - self[:all_desks] = status[:all_desks] || [] + self[:busy_desks] = status[:busy_desks] || {} # hash of gateway names => Array of desk names + self[:free_desks] = status[:free_desks] || {} + self[:all_desks] = status[:all_desks] || {} self[:last_update] = status[:last_update] || "Never" self[:environment] = {} # Environment sensor values (temp, humidity) - @busy_desks = self[:busy_desks].to_set - @free_desks = self[:free_desks].to_set + @busy_desks = self[:busy_desks] + @free_desks = self[:free_desks] on_update end @@ -50,6 +50,27 @@ def connected def disconnected end + def mock(sensor, occupied) + gateway = which_gateway(sensor) + self[:gateways][gateway][:busy_desks] = occuupied ? self[:gateways][gateway][:busy_desks] | [sensor] : self[:gateways][gateway][:busy_desks] - sensor + self[:gateways][gateway][:free_desks] = occuupied ? self[:gateways][gateway][:free_desks] - sensor : self[:gateways][gateway][:free_desks] | [sensor] + self[:gateways][gateway][sensor_name] = self[gateway] = { + id: 'mock_data' + name: sensor, + motion: occupied, + voltage: '3.0', + location: nil, + timestamp: Time.now.to_s, + gateway: gateway + } + end + + def which_gateway(sensor) + self[:gateways]&.each do |g,sensors| + return g if sensors.include? sensor + end + end + protected @@ -80,33 +101,41 @@ def on_message(raw_string) logger.debug { "received: #{raw_string}" } sensor = JSON.parse(raw_string, symbolize_names: true) - case sensor[:deviceType] + case (sensor[:deviceType] || sensor[:devicetype]) when 'Under-Desk-Sensor' - sensor_name = sensor[:deviceName].to_sym - gateway = sensor[:gatewayName].to_sym - occupied = sensor[:motionDetected] == true - if occupied - @busy_desks.add(sensor_name) - @free_desks.delete(sensor_name) + # Variations in captialisation of sensor's key names exist amongst different firmware versions + sensor_name = sensor[:deviceName].to_sym || sensor[:devicename].to_sym + gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym + occupancy = sensor[:motionDetected] == true + + @free_desks[gateway] ||= [].to_set + @busy_desks[gateway] ||= [].to_set + self[:gateways][gateway] ||= {} + + if occupancy + @busy_desks[gateway].add(sensor_name) + @free_desks[gateway].delete(sensor_name) else - @busy_desks.delete(sensor_name) - @free_desks.add(sensor_name) - end - self[:busy_desks] = @busy_desks.to_a - self[:free_desks] = @free_desks.to_a - self[:all_desks] = self[:all_desks] | [sensor_name] - if gateway - self[gateway] ||= {} - self[gateway][sensor_name] = { - id: sensor[:deviceId], - motion: occupied, - voltage: sensor[:supplyVoltage][:value], - location: sensor[:location], - timestamp: sensor[:timestamp], - gateway: gateway - } - signal_status(gateway) + @busy_desks[gateway].delete(sensor_name) + @free_desks[gateway].add(sensor_name) end + self[:gateways][gateway][:busy_desks] = @busy_desks[gateway].to_a + self[:gateways][gateway][:free_desks] = @free_desks[gateway].to_a + self[:gateways][gateway][:all_desks] = @busy_desks[gateway] + @free_desks[gateway] + + # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), + # AND as the latest notification from this gateway (self[gateway]) (for the purpose of the DeskManagent logic upstream) + self[:gateways][gateway][sensor_name] = self[gateway] = { + id: sensor[:deviceId] || sensor[:deviceid], + name: sensor_name, + motion: occupancy, + voltage: sensor[:supplyVoltage][:value] || sensor[:supplyVoltage], + location: sensor[:location], + timestamp: sensor[:timestamp], + gateway: gateway + } + #signal_status(gateway) + self[:gateways][gateway][:last_update] = sensor[:timestamp] when 'CO2-Temperature-and-Humidity' self[:environment][sensor[:devicename]] = { temp: sensor[:temperature], From 55455297ba73314c3173469e74131dfd26d44f13 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 08:09:03 +0800 Subject: [PATCH 1471/1752] fix(pressac/desks): first working version configurable delays. currently testing --- modules/pressac/desk_management.rb | 63 +++++++++++++++++--------- modules/pressac/sensors/ws_protocol.rb | 3 +- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index eefa1cae..76585326 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -51,10 +51,17 @@ def on_update @desks_pending_free ||= {} @zones.keys.each do |zone_id| - self[zone_id] ||= [] # occupied (busy) desk ids in this zone - self[zone_id+':desk_ids'] ||= [] # all desk ids in this zone - self[zone_id+':occupied_count'] ||= 0 - self[zone_id+':desk_count'] ||= 0 + if setting('purge_data') + self[zone_id] = [] + self[zone_id+':desk_ids'] = [] + self[zone_id+':occupied_count'] = 0 + self[zone_id+':desk_count'] = 0 + else + self[zone_id] ||= [] # occupied (busy) desk ids in this zone + self[zone_id+':desk_ids'] ||= [] # all desk ids in this zone + self[zone_id+':occupied_count'] ||= 0 + self[zone_id+':desk_count'] ||= 0 + end end @zones.each do |zone,gateways| @@ -95,7 +102,7 @@ def desk_details(*desk_ids) def update_zone(zone, gateway) # The below values reflect just this ONE gateway, not neccesarily the whole zone begin - gateway_data = system[@hub][:gateways][gateway] + gateway_data = system[@hub][:gateways][gateway] || {} rescue gateway_data = {} end @@ -113,7 +120,7 @@ def update_zone(zone, gateway) # Update desks_pending_busy/free hashes with a single sensor's data recieved from a notification def update_desk(notification) current_state = notification.value - previous_state = notification.old_value + previous_state = notification.old_value || {motion: false} # sample_state = { # id: string, # name: string, @@ -122,19 +129,38 @@ def update_desk(notification) # location: string, # timestamp: string, # gateway: string } - d = id [notification.value[:name]] + desk = notification.value + desk_name = id([desk[:name]])&.first + + logger.debug "NOTIFICATION FROM DESK SENSOR============" + logger.debug notification.value + logger.debug desk[:gateway] if current_state[:motion] && !previous_state[:motion] - @desks_pending_busy[d] ||= { timestamp: Time.now.to_i, gateway: d[:gateway]} - @desks_pending_free.delete(d) + @desks_pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} + @desks_pending_free.delete(desk_name) elsif !current_state[:motion] && previous_state[:motion] - @desks_pending_free[d] ||= { timestamp: Time.now.to_i, gateway: d[:gateway]} - @desks_pending_busy.delete(d) + @desks_pending_free[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} + @desks_pending_busy.delete(desk_name) end - self[zone+':desk_ids'] = self[zone] | [d] - self[zone+':desk_count'] = self[zone+':desk_ids'].count - self[:last_update] = Time.now.in_time_zone($TZ).to_s + zone = which_zone(desk[:gateway]) + logger.debug "=======VALUE FOR ZONE: #{zone}, #{desk[:gateway]}" + if zone + zone = zone.to_s + self[zone+':desk_ids'] = self[zone+':desk_ids'] | [desk_name] + self[zone+':desk_count'] = self[zone+':desk_ids'].count + self[:last_update] = Time.now.in_time_zone($TZ).to_s + end + end + + # return the (first) zone that a gateway is in + def which_zone(gateway) + @zones&.each do |zone, gateways| + logger.debug "#{zone}: #{gateways}" + return zone if gateways.include? gateway.to_s + end + nil end def determine_desk_status @@ -151,6 +177,8 @@ def determine_desk_status @desks_pending_free.delete(desk) end end + self[:desks_pending_busy] = @desks_pending_busy + self[:desks_pending_free] = @desks_pending_free end def expose_desk_status(desk_name, zone, occupied) @@ -158,13 +186,6 @@ def expose_desk_status(desk_name, zone, occupied) self[zone+':occupied_count'] = self[zone].count end - # return the (first) zone that a gateway is in - def which_zone(gateway) - @zones&.each do |z,gateways| - return z if gateways.include? gateway - end - end - # Transform an array of Sensor Names to SVG Map IDs, IF the user has specified a mapping in settings(sensor_to_desk_mappings) def id(array) return [] if array.nil? diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index cc5b8001..b6140291 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -30,6 +30,7 @@ def on_load self[:busy_desks] = status[:busy_desks] || {} # hash of gateway names => Array of desk names self[:free_desks] = status[:free_desks] || {} self[:all_desks] = status[:all_desks] || {} + self[:gateways] = status[:gateways] || {} self[:last_update] = status[:last_update] || "Never" self[:environment] = {} # Environment sensor values (temp, humidity) @busy_desks = self[:busy_desks] @@ -55,7 +56,7 @@ def mock(sensor, occupied) self[:gateways][gateway][:busy_desks] = occuupied ? self[:gateways][gateway][:busy_desks] | [sensor] : self[:gateways][gateway][:busy_desks] - sensor self[:gateways][gateway][:free_desks] = occuupied ? self[:gateways][gateway][:free_desks] - sensor : self[:gateways][gateway][:free_desks] | [sensor] self[:gateways][gateway][sensor_name] = self[gateway] = { - id: 'mock_data' + id: 'mock_data', name: sensor, motion: occupied, voltage: '3.0', From 4471525787c77802714b153ad6c2cb9ddba14aea Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 11:29:47 +0800 Subject: [PATCH 1472/1752] fix(pressac/desks): fix logic error on occupancy Set.add --- modules/pressac/sensors/ws_protocol.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index b6140291..9a465445 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -114,11 +114,11 @@ def on_message(raw_string) self[:gateways][gateway] ||= {} if occupancy - @busy_desks[gateway].add(sensor_name) - @free_desks[gateway].delete(sensor_name) + @busy_desks[gateway] | [sensor_name] + @free_desks[gateway] - [sensor_name] else - @busy_desks[gateway].delete(sensor_name) - @free_desks[gateway].add(sensor_name) + @busy_desks[gateway] - [sensor_name] + @free_desks[gateway] | [sensor_name] end self[:gateways][gateway][:busy_desks] = @busy_desks[gateway].to_a self[:gateways][gateway][:free_desks] = @free_desks[gateway].to_a From 268a139117afcc568a1c1fcefc574acc8629ad96 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 11:50:05 +0800 Subject: [PATCH 1473/1752] feature(pressac/desks): persist desk status across restarts --- modules/pressac/desk_management.rb | 37 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 76585326..683aabc2 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -50,18 +50,14 @@ def on_update @desks_pending_busy ||= {} @desks_pending_free ||= {} - @zones.keys.each do |zone_id| - if setting('purge_data') - self[zone_id] = [] - self[zone_id+':desk_ids'] = [] - self[zone_id+':occupied_count'] = 0 - self[zone_id+':desk_count'] = 0 - else - self[zone_id] ||= [] # occupied (busy) desk ids in this zone - self[zone_id+':desk_ids'] ||= [] # all desk ids in this zone - self[zone_id+':occupied_count'] ||= 0 - self[zone_id+':desk_count'] ||= 0 - end + saved_status = setting('zzz_status') + if saved_status + @saved_status.keys.each { |key, value| self[key] = value } + else + self[zone_id] = [] + self[zone_id+':desk_ids'] = [] + self[zone_id+':occupied_count'] = 0 + self[zone_id+':desk_count'] = 0 end @zones.each do |zone,gateways| @@ -184,6 +180,23 @@ def determine_desk_status def expose_desk_status(desk_name, zone, occupied) self[zone] = occupied ? (self[zone] | [desk_name]) : (self[zone] - desk_name) self[zone+':occupied_count'] = self[zone].count + persist_current_status + end + + def persist_current_status + status = { + desks_pending_busy: self[:busy_desks], + desks_pending_free: self[:free_desks], + last_update: self[:last_update], + } + + @zones.each do |zone, gateways| + status[zone] = self[zone] + status[zone+':desk_ids'] = self[zone+':desk_ids'] + status[zone+':desk_count'] = self[zone+':desk_count'] + status[zone+':occupied_count'] = self[zone+':occupied_count'] + end + define_setting(:zzz_status, status) end # Transform an array of Sensor Names to SVG Map IDs, IF the user has specified a mapping in settings(sensor_to_desk_mappings) From 88a3a70aa9508292bc2157aaf3e5bff23a4b7009 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 11:53:38 +0800 Subject: [PATCH 1474/1752] fix(pressac/desks): syntax of Array +/- operations --- modules/pressac/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 683aabc2..329b74ba 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -178,7 +178,7 @@ def determine_desk_status end def expose_desk_status(desk_name, zone, occupied) - self[zone] = occupied ? (self[zone] | [desk_name]) : (self[zone] - desk_name) + + self[zone] = occupied ? (self[zone] | [desk_name]) : (self[zone] - [desk_name]) self[zone+':occupied_count'] = self[zone].count persist_current_status end From d637e261d785cefdae11bf52ea141bb05ca8142a Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 11:56:36 +0800 Subject: [PATCH 1475/1752] fix(pressac/desks): init of status --- modules/pressac/desk_management.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 329b74ba..d70c9064 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -54,10 +54,12 @@ def on_update if saved_status @saved_status.keys.each { |key, value| self[key] = value } else - self[zone_id] = [] - self[zone_id+':desk_ids'] = [] - self[zone_id+':occupied_count'] = 0 - self[zone_id+':desk_count'] = 0 + @zones.keys.each do |zone_id| + self[zone_id] = [] + self[zone_id+':desk_ids'] = [] + self[zone_id+':occupied_count'] = 0 + self[zone_id+':desk_count'] = 0 + end end @zones.each do |zone,gateways| From c53f0503992b0cc6471a412af3cdf4b51440707a Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 11:59:38 +0800 Subject: [PATCH 1476/1752] fix(pressac/desks): syntax in expose_desk_status --- modules/pressac/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index d70c9064..ec906f49 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -180,7 +180,7 @@ def determine_desk_status end def expose_desk_status(desk_name, zone, occupied) - + self[zone] = occupied ? (self[zone] | [desk_name]) : (self[zone] - [desk_name]) + self[zone] = occupied ? (self[zone] | [desk_name]) : (self[zone] - [desk_name]) self[zone+':occupied_count'] = self[zone].count persist_current_status end From 67167d8b9cc20c07e8bf7994283af134541e09a2 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 14:01:05 +0800 Subject: [PATCH 1477/1752] fix(prpressac/desks): loading of persisted desk data --- modules/pressac/desk_management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index ec906f49..900bdfae 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -52,9 +52,9 @@ def on_update saved_status = setting('zzz_status') if saved_status - @saved_status.keys.each { |key, value| self[key] = value } + saved_status&.each { |key, value| self[key] = value } else - @zones.keys.each do |zone_id| + @zones.keys&.each do |zone_id| self[zone_id] = [] self[zone_id+':desk_ids'] = [] self[zone_id+':occupied_count'] = 0 From fea1fdc932b1a4bd55e0ba5e5f805cdf0d3e4ba0 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 16 Oct 2019 14:06:48 +0800 Subject: [PATCH 1478/1752] fix()(pressac/desks): fix persisting of sensor data --- modules/pressac/sensors/ws_protocol.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 9a465445..4bf9aca1 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -153,7 +153,8 @@ def on_message(raw_string) busy_desks: self[:busy_desks], free_desks: self[:free_desks], all_desks: self[:all_desks], - last_update: self[:last_update] + last_update: self[:last_update], + gateways: self[:gateways] } define_setting(:status, status) end From f416b80f3b06555add99241b8d4b549319b6b6fd Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 17 Oct 2019 14:25:26 +0800 Subject: [PATCH 1479/1752] feature(pressac/desk/logic): more explanatory variable name --- modules/pressac/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 900bdfae..3410d46f 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -41,7 +41,7 @@ def on_update @hub = setting('iot_hub_device') || "Websocket_1" @zones = setting('zone_to_gateway_mappings') || {} - @desk_ids = setting('sensor_to_desk_mappings') || {} + @desk_ids = setting('sensor_name_to_desk_mappings') || {} # convert '1m2s' to '62' @busy_delay = UV::Scheduler.parse_duration(setting('delaty_until_shown_as_busy') || '0m') / 1000 @free_delay = UV::Scheduler.parse_duration(setting('delaty_until_shown_as_free') || '0m') / 1000 From 3d666364d73e9ef1e02b323b904c9621733f92b0 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 17 Oct 2019 22:38:41 +0800 Subject: [PATCH 1480/1752] feature(event/locations): use .rooms to fill .locations; rename .location to .locations --- lib/microsoft/office2/events.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 8de88834..56eda777 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -143,7 +143,7 @@ def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_i # @option options [Boolean] :is_private Whether to mark the booking as private or just normal # @option options [String] :timezone The timezone of the booking. This will be overridden by a timezone in the room's settings # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking - # @option options [String] :location The location field to set. This will not be used if a room is passed in + # @option options [String] :locations The locations field to set. This will not be used if a room is passed in def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, calendar_id: nil, options: {}) default_options = { rooms: [], @@ -155,7 +155,7 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca is_private: false, timezone: 'UTC', extensions: {}, - location: nil + locations: nil } # Merge in our default options with those passed in options = options.reverse_merge(default_options) @@ -168,7 +168,7 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca start_param: start_param, end_param: end_param, timezone: options[:timezone], - location: options[:location], + locations: options[:rooms] || options[:locations], attendees: options[:attendees].dup, organizer: options[:organizer], recurrence: options[:recurrence], From 66319886c555c2d0c62e725f25380acbc40511a0 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 17 Oct 2019 22:42:24 +0800 Subject: [PATCH 1481/1752] fix(pressac/desk/logic): fir logic in determining desk status (with configurable delay times); fix persisting datal --- modules/pressac/desk_management.rb | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 3410d46f..5a1a5ecb 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -43,8 +43,8 @@ def on_update @zones = setting('zone_to_gateway_mappings') || {} @desk_ids = setting('sensor_name_to_desk_mappings') || {} # convert '1m2s' to '62' - @busy_delay = UV::Scheduler.parse_duration(setting('delaty_until_shown_as_busy') || '0m') / 1000 - @free_delay = UV::Scheduler.parse_duration(setting('delaty_until_shown_as_free') || '0m') / 1000 + @busy_delay = UV::Scheduler.parse_duration(setting('delay_until_shown_as_busy') || '0m') / 1000 + @free_delay = UV::Scheduler.parse_duration(setting('delay_until_shown_as_free') || '0m') / 1000 # Initialize desk tracking variables to [] or 0, but keep existing values if they exist (||=) @desks_pending_busy ||= {} @@ -128,7 +128,7 @@ def update_desk(notification) # timestamp: string, # gateway: string } desk = notification.value - desk_name = id([desk[:name]])&.first + desk_name = id([desk[:name].to_sym])&.first logger.debug "NOTIFICATION FROM DESK SENSOR============" logger.debug notification.value @@ -164,31 +164,38 @@ def which_zone(gateway) def determine_desk_status now = Time.now.to_i @desks_pending_busy.each do |desk,sensor| - if sensor[:timestamp] + @busy_delay > now + if now > sensor[:timestamp] + @busy_delay expose_desk_status(desk, which_zone(sensor[:gateway]), true) - @desks_pending_busy.delete(desk) + @desks_pending_busy.delete(desk.to_sym) end end @desks_pending_free.each do |desk,sensor| - if sensor[:timestamp] + @free_delay > now + if now > sensor[:timestamp] + @free_delay expose_desk_status(desk, which_zone(sensor[:gateway]), false) - @desks_pending_free.delete(desk) + @desks_pending_free.delete(desk.to_sym) end end self[:desks_pending_busy] = @desks_pending_busy self[:desks_pending_free] = @desks_pending_free + persist_current_status end def expose_desk_status(desk_name, zone, occupied) - self[zone] = occupied ? (self[zone] | [desk_name]) : (self[zone] - [desk_name]) + if occupied + self[zone] = self[zone] - [desk_name] + self[zone+':desk_ids'] = self[zone+':desk_ids'] | [desk_name] + else + self[zone] = self[zone] | [desk_name] + self[zone+':desk_ids'] = self[zone+':desk_ids'] - [desk_name] + end self[zone+':occupied_count'] = self[zone].count - persist_current_status + self[zone+':desk_count'] = self[zone+':desk_ids'].count end def persist_current_status status = { - desks_pending_busy: self[:busy_desks], - desks_pending_free: self[:free_desks], + desks_pending_busy: @desks_pending_busy, + desks_pending_free: @desks_pending_free, last_update: self[:last_update], } From 45b71f71d921152334abd025f0b5d8e7a6785d40 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 18 Oct 2019 00:24:09 +0800 Subject: [PATCH 1482/1752] fix(office2/event/locations): fix create event, populate locations field --- lib/microsoft/office2/events.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 56eda777..5de54bbe 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -143,7 +143,7 @@ def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_i # @option options [Boolean] :is_private Whether to mark the booking as private or just normal # @option options [String] :timezone The timezone of the booking. This will be overridden by a timezone in the room's settings # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking - # @option options [String] :locations The locations field to set. This will not be used if a room is passed in + # @option options [String] :location The location field to set. This will not be used if a room is passed in def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, calendar_id: nil, options: {}) default_options = { rooms: [], @@ -155,7 +155,7 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca is_private: false, timezone: 'UTC', extensions: {}, - locations: nil + location: nil } # Merge in our default options with those passed in options = options.reverse_merge(default_options) @@ -168,7 +168,7 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca start_param: start_param, end_param: end_param, timezone: options[:timezone], - locations: options[:rooms] || options[:locations], + location: options[:location], attendees: options[:attendees].dup, organizer: options[:organizer], recurrence: options[:recurrence], @@ -296,9 +296,6 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, attendees.push({ type: "resource", emailAddress: { address: room[:email], name: room[:name] } }) end - # If we have rooms then build the location from that, otherwise use the passed in value - event_location = rooms.map{ |room| room[:name] }.join(" and ") - event_location = ( event_location.present? ? event_location : location ) event_json = {} event_json[:subject] = subject @@ -320,9 +317,8 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timeZone: timezone } if end_param - event_json[:location] = { - displayName: location - } if location + # If we have rooms then use that, otherwise use locations, which we expect to be in the correct o365 graph OData format + event_json[:locations] = rooms.present? ? rooms.map { |r| { displayName: r[:name] } } : location ? [{displayName: location}] : [] event_json[:organizer] = { emailAddress: { From a1d63c4f205f75c5d401032a5895b4a13e666f65 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 18 Oct 2019 00:41:13 +0800 Subject: [PATCH 1483/1752] docs(office2/event/location): slightly better comment explaining hard to read line of code --- lib/microsoft/office2/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 5de54bbe..c8204607 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -317,7 +317,7 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timeZone: timezone } if end_param - # If we have rooms then use that, otherwise use locations, which we expect to be in the correct o365 graph OData format + # If we have rooms then use that, otherwise use the location string. Fall back is []. event_json[:locations] = rooms.present? ? rooms.map { |r| { displayName: r[:name] } } : location ? [{displayName: location}] : [] event_json[:organizer] = { From 26443e67f75d88ecb308e67baa7c0bed8e3a633d Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 18 Oct 2019 01:20:02 +0800 Subject: [PATCH 1484/1752] fix(pressac/desk/logic): for determining pending desks --- modules/pressac/desk_management.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 5a1a5ecb..1c072d96 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -134,22 +134,23 @@ def update_desk(notification) logger.debug notification.value logger.debug desk[:gateway] - if current_state[:motion] && !previous_state[:motion] + if current_state[:motion] @desks_pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} @desks_pending_free.delete(desk_name) - elsif !current_state[:motion] && previous_state[:motion] + elsif !current_state[:motion] @desks_pending_free[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} @desks_pending_busy.delete(desk_name) end zone = which_zone(desk[:gateway]) - logger.debug "=======VALUE FOR ZONE: #{zone}, #{desk[:gateway]}" if zone zone = zone.to_s self[zone+':desk_ids'] = self[zone+':desk_ids'] | [desk_name] self[zone+':desk_count'] = self[zone+':desk_ids'].count self[:last_update] = Time.now.in_time_zone($TZ).to_s end + self[:desks_pending_busy] = @desks_pending_busy + self[:desks_pending_free] = @desks_pending_free end # return the (first) zone that a gateway is in From ab65bf4316bbf9506a13b68a60085d62f4d82d31 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 18 Oct 2019 01:21:05 +0800 Subject: [PATCH 1485/1752] fix(pressac/desk/sensor)remove root level desk tracking (now its gateway level) --- modules/pressac/sensors/ws_protocol.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 4bf9aca1..77d99b0a 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -27,14 +27,9 @@ class Pressac::Sensors::WsProtocol def on_load status = setting(:status) || {} - self[:busy_desks] = status[:busy_desks] || {} # hash of gateway names => Array of desk names - self[:free_desks] = status[:free_desks] || {} - self[:all_desks] = status[:all_desks] || {} self[:gateways] = status[:gateways] || {} self[:last_update] = status[:last_update] || "Never" - self[:environment] = {} # Environment sensor values (temp, humidity) - @busy_desks = self[:busy_desks] - @free_desks = self[:free_desks] + self[:environment] = {} # Environment sensor values (temp, humidity) on_update end @@ -109,6 +104,8 @@ def on_message(raw_string) gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym occupancy = sensor[:motionDetected] == true + @busy_desks ||= {} + @free_desks ||= {} @free_desks[gateway] ||= [].to_set @busy_desks[gateway] ||= [].to_set self[:gateways][gateway] ||= {} @@ -150,9 +147,6 @@ def on_message(raw_string) self[:last_update] = Time.now.in_time_zone($TZ).to_s # Save the current status to database, so that it can retrieved when engine restarts status = { - busy_desks: self[:busy_desks], - free_desks: self[:free_desks], - all_desks: self[:all_desks], last_update: self[:last_update], gateways: self[:gateways] } From 049a077008c5a8bc358b95c468b8ac2130e2279b Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 18 Oct 2019 03:18:26 +0800 Subject: [PATCH 1486/1752] fix(pressac/desk/sensor): bug in registering busy/free desks per gateway --- modules/pressac/sensors/ws_protocol.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 77d99b0a..3eec5b9a 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -26,6 +26,8 @@ class Pressac::Sensors::WsProtocol }) def on_load + @busy_desks = {} + @free_desks = {} status = setting(:status) || {} self[:gateways] = status[:gateways] || {} self[:last_update] = status[:last_update] || "Never" @@ -104,21 +106,19 @@ def on_message(raw_string) gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym occupancy = sensor[:motionDetected] == true - @busy_desks ||= {} - @free_desks ||= {} - @free_desks[gateway] ||= [].to_set - @busy_desks[gateway] ||= [].to_set + @free_desks[gateway] ||= [] + @busy_desks[gateway] ||= [] self[:gateways][gateway] ||= {} if occupancy - @busy_desks[gateway] | [sensor_name] - @free_desks[gateway] - [sensor_name] + @busy_desks[gateway] = @busy_desks[gateway] | [sensor_name] + @free_desks[gateway] = @free_desks[gateway] - [sensor_name] else - @busy_desks[gateway] - [sensor_name] - @free_desks[gateway] | [sensor_name] + @busy_desks[gateway] = @busy_desks[gateway] - [sensor_name] + @free_desks[gateway] = @free_desks[gateway] | [sensor_name] end - self[:gateways][gateway][:busy_desks] = @busy_desks[gateway].to_a - self[:gateways][gateway][:free_desks] = @free_desks[gateway].to_a + self[:gateways][gateway][:busy_desks] = @busy_desks[gateway] + self[:gateways][gateway][:free_desks] = @free_desks[gateway] self[:gateways][gateway][:all_desks] = @busy_desks[gateway] + @free_desks[gateway] # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), From afa616a35ef393490635830e95fd693f5dca75e6 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 18 Oct 2019 03:19:10 +0800 Subject: [PATCH 1487/1752] fix(pressac/desk/logic): fix inverted logic for desk status! --- modules/pressac/desk_management.rb | 33 +++++++++++++----------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 1c072d96..a591b039 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -110,7 +110,7 @@ def update_zone(zone, gateway) free_desks = id gateway_data[:free_desks] all_desks = id gateway_data[:all_desks] - self[zone+':desk_ids'] = self[zone] | all_desks + self[zone+':desk_ids'] = self[zone+':desk_ids'] | all_desks self[zone+':desk_count'] = self[zone+':desk_ids'].count self[:last_update] = Time.now.in_time_zone($TZ).to_s end @@ -128,27 +128,24 @@ def update_desk(notification) # timestamp: string, # gateway: string } desk = notification.value - desk_name = id([desk[:name].to_sym])&.first + desk_name_str = id([desk[:name].to_sym])&.first + desk_name = desk_name_str.to_sym - logger.debug "NOTIFICATION FROM DESK SENSOR============" - logger.debug notification.value - logger.debug desk[:gateway] + zone = which_zone(desk[:gateway]) + return unless zone - if current_state[:motion] + if current_state[:motion] && !self[zone].include?(desk_name_str) # if motion, and desk is currently free @desks_pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} @desks_pending_free.delete(desk_name) - elsif !current_state[:motion] + elsif !current_state[:motion] && self[zone].include?(desk_name_str) # if no motion, and desk is currently busy @desks_pending_free[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} @desks_pending_busy.delete(desk_name) end - - zone = which_zone(desk[:gateway]) - if zone - zone = zone.to_s - self[zone+':desk_ids'] = self[zone+':desk_ids'] | [desk_name] - self[zone+':desk_count'] = self[zone+':desk_ids'].count - self[:last_update] = Time.now.in_time_zone($TZ).to_s - end + + zone = zone.to_s + self[zone+':desk_ids'] = self[zone+':desk_ids'] | [desk_name] + self[zone+':desk_count'] = self[zone+':desk_ids'].count + self[:last_update] = Time.now.in_time_zone($TZ).to_s self[:desks_pending_busy] = @desks_pending_busy self[:desks_pending_free] = @desks_pending_free end @@ -183,11 +180,9 @@ def determine_desk_status def expose_desk_status(desk_name, zone, occupied) if occupied - self[zone] = self[zone] - [desk_name] - self[zone+':desk_ids'] = self[zone+':desk_ids'] | [desk_name] + self[zone] = self[zone] | [desk_name] else - self[zone] = self[zone] | [desk_name] - self[zone+':desk_ids'] = self[zone+':desk_ids'] - [desk_name] + self[zone] = self[zone] - [desk_name] end self[zone+':occupied_count'] = self[zone].count self[zone+':desk_count'] = self[zone+':desk_ids'].count From c448138b4e7b80f81f17c1dfc4c80a2b310e5fff Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 18 Oct 2019 17:35:36 +0800 Subject: [PATCH 1488/1752] feature(events/POST/PUT/DELETE): Handle 409 "Conflict/ConcurrentItemSave" with a retry --- lib/microsoft/office2/client.rb | 3 ++- lib/microsoft/office2/events.rb | 25 +++++++++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index d86f3b07..115d6924 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -16,6 +16,7 @@ class InvalidAuthenticationToken < Error; end class BadRequest < Error; end class ErrorInvalidIdMalformed < Error; end class ErrorAccessDenied < Error; end + class Conflict < Error; end end end @@ -166,7 +167,7 @@ def check_response(response) when 404 raise Microsoft::Error::ResourceNotFound.new(response.body) when 409 - raise Microsoft::Error::ErrorFolderExists.new(response.body) + raise Microsoft::Error::Conflict.new(response.body) end end diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index c8204607..0b8a0997 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -177,8 +177,13 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca ) # Make the request and check the response - request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events"], data: event_json) - check_response(request) + begin + retries ||= 0 + request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events"], data: event_json) + check_response(request) + rescue Microsoft::Error::Conflict => e + retry if (retries += 1) < 3 + end Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body)).event end @@ -244,8 +249,12 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni end # Make the request and check the response - request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}"], data: event_json) - check_response(request) + begin + request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}"], data: event_json) + check_response(request) + rescue Microsoft::Error::Conflict => e + retry if (retries += 1) < 3 + end Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event end @@ -257,8 +266,12 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni # @param booking_id [String] The ID of the booking to be deleted def delete_booking(mailbox:, booking_id:, calendargroup_id: nil, calendar_id: nil) endpoint = "/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}" - request = graph_request(request_method: 'delete', endpoints: [endpoint]) - check_response(request) + begin + request = graph_request(request_method: 'delete', endpoints: [endpoint]) + check_response(request) + rescue Microsoft::Error::Conflict => e + retry if (retries += 1) < 3 + end 200 end From 1cdac6e54b98120ba94352cfad5ac526c07d8ff0 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 20 Oct 2019 16:29:07 +0800 Subject: [PATCH 1489/1752] fix(office2/events/POST): randomise delay time on retry for 409 from Graph --- lib/microsoft/office2/events.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 0b8a0997..967eef48 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -182,7 +182,10 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events"], data: event_json) check_response(request) rescue Microsoft::Error::Conflict => e - retry if (retries += 1) < 3 + if (retries += 1) < 3 + sleep(rand()) + retry + end end Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body)).event end @@ -253,9 +256,11 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}"], data: event_json) check_response(request) rescue Microsoft::Error::Conflict => e - retry if (retries += 1) < 3 - end - + if (retries += 1) < 3 + sleep(rand()*2) + retry + end + end Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event end @@ -270,7 +275,10 @@ def delete_booking(mailbox:, booking_id:, calendargroup_id: nil, calendar_id: ni request = graph_request(request_method: 'delete', endpoints: [endpoint]) check_response(request) rescue Microsoft::Error::Conflict => e - retry if (retries += 1) < 3 + if (retries += 1) < 3 + sleep(rand()*2) + retry + end end 200 end From 3afc45a86f4bb0f9e45fda0bcf3bfab7b6a47d22 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 20 Oct 2019 19:16:25 +0800 Subject: [PATCH 1490/1752] fix(pressac/desk/logic): fix potential str/sym issue when exposing desk status --- modules/pressac/desk_management.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index a591b039..8f1c42ea 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -128,7 +128,7 @@ def update_desk(notification) # timestamp: string, # gateway: string } desk = notification.value - desk_name_str = id([desk[:name].to_sym])&.first + desk_name_str = id([desk[:name].to_sym])&.first.to_s desk_name = desk_name_str.to_sym zone = which_zone(desk[:gateway]) @@ -161,6 +161,8 @@ def which_zone(gateway) def determine_desk_status now = Time.now.to_i + new_busy_desks = [] + new_free_desks = [] @desks_pending_busy.each do |desk,sensor| if now > sensor[:timestamp] + @busy_delay expose_desk_status(desk, which_zone(sensor[:gateway]), true) @@ -179,11 +181,13 @@ def determine_desk_status end def expose_desk_status(desk_name, zone, occupied) + desk_name_str = desk_name.to_s if occupied - self[zone] = self[zone] | [desk_name] + self[zone] = self[zone] | [desk_name_str] else - self[zone] = self[zone] - [desk_name] + self[zone] = self[zone] - [desk_name_str] end + signal_status(zone) self[zone+':occupied_count'] = self[zone].count self[zone+':desk_count'] = self[zone+':desk_ids'].count end From a3cbcddbc105b3e800b4ef840dfae8c214d2c786 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 20 Oct 2019 20:14:10 +0800 Subject: [PATCH 1491/1752] feature(office2/RBP): stagger fetch over random times to ease load --- modules/aca/o365_booking_panel.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index b473094c..7b4d19cc 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -89,9 +89,10 @@ def on_update self[:last_meeting_started] = setting(:last_meeting_started) self[:cancel_meeting_after] = setting(:cancel_meeting_after) - fetch_bookings schedule.clear - schedule.every(setting(:update_every) || '5m') { fetch_bookings } + schedule.in(rand(10000)) { fetch_bookings } + fetch_interval = UV::Scheduler.parse_duration(setting(:update_every) || '5m') + rand(10000) + schedule.every(fetch_interval)) { fetch_bookings } end def fetch_bookings(*args) From 39b3aa4c7b258107b2dea5b1fd94df6993591c37 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 20 Oct 2019 20:24:36 +0800 Subject: [PATCH 1492/1752] fixx(office2/RBP): stagger fetch requests: fix syntax --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 7b4d19cc..f428db03 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -92,7 +92,7 @@ def on_update schedule.clear schedule.in(rand(10000)) { fetch_bookings } fetch_interval = UV::Scheduler.parse_duration(setting(:update_every) || '5m') + rand(10000) - schedule.every(fetch_interval)) { fetch_bookings } + schedule.every(fetch_interval) { fetch_bookings } end def fetch_bookings(*args) From 8d6cc1d5d6f4d71bfcd27454c5f288caf42b5d79 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 21 Oct 2019 02:04:19 +0800 Subject: [PATCH 1493/1752] feature(pressace/desk/logic): remove status for unresponsive sensors --- modules/pressac/desk_management.rb | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 8f1c42ea..dc670cc5 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -80,6 +80,7 @@ def on_update end schedule.clear schedule.every('1m') { determine_desk_status } + schedule.every('1h') { unexpose_unresponsive_desks } end # @param zone [String] the engine zone id @@ -128,7 +129,7 @@ def update_desk(notification) # timestamp: string, # gateway: string } desk = notification.value - desk_name_str = id([desk[:name].to_sym])&.first.to_s + desk_name_str = id([desk[:name].to_sym])&.first.to_s desk_name = desk_name_str.to_sym zone = which_zone(desk[:gateway]) @@ -153,7 +154,6 @@ def update_desk(notification) # return the (first) zone that a gateway is in def which_zone(gateway) @zones&.each do |zone, gateways| - logger.debug "#{zone}: #{gateways}" return zone if gateways.include? gateway.to_s end nil @@ -181,13 +181,13 @@ def determine_desk_status end def expose_desk_status(desk_name, zone, occupied) - desk_name_str = desk_name.to_s + desk_name_str = desk_name.to_s if occupied self[zone] = self[zone] | [desk_name_str] else self[zone] = self[zone] - [desk_name_str] end - signal_status(zone) + signal_status(zone) self[zone+':occupied_count'] = self[zone].count self[zone+':desk_count'] = self[zone+':desk_ids'].count end @@ -213,4 +213,19 @@ def id(array) return [] if array.nil? array.map { |i| @desk_ids[i] || i } end + + def unexpose_unresponsive_desks + gateways = system[@hub][:gateways] + gateways.each do |gateway, sensor| + last_update = Time.parse(sensor[:timestamp]) + if Time.now > last_update + 1.hour + desk_name = id([sensor[:name]]).first.to_s + zone = which_zone(sensor[:gateway]) + self[zone] = self[zone] - [desk_name] + self[zone+':desk_ids'] = self[zone+':desk_ids'] - [desk_name] + self[zone+':desk_count'] = self[zone+':desk_ids'].count + self[zone+':occupied_count'] = self[zone].count + end + end + end end From 006b4f5403601523606e99379639574cfcdde5c5 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 21 Oct 2019 03:21:20 +0800 Subject: [PATCH 1494/1752] fix(pessac/desk/logic): update zone status all in one hit instead of once per desk change --- modules/pressac/desk_management.rb | 36 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index dc670cc5..9e806a8f 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -80,7 +80,7 @@ def on_update end schedule.clear schedule.every('1m') { determine_desk_status } - schedule.every('1h') { unexpose_unresponsive_desks } + #schedule.every('1h') { unexpose_unresponsive_desks } end # @param zone [String] the engine zone id @@ -161,35 +161,39 @@ def which_zone(gateway) def determine_desk_status now = Time.now.to_i - new_busy_desks = [] - new_free_desks = [] + new_busy_desks = {} + new_free_desks = {} @desks_pending_busy.each do |desk,sensor| if now > sensor[:timestamp] + @busy_delay - expose_desk_status(desk, which_zone(sensor[:gateway]), true) - @desks_pending_busy.delete(desk.to_sym) + zone = which_zone(sensor[:gateway] + new_busy_desks[zone] ||= [] + new_busy_desks[zone] = new_busy_desks[zone] | [desk] + @desks_pending_busy.delete(desk) end end @desks_pending_free.each do |desk,sensor| if now > sensor[:timestamp] + @free_delay - expose_desk_status(desk, which_zone(sensor[:gateway]), false) - @desks_pending_free.delete(desk.to_sym) + zone = which_zone(sensor[:gateway] + new_free_desks[zone] ||= [] + new_free_desks[zone] = new_free_desks[zone] | [desk] + @desks_pending_free.delete(desk) end end self[:desks_pending_busy] = @desks_pending_busy self[:desks_pending_free] = @desks_pending_free + expose_desks(new_busy_desks, new_free_desks) persist_current_status end - def expose_desk_status(desk_name, zone, occupied) - desk_name_str = desk_name.to_s - if occupied - self[zone] = self[zone] | [desk_name_str] - else - self[zone] = self[zone] - [desk_name_str] + def expose_desks(busy, free) + @zones.keys&.each do |z| + total_busy = self[z] | busy[z] - free[z] + total_ids = self[z+':desk_ids'] | busy[z] | free[z] + self[z] = total_busy + self[z+':desk_ids'] = total_ids + self[z+':desk_count'] = total_ids.count + self[z+':occupied_count'] = total_busy.count end - signal_status(zone) - self[zone+':occupied_count'] = self[zone].count - self[zone+':desk_count'] = self[zone+':desk_ids'].count end def persist_current_status From 6a82141a46568a2568bb1984ef3253aaa0223f3e Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 21 Oct 2019 03:26:28 +0800 Subject: [PATCH 1495/1752] fix(pressac/desk/logic): syntax in status update --- modules/pressac/desk_management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 9e806a8f..744ccff1 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -165,7 +165,7 @@ def determine_desk_status new_free_desks = {} @desks_pending_busy.each do |desk,sensor| if now > sensor[:timestamp] + @busy_delay - zone = which_zone(sensor[:gateway] + zone = which_zone(sensor[:gateway]) new_busy_desks[zone] ||= [] new_busy_desks[zone] = new_busy_desks[zone] | [desk] @desks_pending_busy.delete(desk) @@ -173,7 +173,7 @@ def determine_desk_status end @desks_pending_free.each do |desk,sensor| if now > sensor[:timestamp] + @free_delay - zone = which_zone(sensor[:gateway] + zone = which_zone(sensor[:gateway]) new_free_desks[zone] ||= [] new_free_desks[zone] = new_free_desks[zone] | [desk] @desks_pending_free.delete(desk) From 96995635495d1edef4b753a81ba52ffcf43853b2 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 21 Oct 2019 03:38:29 +0800 Subject: [PATCH 1496/1752] fix(pressac/desk/logic): expose status: logic error when nil --- modules/pressac/desk_management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 744ccff1..8b1bfb9f 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -187,8 +187,8 @@ def determine_desk_status def expose_desks(busy, free) @zones.keys&.each do |z| - total_busy = self[z] | busy[z] - free[z] - total_ids = self[z+':desk_ids'] | busy[z] | free[z] + total_busy = self[z] | (busy[z] || []) - (free[z] || []) + total_ids = self[z+':desk_ids'] | (busy[z] || []) | (free[z] || []) self[z] = total_busy self[z+':desk_ids'] = total_ids self[z+':desk_count'] = total_ids.count From 2022f61b2fefbb7ce9c816809e275454e7837b44 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 21 Oct 2019 03:43:59 +0800 Subject: [PATCH 1497/1752] feature(desk/pressac/logic): REMOVE initial update from sensors on_load (rely on notifications only) --- modules/pressac/desk_management.rb | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 8b1bfb9f..b4b040e3 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -61,13 +61,6 @@ def on_update self[zone_id+':desk_count'] = 0 end end - - @zones.each do |zone,gateways| - gateways.each do |gateway| - # Populate our initial status with the current data from all known sensors - update_zone(zone, gateway.to_sym) - end - end # Subscribe to live updates from each gateway device,index = @hub.split('_') @@ -97,25 +90,6 @@ def desk_details(*desk_ids) protected - # Update one zone with the current data from one gateway - def update_zone(zone, gateway) - # The below values reflect just this ONE gateway, not neccesarily the whole zone - begin - gateway_data = system[@hub][:gateways][gateway] || {} - rescue - gateway_data = {} - end - logger.debug "#{zone}: #{gateway_data}" - - busy_desks = id gateway_data[:busy_desks] - free_desks = id gateway_data[:free_desks] - all_desks = id gateway_data[:all_desks] - - self[zone+':desk_ids'] = self[zone+':desk_ids'] | all_desks - self[zone+':desk_count'] = self[zone+':desk_ids'].count - self[:last_update] = Time.now.in_time_zone($TZ).to_s - end - # Update desks_pending_busy/free hashes with a single sensor's data recieved from a notification def update_desk(notification) current_state = notification.value From b20c9e521fb965593c9114aaae056d7a978b127d Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Mon, 21 Oct 2019 12:12:17 +1000 Subject: [PATCH 1498/1752] fix(extron/switcher/in): switch methods Resolved incorect references used in compatability switch methods. Fixes #81 --- modules/extron/switcher/in.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/extron/switcher/in.rb b/modules/extron/switcher/in.rb index f2d26e2d..82679258 100644 --- a/modules/extron/switcher/in.rb +++ b/modules/extron/switcher/in.rb @@ -39,17 +39,16 @@ def switch_to(input = nil) do_send("#{input}!") end - # nil as these functions can be used to request state too def switch(map) - send("#{input.keys[-1]}!") + send("#{map.keys[-1]}!") end def switch_video(map) - send("#{input.keys[-1]}&") + send("#{map.keys[-1]}&") end def switch_audio(map) - send("#{input.keys[-1]}$") + send("#{map.keys[-1]}$") end From 78fb62d3509f6917e6911ad6f38c147a627d427b Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Mon, 21 Oct 2019 15:10:30 +1100 Subject: [PATCH 1499/1752] add in built time remaining calculation for mediasite recordings --- modules/mediasite/module.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index b650d601..166fe2d1 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -29,7 +29,6 @@ def on_load end def on_update - schedule.clear self[:room_name] = room_name self[:device_id] = get_device_id poll @@ -40,6 +39,7 @@ def room_name end def poll + schedule.clear state schedule.every("#{setting(:update_every)}s") do state @@ -49,7 +49,7 @@ def poll def get_request(url) req_url = url logger.debug(req_url) - + task { uri = URI(req_url) req = Net::HTTP::Get.new(uri) @@ -63,7 +63,7 @@ def get_request(url) def post_request(url) req_url = setting(:url) + url - + task { uri = URI(req_url) req = Net::HTTP::Post.new(uri) @@ -136,8 +136,8 @@ def state res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/ActiveInputs")) self[:dual] = res['ActiveInputs'].size >= 2 if res['ActiveInputs'] - res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/TimeRemaining")) - self[:time_remaining] = res['SecondsRemaining'] + #res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/TimeRemaining")) + #self[:time_remaining] = res['SecondsRemaining'] self[:volume] = 0 # TODO: end @@ -152,7 +152,11 @@ def live? if start_time <= current_time && current_time <= end_time presentation = get_request(schedule['ScheduleLink'] + '/Presentations') live = presentation['value'][0]['Status'] == 'Live' + self[:time_remaining] = (end_time - current_time).to_i + logger.debug "Time remaining is #{distance_of_time_in_words(self[:time_remaining])}" break + else + self[:time_remaining] = 0 end end live From 4cc8cf7b74e084cc83318580981ac0c836059274 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 21 Oct 2019 23:59:33 +0800 Subject: [PATCH 1500/1752] fix(pressac/desk/logic): fix logic causing desks to never appear free --- modules/pressac/desk_management.rb | 81 ++++++++++++++---------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index b4b040e3..fc5b95b6 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -47,8 +47,8 @@ def on_update @free_delay = UV::Scheduler.parse_duration(setting('delay_until_shown_as_free') || '0m') / 1000 # Initialize desk tracking variables to [] or 0, but keep existing values if they exist (||=) - @desks_pending_busy ||= {} - @desks_pending_free ||= {} + @pending_busy ||= {} + @pending_free ||= {} saved_status = setting('zzz_status') if saved_status @@ -61,6 +61,12 @@ def on_update self[zone_id+':desk_count'] = 0 end end + + # Create a reverse lookup (gateway => zone) + @which_zone = {} + @zones.each do |z, gateways| + gateways.each {|g| @which_zone[g] = z} + end # Subscribe to live updates from each gateway device,index = @hub.split('_') @@ -90,7 +96,7 @@ def desk_details(*desk_ids) protected - # Update desks_pending_busy/free hashes with a single sensor's data recieved from a notification + # Update pending_busy/free hashes with a single sensor's data recieved from a notification def update_desk(notification) current_state = notification.value previous_state = notification.old_value || {motion: false} @@ -106,65 +112,55 @@ def update_desk(notification) desk_name_str = id([desk[:name].to_sym])&.first.to_s desk_name = desk_name_str.to_sym - zone = which_zone(desk[:gateway]) + zone = @which_zone(desk[:gateway]) return unless zone if current_state[:motion] && !self[zone].include?(desk_name_str) # if motion, and desk is currently free - @desks_pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} - @desks_pending_free.delete(desk_name) + @pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} + @pending_free.delete(desk_name) elsif !current_state[:motion] && self[zone].include?(desk_name_str) # if no motion, and desk is currently busy - @desks_pending_free[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} - @desks_pending_busy.delete(desk_name) + @pending_free[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} + @pending_busy.delete(desk_name) end zone = zone.to_s self[zone+':desk_ids'] = self[zone+':desk_ids'] | [desk_name] self[zone+':desk_count'] = self[zone+':desk_ids'].count self[:last_update] = Time.now.in_time_zone($TZ).to_s - self[:desks_pending_busy] = @desks_pending_busy - self[:desks_pending_free] = @desks_pending_free - end - - # return the (first) zone that a gateway is in - def which_zone(gateway) - @zones&.each do |zone, gateways| - return zone if gateways.include? gateway.to_s - end - nil + self[:pending_busy] = @pending_busy + self[:pending_free] = @pending_free end def determine_desk_status + new_status = {} + @zones.each do |zone, gateways| + new_status[zone] = {} + new_status[zone][:busy] = new_status[zone][:free] = [] + end + now = Time.now.to_i - new_busy_desks = {} - new_free_desks = {} - @desks_pending_busy.each do |desk,sensor| + @pending_busy.each do |desk,sensor| if now > sensor[:timestamp] + @busy_delay - zone = which_zone(sensor[:gateway]) - new_busy_desks[zone] ||= [] - new_busy_desks[zone] = new_busy_desks[zone] | [desk] - @desks_pending_busy.delete(desk) + zone = @which_zone(sensor[:gateway]) + new_status[zone][:busy] |= [desk] end end - @desks_pending_free.each do |desk,sensor| + @pending_free.each do |desk,sensor| if now > sensor[:timestamp] + @free_delay - zone = which_zone(sensor[:gateway]) - new_free_desks[zone] ||= [] - new_free_desks[zone] = new_free_desks[zone] | [desk] - @desks_pending_free.delete(desk) + zone = @which_zone(sensor[:gateway]) + new_status[zone][:free] |= [desk] end end - self[:desks_pending_busy] = @desks_pending_busy - self[:desks_pending_free] = @desks_pending_free - expose_desks(new_busy_desks, new_free_desks) + expose_desks(new_status) + self[:pending_busy] = @pending_busy = {} + self[:pending_free] = @pending_free = {} persist_current_status end - def expose_desks(busy, free) - @zones.keys&.each do |z| - total_busy = self[z] | (busy[z] || []) - (free[z] || []) - total_ids = self[z+':desk_ids'] | (busy[z] || []) | (free[z] || []) - self[z] = total_busy - self[z+':desk_ids'] = total_ids + def expose_desks(new_status) + new_status&.each do |z| + self[z] = self[z] | z[:busy] - z[:free] + self[z+':desk_ids'] = self[z+':desk_ids'] | z[:busy] | z[:free] self[z+':desk_count'] = total_ids.count self[z+':occupied_count'] = total_busy.count end @@ -172,11 +168,10 @@ def expose_desks(busy, free) def persist_current_status status = { - desks_pending_busy: @desks_pending_busy, - desks_pending_free: @desks_pending_free, + pending_busy: @pending_busy, + pending_free: @pending_free, last_update: self[:last_update], } - @zones.each do |zone, gateways| status[zone] = self[zone] status[zone+':desk_ids'] = self[zone+':desk_ids'] @@ -198,7 +193,7 @@ def unexpose_unresponsive_desks last_update = Time.parse(sensor[:timestamp]) if Time.now > last_update + 1.hour desk_name = id([sensor[:name]]).first.to_s - zone = which_zone(sensor[:gateway]) + zone = @which_zone(sensor[:gateway]) self[zone] = self[zone] - [desk_name] self[zone+':desk_ids'] = self[zone+':desk_ids'] - [desk_name] self[zone+':desk_count'] = self[zone+':desk_ids'].count From 44d5b64f6672dcd9c3d8ed4105437583640714e2 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 22 Oct 2019 00:02:16 +0800 Subject: [PATCH 1501/1752] fix(pressac/desk/logic): fix syntax of which_zone hash access --- modules/pressac/desk_management.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index fc5b95b6..8dbcd538 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -112,7 +112,7 @@ def update_desk(notification) desk_name_str = id([desk[:name].to_sym])&.first.to_s desk_name = desk_name_str.to_sym - zone = @which_zone(desk[:gateway]) + zone = @which_zone[desk[:gateway]] return unless zone if current_state[:motion] && !self[zone].include?(desk_name_str) # if motion, and desk is currently free @@ -141,13 +141,13 @@ def determine_desk_status now = Time.now.to_i @pending_busy.each do |desk,sensor| if now > sensor[:timestamp] + @busy_delay - zone = @which_zone(sensor[:gateway]) + zone = @which_zone[sensor[:gateway]] new_status[zone][:busy] |= [desk] end end @pending_free.each do |desk,sensor| if now > sensor[:timestamp] + @free_delay - zone = @which_zone(sensor[:gateway]) + zone = @which_zone[sensor[:gateway]] new_status[zone][:free] |= [desk] end end @@ -193,7 +193,7 @@ def unexpose_unresponsive_desks last_update = Time.parse(sensor[:timestamp]) if Time.now > last_update + 1.hour desk_name = id([sensor[:name]]).first.to_s - zone = @which_zone(sensor[:gateway]) + zone = @which_zone[sensor[:gateway]] self[zone] = self[zone] - [desk_name] self[zone+':desk_ids'] = self[zone+':desk_ids'] - [desk_name] self[zone+':desk_count'] = self[zone+':desk_ids'].count From 8ef2ad2d737d85dc0529d624a2333dbc3e06bbdc Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 22 Oct 2019 01:12:37 +0800 Subject: [PATCH 1502/1752] fix(pressac/desk/logic): fix logix and syntax issues causing status not to be updated --- modules/pressac/desk_management.rb | 38 ++++++++++++++---------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 8dbcd538..fdd1da3a 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -78,7 +78,7 @@ def on_update end end schedule.clear - schedule.every('1m') { determine_desk_status } + schedule.every('30s') { determine_desk_status } #schedule.every('1h') { unexpose_unresponsive_desks } end @@ -109,23 +109,19 @@ def update_desk(notification) # timestamp: string, # gateway: string } desk = notification.value - desk_name_str = id([desk[:name].to_sym])&.first.to_s - desk_name = desk_name_str.to_sym + desk_name = id([desk[:name].to_sym])&.first.to_s - zone = @which_zone[desk[:gateway]] + zone = @which_zone[desk[:gateway].to_s] return unless zone - if current_state[:motion] && !self[zone].include?(desk_name_str) # if motion, and desk is currently free - @pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} - @pending_free.delete(desk_name) - elsif !current_state[:motion] && self[zone].include?(desk_name_str) # if no motion, and desk is currently busy + if current_state[:motion] && !self[zone].include?(desk_name) # if motion, and desk is currently free + @pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} + @pending_free.delete(desk_name) + elsif !current_state[:motion] && self[zone].include?(desk_name) # if no motion, and desk is currently busy @pending_free[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} @pending_busy.delete(desk_name) end - zone = zone.to_s - self[zone+':desk_ids'] = self[zone+':desk_ids'] | [desk_name] - self[zone+':desk_count'] = self[zone+':desk_ids'].count self[:last_update] = Time.now.in_time_zone($TZ).to_s self[:pending_busy] = @pending_busy self[:pending_free] = @pending_free @@ -141,14 +137,14 @@ def determine_desk_status now = Time.now.to_i @pending_busy.each do |desk,sensor| if now > sensor[:timestamp] + @busy_delay - zone = @which_zone[sensor[:gateway]] - new_status[zone][:busy] |= [desk] + zone = @which_zone[sensor[:gateway].to_s] + new_status[zone][:busy] |= [desk] if zone end end @pending_free.each do |desk,sensor| if now > sensor[:timestamp] + @free_delay - zone = @which_zone[sensor[:gateway]] - new_status[zone][:free] |= [desk] + zone = @which_zone[sensor[:gateway].to_s] + new_status[zone][:free] |= [desk] if zone end end expose_desks(new_status) @@ -158,11 +154,13 @@ def determine_desk_status end def expose_desks(new_status) - new_status&.each do |z| - self[z] = self[z] | z[:busy] - z[:free] - self[z+':desk_ids'] = self[z+':desk_ids'] | z[:busy] | z[:free] - self[z+':desk_count'] = total_ids.count - self[z+':occupied_count'] = total_busy.count + new_status&.each do |z,desks| + zone = z.to_sym + self[zone] ||= [] + self[zone] = self[zone] - desks[:free] | desks[:busy] + self[z+':desk_ids'] = self[z+':desk_ids'] | desks[:free] | desks[:busy] + self[z+':desk_count'] = self[z+':desk_ids'].count + self[z+':occupied_count'] = self[zone].count end end From b05792705402b9d325162984901fd01eff57aaa9 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 22 Oct 2019 13:01:01 +1100 Subject: [PATCH 1503/1752] feat(pexip:management): allow conferences without any pin --- modules/pexip/management.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index e7ee643f..dd10bd6f 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -50,11 +50,12 @@ def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(999 conf_alias ||= SecureRandom.uuid name ||= conf_alias + pin = pin.to_s.rjust(4, '0') if pin post('/api/admin/configuration/v1/conference/', body: { name: name.to_s, service_type: type, - pin: pin.to_s.rjust(4, '0'), + pin: pin, aliases: [{"alias" => conf_alias}], tag: tag }.merge(options).to_json, headers: { From b7706c4e27007f87fa77443dc5219e956f30cf05 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 23 Oct 2019 14:53:17 +1100 Subject: [PATCH 1504/1752] mediasite timer fixes --- modules/mediasite/module.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/mediasite/module.rb b/modules/mediasite/module.rb index 166fe2d1..c9216fd5 100644 --- a/modules/mediasite/module.rb +++ b/modules/mediasite/module.rb @@ -145,20 +145,20 @@ def state def live? live = self[:state] == 'Recording' res = get_request(create_url("/api/v1/Recorders('#{self[:device_id]}')/ScheduledRecordingTimes")) + current_schedule = false res['value'].each do |schedule| current_time = ActiveSupport::TimeZone.new('UTC').now start_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['StartTime']) end_time = ActiveSupport::TimeZone.new('UTC').parse(schedule['EndTime']) if start_time <= current_time && current_time <= end_time + current_schedule = true presentation = get_request(schedule['ScheduleLink'] + '/Presentations') live = presentation['value'][0]['Status'] == 'Live' self[:time_remaining] = (end_time - current_time).to_i - logger.debug "Time remaining is #{distance_of_time_in_words(self[:time_remaining])}" break - else - self[:time_remaining] = 0 end end + self[:time_remaining] = 0 if !current_schedule live end From f4517e4c0ddd5461faa99822317f53695d2760d6 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 23 Oct 2019 14:03:37 +0800 Subject: [PATCH 1505/1752] docs(pressac/desk/sensor): add info on initial setup of node-red --- modules/pressac/sensors/ws_protocol.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 3eec5b9a..2f9d0de4 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -12,7 +12,13 @@ module Pressac::Sensors; end # Pressac desk / environment / other sensor modules wirelessly connect to # Pressac Smart Gateway (https://www.pressac.com/smart-gateway/) uses AMQP or MQTT protocol to push data to MS Azure IOT Hub via internet # Local Node-RED docker container (default hostname: node-red) connects to the same Azure IOT hub via AMQP over websocket (using "Azure IoT Hub Receiver" https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) -# Engine module (instance of this driver) connects to Node-RED via websockets. Typically ws://node-red:1880/ws/pressac/ +# Initial setup: Install the Azure IOT Hub module to the node-red docker container by running: +# - docker exec -it node-red npm install node-red-contrib-azure-iot-hub +# - visit http://:1880 to configure node-red: +# - create an "Azure IoT Hub Receiver" node. Connect it it to your IOT Hub by setting the connectionstring (see heading "Reading all messages received into Azure IoT Hub": https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) +# - create a "websocket output" node and connect the output of the Azure node to the input of the websocket node +# - edit the websocket node and set the Type to be "listen on" and Path to be "/ws/pressac" +# Engine module (instance of this driver) connects to Node-RED via websockets. Typically "ws://node-red:1880/ws/pressac/" class Pressac::Sensors::WsProtocol include ::Orchestrator::Constants @@ -161,4 +167,4 @@ def on_close(event) def on_error(error) logger.warn "Websocket error: #{error.message}" end -end \ No newline at end of file +end From 525ab5016a6314e5e72073f4a0affb72af8e9006 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 24 Oct 2019 23:56:48 +0800 Subject: [PATCH 1506/1752] fix(office2/events/availabile?): icaluid for comparison (not id) --- lib/microsoft/office2/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 967eef48..3103e328 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -108,7 +108,7 @@ def get_bookings(mailboxes:, calendargroup_id: nil, calendar_id: nil, options:{} # Go through each booking and extract more info from it bookings.each_with_index do |booking, i| bookings[i] = Microsoft::Office2::Event.new(client: self, event: booking, available_to: options[:available_to], available_from: options[:available_from]).event - is_available = false if !bookings[i]['is_free'] && !options[:ignore_bookings].include?(bookings[i]['id']) + is_available = false if !bookings[i]['is_free'] && !options[:ignore_bookings].include?(bookings[i]['icaluid']) end bookings_by_room[mailboxes[res['id'].to_i]] = {available: is_available, bookings: bookings} end From b38295c0f40368ebf6c008b1094f1e2bf1074090 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 25 Oct 2019 12:45:10 +0800 Subject: [PATCH 1507/1752] fix(pressac/desk/sensor): fix broken mock() --- modules/pressac/sensors/ws_protocol.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 3eec5b9a..9d01a3f7 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -50,9 +50,9 @@ def disconnected def mock(sensor, occupied) gateway = which_gateway(sensor) - self[:gateways][gateway][:busy_desks] = occuupied ? self[:gateways][gateway][:busy_desks] | [sensor] : self[:gateways][gateway][:busy_desks] - sensor - self[:gateways][gateway][:free_desks] = occuupied ? self[:gateways][gateway][:free_desks] - sensor : self[:gateways][gateway][:free_desks] | [sensor] - self[:gateways][gateway][sensor_name] = self[gateway] = { + self[:gateways][gateway][:busy_desks] = occupied ? self[:gateways][gateway][:busy_desks] | [sensor] : self[:gateways][gateway][:busy_desks] - [sensor] + self[:gateways][gateway][:free_desks] = occupied ? self[:gateways][gateway][:free_desks] - [sensor] : self[:gateways][gateway][:free_desks] | [sensor] + self[:gateways][gateway][sensor] = self[gateway] = { id: 'mock_data', name: sensor, motion: occupied, @@ -161,4 +161,4 @@ def on_close(event) def on_error(error) logger.warn "Websocket error: #{error.message}" end -end \ No newline at end of file +end From 334e3ce3f83dccfe05f59a133eff67af55894465 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 25 Oct 2019 13:15:02 +0800 Subject: [PATCH 1508/1752] fix(pressac/desk/logic): mismatched setting name sensor_name_to_desk_mappings --- modules/pressac/desk_management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index fdd1da3a..c8999dad 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -17,7 +17,7 @@ class ::Pressac::DeskManagement "zone-xxx" => ["pressac_gateway_name_1"], "zone-zzz" => ["pressac_gateway_name_2", "pressac_gateway_name_3"] }, - sensor_to_desk_mappings: { + sensor_name_to_desk_mappings: { "Note" => "This mapping is optional. If not present, the sensor NAME will be used and must match SVG map IDs", "Desk01" => "table-SYD.2.17.A", "Desk03" => "table-SYD.2.17.B" @@ -179,7 +179,7 @@ def persist_current_status define_setting(:zzz_status, status) end - # Transform an array of Sensor Names to SVG Map IDs, IF the user has specified a mapping in settings(sensor_to_desk_mappings) + # Transform an array of Sensor Names to SVG Map IDs, IF the user has specified a mapping in settings(sensor_name_to_desk_mappings) def id(array) return [] if array.nil? array.map { |i| @desk_ids[i] || i } From a2b6718940d3bc3a007365b836dd7f540989eff6 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 25 Oct 2019 15:01:22 +0800 Subject: [PATCH 1509/1752] feature(node-RED): Add Node-RED websocket driver which only logs recieved messages --- modules/node_red/websocket.rb | 80 +++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 modules/node_red/websocket.rb diff --git a/modules/node_red/websocket.rb b/modules/node_red/websocket.rb new file mode 100644 index 00000000..ea281ebf --- /dev/null +++ b/modules/node_red/websocket.rb @@ -0,0 +1,80 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'protocols/websocket' + +module NodeRed; end +module NodeRed::Websocket; end + +# Data flow: +# ========== +# Local Node-RED docker container (default hostname: node-red) connects to the same Azure IOT hub via AMQP over websocket (using "Azure IoT Hub Receiver" https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) +# Engine module (instance of this driver) connects to Node-RED via websockets. Typically "ws://node-red:1880/ws/pressac/" + +class NodeRed::Websocket + include ::Orchestrator::Constants + + descriptive_name 'Node-RED Websocket' + generic_name :Websocket + tcp_port 1880 + wait_response false + default_settings({ + websocket_path: '/ws/', + }) + + def on_load + on_update + end + + # Called after dependency reload and settings updates + def on_update + @ws_path = setting('websocket_path') + end + + def connected + new_websocket_client + end + + def disconnected + end + + + protected + + def new_websocket_client + @ws = Protocols::Websocket.new(self, "ws://#{remote_address + @ws_path}") + @ws.start + end + + def received(data, resolve, command) + @ws.parse(data) + :success + rescue => e + logger.print_error(e, 'parsing websocket data') + disconnect + :abort + end + + # ==================== + # Websocket callbacks: + # ==================== + + # websocket ready + def on_open + logger.debug { "Websocket connected" } + end + + def on_message(raw_string) + logger.debug { "received: #{raw_string}" } + sensor = JSON.parse(raw_string, symbolize_names: true) + end + + # connection is closing + def on_close(event) + logger.debug { "Websocket closing... #{event.code} #{event.reason}" } + end + + def on_error(error) + logger.warn "Websocket error: #{error.message}" + end +end From 4da9ac918f827e948652a577f6380e55d724f63c Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 25 Oct 2019 15:29:12 +0800 Subject: [PATCH 1510/1752] feature(Node-RED): Add send_text and send_binary --- modules/node_red/websocket.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/node_red/websocket.rb b/modules/node_red/websocket.rb index ea281ebf..da22ead9 100644 --- a/modules/node_red/websocket.rb +++ b/modules/node_red/websocket.rb @@ -38,6 +38,13 @@ def connected def disconnected end + def send_text(message) + @ws.text(message) + end + + def send_binary(array) + @ws.binary(array) + end protected From e17f551d9e73c3eccda9d648f72443607055c585 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 25 Oct 2019 16:56:12 +0800 Subject: [PATCH 1511/1752] feature(node-red/websocket): expose last message received as a status var. --- modules/node_red/websocket.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/node_red/websocket.rb b/modules/node_red/websocket.rb index da22ead9..7d504f1d 100644 --- a/modules/node_red/websocket.rb +++ b/modules/node_red/websocket.rb @@ -73,7 +73,7 @@ def on_open def on_message(raw_string) logger.debug { "received: #{raw_string}" } - sensor = JSON.parse(raw_string, symbolize_names: true) + self[:message_received] = raw_string end # connection is closing From 5d74b660c58979a062d227b6b338a5d7656f9ec1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 29 Oct 2019 09:34:10 +1100 Subject: [PATCH 1512/1752] fix(cisco sx VC): camera zoom and presentation mode --- modules/cisco/tele_presence/sx_camera_common.rb | 2 +- modules/cisco/tele_presence/sx_series_common.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_camera_common.rb b/modules/cisco/tele_presence/sx_camera_common.rb index 47d358de..9c02f49e 100644 --- a/modules/cisco/tele_presence/sx_camera_common.rb +++ b/modules/cisco/tele_presence/sx_camera_common.rb @@ -118,7 +118,7 @@ def tilt(value) def zoom(position) val = in_range(position.to_i, self[:zoom_max], self[:zoom_min]) - command('Cameras Camera PositionSet', params({ + command('Camera PositionSet', params({ :CameraId => @index, :Zoom => val }), name: :zoom).then do diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index ad426eeb..87ed5dd4 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -366,6 +366,7 @@ def received(data, resolve, command) end self[:incall] = false + self[:presentation] = :none self[:call_status] = {} @last_call_id = nil @call_status = nil From 333182f85e9e5696ebbfda5258b8246618d78792 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 29 Oct 2019 14:42:33 +1100 Subject: [PATCH 1513/1752] fix(pressac sensors): ensure status variables are read only --- modules/pressac/sensors/ws_protocol.rb | 58 ++++++++++++++++---------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 0e749692..d3a7d03f 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -9,8 +9,8 @@ module Pressac::Sensors; end # Data flow: # ========== -# Pressac desk / environment / other sensor modules wirelessly connect to -# Pressac Smart Gateway (https://www.pressac.com/smart-gateway/) uses AMQP or MQTT protocol to push data to MS Azure IOT Hub via internet +# Pressac desk / environment / other sensor modules wirelessly connect to +# Pressac Smart Gateway (https://www.pressac.com/smart-gateway/) uses AMQP or MQTT protocol to push data to MS Azure IOT Hub via internet # Local Node-RED docker container (default hostname: node-red) connects to the same Azure IOT hub via AMQP over websocket (using "Azure IoT Hub Receiver" https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) # Initial setup: Install the Azure IOT Hub module to the node-red docker container by running: # - docker exec -it node-red npm install node-red-contrib-azure-iot-hub @@ -34,16 +34,24 @@ class Pressac::Sensors::WsProtocol def on_load @busy_desks = {} @free_desks = {} - status = setting(:status) || {} - self[:gateways] = status[:gateways] || {} - self[:last_update] = status[:last_update] || "Never" - self[:environment] = {} # Environment sensor values (temp, humidity) - + + # Environment sensor values (temp, humidity) + @environment = {} + self[:environment] = {} + on_update end # Called after dependency reload and settings updates def on_update + status = setting(:status) || {} + + @gateways = status[:gateways] || {} + self[:gateways] = @gateways.dup + + @last_update = status[:last_update] || "Never" + self[:last_update] = @last_update.dup + @ws_path = setting('websocket_path') end @@ -56,9 +64,10 @@ def disconnected def mock(sensor, occupied) gateway = which_gateway(sensor) - self[:gateways][gateway][:busy_desks] = occupied ? self[:gateways][gateway][:busy_desks] | [sensor] : self[:gateways][gateway][:busy_desks] - [sensor] - self[:gateways][gateway][:free_desks] = occupied ? self[:gateways][gateway][:free_desks] - [sensor] : self[:gateways][gateway][:free_desks] | [sensor] - self[:gateways][gateway][sensor] = self[gateway] = { + + @gateways[gateway][:busy_desks] = occupied ? @gateways[gateway][:busy_desks] | [sensor] : @gateways[gateway][:busy_desks] - [sensor] + @gateways[gateway][:free_desks] = occupied ? @gateways[gateway][:free_desks] - [sensor] : @gateways[gateway][:free_desks] | [sensor] + @gateways[gateway][sensor] = self[gateway] = { id: 'mock_data', name: sensor, motion: occupied, @@ -67,10 +76,12 @@ def mock(sensor, occupied) timestamp: Time.now.to_s, gateway: gateway } + + self[:gateways] = @gateways.deep_dup end def which_gateway(sensor) - self[:gateways]&.each do |g,sensors| + @gateways.each do |g, sensors| return g if sensors.include? sensor end end @@ -114,8 +125,8 @@ def on_message(raw_string) @free_desks[gateway] ||= [] @busy_desks[gateway] ||= [] - self[:gateways][gateway] ||= {} - + @gateways[gateway] ||= {} + if occupancy @busy_desks[gateway] = @busy_desks[gateway] | [sensor_name] @free_desks[gateway] = @free_desks[gateway] - [sensor_name] @@ -123,13 +134,13 @@ def on_message(raw_string) @busy_desks[gateway] = @busy_desks[gateway] - [sensor_name] @free_desks[gateway] = @free_desks[gateway] | [sensor_name] end - self[:gateways][gateway][:busy_desks] = @busy_desks[gateway] - self[:gateways][gateway][:free_desks] = @free_desks[gateway] - self[:gateways][gateway][:all_desks] = @busy_desks[gateway] + @free_desks[gateway] - - # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), + @gateways[gateway][:busy_desks] = @busy_desks[gateway] + @gateways[gateway][:free_desks] = @free_desks[gateway] + @gateways[gateway][:all_desks] = @busy_desks[gateway] + @free_desks[gateway] + + # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), # AND as the latest notification from this gateway (self[gateway]) (for the purpose of the DeskManagent logic upstream) - self[:gateways][gateway][sensor_name] = self[gateway] = { + @gateways[gateway][sensor_name] = self[gateway] = { id: sensor[:deviceId] || sensor[:deviceid], name: sensor_name, motion: occupancy, @@ -139,22 +150,25 @@ def on_message(raw_string) gateway: gateway } #signal_status(gateway) - self[:gateways][gateway][:last_update] = sensor[:timestamp] + @gateways[gateway][:last_update] = sensor[:timestamp] + self[:gateways] = @gateways.deep_dup when 'CO2-Temperature-and-Humidity' - self[:environment][sensor[:devicename]] = { + @environment[sensor[:devicename]] = { temp: sensor[:temperature], humidity: sensor[:humidity], concentration: sensor[:concentration], dbm: sensor[:dbm], id: sensor[:deviceid] } + self[:environment] = @environment.deep_dup end self[:last_update] = Time.now.in_time_zone($TZ).to_s + # Save the current status to database, so that it can retrieved when engine restarts status = { last_update: self[:last_update], - gateways: self[:gateways] + gateways: @gateways.deep_dup } define_setting(:status, status) end From 0030b9b435538c2a1e99526895d017168851b5fa Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 29 Oct 2019 12:23:19 +0800 Subject: [PATCH 1514/1752] fix(o365 RBP): remove booking_ from status variables, to match ngx-bookings >=v1.0.0 --- modules/aca/o365_booking_panel.rb | 119 ++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 41 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index f428db03..5599292f 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -44,32 +44,42 @@ def on_update self[:control_url] = setting(:booking_control_url) || system.config.support_url self[:timeout] = setting(:timeout) - self[:booking_cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' - self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) - self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) - self[:booking_controls] = setting(:booking_controls) - self[:booking_catering] = setting(:booking_catering) - self[:booking_hide_details] = setting(:booking_hide_details) - self[:booking_hide_availability] = setting(:booking_hide_availability) - self[:booking_hide_user] = setting(:booking_hide_user) - self[:booking_hide_modal] = setting(:booking_hide_modal) - self[:booking_hide_title] = setting(:booking_hide_title) - self[:booking_hide_description] = setting(:booking_hide_description) - self[:booking_hide_timeline] = setting(:booking_hide_timeline) - self[:booking_set_host] = setting(:booking_set_host) - self[:booking_set_title] = setting(:booking_set_title) - self[:booking_set_ext] = setting(:booking_set_ext) - self[:booking_search_user] = setting(:booking_search_user) - self[:booking_disable_future] = setting(:booking_disable_future) - self[:booking_min_duration] = setting(:booking_min_duration) - self[:booking_max_duration] = setting(:booking_max_duration) - self[:booking_duration_step] = setting(:booking_duration_step) - self[:booking_endable] = setting(:booking_endable) - self[:booking_ask_cancel] = setting(:booking_ask_cancel) - self[:booking_ask_end] = setting(:booking_ask_end) - self[:booking_default_title] = setting(:booking_default_title) || "On the spot booking" - self[:booking_select_free] = setting(:booking_select_free) - self[:booking_hide_all] = setting(:booking_hide_all) || false + self[:disabled] = setting(:booking_disabled) + self[:cancel_timeout] = UV::Scheduler.parse_duration(setting(:cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' + self[:cancel_email_message] = setting(:booking_cancel_email_message) + self[:timeout_email_message] = setting(:booking_timeout_email_message) + + self[:controlable] = setting(:booking_can_control) + self[:searchable] = setting(:booking_can_search) + + self[:catering] = setting(:booking_catering) + self[:hide_details] = setting(:booking_hide_details) + self[:hide_availability] = setting(:booking_hide_availability) + self[:hide_user] = setting(:booking_hide_user) + self[:hide_modal] = setting(:booking_hide_modal) + + + self[:hide_all] = setting(:booking_hide_all) + self[:hide_title] = setting(:booking_hide_title) + self[:hide_details] = setting(:booking_hide_details) + self[:hide_description] = setting(:booking_hide_description) + self[:hide_availability] = setting(:booking_hide_availability) + + self[:hide_timeline] = setting(:booking_hide_timeline) + self[:set_host] = setting(:booking_set_host) + self[:set_title] = setting(:booking_set_title) + self[:set_ext] = setting(:booking_set_ext) + self[:search_user] = setting(:booking_search_user) + self[:disable_future] = setting(:booking_disable_future) + self[:min_duration] = setting(:booking_min_duration) + self[:max_duration] = setting(:booking_max_duration) + self[:duration_step] = setting(:booking_duration_step) + self[:endable] = setting(:booking_endable) + self[:ask_cancel] = setting(:booking_ask_cancel) + self[:ask_end] = setting(:booking_ask_end) + self[:default_title] = setting(:booking_default_title) || "On the spot booking" + self[:select_free] = setting(:booking_select_free) + self[:hide_all] = setting(:booking_hide_all) || false office_client_id = setting(:office_client_id) || ENV['OFFICE_CLIENT_ID'] office_secret = setting(:office_secret) || ENV["OFFICE_CLIENT_SECRET"] @@ -88,7 +98,7 @@ def on_update self[:last_meeting_started] = setting(:last_meeting_started) self[:cancel_meeting_after] = setting(:cancel_meeting_after) - + schedule.clear schedule.in(rand(10000)) { fetch_bookings } fetch_interval = UV::Scheduler.parse_duration(setting(:update_every) || '5m') + rand(10000) @@ -109,17 +119,39 @@ def create_meeting(params) end logger.debug "RBP>#{@office_room}>CREATE>INPUT:\n #{params}" + + host_email = params.dig(:host, :email) + mailbox = host_email || @office_room + calendargroup_id = nil + calendar_id = nil + + booking_options = { + subject: params[:title] || setting(:booking_default_title), + #location: {} + attendees: [ {email: @office_room, type: "resource"} ], + timezone: ENV['TZ'], + extensions: { aca_booking: true } + } + if ENV['O365_PROXY_USER_CALENDARS'] + room_domain = @office_room.split('@').last + user_domain = current_user.email.split('@').last + + calendar_proxy = host_email ? User.find_by_email(current_user.authority_id, host_email)&.calendar_proxy : nil + mailbox = calendar_proxy&.dig(:account) if calendar_proxy&.dig(:account) + calendargroup_id = calendar_proxy&.dig(:calendargroup_id) + calendar_id = calendar_proxy&.dig(:calendar_id) + + booking_options[:attendees] << params[:host] if params[:host] + booking_options[:extensions].merge!( { aca_proxied_organizer: [params.dig(:host, :name), host_email] }) + end begin result = @client.create_booking( - mailbox: params.dig(:host, :email) || @office_room, + mailbox: mailbox, + calendargroup_id: calendargroup_id, + calendar_id: calendar_id, start_param: epoch(params[:start]), - end_param: epoch(params[:end]), - options: { - subject: params[:title] || setting(:booking_default_title), - attendees: [ {email: @office_room, type: "resource"} ], - timezone: ENV['TZ'] - } - ) + end_param: epoch(params[:end]), + options: booking_options ) rescue Exception => e logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e.message}\n#{e.backtrace.join("\n")}" raise e @@ -145,7 +177,7 @@ def cancel_meeting(start_time, reason = "unknown reason") start_epoch = Time.parse(start_time).to_i ms_epoch = start_epoch * 1000 too_early_to_cancel = now < start_epoch - too_late_to_cancel = self[:booking_cancel_timeout] ? (now > (start_epoch + self[:booking_cancel_timeout] + 180)) : false # "180": allow up to 3mins of slippage, in case endpoint is not NTP synced + too_late_to_cancel = self[:cancel_timeout] ? (now > (start_epoch + self[:cancel_timeout] + 180)) : false # "180": allow up to 3mins of slippage, in case endpoint is not NTP synced bookings_to_cancel = bookings_with_start_time(start_epoch) if bookings_to_cancel > 1 @@ -251,9 +283,9 @@ def delete_o365_booking(delete_start_epoch, reason) case reason when RBP_AUTOCANCEL_TRIGGERED - response = delete_or_decline(booking, self[:booking_timeout_email_message]) + response = delete_or_decline(booking, self[:timeout_email_message]) when RBP_STOP_PRESSED - response = delete_or_decline(booking, self[:booking_cancel_email_message]) + response = delete_or_decline(booking, self[:cancel_email_message]) else response = delete_or_decline(booking, "The booking was cancelled due to \"#{reason}\"") end @@ -272,9 +304,13 @@ def expose_bookings(bookings) end_utc = tz.parse(booking['end']['dateTime']).utc start_time = start_utc.iso8601 # output looks like: "2019-05-21T15:50:00Z+08:00" end_time = end_utc.iso8601 - - name = booking.dig('organizer',:name) || booking.dig('attendees',0,'name') - email = booking.dig('organizer',:email) || booking.dig('attendees',0,'email') + attendees = booking['attendees'] + name = booking.dig('organizer',:name) || "Private" + email = booking.dig('organizer',:email) || "Private" + if ENV['O365_PROXY_USER_CALENDARS'] + name = booking.dig('attendees',1,:name) || "Private" + email = booking.dig('attendees',1,:email) || "Private" + end subject = booking['subject'] if ['private','confidential'].include?(booking['sensitivity']) @@ -291,6 +327,7 @@ def expose_bookings(bookings) :owner => name, :email => email, :organizer => {:name => name, :email => email}, + :attendees => attendees, :isAllDay => booking['isAllDay'] }) } From 02ab3d3040a0ffd0ed6e275b844abb98786de57b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 30 Oct 2019 10:01:22 +1100 Subject: [PATCH 1515/1752] feat(floorsense desks): init commit --- modules/floorsense/desks.rb | 112 ++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 modules/floorsense/desks.rb diff --git a/modules/floorsense/desks.rb b/modules/floorsense/desks.rb new file mode 100644 index 00000000..87effa8f --- /dev/null +++ b/modules/floorsense/desks.rb @@ -0,0 +1,112 @@ +require 'jwt' + +module Floorsense; end + +# Documentation: https://documenter.getpostman.com/view/8843075/SVmwvctF?version=latest#3bfbb050-722d-4433-889a-8793fa90af9c + +class Floorsense::Desks + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :service + descriptive_name 'Floorsense Desk Tracking' + generic_name :DeskManagement + + # HTTP keepalive + keepalive false + + default_settings({ + username: "srvc_acct", + password: "password!" + }) + + def on_load + @auth_token = '' + @auth_expiry = 1.minute.ago + on_update + end + + def on_update + username = setting(:username) + password = setting(:password) + @credentials = URI.encode_www_form("username" => username, "password" => password) + + # { "floor_id" => "zone_id" } + @floor_mappings = setting(:floor_mappings) + end + + def expire_token! + @auth_expiry = 1.minute.ago + end + + def token_expired? + @auth_expiry < Time.now + end + + def get_token + return @auth_token unless token_expired? + + response = post("/restapi/login", body: @credentials, headers: { + "Content-Type" => "application/x-www-form-urlencoded", + "Accept" => "application/json" + }).value + + data = response.body + logger.debug { "received login response #{data}" } + + if (200...300).include?(response.status) + resp = JSON.parse(data, symbolize_names: true) + token = resp[:info][:token] + payload, header = JWT.decode(token, nil, false) + @auth_expiry = (Time.at payload["exp"]) - 5.minutes + @auth_token = "Bearer #{token}" + else + case response.status + when 401 + resp = JSON.parse(data, symbolize_names: true) + logger.warn "#{resp[:message]} (#{resp[:code]})" + else + logger.error "authentication failed with HTTP #{response.status}" + end + raise "failed to obtain access token" + end + end + + def desks(group_id) + token = get_token + uri = "/restapi/floorplan-desk?planid=#{group_id}" + + response = get(uri, headers: { + "Accept" => "application/json", + "Authorization" => token + }).value + + if (200...300).include?(response.status) + resp = JSON.parse(response.body, symbolize_names: true) + resp[:info] + else + expire_token! if response.status == 401 + raise "unexpected response #{response.status}\n#{response.body}" + end + end + + def locate(user : String) + token = get_token + uri = "/restapi/user-locate?name=#{URI.encode_www_form_component user}" + + response = get(uri, headers: { + "Accept" => "application/json", + "Authorization" => token + }).value + + if (200...300).include?(response.status) + resp = JSON.parse(response.body, symbolize_names: true) + # Select users where there is a desk key found + resp[:info].select { |user| user[:key] } + else + expire_token! if response.status == 401 + raise "unexpected response #{response.status}\n#{response.body}" + end + end +end From c58193f1f7184da2ddfa5ddf936050ed1315ccea Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 30 Oct 2019 10:16:02 +1100 Subject: [PATCH 1516/1752] fix(floorsense): remove crystal type hint --- modules/floorsense/desks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/floorsense/desks.rb b/modules/floorsense/desks.rb index 87effa8f..0e5281f9 100644 --- a/modules/floorsense/desks.rb +++ b/modules/floorsense/desks.rb @@ -91,7 +91,7 @@ def desks(group_id) end end - def locate(user : String) + def locate(user) token = get_token uri = "/restapi/user-locate?name=#{URI.encode_www_form_component user}" From f9663881239704b6893b20644797148a7ef4a1ec Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 30 Oct 2019 16:04:45 +1100 Subject: [PATCH 1517/1752] fix(floorsense): refactor how the requests are made also exposes state now --- modules/floorsense/desks.rb | 138 +++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 42 deletions(-) diff --git a/modules/floorsense/desks.rb b/modules/floorsense/desks.rb index 0e5281f9..d77665d6 100644 --- a/modules/floorsense/desks.rb +++ b/modules/floorsense/desks.rb @@ -18,7 +18,11 @@ class Floorsense::Desks default_settings({ username: "srvc_acct", - password: "password!" + password: "password!", + floor_mappings: { + zone_id: :group_id, + zone_id: [:group_id, :group_id] + } }) def on_load @@ -32,8 +36,15 @@ def on_update password = setting(:password) @credentials = URI.encode_www_form("username" => username, "password" => password) - # { "floor_id" => "zone_id" } - @floor_mappings = setting(:floor_mappings) + # { "zone_id" => "floor_id" } + @floor_mappings = setting(:floor_mappings) || {} + + # desk_id => [zone_id, floor_id, cid] + # cid required for desk status + @desk_mappings = {} + + schedule.clear + schedule.in("5s") { fetch_desk_state } end def expire_token! @@ -47,66 +58,109 @@ def token_expired? def get_token return @auth_token unless token_expired? - response = post("/restapi/login", body: @credentials, headers: { + post("/restapi/login", body: @credentials, headers: { "Content-Type" => "application/x-www-form-urlencoded", "Accept" => "application/json" - }).value - - data = response.body - logger.debug { "received login response #{data}" } - - if (200...300).include?(response.status) - resp = JSON.parse(data, symbolize_names: true) - token = resp[:info][:token] - payload, header = JWT.decode(token, nil, false) - @auth_expiry = (Time.at payload["exp"]) - 5.minutes - @auth_token = "Bearer #{token}" - else - case response.status - when 401 + }) { |response| + data = response.body + logger.debug { "received login response #{data}" } + + if (200...300).include?(response.status) resp = JSON.parse(data, symbolize_names: true) - logger.warn "#{resp[:message]} (#{resp[:code]})" + token = resp[:info][:token] + payload, header = JWT.decode(token, nil, false) + @auth_expiry = (Time.at payload["exp"]) - 5.minutes + @auth_token = "Bearer #{token}" else - logger.error "authentication failed with HTTP #{response.status}" + case response.status + when 401 + resp = JSON.parse(data, symbolize_names: true) + logger.warn "#{resp[:message]} (#{resp[:code]})" + else + logger.error "authentication failed with HTTP #{response.status}" + end + raise "failed to obtain access token" end - raise "failed to obtain access token" - end + }.value end def desks(group_id) token = get_token uri = "/restapi/floorplan-desk?planid=#{group_id}" - response = get(uri, headers: { + get(uri, headers: { "Accept" => "application/json", "Authorization" => token - }).value - - if (200...300).include?(response.status) - resp = JSON.parse(response.body, symbolize_names: true) - resp[:info] - else - expire_token! if response.status == 401 - raise "unexpected response #{response.status}\n#{response.body}" - end + }) { |response| + if (200...300).include?(response.status) + resp = JSON.parse(response.body, symbolize_names: true) + resp[:info] + else + expire_token! if response.status == 401 + raise "unexpected response #{response.status}\n#{response.body}" + end + }.value end def locate(user) token = get_token uri = "/restapi/user-locate?name=#{URI.encode_www_form_component user}" - response = get(uri, headers: { + get(uri, headers: { "Accept" => "application/json", "Authorization" => token - }).value - - if (200...300).include?(response.status) - resp = JSON.parse(response.body, symbolize_names: true) - # Select users where there is a desk key found - resp[:info].select { |user| user[:key] } - else - expire_token! if response.status == 401 - raise "unexpected response #{response.status}\n#{response.body}" + }) { |response| + if (200...300).include?(response.status) + resp = JSON.parse(response.body, symbolize_names: true) + # Select users where there is a desk key found + resp[:info].select { |user| user[:key] } + else + expire_token! if response.status == 401 + raise "unexpected response #{response.status}\n#{response.body}" + end + }.value + end + + def fetch_desk_state + desk_mappings = {} + + @floor_mappings.each do |zone_id, group| + group_ids = Array(group) + all_desk_ids = [] + desks_in_use = [] + desks_reserved = [] + + # Grab the details for this floor + group_ids.each do |group_id| + desks(group_id).each do |details| + desk_id = details[:key] + controller = details[:cid] + occupied = details[:occupied] + reserved = details[:reserved] + + desk_mappings[desk_id] = [zone_id, group_id, controller] + all_desk_ids << desk_id + desks_in_use << desk_id if occupied + desks_reserved << desk_id if reserved && !occupied + end + end + + # Make the summaries available to the frontend + self[zone_id] = desks_in_use + self["#{zone_id}:reserved"] = desks_reserved + self["#{zone_id}:desk_ids"] = all_desk_ids + + occupied_count = desks_in_use.size + desks_reserved.size + self["#{zone_id}:occupied_count"] = occupied_count + self["#{zone_id}:free_count"] = all_desk_ids.size - occupied_count + self["#{zone_id}:desk_count"] = all_desk_ids.size end + + @desk_mappings = desk_mappings + rescue => error + logger.print_error error, 'fetching desk state' + ensure + schedule.clear + schedule.in("5s") { fetch_desk_state } end end From 7717378d4ab4dbf01eaeb78227472b310c9ee2ea Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 12:57:04 +0800 Subject: [PATCH 1518/1752] fix(pressac/desk/logic): persist pending state prior to actioning, to precent state loss is engine stops during action. --- modules/pressac/desk_management.rb | 18 ++++++++++-------- modules/pressac/sensors/ws_protocol.rb | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index c8999dad..1e8a72d9 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -111,12 +111,12 @@ def update_desk(notification) desk = notification.value desk_name = id([desk[:name].to_sym])&.first.to_s - zone = @which_zone[desk[:gateway].to_s] + zone = @which_zone[desk[:gateway].to_s] return unless zone if current_state[:motion] && !self[zone].include?(desk_name) # if motion, and desk is currently free - @pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} - @pending_free.delete(desk_name) + @pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} + @pending_free.delete(desk_name) elsif !current_state[:motion] && self[zone].include?(desk_name) # if no motion, and desk is currently busy @pending_free[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} @pending_busy.delete(desk_name) @@ -128,6 +128,7 @@ def update_desk(notification) end def determine_desk_status + persist_current_status new_status = {} @zones.each do |zone, gateways| new_status[zone] = {} @@ -148,9 +149,10 @@ def determine_desk_status end end expose_desks(new_status) - self[:pending_busy] = @pending_busy = {} - self[:pending_free] = @pending_free = {} - persist_current_status + @pending_busy = {} + @pending_free = {} + self[:pending_busy] = {} + self[:pending_free] = {} end def expose_desks(new_status) @@ -158,8 +160,8 @@ def expose_desks(new_status) zone = z.to_sym self[zone] ||= [] self[zone] = self[zone] - desks[:free] | desks[:busy] - self[z+':desk_ids'] = self[z+':desk_ids'] | desks[:free] | desks[:busy] - self[z+':desk_count'] = self[z+':desk_ids'].count + self[z+':desk_ids'] = self[z+':desk_ids'] | desks[:free] | desks[:busy] + self[z+':desk_count'] = self[z+':desk_ids'].count self[z+':occupied_count'] = self[zone].count end end diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index d3a7d03f..80e3e313 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -140,7 +140,7 @@ def on_message(raw_string) # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), # AND as the latest notification from this gateway (self[gateway]) (for the purpose of the DeskManagent logic upstream) - @gateways[gateway][sensor_name] = self[gateway] = { + @gateways[gateway][sensor_name] = { id: sensor[:deviceId] || sensor[:deviceid], name: sensor_name, motion: occupancy, @@ -151,6 +151,7 @@ def on_message(raw_string) } #signal_status(gateway) @gateways[gateway][:last_update] = sensor[:timestamp] + self[gateway] = @gateways[gateway][sensor_name].dup self[:gateways] = @gateways.deep_dup when 'CO2-Temperature-and-Humidity' @environment[sensor[:devicename]] = { From 7e36f8a37603b55ecad5609a1f07a8f9a8b45323 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 14:27:49 +0800 Subject: [PATCH 1519/1752] fix(pressac/desk/logic): Add debug status for tracking changes --- modules/pressac/desk_management.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 1e8a72d9..76ef0a1a 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -112,6 +112,7 @@ def update_desk(notification) desk_name = id([desk[:name].to_sym])&.first.to_s zone = @which_zone[desk[:gateway].to_s] + logger.debug "===Updating: #{desk_name} in #{zone}" return unless zone if current_state[:motion] && !self[zone].include?(desk_name) # if motion, and desk is currently free @@ -148,6 +149,9 @@ def determine_desk_status new_status[zone][:free] |= [desk] if zone end end + + self[:new_status] = @new_status.deep_dup + expose_desks(new_status) @pending_busy = {} @pending_free = {} From 360b9c1c99c8b714dec2f4075192b63c232d3077 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 14:31:58 +0800 Subject: [PATCH 1520/1752] fix(pressac/desk/logic): fix var name error in debug output --- modules/pressac/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 76ef0a1a..e573de9c 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -150,7 +150,7 @@ def determine_desk_status end end - self[:new_status] = @new_status.deep_dup + self[:new_status] = new_status.deep_dup expose_desks(new_status) @pending_busy = {} From aabf41a79e77a0d60794c5108e897406a2a616dc Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 15:03:48 +0800 Subject: [PATCH 1521/1752] feature(pressac/desk/sensors): Add list_stale_sensors() --- modules/pressac/sensors/ws_protocol.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 80e3e313..e6ae52b4 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -29,6 +29,7 @@ class Pressac::Sensors::WsProtocol wait_response false default_settings({ websocket_path: '/ws/pressac/', + stale_sensor_threshold: '20m' }) def on_load @@ -44,7 +45,7 @@ def on_load # Called after dependency reload and settings updates def on_update - status = setting(:status) || {} + status = setting('status') || {} @gateways = status[:gateways] || {} self[:gateways] = @gateways.dup @@ -53,6 +54,7 @@ def on_update self[:last_update] = @last_update.dup @ws_path = setting('websocket_path') + @stale_sensor_threshold = UV::Scheduler.parse_duration(setting('stale_sensor_threshold') || '20m') / 1000 end def connected @@ -86,6 +88,16 @@ def which_gateway(sensor) end end + def list_stale_sensors + stale = [] + @gateways.each do |g, sensors| + sensors.each do |sensor| + stale << {sensor[:name]: sensor[:last_update] } if Time.now.to_i - sensor.last_update_epoch > @stale_sensor_threshold + end + end + logger.debug "Sensors that have not posted updates in the past #{setting('stale_sensor_threshold')}:\n#{stale}" + end + protected @@ -147,6 +159,8 @@ def on_message(raw_string) voltage: sensor[:supplyVoltage][:value] || sensor[:supplyVoltage], location: sensor[:location], timestamp: sensor[:timestamp], + last_update: Time.now.in_time_zone($TZ).to_s, + last_update_epoch: Time.now.to_i gateway: gateway } #signal_status(gateway) From 94a7a9afdda8048817a8e2b812d2e96172cee78d Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 15:07:06 +0800 Subject: [PATCH 1522/1752] fix(pressac/desk/sensors): syntax --- modules/pressac/sensors/ws_protocol.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index e6ae52b4..3a9e6968 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -92,7 +92,7 @@ def list_stale_sensors stale = [] @gateways.each do |g, sensors| sensors.each do |sensor| - stale << {sensor[:name]: sensor[:last_update] } if Time.now.to_i - sensor.last_update_epoch > @stale_sensor_threshold + stale << {sensor[:name] => sensor[:last_update]} if Time.now.to_i - sensor.last_update_epoch > @stale_sensor_threshold end end logger.debug "Sensors that have not posted updates in the past #{setting('stale_sensor_threshold')}:\n#{stale}" From 190e1f006a0e5824aa88d9987b934e2c9a2c3460 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 15:08:31 +0800 Subject: [PATCH 1523/1752] fix(pressac/desk/sensor): syntax --- modules/pressac/sensors/ws_protocol.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 3a9e6968..f2fb6f61 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -160,7 +160,7 @@ def on_message(raw_string) location: sensor[:location], timestamp: sensor[:timestamp], last_update: Time.now.in_time_zone($TZ).to_s, - last_update_epoch: Time.now.to_i + last_update_epoch: Time.now.to_i, gateway: gateway } #signal_status(gateway) From 9c66579ecb9f5e33a7a2c52a588f0fa9df8863d9 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 15:41:07 +0800 Subject: [PATCH 1524/1752] fix(pressac/desk/sensor): syntax --- modules/pressac/sensors/ws_protocol.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index f2fb6f61..b5faf605 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -92,10 +92,11 @@ def list_stale_sensors stale = [] @gateways.each do |g, sensors| sensors.each do |sensor| - stale << {sensor[:name] => sensor[:last_update]} if Time.now.to_i - sensor.last_update_epoch > @stale_sensor_threshold + stale << {sensor[:name] => sensor[:last_update] || "Unknown"} if Time.now.to_i - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold end end logger.debug "Sensors that have not posted updates in the past #{setting('stale_sensor_threshold')}:\n#{stale}" + self[:stale] = stale end From f59b1ff19c0f9141e2ca3cf924424075b63b77d6 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 15:55:36 +0800 Subject: [PATCH 1525/1752] fix(pressac/desk/sensor): Remove gateway.busy_desk etc. status var, which is no longer used --- modules/pressac/sensors/ws_protocol.rb | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index b5faf605..75cb4640 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -33,9 +33,6 @@ class Pressac::Sensors::WsProtocol }) def on_load - @busy_desks = {} - @free_desks = {} - # Environment sensor values (temp, humidity) @environment = {} self[:environment] = {} @@ -66,9 +63,6 @@ def disconnected def mock(sensor, occupied) gateway = which_gateway(sensor) - - @gateways[gateway][:busy_desks] = occupied ? @gateways[gateway][:busy_desks] | [sensor] : @gateways[gateway][:busy_desks] - [sensor] - @gateways[gateway][:free_desks] = occupied ? @gateways[gateway][:free_desks] - [sensor] : @gateways[gateway][:free_desks] | [sensor] @gateways[gateway][sensor] = self[gateway] = { id: 'mock_data', name: sensor, @@ -136,21 +130,6 @@ def on_message(raw_string) gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym occupancy = sensor[:motionDetected] == true - @free_desks[gateway] ||= [] - @busy_desks[gateway] ||= [] - @gateways[gateway] ||= {} - - if occupancy - @busy_desks[gateway] = @busy_desks[gateway] | [sensor_name] - @free_desks[gateway] = @free_desks[gateway] - [sensor_name] - else - @busy_desks[gateway] = @busy_desks[gateway] - [sensor_name] - @free_desks[gateway] = @free_desks[gateway] | [sensor_name] - end - @gateways[gateway][:busy_desks] = @busy_desks[gateway] - @gateways[gateway][:free_desks] = @free_desks[gateway] - @gateways[gateway][:all_desks] = @busy_desks[gateway] + @free_desks[gateway] - # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), # AND as the latest notification from this gateway (self[gateway]) (for the purpose of the DeskManagent logic upstream) @gateways[gateway][sensor_name] = { @@ -164,8 +143,6 @@ def on_message(raw_string) last_update_epoch: Time.now.to_i, gateway: gateway } - #signal_status(gateway) - @gateways[gateway][:last_update] = sensor[:timestamp] self[gateway] = @gateways[gateway][sensor_name].dup self[:gateways] = @gateways.deep_dup when 'CO2-Temperature-and-Humidity' From 29e66e935fb6a9525b05d3895d85ff22d8661e7c Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 16:10:59 +0800 Subject: [PATCH 1526/1752] fix(pressac/desk/sensor): fix error on receive and list_stale --- modules/pressac/sensors/ws_protocol.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 75cb4640..43d6e10b 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -42,7 +42,7 @@ def on_load # Called after dependency reload and settings updates def on_update - status = setting('status') || {} + status = setting(:status) || {} @gateways = status[:gateways] || {} self[:gateways] = @gateways.dup @@ -84,9 +84,12 @@ def which_gateway(sensor) def list_stale_sensors stale = [] + now = Time.now.to_i @gateways.each do |g, sensors| - sensors.each do |sensor| - stale << {sensor[:name] => sensor[:last_update] || "Unknown"} if Time.now.to_i - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold + sensors.each do |name, sensor| + if now - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold + stale << {name => (sensor[:last_update] || "Unknown")} + end end end logger.debug "Sensors that have not posted updates in the past #{setting('stale_sensor_threshold')}:\n#{stale}" @@ -132,6 +135,7 @@ def on_message(raw_string) # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), # AND as the latest notification from this gateway (self[gateway]) (for the purpose of the DeskManagent logic upstream) + @gateways[gateway] ||= {} @gateways[gateway][sensor_name] = { id: sensor[:deviceId] || sensor[:deviceid], name: sensor_name, From 089dd99f8d090cfef2d2b49fa1c58813425e2380 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 16:36:03 +0800 Subject: [PATCH 1527/1752] feature(pressac/desk/logic): unexpose stale sensors --- modules/pressac/desk_management.rb | 30 +++++++++++++++----------- modules/pressac/sensors/ws_protocol.rb | 3 +++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index e573de9c..3bb2365f 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -70,6 +70,9 @@ def on_update # Subscribe to live updates from each gateway device,index = @hub.split('_') + @subscriptions << system.subscribe(device, index.to_i, :stale) do |notification| + unexpose_unresponsive_desks(notification) + end @zones.each do |zone,gateways| gateways.each do |gateway| @subscriptions << system.subscribe(device, index.to_i, gateway.to_sym) do |notification| @@ -79,7 +82,6 @@ def on_update end schedule.clear schedule.every('30s') { determine_desk_status } - #schedule.every('1h') { unexpose_unresponsive_desks } end # @param zone [String] the engine zone id @@ -191,18 +193,20 @@ def id(array) array.map { |i| @desk_ids[i] || i } end - def unexpose_unresponsive_desks - gateways = system[@hub][:gateways] - gateways.each do |gateway, sensor| - last_update = Time.parse(sensor[:timestamp]) - if Time.now > last_update + 1.hour - desk_name = id([sensor[:name]]).first.to_s - zone = @which_zone[sensor[:gateway]] - self[zone] = self[zone] - [desk_name] - self[zone+':desk_ids'] = self[zone+':desk_ids'] - [desk_name] - self[zone+':desk_count'] = self[zone+':desk_ids'].count - self[zone+':occupied_count'] = self[zone].count - end + def unexpose_unresponsive_desks(notification) + stale_sensors = notification.value + stale_ids = [] + stale_sensors.each do |sensor, last_update| + stale_ids << id([sensor])&.first + end + + logger.debug "Removing stale sensors: #{stale_ids}" + + @zones.keys&.each do |zone_id| + self[zone_id] = self[zone_id] - stale_ids + self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] - stale_ids + self[zone_id+':occupied_count'] = self[zone_id].count + self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count end end end diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 43d6e10b..fbe31b68 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -52,6 +52,9 @@ def on_update @ws_path = setting('websocket_path') @stale_sensor_threshold = UV::Scheduler.parse_duration(setting('stale_sensor_threshold') || '20m') / 1000 + + schedule.clear + schedule.every(settings('stale_sensor_threshold') { list_stale_sensors } end def connected From f18b9d285f6721b9431e9d0c7361ed210de5ad65 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 16:39:16 +0800 Subject: [PATCH 1528/1752] fix(pressac/desk/logic): typo --- modules/pressac/sensors/ws_protocol.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index fbe31b68..f2a2bc30 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -54,7 +54,7 @@ def on_update @stale_sensor_threshold = UV::Scheduler.parse_duration(setting('stale_sensor_threshold') || '20m') / 1000 schedule.clear - schedule.every(settings('stale_sensor_threshold') { list_stale_sensors } + schedule.every(settings('stale_sensor_threshold')) { list_stale_sensors } end def connected From 6e8f772e3a7f1fdac224da5c393021a85afda7e0 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 23:10:27 +0800 Subject: [PATCH 1529/1752] fix(pressac/desk/logic): fix list_stale_sensors() --- modules/pressac/desk_management.rb | 9 +++------ modules/pressac/sensors/ws_protocol.rb | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 3bb2365f..4e787f4a 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -111,7 +111,7 @@ def update_desk(notification) # timestamp: string, # gateway: string } desk = notification.value - desk_name = id([desk[:name].to_sym])&.first.to_s + desk_name = id([desk[:name].to_sym])&.first zone = @which_zone[desk[:gateway].to_s] logger.debug "===Updating: #{desk_name} in #{zone}" @@ -190,15 +190,12 @@ def persist_current_status # Transform an array of Sensor Names to SVG Map IDs, IF the user has specified a mapping in settings(sensor_name_to_desk_mappings) def id(array) return [] if array.nil? - array.map { |i| @desk_ids[i] || i } + array.map { |i| @desk_ids[i] || i&.to_s } end def unexpose_unresponsive_desks(notification) stale_sensors = notification.value - stale_ids = [] - stale_sensors.each do |sensor, last_update| - stale_ids << id([sensor])&.first - end + stale_ids = id(stale_sensors.map {|s| s.keys.first}) logger.debug "Removing stale sensors: #{stale_ids}" diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index fbe31b68..1038c120 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -46,15 +46,15 @@ def on_update @gateways = status[:gateways] || {} self[:gateways] = @gateways.dup - @last_update = status[:last_update] || "Never" self[:last_update] = @last_update.dup + self[:stale] = [] @ws_path = setting('websocket_path') @stale_sensor_threshold = UV::Scheduler.parse_duration(setting('stale_sensor_threshold') || '20m') / 1000 schedule.clear - schedule.every(settings('stale_sensor_threshold') { list_stale_sensors } + schedule.every(setting('stale_sensor_threshold')) { list_stale_sensors } end def connected From 9e3c6f5f1320247b287fee2b70a71a9e0581b118 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 31 Oct 2019 23:21:22 +0800 Subject: [PATCH 1530/1752] fix(pressac/desk/logic): add new desks to pending arrays --- modules/pressac/desk_management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 4e787f4a..2db9774d 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -117,10 +117,10 @@ def update_desk(notification) logger.debug "===Updating: #{desk_name} in #{zone}" return unless zone - if current_state[:motion] && !self[zone].include?(desk_name) # if motion, and desk is currently free + if current_state[:motion] @pending_busy[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} @pending_free.delete(desk_name) - elsif !current_state[:motion] && self[zone].include?(desk_name) # if no motion, and desk is currently busy + elsif !current_state[:motion] @pending_free[desk_name] ||= { timestamp: Time.now.to_i, gateway: desk[:gateway]} @pending_busy.delete(desk_name) end From 00e0f537bfd691b2933189450662c29bda7f748b Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 1 Nov 2019 11:14:56 +0800 Subject: [PATCH 1531/1752] fix(pressac/desk/logic): Fix status =false instead of [] when adding a new zone --- modules/pressac/desk_management.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 2db9774d..2afd7036 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -51,15 +51,11 @@ def on_update @pending_free ||= {} saved_status = setting('zzz_status') - if saved_status - saved_status&.each { |key, value| self[key] = value } - else - @zones.keys&.each do |zone_id| - self[zone_id] = [] - self[zone_id+':desk_ids'] = [] - self[zone_id+':occupied_count'] = 0 - self[zone_id+':desk_count'] = 0 - end + @zones.keys&.each do |zone_id| + self[zone_id] ||= saved_status[zone_id] || [] + self[zone_id+':desk_ids'] ||= saved_status[zone_id+':desk_ids'] || [] + self[zone_id+':occupied_count'] = self[zone_id].count + self[zone_id+':desk_count'] = self[zone_id+'desk_ids'].count end # Create a reverse lookup (gateway => zone) From 291afd967110d8399db0fc75b179ed8ec1e83038 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 1 Nov 2019 11:19:20 +0800 Subject: [PATCH 1532/1752] fix(pressac/desk/logic): avoid error on setting update --- modules/pressac/desk_management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 2afd7036..31ec0936 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -54,8 +54,8 @@ def on_update @zones.keys&.each do |zone_id| self[zone_id] ||= saved_status[zone_id] || [] self[zone_id+':desk_ids'] ||= saved_status[zone_id+':desk_ids'] || [] - self[zone_id+':occupied_count'] = self[zone_id].count - self[zone_id+':desk_count'] = self[zone_id+'desk_ids'].count + self[zone_id+':occupied_count'] = self[zone_id]&.count || 0 + self[zone_id+':desk_count'] = self[zone_id+'desk_ids']&.count || 0 end # Create a reverse lookup (gateway => zone) From 3a2265cd5f956bdf3ff526de4cf783252cb718b3 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 11 Nov 2019 12:01:06 +1000 Subject: [PATCH 1533/1752] feat(sony:visca over ip): add camera driver --- modules/sony/camera/visca_over_ip.rb | 387 +++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 modules/sony/camera/visca_over_ip.rb diff --git a/modules/sony/camera/visca_over_ip.rb b/modules/sony/camera/visca_over_ip.rb new file mode 100644 index 00000000..69f67a94 --- /dev/null +++ b/modules/sony/camera/visca_over_ip.rb @@ -0,0 +1,387 @@ +# encoding: ASCII-8BIT + +module Sony; end +module Sony::Camera; end + +# Documentation: https://aca.im/driver_docs/Sony/sony_visca_over_ip.pdf + +class Sony::Camera::ViscaOverIP + include ::Orchestrator::Constants # these provide optional helper methods + include ::Orchestrator::Transcoder # (not used in this module) + + # Discovery Information + # NOTE:: the camera will also only reply to this exact port on the host + # so you'll need to configure: `Rails.configuration.orchestrator.datagram_port = 52381` + # in you ruby-engine-app initializer to make this work. + udp_port 52381 + descriptive_name 'Sony IP VISCA PTZ Camera' + generic_name :Camera + + # Communication settings + tokenize delimiter: "\xFF" + delay between_sends: 150 + + + def on_load + # Constants that are made available to interfaces + self[:pan_speed_max] = 0x18 + self[:pan_speed_min] = 1 + self[:tilt_speed_max] = 0x17 + self[:tilt_speed_min] = 1 + + self[:joy_left] = -0x14 + self[:joy_right] = 0x14 + self[:joy_center] = 0 + + # NOTE:: Don't think this actually correct. + # As we probably need to use 2s compliment to use this... + self[:pan_max] = 0xE1E5 # Right + self[:pan_min] = 0x1E1B # Left + self[:pan_center] = 0 + self[:tilt_max] = 0xF010 # UP + self[:tilt_min] = 0x038B # Down + self[:tilt_center] = 0 + + self[:zoom_max] = 0x4000 + self[:zoom_min] = 0 + + @sequence = 0 + + on_update + end + + def on_unload + end + + def on_update + timeout = setting(:timeout) || 5000 + defaults timeout: timeout + + @presets = setting(:presets) || {} + self[:presets] = @presets.keys + self[:invert] = setting(:invert) + end + + def connected + schedule.every('60s') do + logger.debug "-- Polling Sony Camera" + power? do + if self[:power] == On + zoom? + pantilt? + autofocus? + end + end + end + end + + def disconnected + # Disconnected will be called before connect if initial connect fails + schedule.clear + end + + + def power(state) + target = is_affirmative?(state) + + # Execute command + if target == On && self[:power] == Off + send_cmd "\x04\x00\x02", name: :power, delay: 15000 + elsif target == Off && self[:power] == On + send_cmd "\x04\x00\x03", name: :power, delay: 15000 + end + + # ensure the comman ran successfully + self[:power_target] = target + power? + set_autofocus + end + + def home + send_cmd "\x06\x04", name: :home + end + + def reset + send_cmd "\x06\x05", name: :reset + end + + def power?(options = {}, &block) + options[:emit] = block if block_given? + options[:inq] = :power + send_inq "\x04\x00", options + end + + def zoom? + send_inq "\x04\x47", priority: 0, inq: :zoom + end + + def autofocus? + send_inq "\x04\x38", priority: 0, inq: :autofocus + end + + def exposure? + send_inq "\x04\x39", priority: 0, inq: :exposure + end + + def pantilt? + send_inq "\x06\x12", priority: 0, inq: :pantilt + end + + + # Absolute position + def pantilt(pan, tilt) + pan = in_range(pan.to_i, self[:pan_max], self[:pan_min]) + tilt = in_range(tilt.to_i, self[:tilt_max], self[:tilt_min]) + + cmd = [0x06, 0x02, (self[:pan_speed_max] * 0.7).to_i, (self[:tilt_speed_max] * 0.7).to_i].pack('C*') + + # Format the pan tilt value as required + val = pan.to_s(16).rjust(4, '0') + val << tilt.to_s(16).rjust(4, '0') + value = '' + val.each_char do |char| + value << '0' + value << char + end + cmd << hex_to_byte(value) + + send_cmd cmd, name: :position + end + + def joystick(pan_speed, tilt_speed) + left_max = self[:joy_left] + right_max = self[:joy_right] + + pan_speed = in_range(pan_speed.to_i, right_max, left_max) + tilt_speed = in_range(tilt_speed.to_i, right_max, left_max) + + is_centered = false + if pan_speed == 0 && tilt_speed == 0 + is_centered = true + end + + options = {} + options[:name] = :joystick + + cmd = "\x06\x01" + + if is_centered + options[:priority] = 99 + options[:retries] = 5 + cmd << "\x01\x01\x03\x03" + + # Request the current position once the stop command + # has run, we are clearing the queue so we use promises to + # ensure the pantilt command is executed + send_cmd(cmd, options).then do + pantilt? + end + else + options[:retries] = 0 + + # Calculate direction + dir_hori = nil + if pan_speed > 0 + dir_hori = :right + elsif pan_speed < 0 + dir_hori = :left + end + + dir_vert = nil + if tilt_speed > 0 + dir_vert = :up + elsif tilt_speed < 0 + dir_vert = :down + end + + # Add the absolute speed + pan_speed = pan_speed * -1 if pan_speed < 0 + tilt_speed = tilt_speed * -1 if tilt_speed < 0 + cmd << pan_speed + cmd << tilt_speed + + # Provide the direction information + cmd << __send__("stick_#{dir_vert}#{dir_hori}".to_sym) + send_cmd cmd, options + end + end + + def zoom(position, focus = -1) + val = in_range(position.to_i, self[:zoom_max], self[:zoom_min]) + + cmd = "\x04\x47" + + # Format the zoom focus values as required + val = position.to_s(16).rjust(4, '0') + val << focus.to_s(16).rjust(4, '0') if focus >= 0 + value = '' + val.each_char do |char| + value << '0' + value << char + end + cmd << hex_to_byte(value) + + self[:zoom] = position + + send_cmd cmd, name: :zoom + end + + def adjust_tilt(direction) + speed = 0 + if direction == 'down' + speed = self[:invert] ? -0x10 : 0x10 + elsif direction == 'up' + speed = self[:invert] ? 0x10 : -0x10 + end + + joystick(0, speed) + end + + def adjust_pan(direction) + speed = 0 + if direction == 'right' + speed = 0x10 + elsif direction == 'left' + speed = -0x10 + end + + joystick(speed, 0) + end + + # Set autofocus ON + def set_autofocus + send_cmd "\x04\x38\x02", name: :set_autofocus + end + + Exposure = { + auto: "\x00", + manual: "\x03", + shutter: "\x0A", + iris: "\x0B" + } + Exposure.merge!(Exposure.invert) + def set_exposure(mode = :full_auto) + send_cmd "\x04\x39#{Exposure[:mode]}", name: :set_exposure + end + + # Recall a preset from the database + def preset(name) + name_sym = name.to_sym + values = @presets[name_sym] + if values + pantilt(values[:pan], values[:tilt]) + zoom(values[:zoom]) + true + elsif name_sym == :default + home + else + false + end + end + + # Recall a preset from the camera + def recall_position(number) + number = in_range(number, 5) + cmd = "\x04\x3f\x02" + cmd << number + send_cmd cmd, name: :recall_position + end + + def save_position(number) + number = in_range(number, 5) + cmd = "\x04\x3f\x01" + cmd << number + # No name as we might want to queue this + send_cmd cmd + end + + + protected + + + # Joystick command type + def stick_up; "\x03\x01"; end + def stick_down; "\x03\x02"; end + def stick_left; "\x01\x03"; end + def stick_right; "\x02\x03"; end + def stick_upleft; "\x01\x01"; end + def stick_upright; "\x02\x01"; end + def stick_downleft; "\x01\x02"; end + def stick_downright; "\x02\x02"; end + + + def send_cmd(cmd, options = {}) + req = "\x81\x01#{cmd}\xff" + header = "\x01\x00" + payload_length = [req.bytesize].pack("S>") + @sequence += 1 + sequence_number = [@sequence].pack("L>") + logger.debug { "tell -- 0x#{byte_to_hex(req)} -- #{options[:name]}" } + send "#{header}#{payload_length}#{sequence_number}#{req}", options + end + + def send_inq(inq, options = {}) + req = "\x81\x09#{inq}\xff" + header = "\x01\x10" + payload_length = [req.bytesize].pack("S>") + @sequence += 1 + sequence_number = [@sequence].pack("L>") + + logger.debug { "ask -- 0x#{byte_to_hex(req)} -- #{options[:inq]}" } + send "#{header}#{payload_length}#{sequence_number}#{req}", options + end + + + RespComplete = "\x90\x51".freeze + RespIgnore = "\x90\x41".freeze + def received(data, resolve, command) + logger.debug { "Sony sent 0x#{byte_to_hex(data)}" } + + strip_header = data[0..7] + data = data[8..-1] + + # Process command responses + if command && command[:inq].nil? + if data == RespComplete + return :success + else + return :ignore + end + end + + # This will probably not ever be true + return :success unless command && command[:inq] + return :ignore if data == RespIgnore + + # Process the response + bytes = str_to_array(data) + case command[:inq] + when :power + self[:power] = bytes[-1] == 2 + + if !self[:power_target].nil? && self[:power_target] != self[:power] + schedule.in 3000 do + power(self[:power_target]) if self[:power_target] != self[:power] + end + end + when :zoom + hex = byte_to_hex(data[2..-1]) + hex_new = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" + self[:zoom] = hex_new.to_i(16) + when :exposure + self[:exposure] = Exposure[data[-1]] + when :autofocus + self[:auto_focus] = bytes[-1] == 2 + when :pantilt + hex = byte_to_hex(data[2..5]) + pan_hex = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" + self[:pan] = pan_hex.to_i(16) + + hex = byte_to_hex(data[6..-1]) + tilt_hex = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" + self[:tilt] = tilt_hex.to_i(16) + end + + :success + end +end From 5f5a77c7d5ea327593f7a0edfb5517f4c5af3f2c Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 17 Nov 2019 17:13:50 +0800 Subject: [PATCH 1534/1752] feature(pressac/desk/logic): Add custom delays (untested) --- modules/pressac/desk_management.rb | 35 ++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 31ec0936..90997b20 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -22,6 +22,13 @@ class ::Pressac::DeskManagement "Desk01" => "table-SYD.2.17.A", "Desk03" => "table-SYD.2.17.B" } + custom_delays: [ + { + "regex_match": "^Example[0-9]$", + "delay_until_shown_as_busy": "5m", + "delay_until_shown_as_free": "1h" + } + ] }) def on_load @@ -42,9 +49,17 @@ def on_update @hub = setting('iot_hub_device') || "Websocket_1" @zones = setting('zone_to_gateway_mappings') || {} @desk_ids = setting('sensor_name_to_desk_mappings') || {} + @custom_delays = setting('custom_delays')&.map {|d| + { + regex_match: d[:regex_match], + busy_delay: UV::Scheduler.parse_duration(d[:delay_until_shown_as_busy] || '0m') / 1000, + free_delay: UV::Scheduler.parse_duration(d[:delay_until_shown_as_free] || '0m') / 1000, + } + } || [] + # convert '1m2s' to '62' - @busy_delay = UV::Scheduler.parse_duration(setting('delay_until_shown_as_busy') || '0m') / 1000 - @free_delay = UV::Scheduler.parse_duration(setting('delay_until_shown_as_free') || '0m') / 1000 + @default_busy_delay = UV::Scheduler.parse_duration(setting('delay_until_shown_as_busy') || '0m') / 1000 + @default_free_delay = UV::Scheduler.parse_duration(setting('delay_until_shown_as_free') || '0m') / 1000 # Initialize desk tracking variables to [] or 0, but keep existing values if they exist (||=) @pending_busy ||= {} @@ -126,6 +141,18 @@ def update_desk(notification) self[:pending_free] = @pending_free end + def delay_of(desk_id) + @custom_delays.each do |setting| + #regex = Regexp.new([:regex_match]) + if desk_id.match?(setting[:regex_match]) + logger.debug "REGEX MATCHED #{sensor_name} to #{setting[:regex_match]}" + return {busy: setting[:busy_delay], free: setting[:free_delay]} + end + end + logger.debug "NO REGEX MATCH for #{sensor_name}" + return {busy: @default_busy_delay, free: @default_free_delay} + end + def determine_desk_status persist_current_status new_status = {} @@ -136,13 +163,13 @@ def determine_desk_status now = Time.now.to_i @pending_busy.each do |desk,sensor| - if now > sensor[:timestamp] + @busy_delay + if now > sensor[:timestamp] + delay_of(sensor)[:busy] zone = @which_zone[sensor[:gateway].to_s] new_status[zone][:busy] |= [desk] if zone end end @pending_free.each do |desk,sensor| - if now > sensor[:timestamp] + @free_delay + if now > sensor[:timestamp] + delay_of(sensor)[:free] zone = @which_zone[sensor[:gateway].to_s] new_status[zone][:free] |= [desk] if zone end From 83c2c2137b7be81eea0c56228d1533b951605c58 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 17 Nov 2019 17:26:51 +0800 Subject: [PATCH 1535/1752] fix(pressac/desk/logic: custom delays: syntax errors --- modules/pressac/desk_management.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 90997b20..b89d498b 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -21,14 +21,14 @@ class ::Pressac::DeskManagement "Note" => "This mapping is optional. If not present, the sensor NAME will be used and must match SVG map IDs", "Desk01" => "table-SYD.2.17.A", "Desk03" => "table-SYD.2.17.B" - } + }, custom_delays: [ { "regex_match": "^Example[0-9]$", "delay_until_shown_as_busy": "5m", "delay_until_shown_as_free": "1h" } - ] + ] }) def on_load @@ -145,11 +145,11 @@ def delay_of(desk_id) @custom_delays.each do |setting| #regex = Regexp.new([:regex_match]) if desk_id.match?(setting[:regex_match]) - logger.debug "REGEX MATCHED #{sensor_name} to #{setting[:regex_match]}" + logger.debug "REGEX MATCHED #{desk_id} to #{setting[:regex_match]}" return {busy: setting[:busy_delay], free: setting[:free_delay]} end end - logger.debug "NO REGEX MATCH for #{sensor_name}" + logger.debug "NO REGEX MATCH for #{desk_id}" return {busy: @default_busy_delay, free: @default_free_delay} end @@ -163,13 +163,13 @@ def determine_desk_status now = Time.now.to_i @pending_busy.each do |desk,sensor| - if now > sensor[:timestamp] + delay_of(sensor)[:busy] + if now > sensor[:timestamp] + delay_of(desk)[:busy] zone = @which_zone[sensor[:gateway].to_s] new_status[zone][:busy] |= [desk] if zone end end @pending_free.each do |desk,sensor| - if now > sensor[:timestamp] + delay_of(sensor)[:free] + if now > sensor[:timestamp] + delay_of(desk)[:free] zone = @which_zone[sensor[:gateway].to_s] new_status[zone][:free] |= [desk] if zone end From 533e2d183bd6e3c481e0d1b522595885daff5b77 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 17 Nov 2019 17:30:23 +0800 Subject: [PATCH 1536/1752] style(pressac/desk/logic: standardise log output --- modules/pressac/desk_management.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index b89d498b..b719e604 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -125,7 +125,7 @@ def update_desk(notification) desk_name = id([desk[:name].to_sym])&.first zone = @which_zone[desk[:gateway].to_s] - logger.debug "===Updating: #{desk_name} in #{zone}" + logger.debug "PRESSAC > DESK > LOGIC: Updating #{desk_name} in #{zone}" return unless zone if current_state[:motion] @@ -145,11 +145,10 @@ def delay_of(desk_id) @custom_delays.each do |setting| #regex = Regexp.new([:regex_match]) if desk_id.match?(setting[:regex_match]) - logger.debug "REGEX MATCHED #{desk_id} to #{setting[:regex_match]}" + logger.debug "PRESSAC > DESK > LOGIC: Regex MATCHED #{desk_id} to #{setting[:regex_match]}" return {busy: setting[:busy_delay], free: setting[:free_delay]} end end - logger.debug "NO REGEX MATCH for #{desk_id}" return {busy: @default_busy_delay, free: @default_free_delay} end @@ -220,7 +219,7 @@ def unexpose_unresponsive_desks(notification) stale_sensors = notification.value stale_ids = id(stale_sensors.map {|s| s.keys.first}) - logger.debug "Removing stale sensors: #{stale_ids}" + logger.debug "PRESSAC > DESK > LOGIC: Removing stale sensors: #{stale_ids}" @zones.keys&.each do |zone_id| self[zone_id] = self[zone_id] - stale_ids From 405c52610ecc36e48210f1cb7ac0d04fb6f50a64 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 17 Nov 2019 18:03:36 +0800 Subject: [PATCH 1537/1752] feature(pressac/desk/sensor): delete stale sensors from status --- modules/pressac/desk_management.rb | 2 +- modules/pressac/sensors/ws_protocol.rb | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index b719e604..79a90c7f 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -217,7 +217,7 @@ def id(array) def unexpose_unresponsive_desks(notification) stale_sensors = notification.value - stale_ids = id(stale_sensors.map {|s| s.keys.first}) + stale_ids = id(stale_sensors.map {|s| s.keys.first}) logger.debug "PRESSAC > DESK > LOGIC: Removing stale sensors: #{stale_ids}" diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 1038c120..fd612928 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -91,12 +91,20 @@ def list_stale_sensors @gateways.each do |g, sensors| sensors.each do |name, sensor| if now - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold - stale << {name => (sensor[:last_update] || "Unknown")} + stale << {name => (sensor[:last_update] || "Unknown")} + @gateways[g].delete(name) end end end - logger.debug "Sensors that have not posted updates in the past #{setting('stale_sensor_threshold')}:\n#{stale}" self[:stale] = stale + self[:gateways] = @gateways.deep_dup + # Save the current status to database, so that it can retrieved when engine restarts + status = { + last_update: self[:last_update], + gateways: @gateways, + stale: stale + } + define_setting(:status, status) end @@ -168,7 +176,8 @@ def on_message(raw_string) # Save the current status to database, so that it can retrieved when engine restarts status = { last_update: self[:last_update], - gateways: @gateways.deep_dup + gateways: @gateways.deep_dup, + stale: self[:stale] } define_setting(:status, status) end From b66a3d40ac7eb906ce9d104800429905920366fa Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 18 Nov 2019 12:10:02 +1000 Subject: [PATCH 1538/1752] fix(sony IP camera): improve responsivness and presets --- modules/sony/camera/visca_over_ip.rb | 53 ++++++++++++++++------------ 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/modules/sony/camera/visca_over_ip.rb b/modules/sony/camera/visca_over_ip.rb index 69f67a94..8011cd97 100644 --- a/modules/sony/camera/visca_over_ip.rb +++ b/modules/sony/camera/visca_over_ip.rb @@ -19,7 +19,7 @@ class Sony::Camera::ViscaOverIP # Communication settings tokenize delimiter: "\xFF" - delay between_sends: 150 + delay between_sends: 200 def on_load @@ -54,8 +54,8 @@ def on_unload end def on_update - timeout = setting(:timeout) || 5000 - defaults timeout: timeout + timeout = setting(:timeout) || 3000 + defaults timeout: timeout, retries: 1 @presets = setting(:presets) || {} self[:presets] = @presets.keys @@ -63,23 +63,17 @@ def on_update end def connected - schedule.every('60s') do + schedule.every('120s') do logger.debug "-- Polling Sony Camera" power? do if self[:power] == On zoom? pantilt? - autofocus? end end end end - def disconnected - # Disconnected will be called before connect if initial connect fails - schedule.clear - end - def power(state) target = is_affirmative?(state) @@ -266,8 +260,8 @@ def set_exposure(mode = :full_auto) # Recall a preset from the database def preset(name) - name_sym = name.to_sym - values = @presets[name_sym] + name = name.to_s + values = @presets[name] if values pantilt(values[:pan], values[:tilt]) zoom(values[:zoom]) @@ -281,18 +275,29 @@ def preset(name) # Recall a preset from the camera def recall_position(number) - number = in_range(number, 5) - cmd = "\x04\x3f\x02" - cmd << number - send_cmd cmd, name: :recall_position + preset(number) end - def save_position(number) - number = in_range(number, 5) - cmd = "\x04\x3f\x01" - cmd << number - # No name as we might want to queue this - send_cmd cmd + def save_position(name) + name = name.to_s + + power? do + if self[:power] == On + zoom? + pantilt?.value + + @presets[name] = { + pan: self[:pan], + tilt: self[:pan], + zoom: self[:zoom] + } + + self[:presets] = @presets.keys + define_setting(:presets, @presets) + else + raise "camera is powered off" + end + end end @@ -373,6 +378,10 @@ def received(data, resolve, command) when :autofocus self[:auto_focus] = bytes[-1] == 2 when :pantilt + # 0x0111000d0000 008c 90500004 0b09040004020f0c + # 0x011100040000 0089 906041 + return :success unless data.bytesize > 7 + hex = byte_to_hex(data[2..5]) pan_hex = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" self[:pan] = pan_hex.to_i(16) From f0f721a93bbf72a716f73c461c60d5ac3eaa5f30 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 18 Nov 2019 11:36:49 +0800 Subject: [PATCH 1539/1752] fix(pressac/desk/logic): fix hash ref for saved status on init --- modules/pressac/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 79a90c7f..255a27c1 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -65,7 +65,7 @@ def on_update @pending_busy ||= {} @pending_free ||= {} - saved_status = setting('zzz_status') + saved_status = setting('zzz_status') || {} @zones.keys&.each do |zone_id| self[zone_id] ||= saved_status[zone_id] || [] self[zone_id+':desk_ids'] ||= saved_status[zone_id+':desk_ids'] || [] From d6c8ba3fcdc571cc92bf34def0881244510b67bf Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 18 Nov 2019 11:47:35 +0800 Subject: [PATCH 1540/1752] fix(pressac/desk/logic): logic error with custom delays --- modules/pressac/desk_management.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 255a27c1..3a5cbc11 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -163,24 +163,23 @@ def determine_desk_status now = Time.now.to_i @pending_busy.each do |desk,sensor| if now > sensor[:timestamp] + delay_of(desk)[:busy] - zone = @which_zone[sensor[:gateway].to_s] + zone = @which_zone[sensor[:gateway].to_s] new_status[zone][:busy] |= [desk] if zone + @pending_busy.delete(desk) end end @pending_free.each do |desk,sensor| if now > sensor[:timestamp] + delay_of(desk)[:free] - zone = @which_zone[sensor[:gateway].to_s] + zone = @which_zone[sensor[:gateway].to_s] new_status[zone][:free] |= [desk] if zone + @pending_free.delete(desk) end end - self[:new_status] = new_status.deep_dup - + self[:new_status] = new_status.deep_dup + self[:pending_busy] = @pending_busy.deep_dup + self[:pending_free] = @pending_free.deep_dup expose_desks(new_status) - @pending_busy = {} - @pending_free = {} - self[:pending_busy] = {} - self[:pending_free] = {} end def expose_desks(new_status) From e41b19d27f112aa7b1d59d78a28c05711079c406 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 19 Nov 2019 10:16:43 +1000 Subject: [PATCH 1541/1752] feat(sony camera): add HTTP CGI protocol --- modules/sony/camera/cgi.rb | 310 +++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 modules/sony/camera/cgi.rb diff --git a/modules/sony/camera/cgi.rb b/modules/sony/camera/cgi.rb new file mode 100644 index 00000000..7309877f --- /dev/null +++ b/modules/sony/camera/cgi.rb @@ -0,0 +1,310 @@ +# encoding: ASCII-8BIT + +module Sony; end +module Sony::Camera; end + +# Documentation: https://aca.im/driver_docs/Sony/sony-camera-CGI-Commands-1.pdf + +class Sony::Camera::CGI + include ::Orchestrator::Constants # these provide optional helper methods + include ::Orchestrator::Transcoder # (not used in this module) + + # Discovery Information + descriptive_name "Sony Camera HTTP CGI Protocol" + generic_name :Camera + implements :service + keepalive false + + default_settings({ + username: "admin", + password: "Admin_1234", + invert: false, + presets: { + name: { pan: 1, tilt: 1, zoom: 1 } + } + }) + + def on_load + # Constants that are made available to interfaces + self[:joy_left] = -100 + self[:joy_right] = 100 + self[:joy_center] = 0 + + @moving = false + @max_speed = 1 + + schedule.every('60s') do + logger.debug "-- Polling Sony Camera" + query_status + end + + on_update + end + + def on_update + @presets = setting(:presets) || {} + self[:presets] = @presets.keys + self[:invert] = @invert = setting(:invert) || false + @authorization = [setting(:username), setting(:password)] + end + + def connected + query_status + end + + def query_status(priority = 0) + query("/command/inquiry.cgi?inq=ptzf", priority: priority) do |response| + response.each do |key, value| + case key + when "AbsolutePTZF" + # Pan, Tilt, Zoom,Focus + # AbsolutePTZF=15400,fd578,0000,ca52 + parts = value.split(",") + self[:pan] = @pan = twos_complement(parts[0].to_i(16)) + self[:tilt] = @tilt = twos_complement(parts[1].to_i(16)) + self[:zoom] = @zoom = twos_complement(parts[2].to_i(16)) + + when "PanPanoramaRange" + # PanMovementRange=eac00,15400 + parts = value.split(",") + pan_min = twos_complement parts[0].to_i(16) + pan_max = twos_complement parts[1].to_i(16) + + @pan_range = (pan_min..pan_max) + self[:pan_range] = {min: pan_min, max: pan_max} + self[:pan_max] = pan_max # Right + self[:pan_min] = pan_min # Left + + when "TiltPanoramaRange" + # TiltMovementRange=fc400,b400 + parts = value.split(",") + tilt_min = twos_complement parts[0].to_i(16) + tilt_max = twos_complement parts[1].to_i(16) + + @tilt_range = (tilt_min..tilt_max) + self[:tilt_range] = {min: tilt_min, max: tilt_max} + self[:tilt_max] = tilt_max # UP + self[:tilt_min] = tilt_min # Down + + when "ZoomMovementRange" + # min, max, digital + # ZoomMovementRange=0000,4000,7ac0 + parts = value.split(",") + zoom_min = twos_complement parts[0].to_i(16) + zoom_max = twos_complement parts[1].to_i(16) + @zoom_range = (zoom_min..zoom_max) + self[:zoom_range] = {min: zoom_min, max: zoom_max} + self[:zoom_min] = zoom_min + self[:zoom_max] = zoom_max + + when "PtzfStatus" + # PtzfStatus=idle,idle,idle,idle + parts = value.split(",")[0..2] + self[:moving] = @moving = parts.include?("moving") + + # when "AbsoluteZoom" + # # AbsoluteZoom=609 + # self[:zoom] = @zoom = value.to_i(16) + + # NOTE:: These are not required as speeds are scaled + # + # when "ZoomMaxVelocity" + # # ZoomMaxVelocity=8 + # @zoom_speed = 1..value.to_i(16) + + when "PanTiltMaxVelocity" + # PanTiltMaxVelocity=24 + @max_speed = value.to_i(16) + end + end + end + end + + def info? + query("/command/inquiry.cgi?inq=system", priority: 0) do |response| + keys = ["ModelName", "Serial", "SoftVersion", "ModelForm", "CGIVersion"] + response.each do |key, value| + if keys.include?(key) + self[key.underscore] = value + end + end + end + end + + # Absolute position + def pantilt(pan, tilt, zoom = nil) + pan = twos_complement in_range(pan.to_i, @pan_range.end, @pan_range.begin) + tilt = twos_complement in_range(tilt.to_i, @tilt_range.end, @tilt_range.begin) + + if zoom + zoom = twos_complement in_range(zoom.to_i, @zoom_range.end, @zoom_range.begin) + + action("/command/ptzf.cgi?AbsolutePTZF=#{pan.to_s(16)},#{tilt.to_s(16)},#{zoom.to_s(16)}", + name: "position" + ) do + self[:pan] = @pan = pan + self[:tilt] = @tilt = tilt + self[:zoom] = @zoom = zoom + end + else + action("/command/ptzf.cgi?AbsolutePanTilt=#{pan.to_s(16)},#{tilt.to_s(16)},#{@max_speed.to_s(16)}", + name: "position" + ) do + self[:pan] = @pan = pan + self[:tilt] = @tilt = tilt + end + end + end + + def joystick(pan_speed, tilt_speed) + range = -100..100 + pan_speed = in_range(pan_speed.to_i, range.end, range.begin) + tilt_speed = in_range(tilt_speed.to_i, range.end, range.begin) + is_centered = pan_speed == 0 && tilt_speed == 0 + + tilt_speed = -tilt_speed if @invert && tilt_speed != 0 + + if is_centered + action("/command/ptzf.cgi?Move=stop,motor,image1", + priority: 999, + name: "moving" + ) do + self[:moving] = @moving = false + query_status + end + else + action("/command/ptzf.cgi?ContinuousPanTiltZoom=#{pan_speed.to_s(16)},#{tilt_speed.to_s(16)},0,image1", + name: "moving" + ) do + self[:moving] = @moving = true + end + end + end + + def zoom(position) + position = in_range(position.to_i, @zoom_range.end, @zoom_range.begin) + + action("/command/ptzf.cgi?AbsoluteZoom=#{position.to_s(16)}", + name: "zooming" + ) { self[:zoom] = @zoom = position } + end + + def move(position) + position = position.to_s.downcase + case position + when 'up', 'down', 'left', 'right' + # Tilt, Pan + if @invert && ['up', 'down'].include?(position) + position = position == 'up' ? 'down' : 'up' + end + + speed = (@max_speed.to_f * 0.5).to_i + + action("/command/ptzf.cgi?Move=#{position},#{speed.to_s(16)},image1", + name: "moving" + ) { self[:moving] = @moving = true } + when 'stop' + joystick(0, 0) + else + raise "unsupported direction: #{position}" + end + end + + def adjust_tilt(direction) + direction = direction.to_s.downcase + if ['up', 'down'].include?(direction) + move(direction) + else + joystick(0, 0) + end + end + + def adjust_pan(direction) + direction = direction.to_s.downcase + if ['left', 'right'].include?(direction) + move(direction) + else + joystick(0, 0) + end + end + + def home + action("/command/presetposition.cgi?HomePos=ptz-recall", + name: "position" + ) { query_status } + end + + # Recall a preset from the database + def preset(name) + name_sym = name.to_sym + values = @presets[name_sym] + if values + pantilt(values[:pan], values[:tilt], values[:zoom]) + true + elsif name_sym == :default + home + else + false + end + end + + # Recall a preset from the camera + def recall_position(number) + preset number.to_s + end + + def save_position(name) + name = name.to_s + logger.debug { "saving preset #{name} - pan: #{pan}, tilt: #{tilt}, zoom: #{zoom}" } + @presets[name] = { + pan: pan, + tilt: tilt, + zoom: zoom + } + self[:presets] = @presets.keys + define_setting(:presets, @presets) + self[:presets] + end + + + protected + + + # 16bit twos complement + def twos_complement(value) + if value > 0 + value > 0x8000 ? -(((~(value & 0xFFFF)) + 1) & 0xFFFF) : value + else + ((~(-value & 0xFFFF)) + 1) & 0xFFFF + end + end + + def query(path, **options) + options[:headers] ||= {} + options[:headers]['authorization'] = @authorization + + get(path, options) do |response| + raise "unexpected response #{response.status}\n#{response.body}" unless response.status == 200 + + state = {} + response.body.split("&").each do |key_value| + parts = key_value.strip.split("=") + state[parts[0]] = parts[1] + end + + yield state + state + end + end + + def action(path, **options) + options[:headers] ||= {} + options[:headers]['authorization'] = @authorization + + get(path, options) do |response| + raise "request error #{response.status}\n#{response.body}" unless response.status == 200 + yield response + :success + end + end +end From dcdbb1e38dc8666856e3b8fbed363cd9bca893e6 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 19 Nov 2019 10:26:34 +1000 Subject: [PATCH 1542/1752] fix(sony camera): add delete_position method --- modules/sony/camera/cgi.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/sony/camera/cgi.rb b/modules/sony/camera/cgi.rb index 7309877f..bbf8e1ce 100644 --- a/modules/sony/camera/cgi.rb +++ b/modules/sony/camera/cgi.rb @@ -266,6 +266,15 @@ def save_position(name) self[:presets] end + def delete_position(name) + name = name.to_s + logger.debug { "removing preset #{name}" } + @presets.delete name + self[:presets] = @presets.keys + define_setting(:presets, @presets) + self[:presets] + end + protected From ec3e076f1f995437cbb636ad3b4e457a059ca4c7 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 19 Nov 2019 11:25:18 +1000 Subject: [PATCH 1543/1752] fix(sony camera): movement ranges and preset saving --- modules/sony/camera/cgi.rb | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/sony/camera/cgi.rb b/modules/sony/camera/cgi.rb index bbf8e1ce..37f2e9a0 100644 --- a/modules/sony/camera/cgi.rb +++ b/modules/sony/camera/cgi.rb @@ -62,9 +62,9 @@ def query_status(priority = 0) parts = value.split(",") self[:pan] = @pan = twos_complement(parts[0].to_i(16)) self[:tilt] = @tilt = twos_complement(parts[1].to_i(16)) - self[:zoom] = @zoom = twos_complement(parts[2].to_i(16)) + self[:zoom] = @zoom = parts[2].to_i(16) - when "PanPanoramaRange" + when "PanMovementRange" # PanMovementRange=eac00,15400 parts = value.split(",") pan_min = twos_complement parts[0].to_i(16) @@ -75,7 +75,7 @@ def query_status(priority = 0) self[:pan_max] = pan_max # Right self[:pan_min] = pan_min # Left - when "TiltPanoramaRange" + when "TiltMovementRange" # TiltMovementRange=fc400,b400 parts = value.split(",") tilt_min = twos_complement parts[0].to_i(16) @@ -90,8 +90,8 @@ def query_status(priority = 0) # min, max, digital # ZoomMovementRange=0000,4000,7ac0 parts = value.split(",") - zoom_min = twos_complement parts[0].to_i(16) - zoom_max = twos_complement parts[1].to_i(16) + zoom_min = parts[0].to_i(16) + zoom_max = parts[1].to_i(16) @zoom_range = (zoom_min..zoom_max) self[:zoom_range] = {min: zoom_min, max: zoom_max} self[:zoom_min] = zoom_min @@ -137,7 +137,7 @@ def pantilt(pan, tilt, zoom = nil) tilt = twos_complement in_range(tilt.to_i, @tilt_range.end, @tilt_range.begin) if zoom - zoom = twos_complement in_range(zoom.to_i, @zoom_range.end, @zoom_range.begin) + zoom = in_range(zoom.to_i, @zoom_range.end, @zoom_range.begin) action("/command/ptzf.cgi?AbsolutePTZF=#{pan.to_s(16)},#{tilt.to_s(16)},#{zoom.to_s(16)}", name: "position" @@ -255,11 +255,11 @@ def recall_position(number) def save_position(name) name = name.to_s - logger.debug { "saving preset #{name} - pan: #{pan}, tilt: #{tilt}, zoom: #{zoom}" } + logger.debug { "saving preset #{name} - pan: #{@pan}, tilt: #{@tilt}, zoom: #{@zoom}" } @presets[name] = { - pan: pan, - tilt: tilt, - zoom: zoom + pan: @pan, + tilt: @tilt, + zoom: @zoom } self[:presets] = @presets.keys define_setting(:presets, @presets) @@ -279,13 +279,13 @@ def delete_position(name) protected - # 16bit twos complement + # 24bit twos complement def twos_complement(value) - if value > 0 - value > 0x8000 ? -(((~(value & 0xFFFF)) + 1) & 0xFFFF) : value - else - ((~(-value & 0xFFFF)) + 1) & 0xFFFF - end + if value > 0 + value > 0x80000 ? -(((~(value & 0xFFFFF)) + 1) & 0xFFFFF) : value + else + ((~(-value & 0xFFFFF)) + 1) & 0xFFFFF + end end def query(path, **options) From 3af739296b755b10f0c04d140198023614891a9d Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 25 Nov 2019 15:08:55 +0800 Subject: [PATCH 1544/1752] chore(office2/client/logs): always output error responses (remove requirement for env var) --- lib/microsoft/office2/client.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 115d6924..1f2fdece 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -149,8 +149,7 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint STDERR.flush end - def check_response(response) - STDOUT.puts "GRAPH API Response:\n #{response}" if ENV['O365_LOG_RESPONSE'] + def check_response(response case response.status when 200, 201, 204 return @@ -171,4 +170,4 @@ def check_response(response) end end -end \ No newline at end of file +end From b9d30c82b9edf4227b4025054488b420bf84c6a3 Mon Sep 17 00:00:00 2001 From: Viv B Date: Thu, 28 Nov 2019 10:27:52 +1100 Subject: [PATCH 1545/1752] fix(office2/client): fix syntax in check_response --- lib/microsoft/office2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 1f2fdece..44e99f7f 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -149,7 +149,7 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint STDERR.flush end - def check_response(response + def check_response(response) case response.status when 200, 201, 204 return From cd2aabb9927084750cdf25d2c0cb186723a2e30c Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 28 Nov 2019 16:15:13 +0800 Subject: [PATCH 1546/1752] feature(office2/RBP): Add support for https_proxy as a driver setting --- modules/aca/o365_booking_panel.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 5599292f..f77bbde3 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -86,14 +86,15 @@ def on_update office_token_path = setting(:office_token_path) || "/oauth2/v2.0/token" office_token_url = setting(:office_token_url) || ENV["OFFICE_TOKEN_URL"] || "/" + setting(:office_tenant) + office_token_path @office_room = (setting(:office_room) || system.email) - #office_https_proxy = setting(:office_https_proxy) + office_https_proxy = setting(:office_https_proxy) logger.debug "RBP>#{@office_room}>INIT: Instantiating o365 Graph API client" @client = ::Microsoft::Office2::Client.new({ client_id: office_client_id, client_secret: office_secret, - app_token_url: office_token_url + app_token_url: office_token_url, + https_proxy: office_proxy }) self[:last_meeting_started] = setting(:last_meeting_started) From ecde82129a9fd562da52a548e9725cec76d2c612 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 28 Nov 2019 16:16:39 +0800 Subject: [PATCH 1547/1752] fix(office2/RBP): param name for proxy server --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index f77bbde3..07a5cfc7 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -94,7 +94,7 @@ def on_update client_id: office_client_id, client_secret: office_secret, app_token_url: office_token_url, - https_proxy: office_proxy + https_proxy: office_https_proxy }) self[:last_meeting_started] = setting(:last_meeting_started) From 19e078e7e988de3adf33ce4cf93741bec26f781a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 2 Dec 2019 10:14:32 +1000 Subject: [PATCH 1548/1752] feat(exchange bookings): add support for help emails --- modules/aca/exchange_booking.rb | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/aca/exchange_booking.rb b/modules/aca/exchange_booking.rb index cea027aa..5b7447a3 100644 --- a/modules/aca/exchange_booking.rb +++ b/modules/aca/exchange_booking.rb @@ -40,8 +40,8 @@ def on_update # If left as the default (false), the driver will attempt to use Impersonation access to read the room mailbox # https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/impersonation-and-ews-in-exchange # https://www.rubydoc.info/github/zenchild/Viewpoint/Viewpoint%2FEWS%2FFolderAccessors:get_folder - @ews_delegate_accesss = setting(:ews_delegate_accesss) || setting(:use_act_as) - + @ews_delegate_accesss = setting(:ews_delegate_accesss) || setting(:use_act_as) + self[:room_name] = setting(:room_name) || system.name self[:hide_all] = setting(:hide_all) || false self[:touch_enabled] = setting(:touch_enabled) || false @@ -52,6 +52,7 @@ def on_update self[:description] = setting(:description) self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url + self[:help_options] = setting(:help_options) self[:timeout] = setting(:timeout) self[:booking_cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' @@ -224,6 +225,31 @@ def send_email(title, body, to) } end + def request_help(issue) + now = Time.now.to_i + todays = self[:today] + + current = nil + Time.zone = "UTC" + todays.each do |booking| + starting = Time.zone.parse(booking[:Start]).to_i + ending = Time.zone.parse(booking[:End]).to_i + if now >= starting && now < ending + current = booking + break; + end + end + + if current + message = "Issue in #{self[:room_name]}\n#{current[:owner]} requires help with #{issue}" + else + message = "Issue in #{self[:room_name]}\nUser requires help with #{issue}" + end + + # help_email: ["array.of@.email.addresses"] + send_email("Issue in #{self[:room_name]}", message, setting(:help_email)) + end + protected From 4514af0f414a527fff2d87bdf23b9b3c1d2a5d4a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 2 Dec 2019 10:16:08 +1000 Subject: [PATCH 1549/1752] feat(aca meeting room): improve joining startup and shutdown avoids race conditions that might cause a room to present after shutting down --- modules/aca/meeting_room.rb | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/modules/aca/meeting_room.rb b/modules/aca/meeting_room.rb index d3988018..f5487e1d 100644 --- a/modules/aca/meeting_room.rb +++ b/modules/aca/meeting_room.rb @@ -322,7 +322,16 @@ def join(*ids) found = false @join_modes.each_value do |jm| if jm[:rooms] == rms - perform_action(mod: :System, func: :switch_mode, args: [jm[:mode], true]) + perform_action(mod: :System, func: :switch_mode, args: [jm[:mode], true]).then do + schedule.in(1500) do + self[:outputs].each do |key, value| + details = self[key] + next if details[:source] == :none) + + perform_action(mod: :System, func: :present_actual, args: [details[:source], key]).value + end + end + end found = true break end @@ -759,7 +768,7 @@ def emergency_shutdown(active = true) def shutdown(all = false, scheduled_shutdown = false) if all - perform_action(mod: :System, func: :shutdown_actual).then do + perform_action(mod: :System, func: :shutdown_actual, args: [scheduled_shutdown, "2s"]).then do unjoin end else @@ -771,7 +780,14 @@ def shutdown(all = false, scheduled_shutdown = false) end end - def shutdown_actual(scheduled_shutdown = false) + def shutdown_actual(scheduled_shutdown = false, delay = nil) + if delay + schedule.in(delay) do + shutdown_actual(scheduled_shutdown) + end + return + end + # Shudown action on Lights if scheduled_shutdown && @light_scheduled_shutdown lights_to_actual(@light_scheduled_shutdown) From 14ea45f50f572c0d55c4051c40ee3c8e6e748e44 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 2 Dec 2019 17:59:18 +0800 Subject: [PATCH 1550/1752] fix(office2/lib/event/create): fix RECURRENCE (missing date func) --- lib/microsoft/office2/events.rb | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 3103e328..75eaf49c 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -328,13 +328,15 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, content: (body || "") } + start_date = Time.at(start_param).to_datetime.in_time_zone(timezone) event_json[:start] = { - dateTime: ActiveSupport::TimeZone.new(timezone).at(start_param).strftime('%FT%R'), + dateTime: start_date.strftime('%FT%R'), timeZone: timezone } if start_param + end_date = Time.at(end_param).to_datetime.in_time_zone(timezone) event_json[:end] = { - dateTime: ActiveSupport::TimeZone.new(timezone).at(end_param).strftime('%FT%R'), + dateTime: end_date.strftime('%FT%R'), timeZone: timezone } if end_param @@ -358,18 +360,21 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, end event_json[:extensions] = [ext] - event_json[:recurrence] = { - pattern: { - type: recurrence[:type], - interval: 1, - daysOfWeek: [epoch_in_timezone(start_param, timezone).strftime("%A")] - }, - range: { - type: 'endDate', - startDate: epoch_in_timezone(start_param, timezone).strftime("%F"), - endDate: epoch_in_timezone(recurrence[:end], timezone).strftime("%F") + if recurrence + recurrence_end_date = Time.at(recurrence[:end]).to_datetime.in_time_zone(timezone) + event_json[:recurrence] = { + pattern: { + type: recurrence[:type], + interval: 1, + daysOfWeek: [start_date.strftime("%A")] + }, + range: { + type: 'endDate', + startDate: start_date.strftime("%F"), + endDate: recurrence_end_date.strftime("%F") + } } - } if recurrence + } event_json.reject!{|k,v| v.nil?} event_json From 769988d1334fe09df5bfd43dd945017b49549cb9 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 2 Dec 2019 18:06:17 +0800 Subject: [PATCH 1551/1752] feature(office2/RBP): add func for triggering reload of endpoints --- modules/aca/o365_booking_panel.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 07a5cfc7..6276de04 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -244,6 +244,11 @@ def clear_end_meeting_warning end + def refresh_endpoints(epoch) + # Relies on frontend recieving this status variable update and acting upon it. Should result in a full page reload. + # epoch is an integer, in seconds. + self[:reload] = epoch + end protected From c3ba700d65f2e629705703d4a5258101056b4dfd Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 2 Dec 2019 18:10:17 +0800 Subject: [PATCH 1552/1752] fix(office2/lib/event/new/recurrence): Syntax --- lib/microsoft/office2/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 75eaf49c..51b4280a 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -374,7 +374,7 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, endDate: recurrence_end_date.strftime("%F") } } - } + end event_json.reject!{|k,v| v.nil?} event_json From 99b8c11535376c79f6e74d61ba107002b3106ef8 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Dec 2019 14:13:35 +0800 Subject: [PATCH 1553/1752] feature(pressac/desk_sensor): Support room PIR sensor --- modules/pressac/sensors/ws_protocol.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index fd612928..ce407029 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -160,6 +160,24 @@ def on_message(raw_string) } self[gateway] = @gateways[gateway][sensor_name].dup self[:gateways] = @gateways.deep_dup + when 'Occupancy-PIR' + sensor_name = sensor[:deviceName].to_sym + gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym + occupancy = sensor[:motionDetected] == true + @gateways[gateway] ||= {} + @gateways[gateway][sensor_name] = { + id: sensor[:deviceId] + name: sensor_name, + motion: occupancy, + voltage: sensor[:supplyVoltage][:value] || sensor[:supplyVoltage], + location: sensor[:location], + timestamp: sensor[:timestamp], + last_update: Time.now.in_time_zone($TZ).to_s, + last_update_epoch: Time.now.to_i, + gateway: gateway + } + self[gateway] = @gateways[gateway][sensor_name].dup + self[:gateways] = @gateways.deep_dup when 'CO2-Temperature-and-Humidity' @environment[sensor[:devicename]] = { temp: sensor[:temperature], From 4c1e4cc478f8fe8d17d7787afd57c0b69f437603 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Dec 2019 14:15:23 +0800 Subject: [PATCH 1554/1752] feature(pressac/desk_sensor): PIR sensors use samelogic as Desk sensors --- modules/pressac/sensors/ws_protocol.rb | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index ce407029..66f031a5 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -138,7 +138,7 @@ def on_message(raw_string) sensor = JSON.parse(raw_string, symbolize_names: true) case (sensor[:deviceType] || sensor[:devicetype]) - when 'Under-Desk-Sensor' + when 'Under-Desk-Sensor', 'Occupancy-PIR' # Variations in captialisation of sensor's key names exist amongst different firmware versions sensor_name = sensor[:deviceName].to_sym || sensor[:devicename].to_sym gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym @@ -160,24 +160,6 @@ def on_message(raw_string) } self[gateway] = @gateways[gateway][sensor_name].dup self[:gateways] = @gateways.deep_dup - when 'Occupancy-PIR' - sensor_name = sensor[:deviceName].to_sym - gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym - occupancy = sensor[:motionDetected] == true - @gateways[gateway] ||= {} - @gateways[gateway][sensor_name] = { - id: sensor[:deviceId] - name: sensor_name, - motion: occupancy, - voltage: sensor[:supplyVoltage][:value] || sensor[:supplyVoltage], - location: sensor[:location], - timestamp: sensor[:timestamp], - last_update: Time.now.in_time_zone($TZ).to_s, - last_update_epoch: Time.now.to_i, - gateway: gateway - } - self[gateway] = @gateways[gateway][sensor_name].dup - self[:gateways] = @gateways.deep_dup when 'CO2-Temperature-and-Humidity' @environment[sensor[:devicename]] = { temp: sensor[:temperature], From c8630a3cf59f274e84092dad2d32fce9b1a3621f Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Dec 2019 14:19:57 +0800 Subject: [PATCH 1555/1752] docs(pressac/sensor/example): Add room PIR example --- modules/pressac/sensors/example_output.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/pressac/sensors/example_output.txt b/modules/pressac/sensors/example_output.txt index bc8065bd..48d089de 100644 --- a/modules/pressac/sensors/example_output.txt +++ b/modules/pressac/sensors/example_output.txt @@ -1,3 +1,17 @@ +'Occupancy-PIR' sensor { + "timestamp":"2019-12-03 14:09:34", + "deviceName":"SIP-10-002M", + "deviceId":"058A7F72", + "deviceType":"Occupancy-PIR", + "gatewayName":"SIPG-10-004", + "location":"", + "dBm":"-88", + "security":"No", + "motionDetected":true, + "supplyVoltage":{"value":2.58, + "unit":"V"} +} + Desk sensor: { "deviceid":"050B1EE8", From db3f8644412e7a8c4565b1d4198519f85ae1481f Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Dec 2019 15:43:30 +0800 Subject: [PATCH 1556/1752] feature(office2/RBP): add booking.start_EPOCH --- modules/aca/o365_booking_panel.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 6276de04..a8a6ed0f 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -306,10 +306,11 @@ def expose_bookings(bookings) results = [] bookings.each{ |booking| tz = ActiveSupport::TimeZone.new(booking['start']['timeZone']) # in tz database format: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - start_utc = tz.parse(booking['start']['dateTime']).utc # output looks like: "2019-05-21 15:50:00 UTC" - end_utc = tz.parse(booking['end']['dateTime']).utc - start_time = start_utc.iso8601 # output looks like: "2019-05-21T15:50:00Z+08:00" - end_time = end_utc.iso8601 + start_time = tz.parse(booking['start']['dateTime']).utc.iso8601 # output looks like: "2019-05-21 15:50:00 UTC" + end_time = tz.parse(booking['end']['dateTime']).utc.iso8601 + start_epoch = booking['start_epoch'] + end_epoch = booking['end_epoch'] + attendees = booking['attendees'] name = booking.dig('organizer',:name) || "Private" email = booking.dig('organizer',:email) || "Private" From f3280989cd88b5aef5948df1a5c1ed91732d7021 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Dec 2019 15:44:54 +0800 Subject: [PATCH 1557/1752] feature(pressac/booking_canceller): add a new Logic for cancelling bookings --- modules/pressac/booking_canceller.rb | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 modules/pressac/booking_canceller.rb diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb new file mode 100644 index 00000000..e75024be --- /dev/null +++ b/modules/pressac/booking_canceller.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# Designed to work with Pressac Desk sensors (Pressac::Sensors::WsProtocol) and ACA staff app frontend +module Pressac; end +class ::Pressac::BookingCanceller + include ::Orchestrator::Constants + + descriptive_name 'Cancel Bookings if no Presence detected in room' + generic_name :BookingCanceller + implements :logic + + # Constants that the Room Booking Panel UI (ngx-bookings) will use + RBP_AUTOCANCEL_TRIGGERED = 'pending timeout' + + default_settings({ + bookings_device: "Bookings_1", + desk_management_device: "DeskManagement_1", + sensor_zone_id: "zone-xxxxxxxx", + sensor_name: "Pressac_PIR_sensor_name", + check_every: "1m", + delay_until_cancel: "15m" + }) + + def on_load + on_update + end + + def on_update + @subscriptions ||= [] + @subscriptions.each { |ref| unsubscribe(ref) } + @subscriptions.clear + + @bookings = setting('bookings_device') + @desk_management = setting('desk_management_device') + @zone = setting('sensor_zone_id') + @sensor = setting('sensor_name') + @scan_cycle = setting('check_every') + @cancel_delay = UV::Scheduler.parse_duration(setting('delay_until_cancel')) / 1000 + + schedule.clear + schedule.every(@scan_cycle) { determine_booking_presence } + end + + def determine_booking_presence + now = Time.now.to_i + bookings = system[@bookings][:today] + bookings&.each do |booking| + next unless booking[:start_epoch] > now + @cancel_delay + next unless = system[@desk_management][@zone + ':desk_ids'].include? @sensor # don't cancel if the sensor has not registered yet + motion_detected = system[@desk_management][@zone].include? @sensor + cancel(booking) unless motion_detected + end + end + + def cancel(booking) + system[@bookings].cancel_meeting(booking[:start_epoch], "pending timeout").then do |response| + logger.info "Cancelled #{booking[:Subject]} with response #{response}" + end + end +end From 97b7167dfd0a8888a78bf8c12f4c17f783312e50 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Dec 2019 16:09:01 +0800 Subject: [PATCH 1558/1752] fix(pressac/booking_canceller): DeskManagement is in other system --- modules/pressac/booking_canceller.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index e75024be..6acff235 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -14,6 +14,7 @@ class ::Pressac::BookingCanceller default_settings({ bookings_device: "Bookings_1", + desk_management_system_id: "sys-xxxxxxxx", desk_management_device: "DeskManagement_1", sensor_zone_id: "zone-xxxxxxxx", sensor_name: "Pressac_PIR_sensor_name", @@ -31,7 +32,8 @@ def on_update @subscriptions.clear @bookings = setting('bookings_device') - @desk_management = setting('desk_management_device') + @desk_management_system = setting('desk_management_system_id') + @desk_management_device = setting('desk_management_device') @zone = setting('sensor_zone_id') @sensor = setting('sensor_name') @scan_cycle = setting('check_every') @@ -46,8 +48,9 @@ def determine_booking_presence bookings = system[@bookings][:today] bookings&.each do |booking| next unless booking[:start_epoch] > now + @cancel_delay - next unless = system[@desk_management][@zone + ':desk_ids'].include? @sensor # don't cancel if the sensor has not registered yet - motion_detected = system[@desk_management][@zone].include? @sensor + all_sensors = systems(@desk_management_system)[@desk_management_device] + next unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't cancel if the sensor has not registered yet + motion_detected = all_sensors[@zone].include? @sensor cancel(booking) unless motion_detected end end From 1a7be749ded448ea4e292001e382ec20887fcd65 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Dec 2019 16:15:44 +0800 Subject: [PATCH 1559/1752] feature(pressac/booking_canceller): Add debug output --- modules/pressac/booking_canceller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 6acff235..0f9e8cd9 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -5,7 +5,7 @@ module Pressac; end class ::Pressac::BookingCanceller include ::Orchestrator::Constants - descriptive_name 'Cancel Bookings if no Presence detected in room' + descriptive_name 'Pressac Logic: Cancel Bookings if no Presence detected in room' generic_name :BookingCanceller implements :logic @@ -47,15 +47,18 @@ def determine_booking_presence now = Time.now.to_i bookings = system[@bookings][:today] bookings&.each do |booking| + logger.debug "Canceller: checking booking #{booking[:Subject]} with start #{booking[:start_epoch]} and current time #{now}" next unless booking[:start_epoch] > now + @cancel_delay all_sensors = systems(@desk_management_system)[@desk_management_device] next unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't cancel if the sensor has not registered yet motion_detected = all_sensors[@zone].include? @sensor + logger.debug "Canceller: #{@sensor} presence: #{motion_detected}" cancel(booking) unless motion_detected end end def cancel(booking) + logger.debug "Canceller: CANCELLING booking #{booking[:Subject]}" system[@bookings].cancel_meeting(booking[:start_epoch], "pending timeout").then do |response| logger.info "Cancelled #{booking[:Subject]} with response #{response}" end From f62f4b0967c4e8a119e0597340045a090e7ba572 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 3 Dec 2019 16:33:14 +0800 Subject: [PATCH 1560/1752] fix(office2/RBP): expose new start_epoch --- modules/aca/o365_booking_panel.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index a8a6ed0f..068f6b34 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -328,6 +328,8 @@ def expose_bookings(bookings) results.push({ :Start => start_time, :End => end_time, + :start_epoch => start_epoch, + :end_epoch => end_epoch, :Subject => subject, :id => booking['id'], :icaluid => booking['icaluid'], From e2654db6a3c306eab4ba0c8c78829ec779a7f8af Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 5 Dec 2019 10:49:51 +1000 Subject: [PATCH 1561/1752] fix(o365/rbp): change start/end to check for syms --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index a8a6ed0f..2b67cc7e 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -112,7 +112,7 @@ def fetch_bookings(*args) end def create_meeting(params) - required_fields = ["start", "end"] + required_fields = [:start, :end] check = required_fields - params.keys if check != [] logger.debug "Required fields missing: #{check}" From 20637fcc3c4f3832d02689fc6d975343786de4fd Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Thu, 5 Dec 2019 12:06:02 +1100 Subject: [PATCH 1562/1752] [o365 lib] Allow for https proxy to be used in graph request --- lib/microsoft/office2/client.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 44e99f7f..6bb404fc 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -122,8 +122,14 @@ def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, b headers['Prefer'] = ENV['GRAPH_PREFER'] || "outlook.timezone=\"#{ENV['TZ']}\"" log_graph_request(request_method, data, query, headers, graph_path, endpoints) + graph_api_options = { inactivity_timeout: 25000, keepalive: false } - graph_api = UV::HttpEndpoint.new(@graph_domain, { inactivity_timeout: 25000, keepalive: false }) + if @https_proxy + proxy = URI.parse(@https_proxy) + graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } + end + + graph_api = UV::HttpEndpoint.new(@graph_domain, ) response = graph_api.__send__(uv_request_method, path: graph_path, headers: headers, body: data.to_json, query: query) response.value From fc1430d9256b3d48da64a70c7b23a57ff899248e Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 5 Dec 2019 12:01:45 +1000 Subject: [PATCH 1563/1752] fix(lib/office2): pass connection options to client --- lib/microsoft/office2/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 6bb404fc..8b3a51ed 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -122,14 +122,14 @@ def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, b headers['Prefer'] = ENV['GRAPH_PREFER'] || "outlook.timezone=\"#{ENV['TZ']}\"" log_graph_request(request_method, data, query, headers, graph_path, endpoints) - graph_api_options = { inactivity_timeout: 25000, keepalive: false } + graph_api_options = { inactivity_timeout: 25000, keepalive: false } if @https_proxy proxy = URI.parse(@https_proxy) graph_api_options[:proxy] = { host: proxy.host, port: proxy.port } end - graph_api = UV::HttpEndpoint.new(@graph_domain, ) + graph_api = UV::HttpEndpoint.new(@graph_domain, graph_api_options) response = graph_api.__send__(uv_request_method, path: graph_path, headers: headers, body: data.to_json, query: query) response.value From be44a0219a262b1378a43ded3b8965efb2aba931 Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 5 Dec 2019 14:52:20 +1000 Subject: [PATCH 1564/1752] fix(office2/client): fix error check on response --- lib/microsoft/office2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 8b3a51ed..de6e86a3 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -160,7 +160,7 @@ def check_response(response) when 200, 201, 204 return when 400 - if response['error']['code'] == 'ErrorInvalidIdMalformed' + if response.dig('error', 'code') == 'ErrorInvalidIdMalformed' raise Microsoft::Error::ErrorInvalidIdMalformed.new(response.body) else raise Microsoft::Error::BadRequest.new(response.body) From 51981a7be462863ebc9ed00638e5a7ab6603ade2 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 9 Dec 2019 15:49:08 +0800 Subject: [PATCH 1565/1752] feature(office2/event/recurring): add new func for fast checking recurring availability --- lib/microsoft/office2/events.rb | 106 +++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 51b4280a..21178188 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -123,7 +123,111 @@ def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_i check_response(response) JSON.parse(response.body)&.dig('value', 0) end - + + MAX_RECURRENCE = UV::Scheduler.parse_duration(ENV['O365_MAX_BOOKING_WINDOW'] || '180d') + def unroll_recurrence(start:, interval: nil, recurrence_end: nil) + occurences = [current_occurence = start] + return occurences unless interval + end_date = recurrence_end || (start + MAX_RECURRENCE) + loop do + next_occurence = current_occurence + interval + return occurences if next_occurence > end_date + occurences << next_occurence + end + end + + RECURRENCE_MAP = {daily: 1.days.to_i, weekly: 1.week.to_i, monthly: 1.month.to_i} + def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_end: nil, ignore_icaluid: nil) + interval = RECURRENCE_MAP[recurrence_pattern] + start_epochs = unroll_recurrence(from, interval, recurrence_end) + event_length = to - from + graph_dates = start_epochs.map {|start| startDateTime: graph_date(start), endDateTime: graph_date(start + event_length), '$top': 1} + + # create array of requests that will be sent as bulk to Graph API. Match the array index with + requests = [] + rooms.each do |room_email| + room_events = "/users/#{room_email}/calendar/calendarView" + graph_dates.each_with_index do |date, i| + requests << { id: "#{room_email}:#{start_epochs[i]}", method: 'get', endpoint: room_events, query: date } + end + end + + bulk_response = advanced_bulk_request(requests) + check_response(bulk_response) + responses = JSON.parse(bulk_response.body)['responses'] + # search for conflicts + responses.each do |response| + events = response&.dig('body', 'value') + next unless events.any? + # There is at least one conflict, but if it's the same booking currently being edited, then ignore it + room,start_epoch = response['id'].split(':') + events.each do |event| + unless event['iCalUId'] == ignore_icaluid + conflicts[room] ||= [] + conflicts[room] << start_epoch + all_conflicts << start_epoch + end + end + end + return { conflicts: conflicts, first_conflict: all_conflicts.min } + end + + # responses looks like: + # [ + # { + # "id": "1", + # "status": 200, + # "headers": { + # "Cache-Control": "private", + # "OData-Version": "4.0", + # "Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8" + # }, + # "body": { + # "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('admin%40something.onmicrosoft.com')/calendar/calendarView", + # "value": [] + # } + # }, + # { + # "id": "4", + # "status": 200, + # "headers": { + # "Cache-Control": "private", + # "OData-Version": "4.0", + # "Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8" + # }, + # "body": { + # "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('name.ug7.com1%40something.onmicrosoft.com')/calendar/calendarView", + # "value": [] + # } + # }, + # { + # "id": "3", + # "status": 200, + # "headers": { + # "Cache-Control": "private", + # "OData-Version": "4.0", + # "Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8" + # }, + # "body": { + # "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('name.3B.Room1%40something.onmicrosoft.com')/calendar/calendarView", + # "value": [] + # } + # }, + # { + # "id": "2", + # "status": 200, + # "headers": { + # "Cache-Control": "private", + # "OData-Version": "4.0", + # "Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8" + # }, + # "body": { + # "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('name.3b.combine%40something.onmicrosoft.com')/calendar/calendarView", + # "value": [] + # } + # } + # ] + ## # Create an Office365 event in the mailbox passed in. This may have rooms and other # attendees associated with it and thus create events in other mailboxes also. From df77d946b2f8e4dc7cf9245943303d54a86edd33 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 9 Dec 2019 20:38:10 +0800 Subject: [PATCH 1566/1752] feature(office2/event): Add new avalability check endpoint; support recurring checks; new raw_bulk_request func --- lib/microsoft/office2/client.rb | 40 +++++++++++++++++++++++++++++++++ lib/microsoft/office2/events.rb | 16 ++++++------- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index de6e86a3..b37e12f1 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -135,6 +135,46 @@ def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, b response.value end + # Takes an array of requests and makes them in bulk. Is not limited to the same method or endpoint or params (unlike the above graph_request() ). + # requests: [ + # { + # id: [UNIQUE], + # method: 'get/post/put', + # url: [e.g. /users//events?query=param&$top=999] + # } + # ... + # ] + BULK_CONCURRENT_REQUESTS = 20 # The maximum number of requests Graph API will allow in a single bulk request + BULK_REQUEST_METHOD = :post + UV_OPTIONS = { inactivity_timeout: 25000, keepalive: false } + def raw_bulk_request(all_requests) + bulk_request_endpoint = "#{@graph_domain}/v1.0/$batch" + headers = { + 'Authorization': "Bearer #{graph_token}", + 'Content-Type': "application/json", + 'Prefer': "outlook.timezone=\"#{ENV['TZ']}\"" + } + + uv_options = UV_OPTIONS + if @https_proxy + proxy = URI.parse(@https_proxy) + uv_options[:proxy] = { host: proxy.host, port: proxy.port } + end + graph_api = UV::HttpEndpoint.new(@graph_domain, uv_options) + + sliced_requests = [] + all_requests.each_slice(BULK_CONCURRENT_REQUESTS) do some_requests + request_body = { requests: some_requests } + # log_graph_request(request_method, request_body, {}, headers, graph_path, endpoints) + sliced_requests << graph_api.__send__(BULK_REQUEST_METHOD, path: bulk_request_endpoint, headers: headers, body: request_body.to_json, query: {}) + end + + thread = Libuv::Reactor.current + sliced_responses = thread.all(sliced_requests).value + all_responses = sliced_responses.map{ |bulk_body| JSON.parse(bulk_body)['responses'] }.flatten + return all_responses + end + def graph_date(date) Time.at(date.to_i).utc.iso8601.split("+")[0] end diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 21178188..7b71aa82 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -136,25 +136,23 @@ def unroll_recurrence(start:, interval: nil, recurrence_end: nil) end end - RECURRENCE_MAP = {daily: 1.days.to_i, weekly: 1.week.to_i, monthly: 1.month.to_i} + RECURRENCE_MAP = { daily: 1.days.to_i, weekly: 1.week.to_i, monthly: 1.month.to_i } def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_end: nil, ignore_icaluid: nil) interval = RECURRENCE_MAP[recurrence_pattern] start_epochs = unroll_recurrence(from, interval, recurrence_end) event_length = to - from - graph_dates = start_epochs.map {|start| startDateTime: graph_date(start), endDateTime: graph_date(start + event_length), '$top': 1} + query_strings = start_epochs.map { |start| "?startDateTime=#{graph_date(start)}&endDateTime=#{graph_date(start + event_length)}&$top=1" } # create array of requests that will be sent as bulk to Graph API. Match the array index with requests = [] rooms.each do |room_email| - room_events = "/users/#{room_email}/calendar/calendarView" - graph_dates.each_with_index do |date, i| - requests << { id: "#{room_email}:#{start_epochs[i]}", method: 'get', endpoint: room_events, query: date } + room_events_endpoint = "/users/#{room_email}/calendar/calendarView" + query_strings.each_with_index do |query_string, i| + requests << { id: "#{room_email}:#{start_epochs[i]}", method: 'get', url: room_events_endpoint + query_string } end end - bulk_response = advanced_bulk_request(requests) - check_response(bulk_response) - responses = JSON.parse(bulk_response.body)['responses'] + responses = raw_bulk_request(requests) # search for conflicts responses.each do |response| events = response&.dig('body', 'value') @@ -172,7 +170,7 @@ def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_ return { conflicts: conflicts, first_conflict: all_conflicts.min } end - # responses looks like: + # responses look like: # [ # { # "id": "1", From 12144089d2c24ea8b04c7b892d639c42acb953c4 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 11 Dec 2019 10:24:02 +1100 Subject: [PATCH 1567/1752] fix(joiner): only retry load if on_load returns false --- modules/aca/joiner.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/aca/joiner.rb b/modules/aca/joiner.rb index e2c80075..0b365c61 100644 --- a/modules/aca/joiner.rb +++ b/modules/aca/joiner.rb @@ -54,7 +54,7 @@ class Aca::Joiner generic_name :Joiner implements :logic - + def on_load @retry_load = nil @loaded = false @@ -261,7 +261,7 @@ def retry_load @retry_load.cancel unless @retry_load.nil? @retry_load = nil - if on_update + if on_update != false # Wait 30 seconds and check room list is built @retry_load = schedule.in(30_000) do @retry_load = nil @@ -339,4 +339,3 @@ def inform(join, rooms) thread.finally(*promises) end end - From c7cb0c14570534a7e6612bbc4f8e80a8398fc0ed Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 11 Dec 2019 21:57:33 +1100 Subject: [PATCH 1568/1752] feat(point_grab): add cogni point driver --- modules/point_grab/cogni_point.rb | 307 ++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 modules/point_grab/cogni_point.rb diff --git a/modules/point_grab/cogni_point.rb b/modules/point_grab/cogni_point.rb new file mode 100644 index 00000000..3609e642 --- /dev/null +++ b/modules/point_grab/cogni_point.rb @@ -0,0 +1,307 @@ +require "uri" +require "securerandom" + +module PointGrab; end + +# Documentation: https://aca.im/driver_docs/PointGrab/CogniPointAPI2-1.pdf + +class PointGrab::CogniPoint + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :service + generic_name :FloorManagement + descriptive_name "PointGrab CogniPoint REST API" + + keepalive false + + default_settings({ + user_id: "10000000", + app_key: "c5a6adc6-UUID-46e8-b72d-91395bce9565", + floor_mappings: { + "CogniPoint_floor_id" => "zone_id" + }, + area_mappings: { + "CogniPoint_area_id" => "alternative_area_id" + } + }) + + def on_load + on_update + end + + def on_update + @user_id = setting(:user_id) + @app_key = setting(:app_key) + + @auth_token ||= "" + @auth_expiry ||= 1.minute.ago + + @floor_mappings = setting(:floor_mappings) + @area_mappings = setting(:area_mappings) + + # @floor_details["zone_id"] = { + # "area_1": { + # "capacity": 100, + # "people_count": 90 + # }} + @floor_details ||= {} + end + + def expire_token! + @auth_expiry = 1.minute.ago + end + + def token_expired? + @auth_expiry < Time.now + end + + def get_token + return @auth_token unless token_expired? + + post("/be/cp/oauth2/token", body: "grant_type=client_credentials", headers: { + "Content-Type" => "application/x-www-form-urlencoded", + "Accept" => "application/json", + "Authorization" => [@user_id, @app_key] + }) { |response| + body = response.body + logger.debug { "received login response: #{body}" } + + if response.status == 200 + resp = JSON.parse(body, symbolize_names: true) + @auth_expiry = Time.now + (resp[:expires_in] - 5) + @auth_token = "Bearer #{resp[:token]}" + else + logger.error "authentication failed with HTTP #{response.status}" + raise "failed to obtain access token" + end + }.value + end + + def get_request(path) + token = get_token + response = get(path, headers: { + "Accept" => "application/json", + "Authorization" => token + }) { |response| + if (200..299).include? response.status + JSON.parse(response.body, symbolize_names: true) + else + expire_token! if response.status == 401 + raise "unexpected response #{response.status}\n#{response.body}" + end + }.value + end + + def customers + customers = get_request("/be/cp/v2/customers") + # [{id: "", name: ""}] + self[:customers] = customers[:endCustomers] + end + + # [{id: "", name: "", customerId: "", + # location: {houseNo: "", street: "", city: "", county: "", state: "", + # country: "", zip: "", geoPosition: {latitude: 0.0, longitude: 0.0}}}] + def sites + sites = get_request("/be/cp/v2/sites") + self[:sites] = sites[:sites] + end + + def site(site_id) + get_request("/be/cp/v2/sites/#{site_id}") + end + + # [{id: "", name: "", siteId: "", location: {..}}] + def buildings(site_id) + buildings = get_request("/be/cp/v2/sites/#{site_id}/buildings") + self[:buildings] = buildings[:buildings] + end + + def building(site_id, building_id) + get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}") + end + + # [{ id: "", name: "", floorNumber: "", floorPlanURL: "", widthDistance: 0.0, lengthDistance: 0.0 }] + def floors(site_id, building_id) + floors = get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/floors") + self[:floors] = floors[:floors] + end + + def floor(site_id, building_id, floor_id) + get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/floors/#{floor_id}") + end + + # [{floorId: "", areas: [{id: "", name: "", length: 0.0, width: 0.0, centerX: 0.0, centerY: 0.0 + # rotation: 0, frequency: 0, deviceIDs: [""], applications: [{areaType: "", applicationType: ""}] + # metricPositions: [{posX: 0.0, posY: 0.0}], geoPositions: [{latitude: 0.0, longitude: 0.0}] }] }] + def building_areas(site_id, building_id) + floors = get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/areas") + self[:floor_areas] = floors[:floorsAreas] + end + + # as above + def areas(site_id, building_id, floor_id) + areas = get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/floors/#{floor_id}/areas") + self[:areas] = areas[:areas] + end + + def area(site_id, building_id, floor_id, area_id) + get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/floors/#{floor_id}/areas/#{area_id}") + end + + # enum NotificationType + # Counting + # Traffic + # end + + def subscribe(handler_uri, auth_token = SecureRandom.uuid, events = "Counting") + # Ensure the handler is a valid URI + URI.parse handler_uri + + token = get_token + post( + "/be/cp/v2/telemetry/subscriptions", + body: { + subscriptionType: "PUSH", + notificationType: events.to_s.upcase, + endpoint: handler_uri, + token: auth_token, + }.to_json, + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => token, + } + ) { |response| + body = response.body + logger.debug { "received login response: #{body}" } + + if (200..299).include? response.status + JSON.parse(body, symbolize_names: true) + else + logger.error "authentication failed with HTTP #{response.status}" + raise "failed to obtain access token" + end + } + end + + # [{id: "", name: "", started: false, endpoint: "", uri: "", notificationType: "", subscriptionType: ""}] + def subscriptions + get_request("/be/cp/v2/telemetry/subscriptions") + end + + def delete_subscription(id) + token = get_token + delete("/be/cp/v2/telemetry/subscriptions/#{id}", + headers: { + "Accept" => "application/json", + "Authorization" => token, + } + ) { |response| (200..299).include?(response.status) ? :success : :abort } + end + + def update_subscription(id, started = true) + token = get_token + patch( + "/be/cp/v2/telemetry/subscriptions/#{id}", + body: {started: started}.to_json, + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => token, + } + ) { |response| (200..299).include?(response.status) ? :success : :abort } + end + + def get_area_details(area_id) + return @area_details[area_id] if @area_details && @area_details[area_id] + + site_lookup = {} + building_lookup = {} + floor_lookup = {} + area_details = {} + + site_ids = sites.map do |site| + id = site[:id] + site_lookup[id] = site[:name] + id + end + + site_ids.each do |site_id| + site_name = site_lookup[site_id] + + building_ids = buildings(site_id).map do |building| + id = building[:id] + building_lookup[id] = building[:name] + id + end + + building_ids.each do |building_id| + building_name = building_lookup[building_id] + + floors(site_id, building_id).each do |floor| + floor_lookup[floor[:id]] = floor[:name] + end + + building_areas(site_id, building_id).each do |floor| + floor_id = floor[:floorId] + floor_name = floor_lookup[floor_id] + + floor[:areas].each do |area| + floor_area_id = area[:id] + + area_details[floor_area_id] = { + site_id: site_id, + site_name: site_name, + building_id: building_id, + building_name: building_name, + floor_id: floor_id, + floor_name: floor_name + }.merge(area) + end + end + end + end + + @site_lookup = site_lookup + @building_lookup = building_lookup + @floor_lookup = floor_lookup + @area_details = area_details + self[:areas] = area_details + + @area_details[area_id] + end + + # this data is posted to the subscription endpoint + # we need to implement webhooks for this to work properly + # {areaId: "", devices: [""], type: "", timestamp: 0, count: 0} + def update_count(count_json) + count = JSON.parse(count_json, symbolize_names: true) + area_id = count[:areaId] + people_count = count[:count] + + # self["zone_id"] = { + # "area_1": { + # "capacity": 100, + # "people_count": 90 + # }} + + area_details = get_area_details(area_id) + if area_details + # Grab the details + floor_id = area_details[:floor_id] + floor_mapping = @floor_mappings[floor_id] || floor_id + area_mapping = @area_mappings[area_id] || area_id + + # update the details + floor_areas = @floor_details[floor_mapping] || {} + floor_areas[area_mapping] = { + people_count: people_count + } + @floor_details[floor_mapping] = floor_areas + + self[floor_mapping] = floor_areas.dup + end + end +end From 0cae03ac4997e4d190e1735f0037fe0b51796f84 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 12 Dec 2019 12:31:02 +1000 Subject: [PATCH 1569/1752] feat(atlona): virtual switcher can used input output names also adds support for input arrays where the first in a list that is active will be switched --- .../atlona/omni_stream/virtual_switcher.rb | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/modules/atlona/omni_stream/virtual_switcher.rb b/modules/atlona/omni_stream/virtual_switcher.rb index 4da9e559..5c734bbb 100644 --- a/modules/atlona/omni_stream/virtual_switcher.rb +++ b/modules/atlona/omni_stream/virtual_switcher.rb @@ -28,6 +28,37 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil) map.each do |inp, outs| begin + # Select the first input where there is a video signal + if inp.is_a?(Array) + selected = nil + inp.each do |check_inp| + check_inp = check_inp.to_s + input, session_index = inputs[check_inp] + next if input.nil? + + sessions = input[:sessions] + next unless sessions && sessions[session_index] + session = sessions[session_index] + video_input = session[:audio][:encoder] + + video_ins = Array(input[:inputs]).select { |vin| vin[:name] == video_input } + next unless video_ins.length > 0 + + if video_ins[0][:cabledetect] + selected = check_inp + break + end + end + + if selected + inp = selected + logger.debug { "found active input on #{inp}" } + else + inp = inp.last + logger.debug { "no active input found, switching to #{inp}" } + end + end + inp = inp.to_s if inp == '0' enable = enable_override.nil? ? false : enable_override @@ -83,6 +114,7 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil) next end + logger.debug { "switching #{inp} => #{out}" } output.switch(output: index, video_ip: video_ip, video_port: video_port, audio_ip: audio_ip, audio_port: audio_port, enable: enable) end rescue => e @@ -153,11 +185,21 @@ def get_encoders num_sessions = mod[:num_sessions] if mod[:type] == :encoder && num_sessions (1..num_sessions).each do |num| - encoder_mapping[input.to_s] = [mod, num - 1] - info_mapping[input.to_s] = { + mapping_details = [mod, num - 1] + name = begin + mod[:sessions][num - 1][:name] + rescue + nil + end + encoder_mapping[name] = mapping_details if name + encoder_mapping[input.to_s] = mapping_details + + mapping_details = { encoder: "#{@encoder_name}_#{index}", session: num } + info_mapping[input.to_s] = mapping_details + info_mapping[name] = mapping_details if name input += 1 end @@ -188,11 +230,22 @@ def get_decoders num_outputs = mod[:num_outputs] if mod[:type] == :decoder && num_outputs (1..num_outputs).each do |num| - decoder_mapping[output.to_s] = [mod, num] - info_mapping[output.to_s] = { + mapping_details = [mod, num] + name = begin + mod[:outputs][num - 1][:name] + rescue + nil + end + + decoder_mapping[name] = mapping_details if name + decoder_mapping[output.to_s] = mapping_details + + mapping_details = { encoder: "#{@decoder_name}_#{index}", output: num } + info_mapping[output.to_s] = mapping_details + info_mapping[name] = mapping_details if name output += 1 end From cf2cd8e73427c448abb6eefae7d0879f3974f9fd Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 12 Dec 2019 13:42:10 +0800 Subject: [PATCH 1570/1752] fix(office2/event/availability): Test in UAT fix many issues. --- lib/microsoft/office2/client.rb | 13 +++--- lib/microsoft/office2/events.rb | 70 +++++---------------------------- 2 files changed, 15 insertions(+), 68 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index b37e12f1..e9b2896a 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -144,15 +144,15 @@ def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, b # } # ... # ] - BULK_CONCURRENT_REQUESTS = 20 # The maximum number of requests Graph API will allow in a single bulk request + BULK_CONCURRENT_REQUESTS = 15 # The maximum number of requests Graph API will allow in a single bulk request BULK_REQUEST_METHOD = :post UV_OPTIONS = { inactivity_timeout: 25000, keepalive: false } def raw_bulk_request(all_requests) bulk_request_endpoint = "#{@graph_domain}/v1.0/$batch" headers = { - 'Authorization': "Bearer #{graph_token}", - 'Content-Type': "application/json", - 'Prefer': "outlook.timezone=\"#{ENV['TZ']}\"" + 'Authorization' => "Bearer #{graph_token}", + 'Content-Type' => "application/json", + 'Prefer' => "outlook.timezone=\"#{ENV['TZ']}\"" } uv_options = UV_OPTIONS @@ -163,15 +163,14 @@ def raw_bulk_request(all_requests) graph_api = UV::HttpEndpoint.new(@graph_domain, uv_options) sliced_requests = [] - all_requests.each_slice(BULK_CONCURRENT_REQUESTS) do some_requests + all_requests.each_slice(BULK_CONCURRENT_REQUESTS) do |some_requests| request_body = { requests: some_requests } - # log_graph_request(request_method, request_body, {}, headers, graph_path, endpoints) sliced_requests << graph_api.__send__(BULK_REQUEST_METHOD, path: bulk_request_endpoint, headers: headers, body: request_body.to_json, query: {}) end thread = Libuv::Reactor.current sliced_responses = thread.all(sliced_requests).value - all_responses = sliced_responses.map{ |bulk_body| JSON.parse(bulk_body)['responses'] }.flatten + all_responses = sliced_responses.map{ |single_bulk_response| JSON.parse(single_bulk_response.body)['responses'] }.flatten return all_responses end diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 7b71aa82..67899c14 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -133,26 +133,29 @@ def unroll_recurrence(start:, interval: nil, recurrence_end: nil) next_occurence = current_occurence + interval return occurences if next_occurence > end_date occurences << next_occurence + current_occurence = next_occurence end end RECURRENCE_MAP = { daily: 1.days.to_i, weekly: 1.week.to_i, monthly: 1.month.to_i } def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_end: nil, ignore_icaluid: nil) interval = RECURRENCE_MAP[recurrence_pattern] - start_epochs = unroll_recurrence(from, interval, recurrence_end) + start_epochs = unroll_recurrence(start: from, interval: interval, recurrence_end: recurrence_end) event_length = to - from - query_strings = start_epochs.map { |start| "?startDateTime=#{graph_date(start)}&endDateTime=#{graph_date(start + event_length)}&$top=1" } + query_strings = start_epochs.map { |start| "?startDateTime=#{graph_date(start)}&endDateTime=#{graph_date(start + event_length)}&$top=99" } # create array of requests that will be sent as bulk to Graph API. Match the array index with requests = [] rooms.each do |room_email| room_events_endpoint = "/users/#{room_email}/calendar/calendarView" query_strings.each_with_index do |query_string, i| - requests << { id: "#{room_email}:#{start_epochs[i]}", method: 'get', url: room_events_endpoint + query_string } + requests << { id: "#{room_email}:#{start_epochs[i]}", method: 'GET', url: room_events_endpoint + query_string } end end responses = raw_bulk_request(requests) + conflicts = {} + all_conflicts = [] # search for conflicts responses.each do |response| events = response&.dig('body', 'value') @@ -162,70 +165,15 @@ def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_ events.each do |event| unless event['iCalUId'] == ignore_icaluid conflicts[room] ||= [] - conflicts[room] << start_epoch - all_conflicts << start_epoch + istart_epoch = start_epoch.to_i + conflicts[room] << istart_epoch + all_conflicts << istart_epoch end end end return { conflicts: conflicts, first_conflict: all_conflicts.min } end - # responses look like: - # [ - # { - # "id": "1", - # "status": 200, - # "headers": { - # "Cache-Control": "private", - # "OData-Version": "4.0", - # "Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8" - # }, - # "body": { - # "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('admin%40something.onmicrosoft.com')/calendar/calendarView", - # "value": [] - # } - # }, - # { - # "id": "4", - # "status": 200, - # "headers": { - # "Cache-Control": "private", - # "OData-Version": "4.0", - # "Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8" - # }, - # "body": { - # "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('name.ug7.com1%40something.onmicrosoft.com')/calendar/calendarView", - # "value": [] - # } - # }, - # { - # "id": "3", - # "status": 200, - # "headers": { - # "Cache-Control": "private", - # "OData-Version": "4.0", - # "Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8" - # }, - # "body": { - # "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('name.3B.Room1%40something.onmicrosoft.com')/calendar/calendarView", - # "value": [] - # } - # }, - # { - # "id": "2", - # "status": 200, - # "headers": { - # "Cache-Control": "private", - # "OData-Version": "4.0", - # "Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8" - # }, - # "body": { - # "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('name.3b.combine%40something.onmicrosoft.com')/calendar/calendarView", - # "value": [] - # } - # } - # ] - ## # Create an Office365 event in the mailbox passed in. This may have rooms and other # attendees associated with it and thus create events in other mailboxes also. From efe74a87b5cd26ee89a6abd56152d9036a59afbe Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 12 Dec 2019 16:39:48 +1000 Subject: [PATCH 1571/1752] feat(atlona): update drivers to support auto switching based on input detection --- modules/atlona/omni_stream/auto_switcher.rb | 116 ++++++++++++++++++ .../atlona/omni_stream/virtual_switcher.rb | 8 +- modules/atlona/omni_stream/ws_protocol.rb | 7 +- 3 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 modules/atlona/omni_stream/auto_switcher.rb diff --git a/modules/atlona/omni_stream/auto_switcher.rb b/modules/atlona/omni_stream/auto_switcher.rb new file mode 100644 index 00000000..3c354ca5 --- /dev/null +++ b/modules/atlona/omni_stream/auto_switcher.rb @@ -0,0 +1,116 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Atlona; end +module Atlona::OmniStream; end + +class Atlona::OmniStream::AutoSwitcher + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + descriptive_name 'Atlona Omnistream Auto Switcher' + generic_name :AutoSwitcher + implements :logic + + def on_load + # input id => true / false - presence detected + @last_known_state = {} + on_update + end + + def on_update + switcher = setting(:virtual_switcher) || :Switcher + + # { "output": ["input_1", "input_2"] } + auto_switch = setting(:auto_switch) || {} + self[:enabled] = @enabled = setting(:auto_switch_enabled) || true + + # Bind to all the encoders for presence detection + if @virtual_switcher != switcher || @auto_switch != auto_switch + if @auto_switch != auto_switch + @auto_switch = auto_switch + switch_all + end + @virtual_switcher = switcher + subscribe_virtual_inputs + end + end + + def enabled(state) + self[:enabled] = @enabled = !!state + define_setting(:auto_switch_enabled, @enabled) + switch_all + nil + end + + + protected + + + def switch_all + return unless @enabled + + @auto_switch.each do |output, inputs| + system[@virtual_switcher].switch({ inputs => output }) + end + end + + def subscribe_virtual_inputs + # unsubscribe to changes + clear_encoder_subs + unsubscribe(@virtual_input_sub) if @virtual_input_sub + + # subscribe to switcher details + @virtual_input_sub = system.subscribe(@virtual_switcher, :input_mappings) do |notify| + logger.debug { "Detected change of input mappings on #{@virtual_switcher} - resubscribing" } + subscribe_encoders notify.value + end + end + + def clear_encoder_subs + @subscriptions ||= [] + @subscriptions.each { |ref| unsubscribe(ref) } + @subscriptions.clear + end + + def subscribe_encoders(input_mappings) + clear_encoder_subs + @auto_switch.each do |output, inputs| + inputs.each do |input| + input = input.to_s + input_details = input_mappings[input] + session_index = input_details[:session] - 1 + encoder_name = input_details[:encoder] + + encoder = system[encoder_name] + next if encoder.nil? + + sessions = encoder[:sessions] + next unless sessions + vc2_name = sessions.dig(session_index, :video, :encoder) + next unless vc2_name + input_name = (Array(encoder[:encoders]).select { |enc| enc[:name] == vc2_name }).dig(0, :input) + next unless input_name + + @subscriptions << system.subscribe(encoder_name, :inputs) do |notify| + if @enabled + check_auto_switch output, inputs, input, input_name, notify.value + end + end + end + end + end + + def check_auto_switch(auto_output, auto_inputs, checking_input, input_name, encoder_inputs) + input_details = (encoder_inputs.select { |enc_inp| enc_inp[:name] == input_name })[0] + return unless input_details + + current_value = @last_known_state[checking_input] + new_value = input_details[:cabledetect] + if current_value != new_value + logger.debug { "Switching #{auto_inputs} => #{auto_output} as detected change on encoder input #{checking_input}" } + @last_known_state[checking_input] = new_value + system[@virtual_switcher].switch({ auto_inputs => auto_output }) + end + end +end diff --git a/modules/atlona/omni_stream/virtual_switcher.rb b/modules/atlona/omni_stream/virtual_switcher.rb index 5c734bbb..e2286613 100644 --- a/modules/atlona/omni_stream/virtual_switcher.rb +++ b/modules/atlona/omni_stream/virtual_switcher.rb @@ -37,9 +37,11 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil) next if input.nil? sessions = input[:sessions] - next unless sessions && sessions[session_index] - session = sessions[session_index] - video_input = session[:audio][:encoder] + next unless sessions + encoder_name = sessions.dig(session_index, :video, :encoder) + next unless encoder_name + video_input = (Array(input[:encoders]).select { |encoder| encoder[:name] == encoder_name }).dig(0, :input) + next unless video_input video_ins = Array(input[:inputs]).select { |vin| vin[:name] == video_input } next unless video_ins.length > 0 diff --git a/modules/atlona/omni_stream/ws_protocol.rb b/modules/atlona/omni_stream/ws_protocol.rb index 2fdc93a4..caa19861 100644 --- a/modules/atlona/omni_stream/ws_protocol.rb +++ b/modules/atlona/omni_stream/ws_protocol.rb @@ -65,7 +65,7 @@ def disconnected :systeminfo, :alarms, :net, # encoders - :hdmi_input, :sessions, + :hdmi_input, :vc2, :sessions, # decoders :ip_input, :hdmi_output @@ -282,7 +282,7 @@ def on_message(raw_string) id = resp[:id].to_sym self.__send__(id) if self.respond_to?(id) return - end + end # get case resp[:id] @@ -300,6 +300,7 @@ def on_message(raw_string) hdmi_output else hdmi_input + vc2 sessions end @@ -309,6 +310,8 @@ def on_message(raw_string) self[:alarms] = data when 'hdmi_input' self[:inputs] = data + when 'vc2' + self[:encoders] = data when 'sessions' sessions = [] num_sessions = 0 From 2ec98d6f7f3becaf04133312d987f6452fed7dd6 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 12 Dec 2019 17:48:40 +0800 Subject: [PATCH 1572/1752] feature(office2/odata-extensions): disable via ENV var (optional) --- lib/microsoft/office2/events.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 67899c14..210f318a 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -83,7 +83,7 @@ def get_bookings(mailboxes:, calendargroup_id: nil, calendar_id: nil, options:{} query[:startDateTime] = graph_date(options[:bookings_from]) if options[:bookings_from] query[:endDateTime] = graph_date(options[:bookings_to]) if options[:bookings_to] query[:'$filter'] = "createdDateTime gt #{created_from}" if options[:created_from] - query[:'$expand'] = "extensions($filter=id eq '#{options[:extension_name]}')" if options[:extension_name] + query[:'$expand'] = "extensions($filter=id eq '#{options[:extension_name]}')" if options[:extension_name] && ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' # Make the request, check the repsonse then parse it bulk_response = graph_request(request_method: 'get', endpoints: endpoints, query: query, bulk: true) @@ -292,7 +292,7 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni ) # If extensions exist we must make a separate request to add them - if options[:extensions].present? + if options[:extensions].present? && ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' options[:extensions] = options[:extensions].dup options[:extensions]["@odata.type"] = "microsoft.graph.openTypeExtension" options[:extensions]["extensionName"] = "Com.Acaprojects.Extensions" @@ -400,15 +400,16 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } } if organizer - - ext = { - "@odata.type": "microsoft.graph.openTypeExtension", - "extensionName": "Com.Acaprojects.Extensions" - } - extensions.each do |ext_key, ext_value| - ext[ext_key] = ext_value + if ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' + ext = { + "@odata.type": "microsoft.graph.openTypeExtension", + "extensionName": "Com.Acaprojects.Extensions" + } + extensions.each do |ext_key, ext_value| + ext[ext_key] = ext_value + end + event_json[:extensions] = [ext] end - event_json[:extensions] = [ext] if recurrence recurrence_end_date = Time.at(recurrence[:end]).to_datetime.in_time_zone(timezone) From 0eb13ac8928563340be0fa9b69421818892e1fd6 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 13 Dec 2019 13:22:41 +1000 Subject: [PATCH 1573/1752] fix(point grab): update how patch method is called also add support for area capacities --- modules/point_grab/cogni_point.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/point_grab/cogni_point.rb b/modules/point_grab/cogni_point.rb index 3609e642..b60c0762 100644 --- a/modules/point_grab/cogni_point.rb +++ b/modules/point_grab/cogni_point.rb @@ -23,7 +23,10 @@ class PointGrab::CogniPoint "CogniPoint_floor_id" => "zone_id" }, area_mappings: { - "CogniPoint_area_id" => "alternative_area_id" + "CogniPoint_area_id" => { + id: "alternative_area_id", + capacity: 4 + } } }) @@ -155,7 +158,7 @@ def area(site_id, building_id, floor_id, area_id) # Traffic # end - def subscribe(handler_uri, auth_token = SecureRandom.uuid, events = "Counting") + def subscribe_telemetry(handler_uri, auth_token = SecureRandom.uuid, events = "Counting") # Ensure the handler is a valid URI URI.parse handler_uri @@ -192,6 +195,8 @@ def subscriptions end def delete_subscription(id) + # Stop subscription + update_subscription(id, false) token = get_token delete("/be/cp/v2/telemetry/subscriptions/#{id}", headers: { @@ -203,7 +208,8 @@ def delete_subscription(id) def update_subscription(id, started = true) token = get_token - patch( + request( + :patch, "/be/cp/v2/telemetry/subscriptions/#{id}", body: {started: started}.to_json, headers: { @@ -292,13 +298,14 @@ def update_count(count_json) # Grab the details floor_id = area_details[:floor_id] floor_mapping = @floor_mappings[floor_id] || floor_id - area_mapping = @area_mappings[area_id] || area_id + area_mapping = @area_mappings[area_id] || {} + area_id = area_mapping[:id] || area_id # update the details floor_areas = @floor_details[floor_mapping] || {} - floor_areas[area_mapping] = { + floor_areas[area_id] = { people_count: people_count - } + }.merge(area_mapping) @floor_details[floor_mapping] = floor_areas self[floor_mapping] = floor_areas.dup From cddf1c2ace89c9ab00e81cd21f62a452e943f1a1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 13 Dec 2019 13:24:29 +1000 Subject: [PATCH 1574/1752] feat(atlona): auto-switcher to poll inputs for changes --- modules/atlona/omni_stream/auto_switcher.rb | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/modules/atlona/omni_stream/auto_switcher.rb b/modules/atlona/omni_stream/auto_switcher.rb index 3c354ca5..266a310e 100644 --- a/modules/atlona/omni_stream/auto_switcher.rb +++ b/modules/atlona/omni_stream/auto_switcher.rb @@ -25,12 +25,17 @@ def on_update auto_switch = setting(:auto_switch) || {} self[:enabled] = @enabled = setting(:auto_switch_enabled) || true + schedule.clear + poll_every = setting(:auto_switch_poll_every) || '3s' + schedule.every(poll_every) { poll_inputs } + # Bind to all the encoders for presence detection if @virtual_switcher != switcher || @auto_switch != auto_switch if @auto_switch != auto_switch @auto_switch = auto_switch switch_all end + @virtual_switcher = switcher subscribe_virtual_inputs end @@ -47,6 +52,27 @@ def enabled(state) protected + def poll_inputs + input_mappings = system[@virtual_switcher][:input_mappings] + encoders = [] + @auto_switch.each do |output, inputs| + inputs.each do |input| + input = input.to_s + input_details = input_mappings[input] + next unless input_details + + encoders << input_details[:encoder] + end + end + + encoders.uniq! + encoders.each do |encoder| + system[encoder].hdmi_input + end + + nil + end + def switch_all return unless @enabled From f864e3a3284657c8b416cade852a3389a70556c4 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 13 Dec 2019 13:53:50 +1000 Subject: [PATCH 1575/1752] feat(atlona): ignore enabled changes where there is no change --- modules/atlona/omni_stream/auto_switcher.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/atlona/omni_stream/auto_switcher.rb b/modules/atlona/omni_stream/auto_switcher.rb index 266a310e..d22be308 100644 --- a/modules/atlona/omni_stream/auto_switcher.rb +++ b/modules/atlona/omni_stream/auto_switcher.rb @@ -42,9 +42,12 @@ def on_update end def enabled(state) - self[:enabled] = @enabled = !!state - define_setting(:auto_switch_enabled, @enabled) - switch_all + state = !!state + if state != @enabled + self[:enabled] = @enabled = state + define_setting(:auto_switch_enabled, @enabled) + switch_all + end nil end From b8dbd58d70e84c6c013f64276236d6053d6d78fa Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 16 Dec 2019 17:32:26 +1000 Subject: [PATCH 1576/1752] feat: add office rnd driver --- modules/office_rnd/api.rb | 332 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 modules/office_rnd/api.rb diff --git a/modules/office_rnd/api.rb b/modules/office_rnd/api.rb new file mode 100644 index 00000000..5fbb88bb --- /dev/null +++ b/modules/office_rnd/api.rb @@ -0,0 +1,332 @@ +require "uri" + +module OfficeRnd; end + +# Documentation: https://developer.officernd.com/#resources + +class OfficeRnd::API + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :service + generic_name :Bookings + descriptive_name "OfficeRnD REST API" + + keepalive false + + default_settings({ + client_id: "10000000", + client_secret: "c5a6adc6-UUID-46e8-b72d-91395bce9565", + scopes: ["officernd.api.read", "officernd.api.write"], + organization: "org-slug" + }) + + def on_load + on_update + end + + def on_update + @resource_name_cache = {} + + @client_id = setting(:client_id) + @client_secret = setting(:client_secret) + @scopes = setting(:scopes) + @organization = setting(:organization) + + @auth_token ||= "" + @auth_expiry ||= 1.minute.ago + end + + def expire_token! + @auth_expiry = 1.minute.ago + end + + def token_expired? + @auth_expiry < Time.now + end + + def get_token + return @auth_token unless token_expired? + + client = ::UV::HttpEndpoint.new("https://identity.officernd.com") + promise = client.post(path: "/oauth/token", body: { + "client_id" => @client_id, + "client_secret" => @client_secret, + "grant_type" => "client_credentials", + "scope" => @scopes.join(' '), + }.map { |k, v| encode_param(k, v) }.join('&'), headers: { + "Content-Type" => "application/x-www-form-urlencoded", + "Accept" => "application/json", + }).then { |response| + body = response.body + logger.debug { "received login response: #{body}" } + + if (200..299).include? response.status + resp = JSON.parse(body, symbolize_names: true) + @auth_expiry = Time.now + (resp[:expires_in] - 5) + @auth_token = "Bearer #{resp[:access_token]}" + else + logger.error "authentication failed with HTTP #{response.status}" + raise "failed to obtain access token" + end + } + promise.catch { |error| + logger.debug { "error response: #{error.inspect}" } + } + promise.value + end + + def get_request(path, query = nil, url_base: "/api/v1/organizations/#{@organization}") + token = get_token + response = get("#{url_base}#{path}", headers: { + "Accept" => "application/json", + "Authorization" => token + }, query: query) { |response| + if (200..299).include? response.status + JSON.parse(response.body, symbolize_names: true) + else + expire_token! if response.status == 401 + raise "unexpected response #{response.status}\n#{response.body}" + end + }.value + end + + # Floor + ########################################################################### + + # Get a floor + # + def floor(floor_id) + get_request("/floors/#{floor_id}") + end + + # Get floors + # + def floors(office_id = nil, name = nil) + params = {} + params["office"] = office_id if office_id + params["name"] = name if name + params = nil if params.empty? + get_request("/floors", params) + end + + # Booking + ########################################################################### + + # Get bookings for a resource for a given time span + # + def resource_bookings( + resource_id, + range_start = Time.now - 5.minutes.to_i, + range_end = Time.now + 24.hours.to_i, + office_id = nil, + member_id = nil, + team_id = nil + ) + params = {} + params["office"] = office_id if office_id + params["member"] = member_id if member_id + params["team"] = team_id if team_id + + params["start"] = range_start.iso8601 if range_start.is_a?(Time) + params["end"] = range_end.iso8601 if range_end.is_a?(Time) + params["start"] = range_start.iso8601 if range_start.is_a?(String) + params["end"] = range_end.iso8601 if range_end.is_a?(String) + params["start"] = Time.at(range_start).iso8601 if range_start.is_a?(Integer) + params["end"] = Time.at(range_end).iso8601 if range_end.is_a?(Integer) + + params = nil if params.empty? + get_request("/bookings/occurrences", params).select do |booking| + booking[:resourceId] == resource_id + end + end + + def resource_free( + resource_id, + range_start = Time.now - 5.minutes.to_i, + range_end = Time.now + 24.hours.to_i + ) + resource_name = @resource_name_cache[resource_id] || resource(resource_id)[:name] + @resource_name_cache[resource_id] = resource_name + resources( + nil, + nil, + range_start, + range_end, + name: resource_name + ) + end + + # Get a booking + # + def booking(booking_id) + get_request("/bookings/#{booking_id}") + end + + # Get bookings + # + def bookings( + office_id = nil, + member_id = nil, + team_id = nil + ) + params = {} + params["office"] = office_id if office_id + params["member"] = member_id if member_id + params["team"] = team_id if team_id + params = nil if params.empty? + get_request("/bookings", params) + end + + # Delete a booking + # + def delete_booking(booking_id) + !!(delete_request("/bookings/#{booking_id}")) + end + + # Make a booking + # + def create_bookings(bookings) + response = post("/bookings", body: bookings.to_json, headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => get_token, + }) + unless (200..299).include?(response.status) + expire_token! if response.status_code == 401 + raise "unexpected response #{response.status_code}\n#{response.body}" + end + end + + # Create a booking + # + def create_booking( + resource_id, # String + booking_start, # Time + booking_end, # Time + summary = nil, + team_id = nil, + member_id = nil, + description = nil, + tentative = nil, # Bool + free = nil # Bool + ) + create_bookings [{ + resource_id: resource_id, + start: {dateTime: booking_start}, + end: {dateTime: booking_end}, + summary: summary, + team: team_id, + member: member_id, + description: description, + tentative: tentative, + free: free + }] + end + + # Organisation + ########################################################################### + + # List organisations + # + def organisations + path = "/organizations" + get_request(path, url_base: "/api/v1") + end + + # Retrieve organisation + # + def organisation(org_slug) + path = "/organizations/#{org_slug}" + get_request(path, url_base: "/api/v1") + end + + # Office + ########################################################################### + + # List offices + # + def offices + path = "/offices" + get_request(path) + end + + # Retrieve office + # + def office(office_id) + path = "/offices/#{office_id}" + get_request(path) + end + + # Resource + ########################################################################### + + RESOURCE_TYPES = { + "MeetingRoom" => "meeting_room", + "PrivateOffices" => "team_room", + "PrivateOfficeDesk" => "desk_tr", + "DedicatedDesks" => "desk", + "HotDesks" => "hotdesk" + } + + def resource(resource_id) + get_request("/resources/#{resource_id}") + end + + # Get available rooms (resources) by + # - type + # - date range (available_from, available_to) + # - office (office_id) + # - resource name (name) + def resources( + type = nil, + office_id = nil, + available_from = nil, # Time + available_to = nil, + name: nil + ) + type = (RESOURCE_TYPES[type] || type) if type + params = {} + params["type"] = type.to_s if type + params["name"] = name if name + params["office"] = office_id if office_id + + params["availableFrom"] = available_from.iso8601 if available_from.is_a?(Time) + params["availableTo"] = available_to.iso8601 if available_to.is_a?(Time) + params["availableFrom"] = available_from.iso8601 if available_from.is_a?(String) + params["availableTo"] = available_to.iso8601 if available_to.is_a?(String) + params["availableFrom"] = Time.at(available_from).iso8601 if available_from.is_a?(Integer) + params["availableTo"] = Time.at(available_to).iso8601 if available_to.is_a?(Integer) + params = nil if params.empty? + get_request("/resources", params) + end + + def meeting_rooms( + available_from = nil, # Time + available_to = nil, + office_id: nil + ) + resources("MeetingRoom", office_id, available_from, available_to) + end + + def desks( + available_from = nil, # Time + available_to = nil, + office_id: nil + ) + resources("HotDesks", office_id, available_from, available_to) + end + + protected + + def escape(s) + s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/) { + '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase + } + end + + def encode_param(k, v) + escape(k) + "=" + escape(v) + end +end From 83c896dccbe35aa8f752b7dfbda1893c7e44fa2c Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 16 Dec 2019 20:48:52 +1000 Subject: [PATCH 1577/1752] feat: complete Office RnD integration --- modules/office_rnd/api.rb | 51 ++++++++++++++-- modules/office_rnd/bookings.rb | 108 +++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 modules/office_rnd/bookings.rb diff --git a/modules/office_rnd/api.rb b/modules/office_rnd/api.rb index 5fbb88bb..6f6406ad 100644 --- a/modules/office_rnd/api.rb +++ b/modules/office_rnd/api.rb @@ -10,7 +10,7 @@ class OfficeRnd::API # Discovery Information implements :service - generic_name :Bookings + generic_name :OfficeRnD descriptive_name "OfficeRnD REST API" keepalive false @@ -36,6 +36,10 @@ def on_update @auth_token ||= "" @auth_expiry ||= 1.minute.ago + + # cache + @timezone_cache ||= [3.days.ago, []] + @member_cache ||= {} end def expire_token! @@ -117,13 +121,33 @@ def floors(office_id = nil, name = nil) # Get bookings for a resource for a given time span # def resource_bookings( - resource_id, - range_start = Time.now - 5.minutes.to_i, - range_end = Time.now + 24.hours.to_i, + resource_id = nil, + range_start = nil, + range_end = nil, office_id = nil, member_id = nil, - team_id = nil + team_id = nil, + rebuild_cache: false ) + # Use the cache + if range_start.nil? && range_end.nil? && office_id.nil? && member_id.nil? && team_id.nil? + cached_time, data = @timezone_cache + + if !rebuild_cache && cached_time >= 23.minutes.ago + return data unless resource_id + return data.select do |booking| + booking[:resourceId] == resource_id + end + else + rebuild_cache = true + end + + range_start = 20.minutes.ago.utc + range_end = Time.now.utc.tomorrow.tomorrow.midnight + else + rebuild_cache = false + end + params = {} params["office"] = office_id if office_id params["member"] = member_id if member_id @@ -137,7 +161,10 @@ def resource_bookings( params["end"] = Time.at(range_end).iso8601 if range_end.is_a?(Integer) params = nil if params.empty? - get_request("/bookings/occurrences", params).select do |booking| + all_bookings = get_request("/bookings/occurrences", params) + @timezone_cache = [range_start, all_bookings] + return all_bookings unless resource_id + all_bookings.select do |booking| booking[:resourceId] == resource_id end end @@ -225,6 +252,18 @@ def create_booking( }] end + # Retrieve member details + # + def staff_details(member_id, fresh: false) + member_id = member_id.to_s + cached = @member_cache[member_id] + return cached if cached && !fresh + + path = "/members/#{member_id}" + result = get_request(path) + @member_cache[member_id] = result + end + # Organisation ########################################################################### diff --git a/modules/office_rnd/bookings.rb b/modules/office_rnd/bookings.rb new file mode 100644 index 00000000..29890a21 --- /dev/null +++ b/modules/office_rnd/bookings.rb @@ -0,0 +1,108 @@ + +module OfficeRnd; end +class OfficeRnd::Bookings + include ::Orchestrator::Constants + + # Discovery Information + descriptive_name 'Office RnD Room Booking Panel Logic' + generic_name :Bookings + implements :logic + + default_settings({ + resource_id: "resource_id" + }) + + def on_load + on_update + end + + def on_update + @resource_id = setting(:resource_id) + + self[:room_name] = setting(:room_name) || system.name + self[:hide_all] = setting(:hide_all) || false + self[:touch_enabled] = setting(:touch_enabled) || false + self[:arrow_direction] = setting(:arrow_direction) + self[:hearing_assistance] = setting(:hearing_assistance) + self[:timeline_start] = setting(:timeline_start) + self[:timeline_end] = setting(:timeline_end) + self[:description] = setting(:description) + self[:icon] = setting(:icon) + self[:control_url] = setting(:booking_control_url) || system.config.support_url + self[:help_options] = setting(:help_options) + + self[:timeout] = setting(:timeout) + self[:booking_cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' + self[:booking_cancel_email_message] = setting(:booking_cancel_email_message) + self[:booking_timeout_email_message] = setting(:booking_timeout_email_message) + self[:booking_controls] = setting(:booking_controls) + self[:booking_catering] = setting(:booking_catering) + self[:booking_hide_details] = setting(:booking_hide_details) + self[:booking_hide_availability] = setting(:booking_hide_availability) + self[:booking_hide_user] = setting(:booking_hide_user) + self[:booking_hide_modal] = setting(:booking_hide_modal) + self[:booking_hide_title] = setting(:booking_hide_title) + self[:booking_hide_description] = setting(:booking_hide_description) + self[:booking_hide_timeline] = setting(:booking_hide_timeline) + self[:booking_set_host] = setting(:booking_set_host) + self[:booking_set_title] = setting(:booking_set_title) + self[:booking_set_ext] = setting(:booking_set_ext) + self[:booking_search_user] = setting(:booking_search_user) + self[:booking_disable_future] = setting(:booking_disable_future) + self[:booking_min_duration] = setting(:booking_min_duration) + self[:booking_max_duration] = setting(:booking_max_duration) + self[:booking_duration_step] = setting(:booking_duration_step) + self[:booking_endable] = setting(:booking_endable) + self[:booking_ask_cancel] = setting(:booking_ask_cancel) + self[:booking_ask_end] = setting(:booking_ask_end) + self[:booking_default_title] = setting(:booking_default_title) || "On the spot booking" + self[:booking_select_free] = setting(:booking_select_free) + self[:booking_hide_all] = setting(:booking_hide_all) || false + + self[:last_meeting_started] = setting(:last_meeting_started) + self[:cancel_meeting_after] = setting(:cancel_meeting_after) + + schedule.clear + schedule.in(rand(20000)) { fetch_bookings } + schedule.every(30000 + rand(60000)) { fetch_bookings } + schedule.every(30000) { check_room_usage } + end + + def fetch_bookings + logger.debug { "looking up todays bookings for #{@resource_id}" } + officernd = system[:OfficeRnD] + officernd.resource_bookings(@resource_id).then do |bookings| + self[:today] = bookings.map do |booking| + staff_details = officernd.staff_details(booking[:member]).value + { + id: booking[:bookingId], + Start: booking[:start][:dateTime], + End: booking[:end][:dateTime], + Subject: booking[:summary], + owner: staff_details[:name], + setup: 0, + breakdown: 0, + start_epoch: Time.parse(booking[:start][:dateTime]).to_i, + end_epoch: Time.parse(booking[:end][:dateTime]).to_i + } + end + + check_room_usage + end + end + + def check_room_usage + now = Time.now.to_i + current_booking = false + + bookings = self[:today] || [] + bookings.each do |booking| + if now < booking[:end_epoch] && now > booking[:start_epoch] + current_booking = true + break + end + end + + self[:room_in_use] = current_booking + end +end From bb0748d5d812195f66942388f5bfd3e5611aca79 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 30 Dec 2019 14:30:53 +0800 Subject: [PATCH 1578/1752] office2/log/response: Always log response on error --- lib/microsoft/office2/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index e9b2896a..8e242b91 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -195,9 +195,9 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint end def check_response(response) + return if response.status.between?(200,204) + STDOUT.puts "GRAPH API ERROR Response:\n #{response}" case response.status - when 200, 201, 204 - return when 400 if response.dig('error', 'code') == 'ErrorInvalidIdMalformed' raise Microsoft::Error::ErrorInvalidIdMalformed.new(response.body) From 94c5695ae69e011f142c8821ab92543c07d18d5e Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 31 Dec 2019 13:31:03 +0800 Subject: [PATCH 1579/1752] fix(office2/event/available?): return conflicting start times instead of new booking start times --- lib/microsoft/office2/events.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 210f318a..de8d9504 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -165,9 +165,11 @@ def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_ events.each do |event| unless event['iCalUId'] == ignore_icaluid conflicts[room] ||= [] - istart_epoch = start_epoch.to_i - conflicts[room] << istart_epoch - all_conflicts << istart_epoch + start = event['start'] + clashing_start_epoch = ActiveSupport::TimeZone.new(start['timeZone']).parse(start['dateTime']).to_i + puts "\n\nclashing_start_epoch: #{clashing_start_epoch}\n\n" + conflicts[room] << clashing_start_epoch + all_conflicts << clashing_start_epoch end end end @@ -401,6 +403,7 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } if organizer if ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' + puts "\n\n ODATA EXTENSIONS DISABLED \n\n" ext = { "@odata.type": "microsoft.graph.openTypeExtension", "extensionName": "Com.Acaprojects.Extensions" From 747b71897ec7be0fe10d313bb105d00253ba73f9 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 31 Dec 2019 13:33:12 +0800 Subject: [PATCH 1580/1752] style(office2/events): remove debug stdouts --- lib/microsoft/office2/events.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index de8d9504..0a892f9b 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -167,7 +167,6 @@ def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_ conflicts[room] ||= [] start = event['start'] clashing_start_epoch = ActiveSupport::TimeZone.new(start['timeZone']).parse(start['dateTime']).to_i - puts "\n\nclashing_start_epoch: #{clashing_start_epoch}\n\n" conflicts[room] << clashing_start_epoch all_conflicts << clashing_start_epoch end @@ -403,7 +402,6 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } if organizer if ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' - puts "\n\n ODATA EXTENSIONS DISABLED \n\n" ext = { "@odata.type": "microsoft.graph.openTypeExtension", "extensionName": "Com.Acaprojects.Extensions" From 35caafaaa3335e2ebd54dd0ab22c60365031e351 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 3 Jan 2020 15:01:44 +0800 Subject: [PATCH 1581/1752] feat(office2/errors/412): Support graph error "Precondition Failed" --- lib/microsoft/office2/client.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 8e242b91..b0dd3ca7 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -212,6 +212,8 @@ def check_response(response) raise Microsoft::Error::ResourceNotFound.new(response.body) when 409 raise Microsoft::Error::Conflict.new(response.body) + when 412 + raise Microsoft::Error::PreconditionFailed.new(response.body) end end From 4cda22f784b3c9f6ac0a5d3f7b76af40a0eb6548 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 3 Jan 2020 15:02:56 +0800 Subject: [PATCH 1582/1752] feat(office2/event/error): Assume succesful booking on 409, 412. As from experience the booking is always successful --- lib/microsoft/office2/events.rb | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 0a892f9b..bf6d14e6 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -232,11 +232,8 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca retries ||= 0 request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events"], data: event_json) check_response(request) - rescue Microsoft::Error::Conflict => e - if (retries += 1) < 3 - sleep(rand()) - retry - end + rescue Microsoft::Error::Conflict, Microsoft::Error::PreconditionFailed => e + return {} end Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body)).event end @@ -306,12 +303,9 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni begin request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}"], data: event_json) check_response(request) - rescue Microsoft::Error::Conflict => e - if (retries += 1) < 3 - sleep(rand()*2) - retry - end - end + rescue Microsoft::Error::Conflict, Microsoft::Error::PreconditionFailed => e + return {} + end Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event end @@ -325,11 +319,8 @@ def delete_booking(mailbox:, booking_id:, calendargroup_id: nil, calendar_id: ni begin request = graph_request(request_method: 'delete', endpoints: [endpoint]) check_response(request) - rescue Microsoft::Error::Conflict => e - if (retries += 1) < 3 - sleep(rand()*2) - retry - end + rescue Microsoft::Error::Conflict, Microsoft::Error::PreconditionFailed => e + return 200 end 200 end From bc283f4261cd079fa7d014ac1bcdbf3eeb660816 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 3 Jan 2020 15:08:18 +0800 Subject: [PATCH 1583/1752] feat(office2/RBP): support start/end params as both string and sym --- modules/aca/o365_booking_panel.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 2fc9e88c..ff3fedf5 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -112,11 +112,12 @@ def fetch_bookings(*args) end def create_meeting(params) - required_fields = [:start, :end] - check = required_fields - params.keys - if check != [] - logger.debug "Required fields missing: #{check}" - raise "Required fields missing: #{check}" + start_param = params[:start] || params['start'] + end_param = params[:end] || params['end'] + + unless start_param && end_param + logger.debug "Error: start/end param is required and missing" + raise "Error: start/end param is required and missing" end logger.debug "RBP>#{@office_room}>CREATE>INPUT:\n #{params}" @@ -150,8 +151,8 @@ def create_meeting(params) mailbox: mailbox, calendargroup_id: calendargroup_id, calendar_id: calendar_id, - start_param: epoch(params[:start]), - end_param: epoch(params[:end]), + start_param: epoch(start_param), + end_param: epoch(end_param), options: booking_options ) rescue Exception => e logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e.message}\n#{e.backtrace.join("\n")}" From 8049ac624a9852d8eb9f7c529b55bfd2d5bea30d Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 3 Jan 2020 15:13:43 +0800 Subject: [PATCH 1584/1752] fix(office2/errors): 412 is actually "IrresolvableConflict" not "PreconditonFailed" --- lib/microsoft/office2/client.rb | 2 +- lib/microsoft/office2/events.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index b0dd3ca7..117a95d9 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -213,7 +213,7 @@ def check_response(response) when 409 raise Microsoft::Error::Conflict.new(response.body) when 412 - raise Microsoft::Error::PreconditionFailed.new(response.body) + raise Microsoft::Error::Conflict.new(response.body) end end diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index bf6d14e6..027a1083 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -232,7 +232,7 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca retries ||= 0 request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events"], data: event_json) check_response(request) - rescue Microsoft::Error::Conflict, Microsoft::Error::PreconditionFailed => e + rescue Microsoft::Error::Conflict => e return {} end Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body)).event @@ -303,7 +303,7 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni begin request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}"], data: event_json) check_response(request) - rescue Microsoft::Error::Conflict, Microsoft::Error::PreconditionFailed => e + rescue Microsoft::Error::Conflict => e return {} end Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event @@ -319,7 +319,7 @@ def delete_booking(mailbox:, booking_id:, calendargroup_id: nil, calendar_id: ni begin request = graph_request(request_method: 'delete', endpoints: [endpoint]) check_response(request) - rescue Microsoft::Error::Conflict, Microsoft::Error::PreconditionFailed => e + rescue Microsoft::Error::Conflict => e return 200 end 200 From 76924c2833f001f02fe0f2b10b78fd5865b74825 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 3 Jan 2020 16:07:06 +0800 Subject: [PATCH 1585/1752] feat(pressac/desk/logic): stale can be shown as free/busy/blank --- modules/pressac/desk_management.rb | 31 ++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 3a5cbc11..85eb32dd 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -13,6 +13,7 @@ class ::Pressac::DeskManagement iot_hub_device: "Websocket_1", delay_until_shown_as_busy: "0m", delay_until_shown_as_free: "0m", + stale_shown_as: "blank" zone_to_gateway_mappings: { "zone-xxx" => ["pressac_gateway_name_1"], "zone-zzz" => ["pressac_gateway_name_2", "pressac_gateway_name_3"] @@ -49,6 +50,7 @@ def on_update @hub = setting('iot_hub_device') || "Websocket_1" @zones = setting('zone_to_gateway_mappings') || {} @desk_ids = setting('sensor_name_to_desk_mappings') || {} + @stale_status = setting('stale_shown_as')&.downcase&.to_sym || :blank @custom_delays = setting('custom_delays')&.map {|d| { regex_match: d[:regex_match], @@ -218,13 +220,30 @@ def unexpose_unresponsive_desks(notification) stale_sensors = notification.value stale_ids = id(stale_sensors.map {|s| s.keys.first}) - logger.debug "PRESSAC > DESK > LOGIC: Removing stale sensors: #{stale_ids}" + logger.debug "PRESSAC > DESK > LOGIC: Displaying stale sensors as #{@stale_status}: #{stale_ids}" - @zones.keys&.each do |zone_id| - self[zone_id] = self[zone_id] - stale_ids - self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] - stale_ids - self[zone_id+':occupied_count'] = self[zone_id].count - self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + case @stale_status + when :blank + @zones.keys&.each do |zone_id| + self[zone_id] = self[zone_id] - stale_ids + self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] - stale_ids + self[zone_id+':occupied_count'] = self[zone_id].count + self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + end + when :free + @zones.keys&.each do |zone_id| + self[zone_id] = self[zone_id] - stale_ids + self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] | stale_ids + self[zone_id+':occupied_count'] = self[zone_id].count + self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + end + when :busy + @zones.keys&.each do |zone_id| + self[zone_id] = self[zone_id] | stale_ids + self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] | stale_ids + self[zone_id+':occupied_count'] = self[zone_id].count + self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + end end end end From ef8626a27e2b407727ae387151e4d16a76f90156 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 3 Jan 2020 16:08:51 +0800 Subject: [PATCH 1586/1752] fix(pressac/desk/logic): syntax of new default setting --- modules/pressac/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 85eb32dd..d8aee9f9 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -13,7 +13,7 @@ class ::Pressac::DeskManagement iot_hub_device: "Websocket_1", delay_until_shown_as_busy: "0m", delay_until_shown_as_free: "0m", - stale_shown_as: "blank" + stale_shown_as: "blank", zone_to_gateway_mappings: { "zone-xxx" => ["pressac_gateway_name_1"], "zone-zzz" => ["pressac_gateway_name_2", "pressac_gateway_name_3"] From bb81762fb07b79677c82043e70278eedefc437b3 Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 4 Jan 2020 01:26:56 +0800 Subject: [PATCH 1587/1752] feat(office2/RBP): add default param for refresh_endpoints --- modules/aca/o365_booking_panel.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index ff3fedf5..353c8b5e 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -245,10 +245,11 @@ def clear_end_meeting_warning end - def refresh_endpoints(epoch) - # Relies on frontend recieving this status variable update and acting upon it. Should result in a full page reload. - # epoch is an integer, in seconds. - self[:reload] = epoch + + # Relies on frontend recieving this status variable update and acting upon it. Should result in a full page reload. + # epoch is an integer, in seconds. + def refresh_endpoints(epoch = nil) + self[:reload] = epoch || Time.now.to_i + 300 end protected From c0770cc55acae611957f56db83fb17cb4b77235c Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 13:51:45 +1100 Subject: [PATCH 1588/1752] fix(pressac/booking_canceller): fix inverted logic for when to cancel --- modules/pressac/booking_canceller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 0f9e8cd9..436d0d93 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -48,8 +48,8 @@ def determine_booking_presence bookings = system[@bookings][:today] bookings&.each do |booking| logger.debug "Canceller: checking booking #{booking[:Subject]} with start #{booking[:start_epoch]} and current time #{now}" - next unless booking[:start_epoch] > now + @cancel_delay - all_sensors = systems(@desk_management_system)[@desk_management_device] + next unless now + @cancel_delay > booking[:start_epoch] + all_sensors = systems(@desk_management_system)[@desk_management_device] next unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't cancel if the sensor has not registered yet motion_detected = all_sensors[@zone].include? @sensor logger.debug "Canceller: #{@sensor} presence: #{motion_detected}" From f11ac7c6cbf37ca9865bf5ab5ff71e860b130cfb Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 13:53:49 +1100 Subject: [PATCH 1589/1752] feat(office2/events/create+edit): default to timezone from env var instead of UTC --- lib/microsoft/office2/events.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 027a1083..f5dd5b01 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -204,7 +204,7 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca attendees: [], recurrence: nil, is_private: false, - timezone: 'UTC', + timezone: ENV['TZ'], extensions: {}, location: nil } @@ -266,8 +266,7 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni description: nil, attendees: [], recurrence: nil, - is_private: false, - timezone: 'UTC', + timezone: ENV['TZ'], extensions: {}, location: nil } From 6edae00a76e50590a7531006ca1af7772e1678cb Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 13:57:31 +1100 Subject: [PATCH 1590/1752] fix(office2/events/edit): re-instate missing is_private param --- lib/microsoft/office2/events.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index f5dd5b01..e4cd3d00 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -266,6 +266,7 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni description: nil, attendees: [], recurrence: nil, + is_private: nil, timezone: ENV['TZ'], extensions: {}, location: nil From 68c11a7dfc5a473afa8e1e2089b1950e4adebed0 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 15:11:32 +1100 Subject: [PATCH 1591/1752] feat(office2/events/create-json): PUTS with nil params don't overwrite existing values --- lib/microsoft/office2/events.rb | 63 ++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index e4cd3d00..ac252508 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -258,13 +258,14 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking # @option options [String] :location The location field to set. This will not be used if a room is passed in def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: nil, options: {}) + # No defaults for editing, because undefined fields should simply retain the existing value (from the original booking) default_options = { start_param: nil, end_param: nil, - rooms: [], - subject: "Meeting", + rooms: nil, + subject: nil, description: nil, - attendees: [], + attendees: nil, recurrence: nil, is_private: nil, timezone: ENV['TZ'], @@ -277,7 +278,7 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni # Create the JSON body for our event event_json = create_event_json( subject: options[:subject], - body: options[:description], + body: (options[:body] || options[:description]), rooms: options[:rooms], start_param: options[:start_param], end_param: options[:end_param], @@ -347,22 +348,9 @@ def calendar_path(calendargroup_id, calendar_id) result end - def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: [], location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) - # Put the attendees into the MS Graph expeceted format - attendees.map! do |a| - attendee_type = ( a[:optional] ? "optional" : "required" ) - { emailAddress: { address: a[:email], name: a[:name] }, type: attendee_type } - end - - # Add each room to the attendees array - rooms.each do |room| - attendees.push({ type: "resource", emailAddress: { address: room[:email], name: room[:name] } }) - end - - + def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: nil, location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) event_json = {} event_json[:subject] = subject - event_json[:attendees] = attendees event_json[:sensitivity] = ( is_private ? "private" : "normal" ) event_json[:body] = { @@ -382,8 +370,18 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timeZone: timezone } if end_param - # If we have rooms then use that, otherwise use the location string. Fall back is []. - event_json[:locations] = rooms.present? ? rooms.map { |r| { displayName: r[:name] } } : location ? [{displayName: location}] : [] + # Put the attendees into the MS Graph expected format + attendees&.map! do |a| + attendee_type = ( a[:optional] ? "optional" : "required" ) + { emailAddress: { address: a[:email], name: a[:name] }, type: attendee_type } + end + + # Add each room to the attendees array + rooms&.each do |room| + attendees.push({ type: "resource", emailAddress: { address: room[:email], name: room[:name] } }) + end + + event_json[:attendees] = attendees if attendees event_json[:organizer] = { emailAddress: { @@ -392,15 +390,11 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } } if organizer - if ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' - ext = { - "@odata.type": "microsoft.graph.openTypeExtension", - "extensionName": "Com.Acaprojects.Extensions" - } - extensions.each do |ext_key, ext_value| - ext[ext_key] = ext_value - end - event_json[:extensions] = [ext] + # If we have rooms then use that, otherwise use the location string + if rooms? + event_json[:locations] = rooms.map { |r| { displayName: r[:name] } } + elsif location + event_json[:locations] = [{displayName: location}] end if recurrence @@ -419,6 +413,17 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } end + if ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' + ext = { + "@odata.type": "microsoft.graph.openTypeExtension", + "extensionName": "Com.Acaprojects.Extensions" + } + extensions.each do |ext_key, ext_value| + ext[ext_key] = ext_value + end + event_json[:extensions] = [ext] + end + event_json.reject!{|k,v| v.nil?} event_json end From 592bf585593efbebdaf3330d1ae6b3fc76b5cbe4 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 15:38:23 +1100 Subject: [PATCH 1592/1752] fix(office2/events/create-json): syntax for rooms.nil? --- lib/microsoft/office2/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index ac252508..bb740954 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -391,7 +391,7 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } if organizer # If we have rooms then use that, otherwise use the location string - if rooms? + if rooms event_json[:locations] = rooms.map { |r| { displayName: r[:name] } } elsif location event_json[:locations] = [{displayName: location}] From ecac57c74a5d7e18bc32e16a0544fa5d0cb317d5 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 15:39:54 +1100 Subject: [PATCH 1593/1752] feat(office2/RBP): expose event body and organizer --- modules/aca/o365_booking_panel.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 353c8b5e..a7d40d04 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -314,17 +314,20 @@ def expose_bookings(bookings) end_epoch = booking['end_epoch'] attendees = booking['attendees'] - name = booking.dig('organizer',:name) || "Private" - email = booking.dig('organizer',:email) || "Private" if ENV['O365_PROXY_USER_CALENDARS'] name = booking.dig('attendees',1,:name) || "Private" email = booking.dig('attendees',1,:email) || "Private" + else + email = booking.dig('organizer',:email) || "Private" + name = booking.dig('organizer',:name) || "Private" end subject = booking['subject'] + body = booking['body'] if ['private','confidential'].include?(booking['sensitivity']) - name = "Private" + name = "Private" subject = "Private" + body = "Private" end results.push({ @@ -333,6 +336,7 @@ def expose_bookings(bookings) :start_epoch => start_epoch, :end_epoch => end_epoch, :Subject => subject, + :body => body, :id => booking['id'], :icaluid => booking['icaluid'], :owner => name, From 31cdd223424abba9b1cd971ca59c985feb722045 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 15:41:38 +1100 Subject: [PATCH 1594/1752] feat(office2/RBP/end_meeting) Add end_meeting() which SHORTENS events instead of cancelling or declining them --- modules/aca/o365_booking_panel.rb | 32 +++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index a7d40d04..cd75ccb8 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -107,8 +107,8 @@ def on_update end def fetch_bookings(*args) - response = @client.get_bookings(mailboxes: [@office_room], options: {bookings_from: Time.now.midnight.to_i, bookings_to: Time.now.tomorrow.midnight.to_i}).dig(@office_room, :bookings) - self[:today] = expose_bookings(response) + @todays_bookings = @client.get_bookings(mailboxes: [@office_room], options: {bookings_from: Time.now.midnight.to_i, bookings_to: Time.now.tomorrow.midnight.to_i}).dig(@office_room, :bookings) + self[:today] = expose_bookings(@todays_bookings) end def create_meeting(params) @@ -174,6 +174,34 @@ def start_meeting(meeting_ref) define_setting(:last_meeting_started, meeting_ref) end + # New function for this or other engine modules to end meetings early based on sensors or other input. + # Does not cancel or decline meetings - just shortens them to now. + # This new method replaces the frontend app cancelling the booking, which has had many issues. Automated cancellations should be handled by backend modules for frontend apps + def end_meeting(id) + existing = @todays_bookings&.select {|b| b['id'] == id} + return "Booking not found with id: #{id}" unless existing + + now = Time.now + new_details = {} + new_details[:end_param] = now.to_i + new_details[:body] = existing[:body] << "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" + + @client.update_booking(booking_id: id, mailbox: @office_room, options: new_details) + end + + # Legacy function for current/old ngx-booking frontends + # - start_time is a string + # This function will either: + # - DELETE the booking from the room calendar (if the host if the room) + # OR + # - DECLINE the booking from the room calendar (if the host if a person, so that the person recieves a delcline message) + # + # The function is replaced by end_meeting(id) which has improvements and drawbacks: + # + identify meeting by icaluid instead of start time, avoiding ambiguity + # + The meeting will be edited in the room calendar: shortened to the current time. So that external retrospective analytics will still detect and count the meeting in the exchange mailbox. + # + The body will be appended with "This meeting was ended at [time] because no presence was detected in the room" + # - However currently with end_meeting(), the user will not recieve an automated email notifications (these only get sent when the room declines-) + # def cancel_meeting(start_time, reason = "unknown reason") now = Time.now.to_i start_epoch = Time.parse(start_time).to_i From a2a896eb03424dc572ffbbe9e3b702fae58bcc62 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 15:50:30 +1100 Subject: [PATCH 1595/1752] fix(office2/RBP/end_meeting): fix event finding; body retrieval --- modules/aca/o365_booking_panel.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index cd75ccb8..e5c80107 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -178,13 +178,13 @@ def start_meeting(meeting_ref) # Does not cancel or decline meetings - just shortens them to now. # This new method replaces the frontend app cancelling the booking, which has had many issues. Automated cancellations should be handled by backend modules for frontend apps def end_meeting(id) - existing = @todays_bookings&.select {|b| b['id'] == id} + existing = @todays_bookings&.find {|b| b['id'] == id} return "Booking not found with id: #{id}" unless existing now = Time.now new_details = {} new_details[:end_param] = now.to_i - new_details[:body] = existing[:body] << "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" + new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" @client.update_booking(booking_id: id, mailbox: @office_room, options: new_details) end From 62a5c065d6243731f518569820bcd276a053682d Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 15:54:39 +1100 Subject: [PATCH 1596/1752] feat(office2/event/create_json): Allow nil start_param (edit event without changing existing time) --- lib/microsoft/office2/events.rb | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index bb740954..fa607848 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -358,17 +358,21 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, content: (body || "") } - start_date = Time.at(start_param).to_datetime.in_time_zone(timezone) - event_json[:start] = { - dateTime: start_date.strftime('%FT%R'), - timeZone: timezone - } if start_param - - end_date = Time.at(end_param).to_datetime.in_time_zone(timezone) - event_json[:end] = { - dateTime: end_date.strftime('%FT%R'), - timeZone: timezone - } if end_param + if start_param + start_date = Time.at(start_param).to_datetime.in_time_zone(timezone) + event_json[:start] = { + dateTime: start_date.strftime('%FT%R'), + timeZone: timezone + } + end + + if end_param + end_date = Time.at(end_param).to_datetime.in_time_zone(timezone) + event_json[:end] = { + dateTime: end_date.strftime('%FT%R'), + timeZone: timezone + } + end # Put the attendees into the MS Graph expected format attendees&.map! do |a| From 53b3ff181d8e8a42622e3d4c7075e2053535033b Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 16:05:00 +1100 Subject: [PATCH 1597/1752] feat(office2/client/logs): output request if response is an error --- lib/microsoft/office2/client.rb | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 117a95d9..22c7287f 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -179,24 +179,19 @@ def graph_date(date) end def log_graph_request(request_method, data, query, headers, graph_path, endpoints=nil) - return unless ENV['O365_LOG_REQUESTS'] - STDERR.puts "--------------NEW GRAPH REQUEST------------" - STDERR.puts "#{request_method} to #{graph_path}" - STDERR.puts "Data:" - STDERR.puts data.to_json if data - STDERR.puts "Query:" - STDERR.puts query if query - STDERR.puts "Headers:" - STDERR.puts headers if headers - STDERR.puts "Endpoints:" - STDERR.puts endpoints if endpoints - STDERR.puts '--------------------------------------------' - STDERR.flush + #Store the request so that it can be output later IF an error was detected + @request_info = "#{request_method} to #{graph_path}" + @request_info << "Data:\n #{data.to_json}" if data + @request_info << "Query:\n #{data.to_json}" if query + @request_info << "Headers:\n #{data.to_json}" if headers + @request_info << "Endpoints:\n #{data.to_json}" if data end def check_response(response) - return if response.status.between?(200,204) - STDOUT.puts "GRAPH API ERROR Response:\n #{response}" + return if response.status >= 400 + STDERR.puts "GRAPH API ERROR Request:\n #{@request_info}" + STDERR.puts "\n--------------------------------------------"" + STDERR.puts "GRAPH API ERROR Response:\n #{response}" case response.status when 400 if response.dig('error', 'code') == 'ErrorInvalidIdMalformed' From a9b2b144e3bbca0426381a1c8b42c52b89d07f06 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 16:05:57 +1100 Subject: [PATCH 1598/1752] fix(office2/client/log): syntax --- lib/microsoft/office2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 22c7287f..92773c28 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -190,7 +190,7 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint def check_response(response) return if response.status >= 400 STDERR.puts "GRAPH API ERROR Request:\n #{@request_info}" - STDERR.puts "\n--------------------------------------------"" + STDERR.puts "\n--------------------------------------------" STDERR.puts "GRAPH API ERROR Response:\n #{response}" case response.status when 400 From 8af9641772458256b31fa171459d178656359d6b Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 16:31:36 +1100 Subject: [PATCH 1599/1752] fix(office2/client/log): fix inverted logic for log output; visually group req/resp better --- lib/microsoft/office2/client.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 92773c28..17f40313 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -188,10 +188,11 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint end def check_response(response) - return if response.status >= 400 + return if response.status < 300 + STDERR.puts ">>>>>>>>>>>>" STDERR.puts "GRAPH API ERROR Request:\n #{@request_info}" - STDERR.puts "\n--------------------------------------------" STDERR.puts "GRAPH API ERROR Response:\n #{response}" + STDERR.puts "<<<<<<<<<<<<" case response.status when 400 if response.dig('error', 'code') == 'ErrorInvalidIdMalformed' From 5ee2aef79239734fc7f3d62db4853dec5ea1e54d Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 16:39:57 +1100 Subject: [PATCH 1600/1752] fix(office2/client/log): fix request output --- lib/microsoft/office2/client.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 17f40313..89df530f 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -181,16 +181,17 @@ def graph_date(date) def log_graph_request(request_method, data, query, headers, graph_path, endpoints=nil) #Store the request so that it can be output later IF an error was detected @request_info = "#{request_method} to #{graph_path}" - @request_info << "Data:\n #{data.to_json}" if data - @request_info << "Query:\n #{data.to_json}" if query - @request_info << "Headers:\n #{data.to_json}" if headers - @request_info << "Endpoints:\n #{data.to_json}" if data + @request_info << "QUERY: #{query}" if query + @request_info << "DATA: #{data.to_json}" if data + @request_info << "ENDPOINTS: #{endpoints}" if endpoints + @request_info << "HEADERS: #{headers}" if headers end def check_response(response) return if response.status < 300 STDERR.puts ">>>>>>>>>>>>" STDERR.puts "GRAPH API ERROR Request:\n #{@request_info}" + STDERR.puts "============" STDERR.puts "GRAPH API ERROR Response:\n #{response}" STDERR.puts "<<<<<<<<<<<<" case response.status From 9d84fb25618b81668f62bcc381f44eefd7cbea52 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 16:51:01 +1100 Subject: [PATCH 1601/1752] fix(office2/client/log/requests): new lines for readability --- lib/microsoft/office2/client.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 89df530f..9e72c560 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -181,10 +181,10 @@ def graph_date(date) def log_graph_request(request_method, data, query, headers, graph_path, endpoints=nil) #Store the request so that it can be output later IF an error was detected @request_info = "#{request_method} to #{graph_path}" - @request_info << "QUERY: #{query}" if query - @request_info << "DATA: #{data.to_json}" if data - @request_info << "ENDPOINTS: #{endpoints}" if endpoints - @request_info << "HEADERS: #{headers}" if headers + @request_info << "\nQUERY: #{query}" if query + @request_info << "\nDATA: #{data.to_json}" if data + @request_info << "\nENDPOINTS: #{endpoints}" if endpoints + @request_info << "\nHEADERS: #{headers}" if headers end def check_response(response) From afd4b98853935b7962a3605f36cfe808f3d5d0c0 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 16:51:43 +1100 Subject: [PATCH 1602/1752] fix(event/create_json): don't overwrite sensitivity and subject --- lib/microsoft/office2/events.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index fa607848..675f99ea 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -350,8 +350,8 @@ def calendar_path(calendargroup_id, calendar_id) def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: nil, location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) event_json = {} - event_json[:subject] = subject - event_json[:sensitivity] = ( is_private ? "private" : "normal" ) + event_json[:subject] = subject if subject + event_json[:sensitivity] = ( is_private ? "private" : "normal" ) if sensitivity event_json[:body] = { contentType: "HTML", From 92ac0bfc69a31c3a2ba634c04f05fa9c537e18c6 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 16:54:40 +1100 Subject: [PATCH 1603/1752] fix(office2/event/create_json): fix var reference --- lib/microsoft/office2/events.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 675f99ea..3c3d4751 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -348,10 +348,10 @@ def calendar_path(calendargroup_id, calendar_id) result end - def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: nil, location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: false) + def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: nil, location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: nil) event_json = {} event_json[:subject] = subject if subject - event_json[:sensitivity] = ( is_private ? "private" : "normal" ) if sensitivity + event_json[:sensitivity] = ( is_private ? "private" : "normal" ) if is_private event_json[:body] = { contentType: "HTML", From 968812a0f3401bd6ce3b65ed663b519505766d32 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 17:02:25 +1100 Subject: [PATCH 1604/1752] feat(office2/RBP/end_meeting): raise error if attempting to end meeting before it stars --- modules/aca/o365_booking_panel.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index e5c80107..451626cb 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -184,6 +184,7 @@ def end_meeting(id) now = Time.now new_details = {} new_details[:end_param] = now.to_i + raise "Error (400): Events that have not started yet cannot be ended." if existing['start_epoch'] > new_details[:end_param] new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" @client.update_booking(booking_id: id, mailbox: @office_room, options: new_details) From 9517b9aa20e1337d14853dec590533fba34c263d Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 17:25:16 +1100 Subject: [PATCH 1605/1752] fix(office2/RBP/end_meeting): append text to html OR text descriptions --- modules/aca/o365_booking_panel.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 451626cb..14f6e665 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -185,8 +185,12 @@ def end_meeting(id) new_details = {} new_details[:end_param] = now.to_i raise "Error (400): Events that have not started yet cannot be ended." if existing['start_epoch'] > new_details[:end_param] - new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" + if existing['body'] && existing['body'][0..20].downcase.include? '' + new_details[:body] = existing['body']&.gsub(/<\/html>/i, "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}") + else + new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" + end @client.update_booking(booking_id: id, mailbox: @office_room, options: new_details) end From b942ecce1782ba37b41a44b9fd9faeeff47576d4 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 17:42:07 +1100 Subject: [PATCH 1606/1752] style(office2/RBP/end_meeting): assume in first 9 chars not 20 --- modules/aca/o365_booking_panel.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 14f6e665..98846de0 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -185,8 +185,8 @@ def end_meeting(id) new_details = {} new_details[:end_param] = now.to_i raise "Error (400): Events that have not started yet cannot be ended." if existing['start_epoch'] > new_details[:end_param] - - if existing['body'] && existing['body'][0..20].downcase.include? '' + + if existing['body'] && existing['body'][0..9].downcase.include? '' new_details[:body] = existing['body']&.gsub(/<\/html>/i, "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}") else new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" From 026ad73ce98d6c447e785b4ecb1dd0d030b9095f Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 17:43:03 +1100 Subject: [PATCH 1607/1752] feat(office2/RBP/end_meeting): raise 400 if event.id not found --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 98846de0..46c6a53b 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -179,7 +179,7 @@ def start_meeting(meeting_ref) # This new method replaces the frontend app cancelling the booking, which has had many issues. Automated cancellations should be handled by backend modules for frontend apps def end_meeting(id) existing = @todays_bookings&.find {|b| b['id'] == id} - return "Booking not found with id: #{id}" unless existing + raise "Error (400): Booking not found with id: #{id}" unless existing now = Time.now new_details = {} From 1e97ebe8b0d3477fc61a8bb58d91b232651443d6 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 17:43:59 +1100 Subject: [PATCH 1608/1752] fix(office2/RBP/end_meeting): syntax for body.include? --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 46c6a53b..27880319 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -186,7 +186,7 @@ def end_meeting(id) new_details[:end_param] = now.to_i raise "Error (400): Events that have not started yet cannot be ended." if existing['start_epoch'] > new_details[:end_param] - if existing['body'] && existing['body'][0..9].downcase.include? '' + if existing['body'] && existing['body'][0..9].downcase.include?('') new_details[:body] = existing['body']&.gsub(/<\/html>/i, "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}") else new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" From ee1afbcb43fbd19eb7182fee666bf773458d8bd1 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 15:18:20 +0800 Subject: [PATCH 1609/1752] fix(office2/event/availability/check/recur): on the end date of recurrence, check the WHOLE DAY to avoid conflicts --- lib/microsoft/office2/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 3c3d4751..b6f31e8d 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -128,7 +128,7 @@ def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_i def unroll_recurrence(start:, interval: nil, recurrence_end: nil) occurences = [current_occurence = start] return occurences unless interval - end_date = recurrence_end || (start + MAX_RECURRENCE) + end_date = Time.at(recurrence_end || start + MAX_RECURRENCE).midnight.tomorrow.to_i # whole day intervals for recurrence end date loop do next_occurence = current_occurence + interval return occurences if next_occurence > end_date From a4b54b087242e192c13452e12a76626561a020bb Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 13 Jan 2020 15:18:20 +0800 Subject: [PATCH 1610/1752] fix(office2/event/availability/check/recur): on the end date of recurrence, check the WHOLE DAY to avoid conflicts --- lib/microsoft/office2/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index e4cd3d00..8923589c 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -128,7 +128,7 @@ def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_i def unroll_recurrence(start:, interval: nil, recurrence_end: nil) occurences = [current_occurence = start] return occurences unless interval - end_date = recurrence_end || (start + MAX_RECURRENCE) + end_date = Time.at(recurrence_end || start + MAX_RECURRENCE).midnight.tomorrow.to_i # whole day intervals for recurrence end date loop do next_occurence = current_occurence + interval return occurences if next_occurence > end_date From 07f5aa17be3fa403d8a28deb07d0e9c7baea954f Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 14 Jan 2020 11:44:14 +0800 Subject: [PATCH 1611/1752] fix(pressac/booking_canceller): send correct param (id not start_epoch) to bookings.end_meeting; truncate bookings instead of cancelling them --- modules/pressac/booking_canceller.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 436d0d93..93b0f6e1 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -53,14 +53,14 @@ def determine_booking_presence next unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't cancel if the sensor has not registered yet motion_detected = all_sensors[@zone].include? @sensor logger.debug "Canceller: #{@sensor} presence: #{motion_detected}" - cancel(booking) unless motion_detected + truncate(booking) unless motion_detected end end - def cancel(booking) - logger.debug "Canceller: CANCELLING booking #{booking[:Subject]}" - system[@bookings].cancel_meeting(booking[:start_epoch], "pending timeout").then do |response| - logger.info "Cancelled #{booking[:Subject]} with response #{response}" + def truncate(booking) + logger.debug "Canceller: ENDING booking #{booking[:Subject]}" + system[@bookings].end_meeting(booking[:id]).then do |response| + logger.info "Ended #{booking[:Subject]} with response #{response}" end end end From 8607838c6b1aebef85cd278641e0317fd0802110 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 14 Jan 2020 11:47:14 +0800 Subject: [PATCH 1612/1752] feat(office2/RBP/end_meeting): fetch events after ending one; so UI is updated --- modules/aca/o365_booking_panel.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 27880319..c2b2ee75 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -192,6 +192,9 @@ def end_meeting(id) new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" end @client.update_booking(booking_id: id, mailbox: @office_room, options: new_details) + schedule.in('3s') do + fetch_bookings + end end # Legacy function for current/old ngx-booking frontends From 26d1bacbaebb0872d3825cb5ae494b8eb0f93b1e Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 14 Jan 2020 14:53:31 +1100 Subject: [PATCH 1613/1752] style(office2/RBP/end_meeting): line breaks in msg that is appended to ended meeting --- modules/aca/o365_booking_panel.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index c2b2ee75..e83be7a8 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -187,14 +187,14 @@ def end_meeting(id) raise "Error (400): Events that have not started yet cannot be ended." if existing['start_epoch'] > new_details[:end_param] if existing['body'] && existing['body'][0..9].downcase.include?('') - new_details[:body] = existing['body']&.gsub(/<\/html>/i, "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}") + new_details[:body] = existing['body']&.gsub(/<\/html>/i, "

========

This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}

") else new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" end @client.update_booking(booking_id: id, mailbox: @office_room, options: new_details) - schedule.in('3s') do - fetch_bookings - end + schedule.in('3s') do + fetch_bookings + end end # Legacy function for current/old ngx-booking frontends From 225486c2a67bb08d0b97f207abc58ff516031b26 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 15 Jan 2020 08:50:52 +1000 Subject: [PATCH 1614/1752] fix(cisco:sx camera): zoom values are inverted --- modules/cisco/tele_presence/sx_camera_common.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_camera_common.rb b/modules/cisco/tele_presence/sx_camera_common.rb index 9c02f49e..a307adf2 100644 --- a/modules/cisco/tele_presence/sx_camera_common.rb +++ b/modules/cisco/tele_presence/sx_camera_common.rb @@ -116,13 +116,16 @@ def tilt(value) end def zoom(position) + # Cisco has 0 == most zoomed in and 11800 == most zoomed out (unlike all other cameras) + # So we need to invert the values being sent as HTML range slider can't support larger minimum ranges + val = self[:zoom_max] - position val = in_range(position.to_i, self[:zoom_max], self[:zoom_min]) command('Camera PositionSet', params({ :CameraId => @index, :Zoom => val }), name: :zoom).then do - self[:zoom] = val + self[:zoom] = self[:zoom_max] - val autofocus end end From 139230e291255a2fd92fc5a73bc0d5b4a4bf6967 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 15 Jan 2020 14:10:48 +1100 Subject: [PATCH 1615/1752] feat(pressac/booking_canceller): sensor name: use sensor_name || map_id --- modules/pressac/booking_canceller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 93b0f6e1..e1c2fb33 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -35,7 +35,7 @@ def on_update @desk_management_system = setting('desk_management_system_id') @desk_management_device = setting('desk_management_device') @zone = setting('sensor_zone_id') - @sensor = setting('sensor_name') + @sensor = setting('sensor_name') || setting('map_id') @scan_cycle = setting('check_every') @cancel_delay = UV::Scheduler.parse_duration(setting('delay_until_cancel')) / 1000 @@ -47,6 +47,7 @@ def determine_booking_presence now = Time.now.to_i bookings = system[@bookings][:today] bookings&.each do |booking| + logger.debug "Canceller: checking booking #{booking[:Subject]} with start #{booking[:start_epoch]} and current time #{now}" next unless now + @cancel_delay > booking[:start_epoch] all_sensors = systems(@desk_management_system)[@desk_management_device] From c43356b5bbe5355746823f38c3d9b3e939428005 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 15 Jan 2020 14:28:52 +1100 Subject: [PATCH 1616/1752] feat(pressac/booking_canceller): expose presence status; better log output --- modules/pressac/booking_canceller.rb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index e1c2fb33..18a5cae4 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -44,17 +44,23 @@ def on_update end def determine_booking_presence + # Expose presence status + all_sensors = systems(@desk_management_system)[@desk_management_device] + return "Pressac Booking Canceller: Sensor \"#{@sensor}\" NOT FOUND in \"#{@zone}\"" unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist + self[:presence] = all_sensors[@zone].include? @sensor + + # Check each booking now = Time.now.to_i bookings = system[@bookings][:today] bookings&.each do |booking| - - logger.debug "Canceller: checking booking #{booking[:Subject]} with start #{booking[:start_epoch]} and current time #{now}" next unless now + @cancel_delay > booking[:start_epoch] - all_sensors = systems(@desk_management_system)[@desk_management_device] - next unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't cancel if the sensor has not registered yet - motion_detected = all_sensors[@zone].include? @sensor - logger.debug "Canceller: #{@sensor} presence: #{motion_detected}" - truncate(booking) unless motion_detected + logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{sensor} presence: #{self[:presence]}" + if self[:presence] + logger.debug "Pressac Booking Canceller: ENDING \"#{booking[:Subject]}\" now" + truncate(booking) + else + logger.debug "Pressac Booking Canceller: No action for \"#{booking[:Subject]}\"" + end end end From 7c6e489f0899516e908eefe031b4a2809610109e Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 15 Jan 2020 14:29:31 +1100 Subject: [PATCH 1617/1752] style(pressac/booking_canceller): indent --- modules/pressac/booking_canceller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 18a5cae4..6d697fdd 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -66,7 +66,7 @@ def determine_booking_presence def truncate(booking) logger.debug "Canceller: ENDING booking #{booking[:Subject]}" - system[@bookings].end_meeting(booking[:id]).then do |response| + system[@bookings].end_meeting(booking[:id]).then do |response| logger.info "Ended #{booking[:Subject]} with response #{response}" end end From c7d0dda071782c661480d1698009bbbe6190656e Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 15 Jan 2020 14:33:12 +1100 Subject: [PATCH 1618/1752] feat(office2/RBP/end_meeting): prepend ENDED: to ended meetings --- modules/aca/o365_booking_panel.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index e83be7a8..455bbf6b 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -191,6 +191,8 @@ def end_meeting(id) else new_details[:body] = existing['body'] + "\n\n========\n\n This meeting was ended at #{now.to_s} because no presence was detected in #{self[:room_name]}" end + new_details[:subject] = 'ENDED: ' + existing['subject'] + @client.update_booking(booking_id: id, mailbox: @office_room, options: new_details) schedule.in('3s') do fetch_bookings From 68eaafca9befcb337b3844a1080ad525356b9455 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 15 Jan 2020 17:13:41 +1100 Subject: [PATCH 1619/1752] fix(pressac/booking_canceller): don't action bookings that have already ended --- modules/pressac/booking_canceller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 6d697fdd..af54a26d 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -53,6 +53,7 @@ def determine_booking_presence now = Time.now.to_i bookings = system[@bookings][:today] bookings&.each do |booking| + next unless now < booking[:end_epoch] next unless now + @cancel_delay > booking[:start_epoch] logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{sensor} presence: #{self[:presence]}" if self[:presence] From 1931bc34bb595657631b35de2f90094fab171512 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 15 Jan 2020 17:36:22 +1100 Subject: [PATCH 1620/1752] fix(pressac/desk_logic): for each zone, expose only that zone's desks (not all desks) --- modules/pressac/desk_management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index d8aee9f9..7febe6cc 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -188,8 +188,8 @@ def expose_desks(new_status) new_status&.each do |z,desks| zone = z.to_sym self[zone] ||= [] - self[zone] = self[zone] - desks[:free] | desks[:busy] - self[z+':desk_ids'] = self[z+':desk_ids'] | desks[:free] | desks[:busy] + self[zone] = self[zone] - self[zone][:free] | self[zone][:busy] + self[z+':desk_ids'] = self[z+':desk_ids'] | self[zone][:free] | self[zone][:busy] self[z+':desk_count'] = self[z+':desk_ids'].count self[z+':occupied_count'] = self[zone].count end From d2a6a1b05b1acc97f52cc3ec70529a0956669a90 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 16 Jan 2020 18:46:32 +1100 Subject: [PATCH 1621/1752] Revert "fix(pressac/desk_logic): for each zone, expose only that zone's desks (not all desks)" This reverts commit 1931bc34bb595657631b35de2f90094fab171512. --- modules/pressac/desk_management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 7febe6cc..d8aee9f9 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -188,8 +188,8 @@ def expose_desks(new_status) new_status&.each do |z,desks| zone = z.to_sym self[zone] ||= [] - self[zone] = self[zone] - self[zone][:free] | self[zone][:busy] - self[z+':desk_ids'] = self[z+':desk_ids'] | self[zone][:free] | self[zone][:busy] + self[zone] = self[zone] - desks[:free] | desks[:busy] + self[z+':desk_ids'] = self[z+':desk_ids'] | desks[:free] | desks[:busy] self[z+':desk_count'] = self[z+':desk_ids'].count self[z+':occupied_count'] = self[zone].count end From 18b5bc48b415922d0bfc66847a0bc00570d5ead9 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 17 Jan 2020 15:44:03 +1100 Subject: [PATCH 1622/1752] feat(office2/groups): add new groups module with list_user_member_of() --- lib/microsoft/office2/client.rb | 2 ++ lib/microsoft/office2/groups.rb | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 lib/microsoft/office2/groups.rb diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 9e72c560..2b1d99e3 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -9,6 +9,7 @@ require 'microsoft/office2/event' require 'microsoft/office2/events' require 'microsoft/office2/calendars' +require 'microsoft/office2/groups' module Microsoft class Error < StandardError class ResourceNotFound < Error; end @@ -31,6 +32,7 @@ class Microsoft::Office2::Client include Microsoft::Office2::Users include Microsoft::Office2::Contacts include Microsoft::Office2::Calendars + include Microsoft::Office2::Groups ## # Initialize the client for making requests to the Office365 API. diff --git a/lib/microsoft/office2/groups.rb b/lib/microsoft/office2/groups.rb new file mode 100644 index 00000000..ed4e9230 --- /dev/null +++ b/lib/microsoft/office2/groups.rb @@ -0,0 +1,14 @@ +module Microsoft::Office2::Groups + # List all groups that the given user is a direct and INDIRECT member of (i.e. list all the user's groups and subgroups) + # id: email or other ID of the target User + # result_fields: comma seperated string of which group properties should be included in the result. e.g. 'id,displayName'. Defaults to just 'displayName' + # transitive: if false then only list groups that the user is a DIRECT member of (i.e. don't list subgroups) + # https://docs.microsoft.com/en-us/graph/api/user-list-memberof + def list_user_member_of(id, result_fields = 'displayName', transitive = true) + return {'error': "400: No group \'id\' supplied" } if id.nil? + endpoint = "/v1.0/users/#{id}/" + (transitive ? 'transitiveMemberOf' : 'memberOf') + response = graph_request(request_method: 'get', endpoints: [endpoint], query: { '$select': result_fields, '$top': 999 } ) + check_response(response) + JSON.parse(response.body)['value'] + end +end \ No newline at end of file From a9c768d0460db829009231a42c0542c973677134 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 17 Jan 2020 14:07:05 +0800 Subject: [PATCH 1623/1752] fix(pressac/booking_canceller): inverted cancel logic; default sensor_name blocking map_id; add debug msg when sensor not found --- modules/pressac/booking_canceller.rb | 15 +++++++++++---- modules/pressac/desk_management.rb | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index af54a26d..7a99a682 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -17,7 +17,7 @@ class ::Pressac::BookingCanceller desk_management_system_id: "sys-xxxxxxxx", desk_management_device: "DeskManagement_1", sensor_zone_id: "zone-xxxxxxxx", - sensor_name: "Pressac_PIR_sensor_name", + sensor_name: false, check_every: "1m", delay_until_cancel: "15m" }) @@ -44,9 +44,16 @@ def on_update end def determine_booking_presence + + logger.debug "@sensor: #{@sensor}" + # Expose presence status all_sensors = systems(@desk_management_system)[@desk_management_device] - return "Pressac Booking Canceller: Sensor \"#{@sensor}\" NOT FOUND in \"#{@zone}\"" unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist + unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist + message = "Pressac Booking Canceller: Sensor #{@sensor} NOT FOUND in #{@zone}" unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist + logger.debug message + return message + end self[:presence] = all_sensors[@zone].include? @sensor # Check each booking @@ -55,8 +62,8 @@ def determine_booking_presence bookings&.each do |booking| next unless now < booking[:end_epoch] next unless now + @cancel_delay > booking[:start_epoch] - logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{sensor} presence: #{self[:presence]}" - if self[:presence] + logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{@sensor} presence: #{self[:presence]}" + if !self[:presence] logger.debug "Pressac Booking Canceller: ENDING \"#{booking[:Subject]}\" now" truncate(booking) else diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index d8aee9f9..9fece7a4 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -217,6 +217,7 @@ def id(array) end def unexpose_unresponsive_desks(notification) + return stale_sensors = notification.value stale_ids = id(stale_sensors.map {|s| s.keys.first}) From 8b196cfcff6e51570bf4e7de7958165e68e91f93 Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 18 Jan 2020 10:08:23 +0800 Subject: [PATCH 1624/1752] feat(pressac/booking_canceller): more log output --- modules/pressac/booking_canceller.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 7a99a682..4c5d7cbb 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -62,14 +62,19 @@ def determine_booking_presence bookings&.each do |booking| next unless now < booking[:end_epoch] next unless now + @cancel_delay > booking[:start_epoch] - logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{@sensor} presence: #{self[:presence]}" - if !self[:presence] - logger.debug "Pressac Booking Canceller: ENDING \"#{booking[:Subject]}\" now" - truncate(booking) + logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{@sensor} presence: #{self[:presence]}" + if !self[:presence] + msg = "Pressac Booking Canceller: ENDING \"#{booking[:Subject]}\" now" + logger.debug msg + truncate(booking) + return msg else - logger.debug "Pressac Booking Canceller: No action for \"#{booking[:Subject]}\"" + msg = "Pressac Booking Canceller: No action for \"#{booking[:Subject]}\"" + logger.debug msg + return msg end end + return end def truncate(booking) From 4541c4f77558c9f3ee7402a0f1e44fe29726dbe1 Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 18 Jan 2020 10:12:37 +0800 Subject: [PATCH 1625/1752] feat(office2/RBP/refresh_endpoints): in 120s not 300s --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 455bbf6b..42513859 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -287,7 +287,7 @@ def clear_end_meeting_warning # Relies on frontend recieving this status variable update and acting upon it. Should result in a full page reload. # epoch is an integer, in seconds. def refresh_endpoints(epoch = nil) - self[:reload] = epoch || Time.now.to_i + 300 + self[:reload] = epoch || Time.now.to_i + 120 end protected From 63ff63b7ea0c5b03a3bc72d9d76efe7fce658115 Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 18 Jan 2020 10:17:23 +0800 Subject: [PATCH 1626/1752] fix(office2/event/organisation): safer parsing or organisation from email --- lib/microsoft/office2/event.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/event.rb b/lib/microsoft/office2/event.rb index 13eee018..ffbd4c78 100644 --- a/lib/microsoft/office2/event.rb +++ b/lib/microsoft/office2/event.rb @@ -105,7 +105,7 @@ def format_attendees(attendees, organizer) name: attendee['emailAddress']['name'], visitor: is_visitor, external: is_visitor, - organisation: attendee_email.split('@')[1..-1].join("").split('.')[0]&.capitalize + organisation: attendee_email&.split('@')&.dig(1) } new_attendees.push(attendee_object) if attendee['type'] != 'resource' end @@ -129,4 +129,4 @@ def check_availability(start_param, end_param, available_from, available_to) return true end end -end \ No newline at end of file +end From 5c6087fc9c62ad4e3c32c33d8398f942e8878149 Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 18 Jan 2020 15:31:41 +1100 Subject: [PATCH 1627/1752] fix(pressac/desk_logic/stale): when stale desks are marked as busy, don't expose their ids to every zone --- modules/pressac/desk_management.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 9fece7a4..252c0d07 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -213,11 +213,10 @@ def persist_current_status # Transform an array of Sensor Names to SVG Map IDs, IF the user has specified a mapping in settings(sensor_name_to_desk_mappings) def id(array) return [] if array.nil? - array.map { |i| @desk_ids[i] || i&.to_s } + array.map { |i| @desk_ids[i] || i&.to_s } end def unexpose_unresponsive_desks(notification) - return stale_sensors = notification.value stale_ids = id(stale_sensors.map {|s| s.keys.first}) @@ -234,16 +233,21 @@ def unexpose_unresponsive_desks(notification) when :free @zones.keys&.each do |zone_id| self[zone_id] = self[zone_id] - stale_ids - self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] | stale_ids self[zone_id+':occupied_count'] = self[zone_id].count self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count end when :busy @zones.keys&.each do |zone_id| - self[zone_id] = self[zone_id] | stale_ids - self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] | stale_ids - self[zone_id+':occupied_count'] = self[zone_id].count - self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + zone_updated = false + stale_ids.each do |stale_id| + # Mark the stale id busy IF that id exists in this zone + self[zone_id] = self[zone_id] | stale_ids if self[zone_id+':desk_ids'].include?(stale_id) + zone_updated = true + end + if zone_updated + self[zone_id+':occupied_count'] = self[zone_id].count + self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + end end end end From 736a24a15bbd789b955301d89bfd97ce100ccd10 Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 18 Jan 2020 16:05:15 +1100 Subject: [PATCH 1628/1752] style(pressac/booking_canceller): fix indents; tidy up --- modules/pressac/booking_canceller.rb | 31 ++++++++++++---------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 4c5d7cbb..609b3062 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -17,7 +17,6 @@ class ::Pressac::BookingCanceller desk_management_system_id: "sys-xxxxxxxx", desk_management_device: "DeskManagement_1", sensor_zone_id: "zone-xxxxxxxx", - sensor_name: false, check_every: "1m", delay_until_cancel: "15m" }) @@ -44,16 +43,13 @@ def on_update end def determine_booking_presence - - logger.debug "@sensor: #{@sensor}" - # Expose presence status all_sensors = systems(@desk_management_system)[@desk_management_device] - unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist - message = "Pressac Booking Canceller: Sensor #{@sensor} NOT FOUND in #{@zone}" unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist - logger.debug message - return message - end + unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist + msg = "Pressac Booking Canceller: Sensor #{@sensor} NOT FOUND in #{@zone}" unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist + logger.debug msg + return msg + end self[:presence] = all_sensors[@zone].include? @sensor # Check each booking @@ -61,24 +57,23 @@ def determine_booking_presence bookings = system[@bookings][:today] bookings&.each do |booking| next unless now < booking[:end_epoch] - next unless now + @cancel_delay > booking[:start_epoch] - logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{@sensor} presence: #{self[:presence]}" - if !self[:presence] + next unless (now + @cancel_delay) > booking[:start_epoch] + logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{@sensor} presence: #{self[:presence]}" + if !self[:presence] msg = "Pressac Booking Canceller: ENDING \"#{booking[:Subject]}\" now" - logger.debug msg + logger.debug msg truncate(booking) - return msg + return msg else msg = "Pressac Booking Canceller: No action for \"#{booking[:Subject]}\"" - logger.debug msg - return msg + logger.debug msg + return msg end end - return + return end def truncate(booking) - logger.debug "Canceller: ENDING booking #{booking[:Subject]}" system[@bookings].end_meeting(booking[:id]).then do |response| logger.info "Ended #{booking[:Subject]} with response #{response}" end From aaf7bd4ef21efbb9766e47d24a7b4ea9478fdc0d Mon Sep 17 00:00:00 2001 From: William Le Date: Sat, 18 Jan 2020 16:16:46 +1100 Subject: [PATCH 1629/1752] fix(pressac/booking_canceller): logic in determining when to cancel (time) --- modules/pressac/booking_canceller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 609b3062..345b9226 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -57,7 +57,7 @@ def determine_booking_presence bookings = system[@bookings][:today] bookings&.each do |booking| next unless now < booking[:end_epoch] - next unless (now + @cancel_delay) > booking[:start_epoch] + next unless now > booking[:start_epoch] + @cancel_delay logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{@sensor} presence: #{self[:presence]}" if !self[:presence] msg = "Pressac Booking Canceller: ENDING \"#{booking[:Subject]}\" now" From 7e57085954808ab51a8060bcc2a7c0d2891c5184 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 20 Jan 2020 23:24:12 +0800 Subject: [PATCH 1630/1752] chore(pressac/desk_sensor): default stale threshold 20m > 1h --- modules/pressac/sensors/ws_protocol.rb | 386 ++++++++++++------------- 1 file changed, 193 insertions(+), 193 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 66f031a5..d1412096 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -1,193 +1,193 @@ -# encoding: ASCII-8BIT -# frozen_string_literal: true - -require 'protocols/websocket' -require 'set' - -module Pressac; end -module Pressac::Sensors; end - -# Data flow: -# ========== -# Pressac desk / environment / other sensor modules wirelessly connect to -# Pressac Smart Gateway (https://www.pressac.com/smart-gateway/) uses AMQP or MQTT protocol to push data to MS Azure IOT Hub via internet -# Local Node-RED docker container (default hostname: node-red) connects to the same Azure IOT hub via AMQP over websocket (using "Azure IoT Hub Receiver" https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) -# Initial setup: Install the Azure IOT Hub module to the node-red docker container by running: -# - docker exec -it node-red npm install node-red-contrib-azure-iot-hub -# - visit http://:1880 to configure node-red: -# - create an "Azure IoT Hub Receiver" node. Connect it it to your IOT Hub by setting the connectionstring (see heading "Reading all messages received into Azure IoT Hub": https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) -# - create a "websocket output" node and connect the output of the Azure node to the input of the websocket node -# - edit the websocket node and set the Type to be "listen on" and Path to be "/ws/pressac" -# Engine module (instance of this driver) connects to Node-RED via websockets. Typically "ws://node-red:1880/ws/pressac/" - -class Pressac::Sensors::WsProtocol - include ::Orchestrator::Constants - - descriptive_name 'Pressac Sensors via websocket (local Node-RED)' - generic_name :Websocket - tcp_port 1880 - wait_response false - default_settings({ - websocket_path: '/ws/pressac/', - stale_sensor_threshold: '20m' - }) - - def on_load - # Environment sensor values (temp, humidity) - @environment = {} - self[:environment] = {} - - on_update - end - - # Called after dependency reload and settings updates - def on_update - status = setting(:status) || {} - - @gateways = status[:gateways] || {} - self[:gateways] = @gateways.dup - @last_update = status[:last_update] || "Never" - self[:last_update] = @last_update.dup - self[:stale] = [] - - @ws_path = setting('websocket_path') - @stale_sensor_threshold = UV::Scheduler.parse_duration(setting('stale_sensor_threshold') || '20m') / 1000 - - schedule.clear - schedule.every(setting('stale_sensor_threshold')) { list_stale_sensors } - end - - def connected - new_websocket_client - end - - def disconnected - end - - def mock(sensor, occupied) - gateway = which_gateway(sensor) - @gateways[gateway][sensor] = self[gateway] = { - id: 'mock_data', - name: sensor, - motion: occupied, - voltage: '3.0', - location: nil, - timestamp: Time.now.to_s, - gateway: gateway - } - - self[:gateways] = @gateways.deep_dup - end - - def which_gateway(sensor) - @gateways.each do |g, sensors| - return g if sensors.include? sensor - end - end - - def list_stale_sensors - stale = [] - now = Time.now.to_i - @gateways.each do |g, sensors| - sensors.each do |name, sensor| - if now - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold - stale << {name => (sensor[:last_update] || "Unknown")} - @gateways[g].delete(name) - end - end - end - self[:stale] = stale - self[:gateways] = @gateways.deep_dup - # Save the current status to database, so that it can retrieved when engine restarts - status = { - last_update: self[:last_update], - gateways: @gateways, - stale: stale - } - define_setting(:status, status) - end - - - protected - - def new_websocket_client - @ws = Protocols::Websocket.new(self, "ws://#{remote_address + @ws_path}") # Node that id is optional and only required if there are to be multiple endpoints under the /ws/press/ - @ws.start - end - - def received(data, resolve, command) - @ws.parse(data) - :success - rescue => e - logger.print_error(e, 'parsing websocket data') - disconnect - :abort - end - - # ==================== - # Websocket callbacks: - # ==================== - - # websocket ready - def on_open - logger.debug { "Websocket connected" } - end - - def on_message(raw_string) - logger.debug { "received: #{raw_string}" } - sensor = JSON.parse(raw_string, symbolize_names: true) - - case (sensor[:deviceType] || sensor[:devicetype]) - when 'Under-Desk-Sensor', 'Occupancy-PIR' - # Variations in captialisation of sensor's key names exist amongst different firmware versions - sensor_name = sensor[:deviceName].to_sym || sensor[:devicename].to_sym - gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym - occupancy = sensor[:motionDetected] == true - - # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), - # AND as the latest notification from this gateway (self[gateway]) (for the purpose of the DeskManagent logic upstream) - @gateways[gateway] ||= {} - @gateways[gateway][sensor_name] = { - id: sensor[:deviceId] || sensor[:deviceid], - name: sensor_name, - motion: occupancy, - voltage: sensor[:supplyVoltage][:value] || sensor[:supplyVoltage], - location: sensor[:location], - timestamp: sensor[:timestamp], - last_update: Time.now.in_time_zone($TZ).to_s, - last_update_epoch: Time.now.to_i, - gateway: gateway - } - self[gateway] = @gateways[gateway][sensor_name].dup - self[:gateways] = @gateways.deep_dup - when 'CO2-Temperature-and-Humidity' - @environment[sensor[:devicename]] = { - temp: sensor[:temperature], - humidity: sensor[:humidity], - concentration: sensor[:concentration], - dbm: sensor[:dbm], - id: sensor[:deviceid] - } - self[:environment] = @environment.deep_dup - end - - self[:last_update] = Time.now.in_time_zone($TZ).to_s - - # Save the current status to database, so that it can retrieved when engine restarts - status = { - last_update: self[:last_update], - gateways: @gateways.deep_dup, - stale: self[:stale] - } - define_setting(:status, status) - end - - # connection is closing - def on_close(event) - logger.debug { "Websocket closing... #{event.code} #{event.reason}" } - end - - def on_error(error) - logger.warn "Websocket error: #{error.message}" - end -end +# encoding: ASCII-8BIT +# frozen_string_literal: true + +require 'protocols/websocket' +require 'set' + +module Pressac; end +module Pressac::Sensors; end + +# Data flow: +# ========== +# Pressac desk / environment / other sensor modules wirelessly connect to +# Pressac Smart Gateway (https://www.pressac.com/smart-gateway/) uses AMQP or MQTT protocol to push data to MS Azure IOT Hub via internet +# Local Node-RED docker container (default hostname: node-red) connects to the same Azure IOT hub via AMQP over websocket (using "Azure IoT Hub Receiver" https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) +# Initial setup: Install the Azure IOT Hub module to the node-red docker container by running: +# - docker exec -it node-red npm install node-red-contrib-azure-iot-hub +# - visit http://:1880 to configure node-red: +# - create an "Azure IoT Hub Receiver" node. Connect it it to your IOT Hub by setting the connectionstring (see heading "Reading all messages received into Azure IoT Hub": https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) +# - create a "websocket output" node and connect the output of the Azure node to the input of the websocket node +# - edit the websocket node and set the Type to be "listen on" and Path to be "/ws/pressac" +# Engine module (instance of this driver) connects to Node-RED via websockets. Typically "ws://node-red:1880/ws/pressac/" + +class Pressac::Sensors::WsProtocol + include ::Orchestrator::Constants + + descriptive_name 'Pressac Sensors via websocket (local Node-RED)' + generic_name :Websocket + tcp_port 1880 + wait_response false + default_settings({ + websocket_path: '/ws/pressac/', + stale_sensor_threshold: '1h' + }) + + def on_load + # Environment sensor values (temp, humidity) + @environment = {} + self[:environment] = {} + + on_update + end + + # Called after dependency reload and settings updates + def on_update + status = setting(:status) || {} + + @gateways = status[:gateways] || {} + self[:gateways] = @gateways.dup + @last_update = status[:last_update] || "Never" + self[:last_update] = @last_update.dup + self[:stale] = [] + + @ws_path = setting('websocket_path') + @stale_sensor_threshold = UV::Scheduler.parse_duration(setting('stale_sensor_threshold') || '1h') / 1000 + + schedule.clear + schedule.every(setting('stale_sensor_threshold')) { list_stale_sensors } + end + + def connected + new_websocket_client + end + + def disconnected + end + + def mock(sensor, occupied) + gateway = which_gateway(sensor) + @gateways[gateway][sensor] = self[gateway] = { + id: 'mock_data', + name: sensor, + motion: occupied, + voltage: '3.0', + location: nil, + timestamp: Time.now.to_s, + gateway: gateway + } + + self[:gateways] = @gateways.deep_dup + end + + def which_gateway(sensor) + @gateways.each do |g, sensors| + return g if sensors.include? sensor + end + end + + def list_stale_sensors + stale = [] + now = Time.now.to_i + @gateways.each do |g, sensors| + sensors.each do |name, sensor| + if now - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold + stale << {name => (sensor[:last_update] || "Unknown")} + @gateways[g].delete(name) + end + end + end + self[:stale] = stale + self[:gateways] = @gateways.deep_dup + # Save the current status to database, so that it can retrieved when engine restarts + status = { + last_update: self[:last_update], + gateways: @gateways, + stale: stale + } + define_setting(:status, status) + end + + + protected + + def new_websocket_client + @ws = Protocols::Websocket.new(self, "ws://#{remote_address + @ws_path}") # Node that id is optional and only required if there are to be multiple endpoints under the /ws/press/ + @ws.start + end + + def received(data, resolve, command) + @ws.parse(data) + :success + rescue => e + logger.print_error(e, 'parsing websocket data') + disconnect + :abort + end + + # ==================== + # Websocket callbacks: + # ==================== + + # websocket ready + def on_open + logger.debug { "Websocket connected" } + end + + def on_message(raw_string) + logger.debug { "received: #{raw_string}" } + sensor = JSON.parse(raw_string, symbolize_names: true) + + case (sensor[:deviceType] || sensor[:devicetype]) + when 'Under-Desk-Sensor', 'Occupancy-PIR' + # Variations in captialisation of sensor's key names exist amongst different firmware versions + sensor_name = sensor[:deviceName].to_sym || sensor[:devicename].to_sym + gateway = sensor[:gatewayName].to_sym || 'unknown_gateway'.to_sym + occupancy = sensor[:motionDetected] == true + + # store the new sensor data under the gateway name (self[:gateways][gateway][sensor_name]), + # AND as the latest notification from this gateway (self[gateway]) (for the purpose of the DeskManagent logic upstream) + @gateways[gateway] ||= {} + @gateways[gateway][sensor_name] = { + id: sensor[:deviceId] || sensor[:deviceid], + name: sensor_name, + motion: occupancy, + voltage: sensor[:supplyVoltage][:value] || sensor[:supplyVoltage], + location: sensor[:location], + timestamp: sensor[:timestamp], + last_update: Time.now.in_time_zone($TZ).to_s, + last_update_epoch: Time.now.to_i, + gateway: gateway + } + self[gateway] = @gateways[gateway][sensor_name].dup + self[:gateways] = @gateways.deep_dup + when 'CO2-Temperature-and-Humidity' + @environment[sensor[:devicename]] = { + temp: sensor[:temperature], + humidity: sensor[:humidity], + concentration: sensor[:concentration], + dbm: sensor[:dbm], + id: sensor[:deviceid] + } + self[:environment] = @environment.deep_dup + end + + self[:last_update] = Time.now.in_time_zone($TZ).to_s + + # Save the current status to database, so that it can retrieved when engine restarts + status = { + last_update: self[:last_update], + gateways: @gateways.deep_dup, + stale: self[:stale] + } + define_setting(:status, status) + end + + # connection is closing + def on_close(event) + logger.debug { "Websocket closing... #{event.code} #{event.reason}" } + end + + def on_error(error) + logger.warn "Websocket error: #{error.message}" + end +end From 3e6cfd1a95700ea9378c614ec1ee3d322b4a6bc9 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 20 Jan 2020 23:26:28 +0800 Subject: [PATCH 1631/1752] chore(pressac/sensor/stale): persist stale sensors in stale status and gateway tree; remove stale sensors from stale sensors immediately if they post. --- modules/pressac/sensors/ws_protocol.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index d1412096..6ce4998a 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -48,7 +48,7 @@ def on_update self[:gateways] = @gateways.dup @last_update = status[:last_update] || "Never" self[:last_update] = @last_update.dup - self[:stale] = [] + self[:stale] = [] @ws_path = setting('websocket_path') @stale_sensor_threshold = UV::Scheduler.parse_duration(setting('stale_sensor_threshold') || '1h') / 1000 @@ -86,18 +86,15 @@ def which_gateway(sensor) end def list_stale_sensors - stale = [] now = Time.now.to_i @gateways.each do |g, sensors| sensors.each do |name, sensor| if now - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold - stale << {name => (sensor[:last_update] || "Unknown")} - @gateways[g].delete(name) + @stale << {name => (sensor[:last_update] || "Unknown")} end end end - self[:stale] = stale - self[:gateways] = @gateways.deep_dup + self[:stale] = @stale # Save the current status to database, so that it can retrieved when engine restarts status = { last_update: self[:last_update], @@ -158,8 +155,12 @@ def on_message(raw_string) last_update_epoch: Time.now.to_i, gateway: gateway } - self[gateway] = @gateways[gateway][sensor_name].dup + self[gateway] = @gateways[gateway][sensor_name].dup self[:gateways] = @gateways.deep_dup + if @stale[sensor_name] + @stale.except!(sensor_name) + self[:stale] = @stale.deep_dup + end when 'CO2-Temperature-and-Humidity' @environment[sensor[:devicename]] = { temp: sensor[:temperature], From c4c7a69de939d550bbd4ce124160f2d21e4c9aa6 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 21 Jan 2020 00:08:11 +0800 Subject: [PATCH 1632/1752] fix(pressac/desk/logic): new method for exposing stale IDs --- modules/pressac/desk_management.rb | 28 ++++++++++++-------------- modules/pressac/sensors/ws_protocol.rb | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 252c0d07..8a4f9fbb 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -218,36 +218,34 @@ def id(array) def unexpose_unresponsive_desks(notification) stale_sensors = notification.value - stale_ids = id(stale_sensors.map {|s| s.keys.first}) + # Sort into Zones, keeping SVG map ID only + zoned_stale_sensors = Hash.new(Array.new) + stale_sensors&.each { |s| zoned_stale_sensors[@which_zone[s[:gateway]] << id(s.keys) ] } - logger.debug "PRESSAC > DESK > LOGIC: Displaying stale sensors as #{@stale_status}: #{stale_ids}" + logger.debug "PRESSAC > DESK > LOGIC: Displaying stale sensors as #{@stale_status}: #{zoned_stale_sensors}" case @stale_status when :blank - @zones.keys&.each do |zone_id| + zoned_stale_sensors&.each do |zone_id, stale_ids| self[zone_id] = self[zone_id] - stale_ids self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] - stale_ids self[zone_id+':occupied_count'] = self[zone_id].count self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count end when :free - @zones.keys&.each do |zone_id| + zoned_stale_sensors&.each do |zone_id, stale_ids| self[zone_id] = self[zone_id] - stale_ids + self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] | stale_ids self[zone_id+':occupied_count'] = self[zone_id].count self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count end when :busy - @zones.keys&.each do |zone_id| - zone_updated = false - stale_ids.each do |stale_id| - # Mark the stale id busy IF that id exists in this zone - self[zone_id] = self[zone_id] | stale_ids if self[zone_id+':desk_ids'].include?(stale_id) - zone_updated = true - end - if zone_updated - self[zone_id+':occupied_count'] = self[zone_id].count - self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count - end + zoned_stale_sensors&.each do |zone_id, stale_ids| + self[zone_id] = self[zone_id] | stale_ids + self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] | stale_ids + self[zone_id+':occupied_count'] = self[zone_id].count + self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + end end end end diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 6ce4998a..dfdeb562 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -90,7 +90,7 @@ def list_stale_sensors @gateways.each do |g, sensors| sensors.each do |name, sensor| if now - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold - @stale << {name => (sensor[:last_update] || "Unknown")} + @stale << { name => sensor.slice(:last_update, :gateway, :voltage) } # only keep useful values end end end From d32f17fa858aabb9faff64099053690edba1955a Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 21 Jan 2020 00:10:41 +0800 Subject: [PATCH 1633/1752] fix(pressac/desk/logic): syntax --- modules/pressac/desk_management.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 8a4f9fbb..9c21c915 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -246,7 +246,6 @@ def unexpose_unresponsive_desks(notification) self[zone_id+':occupied_count'] = self[zone_id].count self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count end - end end end end From 094ae1ea407395ac4457267fe1bb6e0562e59017 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 21 Jan 2020 00:14:30 +0800 Subject: [PATCH 1634/1752] fix(pressac/desk/logic): missing var init @stale --- modules/pressac/sensors/ws_protocol.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index dfdeb562..90ddef82 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -46,9 +46,12 @@ def on_update @gateways = status[:gateways] || {} self[:gateways] = @gateways.dup + @last_update = status[:last_update] || "Never" self[:last_update] = @last_update.dup - self[:stale] = [] + + @stale = status[:stale] || {} + self[:stale] = @stale.deep_dup @ws_path = setting('websocket_path') @stale_sensor_threshold = UV::Scheduler.parse_duration(setting('stale_sensor_threshold') || '1h') / 1000 From fbb0b4f27b780feb21d5ba06e85f29dc7aac654e Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 22 Jan 2020 17:19:57 +0800 Subject: [PATCH 1635/1752] fix(pressac/sensors/stale): var ref and usage --- modules/pressac/sensors/ws_protocol.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 90ddef82..e710555b 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -93,7 +93,7 @@ def list_stale_sensors @gateways.each do |g, sensors| sensors.each do |name, sensor| if now - (sensor[:last_update_epoch] || 0 ) > @stale_sensor_threshold - @stale << { name => sensor.slice(:last_update, :gateway, :voltage) } # only keep useful values + @stale[name] = sensor.slice(:last_update, :gateway, :voltage) # only keep useful values end end end @@ -102,7 +102,7 @@ def list_stale_sensors status = { last_update: self[:last_update], gateways: @gateways, - stale: stale + stale: @stale } define_setting(:status, status) end From 1be650a5415daec913765687e73bbb531e5dfc4b Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 24 Jan 2020 17:04:13 +0800 Subject: [PATCH 1636/1752] feat(o365/lib/client): support multiple o365 tenants at the same time --- lib/microsoft/office2/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 2b1d99e3..9e4d8b4e 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -50,8 +50,8 @@ def initialize( app_scope: "https://graph.microsoft.com/.default", graph_domain: "https://graph.microsoft.com", https_proxy: nil, - save_token: Proc.new{ |token| User.bucket.set("office-token", token) }, - get_token: Proc.new{ User.bucket.get("office-token", quiet: true) } + save_token: Proc.new{ |token| User.bucket.set("office-token-#{client_id}", token) }, + get_token: Proc.new{ User.bucket.get("office-token-#{client_id}", quiet: true) } ) @client_id = client_id @client_secret = client_secret From a7277a6f2f9a671721878869e6bef1ef3687960e Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 30 Jan 2020 11:04:00 +0800 Subject: [PATCH 1637/1752] feat(pressac/desk/logic): Add free_count to zone status --- modules/pressac/desk_management.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 9c21c915..014274fc 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -71,8 +71,9 @@ def on_update @zones.keys&.each do |zone_id| self[zone_id] ||= saved_status[zone_id] || [] self[zone_id+':desk_ids'] ||= saved_status[zone_id+':desk_ids'] || [] - self[zone_id+':occupied_count'] = self[zone_id]&.count || 0 self[zone_id+':desk_count'] = self[zone_id+'desk_ids']&.count || 0 + self[zone_id+':occupied_count'] = self[zone_id]&.count || 0 + self[zone_id+':free_count'] = self[zone_id+':desk_count'] - self[zone_id+':occupied_count'] end # Create a reverse lookup (gateway => zone) @@ -192,6 +193,7 @@ def expose_desks(new_status) self[z+':desk_ids'] = self[z+':desk_ids'] | desks[:free] | desks[:busy] self[z+':desk_count'] = self[z+':desk_ids'].count self[z+':occupied_count'] = self[zone].count + self[z+':free_count'] = self[z+':desk_count'] - self[z+':occupied_count'] end end @@ -206,6 +208,7 @@ def persist_current_status status[zone+':desk_ids'] = self[zone+':desk_ids'] status[zone+':desk_count'] = self[zone+':desk_count'] status[zone+':occupied_count'] = self[zone+':occupied_count'] + status[zone+':free_count'] = self[zone+':desk_count'] - self[zone+':occupied_count'] end define_setting(:zzz_status, status) end @@ -231,6 +234,7 @@ def unexpose_unresponsive_desks(notification) self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] - stale_ids self[zone_id+':occupied_count'] = self[zone_id].count self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + self[zone_id+':free_count'] = self[zone_id+':desk_count'] - self[zone_id+':occupied_count'] end when :free zoned_stale_sensors&.each do |zone_id, stale_ids| @@ -238,6 +242,7 @@ def unexpose_unresponsive_desks(notification) self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] | stale_ids self[zone_id+':occupied_count'] = self[zone_id].count self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + self[zone_id+':free_count'] = self[zone_id+':desk_count'] - self[zone_id+':occupied_count'] end when :busy zoned_stale_sensors&.each do |zone_id, stale_ids| @@ -245,6 +250,7 @@ def unexpose_unresponsive_desks(notification) self[zone_id+':desk_ids'] = self[zone_id+':desk_ids'] | stale_ids self[zone_id+':occupied_count'] = self[zone_id].count self[zone_id+':desk_count'] = self[zone_id+':desk_ids'].count + self[zone_id+':free_count'] = self[zone_id+':desk_count'] - self[zone_id+':occupied_count'] end end end From 5568c3f7e5bc83c89bce4a7be9f109396d4edae7 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 30 Jan 2020 11:07:24 +0800 Subject: [PATCH 1638/1752] fix(pressac/desk/logic): correct desk_count status on load --- modules/pressac/desk_management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 014274fc..9179e7a4 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -71,7 +71,7 @@ def on_update @zones.keys&.each do |zone_id| self[zone_id] ||= saved_status[zone_id] || [] self[zone_id+':desk_ids'] ||= saved_status[zone_id+':desk_ids'] || [] - self[zone_id+':desk_count'] = self[zone_id+'desk_ids']&.count || 0 + self[zone_id+':desk_count'] = self[zone_id+':desk_ids']&.count || 0 self[zone_id+':occupied_count'] = self[zone_id]&.count || 0 self[zone_id+':free_count'] = self[zone_id+':desk_count'] - self[zone_id+':occupied_count'] end From 8f486d40917e8b8552dbbdbc7500777186c7026b Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 5 Feb 2020 17:27:39 +0800 Subject: [PATCH 1639/1752] feat(desk_bookings): new desk bookings driver (untested) --- modules/aca/desk_bookings.rb | 143 +++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 modules/aca/desk_bookings.rb diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb new file mode 100644 index 00000000..b0fc7118 --- /dev/null +++ b/modules/aca/desk_bookings.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +module Aca; end +class ::Aca::DeskBookings + include ::Orchestrator::Constants + + descriptive_name 'ACA Desk Bookings Logic' + generic_name :DeskBookings + implements :logic + + default_settings({ + cancel_bookings_after: "1h", + check_autocancel_every: "5m" + zone_to_desk_ids: { + "zone-xxx" => ["desk-01.001", "desk-01.002"], + "zone-yyy" => ["desk-02.001", "desk-02.002"] + } + }) + + def on_load + system.load_complete do + begin + @status = {} + on_update + rescue => e + logger.print_error e + end + end + end + + def on_update + # convert '1m2s' to '62' + @autocancel_delay = UV::Scheduler.parse_duration(setting('cancel_bookings_after') || '0s') / 1000 + + # local timezone of all the desks served by this logic (usually, all the desks in this building) + @tz = setting('timezone') || ENV['TZ'] + + # load and expose previously saved status if there is no current status. + # AND create a reverse lookup (desk => zone) + @zone_of = {} + saved_status = setting('status') || {} + @zones_to_desks = setting('zone_to_desk_ids') || {} + @zones_to_desks.each do |zone, desks| + desks.each { |desk| @zone_of[desk] = zone + ':bookings' } + self[zone+':bookings'] = @status[zone+':bookings'] ||= saved_status[zone+':bookings'] || [] + # self[zone+':free'] = @status[zone+':free'] ||= saved_status[zone+':free'] || [] + # self[zone+':busy'] = @status[zone+':busy'] ||= saved_status[zone+':busy'] || [] + end + + schedule.clear + schedule.every(check_autocancel_every) { autocancel_bookings } if @autocancel_delay + end + + # @param desk_id [String] the unique id that represents a desk + def desk_details(*desk_ids) + todays_date = Time.now.in_time_zone(@tz).strftime('%F') #e.g. 2020-12-31 in local time of the desk + desk_ids.map { |desk| @status&.dig(zone_of(desk), desk, todays_date)&.first } + end + + def book(desk_id, start_epoch) + todays_date = Time.now.in_time_zone(@tz).strftime('%F') #e.g. 2020-12-31 in local time of the desk + booking_date = Time.at(start_epoch).in_time_zone(@tz).strftime('%F') + end_epoch = Time.at(start_epoch).in_time_zone(@tz).midnight.tomorrow + zone = zone_of[desk_id] + + new_booking = { + start: start_epoch, + end: end_epoch, + checked_in: (booking_date == todays_date) + } + @status[zone][desk_id][booking_date][current_user.email] = new_booking + expose_status(zone) + + # Also store booking in user profile + current_user.desk_bookings[booking_date] ||= {} + current_user.desk_bookings[booking_date][desk_id] = new_booking + current_user.save + + # STUB: Notify user of desk booking via email here + end + + def cancel(desk_id, start_epoch) + booking_date = Time.at(start_epoch).in_time_zone(@tz).strftime('%F') + zone = zone_of[desk_id] + raise "400 Error: No booking on #{booking_date} for #{current.email} at #{desk_id}" unless @status.dig(zone,desk_id,booking_date,user,start) + + @status[zone][desk_id][booking_date].delete(user) + expose_status(zone) + + # Also delete booking from user profile + current_user.desk_bookings[booking_date]&.delete(desk_id) + current_user.save + end + + # param checking_in is a bool: true = checkin, false = checkout + def check_in(desk_id, checking_in) + zone = zone_of[desk_id] + todays_date = Time.now.in_time_zone(@tz).strftime('%F') + user = current_user.email + raise "400 Error: No booking on #{todays_date} for #{current.email} at #{desk_id}" unless @status.dig(zone,desk_id,todays_date,user,start) + if checking_in + @status[zone][desk_id][todays_date][user][checked_in] = true + current_user.desk_bookings[booking_date][desk_id][checked_in] = true + else + @status[zone][desk_id][todays_date].delete(user) + current_user.desk_bookings[booking_date]&.delete(desk_id) + end + expose_status(zone) + current_user.save + end + + + protected + + def expose_status(zone) + self[zone+':bookings'] = @status[zone+':bookings'] + define_setting(:status, @status) # Also persist new status to DB + end + + def autocancel_bookings + todays_date = Time.now.in_time_zone(@tz).strftime('%F') + now = Time.now + + @status.each do |zone| + zone.each do |desk| + desk[todays_date]&.each_with do |user_email, booking| + next if booking[checked_in] + next unless booking[:start] > now + @autocancel_delay + @status[zone][desk_id][todays_date].delete(user) + expose_status(zone) + + next unless ENV['ENGINE_DEFAULT_AUTHORITY_ID'] + user = User.find_by_email(ENV['ENGINE_DEFAULT_AUTHORITY_ID'], user_email) + user.desk_bookings[booking_date]&.delete(desk_id) + user.save + + # STUB: Notify user of cancellation by email here + end + end + end + end + +end From 2fdddfd6db19cadd0e8022fe562493b1b89e1ab1 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 5 Feb 2020 14:48:39 +0000 Subject: [PATCH 1640/1752] fix(desk_bookings): expose all known desk ids; fix schedule var name and syntax --- modules/aca/desk_bookings.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index b0fc7118..b62c275d 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -10,7 +10,7 @@ class ::Aca::DeskBookings default_settings({ cancel_bookings_after: "1h", - check_autocancel_every: "5m" + check_autocancel_every: "5m", zone_to_desk_ids: { "zone-xxx" => ["desk-01.001", "desk-01.002"], "zone-yyy" => ["desk-02.001", "desk-02.002"] @@ -31,24 +31,27 @@ def on_load def on_update # convert '1m2s' to '62' @autocancel_delay = UV::Scheduler.parse_duration(setting('cancel_bookings_after') || '0s') / 1000 - + @autocancel_scan_interval = setting('cancel_bookings_after') + # local timezone of all the desks served by this logic (usually, all the desks in this building) @tz = setting('timezone') || ENV['TZ'] - # load and expose previously saved status if there is no current status. - # AND create a reverse lookup (desk => zone) @zone_of = {} saved_status = setting('status') || {} @zones_to_desks = setting('zone_to_desk_ids') || {} @zones_to_desks.each do |zone, desks| + # create reverse lookup: desk => zone desks.each { |desk| @zone_of[desk] = zone + ':bookings' } + + # expose all known desk ids + self[zone] = @status[zone] = @zones_to_desks[zone] + + # load and expose previously saved status if there is no current status. self[zone+':bookings'] = @status[zone+':bookings'] ||= saved_status[zone+':bookings'] || [] - # self[zone+':free'] = @status[zone+':free'] ||= saved_status[zone+':free'] || [] - # self[zone+':busy'] = @status[zone+':busy'] ||= saved_status[zone+':busy'] || [] end schedule.clear - schedule.every(check_autocancel_every) { autocancel_bookings } if @autocancel_delay + schedule.every(@autocancel_scan_interval) { autocancel_bookings } if @autocancel_delay end # @param desk_id [String] the unique id that represents a desk From 39f07504118f66d05a81149ed35bcbee276a36f4 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 5 Feb 2020 16:02:04 +0000 Subject: [PATCH 1641/1752] fix(desk_bookings): fix syntax; fix status var notification --- modules/aca/desk_bookings.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index b62c275d..cc50b9c0 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -47,7 +47,7 @@ def on_update self[zone] = @status[zone] = @zones_to_desks[zone] # load and expose previously saved status if there is no current status. - self[zone+':bookings'] = @status[zone+':bookings'] ||= saved_status[zone+':bookings'] || [] + self[zone+':bookings'] = @status[zone+':bookings'] ||= saved_status[zone+':bookings'] || {} end schedule.clear @@ -57,20 +57,23 @@ def on_update # @param desk_id [String] the unique id that represents a desk def desk_details(*desk_ids) todays_date = Time.now.in_time_zone(@tz).strftime('%F') #e.g. 2020-12-31 in local time of the desk - desk_ids.map { |desk| @status&.dig(zone_of(desk), desk, todays_date)&.first } + desk_ids.map { |desk| @status&.dig(@zone_of[desk], desk, todays_date)&.first } end def book(desk_id, start_epoch) todays_date = Time.now.in_time_zone(@tz).strftime('%F') #e.g. 2020-12-31 in local time of the desk - booking_date = Time.at(start_epoch).in_time_zone(@tz).strftime('%F') - end_epoch = Time.at(start_epoch).in_time_zone(@tz).midnight.tomorrow - zone = zone_of[desk_id] + start_time = Time.at(start_epoch).in_time_zone(@tz) + booking_date = start_time.strftime('%F') + end_epoch = start_time.midnight.tomorrow.to_i + zone = @zone_of[desk_id] new_booking = { start: start_epoch, end: end_epoch, checked_in: (booking_date == todays_date) } + @status[zone][desk_id] ||= {} + @status[zone][desk_id][booking_date] ||= {} @status[zone][desk_id][booking_date][current_user.email] = new_booking expose_status(zone) @@ -84,7 +87,7 @@ def book(desk_id, start_epoch) def cancel(desk_id, start_epoch) booking_date = Time.at(start_epoch).in_time_zone(@tz).strftime('%F') - zone = zone_of[desk_id] + zone = @zone_of[desk_id] raise "400 Error: No booking on #{booking_date} for #{current.email} at #{desk_id}" unless @status.dig(zone,desk_id,booking_date,user,start) @status[zone][desk_id][booking_date].delete(user) @@ -97,7 +100,7 @@ def cancel(desk_id, start_epoch) # param checking_in is a bool: true = checkin, false = checkout def check_in(desk_id, checking_in) - zone = zone_of[desk_id] + zone = @zone_of[desk_id] todays_date = Time.now.in_time_zone(@tz).strftime('%F') user = current_user.email raise "400 Error: No booking on #{todays_date} for #{current.email} at #{desk_id}" unless @status.dig(zone,desk_id,todays_date,user,start) @@ -116,7 +119,8 @@ def check_in(desk_id, checking_in) protected def expose_status(zone) - self[zone+':bookings'] = @status[zone+':bookings'] + self[zone] = @status[zone].deep_dup + signal_status(zone) define_setting(:status, @status) # Also persist new status to DB end From 720fe3ed76ccc0ac11a04071ea4510fb8535a713 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 7 Feb 2020 05:25:38 +0000 Subject: [PATCH 1642/1752] fix(desk_bookings): syntax, var name --- modules/aca/desk_bookings.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index cc50b9c0..442acdd7 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -103,13 +103,15 @@ def check_in(desk_id, checking_in) zone = @zone_of[desk_id] todays_date = Time.now.in_time_zone(@tz).strftime('%F') user = current_user.email - raise "400 Error: No booking on #{todays_date} for #{current.email} at #{desk_id}" unless @status.dig(zone,desk_id,todays_date,user,start) + raise "400 Error: No booking on #{todays_date} for #{user} at #{desk_id}" unless @status.dig(zone,desk_id,todays_date,user,:start) if checking_in - @status[zone][desk_id][todays_date][user][checked_in] = true - current_user.desk_bookings[booking_date][desk_id][checked_in] = true + @status[zone][desk_id][todays_date][user][:checked_in] = true + current_user.desk_bookings[todays_date] ||= {} + current_user.desk_bookings[todays_date][desk_id] ||= {} + current_user.desk_bookings[todays_date][desk_id][:checked_in] = true else @status[zone][desk_id][todays_date].delete(user) - current_user.desk_bookings[booking_date]&.delete(desk_id) + current_user.desk_bookings[todays_date]&.delete(desk_id) end expose_status(zone) current_user.save From 4357b8045a9a7dc10c9338b4531247a38715a3f1 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 7 Feb 2020 05:26:28 +0000 Subject: [PATCH 1643/1752] fix(desk_bookings): syntax, simplify status vars --- modules/aca/desk_bookings.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index 442acdd7..df549ff7 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -40,14 +40,13 @@ def on_update saved_status = setting('status') || {} @zones_to_desks = setting('zone_to_desk_ids') || {} @zones_to_desks.each do |zone, desks| - # create reverse lookup: desk => zone - desks.each { |desk| @zone_of[desk] = zone + ':bookings' } - - # expose all known desk ids - self[zone] = @status[zone] = @zones_to_desks[zone] - # load and expose previously saved status if there is no current status. - self[zone+':bookings'] = @status[zone+':bookings'] ||= saved_status[zone+':bookings'] || {} + @status[zone+':bookings'] ||= saved_status[zone+':bookings'] || {} + desks.each do |desk| + @zone_of[desk] = zone + ':bookings' # create reverse lookup: desk => zone + @status[zone+':bookings'][desk] ||= {} # expose all known desk ids without overwriting existing bookings + end + expose_status(zone+':bookings') end schedule.clear @@ -88,7 +87,7 @@ def book(desk_id, start_epoch) def cancel(desk_id, start_epoch) booking_date = Time.at(start_epoch).in_time_zone(@tz).strftime('%F') zone = @zone_of[desk_id] - raise "400 Error: No booking on #{booking_date} for #{current.email} at #{desk_id}" unless @status.dig(zone,desk_id,booking_date,user,start) + raise "400 Error: No booking on #{booking_date} for #{current.email} at #{desk_id}" unless @status.dig(zone,desk_id,booking_date,user,:start) @status[zone][desk_id][booking_date].delete(user) expose_status(zone) @@ -120,10 +119,10 @@ def check_in(desk_id, checking_in) protected - def expose_status(zone) + def expose_status(zone, save_status = true) self[zone] = @status[zone].deep_dup signal_status(zone) - define_setting(:status, @status) # Also persist new status to DB + define_setting(:status, @status) if save_status # Also persist new status to DB end def autocancel_bookings From b7686b8bb2bcc2f008b4f204fd3f67fe696d4003 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 10 Feb 2020 17:07:07 +0000 Subject: [PATCH 1644/1752] fix(desk_bookings): limit potential infinite loop if unexpected settings entered --- modules/aca/desk_bookings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index df549ff7..34d860b5 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -50,7 +50,7 @@ def on_update end schedule.clear - schedule.every(@autocancel_scan_interval) { autocancel_bookings } if @autocancel_delay + schedule.every(@autocancel_scan_interval) { autocancel_bookings } if @autocancel_delay && @autocancel_scan_interval end # @param desk_id [String] the unique id that represents a desk From 00f9d56aa2ee6a92e31e11e23ef933168a8f8dfb Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 10 Feb 2020 17:08:00 +0000 Subject: [PATCH 1645/1752] fix(desk_bookings): save desk bookings to user model --- modules/aca/desk_bookings.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index 34d860b5..298019c3 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -79,7 +79,7 @@ def book(desk_id, start_epoch) # Also store booking in user profile current_user.desk_bookings[booking_date] ||= {} current_user.desk_bookings[booking_date][desk_id] = new_booking - current_user.save + current_user.save! # STUB: Notify user of desk booking via email here end @@ -87,14 +87,15 @@ def book(desk_id, start_epoch) def cancel(desk_id, start_epoch) booking_date = Time.at(start_epoch).in_time_zone(@tz).strftime('%F') zone = @zone_of[desk_id] - raise "400 Error: No booking on #{booking_date} for #{current.email} at #{desk_id}" unless @status.dig(zone,desk_id,booking_date,user,:start) + user = current_user.email + raise "400 Error: No booking on #{booking_date} for #{user} at #{desk_id}" unless @status.dig(zone, desk_id, booking_date, user, :start) @status[zone][desk_id][booking_date].delete(user) expose_status(zone) # Also delete booking from user profile current_user.desk_bookings[booking_date]&.delete(desk_id) - current_user.save + current_user.save! end # param checking_in is a bool: true = checkin, false = checkout @@ -113,7 +114,7 @@ def check_in(desk_id, checking_in) current_user.desk_bookings[todays_date]&.delete(desk_id) end expose_status(zone) - current_user.save + current_user.save! end @@ -140,7 +141,7 @@ def autocancel_bookings next unless ENV['ENGINE_DEFAULT_AUTHORITY_ID'] user = User.find_by_email(ENV['ENGINE_DEFAULT_AUTHORITY_ID'], user_email) user.desk_bookings[booking_date]&.delete(desk_id) - user.save + user.save! # STUB: Notify user of cancellation by email here end From 40d0249e20810eece929e4bf1cd11927bcccb44d Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 10 Feb 2020 17:08:31 +0000 Subject: [PATCH 1646/1752] fix(desk_bookings/book): support optional end time --- modules/aca/desk_bookings.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index 298019c3..e660af66 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -59,21 +59,31 @@ def desk_details(*desk_ids) desk_ids.map { |desk| @status&.dig(@zone_of[desk], desk, todays_date)&.first } end - def book(desk_id, start_epoch) + def book(desk_id, start_epoch, end_epoch = nil) todays_date = Time.now.in_time_zone(@tz).strftime('%F') #e.g. 2020-12-31 in local time of the desk start_time = Time.at(start_epoch).in_time_zone(@tz) booking_date = start_time.strftime('%F') - end_epoch = start_time.midnight.tomorrow.to_i + end_epoch ||= start_time.midnight.tomorrow.to_i zone = @zone_of[desk_id] new_booking = { - start: start_epoch, - end: end_epoch, + start: start_epoch, + end: end_epoch - 1, checked_in: (booking_date == todays_date) } @status[zone][desk_id] ||= {} @status[zone][desk_id][booking_date] ||= {} + if @status[zone][desk_id][booking_date].present? + existing_bookings = @status[zone][desk_id][booking_date] + existing_bookings.each do |existing_booking_owner, existing_booking| + # check for clash + if new_booking[:end] >= existing_booking[:start] && new_booking[:start] <= existing_booking[:end] + raise "400 Error: Clashing booking at #{Time.at(existing_booking[:start]).strftime('%T%:z')} - #{Time.at(existing_booking[:end]).strftime('%T%:z')}" + end + end + else @status[zone][desk_id][booking_date][current_user.email] = new_booking + end expose_status(zone) # Also store booking in user profile From 0877758f39eab4149fd84f00ebede4b675e707dd Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 10 Feb 2020 17:09:19 +0000 Subject: [PATCH 1647/1752] fix(desk_bookings): support optional end time; throw error if booking clashes --- modules/aca/desk_bookings.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index e660af66..d3d3e8c6 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -72,7 +72,7 @@ def book(desk_id, start_epoch, end_epoch = nil) checked_in: (booking_date == todays_date) } @status[zone][desk_id] ||= {} - @status[zone][desk_id][booking_date] ||= {} + @status[zone][desk_id][booking_date] ||= {} if @status[zone][desk_id][booking_date].present? existing_bookings = @status[zone][desk_id][booking_date] existing_bookings.each do |existing_booking_owner, existing_booking| @@ -82,7 +82,7 @@ def book(desk_id, start_epoch, end_epoch = nil) end end else - @status[zone][desk_id][booking_date][current_user.email] = new_booking + @status[zone][desk_id][booking_date][current_user.email] = new_booking end expose_status(zone) From 2f1656d562099f9e23eff515da214f9966c5431c Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 11 Feb 2020 23:19:59 +0000 Subject: [PATCH 1648/1752] fix(desk_bookings): store booking if there's another non-clashing booking on the same day --- modules/aca/desk_bookings.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index d3d3e8c6..7ca97dd8 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -81,9 +81,9 @@ def book(desk_id, start_epoch, end_epoch = nil) raise "400 Error: Clashing booking at #{Time.at(existing_booking[:start]).strftime('%T%:z')} - #{Time.at(existing_booking[:end]).strftime('%T%:z')}" end end - else - @status[zone][desk_id][booking_date][current_user.email] = new_booking end + @status[zone][desk_id][booking_date][current_user.email] = new_booking + expose_status(zone) # Also store booking in user profile From f90ab47d01707c3366177c23dcf4f7bf52172932 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 11 Feb 2020 23:20:59 +0000 Subject: [PATCH 1649/1752] feat(desk_bookings): switch user.desk_bookings to array, from hash --- modules/aca/desk_bookings.rb | 40 +++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index 7ca97dd8..a62ebc0c 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -85,10 +85,11 @@ def book(desk_id, start_epoch, end_epoch = nil) @status[zone][desk_id][booking_date][current_user.email] = new_booking expose_status(zone) - - # Also store booking in user profile - current_user.desk_bookings[booking_date] ||= {} - current_user.desk_bookings[booking_date][desk_id] = new_booking + + # Also store booking in user object, in a slightly different format + new_booking[:desk_id] = desk_id + new_booking[:zone] = zone + current_user.desk_bookings << new_booking current_user.save! # STUB: Notify user of desk booking via email here @@ -104,32 +105,43 @@ def cancel(desk_id, start_epoch) expose_status(zone) # Also delete booking from user profile - current_user.desk_bookings[booking_date]&.delete(desk_id) - current_user.save! + delete_booking_from_user(desk_id, start_epoch) end # param checking_in is a bool: true = checkin, false = checkout - def check_in(desk_id, checking_in) + def check_in(desk_id, start_epoch, checking_in) zone = @zone_of[desk_id] - todays_date = Time.now.in_time_zone(@tz).strftime('%F') user = current_user.email - raise "400 Error: No booking on #{todays_date} for #{user} at #{desk_id}" unless @status.dig(zone,desk_id,todays_date,user,:start) + todays_date = Time.now.in_time_zone(@tz).strftime('%F') + booking = @status.dig(zone,desk_id,todays_date,user) + raise "400 Error: No booking on #{todays_date} for #{user} at #{desk_id}" unless booking.present? if checking_in + # Mark as checked_in on this logic @status[zone][desk_id][todays_date][user][:checked_in] = true - current_user.desk_bookings[todays_date] ||= {} - current_user.desk_bookings[todays_date][desk_id] ||= {} - current_user.desk_bookings[todays_date][desk_id][:checked_in] = true + # Mark as checked_in on the user profile + current_user.desk_bookings.each_with_index do |b, i| + next unless b[:start] == start_epoch && b[:desk_id] == desk_id + current_user.desk_bookings[i][:checked_in] = true + end + current_user.save! else @status[zone][desk_id][todays_date].delete(user) - current_user.desk_bookings[todays_date]&.delete(desk_id) + delete_booking_from_user(desk_id, start_epoch) end expose_status(zone) - current_user.save! end protected + def delete_booking_from_user(desk_id, start_epoch) + current_user.desk_bookings&.each_with_index do |b, i| + next unless b[:start] == start_epoch && b[:desk_id] == desk_id + current_user.desk_bookings&.delete_at(i) + end + current_user.save! + end + def expose_status(zone, save_status = true) self[zone] = @status[zone].deep_dup signal_status(zone) From 210906c119cd10a66e49a1d6395e57ec6097862a Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 17 Feb 2020 07:07:38 +0000 Subject: [PATCH 1650/1752] fix(desk_bookings): save bookings to user model --- modules/aca/desk_bookings.rb | 49 +++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index a62ebc0c..1a553ce0 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -89,8 +89,7 @@ def book(desk_id, start_epoch, end_epoch = nil) # Also store booking in user object, in a slightly different format new_booking[:desk_id] = desk_id new_booking[:zone] = zone - current_user.desk_bookings << new_booking - current_user.save! + add_to_schedule(current_user.email, new_booking) # STUB: Notify user of desk booking via email here end @@ -105,7 +104,7 @@ def cancel(desk_id, start_epoch) expose_status(zone) # Also delete booking from user profile - delete_booking_from_user(desk_id, start_epoch) + delete_from_schedule(current_user.email, desk_id, start_epoch) end # param checking_in is a bool: true = checkin, false = checkout @@ -119,14 +118,16 @@ def check_in(desk_id, start_epoch, checking_in) # Mark as checked_in on this logic @status[zone][desk_id][todays_date][user][:checked_in] = true # Mark as checked_in on the user profile - current_user.desk_bookings.each_with_index do |b, i| + user = get_user(current_user.email) + user.desk_bookings.each_with_index do |b, i| next unless b[:start] == start_epoch && b[:desk_id] == desk_id - current_user.desk_bookings[i][:checked_in] = true + user.desk_bookings[i][:checked_in] = true end - current_user.save! + user.desk_bookings_will_change! + user.save! else + delete_from_schedule(current_user.email, desk_id, start_epoch) @status[zone][desk_id][todays_date].delete(user) - delete_booking_from_user(desk_id, start_epoch) end expose_status(zone) end @@ -134,14 +135,6 @@ def check_in(desk_id, start_epoch, checking_in) protected - def delete_booking_from_user(desk_id, start_epoch) - current_user.desk_bookings&.each_with_index do |b, i| - next unless b[:start] == start_epoch && b[:desk_id] == desk_id - current_user.desk_bookings&.delete_at(i) - end - current_user.save! - end - def expose_status(zone, save_status = true) self[zone] = @status[zone].deep_dup signal_status(zone) @@ -161,9 +154,7 @@ def autocancel_bookings expose_status(zone) next unless ENV['ENGINE_DEFAULT_AUTHORITY_ID'] - user = User.find_by_email(ENV['ENGINE_DEFAULT_AUTHORITY_ID'], user_email) - user.desk_bookings[booking_date]&.delete(desk_id) - user.save! + delete_from_schedule(user_email, desk_id, start_epoch) # STUB: Notify user of cancellation by email here end @@ -171,4 +162,26 @@ def autocancel_bookings end end + def add_to_schedule(email, booking) + user = get_user(email) + user.desk_bookings << booking + user.desk_bookings_will_change! + user.save! + end + + def delete_from_schedule(email, desk_id, start_epoch) + user = get_user(email) + user.desk_bookings&.each_with_index do |b, i| + next unless b[:start] == start_epoch && b[:desk_id] == desk_id + user.desk_bookings.delete_at(i) + logger.debug "DELETING DESK BOOKING: #{desk_id} #{start_epoch}" + end + user.desk_bookings_will_change! + user.save! + end + + def get_user(email) + raise "Environment Variable 'ENGINE_DEFAULT_AUTHORITY_ID' not set " unless ENV['ENGINE_DEFAULT_AUTHORITY_ID'] + user = User.find_by_email(ENV['ENGINE_DEFAULT_AUTHORITY_ID'], email) + end end From 13959dfa903f84b7e5d30ee24ca528e2d3aba2c3 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 17 Feb 2020 07:08:33 +0000 Subject: [PATCH 1651/1752] feat(desk_bookings): remove ":bookings" from binding name --- modules/aca/desk_bookings.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index 1a553ce0..bb48aacb 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -41,12 +41,12 @@ def on_update @zones_to_desks = setting('zone_to_desk_ids') || {} @zones_to_desks.each do |zone, desks| # load and expose previously saved status if there is no current status. - @status[zone+':bookings'] ||= saved_status[zone+':bookings'] || {} + @status[zone] ||= saved_status[zone] || {} desks.each do |desk| - @zone_of[desk] = zone + ':bookings' # create reverse lookup: desk => zone - @status[zone+':bookings'][desk] ||= {} # expose all known desk ids without overwriting existing bookings + @zone_of[desk] = zone # create reverse lookup: desk => zone + @status[zone][desk] ||= {} # init all known desk ids without overwriting existing bookings end - expose_status(zone+':bookings') + expose_status(zone) end schedule.clear From 63a34d89eec63b32464a0f2b7ebe8e8c751deab9 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 25 Feb 2020 15:12:53 +0800 Subject: [PATCH 1652/1752] docs(office2/availability): comment about bulk graph request IDs matching array indexes --- lib/microsoft/office2/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index b6f31e8d..b8318d18 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -144,7 +144,7 @@ def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_ event_length = to - from query_strings = start_epochs.map { |start| "?startDateTime=#{graph_date(start)}&endDateTime=#{graph_date(start + event_length)}&$top=99" } - # create array of requests that will be sent as bulk to Graph API. Match the array index with + # create array of requests that will be sent as bulk to Graph API. Match the array index with the request ID, for simple response parsing requests = [] rooms.each do |room_email| room_events_endpoint = "/users/#{room_email}/calendar/calendarView" From b542ba60a5d769398ad9ddc9c50b8e158a11bfce Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 28 Feb 2020 00:15:07 +1100 Subject: [PATCH 1653/1752] feat: add gantner relaxx driver --- modules/gantner/relaxx/protocol_json.rb | 216 ++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 modules/gantner/relaxx/protocol_json.rb diff --git a/modules/gantner/relaxx/protocol_json.rb b/modules/gantner/relaxx/protocol_json.rb new file mode 100644 index 00000000..4878b1ef --- /dev/null +++ b/modules/gantner/relaxx/protocol_json.rb @@ -0,0 +1,216 @@ +module Gantner; end +module Gantner::Relaxx; end + +require "openssl" +require "openssl/cipher" +require "securerandom" +require "base64" +require "json" +require "set" + +class Gantner::Relaxx::ProtocolJSON + include ::Orchestrator::Constants + + # Discovery Information + tcp_port 8237 + descriptive_name "Gantner GAT Relaxx JSON API" + generic_name :Lockers + + # Communication settings + tokenize delimiter: "\x03" + + def on_load + @authenticated = false + @password = "GAT" + @locker_ids = Set.new + @lockers_in_use = Set.new + on_update + end + + def on_update + @password = setting(:password) || "GAT" + end + + def connected + self[:authenticated] = @authenticated = false + request_auth_string + + schedule.every("40s") do + logger.debug "-- maintaining connection" + @authenticated ? keep_alive : request_auth_string + end + end + + def disconnected + schedule.clear + end + + def keep_alive + send_frame({ + Caption: "KeepAliveRequest", + Id: new_request_id + }, priority: 0) + end + + def request_auth_string + send_frame({ + Caption: "AuthenticationRequestA", + Id: new_request_id + }, priority: 9998) + end + + def open_locker(locker_number, locker_group = nil) + set_open_state(true, locker_number, locker_group) + end + + def close_locker(locker_number, locker_group = nil) + set_open_state(false, locker_number, locker_group) + end + + def set_open_state(open, locker_number, locker_group = nil) + action = open ? "0" : "1" + request = { + Caption: "ExecuteLockerActionRequest", + Id: new_request_id, + Action: action + } + if locker_number.include?("-") + request[:LockerId] = locker_number + else + request[:LockerNumber] = locker_number + end + request[:LockerGroupId] = locker_group if locker_group + send_frame(request) + end + + def query_lockers(free_only = false) + send_frame({ + Caption: "GetLockersRequest", + Id: new_request_id, + FreeLockersOnly: free_only, + PersonalLockersOnly: false + }) + end + + LOCKER_STATE = { + 0 => :unknown, + 1 => :disabled, + 2 => :free, + 3 => :in_use, + 4 => :locked, + 5 => :alarmed, + 6 => :in_use_expired, + 7 => :conflict + } + + def received(data, resolve, command) + # Ignore the framing bytes + data = data[1..-1] + logger.debug { "Gantner Relaxx sent: #{data}" } + json = JSON.parse(data) + + return parse_notify(json["Caption"], json) if json["IsNotification"] + + # Check result of the request + result = json["Result"] + return :abort if result["Cancelled"] + return :abort if !result["Successful"] + + # Process response + case json["Caption"] + when "AuthenticationResponseA" + logged_in = json["LoggedIn"] + self["authenticated"] = @authenticated = logged_in + return :success if logged_in + login(json["AuthenticationString"]) + + when "AuthenticationResponseB" + logged_in = json["LoggedIn"] + self["authenticated"] = @authenticated = logged_in + if logged_in + logger.debug "authentication success" + + # Obtain the list of lockers and their current state + query_lockers if @locker_ids.empty? + else + logger.warn "authentication failure - please check credentials" + end + + when "GetLockersResponse" + lockers = json["Lockers"] + lockers.each do |locker| + locker_id = locker["RecordId"] + @locker_ids << locker_id + + if LOCKER_STATE[locker["State"]] == :free + @lockers_in_use.delete(locker_id) + else + @lockers_in_use << locker_id + self["locker_#{locker_id}"] = locker["CardUIDInUse"] + end + end + self[:locker_ids] = @locker_ids.to_a + self[:lockers_in_use] = @lockers_in_use.to_a + + when "CommandNotSupportedResponse" + logger.warn "Command not supported!" + return :abort + end + + return :success + end + + private + + # Converts the data to bytes and wraps it into a frame + def send_frame(data, **options) + logger.debug { "requesting #{data[:Caption]}, id #{data[:Id]}" } + send "\x02#{data.to_json}\x03", **options + end + + def new_request_id + SecureRandom.uuid + end + + def login(authentication_string) + decipher = OpenSSL::Cipher::AES.new(256, :CBC).decrypt + decipher.padding = 1 + + # LE for little endian and avoids a byte order mark + password = @password.encode(Encoding::UTF_16LE).force_encoding("BINARY") + decipher.key = "#{password}#{"\x00" * (32 - password.bytesize)}" + decipher.iv = "#{password}#{"\x00" * (16 - password.bytesize)}" + + plain = decipher.update(Base64.decode64(authentication_string)) + decipher.final + decrypted = plain.force_encoding(Encoding::UTF_16LE) + + send_frame({ + Caption: "AuthenticationRequestB", + Id: new_request_id, + AuthenticationString: decrypted + }, priority: 9999) + end + + def parse_notify(caption, json) + case caption + when "LockerEventNotification" + locker = json["Locker"] + update_locker_state(LOCKER_STATE[locker["State"]] != :free, locker["RecordId"], locker["CardUIDInUse"]) + else + logger.debug { "ignoring event: #{caption}" } + end + nil + end + + def update_locker_state(in_use, locker_id, card_id) + @locker_ids << locker_id + if in_use + @lockers_in_use << locker_id + else + @lockers_in_use.delete(locker_id) + end + self["locker_#{locker_id}"] = card_id + self[:locker_ids] = @locker_ids.to_a + self[:lockers_in_use] = @lockers_in_use.to_a + end +end From 0e78989c3d317f0422f42a1f954231081e29dcf6 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 10:20:48 +0800 Subject: [PATCH 1654/1752] feat(event.showAs): rename show_as > showAs --- lib/microsoft/office2/event.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/event.rb b/lib/microsoft/office2/event.rb index ffbd4c78..fd70e667 100644 --- a/lib/microsoft/office2/event.rb +++ b/lib/microsoft/office2/event.rb @@ -10,7 +10,7 @@ class Microsoft::Office2::Event < Microsoft::Office2::Model 'body' => {'content' => 'body'}, 'attendees' => 'old_attendees', 'iCalUId' => 'icaluid', - 'showAs' => 'show_as', + 'showAs' => 'showAs', 'isCancelled' => 'isCancelled', 'isAllDay' => 'isAllDay', 'sensitivity' => 'sensitivity', From af929fcf21dfa13020e93bac4f1d06e5a147736a Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 10:28:32 +0800 Subject: [PATCH 1655/1752] feat(odata_extensions): REMOVE support for odata extensions --- lib/microsoft/office2/event.rb | 15 +------------- lib/microsoft/office2/events.rb | 36 +++------------------------------ 2 files changed, 4 insertions(+), 47 deletions(-) diff --git a/lib/microsoft/office2/event.rb b/lib/microsoft/office2/event.rb index fd70e667..a5170e53 100644 --- a/lib/microsoft/office2/event.rb +++ b/lib/microsoft/office2/event.rb @@ -46,7 +46,7 @@ def initialize(client:, event:, available_to:nil, available_from:nil) @client = client @available_to = available_to @available_from = available_from - @event = get_extensions(create_aliases(event, ALIAS_FIELDS, NEW_FIELDS, self), event) + @event = create_aliases(event, ALIAS_FIELDS, NEW_FIELDS, self) end attr_accessor :event, :available_to, :available_from @@ -57,19 +57,6 @@ def datestring_to_epoch(date_object) ActiveSupport::TimeZone.new(date_object['timeZone']).parse(date_object['dateTime']).to_i end - def get_extensions(new_event, old_event) - if old_event.key?('extensions') - old_event['extensions'].each do |ext| - if ext&.dig('id') == "Microsoft.OutlookServices.OpenTypeExtension.Com.Acaprojects.Extensions" - ext.each do |ext_key, ext_val| - new_event[ext_key] = ext_val if !['@odata.type', '@odata.context', 'id','extensionName'].include?(ext_key) - end - end - end - end - new_event - end - def set_room_id(attendees) room_ids = [] attendees.each do |attendee| diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index b8318d18..99479e3e 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -11,7 +11,6 @@ module Microsoft::Office2::Events # @option options [Integer] :available_from If bookings exist between this seconds epoch and available_to then the room is market as not available # @option options [Integer] :available_to If bookings exist between this seconds epoch and available_from then the room is market as not available # @option options [Array] :ignore_bookings An array of icaluids for bookings which are ignored if they fall within the available_from and to time range - # @option options [String] :extension_name The name of the extension list to retreive in O365. This probably doesn't need to change # @return [Hash] A hash of room emails to availability and bookings fields, eg: # @example # An example response: @@ -46,8 +45,7 @@ def get_bookings(mailboxes:, calendargroup_id: nil, calendar_id: nil, options:{} bookings_to: nil, available_from: nil, available_to: nil, - ignore_bookings: [], - extension_name: "Com.Acaprojects.Extensions" + ignore_bookings: [] } # Merge in our default options with those passed in options = options.reverse_merge(default_options) @@ -83,7 +81,6 @@ def get_bookings(mailboxes:, calendargroup_id: nil, calendar_id: nil, options:{} query[:startDateTime] = graph_date(options[:bookings_from]) if options[:bookings_from] query[:endDateTime] = graph_date(options[:bookings_to]) if options[:bookings_to] query[:'$filter'] = "createdDateTime gt #{created_from}" if options[:created_from] - query[:'$expand'] = "extensions($filter=id eq '#{options[:extension_name]}')" if options[:extension_name] && ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' # Make the request, check the repsonse then parse it bulk_response = graph_request(request_method: 'get', endpoints: endpoints, query: query, bulk: true) @@ -193,7 +190,6 @@ def get_availability(rooms:, from:, to:, recurrence_pattern: 'none', recurrence_ # @option options [Integer] :recurrence_end A seconds epoch denoting the final day of recurrence # @option options [Boolean] :is_private Whether to mark the booking as private or just normal # @option options [String] :timezone The timezone of the booking. This will be overridden by a timezone in the room's settings - # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking # @option options [String] :location The location field to set. This will not be used if a room is passed in def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, calendar_id: nil, options: {}) default_options = { @@ -205,7 +201,6 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca recurrence: nil, is_private: false, timezone: ENV['TZ'], - extensions: {}, location: nil } # Merge in our default options with those passed in @@ -223,7 +218,6 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca attendees: options[:attendees].dup, organizer: options[:organizer], recurrence: options[:recurrence], - extensions: options[:extensions], is_private: options[:is_private] ) @@ -255,7 +249,6 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca # @option options [Integer] :recurrence_end A seconds epoch denoting the final day of recurrence # @option options [Boolean] :is_private Whether to mark the booking as private or just normal # @option options [String] :timezone The timezone of the booking. This will be overridden by a timezone in the room's settings - # @option options [Hash] :extensions A hash holding a list of extensions to be added to the booking # @option options [String] :location The location field to set. This will not be used if a room is passed in def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: nil, options: {}) # No defaults for editing, because undefined fields should simply retain the existing value (from the original booking) @@ -269,7 +262,6 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni recurrence: nil, is_private: nil, timezone: ENV['TZ'], - extensions: {}, location: nil } # Merge in our default options with those passed in @@ -286,20 +278,9 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni location: options[:location], attendees: options[:attendees].dup, recurrence: options[:recurrence], - extensions: options[:extensions], is_private: options[:is_private] ) - # If extensions exist we must make a separate request to add them - if options[:extensions].present? && ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' - options[:extensions] = options[:extensions].dup - options[:extensions]["@odata.type"] = "microsoft.graph.openTypeExtension" - options[:extensions]["extensionName"] = "Com.Acaprojects.Extensions" - request = graph_request(request_method: 'put', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}/extensions/Microsoft.OutlookServices.OpenTypeExtension.Com.Acaprojects.Extensions"], data: options[:extensions]) - check_response(request) - ext_data = JSON.parse(request.body) - end - # Make the request and check the response begin request = graph_request(request_method: 'patch', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events/#{booking_id}"], data: event_json) @@ -307,7 +288,7 @@ def update_booking(booking_id:, mailbox:, calendargroup_id: nil, calendar_id: ni rescue Microsoft::Error::Conflict => e return {} end - Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body).merge({'extensions' => [ext_data]})).event + Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body)).event end ## @@ -348,7 +329,7 @@ def calendar_path(calendargroup_id, calendar_id) result end - def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: nil, location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, extensions: {}, is_private: nil) + def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: nil, location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, is_private: nil) event_json = {} event_json[:subject] = subject if subject event_json[:sensitivity] = ( is_private ? "private" : "normal" ) if is_private @@ -417,17 +398,6 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, } end - if ENV['O365_DISABLE_ODATA_EXTENSIONS']&.downcase != 'true' - ext = { - "@odata.type": "microsoft.graph.openTypeExtension", - "extensionName": "Com.Acaprojects.Extensions" - } - extensions.each do |ext_key, ext_value| - ext[ext_key] = ext_value - end - event_json[:extensions] = [ext] - end - event_json.reject!{|k,v| v.nil?} event_json end From e1e08db8f3fe9f4a6e5d2e2a4af8469d14b58163 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 12:33:09 +0800 Subject: [PATCH 1656/1752] fix(o365/RBP): fix autocancel timeout setting --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 42513859..d8323725 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -45,7 +45,7 @@ def on_update self[:timeout] = setting(:timeout) self[:disabled] = setting(:booking_disabled) - self[:cancel_timeout] = UV::Scheduler.parse_duration(setting(:cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' + self[:cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' self[:cancel_email_message] = setting(:booking_cancel_email_message) self[:timeout_email_message] = setting(:booking_timeout_email_message) From 0f495af3cd33089c4e591649a3d4e202cdc0e58e Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 12:37:20 +0800 Subject: [PATCH 1657/1752] feat(o365/RBP): setting.timeout to human readable string --- modules/aca/o365_booking_panel.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index d8323725..9d6df139 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -42,10 +42,10 @@ def on_update self[:description] = setting(:description) self[:icon] = setting(:icon) self[:control_url] = setting(:booking_control_url) || system.config.support_url - - self[:timeout] = setting(:timeout) self[:disabled] = setting(:booking_disabled) - self[:cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) # convert '1m2s' to '62' + + self[:timeout] = UV::Scheduler.parse_duration(setting(:timeout)) / 1000 if setting(:timeout) # convert '1m2s' to '62' + self[:cancel_timeout] = UV::Scheduler.parse_duration(setting(:booking_cancel_timeout)) / 1000 if setting(:booking_cancel_timeout) self[:cancel_email_message] = setting(:booking_cancel_email_message) self[:timeout_email_message] = setting(:booking_timeout_email_message) From 797e4fc6ac4c3d768280309d56d2c587b93914b4 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 07:39:36 +0000 Subject: [PATCH 1658/1752] style(o365/RBP): call Graph API 'msgraph' not 'office' --- modules/aca/o365_booking_panel.rb | 62 +++++++++++++++---------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 9d6df139..df3060d6 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -21,9 +21,9 @@ class Aca::O365BookingPanel update_every: '2m', booking_cancel_email_message: 'The Stop button was presseed on the room booking panel', booking_timeout_email_message: 'The Start button was not pressed on the room booking panel', - office_client_id: "enter client ID", - office_secret: "enter client secret", - office_tenant: "tenant_name_or_ID.onMicrosoft.com" + msgraph_client_id: "enter MS Graph Application ID", + msgraph_secret: "enter MS Graph Client secret", + msgraph_tenant: "tenant_name_or_ID.onMicrosoft.com" }) def on_load @@ -81,20 +81,20 @@ def on_update self[:select_free] = setting(:booking_select_free) self[:hide_all] = setting(:booking_hide_all) || false - office_client_id = setting(:office_client_id) || ENV['OFFICE_CLIENT_ID'] - office_secret = setting(:office_secret) || ENV["OFFICE_CLIENT_SECRET"] - office_token_path = setting(:office_token_path) || "/oauth2/v2.0/token" - office_token_url = setting(:office_token_url) || ENV["OFFICE_TOKEN_URL"] || "/" + setting(:office_tenant) + office_token_path - @office_room = (setting(:office_room) || system.email) - office_https_proxy = setting(:office_https_proxy) + msgraph_client_id = setting(:msgraph_client_id) || setting(:office_client_id) || ENV['MSGRAPH_CLIENT_ID'] + msgraph_secret = setting(:msgraph_secret) || setting(:office_secret) || ENV["MSGRAPH_CLIENT_SECRET"] + msgraph_token_path = setting(:msgraph_token_path) || setting(:office_token_path) || "/oauth2/v2.0/token" + msgraph_token_url = setting(:msgraph_token_url) || setting(:office_token_url) || ENV["MSGRAPH_TOKEN_URL"] || "/" + setting(:msgraph_tenant) + msgraph_token_path + @room_mailbox = (setting(:msgraph_room) || system.email) + msgraph_https_proxy = setting(:msgraph_https_proxy) - logger.debug "RBP>#{@office_room}>INIT: Instantiating o365 Graph API client" + logger.debug "RBP>#{@room_mailbox}>INIT: Instantiating o365 Graph API client" @client = ::Microsoft::Office2::Client.new({ - client_id: office_client_id, - client_secret: office_secret, - app_token_url: office_token_url, - https_proxy: office_https_proxy + client_id: msgraph_client_id, + client_secret: msgraph_secret, + app_token_url: msgraph_token_url, + https_proxy: msgraph_https_proxy }) self[:last_meeting_started] = setting(:last_meeting_started) @@ -107,7 +107,7 @@ def on_update end def fetch_bookings(*args) - @todays_bookings = @client.get_bookings(mailboxes: [@office_room], options: {bookings_from: Time.now.midnight.to_i, bookings_to: Time.now.tomorrow.midnight.to_i}).dig(@office_room, :bookings) + @todays_bookings = @client.get_bookings(mailboxes: [@room_mailbox], options: {bookings_from: Time.now.midnight.to_i, bookings_to: Time.now.tomorrow.midnight.to_i}).dig(@room_mailbox, :bookings) self[:today] = expose_bookings(@todays_bookings) end @@ -120,22 +120,22 @@ def create_meeting(params) raise "Error: start/end param is required and missing" end - logger.debug "RBP>#{@office_room}>CREATE>INPUT:\n #{params}" + logger.debug "RBP>#{@room_mailbox}>CREATE>INPUT:\n #{params}" host_email = params.dig(:host, :email) - mailbox = host_email || @office_room + mailbox = host_email || @room_mailbox calendargroup_id = nil calendar_id = nil booking_options = { subject: params[:title] || setting(:booking_default_title), #location: {} - attendees: [ {email: @office_room, type: "resource"} ], + attendees: [ {email: @room_mailbox, type: "resource"} ], timezone: ENV['TZ'], extensions: { aca_booking: true } } if ENV['O365_PROXY_USER_CALENDARS'] - room_domain = @office_room.split('@').last + room_domain = @room_mailbox.split('@').last user_domain = current_user.email.split('@').last calendar_proxy = host_email ? User.find_by_email(current_user.authority_id, host_email)&.calendar_proxy : nil @@ -155,10 +155,10 @@ def create_meeting(params) end_param: epoch(end_param), options: booking_options ) rescue Exception => e - logger.error "RBP>#{@office_room}>CREATE>ERROR: #{e.message}\n#{e.backtrace.join("\n")}" + logger.error "RBP>#{@room_mailbox}>CREATE>ERROR: #{e.message}\n#{e.backtrace.join("\n")}" raise e else - logger.debug { "RBP>#{@office_room}>CREATE>SUCCESS:\n #{result}" } + logger.debug { "RBP>#{@room_mailbox}>CREATE>SUCCESS:\n #{result}" } schedule.in('2s') do fetch_bookings end @@ -193,7 +193,7 @@ def end_meeting(id) end new_details[:subject] = 'ENDED: ' + existing['subject'] - @client.update_booking(booking_id: id, mailbox: @office_room, options: new_details) + @client.update_booking(booking_id: id, mailbox: @room_mailbox, options: new_details) schedule.in('3s') do fetch_bookings end @@ -221,11 +221,11 @@ def cancel_meeting(start_time, reason = "unknown reason") bookings_to_cancel = bookings_with_start_time(start_epoch) if bookings_to_cancel > 1 - logger.warn { "RBP>#{@office_room}>CANCEL>CLASH: No bookings cancelled as Multiple bookings (#{bookings_to_cancel}) were found with same start time #{start_time}" } + logger.warn { "RBP>#{@room_mailbox}>CANCEL>CLASH: No bookings cancelled as Multiple bookings (#{bookings_to_cancel}) were found with same start time #{start_time}" } return end if bookings_to_cancel == 0 - logger.warn { "RBP>#{@office_room}>CANCEL>NOT_FOUND: Could not find booking to cancel with start time #{start_time}" } + logger.warn { "RBP>#{@room_mailbox}>CANCEL>NOT_FOUND: Could not find booking to cancel with start time #{start_time}" } return end @@ -236,11 +236,11 @@ def cancel_meeting(start_time, reason = "unknown reason") if !too_early_to_cancel && !too_late_to_cancel delete_o365_booking(start_epoch, reason) else - logger.warn { "RBP>#{@office_room}>CANCEL>TOO_EARLY: Booking NOT cancelled with start time #{start_time}" } if too_early_to_cancel - logger.warn { "RBP>#{@office_room}>CANCEL>TOO_LATE: Booking NOT cancelled with start time #{start_time}" } if too_late_to_cancel + logger.warn { "RBP>#{@room_mailbox}>CANCEL>TOO_EARLY: Booking NOT cancelled with start time #{start_time}" } if too_early_to_cancel + logger.warn { "RBP>#{@room_mailbox}>CANCEL>TOO_LATE: Booking NOT cancelled with start time #{start_time}" } if too_late_to_cancel end else # an unsupported reason, just cancel the booking and add support to this driver. - logger.error { "RBP>#{@office_room}>CANCEL>UNKNOWN_REASON: Cancelled booking with unknown reason, with start time #{start_time}" } + logger.error { "RBP>#{@room_mailbox}>CANCEL>UNKNOWN_REASON: Cancelled booking with unknown reason, with start time #{start_time}" } delete_o365_booking(start_epoch, reason) end @@ -305,11 +305,11 @@ def epoch(input) end def delete_or_decline(booking, comment = nil) - if booking[:email] == @office_room - logger.warn { "RBP>#{@office_room}>CANCEL>ROOM_OWNED: Deleting booking owned by the room, with start time #{booking[:Start]}" } + if booking[:email] == @room_mailbox + logger.warn { "RBP>#{@room_mailbox}>CANCEL>ROOM_OWNED: Deleting booking owned by the room, with start time #{booking[:Start]}" } response = @client.delete_booking(booking_id: booking[:id], mailbox: system.email) # Bookings owned by the room need to be deleted, instead of declined else - logger.warn { "RBP>#{@office_room}>CANCEL>SUCCESS: Declining booking, with start time #{booking[:Start]}" } + logger.warn { "RBP>#{@room_mailbox}>CANCEL>SUCCESS: Declining booking, with start time #{booking[:Start]}" } response = @client.decline_meeting(booking_id: booking[:id], mailbox: system.email, comment: comment) end end @@ -323,7 +323,7 @@ def delete_o365_booking(delete_start_epoch, reason) booking_start_epoch = Time.parse(booking[:Start]).to_i next if booking_start_epoch != delete_start_epoch if booking[:isAllDay] - logger.warn { "RBP>#{@office_room}>CANCEL>ALL_DAY: An All Day booking was NOT deleted, with start time #{delete_start_epoch}" } + logger.warn { "RBP>#{@room_mailbox}>CANCEL>ALL_DAY: An All Day booking was NOT deleted, with start time #{delete_start_epoch}" } next end From 63b06fd7f9f52a658e111d3bea815b28924964c4 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 07:46:55 +0000 Subject: [PATCH 1659/1752] feat(o365/RBP): graph credentials: remove support for env vars --- modules/aca/o365_booking_panel.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index df3060d6..e0bbc9ef 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -81,10 +81,10 @@ def on_update self[:select_free] = setting(:booking_select_free) self[:hide_all] = setting(:booking_hide_all) || false - msgraph_client_id = setting(:msgraph_client_id) || setting(:office_client_id) || ENV['MSGRAPH_CLIENT_ID'] - msgraph_secret = setting(:msgraph_secret) || setting(:office_secret) || ENV["MSGRAPH_CLIENT_SECRET"] + msgraph_client_id = setting(:msgraph_client_id) || setting(:office_client_id) + msgraph_secret = setting(:msgraph_secret) || setting(:office_secret) msgraph_token_path = setting(:msgraph_token_path) || setting(:office_token_path) || "/oauth2/v2.0/token" - msgraph_token_url = setting(:msgraph_token_url) || setting(:office_token_url) || ENV["MSGRAPH_TOKEN_URL"] || "/" + setting(:msgraph_tenant) + msgraph_token_path + msgraph_token_url = setting(:msgraph_token_url) || setting(:office_token_url) || "/" + setting(:msgraph_tenant) + msgraph_token_path @room_mailbox = (setting(:msgraph_room) || system.email) msgraph_https_proxy = setting(:msgraph_https_proxy) From e69dffa808340fa3d1c18a5dafd3d1ff8864a793 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 16:57:09 +0000 Subject: [PATCH 1660/1752] feat(office2/event): support showAs & responseRequested --- lib/microsoft/office2/events.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 99479e3e..a63bf1ba 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -201,7 +201,9 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca recurrence: nil, is_private: false, timezone: ENV['TZ'], - location: nil + location: nil, + showAs: 'busy', + responseRequested: true } # Merge in our default options with those passed in options = options.reverse_merge(default_options) @@ -218,7 +220,9 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca attendees: options[:attendees].dup, organizer: options[:organizer], recurrence: options[:recurrence], - is_private: options[:is_private] + is_private: options[:is_private], + showAs: options[:showAs], + responseRequested: options[:responseRequested] ) # Make the request and check the response @@ -329,9 +333,13 @@ def calendar_path(calendargroup_id, calendar_id) result end - def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: nil, location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, is_private: nil) + def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, timezone: nil, rooms: nil, location: nil, attendees: nil, organizer_name: nil, organizer:nil, recurrence: nil, is_private: nil, showAs: 'busy', responseRequested: true) event_json = {} + event_json[:showAs] = showAs + event_json[:responseRequested] = responseRequested event_json[:subject] = subject if subject + event_json[:attendees] = attendees if attendees + event_json[:sensitivity] = ( is_private ? "private" : "normal" ) if is_private event_json[:body] = { @@ -366,7 +374,6 @@ def create_event_json(subject: nil, body: nil, start_param: nil, end_param: nil, attendees.push({ type: "resource", emailAddress: { address: room[:email], name: room[:name] } }) end - event_json[:attendees] = attendees if attendees event_json[:organizer] = { emailAddress: { From 1ca6d1760f3d2183cdca55e9435604b6b89319f2 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 16:57:57 +0000 Subject: [PATCH 1661/1752] feat(office2/log): support env var for verbose logging --- lib/microsoft/office2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index 9e4d8b4e..d000c549 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -190,7 +190,7 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint end def check_response(response) - return if response.status < 300 + return unless ENV['LOG_GRAPH_API'] || response.status > 300 STDERR.puts ">>>>>>>>>>>>" STDERR.puts "GRAPH API ERROR Request:\n #{@request_info}" STDERR.puts "============" From 46bf6e81be53a7adb11f1596ca50e50add1fbabc Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 5 Mar 2020 16:58:27 +0000 Subject: [PATCH 1662/1752] feat(desk_bookings): support email invites --- modules/aca/desk_bookings.rb | 64 +++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index bb48aacb..b9c2c4a3 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -11,6 +11,7 @@ class ::Aca::DeskBookings default_settings({ cancel_bookings_after: "1h", check_autocancel_every: "5m", + send_email_invites: false, zone_to_desk_ids: { "zone-xxx" => ["desk-01.001", "desk-01.002"], "zone-yyy" => ["desk-02.001", "desk-02.002"] @@ -48,7 +49,24 @@ def on_update end expose_status(zone) end - + + @email_enabled = setting('send_email_invites') + if @email_enabled + msgraph_client_id = setting(:msgraph_client_id) || setting(:office_client_id) + msgraph_secret = setting(:msgraph_secret) || setting(:office_secret) + msgraph_token_path = setting(:msgraph_token_path) || setting(:office_token_path) || "/oauth2/v2.0/token" + msgraph_token_url = setting(:msgraph_token_url) || setting(:office_token_url) || "/" + setting(:msgraph_tenant) + msgraph_token_path + msgraph_https_proxy = setting(:msgraph_https_proxy) + @desks_mailbox = (setting(:msgraph_mailbox) || system.email) + + @msgraph = ::Microsoft::Office2::Client.new({ + client_id: msgraph_client_id, + client_secret: msgraph_secret, + app_token_url: msgraph_token_url, + https_proxy: msgraph_https_proxy + }) + end + schedule.clear schedule.every(@autocancel_scan_interval) { autocancel_bookings } if @autocancel_delay && @autocancel_scan_interval end @@ -69,7 +87,8 @@ def book(desk_id, start_epoch, end_epoch = nil) new_booking = { start: start_epoch, end: end_epoch - 1, - checked_in: (booking_date == todays_date) + checked_in: (booking_date == todays_date), + name: current_user.name } @status[zone][desk_id] ||= {} @status[zone][desk_id][booking_date] ||= {} @@ -82,19 +101,21 @@ def book(desk_id, start_epoch, end_epoch = nil) end end end - @status[zone][desk_id][booking_date][current_user.email] = new_booking + # Email a meeting invite to the booker, so they can see the booked desk in their calendar + # Store the event ID in our records, so that it can be targetted for deletion later + new_booking[:event_id] = send_email_invite(desk_id, start_epoch, end_epoch, current_user) if @email_enabled + + @status[zone][desk_id][booking_date][current_user.email] = new_booking expose_status(zone) - # Also store booking in user object, in a slightly different format + # Also store booking in the user object, with additional info new_booking[:desk_id] = desk_id new_booking[:zone] = zone add_to_schedule(current_user.email, new_booking) - - # STUB: Notify user of desk booking via email here end - def cancel(desk_id, start_epoch) + def cancel(desk_id, start_epoch, event_id = nil) booking_date = Time.at(start_epoch).in_time_zone(@tz).strftime('%F') zone = @zone_of[desk_id] user = current_user.email @@ -103,8 +124,10 @@ def cancel(desk_id, start_epoch) @status[zone][desk_id][booking_date].delete(user) expose_status(zone) - # Also delete booking from user profile + # Also delete booking from user profile (schedule page of Staff App) delete_from_schedule(current_user.email, desk_id, start_epoch) + + cancel_email_invite(event_id) if @email_enabled && event_id end # param checking_in is a bool: true = checkin, false = checkout @@ -141,6 +164,31 @@ def expose_status(zone, save_status = true) define_setting(:status, @status) if save_status # Also persist new status to DB end + def send_email_invite(desk_id, start_epoch, end_epoch, recipient) + pretty_time = Time.at(start_epoch).in_time_zone(@tz).strftime('%D%l:%M%P') # e.g. "03/05/20 4:18pm" + event_request = { + start_param: start_epoch, + end_param: end_epoch, + mailbox: @desks_mailbox, + options: { + subject: "Desk Booking confirmation: #{desk_id} on #{pretty_time}", + description: "Please arrive on time and scan the QR code with your mobile device to check in.", + attendees: [recipient], + location: desk_id, + timezone: @tz, + showAs: 'free', + responseRequested: false + } + } + event = @msgraph.create_booking(event_request) + logger.debug event.inspect + return event['id'] + end + + def cancel_email_invite(event_id) + @msgraph.delete_booking(mailbox: @desks_mailbox, booking_id: event_id) + end + def autocancel_bookings todays_date = Time.now.in_time_zone(@tz).strftime('%F') now = Time.now From 1726dba12fd4f57e10178a54811ac8cd11bb75d2 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 6 Mar 2020 01:12:40 +0800 Subject: [PATCH 1663/1752] style(desk_bookings): shorter email nvite subject --- modules/aca/desk_bookings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index b9c2c4a3..2be6d537 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -171,7 +171,7 @@ def send_email_invite(desk_id, start_epoch, end_epoch, recipient) end_param: end_epoch, mailbox: @desks_mailbox, options: { - subject: "Desk Booking confirmation: #{desk_id} on #{pretty_time}", + subject: "Desk #{desk_id} on #{pretty_time}", description: "Please arrive on time and scan the QR code with your mobile device to check in.", attendees: [recipient], location: desk_id, From 3f138daa36aa05b259e13b340a97db7b5733bf6b Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 6 Mar 2020 19:35:26 +0800 Subject: [PATCH 1664/1752] feat(office2/events): find by icaluid works on recurrences, given an epoch --- lib/microsoft/office2/events.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index a63bf1ba..9d0a0e93 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -113,9 +113,19 @@ def get_bookings(mailboxes:, calendargroup_id: nil, calendar_id: nil, options:{} end # Return a booking with a matching icaluid - def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_id: nil) - query_params = {'$filter': "iCalUId eq '#{icaluid}'" } - endpoint = "/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events" + def get_booking_by_icaluid(icaluid:, mailbox:, calendargroup_id: nil, calendar_id: nil, start_epoch: nil) + if start_epoch + # if a start epoch is given, search a 48hr window around it using /calendarView + # /calendarView returns all events, including recurrences + start = Time.at(start_epoch.to_i) + endpoint = "/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/calendarView" + query_params = {'$filter': "iCalUId eq '#{icaluid}'", 'startDateTime': start.yesterday.strftime('%FT%T%:z'), 'endDateTime': start.tomorrow.strftime('%FT%T%:z')} + else + # if a start epoch is not given, use /events + # /events does not return event recurrences + endpoint = "/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events" + query_params = {'$filter': "iCalUId eq '#{icaluid}'" } + end response = graph_request(request_method: 'get', endpoints: [endpoint], query: query_params) check_response(response) JSON.parse(response.body)&.dig('value', 0) From cc39b21a95ace924e090c6fe7c0e3c0bab60464b Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 8 Mar 2020 17:12:13 +0800 Subject: [PATCH 1665/1752] fix(office2/RBP): settings/tenant: support "msgraph and "office" setting names --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index e0bbc9ef..353d9e9d 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -84,7 +84,7 @@ def on_update msgraph_client_id = setting(:msgraph_client_id) || setting(:office_client_id) msgraph_secret = setting(:msgraph_secret) || setting(:office_secret) msgraph_token_path = setting(:msgraph_token_path) || setting(:office_token_path) || "/oauth2/v2.0/token" - msgraph_token_url = setting(:msgraph_token_url) || setting(:office_token_url) || "/" + setting(:msgraph_tenant) + msgraph_token_path + msgraph_token_url = setting(:msgraph_token_url) || setting(:office_token_url) || "/" + (setting(:msgraph_tenant) || setting(:office_tenant)) + msgraph_token_path @room_mailbox = (setting(:msgraph_room) || system.email) msgraph_https_proxy = setting(:msgraph_https_proxy) From f294ad0c7fe49add0102221660c5cc5ed245a16f Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 8 Mar 2020 17:13:57 +0800 Subject: [PATCH 1666/1752] feat(office2/RBP): expose booking.showAs ('busy', 'free') --- modules/aca/o365_booking_panel.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 353d9e9d..f915f5b2 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -381,7 +381,8 @@ def expose_bookings(bookings) :email => email, :organizer => {:name => name, :email => email}, :attendees => attendees, - :isAllDay => booking['isAllDay'] + :isAllDay => booking['isAllDay'], + :showAs => booking['showAs'] }) } results From 771f607a94240001536b7ab67a5ee7bc3545a9a8 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 9 Mar 2020 08:35:03 +0000 Subject: [PATCH 1667/1752] style(desk_bookings): shorten email subject --- modules/aca/desk_bookings.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index b9c2c4a3..26188a91 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -171,8 +171,8 @@ def send_email_invite(desk_id, start_epoch, end_epoch, recipient) end_param: end_epoch, mailbox: @desks_mailbox, options: { - subject: "Desk Booking confirmation: #{desk_id} on #{pretty_time}", - description: "Please arrive on time and scan the QR code with your mobile device to check in.", + subject: "Desk #{desk_id} Booked", + description: "#{desk_id} booked from #{pretty_time}\nPlease arrive on time and scan the QR code with your mobile device to check in.", attendees: [recipient], location: desk_id, timezone: @tz, From 01ca52544626978742753a549c0f65b0b818004a Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 9 Mar 2020 13:22:25 +0000 Subject: [PATCH 1668/1752] feat(desk_bookings): add auto check-in window setting --- modules/aca/desk_bookings.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/aca/desk_bookings.rb b/modules/aca/desk_bookings.rb index 26188a91..d35b6040 100644 --- a/modules/aca/desk_bookings.rb +++ b/modules/aca/desk_bookings.rb @@ -9,7 +9,8 @@ class ::Aca::DeskBookings implements :logic default_settings({ - cancel_bookings_after: "1h", + autocheckin_window: "1h", + cancel_bookings_after: "0s", check_autocancel_every: "5m", send_email_invites: false, zone_to_desk_ids: { @@ -30,9 +31,9 @@ def on_load end def on_update - # convert '1m2s' to '62' - @autocancel_delay = UV::Scheduler.parse_duration(setting('cancel_bookings_after') || '0s') / 1000 - @autocancel_scan_interval = setting('cancel_bookings_after') + @autocheckin_window = UV::Scheduler.parse_duration(setting('autocheckin_period') || '0s') / 1000 # convert '1m2s' to '62' + @autocancel_delay = UV::Scheduler.parse_duration(setting('cancel_bookings_after') || '0s') / 1000 + @autocancel_scan_interval = setting('check_autocancel_every') # local timezone of all the desks served by this logic (usually, all the desks in this building) @tz = setting('timezone') || ENV['TZ'] @@ -87,7 +88,7 @@ def book(desk_id, start_epoch, end_epoch = nil) new_booking = { start: start_epoch, end: end_epoch - 1, - checked_in: (booking_date == todays_date), + checked_in: (start_epoch - Time.now.to_i) < @autocheckin_window, # No need to check in to bookings that start very soon name: current_user.name } @status[zone][desk_id] ||= {} From 17bafad15074104560e7d9480cabe78bcec71338 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 10 Mar 2020 18:59:49 +0800 Subject: [PATCH 1669/1752] fix(office2/RBP/autocancel): update for latset ngx-bookings frontend --- modules/aca/o365_booking_panel.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index f915f5b2..5326f2e9 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -13,7 +13,7 @@ class Aca::O365BookingPanel implements :logic # Constants that the Room Booking Panel UI (ngx-bookings) will use - RBP_AUTOCANCEL_TRIGGERED = 'pending timeout' + RBP_AUTOCANCEL_TRIGGERED = 'timeout' RBP_STOP_PRESSED = 'user cancelled' # The room we are interested in @@ -212,11 +212,11 @@ def end_meeting(id) # + The body will be appended with "This meeting was ended at [time] because no presence was detected in the room" # - However currently with end_meeting(), the user will not recieve an automated email notifications (these only get sent when the room declines-) # - def cancel_meeting(start_time, reason = "unknown reason") + def cancel_meeting(start_ms_epoch, reason = "unknown reason") now = Time.now.to_i - start_epoch = Time.parse(start_time).to_i - ms_epoch = start_epoch * 1000 + start_epoch = start_ms_epoch / 1000 too_early_to_cancel = now < start_epoch + start_time = Time.at(start_epoch).in_time_zone(ENV['TZ']) too_late_to_cancel = self[:cancel_timeout] ? (now > (start_epoch + self[:cancel_timeout] + 180)) : false # "180": allow up to 3mins of slippage, in case endpoint is not NTP synced bookings_to_cancel = bookings_with_start_time(start_epoch) @@ -244,8 +244,8 @@ def cancel_meeting(start_time, reason = "unknown reason") delete_o365_booking(start_epoch, reason) end - self[:last_meeting_started] = ms_epoch - self[:meeting_pending] = ms_epoch + self[:last_meeting_started] = start_ms_epoch + self[:meeting_pending] = start_ms_epoch self[:meeting_ending] = false self[:meeting_pending_notice] = false true From d194e43a98d86196310db08469b899ba8abc987c Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 10 Mar 2020 19:01:02 +0800 Subject: [PATCH 1670/1752] feat(offic2/RBP): Support proxy calendars, even when host has never logged in --- modules/aca/o365_booking_panel.rb | 56 ++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 5326f2e9..67a89c62 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -135,24 +135,20 @@ def create_meeting(params) extensions: { aca_booking: true } } if ENV['O365_PROXY_USER_CALENDARS'] - room_domain = @room_mailbox.split('@').last - user_domain = current_user.email.split('@').last - - calendar_proxy = host_email ? User.find_by_email(current_user.authority_id, host_email)&.calendar_proxy : nil - mailbox = calendar_proxy&.dig(:account) if calendar_proxy&.dig(:account) - calendargroup_id = calendar_proxy&.dig(:calendargroup_id) - calendar_id = calendar_proxy&.dig(:calendar_id) - + # Fetch or create the host's proxy calendar + calendar_proxy = proxy_calendar(host_email) + mailbox = calendar_proxy&.dig(:email) if calendar_proxy&.dig(:email) + calendargroup_id = calendar_proxy&.dig(:group) + calendar_id = calendar_proxy&.dig(:id) booking_options[:attendees] << params[:host] if params[:host] - booking_options[:extensions].merge!( { aca_proxied_organizer: [params.dig(:host, :name), host_email] }) end begin result = @client.create_booking( - mailbox: mailbox, + mailbox: mailbox, calendargroup_id: calendargroup_id, - calendar_id: calendar_id, - start_param: epoch(start_param), - end_param: epoch(end_param), + calendar_id: calendar_id, + start_param: epoch(start_param), + end_param: epoch(end_param), options: booking_options ) rescue Exception => e logger.error "RBP>#{@room_mailbox}>CREATE>ERROR: #{e.message}\n#{e.backtrace.join("\n")}" @@ -387,4 +383,38 @@ def expose_bookings(bookings) } results end + + + # takes in a user email address and returns a hash of the proxy calendar account, calendargroup_id, calendar_id + def proxy_calendar(user_email) + # Check for an existing calendar proxy stored in the user object + default_domain = ENV['ENGINE_DEFAULT_DOMAIN'] + authority = ::Authority.find_by_domain(default_domain) + user = User.find_by_email(authority.id, user_email) + user_proxy = user&.calendar_proxy + if !user_proxy&.dig(:calendar_id) + # The proxy calendar and/or user does not exist, create the proxy calendar (and save to the user, if the user exists) + proxy = authority&.config&.dig(:o365_calendar_proxy) + begin + logger.warn "CREATING CALENDAR: #{proxy[:account]}, #{proxy[:calendargroup_id]}, #{user_email}" + calendar = @client.create_calendar(mailbox: proxy[:account], name: user_email, calendargroup_id: proxy[:calendargroup_id]) + logger.warn "o365 calendar proxy: New o365 calendar created:\n#{calendar.inspect}" + rescue + logger.warn "o365 calendar proxy: Error creating proxy calendar. It may already exist for #{user_email}" + results = @client.list_calendars(mailbox: proxy[:account], calendargroup_id: proxy[:calendargroup_id], match: user_email) + logger.warn "LIST CALENDARS: #{calendar}" + calendar = results.first + end + user_proxy = { + account: proxy[:account], + calendargroup_id: proxy[:calendargroup_id], + calendar_id: calendar['id'] + } + if user # if the user exists in engine + user.calendar_proxy = user_proxy + user.save! + end + end + { email: user_proxy[:account], group: user_proxy[:calendargroup_id], id: user_proxy[:calendar_id] } + end end From 69d0d801a88c96a5009438a2c79409ebfde4e1ad Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 10 Mar 2020 23:13:47 +0800 Subject: [PATCH 1671/1752] feat(office2/RBP): end_meeting: force push update to frontend --- modules/aca/o365_booking_panel.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 67a89c62..5fd343aa 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -192,6 +192,7 @@ def end_meeting(id) @client.update_booking(booking_id: id, mailbox: @room_mailbox, options: new_details) schedule.in('3s') do fetch_bookings + signal_status(:today) end end From 5376372d0d6ef74f1031f5b601e04c1ff299ba0d Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 17 Mar 2020 01:13:24 +0800 Subject: [PATCH 1672/1752] feat(office2/groups): List fetches group ID --- lib/microsoft/office2/event.rb | 2 +- lib/microsoft/office2/groups.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/event.rb b/lib/microsoft/office2/event.rb index a5170e53..276f1456 100644 --- a/lib/microsoft/office2/event.rb +++ b/lib/microsoft/office2/event.rb @@ -92,7 +92,7 @@ def format_attendees(attendees, organizer) name: attendee['emailAddress']['name'], visitor: is_visitor, external: is_visitor, - organisation: attendee_email&.split('@')&.dig(1) + organisation: attendee_email&.split('@')&.dig(1) } new_attendees.push(attendee_object) if attendee['type'] != 'resource' end diff --git a/lib/microsoft/office2/groups.rb b/lib/microsoft/office2/groups.rb index ed4e9230..18d10660 100644 --- a/lib/microsoft/office2/groups.rb +++ b/lib/microsoft/office2/groups.rb @@ -4,7 +4,7 @@ module Microsoft::Office2::Groups # result_fields: comma seperated string of which group properties should be included in the result. e.g. 'id,displayName'. Defaults to just 'displayName' # transitive: if false then only list groups that the user is a DIRECT member of (i.e. don't list subgroups) # https://docs.microsoft.com/en-us/graph/api/user-list-memberof - def list_user_member_of(id, result_fields = 'displayName', transitive = true) + def list_user_member_of(id, result_fields = 'id,displayName', transitive = true) return {'error': "400: No group \'id\' supplied" } if id.nil? endpoint = "/v1.0/users/#{id}/" + (transitive ? 'transitiveMemberOf' : 'memberOf') response = graph_request(request_method: 'get', endpoints: [endpoint], query: { '$select': result_fields, '$top': 999 } ) From 22e81e8c6e3c6d62db1f5246e023e2ad636c416f Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 16 Mar 2020 18:41:52 +0000 Subject: [PATCH 1673/1752] feat(gallagher): Restructure folders and create initial rest lib --- lib/gallagher/rest.rb | 312 ++++++++++++++++++++++++ lib/{gallagher.rb => gallagher/soap.rb} | 24 +- 2 files changed, 315 insertions(+), 21 deletions(-) create mode 100644 lib/gallagher/rest.rb rename lib/{gallagher.rb => gallagher/soap.rb} (89%) mode change 100755 => 100644 diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb new file mode 100644 index 00000000..25ab3b8d --- /dev/null +++ b/lib/gallagher/rest.rb @@ -0,0 +1,312 @@ +require 'savon' +require 'active_support/time' + +module Gallagher; end + +class Gallagher::Rest + + ## + # Create a new instance of the Gallagher Rest client. + # + # @param domain [String] The domain to connect to this Gallagher instance with. + # @param api_key [String] The API key to be included in the headers of each request in order to authenticate requests. + # @param unique_pdf_name [String] The name of the personal data field used to align cardholders to staff members. + # @param default_division [String] The division to pass when creating cardholders. + def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: nil) + # Initialize the http endpoint to make requests with our API key + @default_headers = { Authorization: "GGL-API-KEY #{api_key}" } + @default_division = default_division + @endpoint = UV::HttpEndpoint.new(domain) + + # Grab the URLs to be used by the other methods (HATEOAS standard) + # First define where we will find the URLs in the data endpoint's response + data_endpoint = "/api" + pdfs_href = "features.personalDatafields.personalDatafields.href" + cardholders_href = "features.cardholders.cardholders.href" + access_groups_href = "features.accessGroups.accessGroups.href" + card_types_href = "features.cardTypes.assign.href" + + # Get the main data endpoint to determine our new endpoints + response = @endpoint.get(path: data_endpoint, headers: @default_headers).value + @cardholders_endpoint = response[cardholders_href] + @pdfs_endpoint = response[pdfs_href] + @access_groups_endpoint = response[access_groups_href] + @card_types_endpoint = response[card_types_href] + + # Now get our cardholder PDF ID so we don't have to make the request over and over + pdf_response = @endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name }).value + @fixed_pdf_id = pdf_response['results'][0]['id'] # There should only be one result + end + + ## + # Personal Data Fields (PDFs) are custom fields that Gallagher allows definintions of on a site-by-site basis. + # They will usually be for things like email address, employee ID or some other field specific to whoever is hosting the Gallagher instance. + # This method allows retrieval of the PDFs used in the Gallagher instance, primarily so we can get the PDF's ID and use that to filter cardholders based on that PDF. + # + # @param name [String] The name of the PDF which we want to retrieve. This will only return one result (as the PDF names are unique). + # @return [Hash] A list of PDF results and a next link for pagination (we will generally have less than 100 PDFs so 'next' link will mostly be unused): + # @example An example response: + # { + # "results": [ + # { + # "name": "email", + # "id": "5516", + # "href": "https://localhost:8904/api/personal_data_fields/5516" + # }, + # { + # "name": "cellphone", + # "id": "9998", + # "href": "https://localhost:8904/api/personal_data_fields/9998", + # "serverDisplayName": "Site B" + # } + # ], + # "next": { + # "href": "https://localhost:8904/api/personal_data_fields?pos=900&sort=id" + # } + # } + def get_pdfs(name: nil) + # Add quotes around the value because da API bad + name = "\"#{name}\"" if name + @endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: {name: name}.compact).value + end + + ## + # Carholders are essentially users in the Gallagher system. + # This method retrieves cardholders and allows for filtering either based on the PDF provided (by name) at initalisation of the library or by some custom filter. + # For example, if the `unique_pdf_name` param passed in intialisation is `email` then passing `fixed_filter: 'some@email.com'` to this method will only return cardholders with that email. + # If some other PDF is required for filtering, it can be used via the `custom_filter` param. + # + # @param fixed_filter [String] The value to be passed to the fixed PDF filter defined when this library is initialised. By default the PDF's name is `email`. + # @param custom_filter [Hash] A PDF name and value to filter the cardholders by. For now this hash should only have one member. + # @return [Hash] A list of cardholders and a next link for pagination (we will generally have less than 100 PDFs so 'next' link will mostly be unused): + # @example An example response: + # { + # "results": [ + # { + # "href": "https://localhost:8904/api/cardholders/10135", + # "id": "10135", + # "firstName": "Algernon", + # "lastName": "Boothroyd", + # "shortName": "Q", + # "description": "Quartermaster", + # "authorised": true + # } + # ], + # "next": { + # "href": "https://localhost:8904/api/cardholders?skip=61320" + # } + # } + def get_cardholder(fixed_filter: nil, custom_filter: nil) + query = {} + # We can assume either fixed or custom filter may be used, but not both + if fixed_filter + query["pdf_#{@fixed_pdf_id}"] = "\"#{fixed_filter}\"" + elsif custom_filter + # We need to first get the PDF's ID as it's not fixed (that's why it's custom duh lol) + custom_pdf_id = self.get_pdfs(name: custom_filter.first[0].to_s) + query["pdf_#{custom_pdf_id}"] = "\"#{custom_filter}\"" + end + @endpoint.get(path: @cardholders_endpoint, headers: @default_headers, query: query).value + end + + ## + # Get a list of card types that this Gallagher instance has. These may include virutal, physical and ID cards. + # Generally there are not going to be over 100 card types so the `next` field will be unused + # + # @return [Hash] An array of cards in the `results` field and a `next` field for pagination. + # @example An example response: + # { + # "results": [ + # { + # "href": "https://localhost:8904/api/card_types/600", + # "id": "600", + # "name": "Red DESFire visitor badge", + # "facilityCode": "A12345", + # "availableCardStates": [ + # "Active", + # "Disabled (manually)", + # "Lost", + # "Stolen", + # "Damaged" + # ], + # "credentialClass": "card", + # "minimumNumber": "1", + # "maximumNumber": "16777215" + # } + # ], + # "next": { + # "href": "https://localhost:8904/api/card_types/assign?skip=100" + # } + # } + def get_card_types + @endpoint.get(path: @card_types_endpoint, headers: @default_headers).value + end + + ## + # Create a new cardholder. + # @param first_name [String] The first name of the new cardholder. Either this or last name is required (but we should assume both are for most instances). + # @param last_name [String] The last name of the new cardholder. Either this or first name is required (but we should assume both are for most instances). + # @option options [String] :division The division to add the cardholder to. This is required when making the request to create the cardholder but if none is passed the `default_division` is used. + # @option options [Hash] :pdfs A hash containing all PDFs to add to the user in the form `{ some_pdf_name: some_pdf_value, another_pdf_name: another_pdf_value }`. + # @option options [Array] :cards An array of cards to be added to this cardholder which can include both virtual and physical cards. + # @option options [Array] :access_groups An array of access groups to add this cardholder to. These may include `from` and `until` fields to dictate temporary access. + # @option options [Array] :competencies An array of competencies to add this cardholder to. + # @return [Hash] The cardholder that was created. + def create_cardholder(first_name:, last_name:, options: {}) + default_options = { + division: @default_division, + pdfs: nil, + cards: nil, + access_groups: nil, + competencies: nil + } + + # Merge in our default options with those passed in + options = options.reverse_merge(default_options) + + # Format the division correctly + options[:division] = { + href: options[:division] + } + + # The params we're actually passing to Gallagher for creation + create_params = { + firstName: first_name, + lastName: last_name, + shortName: "#{first_name} #{last_name}" + } + + # Add in our passed PDFs appending an '@' to the start of each pdf name + options[:pdfs].each do |pdf_name, pdf_value| + create_params["@#{pdf_name}".to_sym] = pdf_value + end + + # Add in any passed options, converting the keys to camel case which Gallagher uses + create_params.merge(options.except(:pdfs).transform_keys{|k| k.to_s.camelize(:lower)}) + + # Create our cardholder and return the response + @endpoint.post(path: @cardholders_endpoint, headers: @default_headers, body: create_params).value + end + + ## + # This method will take card details and return a hash card detils aligning with the passed parameters in the format Gallagher expects + # + # @example An example response: + # { + # "number": "Nick's mobile", + # "status": { + # "value": "active" + # }, + # "type": { + # "https://localhost:8904/api/card_types/654": null + # }, + # "from": "2017-01-01T00:00:00Z", + # "until": "2018-01-01T00:00:00Z", + # "invitation": { + # "email": "nick@example.com", + # "mobile": "02123456789", + # "singleFactorOnly": true + # } + # } + # @param card_href [String] This defines the type of card and can be pulled from the `get_card_types` method. + # @option options [String] :number The card number to create. If physical you can omit this as it will use a default number. If mobile this can be anything. + # @option options [Integer] :from An epoch denoting the time to start access from. + # @option options [Integer] :until An epoch denoting the time to start access until. + # @option options [String] :email An email to send a mobile credential invitation to. + # @option options [Integer] :mobile A mobile number to associate a mobile credential with. + # @return [Hash] The passed in card formatted for Gallagher. + def format_card(card_href:, options: {}) + default_options = { + number: nil, + from: nil, + until: nil, + email: nil, + mobile: nil + } + + # Merge in our default options with those passed in + options = options.reverse_merge(default_options) + + formatted_card = { + type: { + href: card_href + } + } + formatted_card[:number] = options[:number] if options[:number] + formatted_card[:from] = Time.at(options[:from]).utc.iso8601 if options[:from] + formatted_card[:until] = Time.at(options[:until]).utc.iso8601 if options[:until] + formatted_card[:invitation] = { + email: options[:email], + mobile: options[:mobile], + singleFactorOnly: true + } if options[:email] + formatted_card + end + + ## + # This method updates an existing cardholder to add new cards, access groups or competencies. + # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. + # For now the `from` and `until` params will apply to all fields in the update. + # + # @param cardholder_href [String] The ID of the cardholder inside the URL used to update it. This can be retreived from a cardholders GET. + # @option options [Array] :cards An array of cards to be added. These should at least have the `type.href` field set. + # @option options [Array] :access_groups An array of access_groups to be added. These should at least have the `accessGroup.href` field set. + # @option options [Array] :competencies An array of competencies to be added. These should at least have the `competency.href` field set. + # @return [Hash] The cardholder that access was added for. + def add_cardholder_access(cardholder_href:, options: {}) + self.update_cardholder(type: :add, cardholder_href: cardholder_href, options: options) + end + + ## + # This method updates an existing cardholder to update new cards, access groups or competencies. + # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. + # For now the `from` and `until` params will apply to all fields in the update. + # + # @param cardholder_href [String] The ID of the cardholder inside the URL used to update it. This can be retreived from a cardholders GET. + # @option options [Array] :cards An array of cards to be added. These should at least have the `type.href` field set. + # @option options [Array] :access_groups An array of access_groups to be added. These should at least have the `accessGroup.href` field set. + # @option options [Array] :competencies An array of competencies to be added. These should at least have the `competency.href` field set. + # @return [Hash] The cardholder that access was added for. + def update_cardholder_access(cardholder_href:, options: {}) + self.update_cardholder(type: :update, cardholder_href: cardholder_href, options: options) + end + + ## + # This method updates an existing cardholder to remove new cards, access groups or competencies. + # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. + # For now the `from` and `until` params will apply to all fields in the update. + # + # @param cardholder_href [String] The ID of the cardholder inside the URL used to update it. This can be retreived from a cardholders GET. + # @option options [Array] :cards An array of cards to be added. These should at least have the `type.href` field set. + # @option options [Array] :access_groups An array of access_groups to be added. These should at least have the `accessGroup.href` field set. + # @option options [Array] :competencies An array of competencies to be added. These should at least have the `competency.href` field set. + # @return [Hash] The cardholder that access was added for. + def remove_cardholder_access(cardholder_href:, options: {}) + self.update_cardholder(type: :remove, cardholder_href: cardholder_href, options: options) + end + + protected + + def update_cardholder(type:, cardholder_href:, options: {}) + default_options = { + cards: nil, + access_groups: nil, + competencies: nil + } + + # Merge in our default options with those passed in + options = options.reverse_merge(default_options) + + # Align to their kinda shitty format + patch_params = { + authorised: true + } + + # Add the fields to update if they were passed in + options.except(:from, :until).each do |param, value| + patch_params[param.to_s.camelize(:lower)] = { type => value } if value + end + + @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params).value + end +end \ No newline at end of file diff --git a/lib/gallagher.rb b/lib/gallagher/soap.rb old mode 100755 new mode 100644 similarity index 89% rename from lib/gallagher.rb rename to lib/gallagher/soap.rb index 08d9e00c..b4703cdb --- a/lib/gallagher.rb +++ b/lib/gallagher/soap.rb @@ -1,27 +1,9 @@ require 'savon' require 'active_support/time' -# gallagher = Gallagher.new( -# username: 'system', -# password: 'system', -# endpoint: 'https://DESKTOP-ABH46ML:8082/Cardholder/', -# namespace: 'http://www.gallagher.co/security/commandcentre/webservice', -# namespaces: {"xmlns:wsdl" => "http://www.gallagher.co/security/commandcentre/cifws", "xmlns:web" => 'http://www.gallagher.co/security/commandcentre/webservice'}, -# wsdl: 'https://DESKTOP-ABH46ML:8082/Cardholder/?WSDL', -# cert_path: './client_cert.pem', -# ca_cert_path: './ca_cert.pem', -# key_path: './key.pem', -# log: false, -# log_level: nil -# ) - - -# gallagher.has_cardholder("435") - -# gallagher.get_cards("435") -# gallagher.set_access('435', 'T2 Shared', Time.now.utc.iso8601, (Time.now + 3.hours).utc.iso8601) - -class Gallagher +module Gallagher; end + +class Gallagher::Soap def initialize( username:, password:, From 96c401f3bd6b093e91c39c247d3a4e06fa2cc081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Po=C5=82o=C5=84ski?= Date: Tue, 17 Mar 2020 17:02:40 +1100 Subject: [PATCH 1674/1752] feat(grops):get member groups --- lib/microsoft/office2/groups.rb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office2/groups.rb b/lib/microsoft/office2/groups.rb index 18d10660..642ce4d0 100644 --- a/lib/microsoft/office2/groups.rb +++ b/lib/microsoft/office2/groups.rb @@ -11,4 +11,23 @@ def list_user_member_of(id, result_fields = 'id,displayName', transitive = true) check_response(response) JSON.parse(response.body)['value'] end -end \ No newline at end of file + + + # Return all the groups that the user is a member of. The check is transitive, unlike reading the memberOf navigation property, + # which returns only the groups that the user is a direct member of. + # This function supports Office 365 and other types of groups provisioned in Azure AD. + # The maximum number of groups each request can return is 2046. Note that Office 365 Groups cannot contain groups. + # So membership in an Office 365 Group is always direct. + # id: user id or userPrincipalName + # result_fields: array of strings of group names to which user belongs + # https://docs.microsoft.com/en-us/graph/api/user-getmembergroups + def get_member_groups(id, result_fields = '', transitive = true) + return {'error': "400: No user \'id\' supplied" } if id.nil? + endpoint = "/v1.0/users/#{id}/getMemberGroups" + response = graph_request(request_method: 'get', endpoints: [endpoint], query: { '$top': 999 } ) + check_response(response) + JSON.parse(response.body)['value'] + end + + +end From 70b4a0fd982a2314f1eb583a6bd8f42ab0d59a9c Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 20 Mar 2020 01:02:35 +0800 Subject: [PATCH 1675/1752] fix(pressac/desk/logic): expose stale desks --- modules/pressac/desk_management.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 9179e7a4..94c45c8f 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -222,8 +222,11 @@ def id(array) def unexpose_unresponsive_desks(notification) stale_sensors = notification.value # Sort into Zones, keeping SVG map ID only - zoned_stale_sensors = Hash.new(Array.new) - stale_sensors&.each { |s| zoned_stale_sensors[@which_zone[s[:gateway]] << id(s.keys) ] } + zoned_stale_sensors = {} + stale_sensors&.each do |sensor_name, sensor| + zoned_stale_sensors[@which_zone[sensor['gateway']]] ||= [] + zoned_stale_sensors[@which_zone[sensor['gateway']]] | id([sensor_name]) + end logger.debug "PRESSAC > DESK > LOGIC: Displaying stale sensors as #{@stale_status}: #{zoned_stale_sensors}" From 1425e575cf1089c96a0f3b1fa65a9999899457e1 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 20 Mar 2020 01:04:08 +0800 Subject: [PATCH 1676/1752] feat(pressac/event_canceller): Overhaul cancellation logic --- modules/pressac/booking_canceller.rb | 76 +++++++++++++++----------- modules/pressac/sensors/ws_protocol.rb | 36 ++++++++++-- 2 files changed, 74 insertions(+), 38 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 345b9226..0c53cd17 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -14,11 +14,13 @@ class ::Pressac::BookingCanceller default_settings({ bookings_device: "Bookings_1", - desk_management_system_id: "sys-xxxxxxxx", - desk_management_device: "DeskManagement_1", - sensor_zone_id: "zone-xxxxxxxx", - check_every: "1m", - delay_until_cancel: "15m" + check_every: "1m", + cancel_after: "15m", + stale_after: "1h", + + pressac_system_id: "sys-xxxxxxxx", + pressac_device: "Websocket_1", + sensor_zone_id: "zone-xxxxxxxx" }) def on_load @@ -26,48 +28,56 @@ def on_load end def on_update - @subscriptions ||= [] - @subscriptions.each { |ref| unsubscribe(ref) } - @subscriptions.clear + @bookings = setting('bookings_device') + @pressac_system = setting('pressac_system_id') + @pressac_device = setting('pressac_device') + @zone = setting('sensor_zone_id') + @sensor_name = (setting('sensor_name') || setting('map_id')).to_sym + @scan_cycle = setting('check_every') + @delay_until_cancel = UV::Scheduler.parse_duration(setting('cancel_after')) / 1000 + @stale_timeout = UV::Scheduler.parse_duration(setting('stale_after')) / 1000 + self[:sensor_name] = @sensor_name - @bookings = setting('bookings_device') - @desk_management_system = setting('desk_management_system_id') - @desk_management_device = setting('desk_management_device') - @zone = setting('sensor_zone_id') - @sensor = setting('sensor_name') || setting('map_id') - @scan_cycle = setting('check_every') - @cancel_delay = UV::Scheduler.parse_duration(setting('delay_until_cancel')) / 1000 - schedule.clear schedule.every(@scan_cycle) { determine_booking_presence } end def determine_booking_presence - # Expose presence status - all_sensors = systems(@desk_management_system)[@desk_management_device] - unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist - msg = "Pressac Booking Canceller: Sensor #{@sensor} NOT FOUND in #{@zone}" unless all_sensors[@zone + ':desk_ids'].include? @sensor # don't continue if the sensor does not exist + # Fetch current sensor data from Pressac Module + sensor = systems(@pressac_system)[@pressac_device][:sensors]&.dig(@sensor_name) + if sensor.nil? + # Don't continue if the sensor does not exist + msg = "Pressac Booking Canceller: Sensor #{@sensor_name} NOT FOUND" logger.debug msg return msg end - self[:presence] = all_sensors[@zone].include? @sensor - # Check each booking + # Expose relevant sensor status now = Time.now.to_i - bookings = system[@bookings][:today] - bookings&.each do |booking| - next unless now < booking[:end_epoch] - next unless now > booking[:start_epoch] + @cancel_delay - logger.debug "Pressac Booking Canceller: \"#{booking[:Subject]}\" started at #{Time.at(booking[:start_epoch]).to_s} with #{@sensor} presence: #{self[:presence]}" - if !self[:presence] + self[:stale_sensor] = sensor_is_stale = now - sensor[:last_update_epoch] > @stale_timeout + self[:will_cancel] = prolonged_vacancy = now - (sensor[:became_free] || 0) > @delay_until_cancel # If the sensor has been "free" for longer than the past X mins + self[:presence] = sensor[:motion] + if self[:presence] + self[:became_busy] = Time.at(sensor[:became_busy]).to_s + self[:became_free] = nil + msg = "Pressac Booking Canceller: Presence detected by #{@sensor_name}" + logger.debug msg + return msg + else + self[:became_busy] = nil + self[:became_free] = Time.at(sensor[:became_free]).to_s + end + + if prolonged_vacancy && !sensor_is_stale + # Check each booking + bookings = system[@bookings][:today] + bookings&.each do |booking| + next unless now < booking[:end_epoch] # Skip past bookings + next unless now > booking[:start_epoch] + @delay_until_cancel # Only consider bookings X mins after their start msg = "Pressac Booking Canceller: ENDING \"#{booking[:Subject]}\" now" logger.debug msg truncate(booking) return msg - else - msg = "Pressac Booking Canceller: No action for \"#{booking[:Subject]}\"" - logger.debug msg - return msg end end return @@ -75,7 +85,7 @@ def determine_booking_presence def truncate(booking) system[@bookings].end_meeting(booking[:id]).then do |response| - logger.info "Ended #{booking[:Subject]} with response #{response}" + logger.info "Pressac Booking Canceller: Ended #{booking[:Subject]} with response #{response}" end end end diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index e710555b..846012bb 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -44,9 +44,13 @@ def on_load def on_update status = setting(:status) || {} + # Human readable tree of { gateway: {sensor: {data}} } @gateways = status[:gateways] || {} self[:gateways] = @gateways.dup - + + # Flat hash of {sensor: {data}} + self[:sensors] = status[:sensors].dup || {} + @last_update = status[:last_update] || "Never" self[:last_update] = @last_update.dup @@ -69,16 +73,25 @@ def disconnected def mock(sensor, occupied) gateway = which_gateway(sensor) - @gateways[gateway][sensor] = self[gateway] = { + mock_data = { id: 'mock_data', name: sensor, motion: occupied, voltage: '3.0', location: nil, timestamp: Time.now.to_s, + last_update: Time.now.in_time_zone($TZ).to_s, + last_update_epoch: Time.now.to_i, gateway: gateway } - + if occupied + mock_data[:became_free] = nil + mock_data[:became_busy] ||= Time.now.to_i + else + mock_data[:became_free] ||= Time.now.to_i + mock_data[:became_busy] = nil + end + @gateways[gateway][sensor] = self[gateway] = self[:sensors][sensor] = mock_data self[:gateways] = @gateways.deep_dup end @@ -98,10 +111,12 @@ def list_stale_sensors end end self[:stale] = @stale + signal_status(:stale) # Save the current status to database, so that it can retrieved when engine restarts status = { last_update: self[:last_update], gateways: @gateways, + sensors: self[:sensors], stale: @stale } define_setting(:status, status) @@ -158,8 +173,18 @@ def on_message(raw_string) last_update_epoch: Time.now.to_i, gateway: gateway } - self[gateway] = @gateways[gateway][sensor_name].dup - self[:gateways] = @gateways.deep_dup + # If the occupancy state CHANGED, store this time. So that downstream can calculate the LENGTH of time that the sensor has just been free/busy for + if occupancy + @gateways[gateway][sensor_name][:became_free] = nil + @gateways[gateway][sensor_name][:became_busy] ||= Time.now.to_i + else + @gateways[gateway][sensor_name][:became_free] ||= Time.now.to_i + @gateways[gateway][sensor_name][:became_busy] = nil + end + + self[gateway] = @gateways[gateway][sensor_name].dup # this status var is used to stream notifications to the Pressac Desk Management driver, for map status updates + self[:sensors][sensor_name] = @gateways[gateway][sensor_name].dup # this status var is used by Pressac booking canceller for quick sensor status lookup + self[:gateways] = @gateways.deep_dup # this status var is for humans to conveniently view a tree of sensors, grouped by their gateway if @stale[sensor_name] @stale.except!(sensor_name) self[:stale] = @stale.deep_dup @@ -181,6 +206,7 @@ def on_message(raw_string) status = { last_update: self[:last_update], gateways: @gateways.deep_dup, + sensors: self[:sensors], stale: self[:stale] } define_setting(:status, status) From ead0c4ced9bd255cbb3e998965b6f43754c0654a Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 20 Mar 2020 18:08:44 +0800 Subject: [PATCH 1677/1752] fix(pressac/desk/mock): persist sensor.became_free --- modules/pressac/sensors/ws_protocol.rb | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/modules/pressac/sensors/ws_protocol.rb b/modules/pressac/sensors/ws_protocol.rb index 846012bb..13a929d5 100644 --- a/modules/pressac/sensors/ws_protocol.rb +++ b/modules/pressac/sensors/ws_protocol.rb @@ -49,6 +49,7 @@ def on_update self[:gateways] = @gateways.dup # Flat hash of {sensor: {data}} + @sensors = status[:sensors] || {} self[:sensors] = status[:sensors].dup || {} @last_update = status[:last_update] || "Never" @@ -73,17 +74,18 @@ def disconnected def mock(sensor, occupied) gateway = which_gateway(sensor) - mock_data = { - id: 'mock_data', - name: sensor, - motion: occupied, - voltage: '3.0', - location: nil, - timestamp: Time.now.to_s, - last_update: Time.now.in_time_zone($TZ).to_s, - last_update_epoch: Time.now.to_i, - gateway: gateway - } + mock_data = self[:sensors][sensor]&.dup || + { + id: 'mock_data', + name: sensor, + voltage: '3.0', + location: nil, + gateway: gateway + } + mock_data[:motion] = occupied + mock_data[:timestamp] = Time.now.to_s + mock_data[:last_update] = Time.now.in_time_zone($TZ).to_s + mock_data[:last_update_epoch] = Time.now.to_i if occupied mock_data[:became_free] = nil mock_data[:became_busy] ||= Time.now.to_i @@ -91,8 +93,9 @@ def mock(sensor, occupied) mock_data[:became_free] ||= Time.now.to_i mock_data[:became_busy] = nil end - @gateways[gateway][sensor] = self[gateway] = self[:sensors][sensor] = mock_data + @gateways[gateway][sensor] = self[gateway] = @sensors[sensor] = mock_data self[:gateways] = @gateways.deep_dup + self[:sensors] = @sensors.deep_dup end def which_gateway(sensor) From d93057d33e5edb036fcf30e1b0d218ad26f594b2 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 20 Mar 2020 18:09:38 +0800 Subject: [PATCH 1678/1752] feat(pressac/canceller): more status vars testing --- modules/pressac/booking_canceller.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/pressac/booking_canceller.rb b/modules/pressac/booking_canceller.rb index 0c53cd17..32b08654 100644 --- a/modules/pressac/booking_canceller.rb +++ b/modules/pressac/booking_canceller.rb @@ -40,6 +40,7 @@ def on_update schedule.clear schedule.every(@scan_cycle) { determine_booking_presence } + determine_booking_presence end def determine_booking_presence @@ -55,20 +56,22 @@ def determine_booking_presence # Expose relevant sensor status now = Time.now.to_i self[:stale_sensor] = sensor_is_stale = now - sensor[:last_update_epoch] > @stale_timeout - self[:will_cancel] = prolonged_vacancy = now - (sensor[:became_free] || 0) > @delay_until_cancel # If the sensor has been "free" for longer than the past X mins - self[:presence] = sensor[:motion] - if self[:presence] + self[:motion] = sensor[:motion] + if self[:motion] self[:became_busy] = Time.at(sensor[:became_busy]).to_s self[:became_free] = nil + self[:vacant] = prolonged_vacancy = false msg = "Pressac Booking Canceller: Presence detected by #{@sensor_name}" logger.debug msg return msg else self[:became_busy] = nil self[:became_free] = Time.at(sensor[:became_free]).to_s + self[:vacant] = prolonged_vacancy = now - (sensor[:became_free] || now) > @delay_until_cancel # If the sensor has been "free" for longer than the past X mins end if prolonged_vacancy && !sensor_is_stale + self[:will_cancel] = true # Check each booking bookings = system[@bookings][:today] bookings&.each do |booking| @@ -79,6 +82,8 @@ def determine_booking_presence truncate(booking) return msg end + else + self[:will_cancel] = false end return end From e4e90a05d020ddb848fb3df331d89bcd38560e44 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 24 Mar 2020 09:31:58 +1100 Subject: [PATCH 1679/1752] feat(pexip): add dialing support from admin server --- modules/pexip/management.rb | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index dd10bd6f..85765a3d 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -134,4 +134,45 @@ def cleanup_meetings(older_than) end nil end + + def dial_phone(meeting_alias, phone_number) + phone_number = phone_number.gsub(/\s/, "") + + body = if phone_number.start_with?("+") + { + conference_alias: meeting_alias, + destination: "#{phone_number}@conference.meet.health.nsw.gov.au", + protocol: 'sip', + system_location: 'UN_InternalWebRTC_SIPH323_Proxy' + } + else + { + conference_alias: meeting_alias, + destination: phone_number, + protocol: 'h323', + system_location: 'UN_InternalWebRTC_SIPH323_Proxy' + } + end + + post('/api/admin/command/v1/participant/dial/', + body: body, + headers: { + 'Authorization' => [@username, @password], + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + } + ) do |data| + if (200...300).include?(data.status) + response = JSON.parse(data.body, symbolize_names: true) + if response[:status] == "success" + # {participant_id: "5acac442-7a25-44fa-badf-4bc725a0f035", participant_ids: ["5acac442-7a25-44fa-badf-4bc725a0f035"]} + response[:data] + else + :abort + end + else + :abort + end + end + end end From 11518ffd14b32ac320117b401f559962b4a8c8b5 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 24 Mar 2020 10:06:01 +1100 Subject: [PATCH 1680/1752] fix(pexip): dialing details need to be json --- modules/pexip/management.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 85765a3d..f1015308 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -155,7 +155,7 @@ def dial_phone(meeting_alias, phone_number) end post('/api/admin/command/v1/participant/dial/', - body: body, + body: body.to_json, headers: { 'Authorization' => [@username, @password], 'Content-Type' => 'application/json', From 5b8440787baf9287ce01fc554e820f8aad08e54a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 24 Mar 2020 12:35:24 +1100 Subject: [PATCH 1681/1752] fix(pexip): update with routing rule --- modules/pexip/management.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index f1015308..20c0a9f7 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -140,6 +140,9 @@ def dial_phone(meeting_alias, phone_number) body = if phone_number.start_with?("+") { + call_type: 'audio', + role: 'guest', + routing: 'routing_rule', conference_alias: meeting_alias, destination: "#{phone_number}@conference.meet.health.nsw.gov.au", protocol: 'sip', @@ -147,6 +150,9 @@ def dial_phone(meeting_alias, phone_number) } else { + call_type: 'audio', + role: 'guest', + routing: 'routing_rule', conference_alias: meeting_alias, destination: phone_number, protocol: 'h323', From 2a789ae4a3045d47e5c9014c6adbdb0b4d5b7784 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 25 Mar 2020 19:52:41 +1100 Subject: [PATCH 1682/1752] feat(cogni point): add support for traffic flow --- modules/point_grab/cogni_point.rb | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/modules/point_grab/cogni_point.rb b/modules/point_grab/cogni_point.rb index b60c0762..658495ba 100644 --- a/modules/point_grab/cogni_point.rb +++ b/modules/point_grab/cogni_point.rb @@ -4,6 +4,9 @@ module PointGrab; end # Documentation: https://aca.im/driver_docs/PointGrab/CogniPointAPI2-1.pdf +# subscribe: "https://domain/control/api/webhooks/trig-y8i/router?secret=[secret]&mod=CogniPoint&func=update_count", "random_code", "COUNTING" +# subscribe: "https://domain/control/api/webhooks/trig-y8i/router?secret=[secret]&mod=CogniPoint&func=update_traffic", "random_code", "TRAFFIC" +# Start subscription: update_subscription(id, true) class PointGrab::CogniPoint include ::Orchestrator::Constants @@ -48,7 +51,12 @@ def on_update # "area_1": { # "capacity": 100, # "people_count": 90 - # }} + # }, + # "area_2": { + # "count_in": 100, + # "count_out": 90 + # } + #} @floor_details ||= {} end @@ -279,6 +287,34 @@ def get_area_details(area_id) @area_details[area_id] end + # {"areaId":"ThFEZHPrS7yBtNB-7EkffQ","countIn":0,"devices":["ghcSAW49Tj-ihD-CktweaQ"],"type":"TRAFFIC","countOut":0,"timestamp":1585104716677} + def update_traffic(raw_json) + traffic = JSON.parse(raw_json, symbolize_names: true) + area_id = traffic[:areaId] + count_in = traffic[:countIn] + count_out = traffic[:countOut] + + area_details = get_area_details(area_id) + if area_details + floor_id = area_details[:floor_id] + floor_mapping = @floor_mappings[floor_id] || floor_id + area_mapping = @area_mappings[area_id] || {} + area_id = area_mapping[:id] || area_id + + # update the details + floor_areas = @floor_details[floor_mapping] || {} + floor_areas[area_id] = { + count_in: count_in, + count_out: count_out + }.merge(area_mapping) + @floor_details[floor_mapping] = floor_areas + + self[floor_mapping] = floor_areas.dup + end + + true + end + # this data is posted to the subscription endpoint # we need to implement webhooks for this to work properly # {areaId: "", devices: [""], type: "", timestamp: 0, count: 0} @@ -310,5 +346,7 @@ def update_count(count_json) self[floor_mapping] = floor_areas.dup end + + true end end From 2cde45e261e23aabe60d5837defed06fab7135db Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 27 Mar 2020 18:38:16 +0800 Subject: [PATCH 1683/1752] fix(o365/RBP): also use old setting office_https_proxy (not just msgraph_https_proxy) --- modules/aca/o365_booking_panel.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 5fd343aa..1b44a306 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -85,8 +85,8 @@ def on_update msgraph_secret = setting(:msgraph_secret) || setting(:office_secret) msgraph_token_path = setting(:msgraph_token_path) || setting(:office_token_path) || "/oauth2/v2.0/token" msgraph_token_url = setting(:msgraph_token_url) || setting(:office_token_url) || "/" + (setting(:msgraph_tenant) || setting(:office_tenant)) + msgraph_token_path - @room_mailbox = (setting(:msgraph_room) || system.email) - msgraph_https_proxy = setting(:msgraph_https_proxy) + @room_mailbox = (setting(:msgraph_room) || setting(:office_room) || system.email) + msgraph_https_proxy = setting(:msgraph_https_proxy) || setting(:office_https_proxy) logger.debug "RBP>#{@room_mailbox}>INIT: Instantiating o365 Graph API client" From 5563d6eca24dff001f8001466cf4aa8cb169a04c Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 31 Mar 2020 17:41:51 +0800 Subject: [PATCH 1684/1752] feat(office2/users/list): allow filtering out Guests --- lib/microsoft/office2/users.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office2/users.rb b/lib/microsoft/office2/users.rb index b56f5cd3..6581722c 100644 --- a/lib/microsoft/office2/users.rb +++ b/lib/microsoft/office2/users.rb @@ -16,7 +16,8 @@ def get_user(id:, select: nil) # # @param q [String] The query param which filters all users without a name or email matching this string # @param limit [String] The maximum number of users to return - def get_users(q: nil, limit: nil) + # @param include_guests [Bool] Whether or not to include Guest (external) users in the results + def get_users(q: nil, limit: nil, include_guests: false) # If we have a query and the query has at least one space if q && q.include?(" ") @@ -39,6 +40,9 @@ def get_users(q: nil, limit: nil) # If we have no query then still only grab enabled accounts filter_param = "accountEnabled eq true" if q.nil? + # userType is either "Member" or "Guest" + filter_param << " and (userType eq 'Member')" if !include_guests + # Put our params together and make the request query_params = { '$filter': filter_param, From 2077843ad8d9c5639204736eabf3229389d584c1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 14 Apr 2020 13:50:42 +1000 Subject: [PATCH 1685/1752] feat: add support for auto-switching atlona based on a list of input priorities --- modules/atlona/omni_stream/auto_switcher.rb | 7 +- .../atlona/omni_stream/virtual_switcher.rb | 114 +++++++++++++++++- 2 files changed, 112 insertions(+), 9 deletions(-) diff --git a/modules/atlona/omni_stream/auto_switcher.rb b/modules/atlona/omni_stream/auto_switcher.rb index d22be308..5250edaf 100644 --- a/modules/atlona/omni_stream/auto_switcher.rb +++ b/modules/atlona/omni_stream/auto_switcher.rb @@ -31,12 +31,13 @@ def on_update # Bind to all the encoders for presence detection if @virtual_switcher != switcher || @auto_switch != auto_switch + @virtual_switcher = switcher + if @auto_switch != auto_switch @auto_switch = auto_switch switch_all end - @virtual_switcher = switcher subscribe_virtual_inputs end end @@ -80,7 +81,7 @@ def switch_all return unless @enabled @auto_switch.each do |output, inputs| - system[@virtual_switcher].switch({ inputs => output }) + system[@virtual_switcher].switch({ inputs => output }, priority_auto_switch: false) end end @@ -139,7 +140,7 @@ def check_auto_switch(auto_output, auto_inputs, checking_input, input_name, enco if current_value != new_value logger.debug { "Switching #{auto_inputs} => #{auto_output} as detected change on encoder input #{checking_input}" } @last_known_state[checking_input] = new_value - system[@virtual_switcher].switch({ auto_inputs => auto_output }) + system[@virtual_switcher].switch({ auto_inputs => auto_output }, priority_auto_switch: false) end end end diff --git a/modules/atlona/omni_stream/virtual_switcher.rb b/modules/atlona/omni_stream/virtual_switcher.rb index e2286613..e67c1916 100644 --- a/modules/atlona/omni_stream/virtual_switcher.rb +++ b/modules/atlona/omni_stream/virtual_switcher.rb @@ -20,9 +20,20 @@ def on_update @routes ||= {} @encoder_name = setting(:encoder_name) || :Encoder @decoder_name = setting(:decoder_name) || :Decoder + + # Support auto-switching we remember the last switch for any grouping + # of outputs and then auto-switch based on changes to the outputs + # {"outputs_2_2": { switch: map, monitor: [inp1, inp2], switch_video: true, ... }} + @auto_switch = {} + monitor_encoders + end + + # For introspection from backoffice + def auto_switch + @auto_switch end - def switch(map, switch_video: true, switch_audio: true, enable_override: nil) + def switch(map, switch_video: true, switch_audio: true, enable_override: nil, priority_auto_switch: true, **ignore) inputs = get_encoders outputs = get_decoders @@ -31,11 +42,14 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil) # Select the first input where there is a video signal if inp.is_a?(Array) selected = nil - inp.each do |check_inp| + monitor = [] + inp.each_with_index do |check_inp| + # Get input details check_inp = check_inp.to_s input, session_index = inputs[check_inp] next if input.nil? + # Grab the latest session details sessions = input[:sessions] next unless sessions encoder_name = sessions.dig(session_index, :video, :encoder) @@ -46,6 +60,15 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil) video_ins = Array(input[:inputs]).select { |vin| vin[:name] == video_input } next unless video_ins.length > 0 + monitor.push({ + input_no: check_inp, # virtual input number + # Encoder_index + device: self[:input_mappings][check_inp][:encoder], + # Video input name + video_input: video_input + }) if priority_auto_switch + + # Check if a device is detected if video_ins[0][:cabledetect] selected = check_inp break @@ -53,12 +76,25 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil) end if selected - inp = selected - logger.debug { "found active input on #{inp}" } + logger.debug { "found active input on #{selected}" } else - inp = inp.last - logger.debug { "no active input found, switching to #{inp}" } + selected = inp.last + logger.debug { "no active input found, switching to #{selected}" } end + + if priority_auto_switch + auto_switch_key = outs.map(&:to_s).join("_") + @auto_switch[auto_switch_key] = { + switch: map, + switch_video: switch_video, + switch_audio: switch_audio, + enable_override: enable_override, + # We only need to switch if a change occurs to one of these inputs + monitor: monitor + } + end + + inp = selected end inp = inp.to_s @@ -170,6 +206,72 @@ def get_mappings protected + def monitor_encoders + logger.debug { "monitoring encoder inputs" } + + @subscriptions ||= [] + @subscriptions.each { |ref| unsubscribe(ref) } + @subscriptions.clear + + @cached_state = {} + + encoder_name = @encoder_name + (0...system.count(encoder_name)).each do |index| + index += 1 + encoder_id = "#{encoder_name}_#{index}" + @subscriptions << system.subscribe(encoder_name, index, :inputs) do |notify| + check_auto_switch(encoder_id, notify.value) + end + end + end + + def check_auto_switch(encoder_id, inputs) + return if inputs.nil? + + logger.debug { "checking for autoswitch changes on #{encoder_id}" } + + # Extract the cable detect state + state = {} + Array(inputs).each do |input| + state[input[:name]] = input[:cabledetect] + end + + # Check for changes + changed = [] + state.each do |name, detected| + if @cached_state[name] != detected + @cached_state[name] = detected + changed << name + end + end + + return unless changed + + logger.debug { "detected autoswitch changes on #{encoder_id}" } + + # See if there are any routes we should be auto-switching + do_switch = [] + @auto_switch.each do |outputs, details| + monitoring = details[:monitor] + monitoring.each do |input| + check_encoder_id = input[:device] + next unless encoder_id == check_encoder_id + video_input_name = input[:video_input] + next unless changed.include?(video_input_name) + + do_switch << details + break + end + end + + logger.debug { "found #{do_switch.length} autoswitch actions" } + + # Perform the auto-switching + do_switch.each do |details| + switch(details[:switch], **details) + end + end + # Enumerate the devices that make up this virtual switcher def get_encoders index = 1 From 96041079addc9e0b56e769fbf6c8033c3d638b59 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Apr 2020 14:48:34 +1000 Subject: [PATCH 1686/1752] feat(QSC atlona monitor): provide virtual audio breakaway --- .../atlona/omni_stream/virtual_switcher.rb | 2 +- modules/qsc/atlona_monitor.rb | 81 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 modules/qsc/atlona_monitor.rb diff --git a/modules/atlona/omni_stream/virtual_switcher.rb b/modules/atlona/omni_stream/virtual_switcher.rb index e67c1916..5914a167 100644 --- a/modules/atlona/omni_stream/virtual_switcher.rb +++ b/modules/atlona/omni_stream/virtual_switcher.rb @@ -345,7 +345,7 @@ def get_decoders decoder_mapping[output.to_s] = mapping_details mapping_details = { - encoder: "#{@decoder_name}_#{index}", + decoder: "#{@decoder_name}_#{index}", output: num } info_mapping[output.to_s] = mapping_details diff --git a/modules/qsc/atlona_monitor.rb b/modules/qsc/atlona_monitor.rb new file mode 100644 index 00000000..7ef59369 --- /dev/null +++ b/modules/qsc/atlona_monitor.rb @@ -0,0 +1,81 @@ +# encoding: ASCII-8BIT +# frozen_string_literal: true + +module Qsc; end +class Qsc::AtlonaMonitor + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + include ::Orchestrator::StateBinder + + descriptive_name 'QSC - Atlona Output Monitor' + description 'Monitors QSC devices for changes to input streams and updates QSC controls to match' + generic_name :AtlonaMonitor + implements :logic + + def on_load + # output_id => audio session stream IP + @last_known_state = {} + on_update + end + + def on_update + # { "output": {"component": "B-LC5-105-Rx", control: "PGMRx:Stream"} } + @stream_mappings = setting(:output_stream_mappings) || {} + end + + # Monitor changes to routes + bind :Switcher, :routes do |routes| + check_changes(routes) + end + + # Update QSC with any stream changes + def check_changes(routes) + return unless routes + check_keys = @stream_mappings.keys.map(&:to_s) & routes.keys.map(&:to_s) + return if check_keys.empty? + + # Get the decoder details + mappings = system[:Switcher][:output_mappings] + + # Obtain the current list of multicast addresses + output_streams = {} + check_keys.each do |output| + details = mappings[output] + + decoder = system[details[:decoder]] + if decoder.nil? + logger.warn "unable to find decoder #{details[:decoder].inspect} in system" + next + end + output_index = details[:output] - 1 + + input_name = decoder[:outputs].dig(output_index, :video, :input) + if input_name.nil? + logger.warn "unable to find name of output #{output_index.inspect} -> video -> input in \n#{decoder[:outputs]}" + next + end + mcast_address = decoder[:ip_inputs].dig(input_name, :multicast, :address) + if mcast_address.nil? + logger.warn "unable to find mcast_address of decoder input #{input_name.inspect} -> multicast -> address in \n#{decoder[:ip_inputs]}" + next + end + + output_streams[output] = mcast_address + end + + # check for any changes + qsc = system[:Mixer] + output_streams.each do |output_id, mcast_address| + if @last_known_state[output_id] != mcast_address + logger.debug { "Updating QSC stream for output #{output_id}" } + details = @stream_mappings[output_id] + qsc.component_set(details[:component], { + Name: details[:control], + Value: mcast_address + }) + end + end + + @last_known_state = output_streams + end +end From 644e9d414e81dd021ec9d5d09324275cbe267b9d Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Apr 2020 16:13:56 +1000 Subject: [PATCH 1687/1752] fix(QSC atlona monitor): add a check for unroute and use audio streams instead of video streams --- modules/qsc/atlona_monitor.rb | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/modules/qsc/atlona_monitor.rb b/modules/qsc/atlona_monitor.rb index 7ef59369..a878dcc3 100644 --- a/modules/qsc/atlona_monitor.rb +++ b/modules/qsc/atlona_monitor.rb @@ -33,30 +33,35 @@ def check_changes(routes) return unless routes check_keys = @stream_mappings.keys.map(&:to_s) & routes.keys.map(&:to_s) return if check_keys.empty? + check_keys = check_keys.map { |output| [output, (routes[output] || routes[output.to_i]).to_s] } # Get the decoder details - mappings = system[:Switcher][:output_mappings] + mappings = system[:Switcher][:input_mappings] # Obtain the current list of multicast addresses output_streams = {} - check_keys.each do |output| - details = mappings[output] + check_keys.each do |(output, input)| + if input == "0" + output_streams[output] = "" + next + end - decoder = system[details[:decoder]] - if decoder.nil? - logger.warn "unable to find decoder #{details[:decoder].inspect} in system" + details = mappings[input] + if details.nil? + logger.warn "details for input #{input.inspect} not found for output #{output.inspect} in\n#{mappings}" next end - output_index = details[:output] - 1 - input_name = decoder[:outputs].dig(output_index, :video, :input) - if input_name.nil? - logger.warn "unable to find name of output #{output_index.inspect} -> video -> input in \n#{decoder[:outputs]}" + encoder = system[details[:encoder]] + if encoder.nil? + logger.warn "unable to find encoder #{details[:encoder].inspect} in system" next end - mcast_address = decoder[:ip_inputs].dig(input_name, :multicast, :address) + + session_index = details[:session] - 1 + mcast_address = encoder[:sessions].dig(session_index, :audio, :stream, :destination_address) if mcast_address.nil? - logger.warn "unable to find mcast_address of decoder input #{input_name.inspect} -> multicast -> address in \n#{decoder[:ip_inputs]}" + logger.warn "unable to find mcast_address in session #{session_index} -> audio -> stream -> destination_address in \n#{encoder[:sessions]}" next end From 565f42e0f413e5fd6b486567a438d368b4f58089 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 17 Apr 2020 11:56:08 +1000 Subject: [PATCH 1688/1752] fix(atlona virtual switcher): improve auto-switching --- modules/atlona/omni_stream/auto_switcher.rb | 6 ++- .../atlona/omni_stream/virtual_switcher.rb | 37 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/modules/atlona/omni_stream/auto_switcher.rb b/modules/atlona/omni_stream/auto_switcher.rb index 5250edaf..c61d5fea 100644 --- a/modules/atlona/omni_stream/auto_switcher.rb +++ b/modules/atlona/omni_stream/auto_switcher.rb @@ -47,7 +47,11 @@ def enabled(state) if state != @enabled self[:enabled] = @enabled = state define_setting(:auto_switch_enabled, @enabled) - switch_all + + if state + system[@virtual_switcher].clear_auto_switching + switch_all + end end nil end diff --git a/modules/atlona/omni_stream/virtual_switcher.rb b/modules/atlona/omni_stream/virtual_switcher.rb index 5914a167..f741f5ac 100644 --- a/modules/atlona/omni_stream/virtual_switcher.rb +++ b/modules/atlona/omni_stream/virtual_switcher.rb @@ -21,6 +21,11 @@ def on_update @encoder_name = setting(:encoder_name) || :Encoder @decoder_name = setting(:decoder_name) || :Decoder + # Poll the encoders we want to auto-switch + schedule.clear + poll_every = setting(:auto_switch_poll_every) || '3s' + schedule.every(poll_every) { poll_inputs } + # Support auto-switching we remember the last switch for any grouping # of outputs and then auto-switch based on changes to the outputs # {"outputs_2_2": { switch: map, monitor: [inp1, inp2], switch_video: true, ... }} @@ -33,11 +38,16 @@ def auto_switch @auto_switch end + def clear_auto_switching + @auto_switch = {} + end + def switch(map, switch_video: true, switch_audio: true, enable_override: nil, priority_auto_switch: true, **ignore) inputs = get_encoders outputs = get_decoders map.each do |inp, outs| + outs = Array(outs) begin # Select the first input where there is a video signal if inp.is_a?(Array) @@ -82,8 +92,8 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil, pr logger.debug { "no active input found, switching to #{selected}" } end + auto_switch_key = outs.map(&:to_s).join("_") if priority_auto_switch - auto_switch_key = outs.map(&:to_s).join("_") @auto_switch[auto_switch_key] = { switch: map, switch_video: switch_video, @@ -92,6 +102,8 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil, pr # We only need to switch if a change occurs to one of these inputs monitor: monitor } + else + @auto_switch.delete(auto_switch_key) end inp = selected @@ -256,8 +268,10 @@ def check_auto_switch(encoder_id, inputs) monitoring.each do |input| check_encoder_id = input[:device] next unless encoder_id == check_encoder_id - video_input_name = input[:video_input] - next unless changed.include?(video_input_name) + + # TODO:: fix this check - not really required though + # video_input_name = input[:video_input] + # next unless changed.include?(video_input_name) || changed.include?(video_input_name.to_s) do_switch << details break @@ -363,4 +377,21 @@ def get_decoders self[:output_mappings] = info_mapping decoder_mapping end + + def poll_inputs + encoders = [] + + @auto_switch.each_value do |inputs| + inputs[:monitor].each do |input| + encoders << input[:device] + end + end + + encoders.uniq! + encoders.each do |encoder| + system[encoder].hdmi_input + end + + nil + end end From d0c1b3e74b7e838f0eadb2b7f14a2fbb3164762a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 17 Apr 2020 13:45:18 +1000 Subject: [PATCH 1689/1752] feat: qsc atlona auto switcher to clear virtual switcher routes where the virtual switcher might be auto-switching an old route --- modules/atlona/omni_stream/auto_switcher.rb | 10 ++++++---- modules/qsc/atlona_monitor.rb | 11 +++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/modules/atlona/omni_stream/auto_switcher.rb b/modules/atlona/omni_stream/auto_switcher.rb index c61d5fea..86dea8aa 100644 --- a/modules/atlona/omni_stream/auto_switcher.rb +++ b/modules/atlona/omni_stream/auto_switcher.rb @@ -44,15 +44,17 @@ def on_update def enabled(state) state = !!state + if state != @enabled self[:enabled] = @enabled = state define_setting(:auto_switch_enabled, @enabled) + end - if state - system[@virtual_switcher].clear_auto_switching - switch_all - end + if state + system[@virtual_switcher].clear_auto_switching + switch_all end + nil end diff --git a/modules/qsc/atlona_monitor.rb b/modules/qsc/atlona_monitor.rb index a878dcc3..1c280589 100644 --- a/modules/qsc/atlona_monitor.rb +++ b/modules/qsc/atlona_monitor.rb @@ -28,6 +28,17 @@ def on_update check_changes(routes) end + def unroute_audio + qsc = system[:Mixer] + logger.debug { "unrouting all audio" } + @stream_mappings.each_value do |details| + qsc.component_set(details[:component], { + Name: details[:control], + Value: "" + }) + end + end + # Update QSC with any stream changes def check_changes(routes) return unless routes From f7fa31527d498ad847adeb12688515b7e4985fd2 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 20 Apr 2020 22:21:15 +0800 Subject: [PATCH 1690/1752] feat(pressac/desk_logic): can ignore regex matched sensors --- modules/pressac/desk_management.rb | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/modules/pressac/desk_management.rb b/modules/pressac/desk_management.rb index 94c45c8f..575fb7a5 100644 --- a/modules/pressac/desk_management.rb +++ b/modules/pressac/desk_management.rb @@ -29,7 +29,13 @@ class ::Pressac::DeskManagement "delay_until_shown_as_busy": "5m", "delay_until_shown_as_free": "1h" } - ] + ], + ignore_sensors: [ + { + "name": "Example Meeting Rooms", + "regex_match": "^Example[0-9]$" + } + ] }) def on_load @@ -51,6 +57,7 @@ def on_update @zones = setting('zone_to_gateway_mappings') || {} @desk_ids = setting('sensor_name_to_desk_mappings') || {} @stale_status = setting('stale_shown_as')&.downcase&.to_sym || :blank + @ignore_sensors = setting('ignore_sensors')&.map { |d| d[:regex_match] } || [] @custom_delays = setting('custom_delays')&.map {|d| { regex_match: d[:regex_match], @@ -75,6 +82,7 @@ def on_update self[zone_id+':occupied_count'] = self[zone_id]&.count || 0 self[zone_id+':free_count'] = self[zone_id+':desk_count'] - self[zone_id+':occupied_count'] end + self[:ignored] = [] # Create a reverse lookup (gateway => zone) @which_zone = {} @@ -127,6 +135,11 @@ def update_desk(notification) desk = notification.value desk_name = id([desk[:name].to_sym])&.first + if ignore_sensor?(desk_name) + self[:ignored] << desk_name + return + end + zone = @which_zone[desk[:gateway].to_s] logger.debug "PRESSAC > DESK > LOGIC: Updating #{desk_name} in #{zone}" return unless zone @@ -144,17 +157,21 @@ def update_desk(notification) self[:pending_free] = @pending_free end - def delay_of(desk_id) + def delay_of(desk_name) @custom_delays.each do |setting| #regex = Regexp.new([:regex_match]) - if desk_id.match?(setting[:regex_match]) - logger.debug "PRESSAC > DESK > LOGIC: Regex MATCHED #{desk_id} to #{setting[:regex_match]}" + if desk_name.match?(setting[:regex_match]) + logger.debug "PRESSAC > DESK > LOGIC: Regex MATCHED #{desk_name} to #{setting[:regex_match]}" return {busy: setting[:busy_delay], free: setting[:free_delay]} end end return {busy: @default_busy_delay, free: @default_free_delay} end + def ignore_sensor?(desk_name) + @ignore_sensors.any? { |regex| desk_name.match?(regex) } + end + def determine_desk_status persist_current_status new_status = {} From 806faad13ef532296d3afffa21970dce11b17c22 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 21 Apr 2020 23:20:25 +0800 Subject: [PATCH 1691/1752] feat(gallagher/rest): from downstream (tested OK) --- lib/gallagher/rest.rb | 149 ++++++++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 36 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 25ab3b8d..7ee2bbfe 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -1,6 +1,6 @@ -require 'savon' require 'active_support/time' - +require 'uv-rays' +require 'json' module Gallagher; end class Gallagher::Rest @@ -12,36 +12,52 @@ class Gallagher::Rest # @param api_key [String] The API key to be included in the headers of each request in order to authenticate requests. # @param unique_pdf_name [String] The name of the personal data field used to align cardholders to staff members. # @param default_division [String] The division to pass when creating cardholders. - def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: nil) + # @param default_card_type [String] The default card type to use when creating a new card. This will be in the form of a URL. + def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: nil, default_card_type: nil, default_access_group: nil, default_facility_code: nil, proxy: nil) # Initialize the http endpoint to make requests with our API key - @default_headers = { Authorization: "GGL-API-KEY #{api_key}" } + @default_headers = { + 'Authorization' => "GGL-API-KEY #{api_key}", + 'Content-Type' => 'application/json' + } + @default_access_group = default_access_group @default_division = default_division - @endpoint = UV::HttpEndpoint.new(domain) + @default_card_type = default_card_type + @default_facility_code = default_facility_code + options = {} + if proxy + proxy_uri = URI(proxy) + options[:proxy] = { host: proxy_uri.host, port: proxy_uri.port } + end + @endpoint = UV::HttpEndpoint.new(domain, options) # Grab the URLs to be used by the other methods (HATEOAS standard) # First define where we will find the URLs in the data endpoint's response data_endpoint = "/api" - pdfs_href = "features.personalDatafields.personalDatafields.href" - cardholders_href = "features.cardholders.cardholders.href" - access_groups_href = "features.accessGroups.accessGroups.href" - card_types_href = "features.cardTypes.assign.href" + + @cardholders_endpoint = nil + @pdfs_endpoint = nil + @access_groups_endpoint = nil + @card_types_endpoint = nil + @fixed_pdf_id = nil # Get the main data endpoint to determine our new endpoints - response = @endpoint.get(path: data_endpoint, headers: @default_headers).value - @cardholders_endpoint = response[cardholders_href] - @pdfs_endpoint = response[pdfs_href] - @access_groups_endpoint = response[access_groups_href] - @card_types_endpoint = response[card_types_href] + response = JSON.parse(@endpoint.get(path: data_endpoint, headers: @default_headers).value.body) + + @cardholders_endpoint = response['features']['cardholders']['cardholders']['href'] + @pdfs_endpoint = response['features']['personalDataFields']['personalDataFields']['href'] + @access_groups_endpoint = response['features']['accessGroups']['accessGroups']['href'] + @card_types_endpoint = response['features']['cardTypes']['assign']['href'] + @events_endpoint = response['features']['events']['events']['href'] # Now get our cardholder PDF ID so we don't have to make the request over and over - pdf_response = @endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name }).value + pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name }).value.body) @fixed_pdf_id = pdf_response['results'][0]['id'] # There should only be one result end ## # Personal Data Fields (PDFs) are custom fields that Gallagher allows definintions of on a site-by-site basis. # They will usually be for things like email address, employee ID or some other field specific to whoever is hosting the Gallagher instance. - # This method allows retrieval of the PDFs used in the Gallagher instance, primarily so we can get the PDF's ID and use that to filter cardholders based on that PDF. + # Allows retrieval of the PDFs used in the Gallagher instance, primarily so we can get the PDF's ID and use that to filter cardholders based on that PDF. # # @param name [String] The name of the PDF which we want to retrieve. This will only return one result (as the PDF names are unique). # @return [Hash] A list of PDF results and a next link for pagination (we will generally have less than 100 PDFs so 'next' link will mostly be unused): @@ -67,15 +83,17 @@ def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: ni def get_pdfs(name: nil) # Add quotes around the value because da API bad name = "\"#{name}\"" if name - @endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: {name: name}.compact).value + JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: {name: name}.compact).value.body) end ## + # + # Retrieves cardholders and allows for filtering either based on the PDF provided (by name) at initalisation of the library or by some custom filter. # Carholders are essentially users in the Gallagher system. - # This method retrieves cardholders and allows for filtering either based on the PDF provided (by name) at initalisation of the library or by some custom filter. # For example, if the `unique_pdf_name` param passed in intialisation is `email` then passing `fixed_filter: 'some@email.com'` to this method will only return cardholders with that email. # If some other PDF is required for filtering, it can be used via the `custom_filter` param. # + # @param id [String] The ID of the cardholder to retrieve # @param fixed_filter [String] The value to be passed to the fixed PDF filter defined when this library is initialised. By default the PDF's name is `email`. # @param custom_filter [Hash] A PDF name and value to filter the cardholders by. For now this hash should only have one member. # @return [Hash] A list of cardholders and a next link for pagination (we will generally have less than 100 PDFs so 'next' link will mostly be unused): @@ -96,7 +114,10 @@ def get_pdfs(name: nil) # "href": "https://localhost:8904/api/cardholders?skip=61320" # } # } - def get_cardholder(fixed_filter: nil, custom_filter: nil) + def get_cardholder(id: nil, fixed_filter: nil, custom_filter: nil) + if id + return { 'results' => [JSON.parse(@endpoint.get(path: "#{@cardholders_endpoint}/#{id}", headers: @default_headers).value.body)] } + end query = {} # We can assume either fixed or custom filter may be used, but not both if fixed_filter @@ -106,7 +127,7 @@ def get_cardholder(fixed_filter: nil, custom_filter: nil) custom_pdf_id = self.get_pdfs(name: custom_filter.first[0].to_s) query["pdf_#{custom_pdf_id}"] = "\"#{custom_filter}\"" end - @endpoint.get(path: @cardholders_endpoint, headers: @default_headers, query: query).value + JSON.parse(@endpoint.get(path: @cardholders_endpoint, headers: @default_headers, query: query).value.body) end ## @@ -139,7 +160,7 @@ def get_cardholder(fixed_filter: nil, custom_filter: nil) # } # } def get_card_types - @endpoint.get(path: @card_types_endpoint, headers: @default_headers).value + JSON.parse(@endpoint.get(path: @card_types_endpoint, headers: @default_headers).value.body) end ## @@ -152,12 +173,16 @@ def get_card_types # @option options [Array] :access_groups An array of access groups to add this cardholder to. These may include `from` and `until` fields to dictate temporary access. # @option options [Array] :competencies An array of competencies to add this cardholder to. # @return [Hash] The cardholder that was created. - def create_cardholder(first_name:, last_name:, options: {}) + def create_cardholder(first_name:, last_name:, description: 'A cardholder', options: {}) default_options = { division: @default_division, pdfs: nil, cards: nil, - access_groups: nil, + access_groups: [{ + accessgroup: { + href: @default_access_group + } + }], competencies: nil } @@ -173,19 +198,29 @@ def create_cardholder(first_name:, last_name:, options: {}) create_params = { firstName: first_name, lastName: last_name, - shortName: "#{first_name} #{last_name}" + shortName: "#{first_name} #{last_name}", + # division: options[:division], + authorised: true, + description: description, + # cards: options[:cards] } # Add in our passed PDFs appending an '@' to the start of each pdf name options[:pdfs].each do |pdf_name, pdf_value| create_params["@#{pdf_name}".to_sym] = pdf_value - end + end if options[:pdfs] # Add in any passed options, converting the keys to camel case which Gallagher uses - create_params.merge(options.except(:pdfs).transform_keys{|k| k.to_s.camelize(:lower)}) + create_params.merge!(options.except(:pdfs).transform_keys{|k| k.to_s.camelize(:lower)}) # Create our cardholder and return the response - @endpoint.post(path: @cardholders_endpoint, headers: @default_headers, body: create_params).value + response = @endpoint.post(path: @cardholders_endpoint, headers: @default_headers, body: create_params.to_json).value + + if [200,201].include?(response.status) + return 201 + else + return response.body + end end ## @@ -215,8 +250,9 @@ def create_cardholder(first_name:, last_name:, options: {}) # @option options [String] :email An email to send a mobile credential invitation to. # @option options [Integer] :mobile A mobile number to associate a mobile credential with. # @return [Hash] The passed in card formatted for Gallagher. - def format_card(card_href:, options: {}) + def format_card(options: {}) default_options = { + card_href: @default_card_type, number: nil, from: nil, until: nil, @@ -227,14 +263,16 @@ def format_card(card_href:, options: {}) # Merge in our default options with those passed in options = options.reverse_merge(default_options) + # Create our card format formatted_card = { type: { - href: card_href + href: @default_card_type } } + formatted_card[:number] = options[:number] if options[:number] - formatted_card[:from] = Time.at(options[:from]).utc.iso8601 if options[:from] - formatted_card[:until] = Time.at(options[:until]).utc.iso8601 if options[:until] + formatted_card[:from] = Time.at(options[:from].to_i).utc.iso8601 if options[:from] + formatted_card[:until] = Time.at(options[:until].to_i).utc.iso8601 if options[:until] formatted_card[:invitation] = { email: options[:email], mobile: options[:mobile], @@ -244,7 +282,7 @@ def format_card(card_href:, options: {}) end ## - # This method updates an existing cardholder to add new cards, access groups or competencies. + # Updates an existing cardholder to add new cards, access groups or competencies. # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. # For now the `from` and `until` params will apply to all fields in the update. # @@ -258,7 +296,7 @@ def add_cardholder_access(cardholder_href:, options: {}) end ## - # This method updates an existing cardholder to update new cards, access groups or competencies. + # Updates an existing cardholder to update new cards, access groups or competencies. # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. # For now the `from` and `until` params will apply to all fields in the update. # @@ -272,7 +310,7 @@ def update_cardholder_access(cardholder_href:, options: {}) end ## - # This method updates an existing cardholder to remove new cards, access groups or competencies. + # Updates an existing cardholder to remove new cards, access groups or competencies. # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. # For now the `from` and `until` params will apply to all fields in the update. # @@ -285,6 +323,40 @@ def remove_cardholder_access(cardholder_href:, options: {}) self.update_cardholder(type: :remove, cardholder_href: cardholder_href, options: options) end + ## + # Checks whether a cardholder exists based on an email passed in. + # + # @param email [String] The email to check whether an existing cardholder is associated with. + # @return [Boolean] Whether the cardholder exists. + def cardholder_exists?(email:) + results = self.get_cardholder(fixed_filter: email)['results'] + results.empty? + end + + + ## + # Retrieves events from Gallagher + # + # @param sources [Array] An array of source IDs as strings + # @param groups [Array] An array of group IDs as strings + # @param types [Array] An array of type IDs as strings + def get_events(sources:[], groups:[], types:[], after: nil) + # Convert params to arrays to allow passing of single IDs as strings + sources = Array(sources) + groups = Array(groups) + types = Array(types) + + events_query = { + source: sources.join(","), + group: groups.join(","), + type: types.join(",") + } + + events_query[:after] = after if after + # Create our cardholder and return the response + JSON.parse(response = @endpoint.get(path: @events_endpoint, headers: @default_headers, query: events_query).value.body)['events'] + end + protected def update_cardholder(type:, cardholder_href:, options: {}) @@ -306,7 +378,12 @@ def update_cardholder(type:, cardholder_href:, options: {}) options.except(:from, :until).each do |param, value| patch_params[param.to_s.camelize(:lower)] = { type => value } if value end - - @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params).value + req = @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params.to_json) + response = req.value + if [200,201, 204].include?(response.status) + return 204 + else + return response.body + end end end \ No newline at end of file From fc247440cd0a793c0842fc713e4e47f84d34ce4d Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 5 May 2020 12:11:27 +1000 Subject: [PATCH 1692/1752] fix(atlona virtual switcher): ensure inputs are stored as strings mixing integers and strings results in weird behaviours when converting to JSON as keys override each other --- modules/atlona/omni_stream/virtual_switcher.rb | 9 +++++++-- modules/qsc/atlona_monitor.rb | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/atlona/omni_stream/virtual_switcher.rb b/modules/atlona/omni_stream/virtual_switcher.rb index f741f5ac..d90ad71e 100644 --- a/modules/atlona/omni_stream/virtual_switcher.rb +++ b/modules/atlona/omni_stream/virtual_switcher.rb @@ -40,6 +40,11 @@ def auto_switch def clear_auto_switching @auto_switch = {} + self[:routes] = {} + end + + def clear_routes + @routes = {} end def switch(map, switch_video: true, switch_audio: true, enable_override: nil, priority_auto_switch: true, **ignore) @@ -155,9 +160,9 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil, pr end end - Array(outs).each do |out| + Array(outs).map(&:to_s).each do |out| @routes[out] = inp - output, index = outputs[out.to_s] + output, index = outputs[out] if output.nil? logger.warn "output #{out} not found switching #{inp} => #{outs}" diff --git a/modules/qsc/atlona_monitor.rb b/modules/qsc/atlona_monitor.rb index 1c280589..aab124ab 100644 --- a/modules/qsc/atlona_monitor.rb +++ b/modules/qsc/atlona_monitor.rb @@ -44,7 +44,7 @@ def check_changes(routes) return unless routes check_keys = @stream_mappings.keys.map(&:to_s) & routes.keys.map(&:to_s) return if check_keys.empty? - check_keys = check_keys.map { |output| [output, (routes[output] || routes[output.to_i]).to_s] } + check_keys = check_keys.map { |output| [output, routes[output]] } # Get the decoder details mappings = system[:Switcher][:input_mappings] @@ -52,7 +52,7 @@ def check_changes(routes) # Obtain the current list of multicast addresses output_streams = {} check_keys.each do |(output, input)| - if input == "0" + if ["0", ""].include?(input) output_streams[output] = "" next end From 521dce29ff4d658256f26dc73179b9d6ec3b986a Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 22 Apr 2020 21:20:22 +1000 Subject: [PATCH 1693/1752] feat(gallagher): Add disable card method --- lib/gallagher/rest.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 7ee2bbfe..0beb53cb 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -333,6 +333,29 @@ def cardholder_exists?(email:) results.empty? end + ## + # Disable a card for a certain cardholder + def disable_card(cardholder_href:, card_href:) + patch_params = { + authorised: true, + cards: { + update: [{ + href: card_href, + status: { + value: @default_disabled_state || "Disabled (manually)" + } + }] + } + } + req = @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params.to_json) + response = req.value + if [200,201, 204].include?(response.status) + return 204 + else + return response.body + end + end + ## # Retrieves events from Gallagher From 26be97c92a07039d59db406a6a2043668fa32cc4 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 8 May 2020 14:33:52 +0800 Subject: [PATCH 1694/1752] feat(gallagher/rest): support PDF lookup in api < v8.10 --- lib/gallagher/rest.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 0beb53cb..258f0f63 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -43,14 +43,20 @@ def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: ni # Get the main data endpoint to determine our new endpoints response = JSON.parse(@endpoint.get(path: data_endpoint, headers: @default_headers).value.body) + @api_version = response['version'].to_f @cardholders_endpoint = response['features']['cardholders']['cardholders']['href'] - @pdfs_endpoint = response['features']['personalDataFields']['personalDataFields']['href'] @access_groups_endpoint = response['features']['accessGroups']['accessGroups']['href'] @card_types_endpoint = response['features']['cardTypes']['assign']['href'] @events_endpoint = response['features']['events']['events']['href'] # Now get our cardholder PDF ID so we don't have to make the request over and over - pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name }).value.body) + if @api_version >= 8.10 + @pdfs_endpoint = response['features']['personalDataFields']['personalDataFields']['href'] + pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name }).value.body) + else + @pdfs_endpoint = response['features']['item']['item']['href'] + pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name, type: 33 }).value.body) + end @fixed_pdf_id = pdf_response['results'][0]['id'] # There should only be one result end From 14d6e4138d1d0d6431747a69a309ec9c2647d2b1 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 8 May 2020 19:29:29 +0800 Subject: [PATCH 1695/1752] fix(gallagher/rest): typo in PDF lookup --- lib/gallagher/rest.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 258f0f63..6e1d404a 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -54,7 +54,7 @@ def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: ni @pdfs_endpoint = response['features']['personalDataFields']['personalDataFields']['href'] pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name }).value.body) else - @pdfs_endpoint = response['features']['item']['item']['href'] + @pdfs_endpoint = response['features']['items']['items']['href'] pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name, type: 33 }).value.body) end @fixed_pdf_id = pdf_response['results'][0]['id'] # There should only be one result From 911317a5364058ac94e77ecd31641927417a9e01 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 11 May 2020 16:25:18 +0800 Subject: [PATCH 1696/1752] fix(gallagher/rest): parse api version --- lib/gallagher/rest.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 6e1d404a..a6d8d140 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -43,14 +43,14 @@ def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: ni # Get the main data endpoint to determine our new endpoints response = JSON.parse(@endpoint.get(path: data_endpoint, headers: @default_headers).value.body) - @api_version = response['version'].to_f + @api_version = Gem::Version.new(response['version']) @cardholders_endpoint = response['features']['cardholders']['cardholders']['href'] @access_groups_endpoint = response['features']['accessGroups']['accessGroups']['href'] @card_types_endpoint = response['features']['cardTypes']['assign']['href'] @events_endpoint = response['features']['events']['events']['href'] # Now get our cardholder PDF ID so we don't have to make the request over and over - if @api_version >= 8.10 + if @api_version >= Gem::Version.new('8.10') @pdfs_endpoint = response['features']['personalDataFields']['personalDataFields']['href'] pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name }).value.body) else From 11e0710e7b32289dc2d2df06086ec740185599c4 Mon Sep 17 00:00:00 2001 From: William Le Date: Mon, 11 May 2020 17:58:31 +0800 Subject: [PATCH 1697/1752] feat(gallagher/rest): api 8.0 has different cardTypes endpoint --- lib/gallagher/rest.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index a6d8d140..d543f200 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -46,14 +46,15 @@ def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: ni @api_version = Gem::Version.new(response['version']) @cardholders_endpoint = response['features']['cardholders']['cardholders']['href'] @access_groups_endpoint = response['features']['accessGroups']['accessGroups']['href'] - @card_types_endpoint = response['features']['cardTypes']['assign']['href'] @events_endpoint = response['features']['events']['events']['href'] # Now get our cardholder PDF ID so we don't have to make the request over and over if @api_version >= Gem::Version.new('8.10') + @card_types_endpoint = response['features']['cardTypes']['assign']['href'] @pdfs_endpoint = response['features']['personalDataFields']['personalDataFields']['href'] pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name }).value.body) else + @card_types_endpoint = response['features']['cardTypes']['cardTypes']['href'] @pdfs_endpoint = response['features']['items']['items']['href'] pdf_response = JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: { name: unique_pdf_name, type: 33 }).value.body) end From d202e442f05027d4b188d2d5fdeef66aca85f565 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 20 May 2020 13:14:18 +1000 Subject: [PATCH 1698/1752] feat: various fixes for deakin and LendLease --- modules/atlona/omni_stream/auto_switcher.rb | 3 +- .../atlona/omni_stream/virtual_switcher.rb | 2 +- modules/gantner/relaxx/protocol_json.rb | 8 +- modules/office_rnd/api.rb | 10 +- modules/office_rnd/bookings.rb | 24 ++- modules/office_rnd/meeting_monitor.rb | 146 ++++++++++++++++++ 6 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 modules/office_rnd/meeting_monitor.rb diff --git a/modules/atlona/omni_stream/auto_switcher.rb b/modules/atlona/omni_stream/auto_switcher.rb index 86dea8aa..f57d9e3d 100644 --- a/modules/atlona/omni_stream/auto_switcher.rb +++ b/modules/atlona/omni_stream/auto_switcher.rb @@ -71,7 +71,8 @@ def poll_inputs input_details = input_mappings[input] next unless input_details - encoders << input_details[:encoder] + encoder = input_details[:encoder] + encoders << encoder if encoder end end diff --git a/modules/atlona/omni_stream/virtual_switcher.rb b/modules/atlona/omni_stream/virtual_switcher.rb index d90ad71e..d0258599 100644 --- a/modules/atlona/omni_stream/virtual_switcher.rb +++ b/modules/atlona/omni_stream/virtual_switcher.rb @@ -161,7 +161,7 @@ def switch(map, switch_video: true, switch_audio: true, enable_override: nil, pr end Array(outs).map(&:to_s).each do |out| - @routes[out] = inp + @routes[out] = inp.to_s output, index = outputs[out] if output.nil? diff --git a/modules/gantner/relaxx/protocol_json.rb b/modules/gantner/relaxx/protocol_json.rb index 4878b1ef..be873ff2 100644 --- a/modules/gantner/relaxx/protocol_json.rb +++ b/modules/gantner/relaxx/protocol_json.rb @@ -24,6 +24,9 @@ def on_load @password = "GAT" @locker_ids = Set.new @lockers_in_use = Set.new + + config(size_limit: 10.megabytes) + on_update end @@ -182,12 +185,13 @@ def login(authentication_string) decipher.iv = "#{password}#{"\x00" * (16 - password.bytesize)}" plain = decipher.update(Base64.decode64(authentication_string)) + decipher.final - decrypted = plain.force_encoding(Encoding::UTF_16LE) + decrypted = plain.force_encoding(Encoding::UTF_16LE).encode(Encoding::UTF_8) send_frame({ Caption: "AuthenticationRequestB", Id: new_request_id, - AuthenticationString: decrypted + # lol now you're just taking the piss + AuthenticationString: decrypted.to_i }, priority: 9999) end diff --git a/modules/office_rnd/api.rb b/modules/office_rnd/api.rb index 6f6406ad..d9288fb5 100644 --- a/modules/office_rnd/api.rb +++ b/modules/office_rnd/api.rb @@ -349,7 +349,15 @@ def meeting_rooms( resources("MeetingRoom", office_id, available_from, available_to) end - def desks( + def day_desks( + available_from = nil, # Time + available_to = nil, + office_id: nil + ) + resources("DedicatedDesks", office_id, available_from, available_to) + end + + def hot_desks( available_from = nil, # Time available_to = nil, office_id: nil diff --git a/modules/office_rnd/bookings.rb b/modules/office_rnd/bookings.rb index 29890a21..acb64f7b 100644 --- a/modules/office_rnd/bookings.rb +++ b/modules/office_rnd/bookings.rb @@ -73,13 +73,23 @@ def fetch_bookings officernd = system[:OfficeRnD] officernd.resource_bookings(@resource_id).then do |bookings| self[:today] = bookings.map do |booking| - staff_details = officernd.staff_details(booking[:member]).value + + staff_details = if booking[:member] + officernd.staff_details(booking[:member]).value + else + { + name: "Unknown", + email: "unknown@admin.com" + } + end + { id: booking[:bookingId], Start: booking[:start][:dateTime], End: booking[:end][:dateTime], Subject: booking[:summary], owner: staff_details[:name], + owner_email: staff_details[:email], setup: 0, breakdown: 0, start_epoch: Time.parse(booking[:start][:dateTime]).to_i, @@ -93,16 +103,22 @@ def fetch_bookings def check_room_usage now = Time.now.to_i - current_booking = false + current_booking = nil bookings = self[:today] || [] bookings.each do |booking| if now < booking[:end_epoch] && now > booking[:start_epoch] - current_booking = true + current_booking = booking break end end - self[:room_in_use] = current_booking + if current_booking + self[:owner_email] = current_booking[:owner_email] + self[:room_in_use] = true + else + self[:owner_email] = nil + self[:room_in_use] = false + end end end diff --git a/modules/office_rnd/meeting_monitor.rb b/modules/office_rnd/meeting_monitor.rb new file mode 100644 index 00000000..531e3b40 --- /dev/null +++ b/modules/office_rnd/meeting_monitor.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true +# encoding: ASCII-8BIT + +module OfficeRnd; end + +class OfficeRnd::DeskBookingMonitor + include ::Orchestrator::Constants + + descriptive_name 'OfficeRnD Desk Monitor' + generic_name :DeskMonitor + implements :logic + + def on_load + @updates_pushed = 0 + # id => details + @staff_details = {} + + # desk_id => "booked-by" + @booking_state = {} + on_update + end + + def on_update + @customer = (setting(:customer) || "aca") + @source = (setting(:source) || "OfficeRnD").downcase + + sys = system + @module_id = ::Orchestrator::System.get(sys.id).get(:DeskMonitor, 1).settings.id + zones = ::Orchestrator::ControlSystem.find(sys.id).zone_data + @org_id = zones.select { |zone| zone.tags.include?("org") }.first&.id + @bld_id = zones.select { |zone| zone.tags.include?("building") }.first&.id + + schedule.clear + schedule.every("5m") { check_bookings } + end + + def staff_info(member_id) + return "unknown@admin.com" unless member_id + + details = @staff_details[member_id] + return details if details + + officernd = system[:OfficeRnD] + info = officernd.staff_details(member_id).value + @staff_details[member_id] = info[:email] + info + end + + def check_bookings + officernd = system[:OfficeRnD] + + officernd.resources("day_desk").then { |desks| + logger.debug { "found #{desks.length} desks" } + desks.select { |desk| + # Filter manly desks + desk[:office] == "5caada47b4621d022c8ebe1d" + }.map { |desk| + # Update the data + { + id: desk[:_id], + name: "day_desk_#{desk[:number]}" + } + } + }.then { |desks| + # Find each desks availability + logger.debug { "getting bookings for #{desks.length} desks" } + promises = desks.map do |desk| + officernd.resource_bookings(desk[:id]).then { |bookings| + desk[:bookings] = bookings.map { |booking| + { + start_epoch: Time.parse(booking[:start][:dateTime]).to_i, + end_epoch: Time.parse(booking[:end][:dateTime]).to_i, + email: staff_info(booking[:member]) + } + } + }.value + end + # Wait for resolution - don't want to hit rate limits + # promises.each(&:value) + desks + }.then do |desks| + logger.debug { "Checking usage of #{desks.length} desks" } + now = Time.now.to_i + writer = system[:S3Writer] + desks.each { |desk| check_room_usage(writer, now, desk) } + end + end + + protected + + def check_room_usage(writer, now, desk) + current_booking = nil + + desk[:bookings].each do |booking| + if now < booking[:end_epoch] && now > booking[:start_epoch] + current_booking = booking[:email] + break + end + end + state = current_booking || false + name = desk[:name] + + if @booking_state[name] != state + @booking_state[name] = state + push_changes(writer, name, !!state, state) + end + end + + def push_changes(writer, desk_name, in_use, owner_email) + write_booked(writer, desk_name, @org_id, @bld_id, nil, @module_id, in_use, owner_email) + @updates_pushed += 1 + self[:updates_pushed] = @updates_pushed + end + + def write_booked(place, desk_name, org_id, bld_id, lvl_id, module_id, desk_in_use, owner_email) + in_use = desk_in_use ? 1 : 0 + + logger.debug do + writing = { + evt: "booked", + org: org_id, + bld: bld_id, + lvl: lvl_id, + loc: desk_name, + src: @source, + mod: module_id, + val: in_use, + ref: owner_email + } + "writing #{@customer}\n#{writing}" + end + + place.ingest( + @customer, + evt: "booked", + org: org_id, + bld: bld_id, + lvl: lvl_id, + loc: desk_name, + src: @source, + mod: module_id, + val: in_use, + ref: owner_email + ) + end +end From 6f3c5c89617031fe65117717bb7f69fac46b8221 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 22 May 2020 14:55:27 +0800 Subject: [PATCH 1699/1752] feat(gallagher): add get_cardtype_max_number() --- lib/gallagher/rest.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index d543f200..1b01814d 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -170,6 +170,11 @@ def get_card_types JSON.parse(@endpoint.get(path: @card_types_endpoint, headers: @default_headers).value.body) end + def get_cardtype_max_number() + response = JSON.parse(@endpoint.get(path: @card_types_endpoint, query: {fields: 'maximumNumber'}, headers: @default_headers).value.body) + response['maximumNumber']&.to_i + end + ## # Create a new cardholder. # @param first_name [String] The first name of the new cardholder. Either this or last name is required (but we should assume both are for most instances). From 92537cd85808cfc981cae8b42b214e76585a5860 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 22 May 2020 14:56:28 +0800 Subject: [PATCH 1700/1752] feat(gallagher): card assignment raises custom errors --- lib/gallagher/rest.rb | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 1b01814d..5797db5a 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -415,10 +415,32 @@ def update_cardholder(type:, cardholder_href:, options: {}) end req = @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params.to_json) response = req.value - if [200,201, 204].include?(response.status) - return 204 + process_response({response.status => response.body}) + end + + def process_response(response) + case response.status + when 200..206 + return result + when 400 + case response.body.message + when "Another cardholder already has a card number 2 with the same facility code." + raise CardNumberInUse.new(response.body) + when "Card number is out of range for this Card Type." + raise CardNumberOutOfRange.new(response.body) + end + when 409 + raise Conflict.new else - return response.body + puts "\nERROR > Gallagher response is #{response.status}: #{response.body}\n" + raise StandardError.new(response.body) end - end + + end + + class ErrorAccessDenied < StandardError; end + class InvalidAuthenticationToken < StandardError; end + class Conflict < StandardError; end + class CardNumberOutOfRange < StandardError; end + class CardNumberInUse < StandardError; end end \ No newline at end of file From 81d8fa3f8a66b813a5a24ad41e5196eacf0f2f01 Mon Sep 17 00:00:00 2001 From: William Le Date: Fri, 22 May 2020 20:02:17 +0800 Subject: [PATCH 1701/1752] fix(gallagher): syntax in new random cardnumbers feature --- lib/gallagher/rest.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 5797db5a..8a8f0314 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -171,7 +171,7 @@ def get_card_types end def get_cardtype_max_number() - response = JSON.parse(@endpoint.get(path: @card_types_endpoint, query: {fields: 'maximumNumber'}, headers: @default_headers).value.body) + response = JSON.parse(@endpoint.get(path: @default_card_type, query: {fields: 'maximumNumber'}, headers: @default_headers).value.body) response['maximumNumber']&.to_i end @@ -414,16 +414,15 @@ def update_cardholder(type:, cardholder_href:, options: {}) patch_params[param.to_s.camelize(:lower)] = { type => value } if value end req = @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params.to_json) - response = req.value - process_response({response.status => response.body}) + process_response(req.value) end def process_response(response) - case response.status + case response.code when 200..206 - return result + return response when 400 - case response.body.message + case response.body[:message] when "Another cardholder already has a card number 2 with the same facility code." raise CardNumberInUse.new(response.body) when "Card number is out of range for this Card Type." From e46990c07884ff575af629545abd09852cbe433c Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 24 May 2020 00:03:48 +0800 Subject: [PATCH 1702/1752] fix(gallagher): syntax; compare error codes instead of msg --- lib/gallagher/rest.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 8a8f0314..68832209 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -418,14 +418,14 @@ def update_cardholder(type:, cardholder_href:, options: {}) end def process_response(response) - case response.code + case response.status when 200..206 return response when 400 - case response.body[:message] - when "Another cardholder already has a card number 2 with the same facility code." + case response.body['code'] + when -1056964457 # "Another cardholder already has a card number 2 with the same facility code." raise CardNumberInUse.new(response.body) - when "Card number is out of range for this Card Type." + when -1056964272 # "Card number is out of range for this Card Type." raise CardNumberOutOfRange.new(response.body) end when 409 From 81a13cd906b0401dd673ba5ae79027e4ba2b0794 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 24 May 2020 15:18:02 +0800 Subject: [PATCH 1703/1752] feat(gallagher): add delete_card() --- lib/gallagher/rest.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 68832209..a71ab169 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -368,6 +368,12 @@ def disable_card(cardholder_href:, card_href:) end end + # Delete a specific card, given it's href + def delete_card(card_href:) + req = @endpoint.delete(path: card_href, headers: @default_headers) + process_response(req.value) + end + ## # Retrieves events from Gallagher @@ -428,18 +434,20 @@ def process_response(response) when -1056964272 # "Card number is out of range for this Card Type." raise CardNumberOutOfRange.new(response.body) end + when 404 + raise NotFound.new when 409 raise Conflict.new else puts "\nERROR > Gallagher response is #{response.status}: #{response.body}\n" raise StandardError.new(response.body) end - end class ErrorAccessDenied < StandardError; end class InvalidAuthenticationToken < StandardError; end - class Conflict < StandardError; end + class CardNumberInUse < StandardError; end + class NotFound < StandardError; end class CardNumberOutOfRange < StandardError; end class CardNumberInUse < StandardError; end end \ No newline at end of file From a613a0b4cc76ad4aaaf62b1b635e721300adc1d2 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 24 May 2020 18:09:45 +0800 Subject: [PATCH 1704/1752] feat(gallagher): create_cardholder() raises errors --- lib/gallagher/rest.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index a71ab169..ac3f6980 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -227,12 +227,7 @@ def create_cardholder(first_name:, last_name:, description: 'A cardholder', opti # Create our cardholder and return the response response = @endpoint.post(path: @cardholders_endpoint, headers: @default_headers, body: create_params.to_json).value - - if [200,201].include?(response.status) - return 201 - else - return response.body - end + process_response(response) end ## From 2c71d0c03ff341da9194fd27d5647b3e9f1936e0 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 24 May 2020 19:57:58 +0800 Subject: [PATCH 1705/1752] feat(gallagher): POSTs return location of new object --- lib/gallagher/rest.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index ac3f6980..47f24c73 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -355,12 +355,7 @@ def disable_card(cardholder_href:, card_href:) } } req = @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params.to_json) - response = req.value - if [200,201, 204].include?(response.status) - return 204 - else - return response.body - end + process_response(req.value) end # Delete a specific card, given it's href @@ -420,8 +415,10 @@ def update_cardholder(type:, cardholder_href:, options: {}) def process_response(response) case response.status + when 201 + return response.headers['Location'] # URI of newly created object will be in Location header. Annoyingly, body is blank when 200..206 - return response + return response.body when 400 case response.body['code'] when -1056964457 # "Another cardholder already has a card number 2 with the same facility code." @@ -441,7 +438,7 @@ def process_response(response) class ErrorAccessDenied < StandardError; end class InvalidAuthenticationToken < StandardError; end - class CardNumberInUse < StandardError; end + class Conflict < StandardError; end class NotFound < StandardError; end class CardNumberOutOfRange < StandardError; end class CardNumberInUse < StandardError; end From 9cee0b1c20c19de6ae5dad8cd876c618af78d64f Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 24 May 2020 22:02:54 +0800 Subject: [PATCH 1706/1752] fix(gallagher): POST response header syntax --- lib/gallagher/rest.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 47f24c73..9570c881 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -416,7 +416,8 @@ def update_cardholder(type:, cardholder_href:, options: {}) def process_response(response) case response.status when 201 - return response.headers['Location'] # URI of newly created object will be in Location header. Annoyingly, body is blank + puts "INFO > Gallagher CREATED #{response.status}: #{response['Location']}" + return response['Location'] # URI of newly created object will be in Location header. Annoyingly, body is blank when 200..206 return response.body when 400 @@ -431,7 +432,7 @@ def process_response(response) when 409 raise Conflict.new else - puts "\nERROR > Gallagher response is #{response.status}: #{response.body}\n" + puts "ERROR > Gallagher returns #{response.status}: #{response.body}" raise StandardError.new(response.body) end end From 362eb47a1b4d86b65990e47bcc9e3ae200fa42b4 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Mon, 25 May 2020 14:16:45 +1000 Subject: [PATCH 1707/1752] (ismart:camera) added discrete ismart driver --- modules/ismart/ismart_ptz.rb | 371 +++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 modules/ismart/ismart_ptz.rb diff --git a/modules/ismart/ismart_ptz.rb b/modules/ismart/ismart_ptz.rb new file mode 100644 index 00000000..2be27b86 --- /dev/null +++ b/modules/ismart/ismart_ptz.rb @@ -0,0 +1,371 @@ +# encoding: ASCII-8BIT + +module iSmart; end +module iSmart::Camera; end + +# Documentation: https://aca.im/driver_docs/Sony/EVI-H100V-S-Tech-Manual.pdf + +class iSmart::Camera::Visca + include ::Orchestrator::Constants # these provide optional helper methods + include ::Orchestrator::Transcoder # (not used in this module) + + + # Discovery Information + tcp_port 4999 # Need to go through an RS232 gatway + descriptive_name 'iSmart VISCA PTZ Camera' + generic_name :Camera + + # Communication settings + tokenize delimiter: "\xFF" + delay between_sends: 150 + + + def on_load + # Constants that are made available to interfaces + self[:pan_speed_max] = 0x18 + self[:pan_speed_min] = 1 + self[:tilt_speed_max] = 0x17 + self[:tilt_speed_min] = 1 + + self[:joy_left] = -0x14 + self[:joy_right] = 0x14 + self[:joy_center] = 0 + + # NOTE:: Don't think this actually correct. + # As we probably need to use 2s compliment to use this... + self[:pan_max] = 0xE1E5 # Right + self[:pan_min] = 0x1E1B # Left + self[:pan_center] = 0 + self[:tilt_max] = 0xF010 # UP + self[:tilt_min] = 0x038B # Down + self[:tilt_center] = 0 + + self[:zoom_max] = 0x4000 + self[:zoom_min] = 0 + + on_update + end + + def on_unload + end + + def on_update + timeout = setting(:timeout) || 5000 + defaults timeout: timeout + + @presets = setting(:presets) || {} + self[:presets] = @presets.keys + self[:invert] = setting(:invert) + end + + def connected + schedule.every('60s') do + logger.debug "-- Polling Sony Camera" + power? do + if self[:power] == On + zoom? + pantilt? + autofocus? + end + end + end + end + + def disconnected + # Disconnected will be called before connect if initial connect fails + schedule.clear + end + + + def power(state) + target = is_affirmative?(state) + + # Execute command + if target == On && self[:power] == Off + send_cmd "\x04\x00\x02", name: :power, delay: 15000 + elsif target == Off && self[:power] == On + send_cmd "\x04\x00\x03", name: :power, delay: 15000 + end + + # ensure the comman ran successfully + self[:power_target] = target + power? + set_autofocus + end + + def home + send_cmd "\x06\x04", name: :home + end + + def reset + send_cmd "\x06\x05", name: :reset + end + + def power?(options = {}, &block) + options[:emit] = block if block_given? + options[:inq] = :power + send_inq "\x04\x00", options + end + + def zoom? + send_inq "\x04\x47", priority: 0, inq: :zoom + end + + def autofocus? + send_inq "\x04\x38", priority: 0, inq: :autofocus + end + + def exposure? + send_inq "\x04\x39", priority: 0, inq: :exposure + end + + def pantilt? + send_inq "\x06\x12", priority: 0, inq: :pantilt + end + + + # Absolute position + def pantilt(pan, tilt) + pan = in_range(pan.to_i, self[:pan_max], self[:pan_min]) + tilt = in_range(tilt.to_i, self[:tilt_max], self[:tilt_min]) + + cmd = [0x06, 0x02, (self[:pan_speed_max] * 0.7).to_i, (self[:tilt_speed_max] * 0.7).to_i].pack('C*') + + # Format the pan tilt value as required + val = pan.to_s(16).rjust(4, '0') + val << tilt.to_s(16).rjust(4, '0') + value = '' + val.each_char do |char| + value << '0' + value << char + end + cmd << hex_to_byte(value) + + send_cmd cmd, name: :position + end + + def joystick(pan_speed, tilt_speed) + left_max = self[:joy_left] + right_max = self[:joy_right] + + pan_speed = in_range(pan_speed.to_i, right_max, left_max) + tilt_speed = in_range(tilt_speed.to_i, right_max, left_max) + + is_centered = false + if pan_speed == 0 && tilt_speed == 0 + is_centered = true + end + + options = {} + options[:name] = :joystick + + cmd = "\x06\x01" + + if is_centered + options[:priority] = 99 + options[:retries] = 5 + cmd << "\x01\x01\x03\x03" + + # Request the current position once the stop command + # has run, we are clearing the queue so we use promises to + # ensure the pantilt command is executed + send_cmd(cmd, options).then do + pantilt? + end + else + options[:retries] = 0 + + # Calculate direction + dir_hori = nil + if pan_speed > 0 + dir_hori = :right + elsif pan_speed < 0 + dir_hori = :left + end + + dir_vert = nil + if tilt_speed > 0 + dir_vert = :down + elsif tilt_speed < 0 + dir_vert = :up + end + + # Add the absolute speed + pan_speed = pan_speed * -1 if pan_speed < 0 + tilt_speed = tilt_speed * -1 if tilt_speed < 0 + cmd << pan_speed + cmd << tilt_speed + + # Provide the direction information + cmd << __send__("stick_#{dir_vert}#{dir_hori}".to_sym) + send_cmd cmd, options + end + end + + def zoom(position, focus = -1) + val = in_range(position.to_i, self[:zoom_max], self[:zoom_min]) + + cmd = "\x04\x47" + + # Format the zoom focus values as required + val = position.to_s(16).rjust(4, '0') + val << focus.to_s(16).rjust(4, '0') if focus >= 0 + value = '' + val.each_char do |char| + value << '0' + value << char + end + cmd << hex_to_byte(value) + + self[:zoom] = position + + send_cmd cmd, name: :zoom + end + + def adjust_tilt(direction) + speed = 0 + if direction == 'down' + speed = self[:invert] ? -0x10 : 0x10 + elsif direction == 'up' + speed = self[:invert] ? 0x10 : -0x10 + end + + joystick(0, speed) + end + + def adjust_pan(direction) + speed = 0 + if direction == 'right' + speed = 0x10 + elsif direction == 'left' + speed = -0x10 + end + + joystick(speed, 0) + end + + # Set autofocus ON + def set_autofocus + send_cmd "\x04\x38\x02", name: :set_autofocus + end + + Exposure = { + auto: "\x00", + manual: "\x03", + shutter: "\x0A", + iris: "\x0B" + } + Exposure.merge!(Exposure.invert) + def set_exposure(mode = :full_auto) + send_cmd "\x04\x39#{Exposure[:mode]}", name: :set_exposure + end + + # Recall a preset from the database + def preset(name) + name_sym = name.to_sym + values = @presets[name_sym] + if values + pantilt(values[:pan], values[:tilt]) + zoom(values[:zoom]) + true + elsif name_sym == :default + home + else + false + end + end + + # Recall a preset from the camera + def recall_position(number) + number = in_range(number, 5) + cmd = "\x04\x3f\x02" + cmd << number + send_cmd cmd, name: :recall_position + end + + def save_position(number) + number = in_range(number, 5) + cmd = "\x04\x3f\x01" + cmd << number + # No name as we might want to queue this + send_cmd cmd + end + + + protected + + + # Joystick command type + def stick_up; "\x03\x01"; end + def stick_down; "\x03\x02"; end + def stick_left; "\x01\x03"; end + def stick_right; "\x02\x03"; end + def stick_upleft; "\x01\x01"; end + def stick_upright; "\x02\x01"; end + def stick_downleft; "\x01\x02"; end + def stick_downright; "\x02\x02"; end + + + def send_cmd(cmd, options = {}) + req = "\x81\x01#{cmd}\xff" + logger.debug { "tell -- 0x#{byte_to_hex(req)} -- #{options[:name]}" } + send req, options + end + + def send_inq(inq, options = {}) + req = "\x81\x09#{inq}\xff" + logger.debug { "ask -- 0x#{byte_to_hex(req)} -- #{options[:inq]}" } + send req, options + end + + + RespComplete = "\x90\x51".freeze + RespIgnore = "\x90\x41".freeze + def received(data, resolve, command) + logger.debug { "Sony sent 0x#{byte_to_hex(data)}" } + + # Process command responses + if command && command[:inq].nil? + if data == RespComplete + return :success + else + return :ignore + end + end + + # This will probably not ever be true + return :success unless command && command[:inq] + return :ignore if data == RespIgnore + + # Process the response + bytes = str_to_array(data) + case command[:inq] + when :power + self[:power] = bytes[-1] == 2 + + if !self[:power_target].nil? && self[:power_target] != self[:power] + schedule.in 3000 do + power(self[:power_target]) if self[:power_target] != self[:power] + end + end + when :zoom + hex = byte_to_hex(data[2..-1]) + hex_new = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" + self[:zoom] = hex_new.to_i(16) + when :exposure + self[:exposure] = Exposure[data[-1]] + when :autofocus + self[:auto_focus] = bytes[-1] == 2 + when :pantilt + hex = byte_to_hex(data[2..5]) + pan_hex = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" + self[:pan] = pan_hex.to_i(16) + + hex = byte_to_hex(data[6..-1]) + tilt_hex = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" + self[:tilt] = tilt_hex.to_i(16) + end + + :success + end +end From eb159614adf7747d75bbf2b872c6443f4ad48452 Mon Sep 17 00:00:00 2001 From: Jeremy West Date: Mon, 25 May 2020 14:41:01 +1000 Subject: [PATCH 1708/1752] remove ismart driver --- modules/ismart/ismart_ptz.rb | 371 ----------------------------------- 1 file changed, 371 deletions(-) delete mode 100644 modules/ismart/ismart_ptz.rb diff --git a/modules/ismart/ismart_ptz.rb b/modules/ismart/ismart_ptz.rb deleted file mode 100644 index 2be27b86..00000000 --- a/modules/ismart/ismart_ptz.rb +++ /dev/null @@ -1,371 +0,0 @@ -# encoding: ASCII-8BIT - -module iSmart; end -module iSmart::Camera; end - -# Documentation: https://aca.im/driver_docs/Sony/EVI-H100V-S-Tech-Manual.pdf - -class iSmart::Camera::Visca - include ::Orchestrator::Constants # these provide optional helper methods - include ::Orchestrator::Transcoder # (not used in this module) - - - # Discovery Information - tcp_port 4999 # Need to go through an RS232 gatway - descriptive_name 'iSmart VISCA PTZ Camera' - generic_name :Camera - - # Communication settings - tokenize delimiter: "\xFF" - delay between_sends: 150 - - - def on_load - # Constants that are made available to interfaces - self[:pan_speed_max] = 0x18 - self[:pan_speed_min] = 1 - self[:tilt_speed_max] = 0x17 - self[:tilt_speed_min] = 1 - - self[:joy_left] = -0x14 - self[:joy_right] = 0x14 - self[:joy_center] = 0 - - # NOTE:: Don't think this actually correct. - # As we probably need to use 2s compliment to use this... - self[:pan_max] = 0xE1E5 # Right - self[:pan_min] = 0x1E1B # Left - self[:pan_center] = 0 - self[:tilt_max] = 0xF010 # UP - self[:tilt_min] = 0x038B # Down - self[:tilt_center] = 0 - - self[:zoom_max] = 0x4000 - self[:zoom_min] = 0 - - on_update - end - - def on_unload - end - - def on_update - timeout = setting(:timeout) || 5000 - defaults timeout: timeout - - @presets = setting(:presets) || {} - self[:presets] = @presets.keys - self[:invert] = setting(:invert) - end - - def connected - schedule.every('60s') do - logger.debug "-- Polling Sony Camera" - power? do - if self[:power] == On - zoom? - pantilt? - autofocus? - end - end - end - end - - def disconnected - # Disconnected will be called before connect if initial connect fails - schedule.clear - end - - - def power(state) - target = is_affirmative?(state) - - # Execute command - if target == On && self[:power] == Off - send_cmd "\x04\x00\x02", name: :power, delay: 15000 - elsif target == Off && self[:power] == On - send_cmd "\x04\x00\x03", name: :power, delay: 15000 - end - - # ensure the comman ran successfully - self[:power_target] = target - power? - set_autofocus - end - - def home - send_cmd "\x06\x04", name: :home - end - - def reset - send_cmd "\x06\x05", name: :reset - end - - def power?(options = {}, &block) - options[:emit] = block if block_given? - options[:inq] = :power - send_inq "\x04\x00", options - end - - def zoom? - send_inq "\x04\x47", priority: 0, inq: :zoom - end - - def autofocus? - send_inq "\x04\x38", priority: 0, inq: :autofocus - end - - def exposure? - send_inq "\x04\x39", priority: 0, inq: :exposure - end - - def pantilt? - send_inq "\x06\x12", priority: 0, inq: :pantilt - end - - - # Absolute position - def pantilt(pan, tilt) - pan = in_range(pan.to_i, self[:pan_max], self[:pan_min]) - tilt = in_range(tilt.to_i, self[:tilt_max], self[:tilt_min]) - - cmd = [0x06, 0x02, (self[:pan_speed_max] * 0.7).to_i, (self[:tilt_speed_max] * 0.7).to_i].pack('C*') - - # Format the pan tilt value as required - val = pan.to_s(16).rjust(4, '0') - val << tilt.to_s(16).rjust(4, '0') - value = '' - val.each_char do |char| - value << '0' - value << char - end - cmd << hex_to_byte(value) - - send_cmd cmd, name: :position - end - - def joystick(pan_speed, tilt_speed) - left_max = self[:joy_left] - right_max = self[:joy_right] - - pan_speed = in_range(pan_speed.to_i, right_max, left_max) - tilt_speed = in_range(tilt_speed.to_i, right_max, left_max) - - is_centered = false - if pan_speed == 0 && tilt_speed == 0 - is_centered = true - end - - options = {} - options[:name] = :joystick - - cmd = "\x06\x01" - - if is_centered - options[:priority] = 99 - options[:retries] = 5 - cmd << "\x01\x01\x03\x03" - - # Request the current position once the stop command - # has run, we are clearing the queue so we use promises to - # ensure the pantilt command is executed - send_cmd(cmd, options).then do - pantilt? - end - else - options[:retries] = 0 - - # Calculate direction - dir_hori = nil - if pan_speed > 0 - dir_hori = :right - elsif pan_speed < 0 - dir_hori = :left - end - - dir_vert = nil - if tilt_speed > 0 - dir_vert = :down - elsif tilt_speed < 0 - dir_vert = :up - end - - # Add the absolute speed - pan_speed = pan_speed * -1 if pan_speed < 0 - tilt_speed = tilt_speed * -1 if tilt_speed < 0 - cmd << pan_speed - cmd << tilt_speed - - # Provide the direction information - cmd << __send__("stick_#{dir_vert}#{dir_hori}".to_sym) - send_cmd cmd, options - end - end - - def zoom(position, focus = -1) - val = in_range(position.to_i, self[:zoom_max], self[:zoom_min]) - - cmd = "\x04\x47" - - # Format the zoom focus values as required - val = position.to_s(16).rjust(4, '0') - val << focus.to_s(16).rjust(4, '0') if focus >= 0 - value = '' - val.each_char do |char| - value << '0' - value << char - end - cmd << hex_to_byte(value) - - self[:zoom] = position - - send_cmd cmd, name: :zoom - end - - def adjust_tilt(direction) - speed = 0 - if direction == 'down' - speed = self[:invert] ? -0x10 : 0x10 - elsif direction == 'up' - speed = self[:invert] ? 0x10 : -0x10 - end - - joystick(0, speed) - end - - def adjust_pan(direction) - speed = 0 - if direction == 'right' - speed = 0x10 - elsif direction == 'left' - speed = -0x10 - end - - joystick(speed, 0) - end - - # Set autofocus ON - def set_autofocus - send_cmd "\x04\x38\x02", name: :set_autofocus - end - - Exposure = { - auto: "\x00", - manual: "\x03", - shutter: "\x0A", - iris: "\x0B" - } - Exposure.merge!(Exposure.invert) - def set_exposure(mode = :full_auto) - send_cmd "\x04\x39#{Exposure[:mode]}", name: :set_exposure - end - - # Recall a preset from the database - def preset(name) - name_sym = name.to_sym - values = @presets[name_sym] - if values - pantilt(values[:pan], values[:tilt]) - zoom(values[:zoom]) - true - elsif name_sym == :default - home - else - false - end - end - - # Recall a preset from the camera - def recall_position(number) - number = in_range(number, 5) - cmd = "\x04\x3f\x02" - cmd << number - send_cmd cmd, name: :recall_position - end - - def save_position(number) - number = in_range(number, 5) - cmd = "\x04\x3f\x01" - cmd << number - # No name as we might want to queue this - send_cmd cmd - end - - - protected - - - # Joystick command type - def stick_up; "\x03\x01"; end - def stick_down; "\x03\x02"; end - def stick_left; "\x01\x03"; end - def stick_right; "\x02\x03"; end - def stick_upleft; "\x01\x01"; end - def stick_upright; "\x02\x01"; end - def stick_downleft; "\x01\x02"; end - def stick_downright; "\x02\x02"; end - - - def send_cmd(cmd, options = {}) - req = "\x81\x01#{cmd}\xff" - logger.debug { "tell -- 0x#{byte_to_hex(req)} -- #{options[:name]}" } - send req, options - end - - def send_inq(inq, options = {}) - req = "\x81\x09#{inq}\xff" - logger.debug { "ask -- 0x#{byte_to_hex(req)} -- #{options[:inq]}" } - send req, options - end - - - RespComplete = "\x90\x51".freeze - RespIgnore = "\x90\x41".freeze - def received(data, resolve, command) - logger.debug { "Sony sent 0x#{byte_to_hex(data)}" } - - # Process command responses - if command && command[:inq].nil? - if data == RespComplete - return :success - else - return :ignore - end - end - - # This will probably not ever be true - return :success unless command && command[:inq] - return :ignore if data == RespIgnore - - # Process the response - bytes = str_to_array(data) - case command[:inq] - when :power - self[:power] = bytes[-1] == 2 - - if !self[:power_target].nil? && self[:power_target] != self[:power] - schedule.in 3000 do - power(self[:power_target]) if self[:power_target] != self[:power] - end - end - when :zoom - hex = byte_to_hex(data[2..-1]) - hex_new = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" - self[:zoom] = hex_new.to_i(16) - when :exposure - self[:exposure] = Exposure[data[-1]] - when :autofocus - self[:auto_focus] = bytes[-1] == 2 - when :pantilt - hex = byte_to_hex(data[2..5]) - pan_hex = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" - self[:pan] = pan_hex.to_i(16) - - hex = byte_to_hex(data[6..-1]) - tilt_hex = "#{hex[1]}#{hex[3]}#{hex[5]}#{hex[7]}" - self[:tilt] = tilt_hex.to_i(16) - end - - :success - end -end From 0e5f93e12c7c1a2c5ec95f275ea3191fa32a64a9 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 11 Jun 2020 12:50:17 +1000 Subject: [PATCH 1709/1752] fix(cisco camera): improve zoom feedback --- modules/cisco/tele_presence/sx_camera_common.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/cisco/tele_presence/sx_camera_common.rb b/modules/cisco/tele_presence/sx_camera_common.rb index a307adf2..81595ba9 100644 --- a/modules/cisco/tele_presence/sx_camera_common.rb +++ b/modules/cisco/tele_presence/sx_camera_common.rb @@ -31,7 +31,8 @@ def on_load def on_update @presets = setting(:presets) || {} self[:presets] = @presets.keys - self[:zoom_max] = 11800 + self[:zoom_max] = setting(:zoom_max) || 11800 + self[:zoom_min] = setting(:zoom_min) || 0 @index = setting(:camera_index) || 1 self[:camera_index] = @index @@ -325,7 +326,15 @@ def received(data, resolve, command) case type when :position # Tilt: Pan: Zoom: etc so we massage to our desired status variables - self[result[5].downcase.gsub(':', '').to_sym] = result[-1].to_i + pos_name = result[5].downcase.gsub(':', '').to_sym + value = result[-1].to_i + + case pos_name + when :zoom + self[pos_name] = self[:zoom_max] - value + else + self[pos_name] = value + end when :connected self[:connected] = result[-1].downcase == 'true' when :model From bd49ed9a3b39ec82a12fa7ad19cac69de9183c19 Mon Sep 17 00:00:00 2001 From: William Le Date: Thu, 18 Jun 2020 13:05:12 +0800 Subject: [PATCH 1710/1752] chore(office2/logging): remove "error" from log output, as it may just be normal verbose output --- lib/microsoft/office2/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index d000c549..f1a85898 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -192,9 +192,9 @@ def log_graph_request(request_method, data, query, headers, graph_path, endpoint def check_response(response) return unless ENV['LOG_GRAPH_API'] || response.status > 300 STDERR.puts ">>>>>>>>>>>>" - STDERR.puts "GRAPH API ERROR Request:\n #{@request_info}" + STDERR.puts "GRAPH API Request:\n #{@request_info}" STDERR.puts "============" - STDERR.puts "GRAPH API ERROR Response:\n #{response}" + STDERR.puts "GRAPH API Response:\n #{response}" STDERR.puts "<<<<<<<<<<<<" case response.status when 400 From 1dbfaf08f4f183cec1edf3e9e77b0edfb8eceeb3 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 22 Jun 2020 14:10:37 +1000 Subject: [PATCH 1711/1752] feat(pexip): support creating meetings with a theme --- modules/pexip/management.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 20c0a9f7..cb20cced 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -32,6 +32,8 @@ def on_update # NOTE:: base URI https://pexip.company.com @username = setting(:username) @password = setting(:password) + @default_theme = setting(:default_theme) + proxy = setting(:proxy) if proxy config({ @@ -52,6 +54,9 @@ def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(999 name ||= conf_alias pin = pin.to_s.rjust(4, '0') if pin + # {"id": 1, "name": "Pexip theme (English_US) with entry tones", "uuid": "defaultplustones"} + options[:ivr_theme] ||= @default_theme if @default_theme + post('/api/admin/configuration/v1/conference/', body: { name: name.to_s, service_type: type, From ea476be2b3c2f5415d8395883d56e6f91e5750c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Po=C5=82o=C5=84ski?= Date: Tue, 30 Jun 2020 15:50:29 +1000 Subject: [PATCH 1712/1752] update(Gemfile):add yajl gem --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 9f040246..197be969 100644 --- a/Gemfile +++ b/Gemfile @@ -3,3 +3,4 @@ source "https://rubygems.org" gemspec gem 'yard' +gem 'yajl-ruby' From b5bcd2301600150093e0b4bb2ad8c8d3c3f4ea90 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 2 Jul 2020 17:51:25 +1000 Subject: [PATCH 1713/1752] fix(atlona:autoswitcher): clearing auto-switching --- modules/atlona/omni_stream/auto_switcher.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/atlona/omni_stream/auto_switcher.rb b/modules/atlona/omni_stream/auto_switcher.rb index f57d9e3d..c3c0b761 100644 --- a/modules/atlona/omni_stream/auto_switcher.rb +++ b/modules/atlona/omni_stream/auto_switcher.rb @@ -48,13 +48,16 @@ def enabled(state) if state != @enabled self[:enabled] = @enabled = state define_setting(:auto_switch_enabled, @enabled) - end - if state - system[@virtual_switcher].clear_auto_switching - switch_all + begin + # wait for this to occur + system[@virtual_switcher].clear_auto_switching.value + rescue + end end + switch_all if state + nil end From c02e7feb191f2cdafeabece08bb8a9436eb1dcef Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 3 Jul 2020 13:40:01 +1000 Subject: [PATCH 1714/1752] feat(pexip): add setting for theme name --- modules/pexip/management.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index cb20cced..822b1018 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -33,6 +33,7 @@ def on_update @username = setting(:username) @password = setting(:password) @default_theme = setting(:default_theme) + @default_theme_name = setting(:default_theme_name) proxy = setting(:proxy) if proxy @@ -56,6 +57,7 @@ def new_meeting(name = nil, conf_alias = nil, type = "conference", pin: rand(999 # {"id": 1, "name": "Pexip theme (English_US) with entry tones", "uuid": "defaultplustones"} options[:ivr_theme] ||= @default_theme if @default_theme + options[:ivr_theme_name] ||= @default_theme_name if @default_theme_name post('/api/admin/configuration/v1/conference/', body: { name: name.to_s, From 788dce7381a928bb1e463bf5233faa378923d257 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 13 Jul 2020 12:36:24 +1000 Subject: [PATCH 1715/1752] feat: ensure keys are cleared when unrouting --- modules/qsc/atlona_monitor.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/qsc/atlona_monitor.rb b/modules/qsc/atlona_monitor.rb index aab124ab..df1a48ba 100644 --- a/modules/qsc/atlona_monitor.rb +++ b/modules/qsc/atlona_monitor.rb @@ -31,6 +31,7 @@ def on_update def unroute_audio qsc = system[:Mixer] logger.debug { "unrouting all audio" } + @last_known_state.clear @stream_mappings.each_value do |details| qsc.component_set(details[:component], { Name: details[:control], @@ -41,8 +42,11 @@ def unroute_audio # Update QSC with any stream changes def check_changes(routes) + logger.debug { "new routes: #{routes}" } + return unless routes check_keys = @stream_mappings.keys.map(&:to_s) & routes.keys.map(&:to_s) + logger.debug { "checking keys: #{check_keys}" } return if check_keys.empty? check_keys = check_keys.map { |output| [output, routes[output]] } From 9e200ac8230f4af04e3880e950902ea71c507ae8 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 24 Aug 2020 23:52:22 +1000 Subject: [PATCH 1716/1752] feat(lumens): add DC193 visualiser --- modules/lumens/dc193.rb | 218 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 modules/lumens/dc193.rb diff --git a/modules/lumens/dc193.rb b/modules/lumens/dc193.rb new file mode 100644 index 00000000..a48c8c5c --- /dev/null +++ b/modules/lumens/dc193.rb @@ -0,0 +1,218 @@ +module Lumens; end + +# Documentation: https://aca.im/driver_docs/Lumens/DC193-Protocol.pdf +# RS232 controlled device + +class Lumens::DC193 + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :device + descriptive_name "Lumens DC 193 Document Camera" + generic_name :Visualiser + + tokenize indicator: "\xA0", msg_length: 6 + delay between_sends: 100 + + def on_load + @zoom_range = 0..@zoom_max + self[:zoom_max] = 864 + self[:zoom_min] = 0 + + @ready = true + @power = false + @zoom_max = 864 + @lamp = false + @head_led = false + @frozen = false + end + + def connected + schedule.every('50s') { query_status } + query_status + end + + def disconnected + schedule.clear + end + + def query_status + # Responses are JSON encoded + power?.value + if self[:power] + lamp? + zoom? + frozen? + max_zoom? + picture_mode? + end + end + + def power(state : Bool) + state = state ? 0x01 : 0x00 + send [0xA0, 0xB0, state, 0x00, 0x00, 0xAF], name: :power + power? + end + + def power? + # item 58 call system status + send [0xA0, 0xB7, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def lamp(state, head_led = false) + lamps = if state && head_led + 1 + elsif state + 2 + elsif head_led + 3 + else + 0 + end + + send [0xA0, 0xC1, lamps, 0x00, 0x00, 0xAF], name: :lamp + end + + def lamp? + send [0xA0, 0x50, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def zoom_to(position, auto_focus = true) + position = (position < 0 ? 0 : @zoom_max) unless @zoom_range.include?(position) + low = (position & 0xFF) + high = ((position >> 8) & 0xFF) + auto_focus = auto_focus ? 0x1F : 0x13 + send [0xA0, auto_focus, low, high, 0x00, 0xAF], name: :zoom_to + end + + def zoom(direction) + case direction.to_s.downcase + when "stop" + send [0xA0, 0x10, 0x00, 0x00, 0x00, 0xAF] + # Ensures this request is at the normal priority and ordering is preserved + zoom?(priority: 50) + # This prevents the auto-focus if someone starts zooming again + auto_focus(name: :zoom) + when "in" + send [0xA0, 0x11, 0x00, 0x00, 0x00, 0xAF], name: :zoom + when "out" + send [0xA0, 0x11, 0x01, 0x00, 0x00, 0xAF], name: :zoom + end + end + + def auto_focus(name = :auto_focus) + send [0xA0, 0xA3, 0x01, 0x00, 0x00, 0xAF], name: name + end + + def zoom?(priority = 0) + send [0xA0, 0x60, 0x00, 0x00, 0x00, 0xAF], priority: priority + end + + def freeze(state) + state = state ? 1 : 0 + send [0xA0, 0x2C, state, 0x00, 0x00, 0xAF], name: :freeze + end + + def frozen? + send [0xA0, 0x78, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def picture_mode(state) + mode = case state.to_s.downcase + when "photo" + 0x00 + when "text" + 0x01 + when "greyscale", "grayscale" + 0x02 + else + raise ArgumentError.new("unknown picture mode #{state}") + end + send [0xA0, 0xA7, mode, 0x00, 0x00, 0xAF], name: :picture_mode + end + + def picture_mode? + send [0xA0, 0x51, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def max_zoom? + send [0xA0, 0x8A, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + COMMANDS = { + 0xC1 => :lamp, + 0xB0 => :power, + 0xB7 => :power_staus, + 0xA7 => :picture_mode, + 0xA3 => :auto_focus, + 0x8A => :max_zoom, + 0x78 => :frozen_status, + 0x60 => :zoom_staus, + 0x51 => :picture_mode_staus, + 0x50 => :lamp_staus, + 0x2C => :freeze, + 0x1F => :zoom_direct_auto_focus, + 0x13 => :zoom_direct, + 0x11 => :zoom, + 0x10 => :zoom_stop, + } + + PICTURE_MODES = {:photo, :test, :greyscale} + + def received(data, reesolve, command) + logger.debug { "Lumens sent: #{byte_to_hex data}" } + data = str_to_array(data) + + error = (data[4] & 0x01) > 0 + ignored = (data[4] & 0x02) > 0 + + return :abort if error + return :retry if ignored + + case COMMANDS[data[1]] + when :power + data[2] == 0x01 + when :power_staus + @ready == data[2] == 0x01 + @power == data[3] == 0x01 + logger.debug { "System power: #{@power}, ready: #{@ready}" } + self[:ready] = @ready + self[:power] = @power + when :max_zoom + @zoom_max = data[2] + (data[3] << 8) + @zoom_range = 0..@zoom_max + self[:zoom_range] = {min: 0, max: @zoom_max} + when :frozen_status, :freeze + self[:frozen] = @frozen = data[2] == 1 + when :zoom_staus, :zoom_direct_auto_focus, :zoom_direct + @zoom = data[2].to_i + (data[3] << 8) + self[:zoom] = @zoom + when :picture_mode_staus, :picture_mode + self[:picture_mode] = PICTURE_MODES[data[2]] + when :lamp_staus, :lamp + case data[2] + when 0 + @head_led = @lamp = false + when 1 + @head_led = @lamp = true + when 2 + @head_led = false + @lamp = true + when 3 + @head_led = true + @lamp = false + end + self[:head_led] = @head_led + self[:lamp] = @lamp + when :auto_focus + # Can ignore this response + else + error = "Unknown command #{data[1]}" + logger.debug { error } + return :abort + end + + :success + end +end From 7ae65fa36e3846b902a3e9675e178e254e3395d2 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 25 Aug 2020 09:04:12 +1000 Subject: [PATCH 1717/1752] fix(lumens dc193): issues found during testing --- modules/lumens/dc193.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/modules/lumens/dc193.rb b/modules/lumens/dc193.rb index a48c8c5c..cd307a77 100644 --- a/modules/lumens/dc193.rb +++ b/modules/lumens/dc193.rb @@ -49,7 +49,7 @@ def query_status end end - def power(state : Bool) + def power(state) state = state ? 0x01 : 0x00 send [0xA0, 0xB0, state, 0x00, 0x00, 0xAF], name: :power power? @@ -158,24 +158,21 @@ def max_zoom? 0x10 => :zoom_stop, } - PICTURE_MODES = {:photo, :test, :greyscale} + PICTURE_MODES = [:photo, :test, :greyscale] def received(data, reesolve, command) - logger.debug { "Lumens sent: #{byte_to_hex data}" } + logger.debug { "DC193 sent: #{byte_to_hex data}" } data = str_to_array(data) - error = (data[4] & 0x01) > 0 - ignored = (data[4] & 0x02) > 0 - - return :abort if error - return :retry if ignored + return :abort if (data[4] & 0x01) > 0 + return :retry if (data[4] & 0x02) > 0 case COMMANDS[data[1]] when :power data[2] == 0x01 when :power_staus - @ready == data[2] == 0x01 - @power == data[3] == 0x01 + @ready = data[2] == 0x01 + @power = data[3] == 0x01 logger.debug { "System power: #{@power}, ready: #{@ready}" } self[:ready] = @ready self[:power] = @power From 3e698ad6da3e564d12d93eae0728e5b5acbc6d1c Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 25 Aug 2020 10:01:32 +1000 Subject: [PATCH 1718/1752] feat(lumens dc193): match existing interface --- modules/lumens/dc193.rb | 60 ++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/modules/lumens/dc193.rb b/modules/lumens/dc193.rb index cd307a77..3d56c4c8 100644 --- a/modules/lumens/dc193.rb +++ b/modules/lumens/dc193.rb @@ -3,7 +3,7 @@ module Lumens; end # Documentation: https://aca.im/driver_docs/Lumens/DC193-Protocol.pdf # RS232 controlled device -class Lumens::DC193 +class Lumens::Dc193 include ::Orchestrator::Constants include ::Orchestrator::Transcoder @@ -12,11 +12,10 @@ class Lumens::DC193 descriptive_name "Lumens DC 193 Document Camera" generic_name :Visualiser - tokenize indicator: "\xA0", msg_length: 6 + tokenize delimiter: "\xAF", indicator: "\xA0" delay between_sends: 100 def on_load - @zoom_range = 0..@zoom_max self[:zoom_max] = 864 self[:zoom_min] = 0 @@ -26,6 +25,7 @@ def on_load @lamp = false @head_led = false @frozen = false + @zoom_range = 0..@zoom_max end def connected @@ -61,6 +61,8 @@ def power? end def lamp(state, head_led = false) + return if @frozen + lamps = if state && head_led 1 elsif state @@ -79,6 +81,8 @@ def lamp? end def zoom_to(position, auto_focus = true) + return if @frozen + position = (position < 0 ? 0 : @zoom_max) unless @zoom_range.include?(position) low = (position & 0xFF) high = ((position >> 8) & 0xFF) @@ -87,9 +91,11 @@ def zoom_to(position, auto_focus = true) end def zoom(direction) + return if @frozen + case direction.to_s.downcase when "stop" - send [0xA0, 0x10, 0x00, 0x00, 0x00, 0xAF] + send [0xA0, 0x10, 0x00, 0x00, 0x00, 0xAF], name: :stop_zoom # Ensures this request is at the normal priority and ordering is preserved zoom?(priority: 50) # This prevents the auto-focus if someone starts zooming again @@ -101,7 +107,20 @@ def zoom(direction) end end + def zoom_in + zoom("in") + end + + def zoom_out + zoom("out") + end + + def zoom_stop + zoom("stop") + end + def auto_focus(name = :auto_focus) + return if @frozen send [0xA0, 0xA3, 0x01, 0x00, 0x00, 0xAF], name: name end @@ -114,11 +133,16 @@ def freeze(state) send [0xA0, 0x2C, state, 0x00, 0x00, 0xAF], name: :freeze end + def frozen(state) + freeze state + end + def frozen? send [0xA0, 0x78, 0x00, 0x00, 0x00, 0xAF], priority: 0 end def picture_mode(state) + return if @frozen mode = case state.to_s.downcase when "photo" 0x00 @@ -132,6 +156,10 @@ def picture_mode(state) send [0xA0, 0xA7, mode, 0x00, 0x00, 0xAF], name: :picture_mode end + def sharp(state) + picture_mode(state ? "text" : "photo") + end + def picture_mode? send [0xA0, 0x51, 0x00, 0x00, 0x00, 0xAF], priority: 0 end @@ -164,31 +192,31 @@ def received(data, reesolve, command) logger.debug { "DC193 sent: #{byte_to_hex data}" } data = str_to_array(data) - return :abort if (data[4] & 0x01) > 0 - return :retry if (data[4] & 0x02) > 0 + return :abort if (data[3] & 0x01) > 0 + return :retry if (data[3] & 0x02) > 0 - case COMMANDS[data[1]] + case COMMANDS[data[0]] when :power - data[2] == 0x01 + data[1] == 0x01 when :power_staus - @ready = data[2] == 0x01 - @power = data[3] == 0x01 + @ready = data[1] == 0x01 + @power = data[2] == 0x01 logger.debug { "System power: #{@power}, ready: #{@ready}" } self[:ready] = @ready self[:power] = @power when :max_zoom - @zoom_max = data[2] + (data[3] << 8) + @zoom_max = data[1] + (data[2] << 8) @zoom_range = 0..@zoom_max self[:zoom_range] = {min: 0, max: @zoom_max} when :frozen_status, :freeze - self[:frozen] = @frozen = data[2] == 1 + self[:frozen] = @frozen = data[1] == 1 when :zoom_staus, :zoom_direct_auto_focus, :zoom_direct - @zoom = data[2].to_i + (data[3] << 8) + @zoom = data[1].to_i + (data[2] << 8) self[:zoom] = @zoom when :picture_mode_staus, :picture_mode - self[:picture_mode] = PICTURE_MODES[data[2]] + self[:picture_mode] = PICTURE_MODES[data[1]] when :lamp_staus, :lamp - case data[2] + case data[1] when 0 @head_led = @lamp = false when 1 @@ -205,7 +233,7 @@ def received(data, reesolve, command) when :auto_focus # Can ignore this response else - error = "Unknown command #{data[1]}" + error = "Unknown command #{data[0]}" logger.debug { error } return :abort end From 3886be7d989b40834ae809809259522e3d519afa Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 25 Aug 2020 10:40:50 +1000 Subject: [PATCH 1719/1752] feat: replace lumens 192 with 193 code --- modules/lumens/dc192.rb | 577 +++++++++++++++++----------------------- 1 file changed, 238 insertions(+), 339 deletions(-) diff --git a/modules/lumens/dc192.rb b/modules/lumens/dc192.rb index d65525ca..bf58e0ae 100644 --- a/modules/lumens/dc192.rb +++ b/modules/lumens/dc192.rb @@ -1,344 +1,243 @@ module Lumens; end -# Documentation: https://aca.im/driver_docs/Lumens/DC192%20Protocol.pdf +# Documentation: https://aca.im/driver_docs/Lumens/DC193-Protocol.pdf +# RS232 controlled device class Lumens::Dc192 - include ::Orchestrator::Constants - include ::Orchestrator::Transcoder - - - # Discovery Information - implements :device - descriptive_name 'Lumens Visualiser DC192' - generic_name :Visualiser - - # Communication settings - tokenize delimiter: "\xAF", indicator: "\xA0" - delay between_sends: 300 - wait_response retries: 8 - - - def on_load - self[:zoom_max] = 855 - self[:zoom_min] = 0 - end - - def on_unload - end - - def on_update - end - - - - def connected - do_poll - schedule.every('60s') do - logger.debug "-- Polling Lumens DC Series Visualiser" - do_poll - end - end - - def disconnected - schedule.clear - end - - - - - COMMANDS = { - :zoom_stop => 0x10, # p1 = 0x00 - :zoom_start => 0x11, # p1 (00/01:Tele/Wide) - :zoom_direct => 0x13, # p1-LowByte, p2 highbyte (0~620) - :lamp => 0xC1, # p1 (00/01:Off/On) - :power => 0xB1, # p1 (00/01:Off/On) - :sharp => 0xA7, # p1 (00/01/02:Photo/Text/Gray) only photo or text - :auto_focus => 0xA3, # p1 = 0x01 - :frozen => 0x2C, # p1 (00/01:Off/On) - - 0x10 => :zoom_stop, - 0x11 => :zoom_start, - 0x13 => :zoom_direct, - 0xC1 => :lamp, - 0xB1 => :power, - 0xA7 => :sharp, - 0xA3 => :auto_focus, - 0x2C => :frozen, - - # Status response codes: - 0x78 => :frozen, - 0x51 => :sharp, - 0x50 => :lamp, - 0x60 => :zoom_direct, - 0xB7 => :system_status - } - - - def power(state) - state = is_affirmative?(state) - self[:power_target] = state - power? do - if state && !self[:power] # Request to power on if off - do_send([COMMANDS[:power], 0x01], :timeout => 15000, :delay_on_receive => 5000, :name => :power) - - elsif !state && self[:power] # Request to power off if on - do_send([COMMANDS[:power], 0x00], :timeout => 15000, :delay_on_receive => 5000, :name => :power) - self[:frozen] = false - end - end - end - - def power?(options = {}, &block) - options[:emit] = block - do_send(STATUS_CODE[:system_status], options) - end - - def zoom_in - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:zoom_start]) - end - - def zoom_out - return if self[:frozen] - cancel_focus - do_send([COMMANDS[:zoom_start], 0x01]) - end - - def zoom_stop - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:zoom_stop]) - end - - def zoom(position) - return if self[:frozen] - cancel_focus - position = position.to_i - - position = in_range(position, self[:zoom_max]) - - - low = position & 0xFF - high = (position >> 8) & 0xFF - - do_send([COMMANDS[:zoom_direct], low, high], {:name => :zoom}) - end - - - def lamp(power) - return if self[:frozen] - power = is_affirmative?(power) - - if power - do_send([COMMANDS[:lamp], 0x01], {:name => :lamp}) - else - do_send([COMMANDS[:lamp], 0x00], {:name => :lamp}) - end - end - - - def sharp(state) - return if self[:frozen] - state = is_affirmative?(state) - - if state - do_send([COMMANDS[:sharp], 0x01], {:name => :sharp}) - else - do_send([COMMANDS[:sharp], 0x00], {:name => :sharp}) - end - end - - - def frozen(state) - state = is_affirmative?(state) - - if state - do_send([COMMANDS[:frozen], 0x01], {:name => :frozen}) - else - do_send([COMMANDS[:frozen], 0x00], {:name => :frozen}) - end - end - - - def auto_focus - return if self[:frozen] - cancel_focus - do_send(COMMANDS[:auto_focus], :timeout => 8000, :name => :auto_focus) - end - - - def reset - return if self[:frozen] - cancel_focus - power(On) - - RESET_CODES.each_value do |value| - do_send(value) - end - - sharp(Off) - frozen(Off) - lamp(On) - zoom(0) - end - - - def received(data, reesolve, command) - logger.debug "Lumens sent #{byte_to_hex(data)}" - - - data = str_to_array(data) - - - # - # Process response - # - logger.debug "command was #{COMMANDS[data[0]]}" - case COMMANDS[data[0]] - when :zoom_stop - # - # A 3 second delay for zoom status and auto focus - # - zoom_status - delay_focus - when :zoom_direct - self[:zoom] = data[1] + (data[2] << 8) - delay_focus if COMMANDS[:zoom_direct] == data[0] # then 3 second delay for auto focus - when :lamp - self[:lamp] = data[1] == 0x01 - when :power - self[:power] = data[1] == 0x01 - if (self[:power] != self[:power_target]) && !self[:power_target].nil? - power(self[:power_target]) - logger.debug "Lumens state == unstable - power resp" - else - self[:zoom] = self[:zoom_min] unless self[:power] - end - when :sharp - self[:sharp] = data[1] == 0x01 - when :frozen - self[:frozen] = data[1] == 0x01 - when :system_status - self[:power] = data[2] == 0x01 - if (self[:power] != self[:power_target]) && !self[:power_target].nil? - power(self[:power_target]) - logger.debug "Lumens state == unstable - status" - else - self[:zoom] = self[:zoom_min] unless self[:power] - end - # ready = data[1] == 0x01 - end - - - # - # Check for error - # => We check afterwards as power for instance may be on when we call on - # => The power status is sent as on with a NAK as the command did nothing - # - if data[3] != 0x00 && (!!!self[:frozen]) - case data[3] - when 0x01 - logger.error "Lumens NAK error" - when 0x10 - logger.error "Lumens IGNORE error" - if command.present? - command[:delay_on_receive] = 2000 # update the command - return :abort # retry the command - # - # TODO:: Call system_status(0) and check for ready every second until the command will go through - # - end - else - logger.warn "Lumens unknown error code #{data[3]}" - end - - logger.error "Error on #{byte_to_hex(command[:data])}" unless command.nil? - return :abort - end - - - return :success - end - - - - private - - - def delay_focus - @focus_timer.cancel unless @focus_timer.nil? - @focus_timer = schedule.in('4s') do - auto_focus - end - end - - def cancel_focus - @focus_timer.cancel unless @focus_timer.nil? - end - - - - RESET_CODES = { - :OSD => [0x4B, 0x00], # p1 (00/01:Off/On) on screen display - :digital_zoom => [0x40, 0x00], # p1 (00/01:Disable/Enable) - :language => [0x38, 0x00], # p1 == 00 (english) - :colour => [0xA7, 0x00], # p1 (00/01:Photo/Gray) - :mode => [0xA9, 0x00], # P1 (00/01/02/03:Normal/Slide/Film/Microscope) - :logo => [0x47, 0x00], # p1 (00/01:Off/On) - :source => [0x3A, 0x00], # p1 (00/01:Live/PC) used for reset - :slideshow => [0x04, 0x00] # p1 (00/01:Off/On) -- NAKs - } - - - STATUS_CODE = { - :frozen_status => 0x78, # p1 = 0x00 - :sharp_status => 0x51, # p1 = 0x00 - :lamp_status => 0x50, # p1 = 0x00 - :zoom_status => 0x60, # p1 = 0x00 - :system_status => 0xB7 - } - # - # Automatically creates a callable function for each command - # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html - # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html - # - STATUS_CODE.each_key do |command| - define_method command do |*args| - priority = 99 - if args.length > 0 - priority = args[0] - end - - do_send(STATUS_CODE[command], {:priority => priority, :wait => true}) # Status polling is a low priority - end - end - - - def do_poll - power?(:priority => 99) do - if self[:power] == On - frozen_status - if not self[:frozen] - zoom_status - lamp_status - sharp_status - end - end - end - end - - - def do_send(command, options = {}) - #logger.debug "-- GlobalCache, sending: #{command}" - command = [command] unless command.is_a?(Array) - while command.length < 4 - command << 0x00 - end - - command = [0xA0] + command + [0xAF] - logger.debug "requesting #{byte_to_hex(command)}" - - send(command, options) - end + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :device + descriptive_name "Lumens DC 193 Document Camera" + generic_name :Visualiser + + tokenize delimiter: "\xAF", indicator: "\xA0" + delay between_sends: 100 + + def on_load + self[:zoom_max] = 864 + self[:zoom_min] = 0 + + @ready = true + @power = false + @zoom_max = 864 + @lamp = false + @head_led = false + @frozen = false + @zoom_range = 0..@zoom_max + end + + def connected + schedule.every('50s') { query_status } + query_status + end + + def disconnected + schedule.clear + end + + def query_status + # Responses are JSON encoded + power?.value + if self[:power] + lamp? + zoom? + frozen? + max_zoom? + picture_mode? + end + end + + def power(state) + state = state ? 0x01 : 0x00 + send [0xA0, 0xB0, state, 0x00, 0x00, 0xAF], name: :power + power? + end + + def power? + # item 58 call system status + send [0xA0, 0xB7, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def lamp(state, head_led = false) + return if @frozen + + lamps = if state && head_led + 1 + elsif state + 2 + elsif head_led + 3 + else + 0 + end + + send [0xA0, 0xC1, lamps, 0x00, 0x00, 0xAF], name: :lamp + end + + def lamp? + send [0xA0, 0x50, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def zoom_to(position, auto_focus = true) + return if @frozen + + position = (position < 0 ? 0 : @zoom_max) unless @zoom_range.include?(position) + low = (position & 0xFF) + high = ((position >> 8) & 0xFF) + auto_focus = auto_focus ? 0x1F : 0x13 + send [0xA0, auto_focus, low, high, 0x00, 0xAF], name: :zoom_to + end + + def zoom(direction) + return if @frozen + + case direction.to_s.downcase + when "stop" + send [0xA0, 0x10, 0x00, 0x00, 0x00, 0xAF], name: :stop_zoom + # Ensures this request is at the normal priority and ordering is preserved + zoom?(priority: 50) + # This prevents the auto-focus if someone starts zooming again + auto_focus(name: :zoom) + when "in" + send [0xA0, 0x11, 0x00, 0x00, 0x00, 0xAF], name: :zoom + when "out" + send [0xA0, 0x11, 0x01, 0x00, 0x00, 0xAF], name: :zoom + end + end + + def zoom_in + zoom("in") + end + + def zoom_out + zoom("out") + end + + def zoom_stop + zoom("stop") + end + + def auto_focus(name = :auto_focus) + return if @frozen + send [0xA0, 0xA3, 0x01, 0x00, 0x00, 0xAF], name: name + end + + def zoom?(priority = 0) + send [0xA0, 0x60, 0x00, 0x00, 0x00, 0xAF], priority: priority + end + + def freeze(state) + state = state ? 1 : 0 + send [0xA0, 0x2C, state, 0x00, 0x00, 0xAF], name: :freeze + end + + def frozen(state) + freeze state + end + + def frozen? + send [0xA0, 0x78, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def picture_mode(state) + return if @frozen + mode = case state.to_s.downcase + when "photo" + 0x00 + when "text" + 0x01 + when "greyscale", "grayscale" + 0x02 + else + raise ArgumentError.new("unknown picture mode #{state}") + end + send [0xA0, 0xA7, mode, 0x00, 0x00, 0xAF], name: :picture_mode + end + + def sharp(state) + picture_mode(state ? "text" : "photo") + end + + def picture_mode? + send [0xA0, 0x51, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def max_zoom? + send [0xA0, 0x8A, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + COMMANDS = { + 0xC1 => :lamp, + 0xB0 => :power, + 0xB7 => :power_staus, + 0xA7 => :picture_mode, + 0xA3 => :auto_focus, + 0x8A => :max_zoom, + 0x78 => :frozen_status, + 0x60 => :zoom_staus, + 0x51 => :picture_mode_staus, + 0x50 => :lamp_staus, + 0x2C => :freeze, + 0x1F => :zoom_direct_auto_focus, + 0x13 => :zoom_direct, + 0x11 => :zoom, + 0x10 => :zoom_stop, + } + + PICTURE_MODES = [:photo, :test, :greyscale] + + def received(data, reesolve, command) + logger.debug { "Lumens sent: #{byte_to_hex data}" } + data = str_to_array(data) + + return :abort if (data[3] & 0x01) > 0 + return :retry if (data[3] & 0x02) > 0 + + case COMMANDS[data[0]] + when :power + data[1] == 0x01 + when :power_staus + @ready = data[1] == 0x01 + @power = data[2] == 0x01 + logger.debug { "System power: #{@power}, ready: #{@ready}" } + self[:ready] = @ready + self[:power] = @power + when :max_zoom + @zoom_max = data[1] + (data[2] << 8) + @zoom_range = 0..@zoom_max + self[:zoom_range] = {min: 0, max: @zoom_max} + when :frozen_status, :freeze + self[:frozen] = @frozen = data[1] == 1 + when :zoom_staus, :zoom_direct_auto_focus, :zoom_direct + @zoom = data[1].to_i + (data[2] << 8) + self[:zoom] = @zoom + when :picture_mode_staus, :picture_mode + self[:picture_mode] = PICTURE_MODES[data[1]] + when :lamp_staus, :lamp + case data[1] + when 0 + @head_led = @lamp = false + when 1 + @head_led = @lamp = true + when 2 + @head_led = false + @lamp = true + when 3 + @head_led = true + @lamp = false + end + self[:head_led] = @head_led + self[:lamp] = @lamp + when :auto_focus + # Can ignore this response + else + error = "Unknown command #{data[0]}" + logger.debug { error } + return :abort + end + + :success + end end From c11f39970140cc4572913eb53025e08eeb05a7de Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 25 Aug 2020 10:47:15 +1000 Subject: [PATCH 1720/1752] Revert "feat: replace lumens 192 with 193 code" This reverts commit 3886be7d989b40834ae809809259522e3d519afa. --- modules/lumens/dc192.rb | 577 +++++++++++++++++++++++----------------- 1 file changed, 339 insertions(+), 238 deletions(-) diff --git a/modules/lumens/dc192.rb b/modules/lumens/dc192.rb index bf58e0ae..d65525ca 100644 --- a/modules/lumens/dc192.rb +++ b/modules/lumens/dc192.rb @@ -1,243 +1,344 @@ module Lumens; end -# Documentation: https://aca.im/driver_docs/Lumens/DC193-Protocol.pdf -# RS232 controlled device +# Documentation: https://aca.im/driver_docs/Lumens/DC192%20Protocol.pdf class Lumens::Dc192 - include ::Orchestrator::Constants - include ::Orchestrator::Transcoder - - # Discovery Information - implements :device - descriptive_name "Lumens DC 193 Document Camera" - generic_name :Visualiser - - tokenize delimiter: "\xAF", indicator: "\xA0" - delay between_sends: 100 - - def on_load - self[:zoom_max] = 864 - self[:zoom_min] = 0 - - @ready = true - @power = false - @zoom_max = 864 - @lamp = false - @head_led = false - @frozen = false - @zoom_range = 0..@zoom_max - end - - def connected - schedule.every('50s') { query_status } - query_status - end - - def disconnected - schedule.clear - end - - def query_status - # Responses are JSON encoded - power?.value - if self[:power] - lamp? - zoom? - frozen? - max_zoom? - picture_mode? - end - end - - def power(state) - state = state ? 0x01 : 0x00 - send [0xA0, 0xB0, state, 0x00, 0x00, 0xAF], name: :power - power? - end - - def power? - # item 58 call system status - send [0xA0, 0xB7, 0x00, 0x00, 0x00, 0xAF], priority: 0 - end - - def lamp(state, head_led = false) - return if @frozen - - lamps = if state && head_led - 1 - elsif state - 2 - elsif head_led - 3 - else - 0 - end - - send [0xA0, 0xC1, lamps, 0x00, 0x00, 0xAF], name: :lamp - end - - def lamp? - send [0xA0, 0x50, 0x00, 0x00, 0x00, 0xAF], priority: 0 - end - - def zoom_to(position, auto_focus = true) - return if @frozen - - position = (position < 0 ? 0 : @zoom_max) unless @zoom_range.include?(position) - low = (position & 0xFF) - high = ((position >> 8) & 0xFF) - auto_focus = auto_focus ? 0x1F : 0x13 - send [0xA0, auto_focus, low, high, 0x00, 0xAF], name: :zoom_to - end - - def zoom(direction) - return if @frozen - - case direction.to_s.downcase - when "stop" - send [0xA0, 0x10, 0x00, 0x00, 0x00, 0xAF], name: :stop_zoom - # Ensures this request is at the normal priority and ordering is preserved - zoom?(priority: 50) - # This prevents the auto-focus if someone starts zooming again - auto_focus(name: :zoom) - when "in" - send [0xA0, 0x11, 0x00, 0x00, 0x00, 0xAF], name: :zoom - when "out" - send [0xA0, 0x11, 0x01, 0x00, 0x00, 0xAF], name: :zoom - end - end - - def zoom_in - zoom("in") - end - - def zoom_out - zoom("out") - end - - def zoom_stop - zoom("stop") - end - - def auto_focus(name = :auto_focus) - return if @frozen - send [0xA0, 0xA3, 0x01, 0x00, 0x00, 0xAF], name: name - end - - def zoom?(priority = 0) - send [0xA0, 0x60, 0x00, 0x00, 0x00, 0xAF], priority: priority - end - - def freeze(state) - state = state ? 1 : 0 - send [0xA0, 0x2C, state, 0x00, 0x00, 0xAF], name: :freeze - end - - def frozen(state) - freeze state - end - - def frozen? - send [0xA0, 0x78, 0x00, 0x00, 0x00, 0xAF], priority: 0 - end - - def picture_mode(state) - return if @frozen - mode = case state.to_s.downcase - when "photo" - 0x00 - when "text" - 0x01 - when "greyscale", "grayscale" - 0x02 - else - raise ArgumentError.new("unknown picture mode #{state}") - end - send [0xA0, 0xA7, mode, 0x00, 0x00, 0xAF], name: :picture_mode - end - - def sharp(state) - picture_mode(state ? "text" : "photo") - end - - def picture_mode? - send [0xA0, 0x51, 0x00, 0x00, 0x00, 0xAF], priority: 0 - end - - def max_zoom? - send [0xA0, 0x8A, 0x00, 0x00, 0x00, 0xAF], priority: 0 - end - - COMMANDS = { - 0xC1 => :lamp, - 0xB0 => :power, - 0xB7 => :power_staus, - 0xA7 => :picture_mode, - 0xA3 => :auto_focus, - 0x8A => :max_zoom, - 0x78 => :frozen_status, - 0x60 => :zoom_staus, - 0x51 => :picture_mode_staus, - 0x50 => :lamp_staus, - 0x2C => :freeze, - 0x1F => :zoom_direct_auto_focus, - 0x13 => :zoom_direct, - 0x11 => :zoom, - 0x10 => :zoom_stop, - } - - PICTURE_MODES = [:photo, :test, :greyscale] - - def received(data, reesolve, command) - logger.debug { "Lumens sent: #{byte_to_hex data}" } - data = str_to_array(data) - - return :abort if (data[3] & 0x01) > 0 - return :retry if (data[3] & 0x02) > 0 - - case COMMANDS[data[0]] - when :power - data[1] == 0x01 - when :power_staus - @ready = data[1] == 0x01 - @power = data[2] == 0x01 - logger.debug { "System power: #{@power}, ready: #{@ready}" } - self[:ready] = @ready - self[:power] = @power - when :max_zoom - @zoom_max = data[1] + (data[2] << 8) - @zoom_range = 0..@zoom_max - self[:zoom_range] = {min: 0, max: @zoom_max} - when :frozen_status, :freeze - self[:frozen] = @frozen = data[1] == 1 - when :zoom_staus, :zoom_direct_auto_focus, :zoom_direct - @zoom = data[1].to_i + (data[2] << 8) - self[:zoom] = @zoom - when :picture_mode_staus, :picture_mode - self[:picture_mode] = PICTURE_MODES[data[1]] - when :lamp_staus, :lamp - case data[1] - when 0 - @head_led = @lamp = false - when 1 - @head_led = @lamp = true - when 2 - @head_led = false - @lamp = true - when 3 - @head_led = true - @lamp = false - end - self[:head_led] = @head_led - self[:lamp] = @lamp - when :auto_focus - # Can ignore this response - else - error = "Unknown command #{data[0]}" - logger.debug { error } - return :abort - end - - :success - end + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + + # Discovery Information + implements :device + descriptive_name 'Lumens Visualiser DC192' + generic_name :Visualiser + + # Communication settings + tokenize delimiter: "\xAF", indicator: "\xA0" + delay between_sends: 300 + wait_response retries: 8 + + + def on_load + self[:zoom_max] = 855 + self[:zoom_min] = 0 + end + + def on_unload + end + + def on_update + end + + + + def connected + do_poll + schedule.every('60s') do + logger.debug "-- Polling Lumens DC Series Visualiser" + do_poll + end + end + + def disconnected + schedule.clear + end + + + + + COMMANDS = { + :zoom_stop => 0x10, # p1 = 0x00 + :zoom_start => 0x11, # p1 (00/01:Tele/Wide) + :zoom_direct => 0x13, # p1-LowByte, p2 highbyte (0~620) + :lamp => 0xC1, # p1 (00/01:Off/On) + :power => 0xB1, # p1 (00/01:Off/On) + :sharp => 0xA7, # p1 (00/01/02:Photo/Text/Gray) only photo or text + :auto_focus => 0xA3, # p1 = 0x01 + :frozen => 0x2C, # p1 (00/01:Off/On) + + 0x10 => :zoom_stop, + 0x11 => :zoom_start, + 0x13 => :zoom_direct, + 0xC1 => :lamp, + 0xB1 => :power, + 0xA7 => :sharp, + 0xA3 => :auto_focus, + 0x2C => :frozen, + + # Status response codes: + 0x78 => :frozen, + 0x51 => :sharp, + 0x50 => :lamp, + 0x60 => :zoom_direct, + 0xB7 => :system_status + } + + + def power(state) + state = is_affirmative?(state) + self[:power_target] = state + power? do + if state && !self[:power] # Request to power on if off + do_send([COMMANDS[:power], 0x01], :timeout => 15000, :delay_on_receive => 5000, :name => :power) + + elsif !state && self[:power] # Request to power off if on + do_send([COMMANDS[:power], 0x00], :timeout => 15000, :delay_on_receive => 5000, :name => :power) + self[:frozen] = false + end + end + end + + def power?(options = {}, &block) + options[:emit] = block + do_send(STATUS_CODE[:system_status], options) + end + + def zoom_in + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:zoom_start]) + end + + def zoom_out + return if self[:frozen] + cancel_focus + do_send([COMMANDS[:zoom_start], 0x01]) + end + + def zoom_stop + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:zoom_stop]) + end + + def zoom(position) + return if self[:frozen] + cancel_focus + position = position.to_i + + position = in_range(position, self[:zoom_max]) + + + low = position & 0xFF + high = (position >> 8) & 0xFF + + do_send([COMMANDS[:zoom_direct], low, high], {:name => :zoom}) + end + + + def lamp(power) + return if self[:frozen] + power = is_affirmative?(power) + + if power + do_send([COMMANDS[:lamp], 0x01], {:name => :lamp}) + else + do_send([COMMANDS[:lamp], 0x00], {:name => :lamp}) + end + end + + + def sharp(state) + return if self[:frozen] + state = is_affirmative?(state) + + if state + do_send([COMMANDS[:sharp], 0x01], {:name => :sharp}) + else + do_send([COMMANDS[:sharp], 0x00], {:name => :sharp}) + end + end + + + def frozen(state) + state = is_affirmative?(state) + + if state + do_send([COMMANDS[:frozen], 0x01], {:name => :frozen}) + else + do_send([COMMANDS[:frozen], 0x00], {:name => :frozen}) + end + end + + + def auto_focus + return if self[:frozen] + cancel_focus + do_send(COMMANDS[:auto_focus], :timeout => 8000, :name => :auto_focus) + end + + + def reset + return if self[:frozen] + cancel_focus + power(On) + + RESET_CODES.each_value do |value| + do_send(value) + end + + sharp(Off) + frozen(Off) + lamp(On) + zoom(0) + end + + + def received(data, reesolve, command) + logger.debug "Lumens sent #{byte_to_hex(data)}" + + + data = str_to_array(data) + + + # + # Process response + # + logger.debug "command was #{COMMANDS[data[0]]}" + case COMMANDS[data[0]] + when :zoom_stop + # + # A 3 second delay for zoom status and auto focus + # + zoom_status + delay_focus + when :zoom_direct + self[:zoom] = data[1] + (data[2] << 8) + delay_focus if COMMANDS[:zoom_direct] == data[0] # then 3 second delay for auto focus + when :lamp + self[:lamp] = data[1] == 0x01 + when :power + self[:power] = data[1] == 0x01 + if (self[:power] != self[:power_target]) && !self[:power_target].nil? + power(self[:power_target]) + logger.debug "Lumens state == unstable - power resp" + else + self[:zoom] = self[:zoom_min] unless self[:power] + end + when :sharp + self[:sharp] = data[1] == 0x01 + when :frozen + self[:frozen] = data[1] == 0x01 + when :system_status + self[:power] = data[2] == 0x01 + if (self[:power] != self[:power_target]) && !self[:power_target].nil? + power(self[:power_target]) + logger.debug "Lumens state == unstable - status" + else + self[:zoom] = self[:zoom_min] unless self[:power] + end + # ready = data[1] == 0x01 + end + + + # + # Check for error + # => We check afterwards as power for instance may be on when we call on + # => The power status is sent as on with a NAK as the command did nothing + # + if data[3] != 0x00 && (!!!self[:frozen]) + case data[3] + when 0x01 + logger.error "Lumens NAK error" + when 0x10 + logger.error "Lumens IGNORE error" + if command.present? + command[:delay_on_receive] = 2000 # update the command + return :abort # retry the command + # + # TODO:: Call system_status(0) and check for ready every second until the command will go through + # + end + else + logger.warn "Lumens unknown error code #{data[3]}" + end + + logger.error "Error on #{byte_to_hex(command[:data])}" unless command.nil? + return :abort + end + + + return :success + end + + + + private + + + def delay_focus + @focus_timer.cancel unless @focus_timer.nil? + @focus_timer = schedule.in('4s') do + auto_focus + end + end + + def cancel_focus + @focus_timer.cancel unless @focus_timer.nil? + end + + + + RESET_CODES = { + :OSD => [0x4B, 0x00], # p1 (00/01:Off/On) on screen display + :digital_zoom => [0x40, 0x00], # p1 (00/01:Disable/Enable) + :language => [0x38, 0x00], # p1 == 00 (english) + :colour => [0xA7, 0x00], # p1 (00/01:Photo/Gray) + :mode => [0xA9, 0x00], # P1 (00/01/02/03:Normal/Slide/Film/Microscope) + :logo => [0x47, 0x00], # p1 (00/01:Off/On) + :source => [0x3A, 0x00], # p1 (00/01:Live/PC) used for reset + :slideshow => [0x04, 0x00] # p1 (00/01:Off/On) -- NAKs + } + + + STATUS_CODE = { + :frozen_status => 0x78, # p1 = 0x00 + :sharp_status => 0x51, # p1 = 0x00 + :lamp_status => 0x50, # p1 = 0x00 + :zoom_status => 0x60, # p1 = 0x00 + :system_status => 0xB7 + } + # + # Automatically creates a callable function for each command + # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html + # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html + # + STATUS_CODE.each_key do |command| + define_method command do |*args| + priority = 99 + if args.length > 0 + priority = args[0] + end + + do_send(STATUS_CODE[command], {:priority => priority, :wait => true}) # Status polling is a low priority + end + end + + + def do_poll + power?(:priority => 99) do + if self[:power] == On + frozen_status + if not self[:frozen] + zoom_status + lamp_status + sharp_status + end + end + end + end + + + def do_send(command, options = {}) + #logger.debug "-- GlobalCache, sending: #{command}" + command = [command] unless command.is_a?(Array) + while command.length < 4 + command << 0x00 + end + + command = [0xA0] + command + [0xAF] + logger.debug "requesting #{byte_to_hex(command)}" + + send(command, options) + end end From f820e8acba9db58cada0ba5bc7d7884925d0e535 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 26 Aug 2020 09:41:01 +1000 Subject: [PATCH 1721/1752] fix(philips sicp): ensure buffer doesn't grow unbounded --- modules/philips/display/sicp_protocol.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/philips/display/sicp_protocol.rb b/modules/philips/display/sicp_protocol.rb index d27ec4b7..49c8af0d 100644 --- a/modules/philips/display/sicp_protocol.rb +++ b/modules/philips/display/sicp_protocol.rb @@ -36,7 +36,7 @@ def on_update end def connected - @buffer.clear + @buffer = [] schedule.every('50s') do logger.debug "-- Polling Display" @@ -45,7 +45,7 @@ def connected end def disconnected - @buffer.clear + @buffer = [] # # Disconnected may be called without calling connected @@ -231,12 +231,22 @@ def received(data, resolve, command) # Buffer data @buffer.concat str_to_array(data) + # Ensure buffer doesn't grow too large + if @buffer.length > 4096 + @buffer = [] + disconnect + return :abort + end + # Extract any messages tokens = [] while @buffer[0] && @buffer.length >= @buffer[0] tokens << @buffer.slice!(0, @buffer[0]) end + # Allocate a new array if buffer empty + @buffer = [] if @buffer.empty? + # Process responses if tokens.length > 0 response = nil @@ -247,6 +257,11 @@ def received(data, resolve, command) else :ignore end + rescue error + @buffer = [] + logger.error { "#{error.message}\n#{error.backtrace}" } + disconnect + :abort end def checksum(data) From ee1ff475aed1852cdabcac050d26dc434a6b2383 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 10 Sep 2020 16:28:23 +0100 Subject: [PATCH 1722/1752] feat(nec): add basic spec --- modules/nec/display/all_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 modules/nec/display/all_spec.rb diff --git a/modules/nec/display/all_spec.rb b/modules/nec/display/all_spec.rb new file mode 100644 index 00000000..78561ee8 --- /dev/null +++ b/modules/nec/display/all_spec.rb @@ -0,0 +1,5 @@ +Orchestrator::Testing.mock_device "Nec::Display::All" do + should_send("\x010\x2A0A06\x0201D6\x03\x1F\x0D") + responds("\x0100\x2AB12\x020200D60000040001\x03\x1F\x0D") + expect(status[:power]).to be(true) +end From 90eb5372af824f8e657cdc6480f903fb789a23ca Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 23 Sep 2020 12:29:24 +1000 Subject: [PATCH 1723/1752] feat(toshiba/display/e_series): setting defines soft/hard power control --- modules/toshiba/display/e_series.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/toshiba/display/e_series.rb b/modules/toshiba/display/e_series.rb index e8a2ac19..152ba872 100644 --- a/modules/toshiba/display/e_series.rb +++ b/modules/toshiba/display/e_series.rb @@ -16,7 +16,12 @@ class Toshiba::Display::ESeries # Communication settings delay between_sends: 100 - + default_settings({ + # If TRUE, power() will mute/unmute video+audio while keeping the device "on" + # Use only when old firmwares display colour bars when sent the proper power off command. + soft_power: false + }) + def on_load on_update end @@ -27,8 +32,10 @@ def on_update max_waits: 5 }) + @soft_power = setting(:soft_power) @force_state = setting(:force_state) self[:power_target] = setting(:power_target) if @force_state + end def connected @@ -50,6 +57,10 @@ def disconnected def power(state) + @soft_power ? soft(state) : hard(state) + end + + def soft(state) self[:power_stable] = false promise = if is_affirmative?(state) hard(On) if self[:hard_off] @@ -59,16 +70,17 @@ def power(state) self[:power_target] = self[:power] = false do_send([0xFB, 0xD8, 0x01, 0x00, 0x20, 0x30, 0x00, 0x00], name: :power) end - define_setting(:power_target, self[:power_target]) if @force_state promise end + def hard(state) - # Results in colour bars + # Results in colour bars on older Toshiba firmwares if is_affirmative?(state) self[:power_stable] = true self[:hard_off] = false + # Swich to "OPS" input just to remove colour bars from display (buggy Toshiba firmware) schedule.in(50000) { do_send([0xFE, 0xD2, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00]) self[:power_stable] = false From b88c0fa74783eb0f21f84dd41003c51c3c88243d Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 23 Sep 2020 13:34:18 +1000 Subject: [PATCH 1724/1752] feat(toshiba/display/e_series): assume hard power off succeeded --- modules/toshiba/display/e_series.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/toshiba/display/e_series.rb b/modules/toshiba/display/e_series.rb index 152ba872..31fb2c40 100644 --- a/modules/toshiba/display/e_series.rb +++ b/modules/toshiba/display/e_series.rb @@ -88,9 +88,9 @@ def hard(state) } do_send([0xBA, 0xD2, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00], name: :hard_off) else - self[:power_stable] = false - self[:power_target] = false + # ASSUME the command succeeded because the Toshiba will not respond to any subsequent queries (e.g. power?) except power(true) self[:hard_off] = true + self[:power] = self[:power_stable] = self[:power_target] = false do_send([0x2A, 0xD3, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00], name: :hard_off) end end From 24ccbe25159b679b5c3804ca3c89cee5c2a5450b Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 23 Sep 2020 14:55:23 +1000 Subject: [PATCH 1725/1752] fix(toshiba/display/e_series): naming of power command --- modules/toshiba/display/e_series.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/toshiba/display/e_series.rb b/modules/toshiba/display/e_series.rb index 31fb2c40..e2b35cbd 100644 --- a/modules/toshiba/display/e_series.rb +++ b/modules/toshiba/display/e_series.rb @@ -86,7 +86,7 @@ def hard(state) self[:power_stable] = false self[:power_target] = true } - do_send([0xBA, 0xD2, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00], name: :hard_off) + do_send([0xBA, 0xD2, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00], name: :hard_on) else # ASSUME the command succeeded because the Toshiba will not respond to any subsequent queries (e.g. power?) except power(true) self[:hard_off] = true From f8d14f3f67a6ea761596ce1c7e070359954f6566 Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 23 Sep 2020 15:08:54 +1000 Subject: [PATCH 1726/1752] fix(toshiba/display/e_series): power query supports soft/hard power --- modules/toshiba/display/e_series.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/toshiba/display/e_series.rb b/modules/toshiba/display/e_series.rb index e2b35cbd..6e086a83 100644 --- a/modules/toshiba/display/e_series.rb +++ b/modules/toshiba/display/e_series.rb @@ -119,7 +119,11 @@ def hard_off? def power?(options = {}, &block) options[:emit] = block if block_given? options[:name] = :power_query - do_send([0xC8, 0xD8, 0x02, 0x00, 0x20, 0x30, 0x00, 0x00], options) + if @soft_power + do_send([0xC8, 0xD8, 0x02, 0x00, 0x20, 0x30, 0x00, 0x00], options) + else + do_send([0x19, 0xD3, 0x02, 0x00, 0x00, 0x60, 0x00, 0x00], options) + end end INPUTS = { From 7f2276fcef857672defa8b08cb70be9fc30eb9c0 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 29 Sep 2020 17:33:15 +1000 Subject: [PATCH 1727/1752] fix(toshiba/display/e_series): don't select OPS input after power on; Ignore power query response (it's always 'false') --- modules/toshiba/display/e_series.rb | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/modules/toshiba/display/e_series.rb b/modules/toshiba/display/e_series.rb index 6e086a83..08ae8e5c 100644 --- a/modules/toshiba/display/e_series.rb +++ b/modules/toshiba/display/e_series.rb @@ -78,21 +78,14 @@ def soft(state) def hard(state) # Results in colour bars on older Toshiba firmwares if is_affirmative?(state) - self[:power_stable] = true - self[:hard_off] = false - # Swich to "OPS" input just to remove colour bars from display (buggy Toshiba firmware) - schedule.in(50000) { - do_send([0xFE, 0xD2, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00]) - self[:power_stable] = false - self[:power_target] = true - } do_send([0xBA, 0xD2, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00], name: :hard_on) else - # ASSUME the command succeeded because the Toshiba will not respond to any subsequent queries (e.g. power?) except power(true) - self[:hard_off] = true - self[:power] = self[:power_stable] = self[:power_target] = false do_send([0x2A, 0xD3, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00], name: :hard_off) end + # ASSUME the command succeeded because the Toshiba will not respond to any subsequent queries (e.g. power?) except power(true) + self[:power] = self[:power_target] = state + self[:hard_off] = !state + self[:power_stable] = true end def remove_colour_bars @@ -288,14 +281,15 @@ def received(data, resolve, command) else :unknown end - when :power_query - self[:power] = data == "\x1D\0\1" - if !self[:power_stable] && self[:power] != self[:power_target] + #when :power_query + # Unfortunately some displays will always reply as power=false, even when they are on. So this power query reponse cannot be used, until a firmware update resolves the power query behaviour + #self[:power] = data == "\x1D\0\1" + #if !self[:power_stable] && self[:power] != self[:power_target] # We won't play around with forced state as feedback is too unreliable # power(self[:power_target]) - else - self[:power_stable] = true - end + #else + # self[:power_stable] = true + #end # Toshibas report this as always being off. So need to rely on internal state #when :hard_off_query # Power false == hard off true From 9c906918620986fe4835864b20eb8f6f0d934523 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 10 Feb 2021 13:20:01 +1100 Subject: [PATCH 1728/1752] feat: add whispir sms message service --- modules/whispir/messages.rb | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 modules/whispir/messages.rb diff --git a/modules/whispir/messages.rb b/modules/whispir/messages.rb new file mode 100644 index 00000000..7ce3013c --- /dev/null +++ b/modules/whispir/messages.rb @@ -0,0 +1,61 @@ +module Whispir; end + +# Documentation: https://whispir.github.io/api/#messages + +class Whispir::Messages + include ::Orchestrator::Constants + include ::Orchestrator::Transcoder + + # Discovery Information + implements :service + descriptive_name 'Whispir messages service' + generic_name :SMS + + # HTTP keepalive + keepalive false + + def on_load + on_update + end + + def on_update + # NOTE:: base URI https://api.messagemedia.com + @username = setting(:username) + @password = setting(:password) + @api_key = setting(:api_key) + proxy = setting(:proxy) + if proxy + config({ + proxy: { + host: proxy[:host], + port: proxy[:port] + } + }) + end + end + + def sms(text, numbers, source = nil) + text = text.to_s + numbers = Array(numbers) + + post("/messages?apikey=#{@api_key}", body: { + to: numbers.join(";"), + # As far as I can tell, this field is not passed to the recipients + subject: "PlaceOS Notification", + body: text, + }.to_json, headers: { + 'Authorization' => [@username, @password], + "Content-Type" => "application/vnd.whispir.message-v1+json", + "Accept" => "application/vnd.whispir.message-v1+json", + "x-api-key" => @api_key, + }) + end + + def received(data, resolve, command) + if data.status == 202 + :success + else + :retry + end + end +end From 3c2d6cc28b4f164690aab3402c4e1d54263fe7af Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 6 Apr 2021 19:19:25 +1000 Subject: [PATCH 1729/1752] fix(microsoft office2): remove domain from request paths --- lib/microsoft/office2/client.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index f1a85898..bded0125 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -108,7 +108,7 @@ def graph_token def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, bulk: false) if bulk uv_request_method = :post - graph_path = "#{@graph_domain}/v1.0/$batch" + graph_path = "/v1.0/$batch" query_string = "?#{query.map { |k,v| "#{k}=#{v}" }.join('&')}" data = { requests: endpoints.each_with_index.map { |endpoint, i| { id: i, method: request_method.upcase, url: "#{endpoint}#{query_string}" } } @@ -116,7 +116,7 @@ def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, b query = {} else uv_request_method = request_method.to_sym - graph_path = "#{@graph_domain}#{endpoints[0]}" + graph_path = endpoints[0] end headers['Authorization'] = "Bearer #{graph_token}" @@ -150,7 +150,7 @@ def graph_request(request_method:, endpoints:, data:nil, query:{}, headers:{}, b BULK_REQUEST_METHOD = :post UV_OPTIONS = { inactivity_timeout: 25000, keepalive: false } def raw_bulk_request(all_requests) - bulk_request_endpoint = "#{@graph_domain}/v1.0/$batch" + bulk_request_endpoint = "/v1.0/$batch" headers = { 'Authorization' => "Bearer #{graph_token}", 'Content-Type' => "application/json", From 2776408c20b5bbfe5757643afb0639c16491c107 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Tue, 6 Apr 2021 12:50:05 +0100 Subject: [PATCH 1730/1752] refactor(gallagher): remove debugging and basic cleanup --- lib/gallagher/rest.rb | 121 +++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 56 deletions(-) diff --git a/lib/gallagher/rest.rb b/lib/gallagher/rest.rb index 9570c881..ade0ea1b 100644 --- a/lib/gallagher/rest.rb +++ b/lib/gallagher/rest.rb @@ -2,9 +2,9 @@ require 'uv-rays' require 'json' module Gallagher; end - + class Gallagher::Rest - + ## # Create a new instance of the Gallagher Rest client. # @@ -15,9 +15,9 @@ class Gallagher::Rest # @param default_card_type [String] The default card type to use when creating a new card. This will be in the form of a URL. def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: nil, default_card_type: nil, default_access_group: nil, default_facility_code: nil, proxy: nil) # Initialize the http endpoint to make requests with our API key - @default_headers = { + @default_headers = { 'Authorization' => "GGL-API-KEY #{api_key}", - 'Content-Type' => 'application/json' + 'Content-Type' => 'application/json' } @default_access_group = default_access_group @default_division = default_division @@ -29,25 +29,25 @@ def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: ni options[:proxy] = { host: proxy_uri.host, port: proxy_uri.port } end @endpoint = UV::HttpEndpoint.new(domain, options) - + # Grab the URLs to be used by the other methods (HATEOAS standard) # First define where we will find the URLs in the data endpoint's response data_endpoint = "/api" - + @cardholders_endpoint = nil @pdfs_endpoint = nil @access_groups_endpoint = nil @card_types_endpoint = nil @fixed_pdf_id = nil - + # Get the main data endpoint to determine our new endpoints response = JSON.parse(@endpoint.get(path: data_endpoint, headers: @default_headers).value.body) - + @api_version = Gem::Version.new(response['version']) @cardholders_endpoint = response['features']['cardholders']['cardholders']['href'] @access_groups_endpoint = response['features']['accessGroups']['accessGroups']['href'] @events_endpoint = response['features']['events']['events']['href'] - + # Now get our cardholder PDF ID so we don't have to make the request over and over if @api_version >= Gem::Version.new('8.10') @card_types_endpoint = response['features']['cardTypes']['assign']['href'] @@ -60,7 +60,7 @@ def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: ni end @fixed_pdf_id = pdf_response['results'][0]['id'] # There should only be one result end - + ## # Personal Data Fields (PDFs) are custom fields that Gallagher allows definintions of on a site-by-site basis. # They will usually be for things like email address, employee ID or some other field specific to whoever is hosting the Gallagher instance. @@ -74,7 +74,7 @@ def initialize(domain:, api_key:, unique_pdf_name: 'email', default_division: ni # { # "name": "email", # "id": "5516", - # "href": "https://localhost:8904/api/personal_data_fields/5516" + # "href": "https://localhost:8904/api/personal_data_fields/5516" # }, # { # "name": "cellphone", @@ -92,9 +92,9 @@ def get_pdfs(name: nil) name = "\"#{name}\"" if name JSON.parse(@endpoint.get(path: @pdfs_endpoint, headers: @default_headers, query: {name: name}.compact).value.body) end - + ## - # + # # Retrieves cardholders and allows for filtering either based on the PDF provided (by name) at initalisation of the library or by some custom filter. # Carholders are essentially users in the Gallagher system. # For example, if the `unique_pdf_name` param passed in intialisation is `email` then passing `fixed_filter: 'some@email.com'` to this method will only return cardholders with that email. @@ -134,9 +134,12 @@ def get_cardholder(id: nil, fixed_filter: nil, custom_filter: nil) custom_pdf_id = self.get_pdfs(name: custom_filter.first[0].to_s) query["pdf_#{custom_pdf_id}"] = "\"#{custom_filter}\"" end - JSON.parse(@endpoint.get(path: @cardholders_endpoint, headers: @default_headers, query: query).value.body) + + # Add some debugging as Gallagher appears to be failing for certain requests and we need to log this + resp = @endpoint.get(path: @cardholders_endpoint, headers: @default_headers, query: query).value + JSON.parse(resp.body) end - + ## # Get a list of card types that this Gallagher instance has. These may include virutal, physical and ID cards. # Generally there are not going to be over 100 card types so the `next` field will be unused @@ -169,12 +172,12 @@ def get_cardholder(id: nil, fixed_filter: nil, custom_filter: nil) def get_card_types JSON.parse(@endpoint.get(path: @card_types_endpoint, headers: @default_headers).value.body) end - + def get_cardtype_max_number() response = JSON.parse(@endpoint.get(path: @default_card_type, query: {fields: 'maximumNumber'}, headers: @default_headers).value.body) response['maximumNumber']&.to_i end - + ## # Create a new cardholder. # @param first_name [String] The first name of the new cardholder. Either this or last name is required (but we should assume both are for most instances). @@ -197,15 +200,15 @@ def create_cardholder(first_name:, last_name:, description: 'A cardholder', opti }], competencies: nil } - + # Merge in our default options with those passed in options = options.reverse_merge(default_options) - + # Format the division correctly options[:division] = { href: options[:division] } - + # The params we're actually passing to Gallagher for creation create_params = { firstName: first_name, @@ -216,20 +219,20 @@ def create_cardholder(first_name:, last_name:, description: 'A cardholder', opti description: description, # cards: options[:cards] } - + # Add in our passed PDFs appending an '@' to the start of each pdf name options[:pdfs].each do |pdf_name, pdf_value| create_params["@#{pdf_name}".to_sym] = pdf_value end if options[:pdfs] - + # Add in any passed options, converting the keys to camel case which Gallagher uses create_params.merge!(options.except(:pdfs).transform_keys{|k| k.to_s.camelize(:lower)}) - + # Create our cardholder and return the response response = @endpoint.post(path: @cardholders_endpoint, headers: @default_headers, body: create_params.to_json).value process_response(response) end - + ## # This method will take card details and return a hash card detils aligning with the passed parameters in the format Gallagher expects # @@ -266,17 +269,17 @@ def format_card(options: {}) email: nil, mobile: nil } - + # Merge in our default options with those passed in options = options.reverse_merge(default_options) - + # Create our card format formatted_card = { type: { href: @default_card_type } } - + formatted_card[:number] = options[:number] if options[:number] formatted_card[:from] = Time.at(options[:from].to_i).utc.iso8601 if options[:from] formatted_card[:until] = Time.at(options[:until].to_i).utc.iso8601 if options[:until] @@ -287,7 +290,7 @@ def format_card(options: {}) } if options[:email] formatted_card end - + ## # Updates an existing cardholder to add new cards, access groups or competencies. # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. @@ -301,7 +304,7 @@ def format_card(options: {}) def add_cardholder_access(cardholder_href:, options: {}) self.update_cardholder(type: :add, cardholder_href: cardholder_href, options: options) end - + ## # Updates an existing cardholder to update new cards, access groups or competencies. # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. @@ -315,7 +318,7 @@ def add_cardholder_access(cardholder_href:, options: {}) def update_cardholder_access(cardholder_href:, options: {}) self.update_cardholder(type: :update, cardholder_href: cardholder_href, options: options) end - + ## # Updates an existing cardholder to remove new cards, access groups or competencies. # We will often have to add a card and an access group to a user so doing these at the same time should save on requests. @@ -328,8 +331,8 @@ def update_cardholder_access(cardholder_href:, options: {}) # @return [Hash] The cardholder that access was added for. def remove_cardholder_access(cardholder_href:, options: {}) self.update_cardholder(type: :remove, cardholder_href: cardholder_href, options: options) - end - + end + ## # Checks whether a cardholder exists based on an email passed in. # @@ -339,7 +342,7 @@ def cardholder_exists?(email:) results = self.get_cardholder(fixed_filter: email)['results'] results.empty? end - + ## # Disable a card for a certain cardholder def disable_card(cardholder_href:, card_href:) @@ -357,63 +360,63 @@ def disable_card(cardholder_href:, card_href:) req = @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params.to_json) process_response(req.value) end - + # Delete a specific card, given it's href def delete_card(card_href:) req = @endpoint.delete(path: card_href, headers: @default_headers) process_response(req.value) end - - + + ## # Retrieves events from Gallagher # - # @param sources [Array] An array of source IDs as strings - # @param groups [Array] An array of group IDs as strings - # @param types [Array] An array of type IDs as strings + # @param sources [Array] An array of source IDs as strings + # @param groups [Array] An array of group IDs as strings + # @param types [Array] An array of type IDs as strings def get_events(sources:[], groups:[], types:[], after: nil) # Convert params to arrays to allow passing of single IDs as strings sources = Array(sources) groups = Array(groups) types = Array(types) - + events_query = { source: sources.join(","), group: groups.join(","), type: types.join(",") } - + events_query[:after] = after if after # Create our cardholder and return the response JSON.parse(response = @endpoint.get(path: @events_endpoint, headers: @default_headers, query: events_query).value.body)['events'] end - + protected - + def update_cardholder(type:, cardholder_href:, options: {}) default_options = { cards: nil, access_groups: nil, competencies: nil } - + # Merge in our default options with those passed in options = options.reverse_merge(default_options) - + # Align to their kinda shitty format patch_params = { authorised: true } - + # Add the fields to update if they were passed in options.except(:from, :until).each do |param, value| patch_params[param.to_s.camelize(:lower)] = { type => value } if value end req = @endpoint.patch(path: cardholder_href, headers: @default_headers, body: patch_params.to_json) - process_response(req.value) + process_response(req.value, "Update Cardholder", patch_params) end - - def process_response(response) + + def process_response(response, method=nil, metadata=nil) case response.status when 201 puts "INFO > Gallagher CREATED #{response.status}: #{response['Location']}" @@ -421,12 +424,18 @@ def process_response(response) when 200..206 return response.body when 400 - case response.body['code'] - when -1056964457 # "Another cardholder already has a card number 2 with the same facility code." - raise CardNumberInUse.new(response.body) - when -1056964272 # "Card number is out of range for this Card Type." - raise CardNumberOutOfRange.new(response.body) + puts "Got 400, Body is:" + puts response.body.inspect + if response.body.start_with?("{") + case JSON.parse(response.body)['code'] + when -1056964457 # "Another cardholder already has a card number 2 with the same facility code." + raise CardNumberInUse.new(response.body) + when -1056964272 # "Card number is out of range for this Card Type." + raise CardNumberOutOfRange.new(response.body) + end end + puts "ERROR > Gallagher returns #{response.status}: #{response.body}" + raise StandardError.new(response.body) when 404 raise NotFound.new when 409 @@ -436,11 +445,11 @@ def process_response(response) raise StandardError.new(response.body) end end - + class ErrorAccessDenied < StandardError; end class InvalidAuthenticationToken < StandardError; end class Conflict < StandardError; end class NotFound < StandardError; end class CardNumberOutOfRange < StandardError; end class CardNumberInUse < StandardError; end -end \ No newline at end of file +end From 642830d573487780a94d190a1d94bb534a69c71a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 30 May 2021 22:42:54 +1000 Subject: [PATCH 1731/1752] feat(samsung): disable input osd display --- modules/samsung/displays/md_series.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/samsung/displays/md_series.rb b/modules/samsung/displays/md_series.rb index f62e72ab..fd2d09df 100755 --- a/modules/samsung/displays/md_series.rb +++ b/modules/samsung/displays/md_series.rb @@ -100,7 +100,8 @@ def disconnected :auto_power => 0x33, :screen_split => 0xB2, # Tri / quad split (larger panels only) :software_version => 0x0E, - :serial_number => 0x0B + :serial_number => 0x0B, + :osd => 0xA3 } COMMAND.merge!(COMMAND.invert) @@ -306,6 +307,13 @@ def auto_power(enable, options = {}) do_send(:auto_power, state, options) end + # + # control which on screen displays are shown, 0x00 == source indicator + def osd_enable(enable, osd_type = 0x00, options = {}) + state = is_affirmative?(enable) ? 1 : 0 + do_send(:osd, [osd_type, state], options) + end + # # Colour control [ @@ -358,7 +366,8 @@ def auto_power(enable, options = {}) :tint, :red_gain, :green_gain, - :blue_gain + :blue_gain, + :osd_enable ] # # Push any configured device settings @@ -423,6 +432,7 @@ def received(response, resolve, command) self[:brightness] = value when :input self[:input] = INPUTS[value] + osd_enable false # 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] From 6f058be2eaf4852808f945f99e179098fe32c944 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 17 Jun 2021 12:42:34 +1000 Subject: [PATCH 1732/1752] fix(cisco sx series): don't strip out `-` characters --- modules/cisco/tele_presence/sx_ssh.rb | 10 +++++----- modules/cisco/tele_presence/sx_telnet.rb | 13 ++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/modules/cisco/tele_presence/sx_ssh.rb b/modules/cisco/tele_presence/sx_ssh.rb index 44d5ccb0..d36512c5 100644 --- a/modules/cisco/tele_presence/sx_ssh.rb +++ b/modules/cisco/tele_presence/sx_ssh.rb @@ -43,14 +43,14 @@ def on_load defaults max_waits: 100 end - + def connected do_send "Echo off", wait: false, priority: 96 end def disconnected; end - - + + protected @@ -62,7 +62,7 @@ def params(opts = {}) next if value.blank? cmd << key.to_s cmd << ':' - val = value.to_s.gsub(/[^\w\s\.\:\@\#\*]/, '').strip + val = value.to_s.gsub(/[^\w\s\.\:\@\#\*\-]/, '').strip if val.include? ' ' cmd << '"' cmd << val @@ -75,7 +75,7 @@ def params(opts = {}) cmd.chop! cmd end - + def command(*args, **options) args.reject! { |item| item.blank? } diff --git a/modules/cisco/tele_presence/sx_telnet.rb b/modules/cisco/tele_presence/sx_telnet.rb index 9975c424..f44d9afa 100644 --- a/modules/cisco/tele_presence/sx_telnet.rb +++ b/modules/cisco/tele_presence/sx_telnet.rb @@ -55,19 +55,19 @@ def on_load } end - + def connected do_send (setting(:username) || :admin), wait: false, delay: 200, priority: 98 do_send setting(:password), wait: false, delay: 200, priority: 97 do_send "Echo off", wait: false, priority: 96 end - + def disconnected # Ensures the buffer is cleared new_telnet_client end - - + + protected @@ -86,7 +86,7 @@ def params(opts = {}) next if value.blank? cmd << key.to_s cmd << ':' - val = value.to_s.gsub(/[^\w\s\.\:\@\#\*]/, '').strip + val = value.to_s.gsub(/[^\w\s\.\:\@\#\*\-]/, '').strip if val.include? ' ' cmd << '"' cmd << val @@ -99,7 +99,7 @@ def params(opts = {}) cmd.chop! cmd end - + def command(*args, **options) args.reject! { |item| item.blank? } @@ -122,4 +122,3 @@ def do_send(command, options = {}) send @telnet.prepare(command), options end end - From 6bcc8246d607053f979019e20dd04bce6080a2cc Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 6 Jul 2021 22:42:54 +1000 Subject: [PATCH 1733/1752] feat(polycom group series): add a delay button press function --- modules/polycom/real_presence/group_series.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/polycom/real_presence/group_series.rb b/modules/polycom/real_presence/group_series.rb index a080f03d..04a8cc27 100644 --- a/modules/polycom/real_presence/group_series.rb +++ b/modules/polycom/real_presence/group_series.rb @@ -32,6 +32,7 @@ def on_update @default_content = setting(:default_content) || 2 @vmr_prefix = setting(:vmr_prefix) || '60' @vmr_append = setting(:vmr_append) || '' + @press_delay = false end def connected @@ -54,6 +55,7 @@ def connected def disconnected schedule.clear + @press_delay = false end protect_method :reboot, :reset, :whoami, :unregister, :register @@ -273,6 +275,14 @@ def button_press(*keys) keys.each { |key| send "button #{key}\r", delay: 1000 } end + def button_press_delay(*keys) + return if @press_delay + @press_delay = true + schedule.every('6s') { @press_delay = false } + keys = keys.map(&:downcase) + keys.each { |key| send "button #{key}\r", delay: 1000 } + end + def send_DTMF(char) send "gendial #{char}\r" end From 104fdfcd7d70f3f6a8442f6b54903f6febf53ab8 Mon Sep 17 00:00:00 2001 From: jeremyw24 Date: Thu, 9 Sep 2021 15:38:50 +1000 Subject: [PATCH 1734/1752] chore(pexip) update system_location --- modules/pexip/management.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 822b1018..b87f7d28 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -153,7 +153,7 @@ def dial_phone(meeting_alias, phone_number) conference_alias: meeting_alias, destination: "#{phone_number}@conference.meet.health.nsw.gov.au", protocol: 'sip', - system_location: 'UN_InternalWebRTC_SIPH323_Proxy' + system_location: 'AWS_AsiaPacSyd_Proxy_Internal_SV' } else { @@ -163,7 +163,7 @@ def dial_phone(meeting_alias, phone_number) conference_alias: meeting_alias, destination: phone_number, protocol: 'h323', - system_location: 'UN_InternalWebRTC_SIPH323_Proxy' + system_location: 'AWS_AsiaPacSyd_Proxy_Internal_UN' } end From 0915bd7e4ad1952a344d7578c7d8c32b0480da59 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 28 Sep 2021 18:04:11 +1000 Subject: [PATCH 1735/1752] chore(pexip): add some extra logging on failure --- modules/pexip/management.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index b87f7d28..5e3527aa 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -181,10 +181,12 @@ def dial_phone(meeting_alias, phone_number) # {participant_id: "5acac442-7a25-44fa-badf-4bc725a0f035", participant_ids: ["5acac442-7a25-44fa-badf-4bc725a0f035"]} response[:data] else + logger.warn { "failed to dial, got response: #{response}" } :abort end else - :abort + logger.warn { "failed to dial, got: #{data.status}\nbody: #{data.body}" } + :abort end end end From 24751ce49b99a764784ba146b610b520d67b714c Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 25 Oct 2021 23:21:51 +1100 Subject: [PATCH 1736/1752] feat(cisco sx series): keep track of upcoming bookings --- .../cisco/tele_presence/sx_series_common.rb | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/modules/cisco/tele_presence/sx_series_common.rb b/modules/cisco/tele_presence/sx_series_common.rb index 87ed5dd4..32870ae9 100644 --- a/modules/cisco/tele_presence/sx_series_common.rb +++ b/modules/cisco/tele_presence/sx_series_common.rb @@ -37,11 +37,15 @@ def connected @count -= 1 end end + + schedule.every('60s') { booking_list } end def disconnected super + @listing_phonebook = false + @listing_bookings = false schedule.clear end @@ -254,6 +258,10 @@ def video_output_mode? # END Common functions # ==================== + def booking_list + command 'bookings list' + end + # =========================================== # IR REMOTE KEYS (NOT AVAILABLE IN SX SERIES) @@ -335,7 +343,10 @@ def received(data, resolve, command) if command if response == :complete # Update status variables - if @listing_phonebook + if @listing_bookings + @listing_bookings = false + update_booking_state + elsif @listing_phonebook @listing_phonebook = false # expose results, unique every time @@ -391,8 +402,60 @@ def received(data, resolve, command) protected + def update_booking_state + time_now = Time.now.to_i + current_booking = nil + next_booking = nil + additional_bookings = 0 + @current_booking_list.sort! { |a, b| a["time_start_time"] <=> b["time_start_time"] } + @current_booking_list.each do |booking| + next if booking["time_end_time"] < time_now + start_time = booking["time_start_time"] + + if start_time <= time_now + current_booking = booking + elsif next_booking.nil? + next_booking = booking + else + additional_bookings += 1 + end + end + self[:bookings] = @current_booking_list + self[:booking_next] = next_booking + self[:booking_current] = current_booking + self[:additional_bookings] = additional_bookings + end + def process_results(result) case result[1].downcase.to_sym + when :bookingslistresult + if !@listing_bookings + # configure the data structures + case result[2].downcase + when "resultinfo" + @listing_bookings = true + @current_booking_list = [] + when "lastupdated:" + @listing_bookings = false if @bookings_last_updated == result[3] + @bookings_last_updated = result[3] + end + elsif result[2].downcase == "booking" + bindex = result[3].to_i - 1 + booking = @current_booking_list[bindex] + if booking.nil? + booking = {} + @current_booking_list << booking + end + details = result[4..-1] + value = details[-1] + bkey = details[0..-2].join('').underscore.gsub(":", "") + case result[-2].downcase + when "starttime:", "endtime:" + booking[bkey] = Time.parse(value).to_i + else + booking[bkey] = value + end + end when :phonebooksearchresult, :resultset @listing_phonebook = true From fc485bc812491ae154d06682f33cb495fd0a42b4 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Dec 2021 11:08:48 +1100 Subject: [PATCH 1737/1752] fix(pexip management): make dialing locations configurable --- modules/pexip/management.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/pexip/management.rb b/modules/pexip/management.rb index 5e3527aa..4513ea5c 100644 --- a/modules/pexip/management.rb +++ b/modules/pexip/management.rb @@ -35,6 +35,10 @@ def on_update @default_theme = setting(:default_theme) @default_theme_name = setting(:default_theme_name) + @sip_location = setting(:sip_location) || 'AWS_AsiaPacSyd_Proxy_Internal_SV' + @sip_domain = setting(:sip_domain) || 'conference.meet.health.nsw.gov.au' + @phone_location = setting(:phone_location) || 'AWS_AsiaPacSyd_Proxy_Internal_UN' + proxy = setting(:proxy) if proxy config({ @@ -151,9 +155,9 @@ def dial_phone(meeting_alias, phone_number) role: 'guest', routing: 'routing_rule', conference_alias: meeting_alias, - destination: "#{phone_number}@conference.meet.health.nsw.gov.au", + destination: "#{phone_number}@#{@sip_domain}", protocol: 'sip', - system_location: 'AWS_AsiaPacSyd_Proxy_Internal_SV' + system_location: @sip_location } else { @@ -163,7 +167,7 @@ def dial_phone(meeting_alias, phone_number) conference_alias: meeting_alias, destination: phone_number, protocol: 'h323', - system_location: 'AWS_AsiaPacSyd_Proxy_Internal_UN' + system_location: @phone_location } end From 156a319548885667bd1cdc454a4029a409fcaefb Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 7 Sep 2022 17:39:53 +0800 Subject: [PATCH 1738/1752] feat(qsc/camera): allow power command to be inverted --- modules/qsc/q_sys_camera.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/qsc/q_sys_camera.rb b/modules/qsc/q_sys_camera.rb index 93236995..c9a9c364 100644 --- a/modules/qsc/q_sys_camera.rb +++ b/modules/qsc/q_sys_camera.rb @@ -18,11 +18,12 @@ def on_load def on_update @mod_id = setting(:driver) || :Mixer @ids = setting(:ids) + @invert_power = setting(:invert_power) || false self[:no_discrete_zoom] = true end def power(state) - state = is_affirmative?(state) + state = @invert_power ? !is_affirmative?(state) : is_affirmative?(state) camera.mute(@ids[:power], state) end From d3688978fb84f8fd543481c3b4c895838fd0b051 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Mon, 10 Oct 2022 14:49:09 +1100 Subject: [PATCH 1739/1752] fix(o365_panel): convert cancel epoch to epoch if string --- modules/aca/o365_booking_panel.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 1b44a306..3b63a29e 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -211,6 +211,8 @@ def end_meeting(id) # def cancel_meeting(start_ms_epoch, reason = "unknown reason") now = Time.now.to_i + # If we have anything other than an epoch, convert it + start_ms_epoch = Time.parse(start_ms_epoch).to_i if start_ms_epoch.to_s[0] != 1 start_epoch = start_ms_epoch / 1000 too_early_to_cancel = now < start_epoch start_time = Time.at(start_epoch).in_time_zone(ENV['TZ']) From 4496f845a355092121e6640955530f96bddf3f78 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 17 Oct 2022 16:47:09 +1100 Subject: [PATCH 1740/1752] fix(office2/event): datestring_to_epoch should handle epochs --- lib/microsoft/office2/event.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/microsoft/office2/event.rb b/lib/microsoft/office2/event.rb index 276f1456..7398a585 100644 --- a/lib/microsoft/office2/event.rb +++ b/lib/microsoft/office2/event.rb @@ -54,7 +54,11 @@ def initialize(client:, event:, available_to:nil, available_from:nil) protected def datestring_to_epoch(date_object) - ActiveSupport::TimeZone.new(date_object['timeZone']).parse(date_object['dateTime']).to_i + if date_object.is_a? Integer + date_object + else + ActiveSupport::TimeZone.new(date_object['timeZone']).parse(date_object['dateTime']).to_i + end end def set_room_id(attendees) From efdb7146429a4d68b13630172c9625c6e722e961 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 17 Oct 2022 20:30:22 +1100 Subject: [PATCH 1741/1752] revert: epoch check --- lib/microsoft/office2/event.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/microsoft/office2/event.rb b/lib/microsoft/office2/event.rb index 7398a585..276f1456 100644 --- a/lib/microsoft/office2/event.rb +++ b/lib/microsoft/office2/event.rb @@ -54,11 +54,7 @@ def initialize(client:, event:, available_to:nil, available_from:nil) protected def datestring_to_epoch(date_object) - if date_object.is_a? Integer - date_object - else - ActiveSupport::TimeZone.new(date_object['timeZone']).parse(date_object['dateTime']).to_i - end + ActiveSupport::TimeZone.new(date_object['timeZone']).parse(date_object['dateTime']).to_i end def set_room_id(attendees) From 5833f453877fbc935bccb3f4604908080f3e4363 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 17 Oct 2022 20:32:08 +1100 Subject: [PATCH 1742/1752] feat(microsoft/office2/client): return response body --- lib/microsoft/office2/client.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index bded0125..fcfdac8e 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -214,6 +214,8 @@ def check_response(response) when 412 raise Microsoft::Error::Conflict.new(response.body) end + + response.body end end From 8182876cdf72f3a9402f5b392b36948dd85afd75 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 17 Oct 2022 20:33:45 +1100 Subject: [PATCH 1743/1752] feat(microsoft/office2/client): return response after checking --- lib/microsoft/office2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/client.rb b/lib/microsoft/office2/client.rb index fcfdac8e..9b129ddc 100644 --- a/lib/microsoft/office2/client.rb +++ b/lib/microsoft/office2/client.rb @@ -215,7 +215,7 @@ def check_response(response) raise Microsoft::Error::Conflict.new(response.body) end - response.body + response end end From 66e617656867dd36ab076dfa9ed149005bd0cbee Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 17 Oct 2022 20:38:19 +1100 Subject: [PATCH 1744/1752] fix(microsoft/office2/events): don't error on success --- lib/microsoft/office2/events.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index 9d0a0e93..bb068c72 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -237,13 +237,18 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca # Make the request and check the response begin - retries ||= 0 - request = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events"], data: event_json) - check_response(request) + # retries ||= 0 + response = graph_request(request_method: 'post', endpoints: ["/v1.0/users/#{mailbox}#{calendar_path(calendargroup_id, calendar_id)}/events"], data: event_json) + check_response(response) rescue Microsoft::Error::Conflict => e return {} end - Microsoft::Office2::Event.new(client: self, event: JSON.parse(request.body)).event + + begin + Microsoft::Office2::Event.new(client: self, event: JSON.parse(response.body)).event + rescue error + return {} + end end ## From f89b261939a91d9132846ba7a8adf400bb00d524 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 17 Oct 2022 21:25:02 +1100 Subject: [PATCH 1745/1752] fix(microsoft/office2/events): remove crystal error syntax --- lib/microsoft/office2/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/microsoft/office2/events.rb b/lib/microsoft/office2/events.rb index bb068c72..4a1b18d2 100644 --- a/lib/microsoft/office2/events.rb +++ b/lib/microsoft/office2/events.rb @@ -246,7 +246,7 @@ def create_booking(mailbox:, start_param:, end_param:, calendargroup_id: nil, ca begin Microsoft::Office2::Event.new(client: self, event: JSON.parse(response.body)).event - rescue error + rescue return {} end end From f3c17e6f5d8e104e46f6690f925c4e8486fe1d70 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 17 Oct 2022 21:42:20 +1100 Subject: [PATCH 1746/1752] fix: attendee email parsing --- lib/microsoft/office2/event.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/microsoft/office2/event.rb b/lib/microsoft/office2/event.rb index 276f1456..fb21618b 100644 --- a/lib/microsoft/office2/event.rb +++ b/lib/microsoft/office2/event.rb @@ -78,6 +78,7 @@ def format_attendees(attendees, organizer) new_attendees = [] attendees.each do |attendee| attendee_email = attendee['emailAddress']['address'] + next unless attendee_email && attendee_email.include?("@") # Compare the domain of this attendee's email against the internal domain mail_object = ::Mail::Address.new(attendee_email) From 59f839d17965aed8c27ddd97015ffb9d7994a401 Mon Sep 17 00:00:00 2001 From: camreeves <62260424+camreeves@users.noreply.github.com> Date: Wed, 19 Oct 2022 15:49:18 +1100 Subject: [PATCH 1747/1752] fix(o365 booking panel): only convert to seconds epoch if using ms --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 3b63a29e..48517afd 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -213,7 +213,7 @@ def cancel_meeting(start_ms_epoch, reason = "unknown reason") now = Time.now.to_i # If we have anything other than an epoch, convert it start_ms_epoch = Time.parse(start_ms_epoch).to_i if start_ms_epoch.to_s[0] != 1 - start_epoch = start_ms_epoch / 1000 + start_epoch = start_ms_epoch / 1000 if start_ms_epoch.to_s.length > 10 too_early_to_cancel = now < start_epoch start_time = Time.at(start_epoch).in_time_zone(ENV['TZ']) too_late_to_cancel = self[:cancel_timeout] ? (now > (start_epoch + self[:cancel_timeout] + 180)) : false # "180": allow up to 3mins of slippage, in case endpoint is not NTP synced From c87c15f295e8118ef641d06809f8c703b4c3a122 Mon Sep 17 00:00:00 2001 From: camreeves <62260424+camreeves@users.noreply.github.com> Date: Wed, 19 Oct 2022 16:00:20 +1100 Subject: [PATCH 1748/1752] fix(o365 booking panel): ensure start_epoch is set --- modules/aca/o365_booking_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aca/o365_booking_panel.rb b/modules/aca/o365_booking_panel.rb index 48517afd..d53e2ee1 100644 --- a/modules/aca/o365_booking_panel.rb +++ b/modules/aca/o365_booking_panel.rb @@ -213,7 +213,7 @@ def cancel_meeting(start_ms_epoch, reason = "unknown reason") now = Time.now.to_i # If we have anything other than an epoch, convert it start_ms_epoch = Time.parse(start_ms_epoch).to_i if start_ms_epoch.to_s[0] != 1 - start_epoch = start_ms_epoch / 1000 if start_ms_epoch.to_s.length > 10 + start_epoch = start_ms_epoch.to_s.length > 10 ? start_ms_epoch / 1000 : start_ms_epoch too_early_to_cancel = now < start_epoch start_time = Time.at(start_epoch).in_time_zone(ENV['TZ']) too_late_to_cancel = self[:cancel_timeout] ? (now > (start_epoch + self[:cancel_timeout] + 180)) : false # "180": allow up to 3mins of slippage, in case endpoint is not NTP synced From b2b6763b24ffee58651c1919d6b253467e2c1a53 Mon Sep 17 00:00:00 2001 From: William Le Date: Tue, 15 Nov 2022 21:02:01 +1100 Subject: [PATCH 1749/1752] Update example_output.txt --- modules/pressac/sensors/example_output.txt | 35 +++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/modules/pressac/sensors/example_output.txt b/modules/pressac/sensors/example_output.txt index 48d089de..6b59fee5 100644 --- a/modules/pressac/sensors/example_output.txt +++ b/modules/pressac/sensors/example_output.txt @@ -1,4 +1,4 @@ -'Occupancy-PIR' sensor { +'Occupancy-PIR' sensor 2019 { "timestamp":"2019-12-03 14:09:34", "deviceName":"SIP-10-002M", "deviceId":"058A7F72", @@ -12,7 +12,9 @@ "unit":"V"} } -Desk sensor: + + +Desk sensor 2019: { "deviceid":"050B1EE8", "devicename":"Desk01", @@ -22,7 +24,32 @@ Desk sensor: "supplyVoltage":"3.26" } -Environment sensor: +Desk Sensor 2022: +{ + "d2cMessage": { + "timestamp": "2022-11-15 17:55:02", + "deviceName": "SIP-12-103", + "deviceId": "050B4D4F", + "deviceType": "Under-Desk-Sensor", + "gatewayName": "SIPG-12-006", + "location": "", + "dBm": "-61", + "security": "No", + "motionDetected": false, + "supplyVoltage": { + "value": 3.24, + "unit": "V" + } + }, + "reportedProperties": [], + "messageDate": "2022-11-15T09:55:02.498Z", + "deviceId": "SIPG-12-006", + "_msgid": "277ac701cfb3949c" +} + + + +Environment sensor 2019: { "deviceid":"05189BE4", "devicename":"PEnvironSensor-02","dbm":"-44", @@ -30,4 +57,4 @@ Environment sensor: "concentration":"1740", "temperature":"27.2", "humidity":"55" -} \ No newline at end of file +} From f1b41338f123cfc916f039a172bd82a68ee4622e Mon Sep 17 00:00:00 2001 From: William Le Date: Wed, 30 Nov 2022 22:02:29 +0800 Subject: [PATCH 1750/1752] docs(pressac): new example output --- modules/pressac/sensors/example_output.txt | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/modules/pressac/sensors/example_output.txt b/modules/pressac/sensors/example_output.txt index 6b59fee5..b260a8df 100644 --- a/modules/pressac/sensors/example_output.txt +++ b/modules/pressac/sensors/example_output.txt @@ -1,4 +1,5 @@ -'Occupancy-PIR' sensor 2019 { +'Occupancy-PIR' sensor 2019: +{ "timestamp":"2019-12-03 14:09:34", "deviceName":"SIP-10-002M", "deviceId":"058A7F72", @@ -12,6 +13,30 @@ "unit":"V"} } +Occupancy Sensor 2022: +{ + "d2cMessage": { + "timestamp": "2022-11-30 21:59:12", + "deviceName": "SIP-12-004M", + "deviceId": "058A927B", + "deviceType": "Occupancy-PIR", + "gatewayName": "SIPG-12-003", + "location": "", + "dBm": "-84", + "security": "No", + "motionDetected": true, + "supplyVoltage": { + "value": 3.36, + "unit": "V" + } + }, + "reportedProperties": [], + "messageDate": "2022-11-30T13:59:12.467Z", + "deviceId": "SIPG-12-003", + "_msgid": "2797e8c8afa6efb9" +} + + Desk sensor 2019: From 71e555c6849eab383c6096e4cf181812fffa5215 Mon Sep 17 00:00:00 2001 From: "Viv B." Date: Mon, 23 Sep 2024 14:05:56 +1000 Subject: [PATCH 1751/1752] fix(gem): ruby 2.3 -> ruby 2.5 Attempt to resolve build error: ``` Bundler could not find compatible versions for gem "ruby": In Gemfile: ruby aca-device-modules was resolved to 2.0.0, which depends on ruby (>= 2.3.0) aca-device-modules was resolved to 2.0.0, which depends on orchestrator was resolved to 2.0.0, which depends on rails (~> 6.0) was resolved to 6.1.7.8, which depends on ruby (>= 2.5.0) ``` --- aca-device-modules.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aca-device-modules.gemspec b/aca-device-modules.gemspec index b3b2dd25..b75fb8bd 100644 --- a/aca-device-modules.gemspec +++ b/aca-device-modules.gemspec @@ -24,5 +24,5 @@ Gem::Specification.new do |s| s.add_dependency 'rails' s.add_dependency 'orchestrator' - s.required_ruby_version = '>= 2.3.0' + s.required_ruby_version = '>= 2.5.0' end From 2754bcc7a71cec0c643319c13aacc3b6acbd14ef Mon Sep 17 00:00:00 2001 From: "Viv B." Date: Mon, 23 Sep 2024 14:14:29 +1000 Subject: [PATCH 1752/1752] revert(gem): revert ruby version change --- aca-device-modules.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aca-device-modules.gemspec b/aca-device-modules.gemspec index b75fb8bd..b3b2dd25 100644 --- a/aca-device-modules.gemspec +++ b/aca-device-modules.gemspec @@ -24,5 +24,5 @@ Gem::Specification.new do |s| s.add_dependency 'rails' s.add_dependency 'orchestrator' - s.required_ruby_version = '>= 2.5.0' + s.required_ruby_version = '>= 2.3.0' end