| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package ControlX10::CM11; |
|
2
|
|
|
|
|
|
|
#----------------------------------------------------------------------------- |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# An X10 ActiveHome interface, used by Misterhouse ( http://misterhouse.net ) |
|
5
|
|
|
|
|
|
|
# |
|
6
|
|
|
|
|
|
|
# Uses the Windows or Posix SerialPort.pm functions by Bill Birthisel, |
|
7
|
|
|
|
|
|
|
# available on CPAN |
|
8
|
|
|
|
|
|
|
# |
|
9
|
|
|
|
|
|
|
#----------------------------------------------------------------------------- |
|
10
|
1
|
|
|
1
|
|
4079
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
42
|
|
|
11
|
1
|
|
|
1
|
|
5
|
use vars qw($VERSION $DEBUG @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
6087
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
require Exporter; |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
|
16
|
|
|
|
|
|
|
@EXPORT= qw( send_cm11 receive_cm11 read_cm11 dim_decode_cm11 ); |
|
17
|
|
|
|
|
|
|
@EXPORT_OK= qw(); |
|
18
|
|
|
|
|
|
|
%EXPORT_TAGS = (FUNC => [qw( send_cm11 receive_cm11 |
|
19
|
|
|
|
|
|
|
read_cm11 dim_decode_cm11 )]); |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
Exporter::export_ok_tags('FUNC'); |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
$EXPORT_TAGS{ALL} = \@EXPORT_OK; |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
#### Package variable declarations #### |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
($VERSION) = q$Revision: 2.09 $ =~ /: (\S+)/; # Note: cvs version reset when we moved to sourceforge |
|
28
|
|
|
|
|
|
|
$DEBUG = 0; |
|
29
|
|
|
|
|
|
|
my $Last_Dcode; |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub send_cm11 { |
|
32
|
78
|
50
|
|
78
|
0
|
221
|
return unless ( 2 == @_ ); |
|
33
|
78
|
|
|
|
|
145
|
return ControlX10::CM11::send ( @_ ); |
|
34
|
|
|
|
|
|
|
} |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub receive_cm11 { |
|
37
|
1
|
50
|
|
1
|
0
|
6
|
return unless ( 1 == @_ ); |
|
38
|
1
|
|
|
|
|
4
|
return ControlX10::CM11::receive_buffer ( shift ); |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub read_cm11 { |
|
42
|
1
|
50
|
|
1
|
0
|
72
|
return unless ( 2 == @_ ); |
|
43
|
1
|
|
|
|
|
6
|
return ControlX10::CM11::read ( @_ ); |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub dim_decode_cm11 { |
|
47
|
11
|
50
|
|
11
|
0
|
26
|
return unless ( 1 == @_ ); |
|
48
|
11
|
|
|
|
|
90
|
return ControlX10::CM11::dim_level_decode ( shift ); |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# These tables are used in sending data |
|
52
|
|
|
|
|
|
|
my %table_hcodes = qw(A 0110 B 1110 C 0010 D 1010 E 0001 F 1001 G 0101 H 1101 |
|
53
|
|
|
|
|
|
|
I 0111 J 1111 K 0011 L 1011 M 0000 N 1000 O 0100 P 1100); |
|
54
|
|
|
|
|
|
|
my %table_dcodes = qw(1 0110 2 1110 3 0010 4 1010 5 0001 6 1001 7 0101 8 1101 |
|
55
|
|
|
|
|
|
|
9 0111 10 1111 11 0011 12 1011 13 0000 14 1000 15 0100 16 1100 |
|
56
|
|
|
|
|
|
|
A 1111 B 0011 C 1011 D 0000 E 1000 F 0100 G 1100); |
|
57
|
|
|
|
|
|
|
my %table_fcodes = qw(J 0010 K 0011 M 0100 L 0101 O 0001 P 0000 |
|
58
|
|
|
|
|
|
|
ALL_OFF 0000 ALL_ON 0001 ON 0010 OFF 0011 DIM 0100 BRIGHT 0101 |
|
59
|
|
|
|
|
|
|
-10 0100 -20 0100 -30 0100 -40 0100 |
|
60
|
|
|
|
|
|
|
-15 0100 -25 0100 -35 0100 -45 0100 -5 0100 |
|
61
|
|
|
|
|
|
|
-50 0100 -60 0100 -70 0100 -80 0100 -90 0100 |
|
62
|
|
|
|
|
|
|
-55 0100 -65 0100 -75 0100 -85 0100 -95 0100 |
|
63
|
|
|
|
|
|
|
+10 0101 +20 0101 +30 0101 +40 0101 |
|
64
|
|
|
|
|
|
|
+15 0101 +25 0101 +35 0101 +45 0101 +5 0101 |
|
65
|
|
|
|
|
|
|
+50 0101 +60 0101 +70 0101 +80 0101 +90 0101 |
|
66
|
|
|
|
|
|
|
+55 0101 +65 0101 +75 0101 +85 0101 +95 0101 |
|
67
|
|
|
|
|
|
|
ALL_LIGHTS_OFF 0110 EXTENDED_CODE 0111 HAIL_REQUEST 1000 HAIL_ACK 1001 |
|
68
|
|
|
|
|
|
|
PRESET_DIM1 1010 PRESET_DIM2 1011 EXTENDED_DATA 1100 |
|
69
|
|
|
|
|
|
|
STATUS_ON 1101 STATUS_OFF 1110 STATUS 1111); |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# These tables are used in receiving data |
|
73
|
|
|
|
|
|
|
my %table_hcodes2 = qw(0110 A 1110 B 0010 C 1010 D 0001 E 1001 F 0101 G 1101 H |
|
74
|
|
|
|
|
|
|
0111 I 1111 J 0011 K 1011 L 0000 M 1000 N 0100 O 1100 P); |
|
75
|
|
|
|
|
|
|
my %table_dcodes2 = qw(0110 1 1110 2 0010 3 1010 4 0001 5 1001 6 0101 7 1101 8 |
|
76
|
|
|
|
|
|
|
0111 9 1111 A 0011 B 1011 C 0000 D 1000 E 0100 F 1100 G); |
|
77
|
|
|
|
|
|
|
my %table_fcodes2 = qw(0010 J 0011 K 0100 L 0101 M 0001 O 0000 P |
|
78
|
|
|
|
|
|
|
0111 Z 1010 PRESET_DIM1 1011 PRESET_DIM2 |
|
79
|
|
|
|
|
|
|
1101 STATUS_ON 1110 STATUS_OFF 1111 STATUS); |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub receive_buffer { |
|
83
|
1
|
|
|
1
|
1
|
1
|
my ($serial_port) = @_; |
|
84
|
|
|
|
|
|
|
|
|
85
|
1
|
50
|
|
|
|
5
|
if (exists $main::config_parms{debug}) { |
|
86
|
1
|
50
|
|
|
|
6
|
$DEBUG = ($main::config_parms{debug} eq 'X10') ? 1 : 0; |
|
87
|
|
|
|
|
|
|
} |
|
88
|
|
|
|
|
|
|
|
|
89
|
1
|
|
|
|
|
3
|
my $pc_ready = pack('C', 0xc3); |
|
90
|
1
|
50
|
|
|
|
4
|
print "Bad cm11 pc_ready transmition\n" unless 1 == $serial_port->write($pc_ready); |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# Lets not wait for data (use no_block option), or we loop too long and mh slows way down |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# let the 0xc3 ack take hold ... emperically derived ... 1/2 misses at 20 ms |
|
95
|
1
|
|
|
|
|
40234
|
select undef, undef, undef, 40 / 1000; |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
|
|
98
|
1
|
|
|
|
|
8
|
my $data; |
|
99
|
1
|
50
|
|
|
|
7
|
return undef unless $data = &read($serial_port, 1); |
|
100
|
|
|
|
|
|
|
# my $data = &read($serial_port); |
|
101
|
|
|
|
|
|
|
|
|
102
|
1
|
|
|
|
|
47
|
my @bytes = split //, $data; |
|
103
|
|
|
|
|
|
|
|
|
104
|
1
|
|
|
|
|
62
|
my $length = shift @bytes; |
|
105
|
1
|
|
|
|
|
3
|
my $mask = shift @bytes; |
|
106
|
|
|
|
|
|
|
|
|
107
|
1
|
|
|
|
|
4
|
$length = unpack('C', $length); |
|
108
|
1
|
|
|
|
|
3
|
$mask = unpack('B8', $mask); |
|
109
|
1
|
|
|
|
|
5
|
my $data_h = unpack('H*', $data); |
|
110
|
1
|
50
|
|
|
|
4
|
print "receive buffer length=$length, mask=$mask, data_h=$data_h.\n" if $DEBUG; |
|
111
|
|
|
|
|
|
|
|
|
112
|
1
|
|
|
|
|
2
|
my ($house, $function, $device, $i, $extended_count); |
|
113
|
|
|
|
|
|
|
|
|
114
|
1
|
|
|
|
|
2
|
undef $data; |
|
115
|
1
|
|
|
|
|
3
|
foreach my $byte (@bytes) { |
|
116
|
|
|
|
|
|
|
# Send extended data into MH as untranslated hex. |
|
117
|
4
|
50
|
|
|
|
10
|
if ($extended_count) { |
|
118
|
0
|
|
|
|
|
0
|
$data .= unpack('H*', $byte); |
|
119
|
0
|
|
|
|
|
0
|
--$extended_count; |
|
120
|
0
|
|
|
|
|
0
|
++$i; |
|
121
|
|
|
|
|
|
|
} |
|
122
|
|
|
|
|
|
|
else { |
|
123
|
4
|
|
|
|
|
9
|
my $bits = unpack('B8', $byte); |
|
124
|
4
|
|
|
|
|
7
|
my $house_bits = substr($bits, 0, 4); |
|
125
|
4
|
|
|
|
|
8
|
my $code_bits = substr($bits, 4, 4); |
|
126
|
4
|
50
|
|
|
|
11
|
print "CM11 error, not a valid house code: $house_bits\n" unless $house = $table_hcodes2{$house_bits}; |
|
127
|
4
|
100
|
|
|
|
14
|
if (substr($mask, -(++$i), 1)) { |
|
128
|
1
|
50
|
|
|
|
14
|
print "CM11 error, not a valid function code: $code_bits at byte $i value $bits\n" unless $function = $table_fcodes2{$code_bits}; |
|
129
|
|
|
|
|
|
|
# print "function=$house$function\n"; |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
# Add device code back in, since this is not included in status :( |
|
132
|
1
|
50
|
|
|
|
4
|
$function = $Last_Dcode . $function if $function =~ /^STATUS/; |
|
133
|
|
|
|
|
|
|
# Handle Vehicle Interface RF Receiver extended code - assume length of 3 for extended |
|
134
|
1
|
50
|
|
|
|
5
|
$extended_count = 3 if ($function eq 'Z'); |
|
135
|
|
|
|
|
|
|
## 2.08, but 'Z' not numeric ## |
|
136
|
|
|
|
|
|
|
## $extended_count = 3 if ($function == 'Z'); |
|
137
|
|
|
|
|
|
|
|
|
138
|
1
|
|
|
|
|
2
|
$data .= $house . $function; |
|
139
|
1
|
50
|
|
|
|
4
|
print "CM11 db: data=$data\n" if $DEBUG; |
|
140
|
|
|
|
|
|
|
} |
|
141
|
|
|
|
|
|
|
else { |
|
142
|
3
|
50
|
|
|
|
12
|
print "CM11 error, not a valid device code: $code_bits\n" unless $device = $table_dcodes2{$code_bits}; |
|
143
|
|
|
|
|
|
|
# print "device=$house$device\n"; |
|
144
|
3
|
|
|
|
|
9
|
$data .= $house . $device; |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
# print "byte=$byte, $bits\n"; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
1
|
|
|
|
|
10
|
return $data; |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
sub format_data { |
|
153
|
80
|
|
|
80
|
0
|
94
|
my ($house_code) = @_; |
|
154
|
|
|
|
|
|
|
|
|
155
|
80
|
100
|
|
|
|
147
|
print "CM11 send data=$house_code\n" if $DEBUG; |
|
156
|
|
|
|
|
|
|
|
|
157
|
80
|
|
|
|
|
76
|
my ($house, $code, $house_bits, $header, $code_bits, $function, $dim_level); |
|
158
|
0
|
|
|
|
|
0
|
my ($extended, $extended_string, $extended_checksum); |
|
159
|
|
|
|
|
|
|
|
|
160
|
80
|
|
|
|
|
398
|
($house, $code) = $house_code =~ /(\S)(\S+)/; |
|
161
|
80
|
|
|
|
|
133
|
$house = uc($house); |
|
162
|
80
|
|
|
|
|
93
|
$code = uc($code); |
|
163
|
|
|
|
|
|
|
|
|
164
|
80
|
100
|
|
|
|
179
|
unless ($house_bits = $table_hcodes{$house}) { |
|
165
|
1
|
|
|
|
|
5
|
print "CM11 error, invalid house code: $house\n"; |
|
166
|
1
|
|
|
|
|
3
|
return; |
|
167
|
|
|
|
|
|
|
} |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# $code can be |
|
170
|
|
|
|
|
|
|
# 1-9,A-G for Device code |
|
171
|
|
|
|
|
|
|
# d_xyz. for Extended code xyz for device d |
|
172
|
|
|
|
|
|
|
# xyz for Function codes, including +-## for bright/dim |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# Test for extended code |
|
175
|
|
|
|
|
|
|
# - format is D&P## where D is device code and ## is the preset dim level |
|
176
|
79
|
100
|
|
|
|
437
|
if (my($dcode, $extended_data) = $code =~ /(\S)&P(\d+)/) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
177
|
3
|
50
|
|
|
|
9
|
unless ($code_bits = $table_dcodes{$dcode}) { |
|
178
|
0
|
|
|
|
|
0
|
print "CM11 error, invalid device in extended code. device=$dcode code=$code\n"; |
|
179
|
0
|
|
|
|
|
0
|
return; |
|
180
|
|
|
|
|
|
|
} |
|
181
|
3
|
100
|
66
|
|
|
20
|
unless (($extended_data >= 0) && ($extended_data < 65)) { |
|
182
|
1
|
|
|
|
|
6
|
print "CM11 error, invalid extended code. code=$code\n"; |
|
183
|
1
|
|
|
|
|
2
|
return; |
|
184
|
|
|
|
|
|
|
} |
|
185
|
2
|
|
|
|
|
4
|
$code_bits = '0111'; # Extended code |
|
186
|
|
|
|
|
|
|
# print "db cb=$code_bits\n"; |
|
187
|
2
|
|
|
|
|
3
|
$function = '1'; # Extended transmitions are a function |
|
188
|
2
|
|
|
|
|
2
|
$extended = '1'; |
|
189
|
2
|
|
|
|
|
4
|
$dim_level = 0; # Dim level is not applicable to extended transmitions. |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# Hard codeded preset for now ... |
|
192
|
|
|
|
|
|
|
|
|
193
|
2
|
|
|
|
|
3
|
my $extended_junk = '00000101'; # This is not documented, so I have no idea why it is needed! |
|
194
|
|
|
|
|
|
|
# Emperically derived by looking at ActiveHome errata. |
|
195
|
|
|
|
|
|
|
|
|
196
|
2
|
|
|
|
|
2
|
my $extended_code = '00110001'; # Type=3 => Control Modules Func=1 => Preset Receiver |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# Convert from bit to string |
|
199
|
2
|
|
|
|
|
5
|
my $b3 = pack('B8', $extended_junk); |
|
200
|
2
|
|
|
|
|
5
|
my $b4 = pack('C1', $extended_data); |
|
201
|
2
|
|
|
|
|
4
|
my $b5 = pack('B8', $extended_code); |
|
202
|
2
|
|
|
|
|
4
|
$extended_string = $b3 . $b4 . $b5; |
|
203
|
2
|
|
|
|
|
4
|
my $b3c = unpack('C', $b3); |
|
204
|
2
|
|
|
|
|
9
|
my $b4c = unpack('C', $b4); |
|
205
|
2
|
|
|
|
|
3
|
my $b5c = unpack('C', $b5); |
|
206
|
2
|
|
|
|
|
3
|
$extended_checksum = $b3c + $b4c + $b5c; |
|
207
|
2
|
50
|
|
|
|
7
|
if ($DEBUG) { |
|
208
|
0
|
|
|
|
|
0
|
printf "CM11 ed=%d, b345=0x%0.2x,0x%0.2x,0x%0.2x ex=%s cs=0x%0.2x\n", |
|
209
|
|
|
|
|
|
|
$extended_data, $b3c, $b4c, $b5c, $extended_string, |
|
210
|
|
|
|
|
|
|
$extended_checksum; |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
} |
|
213
|
|
|
|
|
|
|
# Test for device code |
|
214
|
|
|
|
|
|
|
elsif ($code_bits = $table_dcodes{$code}) { |
|
215
|
18
|
|
|
|
|
19
|
$function = '0'; |
|
216
|
18
|
|
|
|
|
20
|
$extended = '0'; |
|
217
|
18
|
|
|
|
|
15
|
$dim_level = 0; |
|
218
|
18
|
|
|
|
|
21
|
$Last_Dcode = $code; # This is desperate :) |
|
219
|
|
|
|
|
|
|
} |
|
220
|
|
|
|
|
|
|
# Test for function code |
|
221
|
|
|
|
|
|
|
elsif ($code_bits = $table_fcodes{$code}) { |
|
222
|
48
|
|
|
|
|
62
|
$function = '1'; |
|
223
|
48
|
|
|
|
|
55
|
$extended = '0'; |
|
224
|
48
|
100
|
100
|
|
|
471
|
if ($code eq 'DIM' or $code eq 'M' or $code eq 'BRIGHT' or $code eq 'L') { |
|
|
|
100
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
225
|
5
|
|
|
|
|
8
|
$dim_level = 50; |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
elsif ($code =~ /^[+-]\d\d$/) { |
|
228
|
23
|
|
|
|
|
45
|
$dim_level = abs($code); |
|
229
|
|
|
|
|
|
|
} |
|
230
|
|
|
|
|
|
|
else { |
|
231
|
20
|
|
|
|
|
25
|
$dim_level = 0; |
|
232
|
|
|
|
|
|
|
} |
|
233
|
|
|
|
|
|
|
} |
|
234
|
|
|
|
|
|
|
else { |
|
235
|
10
|
|
|
|
|
53
|
print "CM11 error, invalid cm11 x10 code: $code\n"; |
|
236
|
10
|
|
|
|
|
22
|
return; |
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
|
|
239
|
68
|
|
|
|
|
130
|
my $dim = int($dim_level * 22 / 100); # 22 levels = 100% |
|
240
|
68
|
|
|
|
|
243
|
$header = substr(unpack('B8', pack('C', $dim)), 3); |
|
241
|
|
|
|
|
|
|
|
|
242
|
68
|
|
|
|
|
98
|
$header .= '1'; # Bit 2 is always set to a 1 to ensure synchronization |
|
243
|
68
|
|
|
|
|
87
|
$header .= $function; # 0 for address, 1 for function |
|
244
|
68
|
|
|
|
|
64
|
$header .= $extended; # 0 for standard, 1 for extended transmition |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# Convert from bit to string |
|
247
|
68
|
|
|
|
|
114
|
my $b1 = pack('B8', $header); |
|
248
|
68
|
|
|
|
|
159
|
my $b2 = pack('B8', $house_bits . $code_bits); |
|
249
|
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# Calculate checksum |
|
251
|
68
|
|
|
|
|
114
|
my $b1d = unpack('C', $b1); |
|
252
|
68
|
|
|
|
|
98
|
my $b2d = unpack('C', $b2); |
|
253
|
68
|
|
|
|
|
104
|
my $checksum = ($b1d + $b2d) & 0xff; |
|
254
|
|
|
|
|
|
|
|
|
255
|
68
|
|
|
|
|
81
|
my $data = $b1 . $b2; |
|
256
|
|
|
|
|
|
|
|
|
257
|
68
|
100
|
|
|
|
278
|
if ($extended) { |
|
258
|
2
|
|
|
|
|
3
|
$data .= $extended_string; |
|
259
|
2
|
|
|
|
|
4
|
$checksum = ($checksum + $extended_checksum) & 0xff; |
|
260
|
|
|
|
|
|
|
} |
|
261
|
|
|
|
|
|
|
|
|
262
|
68
|
100
|
|
|
|
125
|
printf("CM11 dim=$dim header=$header hb=$house_bits cb=$code_bits " . |
|
263
|
|
|
|
|
|
|
"bd=0x%0.2x,0x%0.2x checksum=0x%0.2x\n", |
|
264
|
|
|
|
|
|
|
$b1d, $b2d, $checksum) if $DEBUG; |
|
265
|
|
|
|
|
|
|
|
|
266
|
68
|
|
|
|
|
222
|
return $data, $checksum; |
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
} |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub send { |
|
271
|
80
|
|
|
80
|
1
|
122
|
my ($serial_port, $house_code) = @_; |
|
272
|
|
|
|
|
|
|
|
|
273
|
80
|
50
|
|
|
|
178
|
if (exists $main::config_parms{debug}) { |
|
274
|
80
|
100
|
|
|
|
371
|
$DEBUG = ($main::config_parms{debug} eq 'X10') ? 1 : 0; |
|
275
|
|
|
|
|
|
|
# &Win32::SerialPort::debug(1) if $DEBUG; |
|
276
|
|
|
|
|
|
|
} |
|
277
|
|
|
|
|
|
|
|
|
278
|
80
|
|
|
|
|
125
|
my ($data_snd, $checksum) = &format_data($house_code); |
|
279
|
80
|
100
|
|
|
|
189
|
return unless $data_snd; |
|
280
|
|
|
|
|
|
|
|
|
281
|
68
|
|
|
|
|
70
|
my $retry_cnt = 0; |
|
282
|
68
|
100
|
|
|
|
126
|
RETRY: |
|
283
|
|
|
|
|
|
|
print "CM11 send: ", unpack('H*', $data_snd), "\n" if $DEBUG; |
|
284
|
|
|
|
|
|
|
|
|
285
|
68
|
50
|
|
|
|
222
|
print "Bad cm11 data send transmition\n" unless length($data_snd) == $serial_port->write($data_snd); |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# Note: Skip the power fail check, because we the |
|
288
|
|
|
|
|
|
|
# checksum might be the power fail flag (0xa5) |
|
289
|
68
|
|
|
|
|
1547
|
my $data_rcv = &read($serial_port, 0, 1); |
|
290
|
|
|
|
|
|
|
|
|
291
|
68
|
|
|
|
|
114
|
my $data_d = unpack('C', $data_rcv); |
|
292
|
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
# Unrelated incoming data ... process and re-start |
|
294
|
|
|
|
|
|
|
# Note: Some checksums will be 0x5a or 0xa5 ... skip this test if so |
|
295
|
68
|
50
|
66
|
|
|
310
|
if (($data_d == 0x5a or $data_d == 0xa5) and !($checksum == 0x5a or $checksum == 0xa5)) { |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
296
|
0
|
|
|
|
|
0
|
print "Data received while xmiting data ... will receive and retry\n"; |
|
297
|
0
|
|
|
|
|
0
|
&receive_buffer($serial_port); |
|
298
|
0
|
0
|
|
|
|
0
|
goto RETRY if $retry_cnt++ < 3; |
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
|
|
301
|
68
|
50
|
|
|
|
120
|
if ($checksum != $data_d) { |
|
302
|
0
|
|
|
|
|
0
|
print "Bad checksum in cm11 send: cs1=$checksum cs2=$data_d. Will retry\n"; |
|
303
|
0
|
0
|
|
|
|
0
|
goto RETRY if $retry_cnt++ < 3; |
|
304
|
|
|
|
|
|
|
} |
|
305
|
|
|
|
|
|
|
|
|
306
|
68
|
100
|
|
|
|
132
|
print "CM11 ack\n" if $DEBUG; |
|
307
|
68
|
|
|
|
|
83
|
my $pc_ok = pack('C', 0x00); |
|
308
|
68
|
50
|
|
|
|
150
|
print "Bad cm11 acknowledge send transmition\n" unless 1 == $serial_port->write($pc_ok); |
|
309
|
|
|
|
|
|
|
|
|
310
|
68
|
|
|
|
|
1086
|
$data_rcv = &read($serial_port); |
|
311
|
68
|
|
|
|
|
132
|
$data_d = unpack('C', $data_rcv); |
|
312
|
|
|
|
|
|
|
|
|
313
|
68
|
50
|
0
|
|
|
115
|
if ($data_d == 0x55) { |
|
|
|
0
|
|
|
|
|
|
|
314
|
68
|
100
|
|
|
|
119
|
print "CM11 done\n" if $DEBUG; |
|
315
|
|
|
|
|
|
|
} |
|
316
|
|
|
|
|
|
|
# Unrelated incoming data ... process |
|
317
|
|
|
|
|
|
|
elsif ($data_d == 0x5a or $data_d == 0xa5) { |
|
318
|
0
|
|
|
|
|
0
|
print "Data received while xmiting data ... receive and retry\n"; |
|
319
|
0
|
|
|
|
|
0
|
&receive_buffer($serial_port); |
|
320
|
0
|
0
|
|
|
|
0
|
goto RETRY if $retry_cnt++ < 3; |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
68
|
50
|
|
|
|
167
|
if (exists $main::config_parms{debug}) { |
|
324
|
|
|
|
|
|
|
# &Win32::SerialPort::debug(0) if $DEBUG; |
|
325
|
|
|
|
|
|
|
} |
|
326
|
|
|
|
|
|
|
|
|
327
|
68
|
|
|
|
|
319
|
return $data_d; |
|
328
|
|
|
|
|
|
|
} |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
sub read { |
|
331
|
138
|
|
|
138
|
1
|
210
|
my ($serial_port, $no_block, $no_power_fail_check) = @_; |
|
332
|
138
|
|
|
|
|
142
|
my $data; |
|
333
|
|
|
|
|
|
|
# Note ... for dim commands > 20, this will time out after 20*40=1 seconds |
|
334
|
|
|
|
|
|
|
# No harm done, but we would rather not wait :) |
|
335
|
138
|
100
|
|
|
|
228
|
my $tries = ($no_block) ? 1 : 20; |
|
336
|
|
|
|
|
|
|
|
|
337
|
138
|
50
|
|
|
|
298
|
if (exists $main::config_parms{debug}) { |
|
338
|
138
|
100
|
|
|
|
346
|
$DEBUG = ($main::config_parms{debug} eq 'X10') ? 1 : 0; |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
|
|
341
|
138
|
|
|
|
|
288
|
while ($tries--) { |
|
342
|
138
|
100
|
66
|
|
|
329
|
print "." if $DEBUG and !$no_block; |
|
343
|
138
|
50
|
|
|
|
413
|
if ($data = $serial_port->input) { |
|
344
|
138
|
|
|
|
|
2142
|
my $data_d = unpack('C', $data); |
|
345
|
|
|
|
|
|
|
# printf("rcv data=%s, %x.\n", $data, $data_d); |
|
346
|
|
|
|
|
|
|
# my $pc_ready = pack('C', 0xc3); |
|
347
|
|
|
|
|
|
|
# $serial_data = "$pc_ready"; |
|
348
|
|
|
|
|
|
|
# print "serial1 out=$serial_data results=", $serial_port->write($serial_data), ".\n" if $DEBUG; |
|
349
|
138
|
100
|
|
|
|
280
|
printf("\nCM11 data=%s hex=%0.2lx\n", $data_d, $data_d) if $DEBUG; |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
# If we received the power-fail string (0xa5), reset with a blank macro command |
|
352
|
|
|
|
|
|
|
# - Protocol.txt says to send macros string, but that did not work. |
|
353
|
138
|
50
|
66
|
|
|
342
|
if ($data_d == 165 and !$no_power_fail_check) { |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
#55 to 48 timer download header (0x9b) |
|
356
|
|
|
|
|
|
|
#47 to 40 Current time (seconds) |
|
357
|
|
|
|
|
|
|
#39 to 32 Current time (minutes ranging from 0 to 119) |
|
358
|
|
|
|
|
|
|
#31 to 23 Current time (hours/2, ranging from 0 to 11) |
|
359
|
|
|
|
|
|
|
#23 to 16 Current year day (bits 0 to 7) |
|
360
|
|
|
|
|
|
|
#15 Current year day (bit 8) |
|
361
|
|
|
|
|
|
|
#14 to 8 Day mask (SMTWTFS) |
|
362
|
|
|
|
|
|
|
#7 to 4 Monitored house code |
|
363
|
|
|
|
|
|
|
#3 Reserved |
|
364
|
|
|
|
|
|
|
#2 Battery timer clear flag |
|
365
|
|
|
|
|
|
|
#1 Monitored status clear flag |
|
366
|
|
|
|
|
|
|
#0 Timer purge flag |
|
367
|
|
|
|
|
|
|
|
|
368
|
0
|
|
|
|
|
0
|
my ($Second, $Minute, $Hour, $Mday, $Month, $Year, $Wday, $Yday) = localtime time; |
|
369
|
0
|
|
|
|
|
0
|
my $localtime = localtime time; |
|
370
|
0
|
|
|
|
|
0
|
$Wday = 2 ** (7 - $Wday); |
|
371
|
0
|
0
|
|
|
|
0
|
if ($Yday > 255) { |
|
372
|
0
|
|
|
|
|
0
|
$Yday -= 256; |
|
373
|
0
|
|
|
|
|
0
|
$Wday *= 2; |
|
374
|
|
|
|
|
|
|
} |
|
375
|
0
|
|
|
|
|
0
|
my $power_reset = pack('C7', 0x9b, |
|
376
|
|
|
|
|
|
|
$Second, |
|
377
|
|
|
|
|
|
|
$Minute, |
|
378
|
|
|
|
|
|
|
$Hour, |
|
379
|
|
|
|
|
|
|
$Yday, |
|
380
|
|
|
|
|
|
|
$Wday, |
|
381
|
|
|
|
|
|
|
0x03); # Not sure what is best here. x10d.c did this. |
|
382
|
|
|
|
|
|
|
|
|
383
|
0
|
|
|
|
|
0
|
print "\nCM11 power fail detected. Resetting the CM11 clock with:\n $localtime\n"; |
|
384
|
0
|
|
|
|
|
0
|
my $results = $serial_port->write($power_reset); |
|
385
|
0
|
|
|
|
|
0
|
select undef, undef, undef, 50 / 1000; |
|
386
|
0
|
|
|
|
|
0
|
my $checksum = $serial_port->input; # Receive, but ignore, checksum |
|
387
|
0
|
0
|
|
|
|
0
|
if ($DEBUG) { |
|
388
|
0
|
|
|
|
|
0
|
printf "\npower_reset: %s %s %s %s %s %s %s\n", |
|
389
|
|
|
|
|
|
|
unpack ('H2H2H2H2H2H2H2', $power_reset); |
|
390
|
0
|
|
|
|
|
0
|
print " sent $results bytes\n"; |
|
391
|
0
|
|
|
|
|
0
|
printf " checksum = %x\n", ord($checksum); |
|
392
|
|
|
|
|
|
|
} |
|
393
|
0
|
|
|
|
|
0
|
my $pc_ok = pack('C', 0x00); |
|
394
|
0
|
0
|
|
|
|
0
|
print "Bad cm11 checksum acknowledge\n" unless 1 == $serial_port->write($pc_ok); |
|
395
|
|
|
|
|
|
|
# undef $data; |
|
396
|
|
|
|
|
|
|
} |
|
397
|
|
|
|
|
|
|
|
|
398
|
138
|
|
|
|
|
334
|
return $data; |
|
399
|
|
|
|
|
|
|
} |
|
400
|
|
|
|
|
|
|
# If we do not do this, we may get endless error messages. |
|
401
|
|
|
|
|
|
|
else { |
|
402
|
0
|
|
|
|
|
0
|
$serial_port->reset_error; |
|
403
|
|
|
|
|
|
|
} |
|
404
|
|
|
|
|
|
|
|
|
405
|
0
|
0
|
|
|
|
0
|
if ($tries) { |
|
406
|
0
|
|
|
|
|
0
|
select undef, undef, undef, 50 / 1000; |
|
407
|
|
|
|
|
|
|
} |
|
408
|
|
|
|
|
|
|
} |
|
409
|
|
|
|
|
|
|
|
|
410
|
0
|
0
|
0
|
|
|
0
|
print "No data received from cm11\n" if ($DEBUG and !$no_block); |
|
411
|
0
|
|
|
|
|
0
|
return undef; |
|
412
|
|
|
|
|
|
|
} |
|
413
|
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
sub dim_level_decode { |
|
415
|
11
|
|
|
11
|
1
|
16
|
my ($code) = @_; |
|
416
|
|
|
|
|
|
|
|
|
417
|
11
|
|
|
|
|
148
|
my %table_hcodes = qw(A 0110 B 1110 C 0010 D 1010 E 0001 F 1001 G 0101 H 1101 |
|
418
|
|
|
|
|
|
|
I 0111 J 1111 K 0011 L 1011 M 0000 N 1000 O 0100 P 1100); |
|
419
|
11
|
|
|
|
|
89
|
my %table_dcodes = qw(1 0110 2 1110 3 0010 4 1010 5 0001 6 1001 7 0101 8 1101 |
|
420
|
|
|
|
|
|
|
9 0111 10 1111 11 0011 12 1011 13 0000 14 1000 15 0100 16 1100 |
|
421
|
|
|
|
|
|
|
A 1111 B 0011 C 1011 D 0000 E 1000 F 0100 G 1100); |
|
422
|
|
|
|
|
|
|
|
|
423
|
11
|
50
|
|
|
|
27
|
if (exists $main::config_parms{debug}) { |
|
424
|
11
|
50
|
|
|
|
25
|
$DEBUG = ($main::config_parms{debug} eq 'X10') ? 1 : 0; |
|
425
|
|
|
|
|
|
|
} |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
# Convert bit string to decimal |
|
428
|
11
|
|
|
|
|
27
|
my $level_b = $table_hcodes{substr($code, 0, 1)} . $table_dcodes{substr($code, 1, 1)}; |
|
429
|
11
|
|
|
|
|
36
|
my $level_d = unpack('C', pack('B8', $level_b)); |
|
430
|
|
|
|
|
|
|
# Varies from 36 to 201, by 11, then to 210 as a max. |
|
431
|
|
|
|
|
|
|
# 16 different values. Round to nearest 5%, max of 95. |
|
432
|
11
|
|
|
|
|
38
|
my $level_p = int(100 * $level_d / 211); # Do not allow 100% ... not a valid state? |
|
433
|
|
|
|
|
|
|
## print "CM11 debug1: levelb=$level_b level_p=$level_p\n" if $DEBUG; |
|
434
|
11
|
|
|
|
|
19
|
$level_p = $level_p - ($level_p % 5); |
|
435
|
11
|
50
|
|
|
|
21
|
print "CM11 debug: dim_code=$code leveld=$level_d level_p=$level_p\n" if $DEBUG; |
|
436
|
11
|
|
|
|
|
91
|
return $level_p; |
|
437
|
|
|
|
|
|
|
} |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
return 1; # for require |
|
440
|
|
|
|
|
|
|
__END__ |