x10-cm17a 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +189 -0
- data/ext/cm17a_api/MANIFEST +4 -0
- data/ext/cm17a_api/cm17a.c +443 -0
- data/ext/cm17a_api/cm17a.h +63 -0
- data/ext/cm17a_api/cm17a_api.c +114 -0
- data/ext/cm17a_api/extconf.rb +17 -0
- data/lib/x10.rb +102 -0
- data/lib/x10/cm17a.rb +30 -0
- data/lib/x10/cm17a_device.rb +142 -0
- data/setup.rb +1360 -0
- data/test/test_cm17a_controller.rb +34 -0
- data/test/test_cm17a_device.rb +219 -0
- data/test/test_x10.rb +36 -0
- metadata +50 -0
@@ -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")
|
data/lib/x10.rb
ADDED
@@ -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
|
data/lib/x10/cm17a.rb
ADDED
@@ -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
|