line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Power::Outlet::Dingtian; |
2
|
1
|
|
|
1
|
|
995
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
30
|
|
3
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
39
|
|
4
|
1
|
|
|
1
|
|
6
|
use base qw{Power::Outlet::Common::IP::HTTP}; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1092
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.50'; |
7
|
|
|
|
|
|
|
our $PACKAGE = __PACKAGE__; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 NAME |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
Power::Outlet::Dingtian - Control and query Dingtian Relay Boards via the HTTP API |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 SYNOPSIS |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
my $outlet = Power::Outlet::Dingtian->new(host => "my_host", relay => "1"); |
16
|
|
|
|
|
|
|
print $outlet->query, "\n"; |
17
|
|
|
|
|
|
|
print $outlet->on, "\n"; |
18
|
|
|
|
|
|
|
print $outlet->off, "\n"; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 DESCRIPTION |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
Power::Outlet::Dingtian is a package for controlling and querying a relay on Dingtian hardware via the HTTP API. |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
Example commands can be executed via web (HTTP) GET requests, for example: |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
Relay Status URL Example |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
http://192.168.1.100/relay_cgi_load.cgi |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
Relay 1 on example (relays are named one-based but the api uses a zero-based index) |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
http://192.168.1.100/relay_cgi.cgi?type=0&relay=0&on=1&time=0&pwd=0& |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
Relay 2 off example |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
http://192.168.1.100/relay_cgi.cgi?type=0&relay=1&on=0&time=0&pwd=0& |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
Relay 2 cycle off-on-off example (note: time in 100ms increments) |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
http://192.168.1.100/relay_cgi.cgi?type=1&relay=1&on=1&time=100&pwd=0& |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
I have tested this package against the Dingtian DT-R002 V3.6A with V3.1.276A firmware configured for both HTTP and HTTPS. |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 USAGE |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
use Power::Outlet::Dingtian; |
47
|
|
|
|
|
|
|
my $relay = Power::Outlet::Dingtian->new(host=>"my_host", relay=>"1"); |
48
|
|
|
|
|
|
|
print $relay->on, "\n"; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head2 new |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
my $outlet = Power::Outlet->new(type=>"Dingtian", host=>"my_host", relay=>"1"); |
55
|
|
|
|
|
|
|
my $outlet = Power::Outlet::Dingtian->new(host=>"my_host", relay=>"1"); |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=head1 PROPERTIES |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=head2 relay |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
Dingtian API supports up to 32 relays numbered 1 to 32. |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
Default: 1 |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Note: The relays are numbered 1-32 but the api uses a zero based index. |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=cut |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub relay { |
70
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
71
|
0
|
0
|
|
|
|
|
$self->{'relay'} = shift if @_; |
72
|
0
|
0
|
|
|
|
|
$self->{'relay'} = $self->_relay_default unless defined $self->{'relay'}; |
73
|
0
|
0
|
|
|
|
|
die("Error: $PACKAGE relay must be between 1 and 32") unless $self->{'relay'} =~ m/\A([1-9]|[12][0-9]|3[012])\Z/; |
74
|
0
|
|
|
|
|
|
return $self->{'relay'}; |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
|
77
|
0
|
|
|
0
|
|
|
sub _relay_default {'1'}; |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head2 pwd |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Sets and returns the ID token used for authentication with the Dingtian hardware |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
Default: "0" |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Can be set in the Relay Password property in the Other section on the Relay Connect screen. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=cut |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
sub pwd { |
90
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
91
|
0
|
0
|
|
|
|
|
$self->{'pwd'} = shift if @_; |
92
|
0
|
0
|
|
|
|
|
$self->{'pwd'} = $self->_pwd_default unless defined $self->{'pwd'}; |
93
|
0
|
|
|
|
|
|
return $self->{'pwd'}; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
0
|
|
|
0
|
|
|
sub _pwd_default {'0'}; |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=head2 host |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Sets and returns the hostname or IP address. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
Default: 192.168.1.100 |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=cut |
105
|
|
|
|
|
|
|
|
106
|
0
|
|
|
0
|
|
|
sub _host_default {'192.168.1.100'}; |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head2 port |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Sets and returns the port number. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Default: 80 |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Can be set in the HTTP Server Port property on the Setting screen. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=cut |
117
|
|
|
|
|
|
|
|
118
|
0
|
|
|
0
|
|
|
sub _port_default {'80'}; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=head2 http_scheme |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
Sets and returns the http scheme (i.e. protocol) (e.g. http or https). |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
Default: http |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Can be set in the HTTP or HTTPS property on the Setting screen |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=cut |
129
|
|
|
|
|
|
|
|
130
|
0
|
|
|
0
|
|
|
sub _http_scheme_default {'http'}; #see Power::Outlet::Common::IP::HTTP |
131
|
0
|
|
|
0
|
|
|
sub _http_path_default {'/'}; #see Power::Outlet::Common::IP::HTTP |
132
|
0
|
|
|
0
|
|
|
sub _http_path_script_name_set {'relay_cgi.cgi'}; #custom |
133
|
0
|
|
|
0
|
|
|
sub _http_path_script_name_status {'relay_cgi_load.cgi'}; #custom |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head1 METHODS |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head2 name |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Sets and returns the friendly name for this relay. |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=cut |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
#see Power::Outlet::Common |
144
|
|
|
|
|
|
|
|
145
|
0
|
|
|
0
|
|
|
sub _name_default {sprintf("Relay %s", shift->relay)}; |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head2 query |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Sends an HTTP message to the device to query the current state |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=cut |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
sub query { |
154
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
155
|
0
|
|
|
|
|
|
return $self->_call(); #zero params is query but content is different format |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=head2 on |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Sends a message to the device to Turn Power ON |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=cut |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
sub on { |
165
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
166
|
0
|
|
|
|
|
|
return $self->_call(1); |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=head2 off |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
Sends a message to the device to Turn Power OFF |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=cut |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub off { |
176
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
177
|
0
|
|
|
|
|
|
return $self->_call(0); |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head2 switch |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Sends a message to the device to toggle the power |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=cut |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
#see Power::Outlet::Common |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=head2 cycle |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
Sends messages to the device to Cycle Power (ON-OFF-ON or OFF-ON-OFF). |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=cut |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub cycle { |
195
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
196
|
0
|
|
|
|
|
|
my $query = $self->query; |
197
|
0
|
0
|
|
|
|
|
$self->_call(($query eq 'OFF' ? 1 : 0) => $self->cycle_duration); |
198
|
0
|
|
|
|
|
|
return 'CYCLE'; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
#head2 _call |
202
|
|
|
|
|
|
|
# |
203
|
|
|
|
|
|
|
# Returns "ON" or "OFF" for both query and set http calls |
204
|
|
|
|
|
|
|
# |
205
|
|
|
|
|
|
|
# $self->_call(); #query |
206
|
|
|
|
|
|
|
# $self->_call(1); #on |
207
|
|
|
|
|
|
|
# $self->_call(0); #off |
208
|
|
|
|
|
|
|
# $self->_call(1, 10); #when off then does on 10 seconds wait then off |
209
|
|
|
|
|
|
|
# $self->_call(0, 10); #when on then does off 10 seconds wait then on |
210
|
|
|
|
|
|
|
# |
211
|
|
|
|
|
|
|
# When time is 0 or undef the type is set to 0 which is a simple on/off capability |
212
|
|
|
|
|
|
|
# When time is greater than 0 the the type is set to 1 which is cycle (jogging) capability |
213
|
|
|
|
|
|
|
# This package does not support type 2 which is a relay delay switching capability |
214
|
|
|
|
|
|
|
# The api does not support a toggle capability natively so toggle is implemented as a query/set. |
215
|
|
|
|
|
|
|
# |
216
|
|
|
|
|
|
|
#cut |
217
|
|
|
|
|
|
|
|
218
|
0
|
|
|
|
|
|
sub _call { |
219
|
0
|
|
|
0
|
|
|
my $self = shift; |
220
|
0
|
0
|
|
|
|
|
my $set = scalar(@_) ? 1 : 0; |
221
|
0
|
|
|
|
|
|
my $url = $self->url; #isa URI from Power::Outlet::Common::IP::HTTP |
222
|
0
|
|
|
|
|
|
my $relay = $self->relay; #e.g. 1 .. 32 |
223
|
0
|
|
|
|
|
|
my $relay0 = $relay - 1; #e.g. 0 .. 31 |
224
|
0
|
0
|
|
|
|
|
if ($set) { |
225
|
0
|
|
|
|
|
|
my $on = shift; #e.g. "1" | "0" |
226
|
0
|
|
0
|
|
|
|
my $time = shift || 0; #time seconds |
227
|
0
|
|
|
|
|
|
my $time_ds = int($time * 10); #time in 100ms increments (deciseconds) for the api |
228
|
0
|
0
|
|
|
|
|
my $type = $time > 0 ? 1 : 0; #0:relay on/off, 1:relay jogging, 2:relay delay |
229
|
0
|
|
|
|
|
|
my $pwd = $self->pwd; #password id token 0 .. 9999 |
230
|
0
|
|
|
|
|
|
$url->path($self->http_path . $self->_http_path_script_name_set); |
231
|
0
|
|
|
|
|
|
$url->query_form(type => $type, relay => $relay0, on => $on, time => $time_ds, pwd => $pwd); |
232
|
|
|
|
|
|
|
} else { |
233
|
0
|
|
|
|
|
|
$url->path($self->http_path . $self->_http_path_script_name_status); |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
#print "$url\n"; |
236
|
0
|
|
|
|
|
|
my $response = $self->http_client->request(GET => $url); |
237
|
0
|
0
|
|
|
|
|
if ($response->{"status"} eq "599") { |
|
|
0
|
|
|
|
|
|
238
|
0
|
|
|
|
|
|
die(sprintf(qq{HTTP Error: "%s %s", URL: "$url", Content: %s}, $response->{"status"}, $response->{"reason"}, $response->{"content"})); |
239
|
|
|
|
|
|
|
} elsif ($response->{"status"} ne "200") { |
240
|
0
|
|
|
|
|
|
die(sprintf(qq{HTTP Error: "%s %s", URL: "$url"}, $response->{"status"}, $response->{"reason"})); |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
0
|
|
|
|
|
|
my $return = ''; |
244
|
0
|
|
|
|
|
|
my $content = $response->{"content"}; |
245
|
|
|
|
|
|
|
#print "$content\n"; |
246
|
0
|
0
|
|
|
|
|
die(qq{Error: content malformed, url: "$url", content: "$content"}) unless $content =~ m/\A\&[0-9].*\&\Z/; |
247
|
0
|
|
|
|
|
|
my @values = split(/\&/, $content, -1); #LIMIT=-1 since split filters trailing values by default |
248
|
0
|
|
|
|
|
|
shift @values; #API has empty string as first array element |
249
|
0
|
|
|
|
|
|
pop @values; #API has empty string as last array element |
250
|
0
|
|
|
|
|
|
my $ok = shift @values; #0 => OK, 302 => NAK |
251
|
0
|
0
|
|
|
|
|
die(qq{Error: API returned error code "$ok". url: "$url", content: "$content"}) unless $ok eq '0'; |
252
|
0
|
0
|
|
|
|
|
if ($set) { |
253
|
|
|
|
|
|
|
#&0&0&0&1&0& #$ok, $type, $relay, $on, $time |
254
|
0
|
|
|
|
|
|
my ($type, $relay, $on, $time) = @values; |
255
|
0
|
|
|
|
|
|
$return = _state($on); |
256
|
|
|
|
|
|
|
} else { |
257
|
|
|
|
|
|
|
#&0&2&0&0& #$ok, $count, $relay[0], $relay[1] |
258
|
0
|
|
|
|
|
|
my $count = shift @values; |
259
|
0
|
|
|
|
|
|
my $on = $values[$relay0]; #relay is zero-based index |
260
|
0
|
|
|
|
|
|
$return = _state($on); |
261
|
|
|
|
|
|
|
} |
262
|
0
|
|
|
|
|
|
return $return; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
sub _state { |
265
|
0
|
|
|
0
|
|
|
my $state = shift; |
266
|
0
|
0
|
|
|
|
|
die("Error: API returned undefined relay state.") unless defined $state; |
267
|
0
|
0
|
|
|
|
|
return $state eq '1' ? 'ON' |
|
|
0
|
|
|
|
|
|
268
|
|
|
|
|
|
|
: $state eq '0' ? 'OFF' |
269
|
|
|
|
|
|
|
: die(qq{Error: API returned invalid relay state. state: "$state"}); |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
} |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=head1 BUGS |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
Please open an issue on GitHub. |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=head1 AUTHOR |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Michael R. Davis |
280
|
|
|
|
|
|
|
CPAN ID: MRDVT |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=head1 COPYRIGHT |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Copyright (c) 2020 Michael R. Davis |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
The full text of the license can be found in the LICENSE file included with this module. |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=head1 SEE ALSO |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
L => programming_manual_en.pdf page 12 "Protocol: HTTP GET CGI" |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
=cut |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
1; |