Instrument Neutral Distributed Interface INDI 2.2.5
wake_on_lan.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2014 Jasem Mutlaq. All rights reserved.
3
4 Driver for Wake-on-LAN power control
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 The full GNU General Public License is included in this distribution in the
22 file called LICENSE.
23*******************************************************************************/
24
25#include "wake_on_lan.h"
26
27#include <cstring>
28#include <string>
29
30// We declare an auto pointer to WakeOnLAN.
31std::unique_ptr<WakeOnLAN> wakeonlan(new WakeOnLAN());
32
34{
35 setVersion(1, 0);
36 // WoL has no physical connection — always treat as connected
37 setConnected(true, IPS_OK);
38}
39
41{
42 return "Wake On Lan";
43}
44
46{
48
49 // Device 1
50 Device1TP[0].fill("MAC", "MAC Address", "");
51 Device1TP[1].fill("ALIAS", "Alias", "");
52 Device1TP[2].fill("IP", "IP Address", "");
53 Device1TP.fill(getDeviceName(), "DEVICE_1", "Device 1", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
54 Device1TP.load();
55
56 // Device 2
57 Device2TP[0].fill("MAC", "MAC Address", "");
58 Device2TP[1].fill("ALIAS", "Alias", "");
59 Device2TP[2].fill("IP", "IP Address", "");
60 Device2TP.fill(getDeviceName(), "DEVICE_2", "Device 2", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
61 Device2TP.load();
62
63 // Device 3
64 Device3TP[0].fill("MAC", "MAC Address", "");
65 Device3TP[1].fill("ALIAS", "Alias", "");
66 Device3TP[2].fill("IP", "IP Address", "");
67 Device3TP.fill(getDeviceName(), "DEVICE_3", "Device 3", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
68 Device3TP.load();
69
70 // Device 4
71 Device4TP[0].fill("MAC", "MAC Address", "");
72 Device4TP[1].fill("ALIAS", "Alias", "");
73 Device4TP[2].fill("IP", "IP Address", "");
74 Device4TP.fill(getDeviceName(), "DEVICE_4", "Device 4", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
75 Device4TP.load();
76
77 // Send buttons - labels will be updated from aliases
78 Send1SP[0].fill("SEND", "Send", ISS_OFF);
79 Send1SP.fill(getDeviceName(), "Device 1", "Device 1", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
80
81 Send2SP[0].fill("SEND", "Send", ISS_OFF);
82 Send2SP.fill(getDeviceName(), "Device 2", "Device 2", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
83
84 Send3SP[0].fill("SEND", "Send", ISS_OFF);
85 Send3SP.fill(getDeviceName(), "Device 3", "Device 3", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
86
87 Send4SP[0].fill("SEND", "Send", ISS_OFF);
88 Send4SP.fill(getDeviceName(), "Device 4", "Device 4", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
89
91
92 return true;
93}
94
95void WakeOnLAN::ISGetProperties(const char *dev)
96{
98
99 // Define and load device properties first
100 defineProperty(Device1TP);
101 defineProperty(Device2TP);
102 defineProperty(Device3TP);
103 defineProperty(Device4TP);
104
105 // Update Send button labels from saved aliases before defining them
106 if (Device1TP[1].getText() && strlen(Device1TP[1].getText()) > 0)
107 Send1SP.setLabel(Device1TP[1].getText());
108
109 if (Device2TP[1].getText() && strlen(Device2TP[1].getText()) > 0)
110 Send2SP.setLabel(Device2TP[1].getText());
111
112 if (Device3TP[1].getText() && strlen(Device3TP[1].getText()) > 0)
113 Send3SP.setLabel(Device3TP[1].getText());
114
115 if (Device4TP[1].getText() && strlen(Device4TP[1].getText()) > 0)
116 Send4SP.setLabel(Device4TP[1].getText());
117
118 // Now define Send buttons with the correct labels already set
119 defineProperty(Send1SP);
120 defineProperty(Send2SP);
121 defineProperty(Send3SP);
122 defineProperty(Send4SP);
123
124 // Set initial button state: BUSY if an IP is configured (TimerHit will resolve it)
125 if (Device1TP[2].getText() && strlen(Device1TP[2].getText()) > 0) Send1SP.setState(IPS_BUSY);
126 if (Device2TP[2].getText() && strlen(Device2TP[2].getText()) > 0) Send2SP.setState(IPS_BUSY);
127 if (Device3TP[2].getText() && strlen(Device3TP[2].getText()) > 0) Send3SP.setState(IPS_BUSY);
128 if (Device4TP[2].getText() && strlen(Device4TP[2].getText()) > 0) Send4SP.setState(IPS_BUSY);
129}
130
132{
134
135 if (isConnected())
136 {
137 defineProperty(Device1TP);
138 defineProperty(Device2TP);
139 defineProperty(Device3TP);
140 defineProperty(Device4TP);
141
142 defineProperty(Send1SP);
143 defineProperty(Send2SP);
144 defineProperty(Send3SP);
145 defineProperty(Send4SP);
146 }
147 else
148 {
149 deleteProperty(Device1TP);
150 deleteProperty(Device2TP);
151 deleteProperty(Device3TP);
152 deleteProperty(Device4TP);
153
154 deleteProperty(Send1SP);
155 deleteProperty(Send2SP);
156 deleteProperty(Send3SP);
157 deleteProperty(Send4SP);
158 }
159
160 return true;
161}
162
164{
165 LOG_INFO("Wake On Lan driver connected.");
166 m_stopPing = false;
167 SetTimer(100); // fire first ping immediately
168 return true;
169}
170
172{
173 m_stopPing = true;
174 // Reset all button states to idle so the UI is clean
175 Send1SP.setState(IPS_IDLE); Send1SP.apply();
176 Send2SP.setState(IPS_IDLE); Send2SP.apply();
177 Send3SP.setState(IPS_IDLE); Send3SP.apply();
178 Send4SP.setState(IPS_IDLE); Send4SP.apply();
179 LOG_INFO("Wake On Lan driver disconnected. Ping monitoring stopped.");
180 return true;
181}
182
183
185{
186 if (m_stopPing)
187 return;
188
189 checkPingStatus(0, Device1TP, Send1SP);
190 checkPingStatus(1, Device2TP, Send2SP);
191 checkPingStatus(2, Device3TP, Send3SP);
192 checkPingStatus(3, Device4TP, Send4SP);
193
194 SetTimer(5000);
195}
196
197bool WakeOnLAN::pingDevice(const char *ip)
198{
199 std::string cmd = "ping -c1 -W1 " + std::string(ip) + " > /dev/null 2>&1";
200 return system(cmd.c_str()) == 0;
201}
202
203void WakeOnLAN::checkPingStatus(int idx, INDI::PropertyText &devTP, INDI::PropertySwitch &sendSP)
204{
205 INDI_UNUSED(idx);
206
207 const char *ip = devTP[2].getText();
208 if (!ip || strlen(ip) == 0)
209 return;
210
211 const char *alias = devTP[1].getText();
212 const char *label = (alias && strlen(alias) > 0) ? alias : devTP.getLabel();
213
214 if (pingDevice(ip))
215 {
216 LOGF_INFO("%s (%s) is online.", label, ip);
217 sendSP.setState(IPS_OK);
218 }
219 else
220 {
221 LOGF_INFO("%s (%s) is not reachable.", label, ip);
222 sendSP.setState(IPS_ALERT);
223 }
224 sendSP.apply();
225}
226
227bool WakeOnLAN::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
228{
229 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
230 {
231 if (Device1TP.isNameMatch(name))
232 {
233 Device1TP.update(texts, names, n);
234 Device1TP.setState(IPS_OK);
235 Device1TP.apply();
236 saveConfig(true, "DEVICE_1");
237 if (Device1TP[1].getText())
238 Send1SP.setLabel(Device1TP[1].getText());
239 const char *ip1 = Device1TP[2].getText();
240 if (ip1 && strlen(ip1) > 0)
241 {
242 Send1SP.setState(IPS_BUSY);
243 Send1SP.apply();
244 }
245 return true;
246 }
247
248 if (Device2TP.isNameMatch(name))
249 {
250 Device2TP.update(texts, names, n);
251 Device2TP.setState(IPS_OK);
252 Device2TP.apply();
253 saveConfig(true, "DEVICE_2");
254 if (Device2TP[1].getText())
255 Send2SP.setLabel(Device2TP[1].getText());
256 const char *ip2 = Device2TP[2].getText();
257 if (ip2 && strlen(ip2) > 0)
258 {
259 Send2SP.setState(IPS_BUSY);
260 Send2SP.apply();
261 }
262 return true;
263 }
264
265 if (Device3TP.isNameMatch(name))
266 {
267 Device3TP.update(texts, names, n);
268 Device3TP.setState(IPS_OK);
269 Device3TP.apply();
270 saveConfig(true, "DEVICE_3");
271 if (Device3TP[1].getText())
272 Send3SP.setLabel(Device3TP[1].getText());
273 const char *ip3 = Device3TP[2].getText();
274 if (ip3 && strlen(ip3) > 0)
275 {
276 Send3SP.setState(IPS_BUSY);
277 Send3SP.apply();
278 }
279 return true;
280 }
281
282 if (Device4TP.isNameMatch(name))
283 {
284 Device4TP.update(texts, names, n);
285 Device4TP.setState(IPS_OK);
286 Device4TP.apply();
287 saveConfig(true, "DEVICE_4");
288 if (Device4TP[1].getText())
289 Send4SP.setLabel(Device4TP[1].getText());
290 const char *ip4 = Device4TP[2].getText();
291 if (ip4 && strlen(ip4) > 0)
292 {
293 Send4SP.setState(IPS_BUSY);
294 Send4SP.apply();
295 }
296 return true;
297 }
298 }
299
300 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
301}
302
303bool WakeOnLAN::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
304{
305 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
306 {
307 auto handleSend = [&](INDI::PropertyText &devTP, INDI::PropertySwitch &sendSP, int num) -> bool
308 {
309 if (!sendSP.isNameMatch(name))
310 return false;
311 if (devTP[0].getText() == nullptr || strlen(devTP[0].getText()) == 0)
312 {
313 LOGF_ERROR("MAC address %d not configured.", num);
314 sendSP.setState(IPS_ALERT);
315 }
316 else if (sendWakeOnLan(devTP[0].getText()))
317 {
318 LOGF_INFO("Wake On Lan packet sent to %s (%s)", devTP[1].getText(), devTP[0].getText());
319 const char *ip = devTP[2].getText();
320 sendSP.setState((ip && strlen(ip) > 0) ? IPS_BUSY : IPS_OK);
321 }
322 else
323 {
324 LOGF_ERROR("Failed to send Wake On Lan packet for %s (%s)", devTP[1].getText(), devTP[0].getText());
325 sendSP.setState(IPS_ALERT);
326 }
327 sendSP.reset();
328 sendSP.apply();
329 return true;
330 };
331
332 if (handleSend(Device1TP, Send1SP, 1)) return true;
333 if (handleSend(Device2TP, Send2SP, 2)) return true;
334 if (handleSend(Device3TP, Send3SP, 3)) return true;
335 if (handleSend(Device4TP, Send4SP, 4)) return true;
336 }
337
338 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
339}
340
341
343{
345
346 Device1TP.save(fp);
347 Device2TP.save(fp);
348 Device3TP.save(fp);
349 Device4TP.save(fp);
350
351 return true;
352}
353
354bool WakeOnLAN::sendWakeOnLan(const char *macAddress)
355{
356 if (!macAddress || strlen(macAddress) == 0)
357 {
358 LOG_ERROR("MAC address not configured");
359 return false;
360 }
361
362 std::string cmd = "wakeonlan " + std::string(macAddress);
363 int ret = system(cmd.c_str());
364 if (ret == 0)
365 {
366 LOGF_INFO("Wake on LAN packet sent to %s", macAddress);
367 return true;
368 }
369 if (ret == 127)
370 {
371 LOG_ERROR("WoL: wakeonlan tool not found. Please install it on your system.");
372 return false;
373 }
374 LOG_ERROR("WoL: wakeonlan failed to send the packet.");
375 return false;
376}
bool isConnected() const
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual void setConnected(bool status, IPState state=IPS_OK, const char *msg=nullptr)
Set connection switch status in the client.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
void setLabel(const char *label)
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
INDI::PropertyViewText * getText() const
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
const char * getDefaultName() override
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
bool Disconnect() override
Disconnect from device.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ IP_RW
Definition indiapi.h:184
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_ATMOST1
Definition indiapi.h:172
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
uint8_t cmd[4]
Definition pwc-ioctl.h:2
void setState(IPState state)
std::unique_ptr< WakeOnLAN > wakeonlan(new WakeOnLAN())