hive-runner-tv 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b6afb887754f6f8863ac063c54724322aeb1ea80
4
+ data.tar.gz: a3d420f7922168d075b88c4a6b6e34e2055fb735
5
+ SHA512:
6
+ metadata.gz: 2a2fbaaf867d636638972f7c99c21921a8ec97b1125fea6455bdd9af9ccf8973ca733ebe4c4e936bb9a006367c31509f31b93c22d67eab5c8d117adca6480862
7
+ data.tar.gz: 3a9526b391be926e1a8d3aa5d4bf7053732ee498c68bdfb1126363ae8eed2a790a7a8a8b1f4374c815a60bb877b45aabec9e78607a4600625bb92b5953432cd5
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # hive-runner-tv
2
+ TV module for Hive Runner
3
+
4
+ ## Configuration file
5
+
6
+ ```
7
+ controllers:
8
+ tv:
9
+ ir_blaster_clients:
10
+ <devicedb id>:
11
+ type: <Blaster type, eg rat_blaster>
12
+ mac: <IR blaster mac address, eg 00-11-22-33-44-55>
13
+ dataset: <IR blaster dataset name>
14
+ output: <IP blaster output id>
15
+ sequences:
16
+ launch_titantv:
17
+ - signal:Power
18
+ - sleep:2
19
+ - signal:Power
20
+
21
+ network:
22
+ remote_talkshow_address: <talkshow url, eg talkshow.remote>
23
+ remote_talkshow_port_offset: <minimum port number>
24
+ tv:
25
+ titantv_url: <eg http://titantv.url/titantv>
26
+ titantv_name: <Application name reported by Titan TV>
27
+ ir_blaster_host: <ip address of IR blaster hub, eg, 10.20.30.40>
28
+ ir_blaster_port: <port number of IR blaster hub>
29
+
30
+ diagnostics:
31
+ tv:
32
+ uptime:
33
+ reboot_time: <seconds between reboots>
34
+ dead:
35
+ ```
36
+
37
+ Note, the `ir\_blaster\_clients` section under `controllers` may ultimately
38
+ move to the information provided by the Device Database.
39
+
40
+ ## IR Blaster
41
+
42
+ To use an IR blaster with `hive-runner` add the line
43
+
44
+ gem 'device_api-tv', git: 'git@github.com:bbc/device_api-tv'
45
+
46
+ to the Hive's Gemfile and add the configuration as shown above.
47
+
48
+ ## License
49
+
50
+ Hive Runner TV is available to everyone under the terms of the MIT open source licence.
51
+ Take a look at the LICENSE file in the code.
52
+
53
+ Copyright (c) 2016 BBC
@@ -0,0 +1,48 @@
1
+ require 'hive/controller'
2
+ require 'hive/worker/tv'
3
+
4
+ module Hive
5
+ class Controller
6
+ # The TV controller
7
+ class Tv < Controller
8
+ @@exclusion = []
9
+
10
+ def self.add_exclusion ex
11
+ @@exclusion << ex
12
+ end
13
+
14
+ def detect
15
+ Hive.logger.debug("Checking Hive Mind")
16
+ device_list = Hive.hive_mind.device_details['connected_devices']
17
+ Hive.logger.debug("Device list: #{device_list}")
18
+ devices = []
19
+ if device_list.is_a? Array
20
+ device_list.select { |d| valid? d }.collect do |device|
21
+ Hive.logger.debug("Found TV: #{device.inspect}")
22
+ devices << self.create_device(device)
23
+ end
24
+ else
25
+ raise Hive::Controller::DeviceDetectionFailed
26
+ end
27
+ Hive.logger.debug("Devices: #{devices}")
28
+ devices
29
+ end
30
+
31
+ private
32
+ def valid? device
33
+ if device['device_type'] == 'Tv'
34
+ @@exclusion.each do |ex|
35
+ match = true
36
+ ex.each_pair do |key, value|
37
+ match = match && device[key.to_s] != value
38
+ end
39
+ return false if ! match
40
+ end
41
+ return true
42
+ else
43
+ false
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ require 'hive/device'
2
+
3
+ module Hive
4
+ class Device
5
+ # The TV worker
6
+ class Tv < Device
7
+ def initialize(config)
8
+ @identity = config['id']
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ require 'hive/diagnostic'
2
+
3
+ module Hive
4
+ class Diagnostic
5
+ class Tv
6
+ class Dead < Diagnostic
7
+ def diagnose
8
+ status = @device_api.status
9
+ case status
10
+ when 'idle'
11
+ self.pass('Device is idle')
12
+ else
13
+ self.fail("Device is #{status}")
14
+ end
15
+ end
16
+
17
+ def repair(result)
18
+ tries = [
19
+ [ :launch_titantv ],
20
+ [ :power_cycle, :launch_titantv ],
21
+ [ :power_on, :launch_titantv ]
22
+ ]
23
+
24
+ tries.each do |commands|
25
+ catch :commands_failed do
26
+ commands.each do |command|
27
+ throw :commands_failed if ! @device_api.run_sequence(command)
28
+ end
29
+ timeout = Time.now + 600
30
+ while Time.now < timeout
31
+ if @device_api.status == 'idle'
32
+ return self.pass('Titan TV launched')
33
+ end
34
+ sleep 5
35
+ end
36
+ end
37
+ end
38
+
39
+ self.fail("Failed to recover device")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ require 'hive/diagnostic'
2
+
3
+ module Hive
4
+ class Diagnostic
5
+ class Tv
6
+ class Uptime < Diagnostic
7
+ def initialize(config, options)
8
+ @next_reboot_time = Time.now + config[:reboot_timeout] if config.has_key?(:reboot_timeout)
9
+ super(config, options)
10
+ end
11
+
12
+ def diagnose
13
+ if config.has_key?(:reboot_timeout)
14
+ if Time.now < @next_reboot_time
15
+ self.pass("Time to next reboot: #{@next_reboot_time - Time.now}s")
16
+ else
17
+ self.fail("Reboot required")
18
+ end
19
+ else
20
+ self.pass("Not configured to reboot")
21
+ end
22
+ end
23
+
24
+ def repair(result)
25
+ @next_reboot_time += config[:reboot_timeout]
26
+ @device_api.power_cycle ? self.pass("Successful reboot") : self.fail("Reboot failed")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'hive/messages'
4
+
5
+ module Hive
6
+ module Messages
7
+ class TvJob < Hive::Messages::Job
8
+ # TODO Fix this validation
9
+ # validates :application_url, :application_url_parameters, presence: true
10
+
11
+ def application_url
12
+ self.target.symbolize_keys[:application_url]
13
+ end
14
+
15
+ def application_url_parameters
16
+ self.target.symbolize_keys[:application_url_parameters]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,226 @@
1
+ require 'hive/worker'
2
+ require 'hive/messages/tv_job'
3
+ require 'mind_meld/tv'
4
+ require 'talkshow'
5
+
6
+ module Hive
7
+ class Worker
8
+ # The TV worker
9
+ class Tv < Worker
10
+ class FailedRedirect < StandardError
11
+ end
12
+
13
+ def initialize(config)
14
+ @brand = config['brand'].downcase.gsub(/\s/, '_')
15
+ @model = config['model'].downcase.gsub(/\s/, '_')
16
+
17
+ if config['ir_blaster_clients'] and config['ir_blaster_clients'].has_key?(config['id'])
18
+ require 'device_api/tv'
19
+ DeviceAPI::RatBlaster.configure do |rb_config|
20
+ rb_config.host = Hive.config.network.tv.ir_blaster_host if Hive.config.network.tv.ir_blaster_host?
21
+ rb_config.port = Hive.config.network.tv.ir_blaster_port if Hive.config.network.tv.ir_blaster_port?
22
+ end
23
+
24
+ config.merge!({"device_api" => DeviceAPI::TV::Device.new(
25
+ id: config['id'],
26
+ ir: {
27
+ type: config['ir_blaster_clients'][config['id']].type,
28
+ mac: config['ir_blaster_clients'][config['id']].mac,
29
+ dataset: config['ir_blaster_clients'][config['id']].dataset,
30
+ output: config['ir_blaster_clients'][config['id']].output
31
+ }
32
+ )})
33
+ config['ir_blaster_clients'][config['id']].sequences.each do |name, pattern|
34
+ config['device_api'].set_sequence(name.to_sym, pattern)
35
+ end
36
+ end
37
+ super(config)
38
+ end
39
+
40
+ # Prepare the TV
41
+ def pre_script(job, job_paths, script)
42
+ url = job.application_url
43
+ @log.info("Application url: #{url}")
44
+ # TODO Set device as busy in Hive mind
45
+
46
+ params = ""
47
+
48
+ # 'cross_network_restriction' means that the device requires Talkshow
49
+ # on the same network as the application.
50
+ # For the moment, assume networks are '10.10.*.*' and'*.bbc.co.uk'
51
+ # TODO Make this more general
52
+ if @options['features'] && @options['features'].include?('cross_network_restriction') and /bbc.co.uk/.match url
53
+ ts_address = Hive.config.network.remote_talkshow_address
54
+ ts_port = @ts_port = Hive.config.network.remote_talkshow_port_offset + @options['id']
55
+ @log.info("Using remote talkshow on port #{ts_port}")
56
+ script.set_env 'TALKSHOW_REMOTE_URL', "http://#{ts_address}:#{ts_port}"
57
+ # Not actually required but talkshow fails without it set
58
+ script.set_env 'TALKSHOW_PORT', ts_port
59
+ else
60
+ ts_port = @ts_port = self.allocate_port
61
+ @log.info("Using talkshow on port #{ts_port}")
62
+ # TODO Move this more centrally
63
+ ip = Socket.ip_address_list.detect { |intf| intf.ipv4_private? }
64
+ ts_address = ip.ip_address
65
+ script.set_env 'TALKSHOW_PORT', ts_port
66
+ end
67
+ @log.info("Talkshow server address is: #{ts_address}")
68
+
69
+ if job.application_url_parameters.present?
70
+ params = "?#{job.application_url_parameters}&talkshowurl=#{ts_address}:#{ts_port}"
71
+ else
72
+ params = "?talkshowurl=#{ts_address}:#{ts_port}"
73
+ end
74
+ url += params
75
+
76
+ self.redirect(
77
+ url: url,
78
+ old_app: Hive.config.network.tv.titantv_name,
79
+ skip_first_load: true
80
+ )
81
+ load_hive_mind ts_port, url
82
+
83
+ @log.info("Starting TV Application monitor")
84
+ @monitor = Thread.new do
85
+ loop do
86
+ poll_response = Hive.hive_mind.poll(@device_id)
87
+ # if poll_response.is_a? Array and poll_response.length > 0
88
+ # @log.debug("[TV app monitor] Polled TV. Application = #{poll_response.first['application']}")
89
+ # # if @hive_mind.device_details(refresh: true)['application'] == Hive.config.network.tv.titantv_name
90
+ # if poll_response.first['application'] == Hive.config.network.tv.titantv_name
91
+ # # TV has returned to the holding app
92
+ # # Put back in the app under test
93
+ # self.redirect(
94
+ # url: url,
95
+ # old_app: Hive.config.network.tv.titantv_name,
96
+ # log_prefix: '[TV app monitor] '
97
+ # )
98
+ # end
99
+ # else
100
+ # @log.warn("[TV app monitor] Failed to poll TV")
101
+ # end
102
+ sleep 20
103
+ end
104
+ end
105
+
106
+ return nil
107
+ end
108
+
109
+ def job_message_klass
110
+ Hive::Messages::TvJob
111
+ end
112
+
113
+ def mind_meld_klass
114
+ MindMeld::Tv
115
+ end
116
+
117
+ def post_script(job, job_paths, script)
118
+ self.release_port(@ts_port) if @ts_port
119
+
120
+ signal_safe_post_script(job, job_paths, script)
121
+ end
122
+
123
+ def signal_safe_post_script(job, job_paths, script)
124
+ @log.info('Terminating TV Application monitor')
125
+ @monitor.exit if @monitor
126
+
127
+ self.redirect(url: Hive.config.network.tv.titantv_url, new_app: Hive.config.network.tv.titantv_name, skip_last_load: true)
128
+ # TODO Set device as idle in Hive Mind
129
+ end
130
+
131
+ def device_status
132
+ @hive_mind.device_details['status']
133
+ end
134
+
135
+ def set_device_status(status)
136
+ # TODO Set status from Hive Mind
137
+ # At the moment the device state cannot be changed
138
+ device_status
139
+ end
140
+
141
+ def autogenerated_queues
142
+ [ "#{@brand}-#{@model}" ]
143
+ end
144
+
145
+ def redirect(opts)
146
+ raise ArgumentError if ! ( opts.has_key?(:url) && ( opts.has_key?(:old_app) || opts.has_key?(:new_app) ) )
147
+ load_hive_mind(@ts_port, opts[:old_app] || "Trying to redirect ...") if ! opts[:skip_first_load]
148
+ opts[:log_prefix] ||= ''
149
+ @log.info("#{opts[:log_prefix]}Redirecting to #{opts[:url]}")
150
+ @hive_mind.create_action(action_type: 'redirect', body: opts[:url])
151
+ sleep 5
152
+ load_hive_mind(@ts_port, opts[:url]) if ! opts[:skip_last_load]
153
+
154
+ max_wait_count = 30
155
+ wait_count = 0
156
+ max_retry_count = 15
157
+ retry_count = 0
158
+
159
+ app_name = @hive_mind.device_details(refresh: true)['application']
160
+ @log.debug("#{opts[:log_prefix]}Current app: #{app_name}")
161
+ while (opts.has_key?(:new_app) && app_name != opts[:new_app]) || (opts.has_key?(:old_app) && app_name == opts[:old_app])
162
+ if wait_count >= max_wait_count
163
+ if retry_count >= max_retry_count
164
+ raise FailedRedirect
165
+ else
166
+ retry_count += 1
167
+ wait_count = 0
168
+ @log.info("#{opts[:log_prefix]}Redirecting to #{opts[:url]} [#{retry_count}]")
169
+ @hive_mind.create_action(action_type: 'redirect', body: opts[:url])
170
+ sleep 5
171
+ end
172
+ else
173
+ wait_count += 1
174
+ @log.info("#{opts[:log_prefix]} . [#{wait_count}]")
175
+ sleep 5
176
+ load_hive_mind(@ts_port, opts[:url]) if ! opts[:skip_last_load]
177
+ end
178
+ app_name = @hive_mind.device_details(refresh: true)['application']
179
+ @log.debug("#{opts[:log_prefix]}Current app: #{app_name}")
180
+ end
181
+ end
182
+
183
+ # Between tests the TV must be in the holding app
184
+ def diagnostics
185
+ app_name = @hive_mind.device_details(refresh: true)['application']
186
+ raise DeviceNotReady.new("Current application: '#{app_name}'") if app_name != Hive.config.network.tv.titantv_name
187
+ super
188
+ end
189
+
190
+ def load_hive_mind ts_port, app_name
191
+ ts = Talkshow.new
192
+ @log.info("Port: #{ts_port}")
193
+ @log.info("App: #{app_name}")
194
+ @log.info("Logfile: #{@file_system.results_path}/talkshowserver.log")
195
+ @log.info("titantv_url: #{Hive.config.network.tv.titantv_url}")
196
+ ts.start_server(port: ts_port, logfile: "#{@file_system.results_path}/talkshowserver.log")
197
+ 5.times do
198
+ begin
199
+ ts.execute <<JS
200
+ (function(){
201
+ var load_script = document.createElement('script');
202
+ load_script.type = 'text/javascript';
203
+ load_script.charset = 'utf-8';
204
+ load_script.src = '#{Hive.config.network.tv.titantv_url}/script/hive_mind_com.js';
205
+ document.getElementsByTagName('head')[0].appendChild(load_script);
206
+ // Give it 10 seconds to load
207
+ // TODO Do this with a retry
208
+ setTimeout(function() {
209
+ hive_mind_com.init('#{app_name}', '#{Hive.config.network.tv.titantv_url}');
210
+ hive_mind_com.start();
211
+ }, 10000);
212
+ return true;
213
+ })()
214
+ JS
215
+ break
216
+ rescue Talkshow::Timeout
217
+ @log.info("Talkshow timeout")
218
+ sleep 5
219
+ end
220
+ end
221
+ ts.stop_server
222
+ end
223
+
224
+ end
225
+ end
226
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hive-runner-tv
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Joe Haig
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hive-runner
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: talkshow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.4.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: res
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ description: The TV controller module for Hive Runner
56
+ email: joe.haig@bbc.co.uk
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - README.md
62
+ - lib/hive/controller/tv.rb
63
+ - lib/hive/device/tv.rb
64
+ - lib/hive/diagnostic/tv/dead.rb
65
+ - lib/hive/diagnostic/tv/uptime.rb
66
+ - lib/hive/messages/tv_job.rb
67
+ - lib/hive/worker/tv.rb
68
+ homepage: https://github.com/bbc-test/hive-runner-tv
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.5.0
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Hive Runner TV
92
+ test_files: []