ir_ptz 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +42 -0
- data/LICENSE +20 -0
- data/README.md +42 -0
- data/Rakefile +6 -0
- data/arduino/arduino.ino +118 -0
- data/bin/ir_ptz +13 -0
- data/data/ir_remote.yml +16 -0
- data/ir_ptz.gemspec +27 -0
- data/lib/ir_ptz.rb +43 -0
- data/lib/ir_ptz/command_line.rb +62 -0
- data/lib/ir_ptz/ir_command_recorder.rb +63 -0
- data/lib/ir_ptz/ir_remote.rb +40 -0
- data/lib/ir_ptz/version.rb +3 -0
- data/spec/helpers.rb +16 -0
- data/spec/ir_ptz/command_line_spec.rb +73 -0
- data/spec/ir_ptz/ir_command_recorder_spec.rb +58 -0
- data/spec/ir_ptz/ir_remote_spec.rb +44 -0
- data/spec/spec_helper.rb +11 -0
- metadata +168 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ir_ptz (0.0.1)
|
5
|
+
arduino_ir_remote (~> 0.1.1)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
arduino_ir_remote (0.1.1)
|
11
|
+
args_parser (>= 0.2.0)
|
12
|
+
event_emitter
|
13
|
+
serialport
|
14
|
+
args_parser (0.2.0)
|
15
|
+
bourne (1.5.0)
|
16
|
+
mocha (>= 0.13.2, < 0.15)
|
17
|
+
diff-lcs (1.2.4)
|
18
|
+
event_emitter (0.2.5)
|
19
|
+
metaclass (0.0.1)
|
20
|
+
mocha (0.14.0)
|
21
|
+
metaclass (~> 0.0.1)
|
22
|
+
rake (10.1.0)
|
23
|
+
rspec (2.14.1)
|
24
|
+
rspec-core (~> 2.14.0)
|
25
|
+
rspec-expectations (~> 2.14.0)
|
26
|
+
rspec-mocks (~> 2.14.0)
|
27
|
+
rspec-core (2.14.5)
|
28
|
+
rspec-expectations (2.14.3)
|
29
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
30
|
+
rspec-mocks (2.14.3)
|
31
|
+
serialport (1.1.0)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
bourne
|
38
|
+
bundler (~> 1.3)
|
39
|
+
ir_ptz!
|
40
|
+
mocha
|
41
|
+
rake
|
42
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Will Mernagh
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
## IR PTZ
|
2
|
+
|
3
|
+
CLI to controle a Pan, Tilt, and Zoom Camera by InfraRed via an Arduino. This
|
4
|
+
is just a wrapper around
|
5
|
+
[arduino_ir_remote](https://github.com/shokai/arduino_ir_remote) that adds the following:
|
6
|
+
|
7
|
+
- cli to replay recorded codes
|
8
|
+
- ability to record new codes
|
9
|
+
|
10
|
+
## Requirements
|
11
|
+
|
12
|
+
* Arduino Uno
|
13
|
+
* [IR Shield](http://store.linksprite.com/linksprite-infrared-shield-for-arduino/) or IR LED and an IR Reviever
|
14
|
+
* USB cable (printer cabel)
|
15
|
+
|
16
|
+
## Hardware Installation
|
17
|
+
|
18
|
+
* Connect the shield to the Arduino
|
19
|
+
* Connect arduino via USB to yoru computer
|
20
|
+
|
21
|
+
Note if you are not using a shield the IR LED should be connected to PIN 3 and
|
22
|
+
the IR Receiver should be connected to PIN 11
|
23
|
+
|
24
|
+
## Software installation
|
25
|
+
|
26
|
+
Install this gem
|
27
|
+
|
28
|
+
$ gem install ir_ptz
|
29
|
+
|
30
|
+
You will need to install the Arduino SDK and load the sketch from this gem. To
|
31
|
+
find the sketch run the following:
|
32
|
+
|
33
|
+
$ gem contents ir_ptz | grep arduino.ino
|
34
|
+
/PATH_TO_GEMS/1.9.1/gems/ir_ptz-x.x.x/arduino/arduino.ino
|
35
|
+
|
36
|
+
Then in the SDK open the output from above (e.g. `/PATH_TO_GEMS/1.9.1/gems/ir_ptz-x.x.x/arduino/arduino.ino`)
|
37
|
+
|
38
|
+
## Still to do
|
39
|
+
|
40
|
+
Have writing to the Arduino not stop processing incoming key presses. Basically
|
41
|
+
I will buffer the input in one thread and write out the buffer in the other I
|
42
|
+
think.
|
data/Rakefile
ADDED
data/arduino/arduino.ino
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
#define IR_DATA_SIZE 768
|
2
|
+
byte ir_data[IR_DATA_SIZE];
|
3
|
+
unsigned int ir_index;
|
4
|
+
#define PIN_LED 13
|
5
|
+
#define PIN_IR_IN 11
|
6
|
+
#define PIN_IR_OUT 3
|
7
|
+
|
8
|
+
void setup(){
|
9
|
+
Serial.begin(57600);
|
10
|
+
pinMode(PIN_IR_IN, INPUT);
|
11
|
+
pinMode(PIN_IR_OUT, OUTPUT);
|
12
|
+
pinMode(PIN_LED, OUTPUT);
|
13
|
+
}
|
14
|
+
|
15
|
+
unsigned int analog_count = 0;
|
16
|
+
void loop(){
|
17
|
+
if(Serial.available()){
|
18
|
+
char recv = Serial.read();
|
19
|
+
process_input(recv);
|
20
|
+
}
|
21
|
+
else{
|
22
|
+
if(analog_count < 1){
|
23
|
+
for(byte i = 0; i < 6; i++){
|
24
|
+
Serial.print("ANALOG");
|
25
|
+
Serial.println(i);
|
26
|
+
Serial.println(analogRead(i));
|
27
|
+
}
|
28
|
+
}
|
29
|
+
analog_count += 1;
|
30
|
+
if(analog_count > 65534) analog_count = 0;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
void ir_read(byte ir_pin){
|
35
|
+
unsigned int i, j;
|
36
|
+
for(i = 0; i < IR_DATA_SIZE; i++){
|
37
|
+
ir_data[i] = 0;
|
38
|
+
}
|
39
|
+
unsigned long now, last, start_at;
|
40
|
+
boolean stat;
|
41
|
+
start_at = micros();
|
42
|
+
while(stat = digitalRead(ir_pin)){
|
43
|
+
if(micros() - start_at > 2500000) return;
|
44
|
+
}
|
45
|
+
start_at = last = micros();
|
46
|
+
for(i = 0; i < IR_DATA_SIZE; i++){
|
47
|
+
for(j = 0; ; j++){
|
48
|
+
if(stat != digitalRead(ir_pin)) break;
|
49
|
+
if(j > 65534) return;
|
50
|
+
}
|
51
|
+
now = micros();
|
52
|
+
ir_data[i] = (now - last)/100;
|
53
|
+
last = now;
|
54
|
+
stat = !stat;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
void ir_print(){
|
59
|
+
unsigned int i;
|
60
|
+
for(i = 0; i < IR_DATA_SIZE; i++){
|
61
|
+
Serial.print(ir_data[i]);
|
62
|
+
if(ir_data[i] < 1) break;
|
63
|
+
Serial.print(",");
|
64
|
+
}
|
65
|
+
Serial.println();
|
66
|
+
}
|
67
|
+
|
68
|
+
void ir_write(byte ir_pin){
|
69
|
+
unsigned int i;
|
70
|
+
unsigned long interval_sum, start_at;
|
71
|
+
interval_sum = 0;
|
72
|
+
start_at = micros();
|
73
|
+
for(i = 0; i < IR_DATA_SIZE; i++){
|
74
|
+
if(ir_data[i] < 1) break;
|
75
|
+
interval_sum += ir_data[i] * 100;
|
76
|
+
if(i % 2 == 0){
|
77
|
+
while(micros() - start_at < interval_sum){
|
78
|
+
digitalWrite(ir_pin, true);
|
79
|
+
delayMicroseconds(6);
|
80
|
+
digitalWrite(ir_pin, false);
|
81
|
+
delayMicroseconds(8);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
else{
|
85
|
+
while(micros() - start_at < interval_sum);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
void process_input(char input){
|
91
|
+
if(input == 'r'){
|
92
|
+
digitalWrite(PIN_LED, true);
|
93
|
+
ir_read(PIN_IR_IN);
|
94
|
+
Serial.println("READ");
|
95
|
+
ir_print();
|
96
|
+
digitalWrite(PIN_LED, false);
|
97
|
+
}
|
98
|
+
else if(input == 'w'){
|
99
|
+
for(ir_index = 0; ir_index < IR_DATA_SIZE; ir_index++){
|
100
|
+
ir_data[ir_index] = 0;
|
101
|
+
}
|
102
|
+
ir_index = 0;
|
103
|
+
}
|
104
|
+
else if(input == ','){
|
105
|
+
if(ir_index < IR_DATA_SIZE) ir_index += 1;
|
106
|
+
}
|
107
|
+
else if(input >= '0' && '9' >= input){
|
108
|
+
ir_data[ir_index] = ir_data[ir_index]*10 + (input - '0');
|
109
|
+
}
|
110
|
+
else if(input == 'W'){
|
111
|
+
digitalWrite(PIN_LED, true);
|
112
|
+
ir_write(PIN_IR_OUT);
|
113
|
+
ir_index = 0;
|
114
|
+
Serial.println("WRITE");
|
115
|
+
ir_print();
|
116
|
+
digitalWrite(PIN_LED, false);
|
117
|
+
}
|
118
|
+
}
|
data/bin/ir_ptz
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
DATA_FILE = ENV["IR_DATA_FILE"] || File.expand_path('.ir_remote.yml', ENV['HOME'])
|
5
|
+
unless File.exists?(DATA_FILE)
|
6
|
+
default_file = File.join(File.dirname(File.expand_path(__FILE__)), '../data/ir_remote.yml')
|
7
|
+
FileUtils.cp default_file, DATA_FILE
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'ir_ptz'
|
11
|
+
require 'ir_ptz/command_line'
|
12
|
+
|
13
|
+
IrPtz::CommandLine.new.run
|
data/data/ir_remote.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
actions:
|
3
|
+
tilt_up: '89,44,5,5,5,16,5,5,5,5,5,5,5,5,5,5,5,5,5,16,5,5,5,16,5,16,5,16,5,16,5,16,5,16,5,5,5,5,5,16,5,5,5,16,5,16,5,5,5,16,5,16,5,16,5,5,5,16,5,5,5,5,5,16,5,5,5,143,89,22,5,192,89,22,5,192,89,22,5,192,89,22,5,192,89,22,5,0'
|
4
|
+
tilt_down: '90,44,6,4,6,16,6,4,6,4,6,4,6,4,6,4,6,4,6,16,6,4,6,16,6,16,6,16,6,16,6,16,6,16,6,4,6,4,6,16,6,16,6,16,6,16,6,4,6,16,6,16,6,16,6,4,6,4,6,4,6,4,6,16,6,4,6,142,90,21,6,0'
|
5
|
+
pan_right: '90,44,6,4,6,16,6,4,6,4,6,4,6,4,6,4,6,4,6,16,6,4,6,16,6,16,6,16,6,16,6,16,6,16,6,4,6,4,6,16,6,16,6,16,6,16,6,16,6,16,6,16,6,16,6,4,6,4,6,4,6,4,6,4,6,4,6,142,90,21,6,0'
|
6
|
+
pan_left: '90,44,6,4,6,16,6,4,6,4,6,4,6,4,6,4,6,4,6,16,6,4,6,16,6,16,6,16,6,16,6,16,6,16,6,4,6,4,6,16,6,4,6,16,6,16,6,16,6,16,6,16,6,16,6,4,6,16,6,4,6,4,6,4,6,4,6,0'
|
7
|
+
zoom_in: '90,44,6,4,6,16,6,4,6,4,6,4,6,4,6,4,6,4,6,16,6,4,6,16,6,16,6,16,6,16,6,16,6,16,6,4,6,16,6,4,6,4,6,4,6,16,6,4,6,16,6,16,6,4,6,16,6,16,6,16,6,4,6,16,6,4,6,142,90,21,6,0'
|
8
|
+
zoom_out: '89,44,5,5,5,16,5,5,5,5,5,5,5,5,5,5,5,5,5,16,5,5,5,16,5,16,5,16,5,16,5,16,5,16,5,5,5,16,5,5,5,5,5,5,5,16,5,16,5,16,5,16,5,5,5,16,5,16,5,16,5,5,5,5,5,5,5,143,89,22,5,0'
|
9
|
+
action_mappings:
|
10
|
+
k: tilt_up
|
11
|
+
j: tilt_down
|
12
|
+
l: pan_right
|
13
|
+
h: pan_left
|
14
|
+
i: zoom_in
|
15
|
+
o: zoom_out
|
16
|
+
device_path: /dev/tty.usbmodem401321
|
data/ir_ptz.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ir_ptz/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ir_ptz"
|
8
|
+
spec.version = IrPtz::VERSION
|
9
|
+
spec.authors = ["Will Mernagh"]
|
10
|
+
spec.email = ["wmernagh@gmail.com"]
|
11
|
+
spec.description = %q{CLI to control a Pan Tilt Zoom Camera by InfraRed via an Arduino}
|
12
|
+
spec.summary = %q{see homepage}
|
13
|
+
spec.homepage = "https://github.com/wmernagh/ir_ptz"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split("\n")
|
17
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "arduino_ir_remote", "~> 0.1.1"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "mocha"
|
26
|
+
spec.add_development_dependency "bourne"
|
27
|
+
end
|
data/lib/ir_ptz.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
module IrPtz
|
4
|
+
class << self
|
5
|
+
attr_accessor :configuration
|
6
|
+
end
|
7
|
+
|
8
|
+
# Configure IrPtz in ~/.ir_ptz_config.rb to override the defaults
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# IrPtz.configure do |config|
|
12
|
+
# config.device_path = '/dev/tty.usbmodem9999'
|
13
|
+
# config.in_key = 'a'
|
14
|
+
# config.out_key = 's'
|
15
|
+
# end
|
16
|
+
def self.configure
|
17
|
+
yield(configuration) if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.configuration
|
21
|
+
@configuration ||= Configuration.new
|
22
|
+
end
|
23
|
+
|
24
|
+
class Configuration
|
25
|
+
attr_accessor :escape_key, :help_key, :record_key, :actions, :action_mappings
|
26
|
+
attr_accessor :device_path
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
self.escape_key = 'e'
|
30
|
+
self.help_key = '?'
|
31
|
+
self.record_key = 'r'
|
32
|
+
self.action_mappings = ArduinoIrRemote::DATA['action_mappings']
|
33
|
+
self.actions = ArduinoIrRemote::DATA['actions']
|
34
|
+
self.device_path = ArduinoIrRemote::DATA['device_path']
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
require "ir_ptz/version"
|
40
|
+
require 'arduino_ir_remote'
|
41
|
+
require "ir_ptz/ir_remote"
|
42
|
+
require "ir_ptz/ir_command_recorder"
|
43
|
+
require "ir_ptz/command_line"
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
|
3
|
+
module IrPtz
|
4
|
+
class CommandLine
|
5
|
+
attr_reader :ir_remote
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
# ensure_configured
|
9
|
+
@ir_remote = IrRemote.instance
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
print_instructions
|
14
|
+
|
15
|
+
while key = STDIN.getch
|
16
|
+
case key
|
17
|
+
when config.escape_key
|
18
|
+
puts 'Goodbye!'
|
19
|
+
break
|
20
|
+
when config.help_key
|
21
|
+
print_instructions
|
22
|
+
when config.record_key
|
23
|
+
ir_command_recorder.record.save
|
24
|
+
else
|
25
|
+
if action = action(key)
|
26
|
+
ir_remote.send action(key)
|
27
|
+
else
|
28
|
+
print_instructions
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def ir_command_recorder
|
37
|
+
IrCommandRecorder
|
38
|
+
end
|
39
|
+
|
40
|
+
def action(key)
|
41
|
+
config.action_mappings[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
def config
|
45
|
+
IrPtz.configuration
|
46
|
+
end
|
47
|
+
|
48
|
+
def print_instructions
|
49
|
+
puts instructions
|
50
|
+
end
|
51
|
+
|
52
|
+
def instructions
|
53
|
+
title = "\nThe following key commands are available:\n"
|
54
|
+
exit_text = "\t'#{config.escape_key}' to exit."
|
55
|
+
help_text = "\t'#{config.help_key}' for help"
|
56
|
+
record_text = "\t'#{config.record_key}' record a new command"
|
57
|
+
custom_text = config.action_mappings.map { |k, v| "\t'#{k}' to #{v}" }
|
58
|
+
instructions = [title, exit_text, help_text, record_text] + custom_text
|
59
|
+
instructions.join "\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
|
3
|
+
module IrPtz
|
4
|
+
class IrCommandRecorder
|
5
|
+
Command = Struct.new(:title, :key, :action_code) do
|
6
|
+
def save
|
7
|
+
ArduinoIrRemote::DATA['action_mappings'][key] = title
|
8
|
+
ArduinoIrRemote::DATA['actions'][title] = action_code
|
9
|
+
ArduinoIrRemote::DATA.save
|
10
|
+
puts "#{title} saved!"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.record
|
15
|
+
command = nil
|
16
|
+
title = get_title
|
17
|
+
key = get_key
|
18
|
+
code = get_ir_code
|
19
|
+
|
20
|
+
@command = Command.new(title, key, code)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
class BlankException < Exception; end
|
25
|
+
NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]!
|
26
|
+
|
27
|
+
def self.blank?(string)
|
28
|
+
res = string !~ NON_WHITESPACE_REGEXP
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.get_title
|
32
|
+
print 'Enter command title: '
|
33
|
+
title = gets.chomp
|
34
|
+
|
35
|
+
if IrPtz.configuration.actions.has_key? title
|
36
|
+
puts %Q{overwrite "#{title}" ?}
|
37
|
+
print "[Y/n] > "
|
38
|
+
return nil unless gets.strip =~ /Y/
|
39
|
+
end
|
40
|
+
|
41
|
+
blank?(title) ? get_title : title
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.get_key
|
45
|
+
puts 'Enter the key to be used to send the command: '
|
46
|
+
key = getch
|
47
|
+
|
48
|
+
if IrPtz.configuration.action_mappings.has_key? key
|
49
|
+
puts %Q{overwrite "#{args[:read]}" ?}
|
50
|
+
print "[Y/n] > "
|
51
|
+
return nil unless gets.strip =~ /Y/
|
52
|
+
end
|
53
|
+
|
54
|
+
blank?(key) ? get_key : key
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.get_ir_code
|
58
|
+
ir_code = IrRemote.instance.read_code
|
59
|
+
blank?(ir_code) ? get_ir_code : ir_code
|
60
|
+
end
|
61
|
+
private_class_method :get_ir_code
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'arduino_ir_remote'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module IrPtz
|
5
|
+
class IrRemote
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
IrPtz.configuration.actions.keys.each do |action|
|
9
|
+
define_method(action) do
|
10
|
+
if ir_code = IrPtz.configuration.actions[action]
|
11
|
+
ir.write ir_code
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_code
|
17
|
+
data = nil
|
18
|
+
end_of_read = nil
|
19
|
+
|
20
|
+
ir.read do |ir_data|
|
21
|
+
data = ir_data
|
22
|
+
end_of_read = true
|
23
|
+
end
|
24
|
+
|
25
|
+
puts "reading.."
|
26
|
+
ir.wait { sleep 1; break if end_of_read }
|
27
|
+
puts "finished reading!"
|
28
|
+
|
29
|
+
data
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :ir
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@ir = ArduinoIrRemote.connect IrPtz.configuration.device_path
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/helpers.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Helpers
|
2
|
+
def expect_key_delegates_to_remote(key, command)
|
3
|
+
escape_key = IrPtz.configuration.escape_key
|
4
|
+
STDIN.stubs(:getch).returns key, escape_key
|
5
|
+
|
6
|
+
ir_remote = mock
|
7
|
+
IrPtz::IrRemote.stubs(:new).returns ir_remote
|
8
|
+
ir_remote.stubs(command)
|
9
|
+
|
10
|
+
cli = IrPtz::CommandLine.new
|
11
|
+
cli.stubs(:puts)
|
12
|
+
cli.run
|
13
|
+
|
14
|
+
# expect(ir_remote).to have_received(command)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ir_ptz'
|
3
|
+
require 'ir_ptz/ir_remote'
|
4
|
+
|
5
|
+
describe IrPtz::CommandLine do
|
6
|
+
context 'non remote commands' do
|
7
|
+
let(:escape_key) { IrPtz.configuration.escape_key }
|
8
|
+
let(:record_key) { IrPtz.configuration.record_key }
|
9
|
+
let(:bad_key) { '.' }
|
10
|
+
let(:getch_stub) { STDIN.stubs(:getch) }
|
11
|
+
subject(:cli) { IrPtz::CommandLine.new }
|
12
|
+
|
13
|
+
describe '#run' do
|
14
|
+
before { cli.stubs(:puts) }
|
15
|
+
|
16
|
+
it 'prints the instructions' do
|
17
|
+
instructions = cli.send(:instructions)
|
18
|
+
getch_stub.returns escape_key
|
19
|
+
|
20
|
+
cli.run
|
21
|
+
|
22
|
+
expect(cli).to have_received(:puts).with(instructions)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'listens for the escape key and exits upon receipt' do
|
26
|
+
getch_stub.returns escape_key
|
27
|
+
|
28
|
+
cli.run
|
29
|
+
|
30
|
+
expect(cli).to have_received(:puts).with('Goodbye!')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'prints the instruction when a bad key is pressed' do
|
34
|
+
instructions = cli.send(:instructions)
|
35
|
+
getch_stub.returns bad_key, escape_key
|
36
|
+
|
37
|
+
cli.run
|
38
|
+
|
39
|
+
# twice - once at the start and once for the bad key
|
40
|
+
expect(cli).to have_received(:puts).with(instructions).twice
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'loops until the escape key is pressed' do
|
44
|
+
instructions = cli.send(:instructions)
|
45
|
+
getch_stub.returns bad_key, bad_key, escape_key
|
46
|
+
|
47
|
+
cli.run
|
48
|
+
|
49
|
+
# trice - once at the start and once for each bad key press
|
50
|
+
expect(cli).to have_received(:puts).with(instructions).times(3)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'passes record_new_command to IRCommandRecorder' do
|
54
|
+
IrPtz::IrCommandRecorder.stubs(:record).returns(mock(:save))
|
55
|
+
getch_stub.returns record_key, escape_key
|
56
|
+
|
57
|
+
cli.run
|
58
|
+
|
59
|
+
expect(IrPtz::IrCommandRecorder).to have_received(:record)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'remote specific commands' do
|
64
|
+
IrPtz.configuration.action_mappings.keys.each do |key|
|
65
|
+
it 'passes commands to IR Remote' do
|
66
|
+
IrPtz::IrRemote.instance.stubs(:read_code)
|
67
|
+
|
68
|
+
expect_key_delegates_to_remote key, IrPtz.configuration.action_mappings[key]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ir_ptz'
|
3
|
+
|
4
|
+
describe IrPtz::IrCommandRecorder do
|
5
|
+
describe '#record' do
|
6
|
+
subject(:recorder) { IrPtz::IrCommandRecorder }
|
7
|
+
let(:title) { "xxx\n" }
|
8
|
+
let(:confirmation) { '' }
|
9
|
+
|
10
|
+
|
11
|
+
before do
|
12
|
+
recorder.stubs(:puts)
|
13
|
+
recorder.stubs(:print)
|
14
|
+
recorder.stubs(:getch).returns("x")
|
15
|
+
recorder.stubs(:gets).returns(title, confirmation)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.get_title' do
|
19
|
+
it 'asks the user to enter the command title' do
|
20
|
+
recorder.get_title
|
21
|
+
expect(recorder).to have_received(:print).with 'Enter command title: '
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'reads in the command title' do
|
25
|
+
expect(recorder.get_title).to eql 'xxx'
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'title already exists' do
|
29
|
+
let(:title) { "zoom_in\n" }
|
30
|
+
let(:confirmation) { 'Y' }
|
31
|
+
|
32
|
+
it 'asks if you want to override it' do
|
33
|
+
recorder.get_title
|
34
|
+
expect(recorder).to have_received(:puts).with 'overwrite "zoom_in" ?'
|
35
|
+
expect(recorder).to have_received(:print).with '[Y/n] > '
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns the key if you enter Y' do
|
39
|
+
expect(recorder.get_title).to eql title.chomp
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'you do not enter Y' do
|
43
|
+
let(:confirmation) { 'r' }
|
44
|
+
|
45
|
+
it 'returns nil' do
|
46
|
+
expect(recorder.get_title).to eql nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'creates a new command' do
|
53
|
+
IrPtz::IrRemote.stubs(:instance).returns mock(read_code: '93,44')
|
54
|
+
ir_command = IrPtz::IrCommandRecorder::Command.new('xxx', 'x', '93,44')
|
55
|
+
expect(recorder.record).to eql ir_command
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ir_ptz'
|
3
|
+
require 'arduino_ir_remote'
|
4
|
+
|
5
|
+
describe IrPtz::IrRemote do
|
6
|
+
subject(:ir) { IrPtz::IrRemote.instance }
|
7
|
+
let(:arduino_ir) { mock }
|
8
|
+
|
9
|
+
before do
|
10
|
+
ir.stubs(:ir).returns arduino_ir
|
11
|
+
end
|
12
|
+
|
13
|
+
# concrete example
|
14
|
+
describe 'zoom_in' do
|
15
|
+
before do
|
16
|
+
arduino_ir.stubs(:write)
|
17
|
+
ArduinoIrRemote::DATA['actions']['zoom_in'] = 'some value'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'sends the zoom in signal to the board' do
|
21
|
+
ir.zoom_in
|
22
|
+
expect(arduino_ir).to have_received(:write).with 'some value'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# dynamic example
|
27
|
+
ArduinoIrRemote::DATA['actions'].keys.each do |action|
|
28
|
+
describe 'actions' do
|
29
|
+
before do
|
30
|
+
ArduinoIrRemote::DATA['actions'][action] = 'some value'
|
31
|
+
arduino_ir.stubs(:write)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'can send a defined singnal to the board' do
|
35
|
+
ir.send(action)
|
36
|
+
expect(arduino_ir).to have_received(:write).with 'some value'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#read_code' do
|
42
|
+
it 'returns the read in signal'
|
43
|
+
end
|
44
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'mocha/api'
|
2
|
+
require 'bourne'
|
3
|
+
require 'helpers'
|
4
|
+
|
5
|
+
data_file = File.join(File.dirname(File.expand_path(__FILE__)), '../data/ir_remote.yml')
|
6
|
+
ENV["IR_DATA_FILE"] = data_file
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.mock_with :mocha
|
10
|
+
config.include Helpers
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ir_ptz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Will Mernagh
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: arduino_ir_remote
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.1.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.1.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: mocha
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: bourne
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: CLI to control a Pan Tilt Zoom Camera by InfraRed via an Arduino
|
111
|
+
email:
|
112
|
+
- wmernagh@gmail.com
|
113
|
+
executables:
|
114
|
+
- ir_ptz
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- .gitignore
|
119
|
+
- Gemfile
|
120
|
+
- Gemfile.lock
|
121
|
+
- LICENSE
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- arduino/arduino.ino
|
125
|
+
- bin/ir_ptz
|
126
|
+
- data/ir_remote.yml
|
127
|
+
- ir_ptz.gemspec
|
128
|
+
- lib/ir_ptz.rb
|
129
|
+
- lib/ir_ptz/command_line.rb
|
130
|
+
- lib/ir_ptz/ir_command_recorder.rb
|
131
|
+
- lib/ir_ptz/ir_remote.rb
|
132
|
+
- lib/ir_ptz/version.rb
|
133
|
+
- spec/helpers.rb
|
134
|
+
- spec/ir_ptz/command_line_spec.rb
|
135
|
+
- spec/ir_ptz/ir_command_recorder_spec.rb
|
136
|
+
- spec/ir_ptz/ir_remote_spec.rb
|
137
|
+
- spec/spec_helper.rb
|
138
|
+
homepage: https://github.com/wmernagh/ir_ptz
|
139
|
+
licenses:
|
140
|
+
- MIT
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ! '>='
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ! '>='
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
requirements: []
|
158
|
+
rubyforge_project:
|
159
|
+
rubygems_version: 1.8.23
|
160
|
+
signing_key:
|
161
|
+
specification_version: 3
|
162
|
+
summary: see homepage
|
163
|
+
test_files:
|
164
|
+
- spec/helpers.rb
|
165
|
+
- spec/ir_ptz/command_line_spec.rb
|
166
|
+
- spec/ir_ptz/ir_command_recorder_spec.rb
|
167
|
+
- spec/ir_ptz/ir_remote_spec.rb
|
168
|
+
- spec/spec_helper.rb
|