hive-runner-tv 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []