meac_control 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/LICENSE +13 -0
  2. data/README.md +35 -0
  3. data/Rakefile +50 -0
  4. data/VERSION.yml +5 -0
  5. data/lib/meac_control.rb +4 -0
  6. data/lib/meac_control/command.rb +4 -0
  7. data/lib/meac_control/command/drive.rb +20 -0
  8. data/lib/meac_control/command/fan_speed.rb +32 -0
  9. data/lib/meac_control/command/generic.rb +32 -0
  10. data/lib/meac_control/command/inlet_temp.rb +11 -0
  11. data/lib/meac_control/device.rb +11 -0
  12. data/lib/meac_control/http.rb +42 -0
  13. data/lib/meac_control/xml.rb +5 -0
  14. data/lib/meac_control/xml/abstract_request.rb +36 -0
  15. data/lib/meac_control/xml/exceptions.rb +14 -0
  16. data/lib/meac_control/xml/get_request.rb +16 -0
  17. data/lib/meac_control/xml/response.rb +44 -0
  18. data/lib/meac_control/xml/set_request.rb +16 -0
  19. data/spec/fixtures/get-request.xml +7 -0
  20. data/spec/fixtures/get-response-error.xml +8 -0
  21. data/spec/fixtures/get-response-ok.xml +7 -0
  22. data/spec/fixtures/set-request.xml +7 -0
  23. data/spec/lib/meac_control/command/drive_spec.rb +28 -0
  24. data/spec/lib/meac_control/command/fan_speed_spec.rb +23 -0
  25. data/spec/lib/meac_control/command/generic_spec.rb +86 -0
  26. data/spec/lib/meac_control/command/inlet_temp_spec.rb +14 -0
  27. data/spec/lib/meac_control/device_spec.rb +23 -0
  28. data/spec/lib/meac_control/http_spec.rb +76 -0
  29. data/spec/lib/meac_control/xml/abstract_request_spec.rb +141 -0
  30. data/spec/lib/meac_control/xml/get_request_spec.rb +26 -0
  31. data/spec/lib/meac_control/xml/response_spec.rb +76 -0
  32. data/spec/lib/meac_control/xml/set_request_spec.rb +26 -0
  33. data/spec/spec.opts +3 -0
  34. data/spec/spec_helper.rb +13 -0
  35. data/spec/support/shared_examples.rb +5 -0
  36. metadata +130 -0
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2010 Bernd Ahlers <bernd@tuneafish.de>
2
+
3
+ Permission to use, copy, modify, and distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ ## MEACControl
2
+
3
+ The library provides some Ruby classes to interact with a Mitsubishi Electric
4
+ G-50A centralized controller. This device is used to control up to 50 indoor
5
+ air conditioning units.
6
+
7
+ The G-50A offers a XML API for the communication.
8
+
9
+ So far, the library has only been tested with a G-50A. I'm not sure if there
10
+ are other controller/devices which use the same API.
11
+
12
+ ### Security
13
+
14
+ Mitsubishi recommends to put the controller into a private network. In fact,
15
+ there's no authentication or encryption needed to access the controller.
16
+ There's been a post to the BugTraq mailing list in March 2008 regarding that.
17
+ [Archive](http://www.securityfocus.com/archive/1/489970)
18
+
19
+ ### Code Examples
20
+
21
+ Please see the `example` directory for code examples.
22
+
23
+ ### Note on Patches/Pull Requests
24
+
25
+ * Fork the project.
26
+ * Write a spec to cover a bug or a new feature.
27
+ * Make your feature addition or bug fix.
28
+ * Commit, do not mess with rakefile, version, or history.
29
+ (if you want to have your own version, that is fine but bump version in a
30
+ commit by itself I can ignore when I pull)
31
+ * Send me a pull request. Bonus points for topic branches.
32
+
33
+ ### Copyright
34
+
35
+ Copyright (c) 2010 Bernd Ahlers. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "meac_control"
8
+ gem.summary = %Q{Library to communicate with a Mitsubishi Electric G-50A centralized controller}
9
+ gem.description = %Q{Library to communicate with a Mitsubishi Electric G-50A centralized controller}
10
+ gem.email = "bernd@tuneafish.de"
11
+ gem.homepage = "http://github.com/bernd/meac_control"
12
+ gem.authors = ["Bernd Ahlers"]
13
+
14
+ gem.files = FileList["LICENSE", "README.md", "Rakefile", "VERSION.yml", "{lib,spec}/**/*"]
15
+
16
+ gem.add_development_dependency "rspec", ">= 1.2.9"
17
+
18
+ gem.add_dependency "httpclient"
19
+ gem.add_dependency "nokogiri"
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:spec) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.spec_files = FileList['spec/**/*_spec.rb']
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ task :spec => :check_dependencies
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = Jeweler::VersionHelper.new(File.dirname(__FILE__))
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "MEACControl #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :build:
3
+ :major: 1
4
+ :minor: 0
5
+ :patch: 0
@@ -0,0 +1,4 @@
1
+ require 'meac_control/command'
2
+ require 'meac_control/device'
3
+ require 'meac_control/http'
4
+ require 'meac_control/xml'
@@ -0,0 +1,4 @@
1
+ require 'meac_control/command/generic'
2
+ require 'meac_control/command/drive'
3
+ require 'meac_control/command/fan_speed'
4
+ require 'meac_control/command/inlet_temp'
@@ -0,0 +1,20 @@
1
+ require 'meac_control/command/generic'
2
+
3
+ module MEACControl
4
+ module Command
5
+ class Drive < Generic
6
+
7
+ def initialize
8
+ @command = 'Drive'
9
+ end
10
+
11
+ def on
12
+ @value = 'ON'
13
+ end
14
+
15
+ def off
16
+ @value = 'OFF'
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ require 'meac_control/command/generic'
2
+
3
+ module MEACControl
4
+ module Command
5
+ class FanSpeed < Generic
6
+
7
+ def initialize
8
+ @command = 'FanSpeed'
9
+ end
10
+
11
+ def low
12
+ @value = 'low'
13
+ end
14
+
15
+ def mid1
16
+ @value = 'mid1'
17
+ end
18
+
19
+ def mid2
20
+ @value = 'mid2'
21
+ end
22
+
23
+ def high
24
+ @value = 'high'
25
+ end
26
+
27
+ def auto
28
+ @value = 'auto'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module MEACControl
2
+ module Command
3
+ class InvalidValue < Exception
4
+ end
5
+
6
+ class InvalidMode < Exception
7
+ end
8
+
9
+ class Generic
10
+ attr_reader :command, :value
11
+
12
+ def self.request
13
+ new.freeze
14
+ end
15
+
16
+ def hash_for(mode)
17
+ if mode == :set
18
+ raise MEACControl::Command::InvalidValue if (value.nil? or value.empty?)
19
+ {command.to_sym => value}
20
+ elsif mode == :get
21
+ {command.to_sym => '*'}
22
+ else
23
+ raise MEACControl::Command::InvalidMode
24
+ end
25
+ end
26
+
27
+ def command_set?
28
+ (value.nil? or value.empty?) ? false : true
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ require 'meac_control/command/generic'
2
+
3
+ module MEACControl
4
+ module Command
5
+ class InletTemp < Generic
6
+ def initialize
7
+ @command = 'InletTemp'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module MEACControl
2
+ class Device
3
+ attr_reader :id
4
+ attr_accessor :name
5
+
6
+ def initialize(device_id, options = {})
7
+ @id = device_id
8
+ @name = options[:name] if options[:name]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ require 'meac_control/xml/get_request'
2
+ require 'meac_control/xml/set_request'
3
+ require 'meac_control/xml/response'
4
+ require 'httpclient'
5
+
6
+ module MEACControl
7
+ class HTTP
8
+ URI_TEMPLATE = 'http://%s/servlet/MIMEReceiveServlet'
9
+ DEFAULT_HEADER = {'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}
10
+
11
+ class << self
12
+ # Executes a get request and returns a MEACControl::XML::Response object.
13
+ #
14
+ # Example:
15
+ # device = MEACControl::Device.new(23)
16
+ # command = MEACControl::Command::Drive.request
17
+ # resp = MEACControl::HTTP.get('127.0.0.1', device, command)
18
+ # resp.inspect # => "#<MEACControl::XML::Response:0x8bea3ef0 ...>"
19
+ def get(host, devices, commands)
20
+ action(host, MEACControl::XML::GetRequest.new(devices, commands))
21
+ end
22
+
23
+ # Executes a set request and returns a MEACControl::XML::Response object.
24
+ #
25
+ # Example:
26
+ # device = MEACControl::Device.new(23)
27
+ # command = MEACControl::Command::Drive.new
28
+ # command.off
29
+ # resp = MEACControl::HTTP.set('127.0.0.1', device, command)
30
+ # resp.inspect # => "#<MEACControl::XML::Response:0x8bea3ef0 ...>"
31
+ def set(host, devices, commands)
32
+ action(host, MEACControl::XML::SetRequest.new(devices, commands))
33
+ end
34
+
35
+ private
36
+ def action(host, request)
37
+ resp = HTTPClient.post(sprintf(URI_TEMPLATE, host), request.to_xml, DEFAULT_HEADER)
38
+ MEACControl::XML::Response.new(resp.content, request)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ require 'meac_control/xml/exceptions'
2
+ require 'meac_control/xml/abstract_request'
3
+ require 'meac_control/xml/get_request'
4
+ require 'meac_control/xml/set_request'
5
+ require 'meac_control/xml/response'
@@ -0,0 +1,36 @@
1
+ require 'nokogiri'
2
+ require 'meac_control/xml/exceptions'
3
+
4
+ module MEACControl
5
+ module XML
6
+ class AbstractRequest
7
+ attr_reader :devices, :commands
8
+
9
+ def initialize(devices, commands)
10
+ @devices = [devices].compact.flatten
11
+ @commands = [commands].compact.flatten
12
+
13
+ raise MEACControl::XML::Request::EmptyDeviceList if @devices.empty?
14
+ raise MEACControl::XML::Request::EmptyCommandList if @commands.empty?
15
+ end
16
+
17
+ private
18
+ def xml_template(command, mode)
19
+ ::Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do
20
+ Packet do
21
+ Command command
22
+ DatabaseManager do
23
+ devices.each do |dev|
24
+ attributes = {:Group => dev.id}
25
+ commands.each do |cmd|
26
+ attributes.merge!(cmd.hash_for(mode))
27
+ end
28
+ Mnet(attributes)
29
+ end
30
+ end
31
+ end
32
+ end.to_xml
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ module MEACControl
2
+ module XML
3
+ class InvalidResponse < Exception
4
+ end
5
+
6
+ module Request
7
+ class EmptyDeviceList < Exception
8
+ end
9
+
10
+ class EmptyCommandList < Exception
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require 'meac_control/xml/abstract_request'
2
+
3
+ module MEACControl
4
+ module XML
5
+ class GetRequest < AbstractRequest
6
+ # returns a xml get request.
7
+ #
8
+ # example:
9
+ # req = MEACControl::XML::GetRequest.new(device, command)
10
+ # req.to_xml # => "<?xml version="1.0" encoding="UTF-8"?>..."
11
+ def to_xml
12
+ xml_template('getRequest', :get)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ require 'nokogiri'
2
+ require 'meac_control/xml/exceptions'
3
+
4
+ module MEACControl
5
+ module XML
6
+ class Response
7
+ attr_reader :xml, :request
8
+
9
+ def initialize(xml, request = nil)
10
+ @xml = ::Nokogiri::XML(xml)
11
+ @request = request
12
+ raise(MEACControl::XML::InvalidResponse, @xml.to_s) if @xml.root.nil?
13
+ end
14
+
15
+ def to_xml
16
+ @xml.to_s
17
+ end
18
+
19
+ def ok?
20
+ !errors?
21
+ end
22
+
23
+ def errors?
24
+ !@xml.xpath('/Packet/DatabaseManager/ERROR').empty?
25
+ end
26
+
27
+ def errors
28
+ @xml.xpath('/Packet/DatabaseManager/ERROR').map do |error|
29
+ data = {}
30
+ error.each do |key, value|
31
+ data[key] = value
32
+ end
33
+ data
34
+ end
35
+ end
36
+
37
+ def error_messages
38
+ errors.map do |error|
39
+ error['Message']
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ require 'meac_control/xml/abstract_request'
2
+
3
+ module MEACControl
4
+ module XML
5
+ class SetRequest < AbstractRequest
6
+ # returns a xml set request.
7
+ #
8
+ # example:
9
+ # req = MEACControl::XML::SetRequest.new(device, command)
10
+ # req.to_xml # => "<?xml version="1.0" encoding="UTF-8"?>..."
11
+ def to_xml
12
+ xml_template('setRequest', :set)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Packet>
3
+ <Command>getRequest</Command>
4
+ <DatabaseManager>
5
+ <Mnet Group="23" Drive="*"/>
6
+ </DatabaseManager>
7
+ </Packet>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Packet>
3
+ <Command>geErrorResponse</Command>
4
+ <DatabaseManager>
5
+ <Mnet Drive="*" Group="31"/>
6
+ <ERROR Point="geRequest" Code="0102" Message="Insufficiency Attribute"/>
7
+ </DatabaseManager>
8
+ </Packet>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Packet>
3
+ <Command>getResponse</Command>
4
+ <DatabaseManager>
5
+ <Mnet Drive="OFF" Group="31"/>
6
+ </DatabaseManager>
7
+ </Packet>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Packet>
3
+ <Command>getRequest</Command>
4
+ <DatabaseManager>
5
+ <Mnet Group="23" Drive="OFF"/>
6
+ </DatabaseManager>
7
+ </Packet>
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+ require 'meac_control/command/drive'
3
+
4
+ describe MEACControl::Command::Drive do
5
+ before(:each) do
6
+ @cmd = MEACControl::Command::Drive.new
7
+ end
8
+
9
+ it_should_behave_like "a class that includes MEACControl::Command::Generic"
10
+
11
+ it "has set the command to 'Drive'" do
12
+ @cmd.command.should == 'Drive'
13
+ end
14
+
15
+ describe "#on" do
16
+ it "sets the value to 'ON'" do
17
+ @cmd.on
18
+ @cmd.value.should == 'ON'
19
+ end
20
+ end
21
+
22
+ describe "#off" do
23
+ it "sets the value to 'OFF'" do
24
+ @cmd.off
25
+ @cmd.value.should == 'OFF'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+ require 'meac_control/command/fan_speed'
3
+
4
+ describe MEACControl::Command::FanSpeed do
5
+ before(:each) do
6
+ @cmd = MEACControl::Command::FanSpeed.new
7
+ end
8
+
9
+ it_should_behave_like "a class that includes MEACControl::Command::Generic"
10
+
11
+ it "has set the command to 'FanSpeed'" do
12
+ @cmd.command.should == 'FanSpeed'
13
+ end
14
+
15
+ %w{low mid1 mid2 high auto}.each do |c|
16
+ describe "##{c}" do
17
+ it "sets the value to '#{c}'" do
18
+ @cmd.send(c)
19
+ @cmd.value.should == c
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+ require 'meac_control/command/generic'
3
+
4
+ class Cmd < MEACControl::Command::Generic
5
+ def initialize
6
+ @command = "MyCommand"
7
+ @value = "MyValue"
8
+ end
9
+
10
+ def on
11
+ @value = 'ON'
12
+ end
13
+ end
14
+
15
+ describe MEACControl::Command::Generic do
16
+ before(:each) do
17
+ @obj = Cmd.new
18
+ end
19
+
20
+ describe ".request" do
21
+ it "returns a new frozen object" do
22
+ Cmd.request.should be_frozen
23
+ end
24
+
25
+ it "does not allow modifications" do
26
+ lambda { Cmd.request.on }.should raise_error(TypeError)
27
+ end
28
+ end
29
+
30
+ describe "#hash_for" do
31
+ it "will raise an exception if it's called without argument" do
32
+ lambda { @obj.hash_for }.should raise_error(ArgumentError)
33
+ end
34
+
35
+ context "with the :get argument" do
36
+ it "returns a hash with the command name as key and a value of '*'" do
37
+ @obj.hash_for(:get).should == {@obj.command.to_sym => '*'}
38
+ end
39
+ end
40
+
41
+ context "with the :set argument" do
42
+ it "returns a hash with the command as key and the command value as value" do
43
+ @obj.hash_for(:set).should == {@obj.command.to_sym => @obj.value}
44
+ end
45
+
46
+ it "will raise an exception if value is nil" do
47
+ @obj.stub!(:value).and_return(nil)
48
+ lambda { @obj.hash_for(:set) }.should raise_error(MEACControl::Command::InvalidValue)
49
+ end
50
+
51
+ it "will raise an exception if value is an empty string" do
52
+ @obj.stub!(:value).and_return('')
53
+ lambda { @obj.hash_for(:set) }.should raise_error(MEACControl::Command::InvalidValue)
54
+ end
55
+ end
56
+
57
+ context "with an unknown argument" do
58
+ it "will raise an exception" do
59
+ lambda { @obj.hash_for(:foobar_unknown) }.should raise_error(MEACControl::Command::InvalidMode)
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#command_set?" do
65
+ context "with value set to 'YES'" do
66
+ it "returns true" do
67
+ @obj.stub!(:value).and_return('YES')
68
+ @obj.command_set?.should be_true
69
+ end
70
+ end
71
+
72
+ context "with value set to an empty string" do
73
+ it "returns false" do
74
+ @obj.stub!(:value).and_return('')
75
+ @obj.command_set?.should be_false
76
+ end
77
+ end
78
+
79
+ context "with value set to nil" do
80
+ it "returns false" do
81
+ @obj.stub!(:value).and_return(nil)
82
+ @obj.command_set?.should be_false
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+ require 'meac_control/command/inlet_temp'
3
+
4
+ describe MEACControl::Command::InletTemp do
5
+ before(:each) do
6
+ @cmd = MEACControl::Command::InletTemp.new
7
+ end
8
+
9
+ it_should_behave_like "a class that includes MEACControl::Command::Generic"
10
+
11
+ it "has set the command to 'InletTemp'" do
12
+ @cmd.command.should == 'InletTemp'
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+ require 'meac_control/device'
3
+
4
+ describe MEACControl::Device do
5
+ describe "#id" do
6
+ it "returns the device id" do
7
+ MEACControl::Device.new(34).id.should == 34
8
+ end
9
+ end
10
+
11
+ describe "#name" do
12
+ it "returns the device name" do
13
+ device = MEACControl::Device.new(23)
14
+ device.name = "fooac"
15
+ device.name.should == "fooac"
16
+ end
17
+ end
18
+
19
+ it "will set the name value with the initializer option :name" do
20
+ device = MEACControl::Device.new(22, :name => "foobarac")
21
+ device.name.should == "foobarac"
22
+ end
23
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+ require 'meac_control/http'
3
+
4
+ describe MEACControl::HTTP do
5
+ describe "URI_TEMPLATE constant" do
6
+ it "can be used to build the correct uri" do
7
+ uri = sprintf(MEACControl::HTTP::URI_TEMPLATE, '10.0.0.1')
8
+ uri.should == 'http://10.0.0.1/servlet/MIMEReceiveServlet'
9
+ end
10
+ end
11
+
12
+ describe "DEFAULT_HEADER constant" do
13
+ it "has 'Accept' set to 'text/xml'" do
14
+ MEACControl::HTTP::DEFAULT_HEADER['Accept'].should == 'text/xml'
15
+ end
16
+
17
+ it "has 'Content-Type' set to 'text/xml'" do
18
+ MEACControl::HTTP::DEFAULT_HEADER['Content-Type'].should == 'text/xml'
19
+ end
20
+ end
21
+
22
+ before(:each) do
23
+ @device = mock('device')
24
+ @command = mock('command')
25
+ @hostname = '127.0.0.1'
26
+ @uri = sprintf(MEACControl::HTTP::URI_TEMPLATE, @hostname)
27
+ end
28
+
29
+ describe ".get" do
30
+ before(:each) do
31
+ @xml = mock('xml_request', :to_xml => fixture_read('get-request.xml'))
32
+
33
+ HTTPClient.should_receive(:post).with(@uri, @xml.to_xml, MEACControl::HTTP::DEFAULT_HEADER).and_return do
34
+ mock('http_response', :content => fixture_read('get-response-ok.xml'))
35
+ end
36
+
37
+ MEACControl::XML::GetRequest.should_receive(:new).with(@device, @command).and_return(@xml)
38
+ end
39
+
40
+ it "executes a get request" do
41
+ MEACControl::HTTP.get(@hostname, @device, @command)
42
+ end
43
+
44
+ it "returns a response object" do
45
+ MEACControl::HTTP.get(@hostname, @device, @command).should be_ok
46
+ end
47
+
48
+ it "returns a response object which includes the request object" do
49
+ MEACControl::HTTP.get(@hostname, @device, @command).request.should == @xml
50
+ end
51
+ end
52
+
53
+ describe ".set" do
54
+ before(:each) do
55
+ @xml = mock('xml_request', :to_xml => fixture_read('set-request.xml'))
56
+
57
+ HTTPClient.should_receive(:post).with(@uri, @xml.to_xml, MEACControl::HTTP::DEFAULT_HEADER).and_return do
58
+ mock('http_response', :content => fixture_read('get-response-ok.xml'))
59
+ end
60
+
61
+ MEACControl::XML::SetRequest.should_receive(:new).with(@device, @command).and_return(@xml)
62
+ end
63
+
64
+ it "executes a set request" do
65
+ MEACControl::HTTP.set(@hostname, @device, @command)
66
+ end
67
+
68
+ it "returns a response object" do
69
+ MEACControl::HTTP.set(@hostname, @device, @command).should be_ok
70
+ end
71
+
72
+ it "returns a response object which includes the request object" do
73
+ MEACControl::HTTP.set(@hostname, @device, @command).request.should == @xml
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,141 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+ require 'meac_control/xml/get_request'
3
+
4
+ class Dev; end
5
+ class Cmd; end
6
+
7
+ class MyRequest < MEACControl::XML::AbstractRequest
8
+ def to_xml
9
+ xml_template('myRequest', :get)
10
+ end
11
+ end
12
+
13
+ describe MEACControl::XML::AbstractRequest do
14
+ it "creates a get request with one device and a list of commands" do
15
+ req = MyRequest.new(:one, [:one, :two, :three])
16
+ req.should have(1).devices
17
+ req.should have(3).commands
18
+ end
19
+
20
+ it "creates a get request with a list of devices and commands" do
21
+ req = MyRequest.new([:one, :two], [:one, :two, :three])
22
+ req.should have(2).devices
23
+ req.should have(3).commands
24
+ end
25
+
26
+ it "creates a get request with one device and one command" do
27
+ req = MyRequest.new(:two, :one)
28
+ req.should have(1).devices
29
+ req.should have(1).commands
30
+ end
31
+
32
+ it "fails to create a valid get request with an empty device list" do
33
+ lambda {
34
+ MyRequest.new([], [:one, :two, :three])
35
+ }.should raise_error(MEACControl::XML::Request::EmptyDeviceList)
36
+ end
37
+
38
+ it "fails to create a valid get request with a nil device" do
39
+ lambda {
40
+ MyRequest.new(nil, [:one, :two, :three])
41
+ }.should raise_error(MEACControl::XML::Request::EmptyDeviceList)
42
+ end
43
+
44
+ it "fails to create a valid get request with a nil command" do
45
+ lambda {
46
+ MyRequest.new([:one, :two, :three], nil)
47
+ }.should raise_error(MEACControl::XML::Request::EmptyCommandList)
48
+ end
49
+
50
+ describe "#xml_template" do
51
+ it "is not accessible as an instance method" do
52
+ req = MyRequest.new(:one, :two)
53
+ lambda { req.xml_template }.should raise_error(NoMethodError)
54
+ end
55
+ end
56
+
57
+ # Example XML output:
58
+ # <?xml version="1.0" encoding="UTF-8"?>
59
+ # <Packet>
60
+ # <Command>getRequest</Command>
61
+ # <DatabaseManager>
62
+ # <Mnet Group="23" Drive="*"/>
63
+ # </DatabaseManager>
64
+ # </Packet>
65
+ describe "#to_xml" do
66
+ before(:each) do
67
+ device = mock(Dev, :id => 23)
68
+ commands = [
69
+ mock(Cmd, :hash_for => {:Command1 => '*'}),
70
+ mock(Cmd, :hash_for => {:Command2 => '*'}),
71
+ mock(Cmd, :hash_for => {:Command3 => '*'})
72
+ ]
73
+ @req = MyRequest.new(device, commands)
74
+ @xml = Nokogiri::XML(@req.to_xml)
75
+ end
76
+
77
+ it "has one root node named 'Packet'" do
78
+ @xml.root.name.should == 'Packet'
79
+ end
80
+
81
+ describe "<Packet>" do
82
+ it "has one child node named 'Command'" do
83
+ @xml.root.search('/Packet/Command').size.should == 1
84
+ end
85
+
86
+ it "has one child node named 'DatabaseManager'" do
87
+ @xml.root.search('/Packet/DatabaseManager').size.should == 1
88
+ end
89
+ end
90
+
91
+ describe "<Command>" do
92
+ it "has a text of 'getRequest'" do
93
+ @xml.root.at('/Packet/Command').text.should == "myRequest"
94
+ end
95
+
96
+ it "has a parent node named 'Packet'" do
97
+ @xml.root.at('/Packet/Command').parent.name.should == "Packet"
98
+ end
99
+ end
100
+
101
+ describe "<DatabaseManager>" do
102
+ it "has one chile node named 'Mnet'" do
103
+ @xml.root.search('/Packet/DatabaseManager/Mnet').size.should == 1
104
+ end
105
+
106
+ it "has a parent node named 'Packet'" do
107
+ @xml.root.at('/Packet/DatabaseManager').parent.name.should == "Packet"
108
+ end
109
+ end
110
+
111
+ describe "<Mnet>" do
112
+ before(:each) do
113
+ @mnet = @xml.root.at('/Packet/DatabaseManager/Mnet')
114
+ end
115
+
116
+ it "has a 'Group' attribute with value '23'" do
117
+ @mnet.key?('Group').should be_true
118
+ @mnet.attribute('Group').value.should == "23"
119
+ end
120
+
121
+ it "has a 'Command1' attribute with value '*'" do
122
+ @mnet.key?('Command1').should be_true
123
+ @mnet.attribute('Command1').value.should == "*"
124
+ end
125
+
126
+ it "has a 'Command2' attribute with value '*'" do
127
+ @mnet.key?('Command2').should be_true
128
+ @mnet.attribute('Command2').value.should == "*"
129
+ end
130
+
131
+ it "has a 'Command3' attribute with value '*'" do
132
+ @mnet.key?('Command3').should be_true
133
+ @mnet.attribute('Command3').value.should == "*"
134
+ end
135
+
136
+ it "has a parent node named 'DatabaseManager'" do
137
+ @xml.root.at('/Packet/DatabaseManager/Mnet').parent.name.should == "DatabaseManager"
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+ require 'meac_control/xml/get_request'
3
+
4
+ class Dev; end
5
+ class Cmd; end
6
+
7
+ describe MEACControl::XML::GetRequest do
8
+ it "inherits from the MEACControl::XML::AbstractRequest class" do
9
+ req = MEACControl::XML::GetRequest.new(:one, [:one, :two, :three])
10
+ req.should be_kind_of(MEACControl::XML::AbstractRequest)
11
+ end
12
+
13
+ describe "#to_xml" do
14
+ it "returns a xml string with 'getRequest' in the <Command> element" do
15
+ device = mock(Dev, :id => 23)
16
+ commands = [
17
+ mock(Cmd, :hash_for => {:Command1 => 'ON'}),
18
+ mock(Cmd, :hash_for => {:Command2 => 'ON'}),
19
+ mock(Cmd, :hash_for => {:Command3 => 'ON'})
20
+ ]
21
+ req = MEACControl::XML::GetRequest.new(device, commands)
22
+ xml = Nokogiri::XML(req.to_xml)
23
+ xml.root.at('/Packet/Command').text.should == "getRequest"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+ require 'meac_control/xml/response'
3
+
4
+ describe MEACControl::XML::Response do
5
+ before(:each) do
6
+ @string_ok = fixture_read('get-response-ok.xml')
7
+ @string_error = fixture_read('get-response-error.xml')
8
+ @request = mock('xml_request')
9
+ end
10
+
11
+ it "creates a response from a xml string" do
12
+ response = MEACControl::XML::Response.new(@string_ok)
13
+ response.xml.should be_a(Nokogiri::XML::Document)
14
+ end
15
+
16
+ it "creates a response from a xml string and a request object" do
17
+ response = MEACControl::XML::Response.new(@string_ok, @request)
18
+ response.xml.should be_a(Nokogiri::XML::Document)
19
+ end
20
+
21
+ it "will raise an error if the XML response has no root node" do
22
+ lambda { MEACControl::XML::Response.new('foo') }.should raise_error(MEACControl::XML::InvalidResponse)
23
+ end
24
+
25
+ describe "#request" do
26
+ it "returns the request object" do
27
+ response = MEACControl::XML::Response.new(@string_ok, @request)
28
+ response.request.should == @request
29
+ end
30
+ end
31
+
32
+ describe "#to_xml" do
33
+ it "returns the response xml string" do
34
+ response = MEACControl::XML::Response.new(@string_ok)
35
+ response.to_xml.should == @string_ok
36
+ end
37
+ end
38
+
39
+ describe "#ok?" do
40
+ it "returns true if the response has no error messages" do
41
+ response = MEACControl::XML::Response.new(@string_ok)
42
+ response.ok?.should be_true
43
+ end
44
+
45
+ it "returns false if the response has an error message" do
46
+ response = MEACControl::XML::Response.new(@string_error)
47
+ response.ok?.should be_false
48
+ end
49
+ end
50
+
51
+ describe "#errors?" do
52
+ it "returns true if the response has an error message" do
53
+ response = MEACControl::XML::Response.new(@string_error)
54
+ response.errors?.should be_true
55
+ end
56
+
57
+ it "returns false if the response has no error message" do
58
+ response = MEACControl::XML::Response.new(@string_ok)
59
+ response.errors?.should be_false
60
+ end
61
+ end
62
+
63
+ describe "#errors" do
64
+ it "returns a list of hashes with the error messages" do
65
+ response = MEACControl::XML::Response.new(@string_error)
66
+ response.errors.should == [{'Point' => 'geRequest', 'Code' => '0102', 'Message' => 'Insufficiency Attribute'}]
67
+ end
68
+ end
69
+
70
+ describe "#error_messages" do
71
+ it "returns a list of error message strings" do
72
+ response = MEACControl::XML::Response.new(@string_error)
73
+ response.error_messages.should == ['Insufficiency Attribute']
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+ require 'meac_control/xml/set_request'
3
+
4
+ class Dev; end
5
+ class Cmd; end
6
+
7
+ describe MEACControl::XML::SetRequest do
8
+ it "inherits from the MEACControl::XML::AbstractRequest class" do
9
+ req = MEACControl::XML::SetRequest.new(:one, [:one, :two, :three])
10
+ req.should be_kind_of(MEACControl::XML::AbstractRequest)
11
+ end
12
+
13
+ describe "#to_xml" do
14
+ it "returns a xml string with 'setRequest' in the <Command> element" do
15
+ device = mock(Dev, :id => 23)
16
+ commands = [
17
+ mock(Cmd, :hash_for => {:Command1 => 'ON'}),
18
+ mock(Cmd, :hash_for => {:Command2 => 'ON'}),
19
+ mock(Cmd, :hash_for => {:Command3 => 'ON'})
20
+ ]
21
+ req = MEACControl::XML::SetRequest.new(device, commands)
22
+ xml = Nokogiri::XML(req.to_xml)
23
+ xml.root.at('/Packet/Command').text.should == "setRequest"
24
+ end
25
+ end
26
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format specdoc
3
+ --diff unified
@@ -0,0 +1,13 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+ require 'spec'
4
+
5
+ SPEC_ROOT = Pathname(__FILE__).dirname.expand_path
6
+ $:.unshift File.join(SPEC_ROOT.parent, 'lib')
7
+
8
+ # Pull the shared examples.
9
+ require File.join(SPEC_ROOT, 'support', 'shared_examples')
10
+
11
+ def fixture_read(filename)
12
+ File.read(File.join(SPEC_ROOT, 'fixtures', filename))
13
+ end
@@ -0,0 +1,5 @@
1
+ shared_examples_for "a class that includes MEACControl::Command::Generic" do
2
+ it "includes MEACControl::Command::Generic" do
3
+ @cmd.class.ancestors.should include(MEACControl::Command::Generic)
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: meac_control
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Bernd Ahlers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-03 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.9
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: httpclient
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: nokogiri
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Library to communicate with a Mitsubishi Electric G-50A centralized controller
46
+ email: bernd@tuneafish.de
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.md
54
+ files:
55
+ - LICENSE
56
+ - README.md
57
+ - Rakefile
58
+ - VERSION.yml
59
+ - lib/meac_control.rb
60
+ - lib/meac_control/command.rb
61
+ - lib/meac_control/command/drive.rb
62
+ - lib/meac_control/command/fan_speed.rb
63
+ - lib/meac_control/command/generic.rb
64
+ - lib/meac_control/command/inlet_temp.rb
65
+ - lib/meac_control/device.rb
66
+ - lib/meac_control/http.rb
67
+ - lib/meac_control/xml.rb
68
+ - lib/meac_control/xml/abstract_request.rb
69
+ - lib/meac_control/xml/exceptions.rb
70
+ - lib/meac_control/xml/get_request.rb
71
+ - lib/meac_control/xml/response.rb
72
+ - lib/meac_control/xml/set_request.rb
73
+ - spec/fixtures/get-request.xml
74
+ - spec/fixtures/get-response-error.xml
75
+ - spec/fixtures/get-response-ok.xml
76
+ - spec/fixtures/set-request.xml
77
+ - spec/lib/meac_control/command/drive_spec.rb
78
+ - spec/lib/meac_control/command/fan_speed_spec.rb
79
+ - spec/lib/meac_control/command/generic_spec.rb
80
+ - spec/lib/meac_control/command/inlet_temp_spec.rb
81
+ - spec/lib/meac_control/device_spec.rb
82
+ - spec/lib/meac_control/http_spec.rb
83
+ - spec/lib/meac_control/xml/abstract_request_spec.rb
84
+ - spec/lib/meac_control/xml/get_request_spec.rb
85
+ - spec/lib/meac_control/xml/response_spec.rb
86
+ - spec/lib/meac_control/xml/set_request_spec.rb
87
+ - spec/spec.opts
88
+ - spec/spec_helper.rb
89
+ - spec/support/shared_examples.rb
90
+ has_rdoc: true
91
+ homepage: http://github.com/bernd/meac_control
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options:
96
+ - --charset=UTF-8
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: "0"
104
+ version:
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: "0"
110
+ version:
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.3.5
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Library to communicate with a Mitsubishi Electric G-50A centralized controller
118
+ test_files:
119
+ - spec/lib/meac_control/command/drive_spec.rb
120
+ - spec/lib/meac_control/command/fan_speed_spec.rb
121
+ - spec/lib/meac_control/command/generic_spec.rb
122
+ - spec/lib/meac_control/command/inlet_temp_spec.rb
123
+ - spec/lib/meac_control/http_spec.rb
124
+ - spec/lib/meac_control/device_spec.rb
125
+ - spec/lib/meac_control/xml/get_request_spec.rb
126
+ - spec/lib/meac_control/xml/set_request_spec.rb
127
+ - spec/lib/meac_control/xml/abstract_request_spec.rb
128
+ - spec/lib/meac_control/xml/response_spec.rb
129
+ - spec/spec_helper.rb
130
+ - spec/support/shared_examples.rb