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