1 /*
2 USAGE:
3 ======
4 To use "Generic sysfs gpio" you need to export the inputs and outputs you
5 are going to use. On start up "Gerenic sysfs gpio" will pick up the exports
6 that are available and starts processing them. You may want to use chmod to
7 set gpio permissions
8
9 GPIO Sysfs Interface for Userspace
10 ==================================
11
12 Platforms which use the "gpiolib" implementors framework may choose to
13 configure a sysfs user interface to GPIOs. This is different from the
14 debugfs interface, since it provides control over GPIO direction and
15 value instead of just showing a gpio state summary. Plus, it could be
16 present on production systems without debugging support.
17
18 Given appropriate hardware documentation for the system, userspace could
19 know for example that GPIO #23 controls the write protect line used to
20 protect boot loader segments in flash memory. System upgrade procedures
21 may need to temporarily remove that protection, first importing a GPIO,
22 then changing its output state, then updating the code before re-enabling
23 the write protection. In normal use, GPIO #23 would never be touched,
24 and the kernel would have no need to know about it.
25
26 Again depending on appropriate hardware documentation, on some systems
27 userspace GPIO can be used to determine system configuration data that
28 standard kernels won't know about. And for some tasks, simple userspace
29 GPIO drivers could be all that the system really needs.
30
31 DO NOT ABUSE SYSFS TO CONTROL HARDWARE THAT HAS PROPER KERNEL DRIVERS.
32 PLEASE READ THE DOCUMENT NAMED "drivers-on-gpio.txt" IN THIS DOCUMENTATION
33 DIRECTORY TO AVOID REINVENTING KERNEL WHEELS IN USERSPACE. I MEAN IT.
34 REALLY.
35
36 Paths in Sysfs
37 --------------
38 There are three kinds of entries in /sys/class/gpio:
39
40 - Control interfaces used to get userspace control over GPIOs;
41
42 - GPIOs themselves; and
43
44 - GPIO controllers ("gpio_chip" instances).
45
46 That's in addition to standard files including the "device" symlink.
47
48 The control interfaces are write-only:
49
50 /sys/class/gpio/
51
52 "export" ... Userspace may ask the kernel to export control of
53 a GPIO to userspace by writing its number to this file.
54
55 Example: "echo 19 > export" will create a "gpio19" node
56 for GPIO #19, if that's not requested by kernel code.
57
58 "unexport" ... Reverses the effect of exporting to userspace.
59
60 Example: "echo 19 > unexport" will remove a "gpio19"
61 node exported using the "export" file.
62
63 GPIO signals have paths like /sys/class/gpio/gpio42/ (for GPIO #42)
64 and have the following read/write attributes:
65
66 /sys/class/gpio/gpioN/
67
68 "direction" ... reads as either "in" or "out". This value may
69 normally be written. Writing as "out" defaults to
70 initializing the value as low. To ensure glitch free
71 operation, values "low" and "high" may be written to
72 configure the GPIO as an output with that initial value.
73
74 Note that this attribute *will not exist* if the kernel
75 doesn't support changing the direction of a GPIO, or
76 it was exported by kernel code that didn't explicitly
77 allow userspace to reconfigure this GPIO's direction.
78
79 "value" ... reads as either 0 (low) or 1 (high). If the GPIO
80 is configured as an output, this value may be written;
81 any nonzero value is treated as high.
82
83 If the pin can be configured as interrupt-generating interrupt
84 and if it has been configured to generate interrupts (see the
85 description of "edge"), you can poll(2) on that file and
86 poll(2) will return whenever the interrupt was triggered. If
87 you use poll(2), set the events POLLPRI and POLLERR. If you
88 use select(2), set the file descriptor in exceptfds. After
89 poll(2) returns, either lseek(2) to the beginning of the sysfs
90 file and read the new value or close the file and re-open it
91 to read the value.
92
93 "edge" ... reads as either "none", "rising", "falling", or
94 "both". Write these strings to select the signal edge(s)
95 that will make poll(2) on the "value" file return.
96
97 This file exists only if the pin can be configured as an
98 interrupt generating input pin.SysfsGpio
99
100 "active_low" ... reads as either 0 (false) or 1 (true). Write
101 any nonzero value to invert the value attribute both
102 for reading and writing. Existing and subsequent
103 poll(2) support configuration via the edge attribute
104 for "rising" and "falling" edges will follow this
105 setting.
106
107 History:
108 03-jun-2017 HvB Add interrupt support for edge = rising, falling or both.
109 24-jun-2017 HvB Add hardware debounce parameter, range 10..750 milli sec.
110 04-jul-2017 HvB Poll after an interrupt received to recover missed interrupts
111 06-jul-2017 HvB Removed log message for interrupt state change, forum request
112 */
113
114 #include "stdafx.h"
115
116 #ifdef WITH_GPIO
117
118 #include <sys/types.h>
119 #include <stdio.h>
120 #include <string.h>
121 #include <stdlib.h>
122 #include "SysfsGpio.h"
123 #include "../main/Helper.h"
124 #include "../main/Logger.h"
125 #include "hardwaretypes.h"
126 #include "../main/RFXtrx.h"
127 #include "../main/mainworker.h"
128 #include "../main/SQLHelper.h"
129 #include "../main/localtime_r.h"
130
131 /*
132 Note:
133 HEATBEAT_COUNT, UPDATE_MASTER_COUNT and GPIO_POLL_MSEC are related.
134 Set values so that that heartbeat occurs every 10 seconds and update
135 master occurs once, 30 seconds after startup.
136 */
137 #define HEARTBEAT_COUNT 40
138 #define UPDATE_MASTER_COUNT 120
139 #define DB_UPDATE_DELAY 4
140 #define GPIO_POLL_MSEC 250
141
142 #define GPIO_IN 0
143 #define GPIO_OUT 1
144 #define GPIO_LOW 0
145 #define GPIO_HIGH 1
146 #define GPIO_EDGE_NONE 0
147 #define GPIO_EDGE_RISING 1
148 #define GPIO_EDGE_FALLING 2
149 #define GPIO_EDGE_BOTH 3
150 #define GPIO_EDGE_UNKNOWN -1
151 #define GPIO_PIN_MIN 0
152 #define GPIO_PIN_MAX 1023
153 #define GPIO_MAX_VALUE_SIZE 16
154 #define GPIO_MAX_PATH 64
155 #define GPIO_PATH "/sys/class/gpio/gpio"
156 #define GPIO_DEVICE_ID_BASE 0x030E0E00
157
158 std::vector<gpio_info> CSysfsGpio::m_saved_state;
159 int CSysfsGpio::m_sysfs_hwdid;
160 int CSysfsGpio::m_sysfs_req_update;
161
CSysfsGpio(const int ID,const int AutoConfigureDevices,const int Debounce)162 CSysfsGpio::CSysfsGpio(const int ID, const int AutoConfigureDevices, const int Debounce)
163 {
164 m_bIsStarted = false;
165 m_polling_enabled = false;
166 m_interrupts_enabled = false;
167 m_HwdID = ID;
168 m_sysfs_hwdid = ID;
169 m_auto_configure_devices = AutoConfigureDevices;
170 m_debounce_msec = Debounce;
171 m_maxfd = 0;
172 }
173
~CSysfsGpio(void)174 CSysfsGpio::~CSysfsGpio(void)
175 {
176 }
177
StartHardware()178 bool CSysfsGpio::StartHardware()
179 {
180 RequestStart();
181
182 Init();
183
184 m_thread = std::make_shared<std::thread>(&CSysfsGpio::Do_Work, this);
185 SetThreadNameInt(m_thread->native_handle());
186 m_bIsStarted = true;
187
188 return (m_thread != nullptr);
189 }
190
StopHardware()191 bool CSysfsGpio::StopHardware()
192 {
193 RequestStop();
194
195 try
196 {
197 if (m_thread)
198 {
199 m_thread->join();
200 m_thread.reset();
201 }
202 }
203 catch (...)
204 {
205
206 }
207 try
208 {
209 if (m_edge_thread)
210 {
211 m_edge_thread->join();
212 m_edge_thread.reset();
213 }
214 }
215 catch (...)
216 {
217
218 }
219
220 m_bIsStarted = false;
221 return true;
222 }
223
WriteToHardware(const char * pdata,const unsigned char length)224 bool CSysfsGpio::WriteToHardware(const char *pdata, const unsigned char length)
225 {
226 bool bOk = false;
227 const tRBUF *pSen = reinterpret_cast<const tRBUF*>(pdata);
228 unsigned char packettype = pSen->ICMND.packettype;
229 int gpio_pin = pSen->LIGHTING2.unitcode;
230
231 for (int i = 0; i < m_saved_state.size(); i++)
232 {
233 if ((m_saved_state[i].pin_number == gpio_pin) &&
234 (m_saved_state[i].direction == GPIO_OUT) &&
235 (packettype == pTypeLighting2))
236 {
237 int state = pSen->LIGHTING2.cmnd == light2_sOn ? GPIO_HIGH : GPIO_LOW;
238 GpioWrite(gpio_pin, state);
239 m_saved_state[i].db_state = state;
240 m_saved_state[i].value = state;
241 bOk = true;
242 break;
243 }
244 }
245 return bOk;
246 }
247
Do_Work()248 void CSysfsGpio::Do_Work()
249 {
250 bool bUpdateMaster = true;
251 int counter = 0;
252
253 while (!IsStopRequested(GPIO_POLL_MSEC))
254 {
255 counter++;
256
257 if (counter % HEARTBEAT_COUNT == 0) /* Heartbeat maintenance */
258 {
259 m_LastHeartbeat = mytime(NULL);
260 }
261
262 if (m_polling_enabled)
263 {
264 PollGpioInputs(false);
265 UpdateDomoticzInputs(false);
266 }
267
268 if (bUpdateMaster)
269 {
270 if (counter == UPDATE_MASTER_COUNT) /* only executes once, and only if we have a master/slave configuration */
271 {
272 bUpdateMaster = false;
273 std::vector<std::vector<std::string> > result;
274 result = m_sql.safe_query("SELECT ID FROM Users WHERE (RemoteSharing==1) AND (Active==1)");
275
276 if (!result.empty())
277 {
278 _log.Log(LOG_STATUS, "Sysfs GPIO: Update master devices");
279 UpdateDomoticzInputs(true);
280 }
281 }
282 }
283
284 if (m_sysfs_req_update) /* to assist manual device adding */
285 {
286 m_sysfs_req_update--;
287
288 if (m_sysfs_req_update == 0)
289 {
290 UpdateDomoticzDatabase();
291 }
292 }
293 }
294
295 /* termination, close all open fd's */
296 for (int i = 0; i < m_saved_state.size(); i++)
297 {
298 if (m_saved_state[i].read_value_fd != -1)
299 {
300 close(m_saved_state[i].read_value_fd);
301 m_saved_state[i].read_value_fd = -1;
302 }
303 }
304
305 _log.Log(LOG_STATUS, "Sysfs GPIO: Worker stopped");
306 }
307
EdgeDetectThread()308 void CSysfsGpio::EdgeDetectThread()
309 {
310 /*
311 Notes:
312 Changes in line state are signaled as an exception condition rather than a write. Not all
313 GPIOs are interrupt-capable. If the GPIO you're working with doesn't support interrupts
314 you will need to read it periodically. This is for example the case when the GPIO line is
315 implemented with an I2C peripheral or audio codec.
316
317 After one or more GPIO state change interrupts have been detected a poll is done to make
318 sure the domoticz database reflects the actual states of all GPIO pins in all cases. A
319 missed interrupt can occur when a GPIO pin changes state twice within m_debounce_msec.
320 Therefore it is a good practice to set m_debounce_msec to a value not higher then needed
321 depending on the input switch behavior.
322 */
323
324 ssize_t nb;
325 fd_set tmp_fds;
326 timeval tv;
327 int fd;
328 bool poll_once = false;
329
330 FD_ZERO(&m_rfds);
331 m_maxfd = 0;
332
333 for (int i = 0; i < m_saved_state.size(); i++) /* setup gpio pins */
334 {
335 if ((m_saved_state[i].direction == GPIO_IN) && (m_saved_state[i].edge != GPIO_EDGE_NONE))
336 {
337 fd = GpioOpenRw(m_saved_state[i].pin_number);
338 m_saved_state[i].edge_fd = fd; /* store file descriptor, closed after stoprequested */
339
340 if ((fd != -1) && (fd <= FD_SETSIZE))
341 {
342 FD_SET(fd, &m_rfds);
343 m_maxfd = (m_maxfd > fd ? m_maxfd : fd);
344 GpioWrite(m_saved_state[i].pin_number, 1); /* enable interrupts */
345 }
346 }
347 else
348 {
349 m_saved_state[i].edge_fd = -1;
350 }
351 }
352
353 _log.Log(LOG_STATUS, "Sysfs GPIO: Edge detection started");
354
355 while (!IsStopRequested(0)) /* detect gpio state changes */
356 {
357 tv.tv_sec = 1; // Set select timeout to 1 second.
358 tv.tv_usec = 0;
359 memcpy(&tmp_fds, &m_rfds, sizeof(tmp_fds));
360
361 //-------------------------------------------------------------------
362 // Wait for GPIO interrupt.
363 //
364 int retval = select(m_maxfd + 1, NULL, NULL, &tmp_fds, &tv);
365
366 if (IsStopRequested(0))
367 {
368 break;
369 }
370
371 if (retval > 0)
372 {
373 //---------------------------------------------------------------
374 // GPIO interrupt received.
375 //
376 sleep_milliseconds(m_debounce_msec); /* debounce */
377
378 for (int i = 0; i < m_saved_state.size(); i++)
379 {
380 if ((FD_ISSET(m_saved_state[i].edge_fd, &tmp_fds) &&
381 (m_saved_state[i].direction == GPIO_IN) &&
382 (m_saved_state[i].edge != GPIO_EDGE_NONE)))
383 {
384 int value = GpioReadFd(m_saved_state[i].edge_fd);
385 GpioSaveState(i, value);
386 FD_CLR(m_saved_state[i].edge_fd, &tmp_fds);
387 GpioWrite(m_saved_state[i].edge_fd, 1);
388 }
389 }
390
391 if (!m_polling_enabled)
392 {
393 UpdateDomoticzInputs(false);
394 poll_once = true;
395 }
396 }
397 else
398 {
399 //---------------------------------------------------------------
400 // Select timeout, no GPIO interrupt received.
401 //
402 if (poll_once)
403 {
404 PollGpioInputs(true);
405 UpdateDomoticzInputs(false);
406 poll_once = false;
407 }
408 }
409 }
410
411 for (int i = 0; i < m_saved_state.size(); i++) /* close all open edge detect fd's */
412 {
413 if (m_saved_state[i].edge_fd > 0)
414 {
415 close(m_saved_state[i].edge_fd);
416 m_saved_state[i].edge_fd = -1;
417 }
418 }
419
420 _log.Log(LOG_STATUS, "Sysfs GPIO: Edge detection stopped");
421 }
422
Init()423 void CSysfsGpio::Init()
424 {
425 int id = GPIO_DEVICE_ID_BASE + m_HwdID;
426 int input_count = 0;
427 int output_count = 0;
428 char path[GPIO_MAX_PATH];
429 m_polling_enabled = false;
430 m_interrupts_enabled = false;
431 m_sysfs_req_update = 0;
432
433 m_saved_state.clear();
434
435 /* default ligthing2 packet */
436 memset(&m_Packet, 0, sizeof(tRBUF));
437 m_Packet.LIGHTING2.packetlength = sizeof(m_Packet.LIGHTING2) - 1;
438 m_Packet.LIGHTING2.packettype = pTypeLighting2;
439 m_Packet.LIGHTING2.subtype = sTypeAC;
440 m_Packet.LIGHTING2.unitcode = 0;
441 m_Packet.LIGHTING2.id1 = (id >> 24) & 0xFF;
442 m_Packet.LIGHTING2.id2 = (id >> 16) & 0xFF;
443 m_Packet.LIGHTING2.id3 = (id >> 8) & 0xFF;
444 m_Packet.LIGHTING2.id4 = (id >> 0) & 0xFF;
445 m_Packet.LIGHTING2.cmnd = 0;
446 m_Packet.LIGHTING2.level = 0;
447 m_Packet.LIGHTING2.filler = 0;
448 m_Packet.LIGHTING2.rssi = 12;
449
450 FindGpioExports();
451
452 if (m_auto_configure_devices)
453 {
454 CreateDomoticzDevices();
455 }
456
457 UpdateGpioOutputs();
458
459 for (int i = 0; i < m_saved_state.size(); i++)
460 {
461 snprintf(path, GPIO_MAX_PATH, "%s%d/value", GPIO_PATH, m_saved_state[i].pin_number);
462 m_saved_state[i].read_value_fd = open(path, O_RDONLY);
463 if ((m_saved_state[i].direction == GPIO_IN) ? input_count++ : output_count++);
464
465 /* Enable polling if at least one input edge is set to NONE or is INVALID. */
466 if (!m_polling_enabled &&
467 (m_saved_state[i].direction == GPIO_IN) &&
468 ((m_saved_state[i].edge == GPIO_EDGE_NONE) || (m_saved_state[i].edge == GPIO_EDGE_UNKNOWN)))
469 {
470 m_polling_enabled = true;
471 }
472
473 /* Enable interrupts if at least one input edge is set to RISING FALLING or BOTH. */
474 if (!m_interrupts_enabled &&
475 (m_saved_state[i].direction == GPIO_IN) &&
476 ((m_saved_state[i].edge == GPIO_EDGE_RISING) ||
477 (m_saved_state[i].edge == GPIO_EDGE_FALLING) ||
478 (m_saved_state[i].edge == GPIO_EDGE_BOTH)))
479 {
480 m_interrupts_enabled = true;
481 }
482 }
483
484 UpdateDomoticzInputs(false); /* Make sure database inputs are in sync with actual hardware */
485
486 _log.Log(LOG_STATUS, "Sysfs GPIO: Startup - polling:%s interrupts:%s debounce:%dmsec inputs:%d outputs:%d",
487 m_polling_enabled ? "yes":"no",
488 m_interrupts_enabled ? "yes":"no",
489 m_debounce_msec,
490 input_count,
491 output_count);
492
493 if (m_interrupts_enabled)
494 {
495 m_edge_thread = std::make_shared<std::thread>(&CSysfsGpio::EdgeDetectThread, this);
496 SetThreadName(m_edge_thread->native_handle(), "SysfsGpio_Edge");
497 }
498 }
499
FindGpioExports()500 void CSysfsGpio::FindGpioExports()
501 {
502 m_saved_state.clear();
503
504 for (int gpio_pin = GPIO_PIN_MIN; gpio_pin <= GPIO_PIN_MAX; gpio_pin++)
505 {
506 char path[GPIO_MAX_PATH];
507 int fd;
508
509 snprintf(path, GPIO_MAX_PATH, "%s%d", GPIO_PATH, gpio_pin);
510 fd = open(path, O_RDONLY);
511
512 if (-1 != fd) /* GPIO export found */
513 {
514 gpio_info gi;
515 memset(&gi, 0, sizeof(gi));
516
517 gi.pin_number = gpio_pin;
518 gi.value = GpioRead(gpio_pin, "value");
519 gi.direction = GpioRead(gpio_pin, "direction");
520 gi.active_low = GpioRead(gpio_pin, "active_low");
521 gi.edge = GpioRead(gpio_pin, "edge");
522 gi.read_value_fd = -1;
523 gi.db_state = -1;
524 gi.id_valid = -1;
525 gi.id1 = 0;
526 gi.id2 = 0;
527 gi.id3 = 0;
528 gi.id4 = 0;
529 gi.request_update = -1;
530
531 m_saved_state.push_back(gi);
532 close(fd);
533 }
534 }
535 }
536
PollGpioInputs(bool PollOnce)537 void CSysfsGpio::PollGpioInputs(bool PollOnce)
538 {
539 if (m_saved_state.size())
540 {
541 for (int i = 0; i < m_saved_state.size(); i++)
542 {
543 if ((m_saved_state[i].direction == GPIO_IN) &&
544 (m_saved_state[i].read_value_fd != -1) &&
545 (PollOnce || (m_saved_state[i].edge == GPIO_EDGE_NONE) || (m_saved_state[i].edge == GPIO_EDGE_UNKNOWN)))
546 {
547 GpioSaveState(i, GpioReadFd(m_saved_state[i].read_value_fd));
548 }
549 }
550 }
551 }
552
CreateDomoticzDevices()553 void CSysfsGpio::CreateDomoticzDevices()
554 {
555 std::vector<std::vector<std::string> > result;
556 std::vector<std::string> deviceid;
557
558 for (int i = 0; i < m_saved_state.size(); i++)
559 {
560 bool createNewDevice = false;
561 deviceid = GetGpioDeviceId();
562
563 if (m_saved_state[i].direction == GPIO_IN)
564 {
565 /* Input */
566
567 result = m_sql.safe_query("SELECT nValue,Options FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
568 m_HwdID,
569 m_saved_state[i].pin_number);
570
571 if (result.empty())
572 {
573 createNewDevice = true;
574 }
575 else
576 {
577 if (!result.empty()) /* found */
578 {
579 std::vector<std::string> sd = result[0];
580
581 if (sd[1].empty())
582 {
583 /* update manual created device */
584 m_saved_state[i].request_update = 1;
585 UpdateDomoticzDatabase();
586 }
587 else
588 {
589 if (atoi(sd[1].c_str()) != GPIO_IN) /* device was not an input, delete it */
590 {
591 m_sql.safe_query(
592 "DELETE FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
593 m_HwdID, m_saved_state[i].pin_number);
594
595 createNewDevice = true;
596 }
597 }
598 }
599 }
600
601 if (createNewDevice)
602 {
603 /* create new input device */
604 m_sql.safe_query(
605 "INSERT INTO DeviceStatus (HardwareID, DeviceID, Unit, Type, SubType, SwitchType, Used, SignalLevel, BatteryLevel, Name, nValue, sValue, Options) "
606 "VALUES (%d,'%q',%d, %d, %d, %d, 0, 12, 255, '%q', %d, ' ', '0')",
607 m_HwdID, deviceid[0].c_str(), m_saved_state[i].pin_number, pTypeLighting2, sTypeAC, int(STYPE_Contact), "Input", m_saved_state[i].value);
608 }
609 }
610 else
611 {
612 /* Output */
613
614 result = m_sql.safe_query("SELECT nValue,Options FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
615 m_HwdID,
616 m_saved_state[i].pin_number);
617
618 if (result.empty())
619 {
620 createNewDevice = true;
621 }
622 else
623 {
624 if (!result.empty()) /* found */
625 {
626 std::vector<std::string> sd = result[0];
627
628 if (sd[1].empty())
629 {
630 /* update manual created output device */
631 m_saved_state[i].request_update = 1;
632 UpdateDomoticzDatabase();
633 }
634 else
635 {
636 if ((atoi(sd[1].c_str()) != GPIO_OUT)) /* device was not an output, delete it */
637 {
638 m_sql.safe_query(
639 "DELETE FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
640 m_HwdID, m_saved_state[i].pin_number);
641
642 createNewDevice = true;
643 }
644 else
645 {
646 GpioWrite(m_saved_state[i].pin_number, atoi(sd[0].c_str()));
647 }
648 }
649 }
650 }
651
652 if (createNewDevice)
653 {
654 /* create new output device */
655 m_sql.safe_query(
656 "INSERT INTO DeviceStatus (HardwareID, DeviceID, Unit, Type, SubType, SwitchType, Used, SignalLevel, BatteryLevel, Name, nValue, sValue, Options) "
657 "VALUES (%d,'%q',%d, %d, %d, %d, 0, 12, 255, '%q', %d, ' ', '1')",
658 m_HwdID, deviceid[0].c_str(), m_saved_state[i].pin_number, pTypeLighting2, sTypeAC, int(STYPE_OnOff), "Output", m_saved_state[i].value);
659 }
660 }
661 }
662 }
663
UpdateDomoticzDatabase()664 void CSysfsGpio::UpdateDomoticzDatabase()
665 {
666 for (int i = 0; i < m_saved_state.size(); i++)
667 {
668 if (m_saved_state[i].request_update == 1)
669 {
670 std::vector<std::vector<std::string> > result = m_sql.safe_query("SELECT nValue,Options FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
671 m_HwdID,
672 m_saved_state[i].pin_number);
673
674 if (!result.empty())
675 {
676 //std::vector<std::string> sd = result[0];
677 m_saved_state[i].value = GpioRead(m_saved_state[i].pin_number, "value");
678
679 m_sql.safe_query(
680 "UPDATE DeviceStatus SET nValue=%d,Options=%d WHERE (HardwareID==%d) AND (Unit==%d)",
681 m_saved_state[i].value, m_saved_state[i].direction, m_HwdID, m_saved_state[i].pin_number);
682
683 m_saved_state[i].db_state = m_saved_state[i].value;
684 m_saved_state[i].id_valid = -1;
685 }
686
687 m_saved_state[i].request_update = -1;
688 }
689 }
690 }
691
UpdateDomoticzInputs(bool forceUpdate)692 void CSysfsGpio::UpdateDomoticzInputs(bool forceUpdate)
693 {
694 for (int i = 0; i < m_saved_state.size(); i++)
695 {
696 if ((m_saved_state[i].direction == GPIO_IN) && (m_saved_state[i].value != -1))
697 {
698 int state = GPIO_LOW;
699
700 if (m_saved_state[i].active_low)
701 {
702 if (m_saved_state[i].value == GPIO_LOW)
703 {
704 state = GPIO_HIGH;
705 }
706 }
707 else
708 {
709 if (m_saved_state[i].value == GPIO_HIGH)
710 {
711 state = GPIO_HIGH;
712 }
713 }
714
715 if ((m_saved_state[i].db_state != state) || (forceUpdate))
716 {
717 bool updateDatabase = false;
718 bool log_db_change = false;
719 std::vector< std::vector<std::string> > result = m_sql.safe_query("SELECT nValue,Used FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
720 m_HwdID,
721 m_saved_state[i].pin_number);
722
723 if ((!result.empty()) && (result.size() > 0))
724 {
725 std::vector<std::string> sd = result[0];
726
727 if (atoi(sd[1].c_str()) == 1) /* Check if device is used */
728 {
729 int db_state = GPIO_HIGH;
730
731 if (atoi(sd[0].c_str()) == GPIO_LOW) /* determine database state*/
732 {
733 db_state = GPIO_LOW;
734 }
735
736 if ((db_state != state) || (forceUpdate)) /* check if db update is required */
737 {
738 if (db_state != state)
739 {
740 log_db_change = true;
741 }
742 updateDatabase = true;
743 }
744
745 m_saved_state[i].db_state = state; /* save new database state */
746 }
747 }
748
749 if (updateDatabase) /* send packet to Domoticz */
750 {
751 if (state)
752 {
753 m_Packet.LIGHTING2.cmnd = light2_sOn;
754 m_Packet.LIGHTING2.level = 100;
755 }
756 else
757 {
758 m_Packet.LIGHTING2.cmnd = light2_sOff;
759 m_Packet.LIGHTING2.level = 0;
760 }
761
762 UpdateDeviceID(m_saved_state[i].pin_number);
763 m_Packet.LIGHTING2.unitcode = (char)m_saved_state[i].pin_number;
764 m_Packet.LIGHTING2.seqnbr++;
765 sDecodeRXMessage(this, (const unsigned char *)&m_Packet.LIGHTING2, "Input", 255);
766
767 if (log_db_change)
768 {
769 _log.Log(LOG_STATUS, "Sysfs GPIO: gpio%d new state = %s",
770 m_saved_state[i].pin_number,
771 state ? "on" : "off");
772 }
773 }
774 }
775 }
776 }
777 }
778
UpdateDeviceID(int pin)779 void CSysfsGpio::UpdateDeviceID(int pin)
780 {
781 int index;
782 bool pin_found = false;
783
784 /* Note: Support each pin is allowed to have a different device id */
785
786 for (index = 0; index < m_saved_state.size(); index++)
787 {
788 if (m_saved_state[index].pin_number == pin)
789 {
790 pin_found = true;
791 break;
792 }
793 }
794
795 if (pin_found)
796 {
797 /* Existing pin was found */
798
799 if (m_saved_state[index].id_valid == -1)
800 {
801 std::string sdeviceid;
802 std::vector< std::vector<std::string> > result = m_sql.safe_query("SELECT DeviceID FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
803 m_HwdID,
804 pin);
805
806 if ((!result.empty() && (result.size() > 0)))
807 {
808 /* use database device id */
809 std::vector<std::string> sd = result[0];
810 sdeviceid = sd[0];
811 }
812 else
813 {
814 /* use generated device id */
815 std::vector<std::string> deviceid = GetGpioDeviceId();
816 sdeviceid = deviceid[0];
817 }
818
819 /* extract hex device sub-id's */
820 m_saved_state[index].id1 = strtol(sdeviceid.substr(0, 1).c_str(), NULL, 16) & 0xFF;
821 m_saved_state[index].id2 = strtol(sdeviceid.substr(1, 2).c_str(), NULL, 16) & 0xFF;
822 m_saved_state[index].id3 = strtol(sdeviceid.substr(3, 2).c_str(), NULL, 16) & 0xFF;
823 m_saved_state[index].id4 = strtol(sdeviceid.substr(5, 2).c_str(), NULL, 16) & 0xFF;
824 m_saved_state[index].id_valid = 1;
825 }
826
827 /* update device sub-id's in packet */
828 m_Packet.LIGHTING2.id1 = m_saved_state[index].id1;
829 m_Packet.LIGHTING2.id2 = m_saved_state[index].id2;
830 m_Packet.LIGHTING2.id3 = m_saved_state[index].id3;
831 m_Packet.LIGHTING2.id4 = m_saved_state[index].id4;
832 }
833 }
834
UpdateGpioOutputs()835 void CSysfsGpio::UpdateGpioOutputs()
836 {
837 /* make sure actual gpio output values match database */
838
839 for (int i = 0; i < m_saved_state.size(); i++)
840 {
841 if (m_saved_state[i].direction == GPIO_OUT)
842 {
843 std::vector<std::vector<std::string> > result = m_sql.safe_query("SELECT nValue,Used FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
844 m_HwdID,
845 m_saved_state[i].pin_number);
846
847 if ((!result.empty()) && (result.size() > 0))
848 {
849 std::vector<std::string> sd = result[0];
850 m_saved_state[i].db_state = atoi(sd[0].c_str());
851
852 if (atoi(sd[1].c_str()))
853 {
854 /* device is used, update gpio output pin */
855 GpioWrite(m_saved_state[i].pin_number, m_saved_state[i].db_state);
856 m_saved_state[i].value = m_saved_state[i].db_state;
857 }
858 }
859 }
860 }
861 }
862
GetGpioDeviceId()863 std::vector<std::string> CSysfsGpio::GetGpioDeviceId()
864 {
865 std::vector<std::string> gpio_deviceid;
866 char szIdx[10];
867 int id = GPIO_DEVICE_ID_BASE + m_sysfs_hwdid;
868
869 snprintf(szIdx, sizeof(szIdx), "%7X", id);
870 gpio_deviceid.push_back(szIdx);
871
872 return gpio_deviceid;
873 }
874
875 //---------------------------------------------------------------------------
876 // sysfs gpio helper functions
877 //
GetReadResult(int bytecount,char * value_str)878 int CSysfsGpio::GetReadResult(int bytecount, char* value_str)
879 {
880 int retval = -1;
881
882 switch (bytecount)
883 {
884 case -1:
885 case 0:
886 case 1:
887 {
888 break;
889 }
890
891 default:
892 {
893 if ((memcmp("0", value_str, strlen("0")) == 0) ||
894 (memcmp("in", value_str, strlen("in")) == 0) ||
895 (memcmp("none", value_str, strlen("none")) == 0))
896 {
897 retval = 0;
898 break;
899 }
900
901 if ((memcmp("1", value_str, strlen("1")) == 0) ||
902 (memcmp("out", value_str, strlen("out")) == 0) ||
903 (memcmp("rising", value_str, strlen("rising")) == 0))
904 {
905 retval = 1;
906 break;
907 }
908
909 if (memcmp("falling", value_str, strlen("falling")) == 0)
910 {
911 retval = 2;
912 break;
913 }
914
915 if (memcmp("both", value_str, strlen("both")) == 0)
916 {
917 retval = 3;
918 break;
919 }
920 }
921 }
922
923 return (retval);
924 }
925
GpioRead(int gpio_pin,const char * param)926 int CSysfsGpio::GpioRead(int gpio_pin, const char *param)
927 {
928 char path[GPIO_MAX_PATH];
929 char value_str[GPIO_MAX_VALUE_SIZE];
930 int fd;
931 int bytecount = -1;
932
933 snprintf(path, GPIO_MAX_PATH, "%s%d/%s", GPIO_PATH, gpio_pin, param);
934 fd = open(path, O_RDONLY);
935
936 if (fd == -1)
937 {
938 return(-1);
939 }
940
941 bytecount = read(fd, value_str, GPIO_MAX_VALUE_SIZE);
942 close(fd);
943
944 if (-1 == bytecount)
945 {
946 close(fd);
947 return(-1);
948 }
949
950 return(GetReadResult(bytecount, &value_str[0]));
951 }
952
GpioReadFd(int fd)953 int CSysfsGpio::GpioReadFd(int fd)
954 {
955 int bytecount = -1;
956 char value_str[GPIO_MAX_VALUE_SIZE];
957
958 if (fd == -1)
959 {
960 return(-1);
961 }
962
963 if (lseek(fd, 0, SEEK_SET) == -1)
964 {
965 return (-1);
966 }
967
968 bytecount = read(fd, value_str, GPIO_MAX_VALUE_SIZE);
969
970 return(GetReadResult(bytecount, &value_str[0]));
971 }
972
GpioWrite(int gpio_pin,int value)973 int CSysfsGpio::GpioWrite(int gpio_pin, int value)
974 {
975 char path[GPIO_MAX_PATH];
976 int fd;
977
978 snprintf(path, GPIO_MAX_PATH, "%s%d/value", GPIO_PATH, gpio_pin);
979 fd = open(path, O_WRONLY);
980
981 if (fd == -1)
982 {
983 return(-1);
984 }
985
986 if (1 != write(fd, value ? "1" : "0", 1))
987 {
988 close(fd);
989 return(-1);
990 }
991
992 close(fd);
993 return(0);
994 }
995
GpioOpenRw(int gpio_pin)996 int CSysfsGpio::GpioOpenRw(int gpio_pin)
997 {
998 char path[GPIO_MAX_PATH];
999
1000 snprintf(path, GPIO_MAX_PATH, "%s%d/value", GPIO_PATH, gpio_pin);
1001 int fd = open(path, O_RDWR);
1002
1003 return(fd);
1004 }
1005
1006 //---------------------------------------------------------------------------
1007 // Called by WebServer when devices are manually configured.
1008 //
GetGpioIds()1009 std::vector<int> CSysfsGpio::GetGpioIds()
1010 {
1011 std::vector<int> gpio_ids;
1012
1013 for (int i = 0; i < m_saved_state.size(); i++)
1014 {
1015 std::vector<std::vector<std::string> > result = m_sql.safe_query("SELECT ID, Used FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
1016 m_sysfs_hwdid,
1017 m_saved_state[i].pin_number);
1018
1019 if (result.empty())
1020 {
1021 /* add pin to the list only if it does not exist in the db */
1022 gpio_ids.push_back(m_saved_state[i].pin_number);
1023 }
1024 }
1025
1026 return gpio_ids;
1027 }
1028
GetGpioNames()1029 std::vector<std::string> CSysfsGpio::GetGpioNames()
1030 {
1031 std::vector<std::string> gpio_names;
1032
1033 for (int i = 0; i < m_saved_state.size(); i++)
1034 {
1035 std::vector<std::vector<std::string> > result = m_sql.safe_query("SELECT ID, Used FROM DeviceStatus WHERE (HardwareID==%d) AND (Unit==%d)",
1036 m_sysfs_hwdid,
1037 m_saved_state[i].pin_number);
1038
1039 if (result.empty())
1040 {
1041 /* add name to the list only if it does not exist in the db */
1042 char name[32];
1043 snprintf(name, sizeof(name), "gpio%d-%s", m_saved_state[i].pin_number, m_saved_state[i].direction ? "output" : "input");
1044 gpio_names.push_back(name);
1045 }
1046 }
1047
1048 return gpio_names;
1049 }
1050
RequestDbUpdate(int pin)1051 void CSysfsGpio::RequestDbUpdate(int pin)
1052 {
1053 for (int i = 0; i < m_saved_state.size(); i++)
1054 {
1055 if (m_saved_state[i].pin_number == pin)
1056 {
1057 m_saved_state[i].request_update = 1;
1058 }
1059 }
1060
1061 m_sysfs_req_update = DB_UPDATE_DELAY;
1062 }
1063
1064 //---------------------------------------------------------------------------
1065
GpioGetState(int index)1066 int CSysfsGpio::GpioGetState(int index)
1067 {
1068 m_state_mutex.lock();
1069 int value = m_saved_state[index].value;
1070 m_state_mutex.unlock();
1071
1072 return value;
1073 }
1074
GpioSaveState(int index,int value)1075 void CSysfsGpio::GpioSaveState(int index, int value)
1076 {
1077 m_state_mutex.lock();
1078 m_saved_state[index].value = value;
1079 m_state_mutex.unlock();
1080 }
1081
1082 //---------------------------------------------------------------------------
1083
1084 #endif // WITH_GPIO
1085