x10-cm17a 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ /*
2
+ * Modifications for dual windows/unix compatibility:
3
+ * Copyright 2004 by Jim Weirich (jim@weirichhouse.org)
4
+ * All rights reserved.
5
+ *
6
+ * See the MIT-LICENSE file included with the distribution for
7
+ * details on redistribution rights.
8
+ */
9
+
10
+ /*-
11
+ * Copyright (C) 1999, 2002 Matt Armstrong
12
+ * All rights reserved.
13
+ *
14
+ * Redistribution and use in source and binary forms, with or without
15
+ * modification, are permitted provided that the following conditions
16
+ * are met:
17
+ * 1. Redistributions of source code must retain the above copyright
18
+ * notice, this list of conditions and the following disclaimer.
19
+ * 2. Redistributions in binary form must reproduce the above copyright
20
+ * notice, this list of conditions and the following disclaimer in the
21
+ * documentation and/or other materials provided with the distribution.
22
+ *
23
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33
+ * SUCH DAMAGE.
34
+ */
35
+
36
+ #ifndef CM17A_H
37
+ #define CM17A_H
38
+
39
+ #ifdef CM17A_WIN32
40
+ # include <windows.h>
41
+ # define X10_DEVICE HANDLE
42
+ # define INVALID_X10_DEVICE INVALID_HANDLE_VALUE
43
+ #else
44
+ # define X10_DEVICE int
45
+ # define INVALID_X10_DEVICE -1
46
+ #endif
47
+
48
+ enum CM17A_COMMAND {
49
+ CM17A_ON, CM17A_OFF, CM17A_BRIGHTEN, CM17A_DIM
50
+ };
51
+
52
+ X10_DEVICE cm17a_open_device(const char * device_name);
53
+
54
+ void cm17a_close_device(X10_DEVICE fd);
55
+
56
+ void cm17a_command(
57
+ X10_DEVICE fd,
58
+ int house,
59
+ int device,
60
+ enum CM17A_COMMAND,
61
+ int steps);
62
+
63
+ #endif
@@ -0,0 +1,114 @@
1
+ #include "ruby.h"
2
+ #include "cm17a.h"
3
+
4
+ #include <stdio.h>
5
+ #include <stddef.h>
6
+ #include <ctype.h>
7
+ #include <stdlib.h>
8
+ #include <assert.h>
9
+
10
+ #if HAVE_FCNTL_H
11
+ #include <fcntl.h>
12
+ #endif
13
+ #if HAVE_SYS_IOCTL_H
14
+ #include <sys/ioctl.h>
15
+ #endif
16
+ #if HAVE_SYS_TIME_H
17
+ #include <sys/time.h>
18
+ #endif
19
+ #if HAVE_SYS_TYPES_H
20
+ #include <sys/types.h>
21
+ #endif
22
+ #if HAVE_UNISTD_H
23
+ #include <unistd.h>
24
+ #endif
25
+ #if HAVE_TERMIO_H
26
+ #include <termio.h>
27
+ #endif
28
+
29
+ static X10_DEVICE fd;
30
+
31
+ static VALUE mX10;
32
+ static VALUE mCm17a;
33
+ static VALUE cCm17aController;
34
+ static VALUE eX10Error;
35
+
36
+ /*
37
+ * call-seq:
38
+ * X10::Cm17a::Controller.new()
39
+ * X10::Cm17a::Controller.new(device_name)
40
+ *
41
+ * Create a new CM17A controller on the serial device named
42
+ * +device_name+. If no device name is given, then a default device
43
+ * will be selected (<tt>/dev/ttyS0</tt> for linux and <tt>COM1</tt>
44
+ * for windows).
45
+ */
46
+ static VALUE cm17a_init(int argc, VALUE *argv, VALUE self)
47
+ {
48
+ const char * device_name = DEFAULT_SERIAL_DEVICE;
49
+ if (argc > 0)
50
+ device_name = STR2CSTR(argv[0]);
51
+ fd = cm17a_open_device(device_name);
52
+ if (fd == INVALID_X10_DEVICE)
53
+ rb_raise(eX10Error, "Unable to open cm17a device '%s'", device_name);
54
+ return self;
55
+ }
56
+
57
+ /*
58
+ * call-seq:
59
+ * controller.command(house, unit, command_code, steps)
60
+ *
61
+ * Send a command to the CM17A controller. The X10 unit to get the
62
+ * address is given by the +house+ code (0-15) and the +unit+ code
63
+ * (0-15). The command must be one of the following constants:
64
+ *
65
+ * * <tt>X10::Cm17a::ON</tt> -- Turn the device on.
66
+ * * <tt>X10::Cm17a::OFF</tt> -- Turn the device off.
67
+ * * <tt>X10::Cm17a::DIM</tt> -- Dim the device by +steps+ steps.
68
+ * * <tt>X10::Cm17a::BRIGHT</tt> -- Brighten the device by +steps+ steps.
69
+ *
70
+ * Note that the unit code is ignored for the <tt>BRIGHT</tt> and
71
+ * <tt>DIM</tt> commands. The bright/dim commands will effect the
72
+ * last addressed unit.
73
+ */
74
+ static VALUE cm17a_ruby_command(
75
+ VALUE self,
76
+ VALUE rhouse,
77
+ VALUE rdevice,
78
+ VALUE rcommand,
79
+ VALUE rsteps)
80
+ {
81
+ int house = NUM2INT(rhouse);
82
+ int device = NUM2INT(rdevice);
83
+ int command = NUM2INT(rcommand);
84
+ int steps = NUM2INT(rsteps);
85
+ cm17a_command(fd, house, device, command, steps);
86
+ return Qnil;
87
+ }
88
+
89
+ /*
90
+ * Close the controller device.
91
+ */
92
+ static VALUE cm17a_close(VALUE self)
93
+ {
94
+ cm17a_close_device(fd);
95
+ return Qnil;
96
+ }
97
+
98
+ void Init_cm17a_api()
99
+ {
100
+ mX10 = rb_define_module("X10");
101
+ mCm17a = rb_define_module_under(mX10, "Cm17a");
102
+ cCm17aController = rb_define_class_under(mCm17a, "Controller", rb_cObject);
103
+
104
+ eX10Error = rb_eval_string("X10::X10Error");
105
+
106
+ rb_define_method(cCm17aController, "initialize", cm17a_init, -1);
107
+ rb_define_method(cCm17aController, "close", cm17a_close, 0);
108
+ rb_define_method(cCm17aController, "command", cm17a_ruby_command, 4);
109
+
110
+ rb_define_const(mCm17a, "ON", INT2NUM(CM17A_ON));
111
+ rb_define_const(mCm17a, "OFF", INT2NUM(CM17A_OFF));
112
+ rb_define_const(mCm17a, "BRIGHTEN", INT2NUM(CM17A_BRIGHTEN));
113
+ rb_define_const(mCm17a, "DIM", INT2NUM(CM17A_DIM));
114
+ }
@@ -0,0 +1,17 @@
1
+ require 'mkmf'
2
+ require 'rbconfig'
3
+
4
+ have_header("fcntl.h")
5
+ have_header("sys/ioctl.h")
6
+ have_header("sys/time.h")
7
+ have_header("sys/types.h")
8
+ have_header("unistd.h")
9
+ have_header("termio.h")
10
+
11
+ if Config::CONFIG['arch'] =~ /win32/
12
+ $defs.push "-DCM17A_WIN32"
13
+ $defs.push '-DDEFAULT_SERIAL_DEVICE=\\"COM1\\"'
14
+ else
15
+ $defs.push '-DDEFAULT_SERIAL_DEVICE=\\"/dev/ttyS0\\"'
16
+ end
17
+ create_makefile("cm17a_api")
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # = X10 Interface
4
+ #
5
+ # This is a really simple X10 framework for controlling X10 devices.
6
+ #
7
+ # == Example:
8
+ #
9
+ # require 'x10/cm17a'
10
+ # lamp = X10.device("a1")
11
+ # lamp.on
12
+ # sleep(1)
13
+ # lamp.off
14
+ #
15
+ # == Controllers and Devices
16
+ #
17
+ # An X10 controller is responsible for interfacing to the X10
18
+ # interface device. An example is the CM17A firecracker module.
19
+ #
20
+ # == Default Controllers
21
+ #
22
+ # blah blah blah.
23
+ #
24
+ class Class # :nodoc:
25
+ def x10_controller?
26
+ false
27
+ end
28
+ end
29
+
30
+ # The X10 Module. This module provides a root namespace for all X10
31
+ # devices and software. It also provides a few utility methods for
32
+ # use by the X10 controllers and devices.
33
+ module X10
34
+
35
+ # Error thrown for X10 specific failures.
36
+ class X10Error < RuntimeError; end
37
+
38
+ class << self
39
+ # Create an X10 device at the given address. THe address should
40
+ # be a string specifying the device address is standard X10
41
+ # nomenclature (e.g. a1 ... a16, b1 ... b16, ... p1 ... p16).
42
+ def device(address)
43
+ house, unit = parse_address(address)
44
+ controller.device(house, unit)
45
+ end
46
+
47
+ # Return the controller currently in use.
48
+ def controller
49
+ @controller ||= discover_single_controller
50
+ end
51
+
52
+ # Set the controller to be used to create X10 devices. If there
53
+ # is only one X10 controller loaded, then that controller will be
54
+ # used by default. Otherwise, the controller must be explicitly
55
+ # set using this method.
56
+ def controller=(controller)
57
+ @controller = controller
58
+ end
59
+
60
+ # Make a canonical X10 device address from the house number and
61
+ # unit. House and unit numbers are zero based.
62
+ def make_address(house, unit)
63
+ (house + ?a).chr + (unit+1).to_s
64
+ end
65
+
66
+ # Parse a canonical X10 device adderss into house number and unit
67
+ # number. House and unit numbers are zero based.
68
+ def parse_address(address)
69
+ address = address.downcase
70
+ if address !~ /^([a-p])(\d+)$/
71
+ fail X10::X10Error, "Bad X10 device address [#{address}]"
72
+ end
73
+ house_letter = $1
74
+ unit = $2.to_i - 1
75
+
76
+ if unit < 0 || unit > 15
77
+ fail X10::X10Error, "Bad X10 device address [#{address}]"
78
+ end
79
+
80
+ house = address[0] - ?a
81
+ [house, unit]
82
+ end
83
+
84
+ # If there is only one X10 controller class defined in the object
85
+ # space, then use it by default.
86
+ def discover_single_controller
87
+ controllers = []
88
+ ObjectSpace.each_object(Class) do |c|
89
+ controllers << c if c.x10_controller?
90
+ end
91
+ case controllers.size
92
+ when 0
93
+ fail X10::X10Error, "No X10 Controllers Found"
94
+ when 1
95
+ controllers.first.new
96
+ else
97
+ fail X10::X10Error, "Multiple X10 Controllers Found"
98
+ end
99
+ end
100
+ end
101
+
102
+ end
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'x10'
4
+ require 'cm17a_api'
5
+
6
+ require 'x10/cm17a_device'
7
+
8
+ module X10
9
+
10
+ module Cm17a
11
+
12
+ # The Controller object is the low level interface to the CM17A
13
+ # Firecracker controller. Client software generally uses the
14
+ # device level interface rather than the controller directly.
15
+ class Controller
16
+
17
+ # Create an X10::Cm17a::Device on this controller at the given
18
+ # X10 house and unit address.
19
+ def device(house, unit)
20
+ X10::Cm17a::Device.new(house, unit, self)
21
+ end
22
+
23
+ # Yes, this class represents a X10 controller.
24
+ def self.x10_controller?
25
+ true
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module X10
4
+
5
+ module Cm17a
6
+
7
+ # A CM17A device that responds to on/off and adjust (brightness)
8
+ # commands.
9
+ #
10
+ # Typical use:
11
+ #
12
+ # lamp = X10.device("a2") # Create the device
13
+ # lamp.on # Turn the lamp on
14
+ # lamp.adjust(-0.5) # Dim the lamp
15
+ # lamp.off # Turn the lamp off
16
+ #
17
+ class Device
18
+ attr_reader :address, :controller
19
+
20
+ # Create a new X10 device using a CM17A protocol controller.
21
+ # Such a device can handle on/off commands and simple dim
22
+ # controls.
23
+ #
24
+ # Normally device objects are created through the X10 framework
25
+ # and not directly by the client software.
26
+ def initialize(house, unit, controller)
27
+ @house = house
28
+ @unit = unit
29
+ @controller = controller
30
+ @address = X10.make_address(house, unit)
31
+ end
32
+
33
+ # Turn the device on.
34
+ def on
35
+ @controller.command(@house, @unit, X10::Cm17a::ON, 0)
36
+ @level = 1.0 if @on == false
37
+ @on = true
38
+ end
39
+
40
+ # Turn the device off.
41
+ def off
42
+ @controller.command(@house, @unit, X10::Cm17a::OFF, 0)
43
+ @level = 0.0
44
+ @on = false
45
+ end
46
+
47
+ # Adjust the brightness level.
48
+ #
49
+ # The level adjustment is given as a number between 0 and 1. An
50
+ # adjustment amount of 1 will adjust a completely dim device
51
+ # into a completely bright device. An adjustment of 0.5 will be
52
+ # one half of full range.
53
+ #
54
+ # Negative adjustment amounts dim the device. Positive
55
+ # adjustments brighten the device.
56
+ def adjust(amount)
57
+ steps = steps_for(amount)
58
+ while steps > 0
59
+ step = (steps > 6 ? 6 : steps)
60
+ if amount > 0
61
+ brighten(step)
62
+ else
63
+ dim(step)
64
+ end
65
+ steps -= step
66
+ end
67
+ if @level
68
+ @level += amount
69
+ @level = 0.0 if @level < 0
70
+ @level = 1.0 if @level > 1
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ # -- Questionable Methods --------------------------------------
77
+
78
+ # The following methods are provided as private methods. They
79
+ # are used in testing, but I am uncertain whether they should be
80
+ # part of the public interface, due to the uncertaintity of
81
+ # there implementation.
82
+
83
+ # Is the device on?
84
+ #
85
+ # Since the CM17A protocol is write only, we can't really sense
86
+ # the on/off state of the device, so we remember the last
87
+ # commanded state. This approach has the following
88
+ # disadvantages:
89
+ #
90
+ # * The on/off state is indeterminate until the first command is
91
+ # issued. Asking for the on/off state before the first
92
+ # command will throw an X10::X10Error exception.
93
+ # * If there is more than one device object controlling a single
94
+ # physical device, then the on/off state can be invalidated by
95
+ # second device object.
96
+ def on?
97
+ fail X10::X10Error, "On/Off state is unknown" if @on.nil?
98
+ @on
99
+ end
100
+
101
+ # Is the device off?
102
+ #
103
+ # See also the discussion in the on? method.
104
+ def off?
105
+ ! on?
106
+ end
107
+
108
+ # Return the current brightness level between 0 (very dim) and
109
+ # 1.0 (very bright) of the device.
110
+ #
111
+ # Again, since the CM17A protocol is write only, the same
112
+ # caveats that apply to on? and off? are also applicable here.
113
+ #
114
+ # Furthermore, the real brightness level is a mystery. The
115
+ # protocol docs say that stepping the brighteness level changes
116
+ # it by approximately 5%. However, there seems to be only 10 or
117
+ # so steps from completely dim to completely bright.
118
+ def level
119
+ fail X10::X10Error, "Level is unknown" if @level.nil?
120
+ @level
121
+ end
122
+
123
+ # -- Helper Commands -------------------------------------------
124
+
125
+ # Brightness steps for the given amount.
126
+ def steps_for(amount)
127
+ (amount * 10).round.abs
128
+ end
129
+
130
+ # Send a dim command for the given number of steps.
131
+ def dim(steps)
132
+ @controller.command(@house, @unit, X10::Cm17a::DIM, steps)
133
+ end
134
+
135
+ # Send a brighten command for the given number of steps.
136
+ def brighten(steps)
137
+ @controller.command(@house, @unit, X10::Cm17a::BRIGHTEN, steps)
138
+ end
139
+
140
+ end
141
+ end
142
+ end