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