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