1 /************************************************************************
2  *
3  PiFace Interface board driver for Domoticz
4  Date: 01-10-2013
5  Written by: Robin Stevenhagen
6  License: Public domain
7 
8  Date: 18-06-2015
9  GizMoCuz: Added messages to a send queue
10 
11  Date: 08-04-2016
12  BobKersten: Added multiple counter types
13 
14  SPI code is based on code from:
15  The WiringPi project
16  *    Copyright (c) 2012 Gordon Henderson
17  ***********************************************************************
18  * See    https://projects.drogon.net/raspberry-pi/wiringpi/
19  ***********************************************************************
20 
21  Code uses SPI dev 0.0, as this is the interface were the PiFace resides.
22  PiFace has 8 Input (4 buttons) and 8 Output pins, of which output 0 and 1 are relay outputs.
23  Driver autodetects all available PiFace boards on startup, and scans them.
24 
25  Default config (without config):
26  Input 0..3 enabled with rising edge detection
27  Input 4..7 enabled for pulse counting
28  Output 0..7 enbled with level feedback reporting.
29 
30  ************************************************************************/
31 
32 #include "stdafx.h"
33 #include "PiFace.h"
34 #include <stdio.h>
35 #include <fcntl.h>
36 #include <stdlib.h>
37 #include <errno.h>
38 #include <string.h>
39 #include <string>
40 #ifdef HAVE_LINUX_SPI
41     #include <linux/ioctl.h>
42     #include <linux/types.h>
43     #include <linux/spi/spidev.h>
44     #include <unistd.h>
45     #include <sys/ioctl.h>
46 #endif
47 #include "../main/Helper.h"
48 #include "../main/Logger.h"
49 #include "hardwaretypes.h"
50 #include "../main/RFXtrx.h"
51 #include "../main/SQLHelper.h"
52 #include "MCP23x17.h"
53 #include <fstream>
54 #include <algorithm>
55 #include <functional>
56 #include <cctype>
57 #include <locale>
58 #include "../main/localtime_r.h"
59 #include "../main/WebServer.h"
60 #include "../main/mainworker.h"
61 
62 #define PIFACE_INPUT_PROCESS_INTERVAL           5                // 100 msec
63 #define PIFACE_COUNTER_COUNTER_INTERVAL         1
64 #define PIFACE_WORKER_THREAD_SLEEP_INTERVAL_MS  20
65 
66 #define DISABLE_NEW_FUNCTIONS
67 
68 extern std::string szUserDataFolder;
69 
70 const std::string CPiFace::ParameterNames[CONFIG_NR_OF_PARAMETER_TYPES]                       = {"enable","enabled","pin_type","count_enable","count_enabled","count_update_interval_sec","count_update_interval_s","count_update_interval","count_update_interval_diff_perc","count_initial_value","count_minimum_pulse_period_msec","count_type","count_divider"};
71 const std::string CPiFace::ParameterBooleanValueNames[CONFIG_NR_OF_PARAMETER_BOOL_TYPES]      = {"false","0","true","1"};
72 const std::string CPiFace::ParameterPinTypeValueNames[CONFIG_NR_OF_PARAMETER_PIN_TYPES]       = {"level","inv_level","rising","falling"};
73 const std::string CPiFace::ParameterCountTypeValueNames[CONFIG_NR_OF_PARAMETER_COUNT_TYPES]   = {"generic","rfxmeter","energy"};
74 
CPiFace(const int ID)75 CPiFace::CPiFace(const int ID)
76 {
77     m_HwdID=ID;
78     m_bIsStarted=false;
79 
80     for (int i = 0; i < 4; i++)
81     {
82         m_Inputs[i].Callback_pntr = (void*)this; // Set callback pointer, to enable events from IO class
83         m_Inputs[i].SetID(i);
84         m_Outputs[i].Callback_pntr = (void*)this; // Set callback pointer, to enable events from IO class
85         m_Outputs[i].SetID(i);
86     }
87     m_fd = 0;
88 }
89 
~CPiFace()90 CPiFace::~CPiFace()
91 {
92 
93 }
94 
95 
96 /***** config file stuff *****/
97 
98 // trim from start
ltrim(std::string & s)99 std::string & CPiFace::ltrim(std::string &s)
100 {
101     s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
102     return s;
103 }
104 
105 // trim from end
rtrim(std::string & s)106 std::string & CPiFace::rtrim(std::string &s)
107 {
108     s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
109     return s;
110 }
111 
112 // trim from both ends
trim(std::string & s)113 std::string & CPiFace::trim(std::string &s)
114 {
115     return ltrim(rtrim(s));
116 }
117 
118 //strip all the unwanted data from the string before its passed back for further processing
preprocess(std::string & s)119 std::string & CPiFace::preprocess(std::string &s)
120 {
121     std::string temp;
122     std::string tempstripped;
123 
124     temp.resize(s.size());
125     std::transform(s.begin(),s.end(),temp.begin(),::tolower);
126 
127     tempstripped = trim(temp);
128 
129     s=tempstripped;
130     return s;
131 }
132 
LocateValueInParameterArray(std::string Parametername,const std::string * ParameterArray,int Items)133 int CPiFace::LocateValueInParameterArray(std::string Parametername,const std::string *ParameterArray,int Items)
134 {
135     int NameFound=-1; //assume not found
136     int Parameter_Index;
137 
138     for (Parameter_Index=0;(Parameter_Index < Items) && (NameFound==-1);Parameter_Index++)
139     {
140         if (Parametername.compare(ParameterArray[Parameter_Index])==0)
141         {
142             NameFound=Parameter_Index;
143         }
144     }
145     return (NameFound);
146 }
147 
GetParameterString(std::string TargetString,const char * SearchStr,int StartPos,std::string & Parameter)148 int CPiFace::GetParameterString(std::string TargetString,const char * SearchStr, int StartPos,  std::string &Parameter)
149 {
150     int EndPos=-1;
151     std::string Substring;
152     std::string SearchString(SearchStr);
153 
154     EndPos=TargetString.find(SearchString,StartPos);
155     if (EndPos >= 0)
156     {
157         Substring=TargetString.substr(StartPos,EndPos-StartPos);
158         Parameter=preprocess(Substring);
159         EndPos=EndPos+SearchString.length(); //set endposition to new search start position
160     }
161     return EndPos;
162 }
163 
164 
LoadConfig(void)165 int CPiFace::LoadConfig(void)
166 {
167     int result=-1;
168 
169     int StartPos=-1;
170     int EndPos=-1;
171 
172     std::string input;
173 
174     std::string Line;
175     std::string Parameter;
176     std::string Porttype;
177     std::string Pinnumber;
178     std::string Parametername;
179     std::string Parametervalue;
180 
181     unsigned char Address=0;
182     unsigned char PinNumber=0;
183     unsigned char PortType=0;
184     int NameFound=0;
185     int ValueFound=0;
186     CIOPort *IOport=NULL;
187     bool Regenerate_Config=false;
188 
189     std::string configfile=szUserDataFolder + "piface.conf";
190 
191     std::fstream ConfigFile(configfile.c_str(), std::ios::in);
192 
193     if (!ConfigFile.is_open())
194     {
195         //try to create one for the default board 0
196         ConfigFile.close();
197         _log.Log(LOG_ERROR,"PiFace: Error config file: %s not found", configfile.c_str() );
198         _log.Log(LOG_ERROR,"PiFace: Auto creating for board 0");
199         LoadDefaultConfig();
200         AutoCreate_piface_config();
201         std::fstream ConfigFile(configfile.c_str(), std::ios::in);
202     }
203 
204 
205     if (ConfigFile.is_open())
206     {
207         while (ConfigFile.good()==true)
208         {
209             do
210             {
211                 StartPos=-1;
212                 EndPos=-1;
213                 getline(ConfigFile,input,'\n');    //get line from config file
214                 Line=preprocess(input);
215 
216                 //find any comments
217                 StartPos=Line.find("//",0);
218                 if (StartPos>=0)
219                 {
220                     //comment found, remove it
221                     Line=Line.substr(0,StartPos);
222                     StartPos=-1; // reset marker
223                 }
224 
225                 //find linesync
226                 EndPos=GetParameterString(Line,".",0,Parameter);
227                 if (EndPos>=0)
228                 {
229                     if (Parameter.compare("piface")==0)
230                     {
231                         StartPos=EndPos;
232                     }
233                 }
234             }
235             while ((StartPos<0) && (ConfigFile.good()==true));
236 
237             if (StartPos > 0)
238             {
239                 //first lets get PiFace Address
240                 EndPos=GetParameterString(Line,".",StartPos,Parameter);
241                 if (EndPos>=0)
242                 {
243                     Address=(unsigned char)(strtol(Parameter.c_str(),NULL,0) & 0xFF); //data can be restricted further but as we check later on keep it wide for future use.
244                     StartPos=EndPos;
245                 }
246 
247                 //Now lets get Port Type
248                 EndPos=GetParameterString(Line,".",StartPos,Parameter);
249                 if (EndPos>=0)
250                 {
251                     if ( Parameter.compare("input") == 0)
252                     {
253                         // we have an input
254                         PortType='I';
255                     }
256                     else if ( Parameter.compare("output") == 0)
257                     {
258                         // we have an output
259                         PortType='O';
260                     }
261 
262                     StartPos=EndPos;
263                 }
264 
265                 //Now lets get Pinnumber
266                 EndPos=GetParameterString(Line,".",StartPos,Parameter);
267                 if (EndPos>=0)
268                 {
269                     PinNumber=(unsigned char)(strtol(Parameter.c_str(),NULL,0) & 0xFF); //data can be restricted further but as we check later on keep it wide for future use.
270                     StartPos=EndPos;
271                 }
272 
273                 //Now lets get parameter name
274                 EndPos=GetParameterString(Line,"=",StartPos,Parameter);
275                 if (EndPos>=0)
276                 {
277                     Parametername=Parameter;
278                     StartPos=EndPos;
279                 }
280 
281                 //finaly lets get parameter value
282                 Parametervalue=Line.substr(StartPos,Line.length()-StartPos);
283                 Parametervalue=preprocess(Parametervalue);
284 
285                 if ((Address <= 3) && (PinNumber <= 7) && ((PortType=='I') || (PortType=='O')) && (Parametername.length() >0 ) && (Parametervalue.length() >0 ))
286                 {
287                     _log.Log(LOG_STATUS,"PiFace: config file: Valid address: %d , Pin: %d and Port %c Parameter: %s , Value %s",Address,PinNumber,PortType,Parametername.c_str(),Parametervalue.c_str());
288                     NameFound=LocateValueInParameterArray(Parametername,ParameterNames,CONFIG_NR_OF_PARAMETER_TYPES);
289 
290                     if (PortType=='I')
291                     {
292                         IOport=&m_Inputs[Address];
293                         m_Inputs[Address].Pin[PinNumber].Direction = 'I';
294                     }
295                     else {
296                         IOport=&m_Outputs[Address];
297                         m_Outputs[Address].Pin[PinNumber].Direction = 'O';
298                     }
299 
300                     result=0;
301 
302                     switch (NameFound)
303                     {
304                         default:
305                             _log.Log(LOG_ERROR,"PiFace: Error config file: unknown parameter %s found", Parametername.c_str() );
306                             break;
307                         case 0:
308                         case 1:
309                             //found enable(d)
310                             ValueFound=LocateValueInParameterArray(Parametervalue,ParameterBooleanValueNames,CONFIG_NR_OF_PARAMETER_BOOL_TYPES);
311                             if (ValueFound >=0)
312                             {
313                                 if ((ValueFound == 0) || (ValueFound == 1))
314                                 {
315                                     IOport->Pin[PinNumber].Enabled=false;
316                                 }
317                                 else {
318                                     IOport->Pin[PinNumber].Enabled=true;
319                                 }
320                                 result++;
321                             }
322                             else _log.Log(LOG_ERROR,"PiFace: Error config file: unknown value %s found", Parametervalue.c_str() );
323                             break;
324 
325                         case 2:
326                             //found pintype
327                             ValueFound=LocateValueInParameterArray(Parametervalue,ParameterPinTypeValueNames,CONFIG_NR_OF_PARAMETER_PIN_TYPES);
328                             switch ( ValueFound )
329                             {
330                                 default:
331                                     _log.Log(LOG_ERROR,"PiFace: Error config file: unknown value %s found =>setting default level %d", Parametervalue.c_str(),ValueFound );
332                                     IOport->Pin[PinNumber].Type=LEVEL;
333                                     break;
334 
335                                 case 0:
336                                     IOport->Pin[PinNumber].Type=LEVEL;
337                                     break;
338                                 case 1:
339                                     IOport->Pin[PinNumber].Type=INV_LEVEL;
340                                     break;
341                                 case 2:
342                                     IOport->Pin[PinNumber].Type=TOGGLE_RISING;
343                                     break;
344                                 case 3:
345                                     IOport->Pin[PinNumber].Type=TOGGLE_FALLING;
346                                     break;
347                             }
348                             result++;
349                             break;
350 
351                         case 3:
352                         case 4:
353                             //found count_enable(d)
354                             ValueFound=LocateValueInParameterArray(Parametervalue,ParameterBooleanValueNames,CONFIG_NR_OF_PARAMETER_BOOL_TYPES);
355 
356                             if (ValueFound >=0)
357                             {
358                                 if ((ValueFound == 0) || (ValueFound == 1))
359                                 {
360                                     IOport->ConfigureCounter(PinNumber,false);
361                                 }
362                                 else {
363                                     IOport->ConfigureCounter(PinNumber,true);
364                                 }
365                                 result++;
366                             }
367                             else _log.Log(LOG_ERROR,"PiFace: Error config file: unknown value %s found", Parametervalue.c_str() );
368                             break;
369 
370                         case 5:
371                         case 6:
372                         case 7:
373                             //count_update_interval(_s)(ec)
374                             unsigned long UpdateInterval;
375 
376                             UpdateInterval=strtol(Parametervalue.c_str(),NULL,0);
377                             IOport->Pin[PinNumber].Count.SetUpdateInterval(UpdateInterval*1000);
378                             result++;
379                             break;
380 
381                         case 8:
382                             //count_update_interval_diff_perc
383                             unsigned long UpdateIntervalPerc;
384 
385                             UpdateIntervalPerc=strtol(Parametervalue.c_str(),NULL,0);
386                             if ( UpdateIntervalPerc < 1 || UpdateIntervalPerc > 1000 )
387                             {
388                                 _log.Log(LOG_ERROR,"PiFace: Error config file: invalid value %s found", Parametervalue.c_str());
389                                 break;
390                             }
391                             IOport->Pin[PinNumber].Count.SetUpdateIntervalPerc(UpdateIntervalPerc);
392                             result++;
393 
394 #ifndef DISABLE_NEW_FUNCTIONS
395                             /*  disabled until code part is completed and tested */
396                         case 9:
397                             //count_initial_value
398                             unsigned long StartValue;
399 
400                             StartValue=strtol(Parametervalue.c_str(),NULL,0);
401                             IOport->Pin[PinNumber].Count.SetTotal(StartValue);
402                             result++;
403                             Regenerate_Config=true;
404                             break;
405 #endif
406                         case 10:
407                             //count_minimum_pulse_period_msec
408                             unsigned long Min_Pulse_Period;
409 
410                             Min_Pulse_Period=strtol(Parametervalue.c_str(),NULL,0); // results in 0 if str is invalid
411                             IOport->Pin[PinNumber].Count.SetRateLimit(Min_Pulse_Period);
412                             result++;
413                             break;
414 
415                         case 11:
416                             //count_type
417                             ValueFound=LocateValueInParameterArray(Parametervalue,ParameterCountTypeValueNames,CONFIG_NR_OF_PARAMETER_COUNT_TYPES);
418                             switch (ValueFound )
419                             {
420                                 default:
421                                     _log.Log(LOG_ERROR,"PiFace: Error config file: unknown value %s found", Parametervalue.c_str());
422                                     break;
423 
424                                 case 0: // generic
425                                     IOport->Pin[PinNumber].Count.Type = COUNT_TYPE_GENERIC;
426                                     break;
427                                 case 1: // rfxmeter
428                                     IOport->Pin[PinNumber].Count.Type = COUNT_TYPE_RFXMETER;
429                                     break;
430                                 case 2: // energy
431                                     IOport->Pin[PinNumber].Count.Type = COUNT_TYPE_ENERGY;
432                                     break;
433                             }
434                             result++;
435                             break;
436 
437                         case 12:
438                             //count_divider
439                             IOport->Pin[PinNumber].Count.SetDivider(strtol(Parametervalue.c_str(), NULL, 0));
440                             result++;
441                             break;
442                     }
443                 }
444                 else _log.Log(LOG_ERROR,"PiFace: Error config file: misformed config line %s found", Line.c_str() );
445             }
446         }
447         ConfigFile.close();
448 #ifndef DISABLE_NEW_FUNCTIONS
449         if (Regenerate_Config)
450         {
451             Regenerate_Config=false;
452             _log.Log(LOG_ERROR,"PiFace: We got an initial value setting, so we are now recreating the logfile to avoid looping", configfile.c_str() );
453             AutoCreate_piface_config();
454         }
455 #endif
456     }
457     else {
458         _log.Log(LOG_ERROR,"PiFace: Error PiFace config file: %s not found", configfile.c_str() );
459         _log.Log(LOG_ERROR,"PiFace: loading defaults for PiFace(s)");
460         LoadDefaultConfig();
461         AutoCreate_piface_config();
462     }
463     return result;
464 }
465 
LoadDefaultConfig(void)466 void CPiFace::LoadDefaultConfig(void)
467 {
468     for (int i=0; i<=3 ;i++)
469     {
470         for (int PinNr=0; PinNr<=7 ;PinNr++)
471         {
472             if (PinNr < 4)
473             {
474                 m_Inputs[i].Pin[PinNr].Enabled=true;
475             }
476             m_Inputs[i].Pin[PinNr].Type=TOGGLE_RISING;
477             m_Inputs[i].Pin[PinNr].Count.SetUpdateInterval(10000);
478             m_Inputs[i].Pin[PinNr].Direction = 'I';
479 
480             if (PinNr >= 4)
481             {
482                 m_Inputs[i].ConfigureCounter(PinNr,true);
483             }
484             m_Outputs[i].ConfigureCounter(PinNr,true);
485             m_Outputs[i].Pin[PinNr].Type=LEVEL;
486             m_Outputs[i].Pin[PinNr].Count.SetUpdateInterval(10000);
487             m_Outputs[i].Pin[PinNr].Direction = 'O';
488         }
489     }
490 }
491 
AutoCreate_piface_config(void)492 void CPiFace::AutoCreate_piface_config(void)
493 {
494 	char const * const explanation[] = {
495 		"//piface.conf [Autogenerated]\r\n",
496 		"//This file is part of the PiFace driver (written by Robin Stevenhagen) for Domoticz, and allows for more advanced pin configuration options\r\n",
497 		"//This file can optionally hold all the configuration for all attached PiFace boards.\r\n",
498 		"//Please note that if this file is found the default config is discarded, and only the configuration in this file is used, all IO that is not specified will\r\n/",
499 		"//be configured as not used.\r\n",
500 		"//\r\n",
501 		"// Configuration line syntax:\r\n",
502 		"// piface.<board address>.<I/O>.<pin number>.<parameter>=<value>\r\n",
503 		"//\r\n",
504 		"// <board address>:                 Check piface (JP1 & JP2) jumper settings, value between 0..3\r\n",
505 		"//\r\n",
506 		"// <I/O>:                           input\r\n",
507 		"//                                  output\r\n",
508 		"//\r\n",
509 		"// <pinnumber>:                     value between 0..7\r\n",
510 		"//\r\n",
511 		"// <parameter>:                     enabled\r\n",
512 		"//                                  pin_type\r\n",
513 		"//                                  count_enabled\r\n",
514 		"//                                  count_type\r\n",
515 		"//                                  count_divider\r\n",
516         "//                                  count_update_interval_sec\r\n",
517         "//                                  count_update_interval_diff_perc\r\n",
518 #ifndef DISABLE_NEW_FUNCTIONS
519         "//                                  count_initial_value\r\n",
520 #endif
521         "//                                  count_minimum_pulse_period\r\n",
522 		"//\r\n",
523 		"// Available <parameter> / <values>:\r\n",
524 		"// enabled:                         true  or 1\r\n",
525 		"//                                  false or 0\r\n",
526 		"//\r\n",
527 		"// pin_type:                        level                     // best to keep this parameter to level for outputs, as Domoticz does not like other values\r\n",
528 		"//                                  inv_level\r\n",
529 		"//                                  rising\r\n",
530 		"//                                  falling\r\n",
531 		"//\r\n",
532 		"// count_enabled:                   true  or 1\r\n",
533 		"//                                  false or 0\r\n",
534 		"//\r\n",
535 		"// count_type:                      generic or rfxmeter       // default\r\n",
536 		"//                                  energy\r\n",
537 		"//\r\n",
538 		"// count_divider:                   1000                      // default\r\n",
539 		"//                                  Pulses per unit.\r\n",
540 		"//\r\n",
541 		"// count_update_interval_sec:       0 to 3600\r\n",
542         "// count_update_interval_diff_perc: 1 to 1000\r\n",
543         "//                                  This parameter can be used to force trigger an update when the difference in pulse intervals is above a certain percentage.\r\n",
544         "//                                  The value is a percentage of the previous interval. This can be useful for energy counters to quickly report large fluctuations\r\n",
545         "//                                  in active power.\r\n",
546 		"//\r\n",
547 #ifndef DISABLE_NEW_FUNCTIONS
548         "// count_initial_value:             0 to 4294967295 counts\r\n",
549 		"// Note1: The initial_value will only be used once. After it has been set, a new piface.conf will automatically be generated without the initial_value parameter(s)\r\n",
550 		"// to ensure that it will only be used once.\r\n",
551 		"// Using the initial_value to set a counter to a desired (start) value, will result in spikes in the Domoticz graphs.\r\n",
552 		"// Note2: The initial_value is in counter counts, Domoticz scales (divides) this by the factor that is set in the GUI settings \r\n",
553 		"//\r\n",
554 #endif
555         "// count_minimum_pulse_period_msec: 0 to 4294967295 milliseconds\r\n",
556 		"//                                  This parameter sets a pulse rate limiter.\r\n",
557 		"//                                  If multiple pulses are detected within the specified time period, they are ignored until the specified idle time has been met.\r\n",
558 		"//                                  This can be useful for opto reflective sensors that count slow moving dials. Like water meters or gas meters,\r\n",
559 		"//                                  where the dial can stop on an opto barrier and cause oscillations (and unwanted counts).\r\n",
560 		"//                                  0 = off \r\n",
561 		"//                                  < 100 msec times will not be very effective.\r\n",
562 		"//\r\n",
563 		NULL
564 	};
565 
566     char configline[100];
567     std::string ValueText;
568     std::string PortType;
569     int Value;
570     CIOPort *IOport;
571 
572     std::string configfile=szUserDataFolder + "piface.conf";
573     std::fstream ConfigFile(configfile.c_str(), std::ios::out);
574 
575 	int total_explanations = sizeof(explanation);
576 
577     if (ConfigFile.is_open())
578     {
579 		int i = 0;
580 		while (explanation[i]!=NULL)
581         {
582             ConfigFile.write(explanation[i],strlen(explanation[i]));
583 			i++;
584         }
585 
586         for (int BoardNr=0; BoardNr< 1 ;BoardNr++) // Note there could be 4 boards, but this will create a lot of entires in the configfile
587         {
588             for (int PinNr=0; PinNr<=7 ;PinNr++)
589             {
590                 for (int IOType=0; IOType <= 1 ; IOType++)
591                 {
592                     if (IOType==0)
593                     {
594                         PortType="output";
595                         IOport=&m_Outputs[BoardNr];
596                     }
597                     else {
598                         PortType="input";
599                         IOport=&m_Inputs[BoardNr];
600                     }
601 
602                     if (IOport->Pin[PinNr].Enabled)
603                         ValueText="true";
604                     else ValueText="false";
605                     sprintf(configline,"piface.%d.%s.%d.enabled=%s\r\n",BoardNr,PortType.c_str(),PinNr,ValueText.c_str());
606                     ConfigFile.write(configline,strlen(configline));
607 
608                     ValueText=ParameterPinTypeValueNames[IOport->Pin[PinNr].Type];
609                     sprintf(configline,"piface.%d.%s.%d.pin_type=%s\r\n",BoardNr,PortType.c_str(),PinNr,ValueText.c_str());
610                     ConfigFile.write(configline,strlen(configline));
611 
612                     if (IOport->Pin[PinNr].Count.Enabled)
613                         ValueText="true";
614                     else ValueText="false";
615                     sprintf(configline,"piface.%d.%s.%d.count_enabled=%s\r\n",BoardNr,PortType.c_str(),PinNr,ValueText.c_str());
616                     ConfigFile.write(configline,strlen(configline));
617 
618                     ValueText=ParameterCountTypeValueNames[IOport->Pin[PinNr].Count.Type];
619                     sprintf(configline,"piface.%d.%s.%d.count_type=%s\r\n",BoardNr,PortType.c_str(),PinNr,ValueText.c_str());
620                     ConfigFile.write(configline,strlen(configline));
621 
622                     if (
623                         IOport->Pin[PinNr].Count.Type != COUNT_TYPE_GENERIC
624                         && IOport->Pin[PinNr].Count.Type != COUNT_TYPE_RFXMETER
625                     ) {
626                         Value=IOport->Pin[PinNr].Count.GetDivider();
627                         sprintf(configline,"piface.%d.%s.%d.count_divider=%d\r\n",BoardNr,PortType.c_str(),PinNr,Value);
628                         ConfigFile.write(configline,strlen(configline));
629                     }
630 
631                     Value=IOport->Pin[PinNr].Count.GetUpdateInterval()/1000;
632                     sprintf(configline,"piface.%d.%s.%d.count_update_interval_sec=%d\r\n",BoardNr,PortType.c_str(),PinNr,Value);
633                     ConfigFile.write(configline,strlen(configline));
634 
635                     Value=IOport->Pin[PinNr].Count.GetUpdateIntervalPerc();
636                     if ( Value > 0 ) {
637                         sprintf(configline,"piface.%d.%s.%d.count_update_interval_diff_perc=%d\r\n",BoardNr,PortType.c_str(),PinNr,Value);
638                         ConfigFile.write(configline,strlen(configline));
639                     }
640 
641                     Value=IOport->Pin[PinNr].Count.GetRateLimit();
642                     if ( Value > 0 ) {
643                         sprintf(configline,"piface.%d.%s.%d.count_minimum_pulse_period_msec=%d\r\n",BoardNr,PortType.c_str(),PinNr,Value);
644                         ConfigFile.write(configline,strlen(configline));
645                     }
646 
647                     sprintf(configline,"\r\n");
648                     ConfigFile.write(configline,strlen(configline));
649                 }
650             }
651         }
652 
653     }
654     ConfigFile.close();
655 }
656 
657 /***** end of config file stuff *****/
658 
CallBackSendEvent(const unsigned char * pEventPacket,const unsigned int PacketLength)659 void CPiFace::CallBackSendEvent(const unsigned char *pEventPacket, const unsigned int PacketLength)
660 {
661     std::string sendData;
662     sendData.insert(sendData.begin(), pEventPacket, pEventPacket + PacketLength);
663     std::lock_guard<std::mutex> l(m_queue_mutex);
664     if (m_send_queue.size() < 100)
665         m_send_queue.push_back(sendData);
666     else
667         _log.Log(LOG_ERROR, "PiFace: to much messages on queue!");
668 }
669 
CallBackSetPinInterruptMode(unsigned char devId,unsigned char pinID,bool Interrupt_Enable)670 void CPiFace::CallBackSetPinInterruptMode(unsigned char devId,unsigned char pinID, bool Interrupt_Enable)
671 {
672     unsigned char Cur_Int_Enable_State;
673     unsigned char pinmask;
674     //make sure that we clear the interrupts states before we update the port
675     Read_MCP23S17_Register (devId,MCP23x17_INTFB);
676     Read_MCP23S17_Register (devId,MCP23x17_INTCAPB);
677 
678     pinmask=1;
679     pinmask<<=pinID;
680     Cur_Int_Enable_State=Read_MCP23S17_Register (devId,MCP23x17_GPINTENB);
681 
682     if (Interrupt_Enable)
683         Cur_Int_Enable_State|=pinmask; //enable pin interrupt
684     else Cur_Int_Enable_State&=~pinmask; //disable pin interrupt
685 
686     Write_MCP23S17_Register (devId, MCP23x17_GPINTENB, Cur_Int_Enable_State);
687 
688     //_log.Log(LOG_NORM,"PiFace: SetPin Interrupt mode: devid: %d, Pinnr: %d, Enable %d-- Prev 0x%02X Cur 0x%02X",devId,pinID,Interrupt_Enable,Prev_Int_Enable_State,Cur_Int_Enable_State);
689 }
690 
691 
StartHardware()692 bool CPiFace::StartHardware()
693 {
694     StopHardware();
695 
696 	RequestStart();
697 	m_TaskQueue.RequestStart();
698 
699     m_InputSample_waitcntr=(PIFACE_INPUT_PROCESS_INTERVAL)*20;
700     m_CounterEdgeSample_waitcntr=(PIFACE_COUNTER_COUNTER_INTERVAL)*20;
701 
702     memset(m_DetectedHardware,0,sizeof(m_DetectedHardware));
703 
704 #ifndef DISABLE_NEW_FUNCTIONS
705     LoadConfig();
706 #endif
707     //Start worker thread
708 	if (Init_SPI_Device(1) > 0)
709 	{
710 		if (Detect_PiFace_Hardware() > 0)
711 		{
712 			//we have hardware, so lets use it
713 			for (int devId = 0; devId < 4; devId++)
714 			{
715 				if (m_DetectedHardware[devId] == true)
716 					Init_Hardware(devId);
717 			}
718 #ifdef DISABLE_NEW_FUNCTIONS
719 			LoadConfig();
720 #endif
721 
722 			for (int devId = 0; devId < 4; devId++)
723 			{
724 				if (m_DetectedHardware[devId] == true)
725 					GetAndSetInitialDeviceState(devId);
726 			}
727 
728 			m_thread = std::make_shared<std::thread>(&CPiFace::Do_Work, this);
729 			SetThreadNameInt(m_thread->native_handle());
730 			m_queue_thread = std::make_shared<std::thread>(&CPiFace::Do_Work_Queue, this);
731 			SetThreadName(m_queue_thread->native_handle(), "PiFaceQueue");
732 		}
733 		else
734 		{
735 			return false;
736 		}
737 	}
738 	else
739 	{
740 		return false;
741 	}
742     m_bIsStarted=true;
743     sOnConnected(this);
744     return (m_thread != nullptr);
745 }
746 
StopHardware()747 bool CPiFace::StopHardware()
748 {
749 	RequestStop();
750 	m_TaskQueue.RequestStop();
751 
752 	if (m_thread)
753 	{
754 		m_thread->join();
755 		m_thread.reset();
756 	}
757 
758 	if (m_queue_thread)
759 	{
760 		m_queue_thread->join();
761 		m_queue_thread.reset();
762 	}
763 #ifdef HAVE_LINUX_SPI
764     if (m_fd > 0) {
765         close(m_fd);
766         m_fd = 0;
767     }
768 #endif
769     m_bIsStarted=false;
770     return true;
771 }
772 
WriteToHardware(const char * pdata,const unsigned char length)773 bool CPiFace::WriteToHardware(const char *pdata, const unsigned char length)
774 {
775     const tRBUF *SendData = reinterpret_cast<const tRBUF*>(pdata);
776     int mask=0x01;
777     unsigned char CurrentLatchState;
778     unsigned char OutputData;
779     unsigned char PortType=' ';
780 
781     int devId =0;
782     int pinnr =0;
783 
784     if ((SendData->LIGHTING1.packettype == pTypeLighting1) && (SendData->LIGHTING1.subtype == sTypeIMPULS))
785     {
786         PortType=(SendData->LIGHTING1.housecode);
787         devId=(SendData->LIGHTING1.unitcode /10)&0x03;
788         pinnr =(SendData->LIGHTING1.unitcode %10)&0x07;
789         if (PortType == 'O')
790         {
791             //_log.Log(LOG_NORM,"Piface: WriteToHardware housecode %c, packetlength %d", SendData->LIGHTING1.housecode,SendData->LIGHTING1.packetlength );
792             CurrentLatchState = Read_MCP23S17_Register(devId, MCP23x17_OLATA);
793             //_log.Log(LOG_NORM,"PiFace: Read input state 0x%02X", m_OutputState[devId].Current);
794 
795             OutputData = CurrentLatchState;
796             mask <<= pinnr;
797 
798             if (SendData->LIGHTING1.cmnd == light1_sOff)
799             {
800                 OutputData &= ~mask;
801             }
802             else OutputData |= mask;
803 
804             Write_MCP23S17_Register(devId, MCP23x17_GPIOA, OutputData);
805             //  _log.Log(LOG_NORM,"Piface: WriteToHardware housecode %c, devid %d, output %d, PrevOut 0x%02X, Set 0x%02X",PortType, devId, pinnr, CurrentLatchState,OutputData );
806         }
807         else
808         {
809             _log.Log(LOG_ERROR, "Piface: wrong housecode %c", PortType);
810             return false;
811         }
812     }
813     else
814     {
815         _log.Log(LOG_ERROR, "PiFace: WriteToHardware packet type %d or subtype %d unknown", SendData->LIGHTING1.packettype, SendData->LIGHTING1.subtype);
816         return false;
817     }
818     return true;
819 }
820 
Do_Work()821 void CPiFace::Do_Work()
822 {
823     int devId;
824     _log.Log(LOG_STATUS,"PiFace: Worker started...");
825     int msec_counter = 0;
826     int sec_counter = 0;
827     while (!IsStopRequested(PIFACE_WORKER_THREAD_SLEEP_INTERVAL_MS))
828     {
829         msec_counter++;
830         if (msec_counter == (1000 / PIFACE_WORKER_THREAD_SLEEP_INTERVAL_MS))
831         {
832             msec_counter = 0;
833             sec_counter++;
834             if (sec_counter % 12 == 0) {
835                 m_LastHeartbeat = mytime(NULL);
836             }
837         }
838 
839         m_InputSample_waitcntr++;
840         m_CounterEdgeSample_waitcntr++;
841 
842         //sample interrupt states faster for edge detection on count
843         if (m_CounterEdgeSample_waitcntr>=PIFACE_COUNTER_COUNTER_INTERVAL)
844         {
845             m_CounterEdgeSample_waitcntr=0;
846             for (devId=0; devId<4; devId++)
847             {
848                 Sample_and_Process_Input_Interrupts(devId);
849             }
850         }
851 
852         //sample pin states more slowly to avoid spamming Domoticz
853         if (m_InputSample_waitcntr>=PIFACE_INPUT_PROCESS_INTERVAL)
854         {
855             m_InputSample_waitcntr=0;
856             for (devId=0; devId<4; devId++)
857             {
858                 Sample_and_Process_Inputs(devId);
859                 Sample_and_Process_Outputs(devId);
860             }
861         }
862     }
863     _log.Log(LOG_STATUS,"PiFace: Worker stopped...");
864 }
865 
Do_Work_Queue()866 void CPiFace::Do_Work_Queue()
867 {
868     std::vector<std::string>::iterator itt;
869     while (!m_TaskQueue.IsStopRequested(100))
870     {
871         if (m_send_queue.empty())
872             continue;
873 
874         std::string sendData;
875         m_queue_mutex.lock();
876         itt = m_send_queue.begin();
877         sendData = *itt;
878         m_send_queue.erase(itt);
879         m_queue_mutex.unlock();
880         sDecodeRXMessage(this, (const unsigned char*)sendData.c_str(), NULL, 255);
881     }
882 }
883 
884 // Open a connection to the piface
885 // Returns a file id, or -1 if not open/error
Init_SPI_Device(int Init)886 int CPiFace::Init_SPI_Device(int Init)
887 {
888     m_fd=0;
889     int result=-1;
890     unsigned char spiMode  = 0 ;
891     unsigned char spiBPW   = 8 ;
892     int           speed       = 4000000 ;
893 
894     _log.Log(LOG_STATUS,"PiFace: Starting PiFace_SPI_Start()");
895 #ifdef HAVE_LINUX_SPI
896     // Open port for reading and writing
897     if ((m_fd = open("/dev/spidev0.0", O_RDWR)) >= 0)
898     {
899         // Set the port options and set the address of the device
900         if (ioctl (m_fd, SPI_IOC_WR_MODE, &spiMode)         >= 0)
901         {
902             if (ioctl (m_fd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) >= 0)
903             {
904                 if (ioctl (m_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed)   >= 0)
905                 {
906                     result=1;
907                     //we are successfull
908                     _log.Log(LOG_NORM,"PiFace: SPI device opened successfully");
909 
910                 }
911                 else
912                     _log.Log(LOG_NORM,"PiFace: SPI Speed Change failure: %s", strerror (errno)) ;
913             }
914             else
915                 _log.Log(LOG_NORM,"PiFace: SPI BPW Change failure: %s", strerror (errno)) ;
916 
917         }
918         else
919             _log.Log(LOG_NORM,"PiFace: SPI Mode Change failure: %s", strerror (errno)) ;
920     }
921     else
922         _log.Log(LOG_NORM,"PiFace: Unable to open SPI device: %s", strerror (errno));
923 
924 #endif
925 
926     if (result == -1)
927     {
928 #ifdef HAVE_LINUX_SPI
929         close(m_fd);
930 #endif
931         return -1;
932     }
933     else return m_fd;
934 }
935 
Detect_PiFace_Hardware(void)936 int CPiFace::Detect_PiFace_Hardware(void)
937 {
938     int NrOfFoundBoards=0;
939     unsigned char read_iocon;
940     unsigned char read_ioconb;
941     int devId;
942 
943     //first write to all possible PiFace addresses.
944     for (devId=0; devId<4; devId++)
945     {
946         Write_MCP23S17_Register (devId, MCP23x17_IOCON,  IOCON_INIT | IOCON_HAEN) ;
947         Write_MCP23S17_Register (devId, MCP23x17_IOCONB,  IOCON_INIT | IOCON_HAEN) ;
948     }
949 
950     //now read them back, to determine if they are present
951 
952     for (devId=0; devId<4; devId++)
953     {
954         read_iocon=Read_MCP23S17_Register (devId, MCP23x17_IOCON);
955         read_ioconb=Read_MCP23S17_Register (devId, MCP23x17_IOCONB);
956 
957         if ((read_iocon == ( IOCON_INIT | IOCON_HAEN)) && (read_ioconb == ( IOCON_INIT | IOCON_HAEN)))
958         {
959             //hé someone appears to be home...
960             m_DetectedHardware[devId]=true;
961             NrOfFoundBoards++;
962         }
963     }
964 
965     if (NrOfFoundBoards)
966     {
967         _log.Log(LOG_STATUS,"PiFace: Found the following PiFaces:");
968         for (devId=0; devId<4; devId++)
969         {
970             if (m_DetectedHardware[devId]==true)
971                 _log.Log(LOG_STATUS,"PiFace: %d",devId);
972         }
973     }
974     else _log.Log(LOG_STATUS,"PiFace: Sorry, no PiFaces were found");
975     return NrOfFoundBoards;
976 }
977 
Init_Hardware(unsigned char devId)978 void CPiFace::Init_Hardware(unsigned char devId)
979 {
980     Write_MCP23S17_Register (devId, MCP23x17_IOCON,  IOCON_INIT | IOCON_HAEN) ;
981     Write_MCP23S17_Register (devId, MCP23x17_IOCONB, IOCON_INIT | IOCON_HAEN) ;
982 
983     //setup PortA as output
984     Write_MCP23S17_Register (devId, MCP23x17_IODIRA,  0x00) ;         //set all pins on Port A as output
985 
986     //lets init the other registers so we always have a clear startup situation
987     Write_MCP23S17_Register (devId, MCP23x17_GPINTENA, 0x00);          //The PiFace does not support the interrupt capabilities, so set it to 0, and useless for output.
988     Write_MCP23S17_Register (devId, MCP23x17_DEFVALA,     0x00);      //Default compare value register, useless for output
989     Write_MCP23S17_Register (devId, MCP23x17_INTCONA,     0x00);        //Interrupt based on current and prev pin state, not Default value
990     //Write_MCP23S17_Register (devId, MCP23x17_INTFA,     0);           // Read only interrupt flag register,not used on output
991     //Write_MCP23S17_Register (devId, MCP23x17_INTCAPA, );             // Read only interrupt status register, captures port status on interrupt, not ued on output
992     Write_MCP23S17_Register (devId, MCP23x17_OLATA, 0xFF);             // Set the output latches to 1, otherwise the relays are activated
993 
994     //setup PortB as input
995     Write_MCP23S17_Register (devId, MCP23x17_IODIRB, 0xFF) ;         //set all pins on Port B as input
996     Write_MCP23S17_Register (devId, MCP23x17_GPPUB,  0xFF) ;          //Enable pullup resistors, so the buttons work immediately
997     Write_MCP23S17_Register (devId, MCP23x17_IPOLB,  0xFF) ;          //Invert input pin state, so we will see an active state as as 1.
998 
999     //lets init the other registers so we always have a clear startup situation
1000     Write_MCP23S17_Register (devId, MCP23x17_GPINTENB, 0x00);          //The PiFace does not support the interrupt capabilities, so set it to 0.
1001     //Todo: use the int detection for small pulses
1002     Write_MCP23S17_Register (devId, MCP23x17_DEFVALB,     0x00);      //Default compare value register, we do not use it (yet), but lets set it to 0 (assuming not active state).
1003     Write_MCP23S17_Register (devId, MCP23x17_INTCONB,     0x00);         //Interrupt based on current and prev pin state, not Default value
1004     //Write_MCP23S17_Register (devId, MCP23x17_INTFB,     0);           // Read only interrupt flag register, listed as a reminder
1005     //Write_MCP23S17_Register (devId, MCP23x17_INTCAPB, );             // Read only interrupt status register, captures port status on interrupt, listed as a reminder
1006     Write_MCP23S17_Register (devId, MCP23x17_OLATB, 0x00);             // Set the output latches to 0, note: not used.
1007 
1008     Write_MCP23S17_Register (devId, MCP23x17_GPIOA,  0x00) ;         //set all pins on Port A as output, and deactivate
1009 }
1010 
GetAndSetInitialDeviceState(int devId)1011 void CPiFace::GetAndSetInitialDeviceState(int devId)
1012 {
1013     unsigned char PortState;
1014     PortState= Read_MCP23S17_Register (devId, MCP23x17_OLATA) ;
1015     m_Outputs[devId].Init(true,m_HwdID,devId,'O',PortState);
1016 
1017     PortState= Read_MCP23S17_Register (devId, MCP23x17_GPIOB) ;
1018     m_Inputs[devId].Init(true,m_HwdID,devId,'I',PortState);
1019 }
1020 
Read_Write_SPI_Byte(unsigned char * data,int len)1021 int CPiFace::Read_Write_SPI_Byte(unsigned char *data, int len)
1022 {
1023 #ifdef HAVE_LINUX_SPI
1024     struct spi_ioc_transfer spi ;
1025     memset (&spi, 0, sizeof(spi));
1026 
1027     spi.tx_buf        = (unsigned long)data ;
1028     spi.rx_buf        = (unsigned long)data ;
1029     spi.len           = len ;
1030     spi.delay_usecs   = 0 ;
1031     spi.speed_hz      = 4000000;
1032     spi.bits_per_word = 8 ;
1033 
1034     return ioctl (m_fd, SPI_IOC_MESSAGE(1), &spi) ;
1035 #else
1036     return 0;
1037 #endif
1038 }
1039 
Read_MCP23S17_Register(unsigned char devId,unsigned char reg)1040 int CPiFace::Read_MCP23S17_Register(unsigned char devId, unsigned char reg)
1041 {
1042     unsigned char spiData [4] ;
1043 
1044     spiData [0] = CMD_READ | ((devId & 7) << 1);
1045     spiData [1] = reg ;
1046 
1047     Read_Write_SPI_Byte(spiData, 3);
1048 
1049     return spiData [2];
1050 }
1051 
Write_MCP23S17_Register(unsigned char devId,unsigned char reg,unsigned char data)1052 int CPiFace::Write_MCP23S17_Register(unsigned char devId, unsigned char reg, unsigned char data)
1053 {
1054     unsigned char spiData [4] ;
1055 
1056     spiData [0] = CMD_WRITE | ((devId & 7) << 1) ;
1057     spiData [1] = reg ;
1058     spiData [2] = data ;
1059 
1060     return (Read_Write_SPI_Byte( spiData, 3)) ;
1061 }
1062 
Sample_and_Process_Inputs(unsigned char devId)1063 void CPiFace::Sample_and_Process_Inputs(unsigned char devId)
1064 {
1065     unsigned char PortState;
1066 
1067     if ( m_Inputs[devId].IsDevicePresent())
1068     {
1069         PortState=Read_MCP23S17_Register (devId, MCP23x17_GPIOB);
1070         m_Inputs[devId].Update(PortState);
1071     }
1072 }
1073 
Sample_and_Process_Outputs(unsigned char devId)1074 void CPiFace::Sample_and_Process_Outputs(unsigned char devId)
1075 {
1076     unsigned char PortState;
1077 
1078     if ( m_Outputs[devId].IsDevicePresent())
1079     {
1080         PortState=Read_MCP23S17_Register (devId, MCP23x17_GPIOA);
1081         m_Outputs[devId].Update(PortState);
1082     }
1083 }
1084 
Sample_and_Process_Input_Interrupts(unsigned char devId)1085 void CPiFace::Sample_and_Process_Input_Interrupts(unsigned char devId)
1086 {
1087     unsigned char PortInterruptState;
1088     unsigned char PortState;
1089 
1090     if ( m_Inputs[devId].IsDevicePresent())
1091     {
1092         PortInterruptState=Read_MCP23S17_Register(devId, MCP23x17_INTFB);
1093         PortState=Read_MCP23S17_Register(devId, MCP23x17_INTCAPB);
1094         m_Inputs[devId].UpdateInterrupt(PortInterruptState,PortState);
1095     }
1096 }
1097 
SetUpdateInterval(unsigned long NewValue_ms)1098 void CIOCount::SetUpdateInterval(unsigned long NewValue_ms)
1099 {
1100     UpdateInterval_ms=NewValue_ms;
1101     UpdateDownCount_ms=UpdateInterval_ms;
1102 }
1103 
ProcessUpdateInterval(unsigned long PassedTime_ms)1104 bool CIOCount::ProcessUpdateInterval(unsigned long PassedTime_ms)
1105 {
1106     bool Update=false;
1107     if (Enabled)
1108     {
1109         if (UpdateDownCount_ms > PassedTime_ms)
1110             UpdateDownCount_ms-=PassedTime_ms;
1111         else {
1112             UpdateDownCount_ms=UpdateInterval_ms;
1113             Update=true;
1114         }
1115         if (!InitialStateSent)
1116         {
1117             InitialStateSent=true;
1118             Update=true;
1119         }
1120 
1121         boost::posix_time::ptime Now = boost::posix_time::microsec_clock::universal_time();
1122 
1123         if (
1124             UpdateIntervalPerc > 0 &&
1125             Cur_Interval.total_milliseconds() > 0 &&
1126             Last_Interval.total_milliseconds() > 0 &&
1127             //rate limit the callbacks to max 1 per sec to prevent spamming the upper layer when power suddenly drops
1128             ( Now - Last_Callback ).total_milliseconds() >= 1000
1129         )
1130         {
1131             if ( Now - Cur_Pulse > Cur_Interval )
1132             {
1133                 Cur_Interval = Now - Cur_Pulse;
1134             }
1135 
1136 			double perc = (100. / Last_Interval.total_milliseconds()) * labs((long)(Cur_Interval.total_milliseconds() - Last_Interval.total_milliseconds()));
1137             if ( perc > UpdateIntervalPerc )
1138             {
1139                 UpdateDownCount_ms=UpdateInterval_ms;
1140                 Update=true;
1141             }
1142         }
1143     }
1144     return Update;
1145 }
1146 
Update(unsigned long Counts)1147 int CIOCount::Update(unsigned long Counts)
1148 {
1149     int result = -1;
1150 
1151     Last_Pulse = Cur_Pulse;
1152     Cur_Pulse = boost::posix_time::microsec_clock::universal_time();
1153     Cur_Interval = Cur_Pulse - Last_Pulse;
1154 
1155     if (
1156         Enabled && (
1157             Minimum_Pulse_Period_ms == 0 ||
1158             Minimum_Pulse_Period_ms <= Cur_Interval.total_milliseconds()
1159         )
1160     )
1161     {
1162         Total += Counts;
1163         result = 1;
1164     }
1165 
1166     return result;
1167 }
1168 
1169 
CIOCount()1170 CIOCount::CIOCount()
1171 {
1172     ResetTotal();
1173     UpdateInterval_ms = 10000;
1174     UpdateIntervalPerc = 0;
1175     Enabled = false;
1176     Type = COUNT_TYPE_GENERIC;
1177     InitialStateSent = false;
1178     UpdateDownCount_ms = 0;
1179     Minimum_Pulse_Period_ms = 0;
1180     Divider = 1000;
1181     Last_Pulse = boost::posix_time::microsec_clock::universal_time();
1182     Cur_Pulse = boost::posix_time::microsec_clock::universal_time();
1183     Last_Callback = boost::posix_time::microsec_clock::universal_time();
1184     Last_Interval = boost::posix_time::milliseconds(0);
1185     Cur_Interval = boost::posix_time::milliseconds(0);
1186 };
1187 
~CIOCount()1188 CIOCount::~CIOCount()
1189 {
1190 
1191 };
1192 
Update(bool New)1193 int CIOPinState::Update(bool New)
1194 {
1195     int StateChange=-1; //nothing changed
1196 
1197     Last=Current;
1198     Current=New;
1199 
1200     if (Enabled)
1201     {
1202         switch (Type)
1203         {
1204             default:
1205             case LEVEL:
1206                 if ((Last ^ Current) == true)
1207                 {
1208                     if (Current)
1209                         StateChange=1;
1210                     else
1211                         StateChange=0;
1212                 }
1213                 break;
1214             case INV_LEVEL:    //inverted input
1215                 if ((Last ^ Current) == true)
1216                 {
1217                     if (Current)
1218                         StateChange=0;
1219                     else
1220                         StateChange=1;
1221                 }
1222                 break;
1223             case TOGGLE_RISING:
1224                 if (((Last ^ Current) == true) && (Current == true))
1225                 {
1226                     if (Toggle)
1227                         StateChange=1;
1228                     else
1229                         StateChange=0;
1230                     Toggle=!Toggle;
1231                 }
1232                 break;
1233             case TOGGLE_FALLING:
1234                 if (((Last ^ Current) == true) && (Current == false))
1235                 {
1236                     if (Toggle)
1237                         StateChange=1;
1238                     else
1239                         StateChange=0;
1240                     Toggle=!Toggle;
1241                 }
1242                 break;
1243         }
1244     }
1245 
1246     if (Direction == 'O')
1247     {
1248         if (((Last ^ Current) == true) && (Current == true))
1249         {
1250             Count.Update(1);
1251         }
1252     }
1253 
1254     return StateChange;
1255 }
1256 
UpdateInterrupt(bool IntFlag,bool PinState)1257 int CIOPinState::UpdateInterrupt(bool IntFlag,bool PinState)
1258 {
1259     int Changed=-1;
1260 
1261     if (IntFlag && PinState) //we have a rising edge so count
1262     {
1263         Changed=Count.Update(1);
1264     }
1265     return (Changed);
1266 }
1267 
1268 
GetInitialState(void)1269 int CIOPinState::GetInitialState(void)
1270 {
1271     int CurState=-1; //Report not enabled
1272 
1273     if (Enabled)
1274     {
1275         switch (Type)
1276         {
1277             default:
1278             case LEVEL:
1279                 if (Current)
1280                     CurState=1;
1281                 else
1282                     CurState=0;
1283                 break;
1284             case INV_LEVEL:    //inverted input
1285                 if (Current)
1286                     CurState=0;
1287                 else
1288                     CurState=1;
1289                 break;
1290             case TOGGLE_RISING:
1291                 if (Toggle)
1292                     CurState=1;
1293                 else
1294                     CurState=0;
1295                 break;
1296 
1297             case TOGGLE_FALLING:
1298                 if (Toggle)
1299                     CurState=1;
1300                 else
1301                     CurState=0;
1302                 break;
1303         }
1304     }
1305     return CurState;
1306 }
1307 
1308 
CIOPinState()1309 CIOPinState::CIOPinState()
1310 {
1311     Last=false;
1312     Current=true;
1313     Id=0;
1314     Type=0;
1315     Enabled=false;
1316     Toggle=false;
1317     InitialStateSent=false;
1318     Direction=' ';
1319 };
1320 
~CIOPinState()1321 CIOPinState::~CIOPinState()
1322 {
1323 
1324 };
1325 
Init(bool Available,int hwdId,int devId,unsigned char housecode,unsigned char initial_state)1326 void CIOPort::Init(bool Available, int hwdId, int devId /* 0 - 4 */, unsigned char housecode, unsigned char initial_state)
1327 {
1328     int PinNr;
1329     unsigned long value;
1330     bool found;
1331 
1332     std::vector<std::vector<std::string> > result;
1333     std::vector<std::string> resultparts;
1334 
1335     Present = Available;
1336 
1337     //set generic packet info for LIGHTING1 packet, so we do not have every packet
1338     IOPinStatusPacket.LIGHTING1.packetlength = sizeof(IOPinStatusPacket.LIGHTING1) -1;
1339     IOPinStatusPacket.LIGHTING1.housecode = housecode;//report to user as input
1340     IOPinStatusPacket.LIGHTING1.packettype = pTypeLighting1;
1341     IOPinStatusPacket.LIGHTING1.subtype = sTypeIMPULS;
1342     IOPinStatusPacket.LIGHTING1.rssi = 12;
1343     IOPinStatusPacket.LIGHTING1.seqnbr = 0;
1344 
1345     for (PinNr=0;PinNr <= 7;PinNr++)
1346     {
1347         found = false;
1348         switch( Pin[PinNr].Count.Type )
1349         {
1350             case COUNT_TYPE_GENERIC:
1351             case COUNT_TYPE_RFXMETER:
1352                 //set generic packet info for RFXMETER packet, so we do not have every packet
1353                 Pin[PinNr].IOPinCounterPacket.RFXMETER.packetlength = sizeof(Pin[PinNr].IOPinCounterPacket.RFXMETER) -1;
1354                 Pin[PinNr].IOPinCounterPacket.RFXMETER.packettype = pTypeRFXMeter;
1355                 Pin[PinNr].IOPinCounterPacket.RFXMETER.subtype = sTypeRFXMeterCount;
1356                 Pin[PinNr].IOPinCounterPacket.RFXMETER.rssi = 12;
1357                 Pin[PinNr].IOPinCounterPacket.RFXMETER.seqnbr = 0;
1358 
1359                 int meterid;
1360                 if (housecode == 'I')
1361                     meterid=100+PinNr + (devId*10);
1362                 else
1363                     meterid=0+PinNr + (devId*10);
1364 
1365                 result = m_sql.safe_query("SELECT sValue FROM DeviceStatus WHERE (HardwareID=%d AND Type=%d AND DeviceID='%d')", hwdId, int(pTypeRFXMeter), meterid);
1366                 if (result.size() == 1)
1367                 {
1368                     value = atol(result[0][0].c_str());
1369                     found = true;
1370                 }
1371                 break;
1372 
1373             case COUNT_TYPE_ENERGY:
1374                 int nodeId;
1375                 if (housecode == 'I')
1376                     nodeId=300+devId;
1377                 else
1378                     nodeId=200+devId;
1379 
1380                 int dID = (nodeId << 8) | PinNr;
1381 
1382                 result = m_sql.safe_query("SELECT sValue FROM DeviceStatus WHERE (HardwareID=%d AND Type=%d AND DeviceID='%08X')", hwdId, int(pTypeGeneral), dID);
1383                 if (result.size() == 1)
1384                 {
1385                     StringSplit(result[0][0], ";", resultparts);
1386                     double dValue = 0;
1387                     if (resultparts.size() == 1)
1388                     {
1389                         dValue = atol(resultparts[0].c_str()) / ( 1000. / Pin[PinNr].Count.GetDivider() );
1390                         found = true;
1391                     }
1392                     else if (resultparts.size() == 2)
1393                     {
1394                         dValue = atol(resultparts[1].c_str()) / ( 1000. / Pin[PinNr].Count.GetDivider() );
1395                         found = true;
1396                     }
1397                     value = (unsigned long)dValue;
1398                 }
1399                 break;
1400         }
1401 
1402         if ( found ) {
1403             Pin[PinNr].Count.SetTotal(value);
1404         }
1405     }
1406 
1407     Last=initial_state;
1408     Current=initial_state;
1409     Update(initial_state);
1410 }
1411 
Update(unsigned char New)1412 int CIOPort::Update(unsigned char New)
1413 {
1414     int mask=0x01;
1415     int ChangeState=-1;
1416     bool UpdateCounter=false;
1417 
1418     CPiFace *myCallback = reinterpret_cast<CPiFace*>(Callback_pntr);
1419 
1420     Last=Current;
1421     Current=New;
1422 
1423     for (int PinNr=0; PinNr<=7 ;PinNr++)
1424     {
1425         ChangeState=Pin[PinNr].Update((New&mask)==mask);
1426         if ((ChangeState >=0) || ((!Pin[PinNr].InitialStateSent) && Pin[PinNr].Enabled))
1427         {
1428             Pin[PinNr].InitialStateSent=true;
1429             if (ChangeState <0)
1430             {
1431                 //we have to sent current state
1432                 ChangeState=Pin[PinNr].GetInitialState();
1433             }
1434             //We have something to report to the upper layer
1435             IOPinStatusPacket.LIGHTING1.cmnd = (ChangeState != 0) ? light1_sOn : light1_sOff;
1436             IOPinStatusPacket.LIGHTING1.seqnbr++;
1437             IOPinStatusPacket.LIGHTING1.unitcode = PinNr + (devId*10) ; //report inputs from PiFace X (X0..X9)
1438             myCallback->CallBackSendEvent((const unsigned char *)&IOPinStatusPacket, sizeof(IOPinStatusPacket.LIGHTING1));
1439         }
1440 
1441         UpdateCounter=Pin[PinNr].Count.ProcessUpdateInterval(PIFACE_INPUT_PROCESS_INTERVAL*PIFACE_WORKER_THREAD_SLEEP_INTERVAL_MS);
1442         if (UpdateCounter)
1443         {
1444             unsigned long Count = Pin[PinNr].Count.GetTotal();
1445             unsigned long LastCount = Pin[PinNr].Count.GetLastTotal();
1446 
1447             Pin[PinNr].Count.Last_Callback = boost::posix_time::microsec_clock::universal_time();
1448 
1449             switch( Pin[PinNr].Count.Type )
1450             {
1451                 case COUNT_TYPE_GENERIC:
1452                 case COUNT_TYPE_RFXMETER:
1453                     int meterid;
1454                     if (IOPinStatusPacket.LIGHTING1.housecode == 'I')
1455                         meterid=100+PinNr + (devId*10);
1456                     else
1457                         meterid=0+PinNr + (devId*10);
1458 
1459                     Pin[PinNr].IOPinCounterPacket.RFXMETER.id1=((meterid >>8) & 0xFF);
1460                     Pin[PinNr].IOPinCounterPacket.RFXMETER.id2= (meterid & 0xFF);
1461 
1462                     if (Count != LastCount)
1463                     {
1464                         Pin[PinNr].Count.SetLastTotal(Count);
1465                         //    _log.Log(LOG_NORM,"Counter %lu\n",Count);
1466                         Pin[PinNr].IOPinCounterPacket.RFXMETER.count1 = (unsigned char)((Count >> 24) & 0xFF);
1467                         Pin[PinNr].IOPinCounterPacket.RFXMETER.count2 = (unsigned char)((Count >> 16) & 0xFF);
1468                         Pin[PinNr].IOPinCounterPacket.RFXMETER.count3 = (unsigned char)((Count >> 8) & 0xFF);
1469                         Pin[PinNr].IOPinCounterPacket.RFXMETER.count4 = (unsigned char)((Count)& 0xFF);
1470                         Pin[PinNr].IOPinCounterPacket.RFXMETER.seqnbr++;
1471                         //    _log.Log(LOG_NORM,"RFXMeter Packet C1 %d, C2 %d C3 %d C4 %d\n",Pin[PinNr].IOPinCounterPacket.RFXMETER.count1,Pin[PinNr].IOPinCounterPacket.RFXMETER.count2,Pin[PinNr].IOPinCounterPacket.RFXMETER.count3,Pin[PinNr].IOPinCounterPacket.RFXMETER.count4);
1472                         myCallback->CallBackSendEvent((const unsigned char *)&Pin[PinNr].IOPinCounterPacket, sizeof(Pin[PinNr].IOPinCounterPacket.RFXMETER));
1473                     }
1474                     break;
1475 
1476                 case COUNT_TYPE_ENERGY:
1477                     int nodeId;
1478                     if (IOPinStatusPacket.LIGHTING1.housecode == 'I')
1479                         nodeId=300+devId;
1480                     else
1481                         nodeId=200+devId;
1482 
1483                     // Energy devices are updated *every* time because their current power usage (Watt) drops if no pulses
1484                     // are registered between the interval.
1485                     Pin[PinNr].Count.SetLastTotal(Count);
1486 
1487                     if (
1488                         Pin[PinNr].Count.Cur_Interval.total_milliseconds() > 0 &&
1489                         Pin[PinNr].Count.GetDivider() > 0
1490                     )
1491                     {
1492                         Pin[PinNr].Count.Last_Interval = Pin[PinNr].Count.Cur_Interval;
1493                         double dEnergy = ( ( 1. / Pin[PinNr].Count.GetDivider() ) * Count  ); // kWh
1494                         double dPower = ( ( 1000. / Pin[PinNr].Count.GetDivider() ) * ( 1000. / Pin[PinNr].Count.Cur_Interval.total_milliseconds() ) * 3600 ); // Watt
1495                         myCallback->SendKwhMeter(nodeId, PinNr, 255, dPower, dEnergy, "kWh Meter" );
1496                     }
1497                     break;
1498             }
1499         }
1500         mask<<=1;
1501     }
1502 
1503     return (ChangeState);
1504 }
1505 
UpdateInterrupt(unsigned char IntFlag,unsigned char PinState)1506 int CIOPort::UpdateInterrupt(unsigned char IntFlag,unsigned char PinState)
1507 {
1508     int mask=0x01;
1509     int ChangeState=-1; //nothing changed
1510     int Changed;
1511 
1512     for (int PinNr=0; PinNr<=7 ;PinNr++)
1513     {
1514         Changed=Pin[PinNr].UpdateInterrupt(((IntFlag&mask)==mask),((PinState&mask)==mask));
1515         if (Changed != -1)
1516             ChangeState=Changed;
1517         mask<<=1;
1518     }
1519     return (ChangeState);
1520 }
1521 
ConfigureCounter(unsigned char Pinnr,bool Enable)1522 void CIOPort::ConfigureCounter(unsigned char Pinnr,bool Enable)
1523 {
1524     CPiFace *myCallback = reinterpret_cast<CPiFace*>(Callback_pntr);
1525 
1526     Pin[Pinnr].Count.Enabled=Enable;
1527     if (Pin[Pinnr].Direction == 'I')
1528     {
1529         myCallback->CallBackSetPinInterruptMode(devId, Pinnr, Enable);
1530     }
1531 }
1532 
CIOPort()1533 CIOPort::CIOPort()
1534 {
1535     Last=0;
1536     Current=0;
1537     Present=false;
1538     Callback_pntr = NULL;
1539     PortType = 0;
1540     devId = 0;
1541     for (int PinNr=0; PinNr<=7 ;PinNr++)
1542     {
1543         Pin[PinNr].Id=PinNr;
1544     }
1545 
1546 };
1547 
~CIOPort()1548 CIOPort::~CIOPort()
1549 {
1550 
1551 };
1552 
1553 //Webserver helpers
1554 namespace http {
1555     namespace server {
ReloadPiFace(WebEmSession & session,const request & req,std::string & redirect_uri)1556         void CWebServer::ReloadPiFace(WebEmSession & session, const request& req, std::string & redirect_uri)
1557         {
1558             redirect_uri = "/index.html";
1559             if (session.rights != 2)
1560 			{
1561 				session.reply_status = reply::forbidden;
1562 				return; //Only admin user allowed
1563 			}
1564 
1565             std::string idx = request::findValue(&req, "idx");
1566             if (idx == "") {
1567                 return;
1568             }
1569 
1570             m_mainworker.RestartHardware(idx);
1571         }
1572     }
1573 }
1574 
1575 
1576