meac_control 1.0.0

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.
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