51_I2C_TSL2561.pm 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676
  1. =head1
  2. 51_I2C_TSL2561.pm
  3. =head1 SYNOPSIS
  4. Modul for FHEM for reading a TSL2561 ambient light sensor via I2C
  5. connected to the Raspberry Pi.
  6. contributed by Kai Stuke 2014
  7. $Id: 51_I2C_TSL2561.pm 10443 2016-01-10 14:50:08Z jnsbyr $
  8. =head1 DESCRIPTION
  9. 51_I2C_TSL2561.pm reads the illumination of the the ambient light sensor TSL2561
  10. via i2c bus connected to the Raspberry Pi.
  11. This module needs IODev FHEM modules or the HiPi perl modules
  12. IODev see: <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a> or <a href="#NetzerI2C">NetzerI2C</a>
  13. HiPi see: http://raspberrypi.znix.com/hipidocs/
  14. For a simple automated installation of the HiPi perl modules:<br>
  15. wget http://raspberry.znix.com/hipifiles/hipi-install
  16. perl hipi-install
  17. HiPi Example:
  18. define Luminosity I2C_TSL2561 /dev/i2c-0 0x39
  19. attr Luminosity poll_interval 5
  20. IODev Example:
  21. define Luminosity I2C_TSL2561 0x39
  22. attr Luminosity IODev I2CModule
  23. attr Luminosity poll_interval 5
  24. =head1 HiPi CAVEATS
  25. Make sure that the user fhem.pl is running as has read/write access to the i2c device file
  26. (e.g. /dev/i2c-0).
  27. This can be achieved by adding the user to the group i2c (sudo usermod -G i2c -a fhem).
  28. The pinout of the i2c-bus pins differs between revision 1 and revision 2 Raspberry Pi Model B boards.
  29. On revision 1, only bus 0 is accessible, on revision 2 bus 1 is connected to the standard pin header instead.
  30. There is a problem with newer kernel versions (>3.9?) when both i2c and 1-wire are used.
  31. If i2cdetect shows devices on all addresses you are affected by this bug.
  32. To avoid this the kernel modules must be loaded in a specific order.
  33. Try these settings in /etc/modules
  34. w1_therm
  35. w1-gpio
  36. i2c-dev
  37. i2c-bcm2708
  38. snd-bcm2835
  39. and in /etc/modprobe.d/raspi-blacklist.conf
  40. blacklist spi-bcm2708
  41. blacklist i2c-bcm2708
  42. =head1 CHANGES
  43. 18.03.2015 jensb
  44. IODev support added as alternative to HiPi
  45. I2C error detection for IODev mode added
  46. hotplug support (reinit TLS2561 afer each I2C error)
  47. luminosity calculation alternative with float arithmetic for improved precision, especially with ir ratio below 50% and below 10 lux (new default, can be disabled)
  48. scale readings 'broadband' and 'ir' with actual gain and integration time ratios to get values that can be directly used (e.g. for plots)
  49. 'luminosity', 'broadband' and 'ir' readings are not updated if state is 'Saturated' or 'I2C Error' so that timestamp of readings show last valid time
  50. attribute and reading restoration added in I2C_TSL2561_Define
  51. autoGain attribute inversion fix
  52. 'Saturation' state freeze fix
  53. 22.03.2015 jensb
  54. round luminosity to 3 significant digits or max. 1 fractional digit when float arithmetics are enabled
  55. 11.04.2015 jensb
  56. unblock FHEM while waiting for end of integration time - this makes using long integration times preferable
  57. changing attribute 'gain' or 'integrationTime' no longer powers up the TSL2561
  58. attribute 'disable' added
  59. attribute 'autoIntegrationTime' added (decrease when measurement gets saturated, increase when value gets low)
  60. I2C auto address mode added for IODev to compensate floating address selection
  61. improved I2C read error handling for RPII2C IODev
  62. 16.04.2015 jensb
  63. make scaling of readings 'broadband' and 'ir' depended on new attribute 'normalizeRawValues'
  64. 18.04.2015 jensb
  65. new readings 'gain' and 'integrationTime'
  66. 20.04.2015 jensb
  67. update reading 'state' in bulk along with luminosity when toggling between 'Initialized' and 'Saturated'
  68. 17.11.2015 jensb
  69. register InitFn for IODev post initialization and do not init IOdev in Define if FHEM is not initialized
  70. 19.12.2015 jensb
  71. constants renamed with module specific prefix
  72. state machines modified to become I2C read driven for Firmata compatibility (non-blocking I2C I/O)
  73. changing gain/integrationTime attributes will no longer write to device but will be used at next poll
  74. 26.12.2015 kaihs
  75. CalculateLux float arithmetics formula fix
  76. =head1 TODO
  77. manual integration time (optional)
  78. =head1 CREDITS
  79. Based on the module 51_I2C_BMP180.pm by Dirk Hoffmann and Klaus Wittstock
  80. TSL2651 specific code based on the python module by schwabbel as posted on
  81. http://forums.adafruit.com/viewtopic.php?f=8&t=34922&start=75
  82. which in turn is based on the code by Adafruit
  83. https://github.com/adafruit/Adafruit_TSL2561
  84. Lux calculation algorithm is based on the code in the TSL2561 datasheet
  85. http://www.adafruit.com/datasheets/TSL2561.pdf
  86. newer version
  87. http://www.ams.com/eng/content/download/250094/975485/142937
  88. =head1 AUTHOR - Kai Stuke
  89. kaihs@FHEM_Forum (forum.fhem.de)
  90. modified by Jens Beyer jensb@FHEM_Forum (forum.fhem.de)
  91. =cut
  92. package main;
  93. use strict;
  94. use warnings;
  95. use Time::HiRes qw(tv_interval);
  96. use Scalar::Util qw(looks_like_number);
  97. use constant {
  98. # I2C address options
  99. TSL2561_ADDR_LOW => '0x29',
  100. TSL2561_ADDR_FLOAT => '0x39', # Default address (pin left floating)
  101. TSL2561_ADDR_HIGH => '0x49',
  102. TSL2561_ADDR_AUTO => 'AUTO',
  103. # I2C registers
  104. TSL2561_REGISTER_CONTROL => 0x00,
  105. TSL2561_REGISTER_TIMING => 0x01,
  106. TSL2561_REGISTER_THRESHHOLDL_LOW => 0x02,
  107. TSL2561_REGISTER_THRESHHOLDL_HIGH => 0x03,
  108. TSL2561_REGISTER_THRESHHOLDH_LOW => 0x04,
  109. TSL2561_REGISTER_THRESHHOLDH_HIGH => 0x05,
  110. TSL2561_REGISTER_INTERRUPT => 0x06,
  111. TSL2561_REGISTER_CRC => 0x08,
  112. TSL2561_REGISTER_ID => 0x0A,
  113. TSL2561_REGISTER_CHAN0_LOW => 0x0C,
  114. TSL2561_REGISTER_CHAN0_HIGH => 0x0D,
  115. TSL2561_REGISTER_CHAN1_LOW => 0x0E,
  116. TSL2561_REGISTER_CHAN1_HIGH => 0x0F,
  117. # I2C register values
  118. TSL2561_COMMAND_BIT => 0x80, # Must be 1,
  119. TSL2561_CLEAR_BIT => 0x40, # Clears any pending interrupt (write 1 to clear)
  120. TSL2561_WORD_BIT => 0x20, # 1 = read/write word (rather than byte)
  121. TSL2561_BLOCK_BIT => 0x10, # 1 = using block read/write
  122. TSL2561_CONTROL_POWERON => 0x03,
  123. TSL2561_CONTROL_POWEROFF => 0x00,
  124. TSL2561_PACKAGE_CS => 0b0001,
  125. TSL2561_PACKAGE_T_FN_CL => 0b0101,
  126. TSL2561_GAIN_1X => 0x00, # No gain
  127. TSL2561_GAIN_16X => 0x10, # 16x gain
  128. TSL2561_INTEGRATIONTIME_13MS => 0x00, # 13.7ms
  129. TSL2561_INTEGRATIONTIME_101MS => 0x01, # 101ms
  130. TSL2561_INTEGRATIONTIME_402MS => 0x02, # 402ms
  131. TSL2561_INTEGRATIONTIME_MANUAL_STOP => 0x03, # stop manual integration cycle (not implemented)
  132. TSL2561_INTEGRATIONTIME_MANUAL_START => 0x0b, # start manual integration cycle (not implemented)
  133. # Auto-gain thresholds
  134. TSL2561_AGC_THI_13MS => 4850, # Max value at Ti 13.7ms = 5047,
  135. TSL2561_AGC_TLO_13MS => 100,
  136. TSL2561_AGC_THI_101MS => 36000, # Max value at Ti 101ms = 37177,
  137. TSL2561_AGC_TLO_101MS => 200,
  138. TSL2561_AGC_THI_402MS => 63000, # Max value at Ti 402ms = 65535,
  139. TSL2561_AGC_TLO_402MS => 500,
  140. # Saturation clipping thresholds
  141. TSL2561_CLIPPING_13MS => 4946, # 2% below 13.7 ms max. of 5047
  142. TSL2561_CLIPPING_101MS => 36433, # 2% below 101 ms max. of 37177
  143. TSL2561_CLIPPING_402MS => 64224, # 2% below 402 ms max. of 65535
  144. # Lux calculations differ slightly for CS package
  145. TSL2561_LUX_LUXSCALE =>14, # Scale by 2^14,
  146. TSL2561_LUX_RATIOSCALE =>9, # Scale ratio by 2^9,
  147. TSL2561_LUX_CHSCALE =>10, # Scale channel values by 2^10,
  148. TSL2561_LUX_CHSCALE_TINT0 =>0x7517, # 322/11 * 2^TSL2561_LUX_CHSCALE
  149. TSL2561_LUX_CHSCALE_TINT1 =>0x0FE7, # 322/81 * 2^TSL2561_LUX_CHSCALE
  150. # T, FN and CL package values
  151. TSL2561_LUX_K1T =>0x0040, # 0.125 * 2^RATIO_SCALE
  152. TSL2561_LUX_B1T =>0x01f2, # 0.0304 * 2^LUX_SCALE
  153. TSL2561_LUX_M1T =>0x01be, # 0.0272, * 2^LUX_SCALE
  154. TSL2561_LUX_K2T =>0x0080, # 0.250 * 2^RATIO_SCALE
  155. TSL2561_LUX_B2T =>0x0214, # 0.0325 * 2^LUX_SCALE
  156. TSL2561_LUX_M2T =>0x02d1, # 0.0440 * 2^LUX_SCALE
  157. TSL2561_LUX_K3T =>0x00c0, # 0.375 * 2^RATIO_SCALE
  158. TSL2561_LUX_B3T =>0x023f, # 0.0351, * 2^LUX_SCALE
  159. TSL2561_LUX_M3T =>0x037b, # 0.0544, * 2^LUX_SCALE
  160. TSL2561_LUX_K4T =>0x0100, # 0.50 * 2^RATIO_SCALE
  161. TSL2561_LUX_B4T =>0x0270, # 0.0381 * 2^LUX_SCALE
  162. TSL2561_LUX_M4T =>0x03fe, # 0.0624, * 2^LUX_SCALE
  163. TSL2561_LUX_K5T =>0x0138, # 0.61 * 2^RATIO_SCALE
  164. TSL2561_LUX_B5T =>0x016f, # 0.0224, * 2^LUX_SCALE
  165. TSL2561_LUX_M5T =>0x01fc, # 0.0310, * 2^LUX_SCALE
  166. TSL2561_LUX_K6T =>0x019a, # 0.80, * 2^RATIO_SCALE
  167. TSL2561_LUX_B6T =>0x00d2, # 0.0128 * 2^LUX_SCALE
  168. TSL2561_LUX_M6T =>0x00fb, # 0.0153, * 2^LUX_SCALE
  169. TSL2561_LUX_K7T =>0x029a, # 1.3, * 2^RATIO_SCALE
  170. TSL2561_LUX_B7T =>0x0018, # 0.00146 * 2^LUX_SCALE
  171. TSL2561_LUX_M7T =>0x0012, # 0.00112 * 2^LUX_SCALE
  172. TSL2561_LUX_K8T =>0x029a, # 1.3, * 2^RATIO_SCALE
  173. TSL2561_LUX_B8T =>0x0000, # 0.000 * 2^LUX_SCALE
  174. TSL2561_LUX_M8T =>0x0000, # 0.000 * 2^LUX_SCALE
  175. # CS package values
  176. TSL2561_LUX_K1C =>0x0043, # 0.130 * 2^RATIO_SCALE
  177. TSL2561_LUX_B1C =>0x0204, # 0.0315 * 2^LUX_SCALE
  178. TSL2561_LUX_M1C =>0x01ad, # 0.0262, * 2^LUX_SCALE
  179. TSL2561_LUX_K2C =>0x0085, # 0.260 * 2^RATIO_SCALE
  180. TSL2561_LUX_B2C =>0x0228, # 0.0337 * 2^LUX_SCALE
  181. TSL2561_LUX_M2C =>0x02c1, # 0.0430 * 2^LUX_SCALE
  182. TSL2561_LUX_K3C =>0x00c8, # 0.390 * 2^RATIO_SCALE
  183. TSL2561_LUX_B3C =>0x0253, # 0.0363 * 2^LUX_SCALE
  184. TSL2561_LUX_M3C =>0x0363, # 0.0529 * 2^LUX_SCALE
  185. TSL2561_LUX_K4C =>0x010a, # 0.520, * 2^RATIO_SCALE
  186. TSL2561_LUX_B4C =>0x0282, # 0.0392 * 2^LUX_SCALE
  187. TSL2561_LUX_M4C =>0x03df, # 0.0605, * 2^LUX_SCALE
  188. TSL2561_LUX_K5C =>0x014d, # 0.65, * 2^RATIO_SCALE
  189. TSL2561_LUX_B5C =>0x0177, # 0.0229 * 2^LUX_SCALE
  190. TSL2561_LUX_M5C =>0x01dd, # 0.0291, * 2^LUX_SCALE
  191. TSL2561_LUX_K6C =>0x019a, # 0.80, * 2^RATIO_SCALE
  192. TSL2561_LUX_B6C =>0x0101, # 0.0157 * 2^LUX_SCALE
  193. TSL2561_LUX_M6C =>0x0127, # 0.0180 * 2^LUX_SCALE
  194. TSL2561_LUX_K7C =>0x029a, # 1.3, * 2^RATIO_SCALE
  195. TSL2561_LUX_B7C =>0x0037, # 0.00338 * 2^LUX_SCALE
  196. TSL2561_LUX_M7C =>0x002b, # 0.00260, * 2^LUX_SCALE
  197. TSL2561_LUX_K8C =>0x029a, # 1.3, * 2^RATIO_SCALE
  198. TSL2561_LUX_B8C =>0x0000, # 0.000 * 2^LUX_SCALE
  199. TSL2561_LUX_M8C =>0x0000, # 0.000 * 2^LUX_SCALE
  200. TSL2561_STATE_UNDEFINED => 'Undefined',
  201. TSL2561_STATE_DEFINED => 'Defined',
  202. TSL2561_STATE_INITIALIZED => 'Initialized',
  203. TSL2561_STATE_SATURATED => 'Saturated',
  204. TSL2561_STATE_I2C_ERROR => 'I2C Error',
  205. TSL2561_STATE_DISABLED => 'Disabled',
  206. TSL2561_ACQUI_STATE_IDLE => 0,
  207. TSL2561_ACQUI_STATE_SETUP => 1,
  208. TSL2561_ACQUI_STATE_ENABLE_REQUESTED => 2,
  209. TSL2561_ACQUI_STATE_ENABLED => 3,
  210. TSL2561_ACQUI_STATE_DATA_AVAILABLE => 4,
  211. TSL2561_ACQUI_STATE_DATA_REQUESTED => 5,
  212. TSL2561_ACQUI_STATE_DATA_CH0_RECEIVED => 6,
  213. TSL2561_ACQUI_STATE_DATA_CH1_RECEIVED => 7,
  214. TSL2561_ACQUI_STATE_ERROR => 8,
  215. TSL2561_CALC_STATE_IDLE => 0,
  216. TSL2561_CALC_STATE_DATA_REQUESTED => 1,
  217. TSL2561_CALC_STATE_DATA_RECEIVED => 2,
  218. TSL2561_CALC_STATE_ERROR => 3,
  219. TSL2561_CALC_STATE_COMPLETED => 4,
  220. TSL2561_MAX_CONSECUTIVE_OPERATIONS => 20,
  221. };
  222. ##################################################
  223. # Forward declarations
  224. #
  225. sub I2C_TSL2561_Initialize($);
  226. sub I2C_TSL2561_Define($$);
  227. sub I2C_TSL2561_Attr(@);
  228. sub I2C_TSL2561_Poll($);
  229. sub I2C_TSL2561_Set($@);
  230. sub I2C_TSL2561_Get($);
  231. sub I2C_TSL2561_Undef($$);
  232. sub I2C_TSL2561_Enable($);
  233. sub I2C_TSL2561_Disable($);
  234. sub I2C_TSL2561_GetData($);
  235. sub I2C_TSL2561_SetTimingRegister($);
  236. sub I2C_TSL2561_SetIntegrationTime($$);
  237. sub I2C_TSL2561_SetGain($$);
  238. sub I2C_TSL2561_GetLuminosity($);
  239. sub I2C_TSL2561_CalculateLux($);
  240. my $libcheck_hasHiPi = 1;
  241. my %sets = (
  242. "update" => "",
  243. );
  244. my %validAdresses = (
  245. "0x29" => TSL2561_ADDR_LOW,
  246. "0x39" => TSL2561_ADDR_FLOAT,
  247. "0x49" => TSL2561_ADDR_HIGH,
  248. "auto" => TSL2561_ADDR_AUTO,
  249. );
  250. my %validPackages = (
  251. "CS" => TSL2561_PACKAGE_CS,
  252. "T" => TSL2561_PACKAGE_T_FN_CL,
  253. "FN" => TSL2561_PACKAGE_T_FN_CL,
  254. "CL" => TSL2561_PACKAGE_T_FN_CL,
  255. );
  256. my @fsmSubs = (\&I2C_TSL2561_StartMeasurement, \&I2C_TSL2561_GetMeasurement);
  257. =head2 I2C_TSL2561_Initialize
  258. Title: I2C_TSL2561_Initialize
  259. Function: Implements the initialize function.
  260. Returns: -
  261. Args: named arguments:
  262. -argument1 => hash
  263. =cut
  264. sub I2C_TSL2561_Initialize($) {
  265. my ($hash) = @_;
  266. eval "use HiPi::Device::I2C;";
  267. $libcheck_hasHiPi = 0 if($@);
  268. $hash->{DefFn} = 'I2C_TSL2561_Define';
  269. $hash->{InitFn} = 'I2C_TSL2561_Init';
  270. $hash->{AttrFn} = 'I2C_TSL2561_Attr';
  271. $hash->{SetFn} = 'I2C_TSL2561_Set';
  272. $hash->{UndefFn} = 'I2C_TSL2561_Undef';
  273. $hash->{I2CRecFn} = 'I2C_TSL2561_I2CRec';
  274. $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 ' .
  275. 'loglevel:0,1,2,3,4,5,6 poll_interval:1,2,5,10,20,30 ' .
  276. 'gain:1,16 integrationTime:13,101,402 ' .
  277. 'autoGain:0,1 autoIntegrationTime:0,1 normalizeRawValues:0,1 ' .
  278. 'floatArithmetics:0,1 disable:0,1 ' . $readingFnAttributes;
  279. $hash->{AttrList} .= " useHiPiLib:0,1 " if ($libcheck_hasHiPi);
  280. }
  281. sub I2C_TSL2561_Define($$) {
  282. my ($hash, $def) = @_;
  283. my @a = split('[ \t][ \t]*', $def);
  284. my $name = $a[0];
  285. my $device;
  286. readingsSingleUpdate($hash, 'state', TSL2561_STATE_UNDEFINED, 1);
  287. Log3 $name, 1, "I2C_TSL2561_Define start: " . @a . "/" . join(' ', @a);
  288. $hash->{HiPi_exists} = $libcheck_hasHiPi if ($libcheck_hasHiPi);
  289. $hash->{HiPi_used} = 0;
  290. my $address = undef;
  291. my $msg = '';
  292. if (@a < 3) {
  293. $msg = 'wrong syntax: define <name> I2C_TSL2561 [devicename] address';
  294. } elsif (@a == 3) {
  295. $address = lc($a[2]);
  296. } else {
  297. $device = $a[2];
  298. $address = lc($a[3]);
  299. if ($libcheck_hasHiPi) {
  300. $hash->{HiPi_used} = 1;
  301. delete $validAdresses{'auto'};
  302. } else {
  303. $msg = '$name error: HiPi library not installed';
  304. }
  305. }
  306. if ($msg) {
  307. Log3 ($hash, 1, $msg);
  308. return $msg;
  309. }
  310. $address = $validAdresses{$address};
  311. if (!defined($address)) {
  312. $msg = "Wrong address, must be one of " . join(' ', keys %validAdresses);
  313. Log3 ($hash, 1, $msg);
  314. return $msg;
  315. }
  316. if ($address eq TSL2561_ADDR_AUTO) {
  317. # start with lowest address in auto mode
  318. $hash->{autoAddress} = 1;
  319. $address = TSL2561_ADDR_LOW;
  320. } else {
  321. $hash->{autoAddress} = 0;
  322. }
  323. if ($hash->{HiPi_used}) {
  324. $hash->{autoAddress} = 0;
  325. }
  326. $hash->{I2C_Address} = hex($address);
  327. # create default attributes
  328. if (AttrVal($name, 'poll_interval', '?') eq '?') {
  329. $msg = CommandAttr(undef, $name . ' poll_interval 5');
  330. if ($msg) {
  331. Log (1, $msg);
  332. return $msg;
  333. }
  334. }
  335. if (AttrVal($name, 'floatArithmetics', '?') eq '?') {
  336. $msg = CommandAttr(undef, $name . ' floatArithmetics 1');
  337. if ($msg) {
  338. Log (1, $msg);
  339. return $msg;
  340. }
  341. }
  342. # preset some internal readings
  343. if (!defined($hash->{tsl2561IntegrationTime})) {
  344. my $attrVal = AttrVal($name, 'integrationTime', 13);
  345. $hash->{tsl2561IntegrationTime} = $attrVal == 402? TSL2561_INTEGRATIONTIME_402MS : $attrVal == 101? TSL2561_INTEGRATIONTIME_101MS : TSL2561_INTEGRATIONTIME_13MS;
  346. }
  347. if (!defined($hash->{tsl2561Gain})) {
  348. my $attrVal = AttrVal($name, 'gain', 1);
  349. $hash->{tsl2561Gain} = $attrVal == 16? TSL2561_GAIN_16X : TSL2561_GAIN_1X;
  350. }
  351. if (!defined($hash->{acquiState})) {
  352. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  353. }
  354. if (!defined($hash->{calcState})) {
  355. $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
  356. }
  357. if (!defined($hash->{operationCounter})) {
  358. $hash->{operationCounter} = 0;
  359. }
  360. if (!defined($hash->{blockingIO})) {
  361. $hash->{blockingIO} = 0;
  362. }
  363. readingsSingleUpdate($hash, 'state', TSL2561_STATE_DEFINED, 1);
  364. if ($main::init_done || $hash->{HiPi_used}) {
  365. eval {
  366. I2C_TSL2561_Init($hash, [ $device ]);
  367. };
  368. Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;;
  369. }
  370. Log3 $name, 5, "I2C_TSL2561_Define end";
  371. return undef;
  372. }
  373. sub I2C_TSL2561_Init($$) {
  374. my ($hash, $dev) = @_;
  375. my $name = $hash->{NAME};
  376. if ($hash->{HiPi_used}) {
  377. # check for existing i2c device
  378. my $i2cModulesLoaded = 0;
  379. $i2cModulesLoaded = 1 if -e $dev;
  380. if ($i2cModulesLoaded) {
  381. if (-r $dev && -w $dev) {
  382. $hash->{devTSL2561} = HiPi::Device::I2C->new(
  383. devicename => $dev,
  384. address => $hash->{I2C_Address},
  385. busmode => 'i2c',
  386. );
  387. Log3 $name, 3, "I2C_TSL2561_Define device created";
  388. } else {
  389. my @groups = split '\s', $(;
  390. return "$name :Error! $dev isn't readable/writable by user " . getpwuid( $< ) . " or group(s) " .
  391. getgrgid($_) . " " foreach(@groups);
  392. }
  393. } else {
  394. return $name . ': Error! I2C device not found: ' . $dev . '. Please check that these kernelmodules are loaded: i2c_bcm2708, i2c_dev';
  395. }
  396. } else {
  397. AssignIoPort($hash);
  398. }
  399. # clear package identification to force device reinitialization (device may have been powered off)
  400. $hash->{tsl2561Package} = undef;
  401. # start new measurement cycle
  402. RemoveInternalTimer($hash);
  403. InternalTimer(gettimeofday() + 10, 'I2C_TSL2561_Poll', $hash, 0);
  404. return undef;
  405. }
  406. sub I2C_TSL2561_Catch($) {
  407. my $exception = shift;
  408. if ($exception) {
  409. $exception =~ /^(.*)( at.*FHEM.*)$/;
  410. return $1;
  411. }
  412. return undef;
  413. }
  414. =head2 I2C_TSL2561_Attr
  415. Title: I2C_TSL2561_Attr
  416. Function: Implements AttrFn function.
  417. Returns: string|undef
  418. Args: named arguments:
  419. -argument1 => array
  420. =cut
  421. sub I2C_TSL2561_Attr (@) {
  422. my ($cmd, $name, $attr, $val) = @_;
  423. my $hash = $defs{$name};
  424. my $msg = '';
  425. Log3 $name, 5, "I2C_TSL2561_Attr: start cmd=$cmd attr=$attr";
  426. if ($attr eq 'poll_interval') {
  427. my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  428. if ($val > 0) {
  429. # start new measurement cycle
  430. RemoveInternalTimer($hash);
  431. InternalTimer(gettimeofday() + 1, 'I2C_TSL2561_Poll', $hash, 0);
  432. } elsif (defined($val)) {
  433. $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
  434. }
  435. } elsif ($attr eq 'gain') {
  436. my $gain = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  437. Log3 $name, 5, "I2C_TSL2561_Attr: attr gain is " . $gain;
  438. if ($gain == 1) {
  439. I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X);
  440. } elsif ($gain == 16) {
  441. I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X);
  442. } elsif (defined($val)) {
  443. $msg = 'Wrong gain defined. must be 1 or 16';
  444. }
  445. } elsif ($attr eq 'integrationTime') {
  446. my $time = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  447. if ($time == 13) {
  448. I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_13MS);
  449. } elsif ($time == 101) {
  450. I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_101MS);
  451. } elsif ($time == 402) {
  452. I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_402MS);
  453. } elsif (defined($val)) {
  454. $msg = 'Wrong integrationTime defined. must be 13 or 101 or 402';
  455. }
  456. } elsif ($attr eq 'autoGain') {
  457. my $autoGain = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  458. $hash->{timingModified} = 1;
  459. } elsif ($attr eq 'autoIntegrationTime') {
  460. my $autoIntegrationTime = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  461. $hash->{timingModified} = 1;
  462. } elsif ($attr eq 'normalizeRawValues') {
  463. my $normalizeRawValues = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  464. } elsif ($attr eq 'floatArithmetics') {
  465. my $floatArithmetics = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  466. } elsif ($attr eq "disable") {
  467. my $disable = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  468. }
  469. return ($msg) ? $msg : undef;
  470. }
  471. =head2 I2C_TSL2561_Poll
  472. Title: I2C_TSL2561_Poll
  473. Function: Start polling the sensor at interval defined in attribute
  474. Returns: -
  475. Args: named arguments:
  476. - argument1 => hash
  477. =cut
  478. sub I2C_TSL2561_Poll($) {
  479. my ($hash) = @_;
  480. my $name = $hash->{NAME};
  481. RemoveInternalTimer($hash);
  482. Log3 $name, 5, "I2C_TSL2561_Poll: start";
  483. my $pollDelay = 60*AttrVal($hash->{NAME}, 'poll_interval', 0); # seconds polling
  484. if (!AttrVal($hash->{NAME}, "disable", 0)) {
  485. # Request new samples from TSL2561 and calculate luminosity
  486. my $lux = I2C_TSL2561_GetLuminosity($hash);
  487. if ($hash->{calcState} == TSL2561_CALC_STATE_DATA_REQUESTED) {
  488. # Measurement in progress
  489. if ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLED) {
  490. $pollDelay = I2C_TSL2561_GetIntegrationTime($hash) + 0.003; # seconds measurement time
  491. if (!$hash->{blockingIO}) {
  492. $pollDelay += 0.200; # extra time for async transport jitter compensation
  493. }
  494. } else {
  495. $pollDelay = 0.400; # seconds async I2C read reply timeout
  496. }
  497. } else {
  498. # Measurement completed
  499. if ($hash->{calcState} == TSL2561_CALC_STATE_COMPLETED) {
  500. # success, update readings based on new data
  501. my $chScale = 1;
  502. if (AttrVal($hash->{NAME}, "normalizeRawValues", 0)) {
  503. $chScale = I2C_TSL2561_GetChannelScale($hash);
  504. }
  505. readingsBeginUpdate($hash);
  506. readingsBulkUpdate($hash, "gain", I2C_TSL2561_GetGain($hash));
  507. readingsBulkUpdate($hash, "integrationTime", I2C_TSL2561_GetIntegrationTime($hash));
  508. readingsBulkUpdate($hash, "broadband", ceil($chScale*$hash->{broadband}));
  509. readingsBulkUpdate($hash, "ir", ceil($chScale*$hash->{ir}));
  510. if (defined($lux)) {
  511. readingsBulkUpdate($hash, "luminosity", $lux);
  512. }
  513. my $state = ReadingsVal($name, 'state', '');
  514. if ($state ne TSL2561_STATE_SATURATED && $hash->{saturated}) {
  515. readingsBulkUpdate($hash, 'state', TSL2561_STATE_SATURATED, 1);
  516. } elsif ($state ne TSL2561_STATE_INITIALIZED && !$hash->{saturated}) {
  517. readingsBulkUpdate($hash, 'state', TSL2561_STATE_INITIALIZED, 1);
  518. }
  519. readingsEndUpdate($hash, 1);
  520. }
  521. # backup required operations (for diagnostics)
  522. $hash->{requiredOperations} = $hash->{operationCounter};
  523. # Reset state
  524. $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
  525. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  526. $hash->{operationCounter} = 0;
  527. }
  528. } else {
  529. readingsSingleUpdate($hash, 'state', TSL2561_STATE_DISABLED, 1);
  530. }
  531. # Schedule next polling
  532. Log3 $name, 5, "I2C_TSL2561_Poll: $pollDelay s";
  533. if ($pollDelay > 0) {
  534. InternalTimer(gettimeofday() + $pollDelay, 'I2C_TSL2561_Poll', $hash, 0);
  535. }
  536. return undef;
  537. }
  538. sub I2C_TSL2561_Set($@) {
  539. my ( $hash, @args ) = @_;
  540. my $name = $hash->{NAME};
  541. my $cmd = $args[1];
  542. if(!defined($sets{$cmd})) {
  543. return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets)
  544. }
  545. I2C_TSL2561_Poll($hash);
  546. return undef;
  547. }
  548. sub I2C_TSL2561_Undef($$) {
  549. my ($hash, $arg) = @_;
  550. RemoveInternalTimer($hash);
  551. if ($hash->{HiPi_used}) {
  552. $hash->{devTSL2561}->close()
  553. }
  554. return undef;
  555. }
  556. #
  557. # process received control register
  558. #
  559. sub I2C_TSL2561_I2CRcvControl($$) {
  560. my ($hash, $control) = @_;
  561. my $name = $hash->{NAME};
  562. my $enabled = $control & 0x3;
  563. if ($enabled == TSL2561_CONTROL_POWERON) {
  564. Log3 $name, 5, "I2C_TSL2561_I2CRcvControl: is enabled";
  565. $hash->{acquiState} = TSL2561_ACQUI_STATE_ENABLED;
  566. $hash->{acquiStarted} = [gettimeofday];
  567. } else {
  568. Log3 $name, 5, "I2C_TSL2561_I2CRcvControl: is disabled";
  569. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  570. }
  571. if (!$hash->{blockingIO}) {
  572. I2C_TSL2561_Poll($hash);
  573. }
  574. return undef;
  575. }
  576. #
  577. # process received ID register
  578. #
  579. sub I2C_TSL2561_I2CRcvID($$) {
  580. my ($hash, $sensorId) = @_;
  581. my $name = $hash->{NAME};
  582. if ( !($sensorId & 0b00010000) ) {
  583. return $name . ': Error! I2C failure: Please check your i2c bus and the connected device address: ' . $hash->{I2C_Address};
  584. }
  585. my $package = '';
  586. $hash->{tsl2561Package} = $sensorId >> 4;
  587. if ($hash->{tsl2561Package} == TSL2561_PACKAGE_CS) {
  588. $package = 'CS';
  589. } else {
  590. $package = 'T/FN/CL';
  591. }
  592. $hash->{sensorType} = 'TSL2561 Package ' . $package . ' Rev. ' . ( $sensorId & 0x0f );
  593. Log3 $name, 5, 'I2C_TSL2561_I2CRcvID: sensorId ' . $hash->{sensorType};
  594. # init state
  595. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  596. readingsSingleUpdate($hash, 'state', TSL2561_STATE_INITIALIZED, 1);
  597. # force preset of integration time and gain (device may have been powered off)
  598. $hash->{timingModified} = 1;
  599. # I2C-API blocking/non-blocking detection
  600. $hash->{blockingIO} = $hash->{operationInProgress};
  601. if (!$hash->{blockingIO}) {
  602. I2C_TSL2561_Poll($hash);
  603. }
  604. return undef;
  605. }
  606. #
  607. # process received timing register
  608. #
  609. sub I2C_TSL2561_I2CRcvTiming ($$) {
  610. my ($hash, $timing) = @_;
  611. my $name = $hash->{NAME};
  612. $hash->{tsl2561IntegrationTime} = $timing & 0x03;
  613. $hash->{tsl2561Gain} = $timing & 0x10;
  614. Log3 $name, 5, "I2C_TSL2561_I2CRcvTiming: time $hash->{tsl2561IntegrationTime}, gain $hash->{tsl2561Gain}";
  615. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  616. if (!$hash->{blockingIO}) {
  617. I2C_TSL2561_Poll($hash);
  618. }
  619. return undef;
  620. }
  621. #
  622. # process received ADC channel 0 register
  623. #
  624. sub I2C_TSL2561_I2CRcvChan0 ($$) {
  625. my ($hash, $broadband) = @_;
  626. my $name = $hash->{NAME};
  627. $hash->{broadband} = $broadband;
  628. Log3 $name, 5, 'I2C_TSL2561_I2CRcvChan0 ' . $broadband;
  629. $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_CH0_RECEIVED;
  630. return undef;
  631. }
  632. #
  633. # process received ADC channel 1 register
  634. #
  635. sub I2C_TSL2561_I2CRcvChan1 ($$) {
  636. my ($hash, $ir) = @_;
  637. my $name = $hash->{NAME};
  638. $hash->{ir} = $ir;
  639. Log3 $name, 5, 'I2C_TSL2561_I2CRcvChan1 ' . $ir;
  640. $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_CH1_RECEIVED;
  641. if (!$hash->{blockingIO}) {
  642. I2C_TSL2561_Poll($hash);
  643. }
  644. return undef;
  645. }
  646. #
  647. # preprocess received data from I2C bus
  648. #
  649. sub I2C_TSL2561_I2CRec ($$) {
  650. my ($hash, $clientmsg) = @_;
  651. my $name = $hash->{NAME};
  652. my $pname = undef;
  653. unless ($hash->{HiPi_used}) { #nicht nutzen wenn HiPi Bibliothek in Benutzung
  654. my $phash = $hash->{IODev};
  655. $pname = $phash->{NAME};
  656. while (my ( $k, $v ) = each %$clientmsg) { #erzeugen von Internals für alle Keys in $clientmsg die mit dem physical Namen beginnen
  657. $hash->{$k} = $v if $k =~ /^$pname/;
  658. }
  659. }
  660. if ($clientmsg->{direction} && $clientmsg->{reg} &&
  661. (($pname && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok")
  662. || $hash->{HiPi_used})) {
  663. if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received})) {
  664. my $register = $clientmsg->{reg} & 0xF;
  665. Log3 $hash, 5, "$name RX register $register, $clientmsg->{nbyte} byte: $clientmsg->{received}";
  666. my $byte = undef;
  667. my $word = undef;
  668. my @raw = split(" ", $clientmsg->{received});
  669. if ($clientmsg->{nbyte} == 1) {
  670. $byte = $raw[0];
  671. } elsif ($clientmsg->{nbyte} == 2) {
  672. $word = $raw[1] << 8 | $raw[0];
  673. }
  674. if ($register == TSL2561_REGISTER_CONTROL) {
  675. I2C_TSL2561_I2CRcvControl($hash, $byte);
  676. } elsif ($register == TSL2561_REGISTER_ID) {
  677. I2C_TSL2561_I2CRcvID($hash, $byte);
  678. } elsif ($register == TSL2561_REGISTER_TIMING) {
  679. I2C_TSL2561_I2CRcvTiming($hash, $byte);
  680. } elsif ($register == TSL2561_REGISTER_CHAN0_LOW) {
  681. I2C_TSL2561_I2CRcvChan0($hash, $word);
  682. } elsif ($register == TSL2561_REGISTER_CHAN1_LOW) {
  683. I2C_TSL2561_I2CRcvChan1($hash, $word);
  684. } else {
  685. Log3 $name, 3, "I2C_TSL2561_I2CRec unsupported register $register";
  686. }
  687. }
  688. }
  689. return undef;
  690. }
  691. =head2 I2C_TSL2561_Enable
  692. Title: I2C_TSL2561_Enable
  693. Function: Enables the device
  694. Returns: 1 if enabling sensor was initiated, 0 if enabling sensor failed
  695. Args: named arguments:
  696. - argument1 => hash: $hash hash of device
  697. =cut
  698. sub I2C_TSL2561_Enable($) {
  699. my ($hash) = @_;
  700. my $name = $hash->{NAME};
  701. Log3 $name, 5, 'I2C_TSL2561_Enable: start ';
  702. my $success = 0;
  703. if (I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON)) {
  704. $success = I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, 1);
  705. }
  706. Log3 $name, 5, 'I2C_TSL2561_Enable: end ';
  707. return $success;
  708. }
  709. =head2 I2C_TSL2561_Disable
  710. Title: I2C_TSL2561_Disable
  711. Function: Disables the device
  712. Returns: 1 if disabling sensor was initiated, 0 if disabling sensor failed
  713. Args: named arguments:
  714. - argument1 => hash: $hash hash of device
  715. =cut
  716. sub I2C_TSL2561_Disable($) {
  717. my ($hash) = @_;
  718. my $name = $hash->{NAME};
  719. Log3 $name, 5, 'I2C_TSL2561_Disable: start ';
  720. my $success = I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF);
  721. Log3 $name, 5, 'I2C_TSL2561_Disable: end ';
  722. return $success;
  723. }
  724. =head2 I2C_TSL2561_GetData
  725. Title: I2C_TSL2561_GetData
  726. Function: Private function to read luminosity on both channels
  727. Returns: -
  728. Args: named arguments:
  729. - argument1 => hash: $hash hash of device
  730. =cut
  731. sub I2C_TSL2561_GetData($) {
  732. my ($hash) = @_;
  733. my $name = $hash->{NAME};
  734. # Data acquisition state machine with asynchronous wait
  735. my $success = 1;
  736. my $operations = 0;
  737. while (1) {
  738. $operations++;
  739. if ($hash->{acquiState} == TSL2561_ACQUI_STATE_ERROR) {
  740. $success = 0;
  741. last; # Abort, Start again at next slow poll
  742. } elsif ($operations > 10) {
  743. # Too many consecutive operations, abort
  744. $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
  745. Log3 $name, 5, "I2C_TSL2561_GetData: state machine stuck, aborting";
  746. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_IDLE) {
  747. if (!defined($hash->{tsl2561Package})) {
  748. # Choose an address to scan the I2C bus for device in auto address mode
  749. if ($hash->{autoAddress}) {
  750. if ($hash->{I2C_Address} == hex(TSL2561_ADDR_LOW)) {
  751. $hash->{I2C_Address} = hex(TSL2561_ADDR_FLOAT);
  752. } elsif ($hash->{I2C_Address} == hex(TSL2561_ADDR_FLOAT)) {
  753. $hash->{I2C_Address} = hex(TSL2561_ADDR_HIGH);
  754. } else {
  755. $hash->{I2C_Address} = hex(TSL2561_ADDR_LOW);
  756. }
  757. }
  758. # Detect TLS2561 package type and init integration time and gain
  759. Log3 $name, 5, "I2C_TSL2561_GetData: request device id";
  760. $hash->{acquiState} = TSL2561_ACQUI_STATE_SETUP;
  761. if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_ID, 1)) {
  762. last; # Wait for id confirmation, check again after next fast poll
  763. } else {
  764. $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
  765. }
  766. } elsif ($hash->{timingModified}) {
  767. $hash->{acquiState} = TSL2561_ACQUI_STATE_SETUP;
  768. if (I2C_TSL2561_SetTimingRegister($hash)) {
  769. last; # Wait new timing to be confirmed, check again after next fast poll
  770. } else {
  771. $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
  772. }
  773. } else {
  774. # Enable the device
  775. $hash->{acquiState} = TSL2561_ACQUI_STATE_ENABLE_REQUESTED;
  776. if (I2C_TSL2561_Enable($hash)) {
  777. last; # Wait for enable confirmation, check again after next fast poll
  778. } else {
  779. $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
  780. }
  781. }
  782. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_SETUP) {
  783. last; # Wait for setup confirmation, check again after next fast poll
  784. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLE_REQUESTED) {
  785. last; # Wait for enable confirmation, check again after next fast poll
  786. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLED) {
  787. # Wait x ms for ADC to complete
  788. my $now = [gettimeofday];
  789. if (tv_interval($hash->{acquiStarted}, $now) >= I2C_TSL2561_GetIntegrationTime($hash)) {
  790. $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_AVAILABLE;
  791. } else {
  792. last; # Wait for measurement to complete, check again after next fast poll
  793. }
  794. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_AVAILABLE) {
  795. # Read a two byte value from channel 0 and channel 1 (visible + infrared)
  796. $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_REQUESTED;
  797. if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW, 2)) {
  798. if (!I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW, 2)) {
  799. $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
  800. }
  801. } else {
  802. $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
  803. }
  804. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_REQUESTED) {
  805. last; # Wait for channel 0 or channel 1 data to be read
  806. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_CH0_RECEIVED) {
  807. # Read a two byte value from channel 1 (infrared)
  808. $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_REQUESTED;
  809. last; # Wait for channel 1 data to be read
  810. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_CH1_RECEIVED) {
  811. $hash->{calcState} = TSL2561_CALC_STATE_DATA_RECEIVED;
  812. # Try to turn the device off to save power
  813. I2C_TSL2561_Disable($hash);
  814. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  815. last; # Done, start again at next slow poll
  816. } else {
  817. # Undefined state
  818. $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
  819. }
  820. }
  821. return $success;
  822. }
  823. #
  824. # write integration time and gain to device
  825. #
  826. sub I2C_TSL2561_SetTimingRegister($) {
  827. my ($hash) = @_;
  828. my $name = $hash->{NAME};
  829. my $success = 0;
  830. if (!AttrVal($hash->{NAME}, "disable", 0) && defined($hash->{tsl2561Package})) {
  831. # Update the timing register
  832. my $autoGain = AttrVal($name, 'autoGain', 1);
  833. if (!$autoGain) {
  834. my $attrVal = AttrVal($name, 'gain', 1);
  835. $hash->{tsl2561Gain} = $attrVal == 16? TSL2561_GAIN_16X : TSL2561_GAIN_1X;
  836. }
  837. my $autoIntegrationTime = AttrVal($name, 'autoIntegrationTime', 0);
  838. if (!$autoIntegrationTime) {
  839. my $attrVal = AttrVal($name, 'integrationTime', 13);
  840. $hash->{tsl2561IntegrationTime} = $attrVal == 402? TSL2561_INTEGRATIONTIME_402MS : $attrVal == 101? TSL2561_INTEGRATIONTIME_101MS : TSL2561_INTEGRATIONTIME_13MS;
  841. }
  842. Log3 $name, 5, "I2C_TSL2561_SetTimingRegister: time $hash->{tsl2561IntegrationTime}, gain $hash->{tsl2561Gain}";
  843. if (I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, $hash->{tsl2561IntegrationTime} | $hash->{tsl2561Gain})) {
  844. if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, 1)) {
  845. $success = 1;
  846. }
  847. }
  848. }
  849. $hash->{timingModified} = 0;
  850. return $success;
  851. }
  852. =head2 I2C_TSL2561_SetIntegrationTime
  853. Title: I2C_TSL2561_SetIntegrationTime
  854. Function: Sets the integration time for the TSL2561
  855. Returns: -
  856. Args: named arguments:
  857. -argument1 => hash: $hash hash of device
  858. -argument1 => number: $time constant for integration time setting
  859. =cut
  860. sub I2C_TSL2561_SetIntegrationTime($$) {
  861. my ($hash, $time) = @_;
  862. my $name = $hash->{NAME};
  863. # store the value even if $hash->{tsl2561Package} is not set (yet). That happens
  864. # during fhem startup.
  865. $hash->{tsl2561IntegrationTime} = $time;
  866. $hash->{timingModified} = 1;
  867. return undef;
  868. }
  869. #
  870. # decode TSL2561 integration time into decimal value
  871. # @param device hash
  872. # @return integration time in seconds that was last reported by the TSL2561
  873. #
  874. sub I2C_TSL2561_GetIntegrationTime($) {
  875. my ($hash) = @_;
  876. my $tsl2561IntegrationTime = $hash->{tsl2561IntegrationTime};
  877. my $integrationTime = 0.402; # 402 ms
  878. if ($tsl2561IntegrationTime == TSL2561_INTEGRATIONTIME_13MS) {
  879. $integrationTime = 0.0137; # 13.7 ms
  880. } elsif ($tsl2561IntegrationTime == TSL2561_INTEGRATIONTIME_101MS) {
  881. $integrationTime = 0.101; # 101 ms
  882. }
  883. return $integrationTime;
  884. }
  885. =head2 I2C_TSL2561_SetGain
  886. Title: I2C_TSL2561_SetGain
  887. Function: Adjusts the gain on the TSL2561 (adjusts the sensitivity to light)
  888. Returns: -
  889. Args: named arguments:
  890. - argument1 => hash: $hash hash of device
  891. - argument1 => number: $gain constant for gain
  892. =cut
  893. sub I2C_TSL2561_SetGain($$) {
  894. my ($hash, $gain) = @_;
  895. my $name = $hash->{NAME};
  896. # store the value even if $hash->{tsl2561Package} is not set (yet). That happens
  897. # during fhem startup.
  898. $hash->{tsl2561Gain} = $gain;
  899. $hash->{timingModified} = 1;
  900. return undef;
  901. }
  902. #
  903. # decode TSL2561 gain into decimal value
  904. # @param device hash
  905. # @return decimal gain factor that was last reported by the TSL2561
  906. #
  907. sub I2C_TSL2561_GetGain($) {
  908. my ($hash) = @_;
  909. my $tsl2561Gain = $hash->{tsl2561Gain};
  910. my $gain = 1;
  911. if (defined($tsl2561Gain) && $tsl2561Gain) {
  912. $gain = 16;
  913. }
  914. return $gain;
  915. }
  916. =head2 I2C_TSL2561_GetLuminosity
  917. Title: I2C_TSL2561_GetLuminosity
  918. Function: Gets the broadband (mixed lighting) and IR only values from the TSL2561, adjusting gain if auto-gain is enabled and calculate luminosity
  919. Returns: luminosity
  920. Args: named arguments:
  921. -argument1 => hash: $hash hash of device
  922. =cut
  923. sub I2C_TSL2561_GetLuminosity($) {
  924. my ($hash) = @_;
  925. my $name = $hash->{NAME};
  926. # Log3 $name, 5, "I2C_TSL2561_GetLuminosity: start";
  927. $hash->{operationInProgress} = 1;
  928. # Luminosity calculation state machine
  929. my $lux = undef;
  930. while(1) {
  931. $hash->{operationCounter}++;
  932. Log3 $name, 5, "I2C_TSL2561_GetLuminosity: calc state $hash->{calcState} acqui state $hash->{acquiState}";
  933. if ($hash->{calcState} == TSL2561_CALC_STATE_ERROR) {
  934. Log3 $name, 5, "I2C_TSL2561_GetLuminosity: error, aborting";
  935. # Try to turn the device off to save power
  936. I2C_TSL2561_Disable($hash);
  937. # Reset package to force device reinitialization
  938. $hash->{tsl2561Package} = undef;
  939. # Claim I2C error
  940. readingsSingleUpdate($hash, 'state', TSL2561_STATE_I2C_ERROR, 1);
  941. last; # Abort, start again at next slow poll
  942. } elsif ($hash->{operationCounter} > TSL2561_MAX_CONSECUTIVE_OPERATIONS) {
  943. # Too many consecutive operations, abort
  944. $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
  945. Log3 $name, 5, "I2C_TSL2561_GetLuminosity: state machine stuck, aborting";
  946. } elsif ($hash->{calcState} == TSL2561_CALC_STATE_IDLE) {
  947. # Enable device and request data
  948. Log3 $name, 5, "I2C_TSL2561_GetLuminosity: starting new measurement";
  949. if (I2C_TSL2561_GetData($hash)) {
  950. $hash->{calcState} = TSL2561_CALC_STATE_DATA_REQUESTED;
  951. } else {
  952. $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
  953. }
  954. } elsif ($hash->{calcState} == TSL2561_CALC_STATE_DATA_REQUESTED) {
  955. # Wait for device
  956. if (I2C_TSL2561_GetData($hash)) {
  957. if ($hash->{acquiState} == TSL2561_ACQUI_STATE_SETUP) {
  958. last; # Wait for setup confirmation, check again after next fast poll
  959. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLE_REQUESTED) {
  960. last; # Wait for enable to be confirmed, check again at next fast poll
  961. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLED) {
  962. last; # Wait for measurement to complete, check again at next fast poll
  963. } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_REQUESTED) {
  964. last; # Wait for data to be read, check again at next fast poll
  965. }
  966. } else {
  967. $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
  968. }
  969. } elsif ($hash->{calcState} == TSL2561_CALC_STATE_DATA_RECEIVED) {
  970. # Data was received, optimize gain
  971. my $autoGain = AttrVal($name, 'autoGain', 1);
  972. if ($autoGain) {
  973. # Get the hi/low threshold for the current integration time
  974. my $it = $hash->{tsl2561IntegrationTime};
  975. my $hi = TSL2561_AGC_THI_402MS;
  976. my $lo = TSL2561_AGC_TLO_402MS;
  977. if ($it == TSL2561_INTEGRATIONTIME_13MS) {
  978. $hi = TSL2561_AGC_THI_13MS;
  979. $lo = TSL2561_AGC_TLO_13MS;
  980. } elsif ($it == TSL2561_INTEGRATIONTIME_101MS) {
  981. $hi = TSL2561_AGC_THI_101MS;
  982. $lo = TSL2561_AGC_TLO_101MS;
  983. }
  984. if (($hash->{broadband} < $lo) && ($hash->{tsl2561Gain} == TSL2561_GAIN_1X)) {
  985. # Increase gain and try again
  986. I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X);
  987. $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
  988. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  989. next;
  990. } elsif (($hash->{broadband} > $hi) && ($hash->{tsl2561Gain} == TSL2561_GAIN_16X)) {
  991. # Drop gain and try again
  992. I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X);
  993. $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
  994. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  995. next;
  996. } else {
  997. # Reading is either valid, or we're already at the chips limits
  998. }
  999. } else {
  1000. # Auto gain disabled, always valid
  1001. }
  1002. # Optimize integration time (make sure the sensor isn't saturated at 402 ms)
  1003. my $clipThreshold = 0;
  1004. if ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS) {
  1005. $clipThreshold = TSL2561_CLIPPING_13MS;
  1006. } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS) {
  1007. $clipThreshold = TSL2561_CLIPPING_101MS;
  1008. } else {
  1009. $clipThreshold = TSL2561_CLIPPING_402MS;
  1010. }
  1011. my $autoIntegrationTime = AttrVal($name, 'autoIntegrationTime', 0);
  1012. if (($hash->{broadband} > $clipThreshold) || ($hash->{ir} > $clipThreshold)) {
  1013. # ADC saturated, try to decrease integration time
  1014. if ($autoIntegrationTime && $hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_402MS) {
  1015. # Drop integration time and try again
  1016. I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_101MS);
  1017. $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
  1018. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  1019. next;
  1020. } else {
  1021. # Integration time fixed or already below 402 ms, give up
  1022. $hash->{saturated} = 1;
  1023. }
  1024. } elsif ($autoIntegrationTime
  1025. && ($hash->{broadband} < ($clipThreshold >> 2) && $hash->{ir} < ($clipThreshold >> 2))
  1026. && ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS || $hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS)) {
  1027. # Integration time below 178 ms, maximize and try again
  1028. I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_402MS);
  1029. $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
  1030. $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  1031. next;
  1032. } else {
  1033. # Readings are not saturated or auto integration time is disabled
  1034. $hash->{saturated} = 0;
  1035. }
  1036. # Received data is valid, calculate luminosity
  1037. $lux = I2C_TSL2561_CalculateLux($hash);
  1038. $hash->{calcState} = TSL2561_CALC_STATE_COMPLETED;
  1039. last; # Done, start again at next slow poll
  1040. } else {
  1041. # Undefined state
  1042. $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
  1043. }
  1044. }
  1045. $hash->{operationInProgress} = 0;
  1046. # Log3 $name, 5, "I2C_TSL2561_GetLuminosity: end";
  1047. return $lux;
  1048. }
  1049. #
  1050. # get channel scale
  1051. #
  1052. sub I2C_TSL2561_GetChannelScale($) {
  1053. my ($hash) = @_;
  1054. my $name = $hash->{NAME};
  1055. my $chScale = 0;
  1056. if (AttrVal($name, 'floatArithmetics', 0)) {
  1057. # Get the correct scale depending on the integration time
  1058. if (!defined($hash->{tsl2561IntegrationTime})) {
  1059. $chScale = 1.0;
  1060. } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS) {
  1061. $chScale = 322.0/11;
  1062. } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS) {
  1063. $chScale = 322.0/81;
  1064. } else {
  1065. $chScale = 1.0;
  1066. }
  1067. # Scale for gain (1x or 16x)
  1068. if (!defined($hash->{tsl2561Gain}) || !$hash->{tsl2561Gain}) {
  1069. $chScale = $chScale*16;
  1070. }
  1071. } else {
  1072. # Get the correct scale depending on the integration time
  1073. if ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS) {
  1074. $chScale = TSL2561_LUX_CHSCALE_TINT0;
  1075. } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS) {
  1076. $chScale = TSL2561_LUX_CHSCALE_TINT1;
  1077. } else {
  1078. $chScale = (1 << TSL2561_LUX_CHSCALE);
  1079. }
  1080. # Scale for gain (1x or 16x)
  1081. if (!defined($hash->{tsl2561Gain}) || !$hash->{tsl2561Gain}) {
  1082. $chScale = $chScale << 4;
  1083. }
  1084. }
  1085. return $chScale;
  1086. }
  1087. =head2 I2C_TSL2561_CalculateLux
  1088. Title: I2C_TSL2561_CalculateLux
  1089. Function: Converts the raw sensor values to the standard SI lux equivalent. Returns 0 if the sensor is saturated and the values are unreliable.
  1090. Returns: number
  1091. Args: named arguments:
  1092. - argument1 => hash: $hash hash of device
  1093. =cut
  1094. sub I2C_TSL2561_CalculateLux($) {
  1095. my ($hash) = @_;
  1096. my $name = $hash->{NAME};
  1097. # Get the correct scale depending on gain and integration time
  1098. my $chScale = I2C_TSL2561_GetChannelScale($hash);
  1099. if (AttrVal($name, 'floatArithmetics', 0)) {
  1100. # Scale the channel values
  1101. my $channel0 = $chScale*$hash->{broadband};
  1102. my $channel1 = $chScale*$hash->{ir};
  1103. # Find the ratio of the channel values (Channel1/Channel0)
  1104. my $ratio = 0.0;
  1105. if ($channel0 != 0) {
  1106. $ratio = $channel1/$channel0;
  1107. }
  1108. # Calculate luminosity (see TSL2561 data sheet)
  1109. my $lux = undef;
  1110. if ($hash->{tsl2561Package} == TSL2561_PACKAGE_CS) {
  1111. # CS package
  1112. if ($ratio <= 0.52) {
  1113. $lux = 0.0315*$channel0 - 0.0593*$channel0*pow($ratio, 1.4);
  1114. } elsif ($ratio <= 0.65) {
  1115. $lux = 0.0229*$channel0 - 0.0291*$channel1;
  1116. } elsif ($ratio <= 0.80) {
  1117. $lux = 0.0157*$channel0 - 0.0180*$channel1;
  1118. } elsif ($ratio <= 1.30) {
  1119. $lux = 0.00338*$channel0 - 0.00260*$channel1;
  1120. } else {
  1121. $lux = 0.0;
  1122. }
  1123. } else {
  1124. # T, FN and CL package
  1125. if ($ratio <= 0.50) {
  1126. $lux = 0.0304*$channel0 - 0.062*$channel0*pow($ratio, 1.4);
  1127. } elsif ($ratio <= 0.61) {
  1128. $lux = 0.0224*$channel0 - 0.031*$channel1;
  1129. } elsif ($ratio <= 0.80) {
  1130. $lux = 0.0128*$channel0 - 0.0153*$channel1;
  1131. } elsif ($ratio <= 1.30) {
  1132. $lux = 0.00146*$channel0 - 0.00112*$channel1;
  1133. } else {
  1134. $lux = 0.0;
  1135. }
  1136. }
  1137. if ($lux >= 100) {
  1138. # Round to 3 significant digits if at least 100
  1139. my $roundFactor = 10**(floor(log($lux)/log(10)) - 2);
  1140. $lux = $roundFactor*floor($lux/$roundFactor + 0.5);
  1141. } else {
  1142. # Round to 1 fractional digit if less than 100
  1143. $lux = floor(10*$lux + 0.5)/10;
  1144. }
  1145. return $lux;
  1146. } else {
  1147. # Scale the channel values
  1148. my $channel0 = ($hash->{broadband} * $chScale) >> TSL2561_LUX_CHSCALE;
  1149. my $channel1 = ($hash->{ir} * $chScale) >> TSL2561_LUX_CHSCALE;
  1150. # Find the ratio of the channel values (Channel1/Channel0)
  1151. my $ratio1 = 0;
  1152. if ($channel0 != 0) {
  1153. $ratio1 = ($channel1 << (TSL2561_LUX_RATIOSCALE+1)) / $channel0;
  1154. }
  1155. # round the ratio value
  1156. my $ratio = ($ratio1 + 1) >> 1;
  1157. my $b=0;
  1158. my $m=0;
  1159. if ($hash->{tsl2561Package} == TSL2561_PACKAGE_CS) {
  1160. # CS package
  1161. if (($ratio >= 0) && ($ratio <= TSL2561_LUX_K1C)) {
  1162. $b=TSL2561_LUX_B1C;
  1163. $m=TSL2561_LUX_M1C;
  1164. } elsif ($ratio <= TSL2561_LUX_K2C) {
  1165. $b=TSL2561_LUX_B2C;
  1166. $m=TSL2561_LUX_M2C;
  1167. } elsif ($ratio <= TSL2561_LUX_K3C) {
  1168. $b=TSL2561_LUX_B3C;
  1169. $m=TSL2561_LUX_M3C;
  1170. } elsif ($ratio <= TSL2561_LUX_K4C) {
  1171. $b=TSL2561_LUX_B4C;
  1172. $m=TSL2561_LUX_M4C;
  1173. } elsif ($ratio <= TSL2561_LUX_K5C) {
  1174. $b=TSL2561_LUX_B5C;
  1175. $m=TSL2561_LUX_M5C;
  1176. } elsif ($ratio <= TSL2561_LUX_K6C) {
  1177. $b=TSL2561_LUX_B6C;
  1178. $m=TSL2561_LUX_M6C;
  1179. } elsif ($ratio <= TSL2561_LUX_K7C) {
  1180. $b=TSL2561_LUX_B7C;
  1181. $m=TSL2561_LUX_M7C;
  1182. } elsif ($ratio > TSL2561_LUX_K8C) {
  1183. $b=TSL2561_LUX_B8C;
  1184. $m=TSL2561_LUX_M8C;
  1185. }
  1186. } elsif ($hash->{tsl2561Package} == TSL2561_PACKAGE_T_FN_CL) {
  1187. # T, FN and CL package
  1188. if (($ratio >= 0) && ($ratio <= TSL2561_LUX_K1T)) {
  1189. $b=TSL2561_LUX_B1T;
  1190. $m=TSL2561_LUX_M1T;
  1191. } elsif ($ratio <= TSL2561_LUX_K2T) {
  1192. $b=TSL2561_LUX_B2T;
  1193. $m=TSL2561_LUX_M2T;
  1194. } elsif ($ratio <= TSL2561_LUX_K3T) {
  1195. $b=TSL2561_LUX_B3T;
  1196. $m=TSL2561_LUX_M3T;
  1197. } elsif ($ratio <= TSL2561_LUX_K4T) {
  1198. $b=TSL2561_LUX_B4T;
  1199. $m=TSL2561_LUX_M4T;
  1200. } elsif ($ratio <= TSL2561_LUX_K5T) {
  1201. $b=TSL2561_LUX_B5T;
  1202. $m=TSL2561_LUX_M5T;
  1203. } elsif ($ratio <= TSL2561_LUX_K6T) {
  1204. $b=TSL2561_LUX_B6T;
  1205. $m=TSL2561_LUX_M6T;
  1206. } elsif ($ratio <= TSL2561_LUX_K7T) {
  1207. $b=TSL2561_LUX_B7T;
  1208. $m=TSL2561_LUX_M7T;
  1209. } elsif ($ratio > TSL2561_LUX_K8T) {
  1210. $b=TSL2561_LUX_B8T;
  1211. $m=TSL2561_LUX_M8T;
  1212. }
  1213. }
  1214. my $temp = (($channel0 * $b) - ($channel1 * $m));
  1215. # Do not allow negative lux value
  1216. if ($temp < 0) {
  1217. $temp = 0;
  1218. }
  1219. # Round lsb (2^(LUX_SCALE-1))
  1220. $temp += (1 << (TSL2561_LUX_LUXSCALE-1));
  1221. # Strip off fractional portion
  1222. my $lux = $temp >> TSL2561_LUX_LUXSCALE;
  1223. # Signal I2C had no errors
  1224. return $lux;
  1225. }
  1226. }
  1227. =head2 I2C_TSL2561_i2cread
  1228. Title: I2C_TSL2561_i2cread
  1229. Function: implements I2C read operation abstraction
  1230. Returns: 1 on success, 0 on error
  1231. Args: - argument1 => hash
  1232. - argument2 => I2C register
  1233. - argument3 => number of bytes to read
  1234. =cut
  1235. sub I2C_TSL2561_i2cread($$$) {
  1236. my ($hash, $reg, $nbyte) = @_;
  1237. my $success = 1;
  1238. local $SIG{__WARN__} = sub {
  1239. my $message = shift;
  1240. # turn warnings from RPII2C_HWACCESS_ioctl into exception
  1241. if ($message =~ /Exiting subroutine via last at.*00_RPII2C.pm/) {
  1242. die;
  1243. } else {
  1244. warn($message);
  1245. }
  1246. };
  1247. if ($hash->{HiPi_used}) {
  1248. eval {
  1249. my @values = $hash->{devTSL2561}->bus_read($reg, $nbyte);
  1250. I2C_TSL2561_I2CRec($hash, {
  1251. direction => "i2cread",
  1252. i2caddress => $hash->{I2C_Address},
  1253. reg => $reg,
  1254. nbyte => $nbyte,
  1255. received => join (' ',@values),
  1256. });
  1257. };
  1258. Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;
  1259. } elsif (defined (my $iodev = $hash->{IODev})) {
  1260. eval {
  1261. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  1262. direction => "i2cread",
  1263. i2caddress => $hash->{I2C_Address},
  1264. reg => $reg,
  1265. nbyte => $nbyte
  1266. });
  1267. };
  1268. my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
  1269. if (defined($sendStat) && $sendStat eq 'error') {
  1270. readingsSingleUpdate($hash, 'state', TSL2561_STATE_I2C_ERROR, 1);
  1271. Log3 ($hash, 5, $hash->{NAME} . ": i2cread on $iodev->{NAME} failed");
  1272. $success = 0;
  1273. }
  1274. } else {
  1275. Log3 ($hash, 1, $hash->{NAME} . ': ' . "no IODev assigned to '$hash->{NAME}'");
  1276. $success = 0;
  1277. }
  1278. return $success;
  1279. }
  1280. sub I2C_TSL2561_i2cwrite($$$) {
  1281. my ($hash, $reg, @data) = @_;
  1282. my $success = 1;
  1283. if ($hash->{HiPi_used}) {
  1284. eval {
  1285. $hash->{devTSL2561}->bus_write($reg, join (' ',@data));
  1286. I2C_TSL2561_I2CRec($hash, {
  1287. direction => "i2cwrite",
  1288. i2caddress => $hash->{I2C_Address},
  1289. reg => $reg,
  1290. data => join (' ',@data),
  1291. });
  1292. };
  1293. Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;
  1294. } elsif (defined (my $iodev = $hash->{IODev})) {
  1295. eval {
  1296. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  1297. direction => "i2cwrite",
  1298. i2caddress => $hash->{I2C_Address},
  1299. reg => $reg,
  1300. data => join (' ',@data),
  1301. });
  1302. };
  1303. my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
  1304. if (defined($sendStat) && $sendStat eq 'error') {
  1305. readingsSingleUpdate($hash, 'state', TSL2561_STATE_I2C_ERROR, 1);
  1306. Log3 ($hash, 5, $hash->{NAME} . ": i2cwrite on $iodev->{NAME} failed");
  1307. $success = 0;
  1308. }
  1309. } else {
  1310. Log3 ($hash, 1, $hash->{NAME} . ': ' . "no IODev assigned to '$hash->{NAME}'");
  1311. $success = 0;
  1312. }
  1313. return $success;
  1314. }
  1315. 1;
  1316. =pod
  1317. =begin html
  1318. <a name="I2C_TSL2561"></a>
  1319. <h3>I2C_TSL2561</h3>
  1320. <ul>
  1321. <a name="I2C_TSL2561"></a>
  1322. <p>
  1323. With this module you can read values from the ambient light sensor TSL2561
  1324. via the i2c bus on Raspberry Pi.<br>
  1325. The luminosity value returned is a good human eye reponse approximation of an
  1326. illumination measurement in the range of 0.1 to 40000+ lux (but not a replacement for a
  1327. precision measurement, relation between measured value and true value may vary by 40%).
  1328. <br><br>
  1329. <b>There are two possibilities connecting to I2C bus:</b><br>
  1330. <ul>
  1331. <li><b>via IODev module</b><br>
  1332. The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  1333. or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
  1334. <b>attribute IODev must be set</b><br>
  1335. <br>
  1336. </li>
  1337. <li><b>via HiPi library</b><br>
  1338. Add these two lines to your <b>/etc/modules</b> file to load the I2C relevant kernel modules
  1339. automaticly during booting your Raspberry Pi.<br>
  1340. <code><pre>
  1341. i2c-bcm2708
  1342. i2c-dev
  1343. </pre></code>
  1344. Install HiPi perl modules:<br>
  1345. <code><pre> wget http://raspberry.znix.com/hipifiles/hipi-install perl hipi-install</pre></code>
  1346. To change the permissions of the I2C device create file:<br>
  1347. <code><pre> /etc/udev/rules.d/98_i2c.rules</pre></code>
  1348. with this content:<br>
  1349. <code><pre> SUBSYSTEM=="i2c-dev", MODE="0666"</pre></code>
  1350. <b>Reboot</b><br>
  1351. <br>
  1352. To use the sensor on the second I2C bus at P5 connector
  1353. (only for version 2 of Raspberry Pi) you must add the bold
  1354. line of following code to your FHEM start script:
  1355. <code><pre>
  1356. case "$1" in
  1357. 'start')
  1358. <b>sudo hipi-i2c e 0 1</b>
  1359. ...
  1360. </pre></code>
  1361. </li>
  1362. </ul>
  1363. <p>
  1364. <b>Define</b>
  1365. <ul>
  1366. <code>define TSL2561 I2C_TSL2561 [&lt;I2C device&gt;] &lt;I2C address&gt</code><br><br>
  1367. &lt;I2C device&gt; mandatory for HiPi, must be omitted if you connect via IODev<br>
  1368. &lt;I2C address&gt; may be 0x29, 0x39 or 0x49 (and 'AUTO' when using IODev to search for device at startup and after an I2C error)<br>
  1369. <br>
  1370. Examples:
  1371. <pre>
  1372. define TSL2561 I2C_TSL2561 /dev/i2c-0 0x39
  1373. attr TSL2561 poll_interval 5
  1374. </pre>
  1375. <pre>
  1376. define TSL2561 I2C_TSL2561 0x39
  1377. attr TSL2561 IODev I2CModule
  1378. attr TSL2561 poll_interval 5
  1379. </pre>
  1380. <pre>
  1381. define TSL2561 I2C_TSL2561 AUTO
  1382. attr TSL2561 IODev I2CModule
  1383. attr TSL2561 poll_interval 5
  1384. </pre>
  1385. </ul>
  1386. <b>Set</b>
  1387. <ul>
  1388. <code>get &lt;name&gt; update</code><br><br>
  1389. Force immediate illumination measurement and restart a new poll_interval.<br><br>
  1390. Note that the new readings are not yet available after set returns because the
  1391. measurement is performed asynchronously. Depending on the attributes integration time,
  1392. autoGain and autoIntegrationTime this may require more than one second to complete.
  1393. </ul>
  1394. <p>
  1395. <b>Readings</b>
  1396. <ul>
  1397. <li>luminosity<br>
  1398. Good human eye reponse approximation of an illumination measurement in the range of 0.1 to 40000+ lux.<br>
  1399. Rounded to 3 significant digits or one fractional digit.
  1400. </li>
  1401. <li>broadband<br>
  1402. Broadband spectrum sensor sample.<br>
  1403. Enable attribute normalizeRawValues for continuous readings independed of actual gain and integration time settings.
  1404. </li>
  1405. <li>ir<br>
  1406. Infrared spectrum sensor sample.<br>
  1407. Enable attribute normalizeRawValues for continuous readings independed of actual gain and integration time settings.
  1408. </li>
  1409. <li>gain<br>
  1410. sensor gain used for current luminosity measurement (1 or 16)<br>
  1411. </li>
  1412. <li>integrationTime<br>
  1413. integration time in seconds used for current luminosity measurement<br>
  1414. </li>
  1415. <li>state<br>
  1416. Default: Initialized, valid values: Undefined, Defined, Initialized, Saturated, Disabled, I2C Error
  1417. </li>
  1418. </ul>
  1419. <p>
  1420. <a name="I2C_TSL2561attr"></a>
  1421. <b>Attributes</b>
  1422. <ul>
  1423. <li>IODev<br>
  1424. Set the name of an IODev module. If undefined the perl modules HiPi::Device::I2C are required.<br>
  1425. Default: undefined<br>
  1426. </li>
  1427. <li>poll_interval<br>
  1428. Set the polling interval in minutes to query the sensor for new measured values.
  1429. By changing this attribute a new illumination measurement will be triggered.<br>
  1430. Default: 5, valid values: 1, 2, 5, 10, 20, 30<br>
  1431. </li>
  1432. <li>gain<br>
  1433. Set gain factor. Attribute will be ignored if autoGain is enabled.<br>
  1434. Default: 1, valid values: 1, 16
  1435. </li>
  1436. <li>integrationTime<br>
  1437. Set time in ms the sensor takes to measure the light. Attribute will be ignored if autoIntegrationTime is enabled.<br>
  1438. Default: 13, valid values: 13, 101, 402<br>
  1439. See this <a href="https://learn.sparkfun.com/tutorials/tsl2561-luminosity-sensor-hookup-guide/using-the-arduino-library">tutorial</a>
  1440. for more details.
  1441. </li>
  1442. <li>autoGain<br>
  1443. Enable auto gain. If set to 1, the gain parameter is adjusted automatically depending on light conditions.<br>
  1444. Default: 1, valid values: 0, 1<br>
  1445. </li>
  1446. <li>autoIntegrationTime<br>
  1447. Enable auto integration time. If set to 1, the integration time parameter is adjusted automatically depending on light conditions.<br>
  1448. Default: 0, valid values: 0, 1<br>
  1449. </li>
  1450. <li>normalizeRawValues<br>
  1451. Scale the sensor raw values broadband and ir depending on actual gain and integrationTime to the equivalent of the settings for maximum sensitivity (gain=16 and integrationTime=403ms). This feature may be useful when autoGain or autoIntegrationTime is enabled to provide continuous values instead of jumping values when gain or integration time changes.<br>
  1452. Default: 0, valid values: 0, 1<br>
  1453. </li>
  1454. <li>floatArithmetics<br>
  1455. Enable float arithmetics.<br>
  1456. If set to 0, the luminosity is calculated using int arithmetics (for very low powered platforms).<br>
  1457. If set to 1, the luminosity is calculated using float arithmetics, yielding some additional precision.
  1458. Default: 1, valid values: 0, 1<br>
  1459. </li>
  1460. <li>disable<br>
  1461. Disable I2C bus access.<br>
  1462. Default: 0, valid values: 0, 1
  1463. </li>
  1464. </ul>
  1465. <p>
  1466. <b>Notes</b>
  1467. <ul>
  1468. <li>Because the measurement may take several 100 milliseconds a measurement cycle will be executed asynchronously, so
  1469. do not expect to have new values immediately available after "set update" returns. If autoGain or autoIntegrationTime
  1470. are enabled, more than one measurement cycle will be required if light conditions change.
  1471. </li>
  1472. <li>With HiPi and especially IODev there are several I2C interfaces available, some blocking, some non-blocking and
  1473. some with different physical layers. The module has no knowledge of the specific properties of an interface and
  1474. therefore module operation and timing may not be exactly the same with each interface type.
  1475. </li>
  1476. <li>If AUTO is used as device address, one address per measurement cycle will be tested. Depending on your device address
  1477. it may be necessary to execute "set update" several times to find your device.
  1478. </li>
  1479. <li>When using Firmata the I2C write/read delay attribute "i2c-config" of the FRM module can be set to any value.
  1480. </li>
  1481. </ul>
  1482. <br>
  1483. </ul>
  1484. =end html
  1485. =cut