x10-cm17a 0.9.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.
@@ -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