51_I2C_TSL2561.pm 60 KB

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