52_I2C_MMA845X.pm 69 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889
  1. #################################################################################
  2. #
  3. # $Id: 52_I2C_MMA845X.pm 11171 2016-04-02 10:14:47Z jnsbyr $
  4. #
  5. # 52_I2C_MMA845X.pm
  6. #
  7. # (c) 2016 Copyright Jens Beyer < jensb at forum dot fhem dot de >
  8. #
  9. # This script is part of FHEM.
  10. #
  11. # This script is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # The GNU General Public License can be found at
  17. # http://www.gnu.org/copyleft/gpl.html.
  18. # A copy is found in the text file GPL.txt and important notices to the license
  19. # from the author is found in LICENSE.txt distributed with these scripts.
  20. #
  21. # This script is distributed in the hope that it will be useful,
  22. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. # GNU General Public License for more details.
  25. #
  26. # This copyright notice MUST APPEAR in all copies of the script!
  27. #
  28. #################################################################################
  29. # encoding: UTF-8 (äöüÄÖÜß§€)
  30. # -----------------------------------------------------------------------------
  31. =pod
  32. TODO
  33. - configurable max. measurement range?
  34. - return values with get even if using non-blocking I2C IO?
  35. - support power saving?
  36. =cut
  37. # -----------------------------------------------------------------------------
  38. package main;
  39. use strict;
  40. use warnings;
  41. # -----------------------------------------------------------------------------
  42. use constant {
  43. MMA845X_STATE_DEFINED => 'defined',
  44. MMA845X_STATE_INITIALIZED => 'initialized',
  45. MMA845X_STATE_CALIBRATING => 'calibrating',
  46. MMA845X_STATE_INVALID_DEVICE => 'invalid device',
  47. MMA845X_STATE_DISABLED => 'disabled',
  48. MMA845X_STATE_I2C_ERROR => 'I2C error',
  49. MMA845X_DEVICE_CODE_MMA8451 => 0x1A, # sensitivity 4096, FIFO, programmable orientation detection
  50. MMA845X_DEVICE_CODE_MMA8452 => 0x2A, # sensitivity 1024
  51. MMA845X_DEVICE_CODE_MMA8453 => 0x3A, # sensitivity 256
  52. MMA845X_ADDR_LOW => '0x1C',
  53. MMA845X_ADDR_HIGH => '0x1D',
  54. MMA845X_REGISTER_OUT_X_MSB => 0x01, # r/o
  55. MMA845X_REGISTER_WHO_AM_I => 0x0D, # r/o
  56. MMA845X_REGISTER_XYZ_DATA_CFG => 0x0E,
  57. MMA845X_REGISTER_HP_FILTER_CUTOFF => 0x0F,
  58. MMA845X_REGISTER_PL_STATUS => 0x10, # r/o
  59. MMA845X_REGISTER_PL_CFG => 0x11,
  60. MMA845X_REGISTER_PL_COUNT => 0x12,
  61. MMA845X_REGISTER_PL_BF_ZCOMP => 0x13,
  62. MMA845X_REGISTER_PL_THS_REG => 0x14,
  63. MMA845X_REGISTER_FF_MT_CFG => 0x15,
  64. MMA845X_REGISTER_FF_MT_SRC => 0x16, # r/o
  65. MMA845X_REGISTER_FF_MT_THS => 0x17,
  66. MMA845X_REGISTER_FF_MT_COUNT => 0x18,
  67. MMA845X_REGISTER_TRANSIENT_CFG => 0x1D,
  68. MMA845X_REGISTER_TRANSIENT_SRC => 0x1E, # r/o
  69. MMA845X_REGISTER_TRANSIENT_THS => 0x1F,
  70. MMA845X_REGISTER_TRANSIENT_COUNT => 0x20,
  71. MMA845X_REGISTER_PULSE_CFG => 0x21,
  72. MMA845X_REGISTER_PULSE_SRC => 0x22, # r/o
  73. MMA845X_REGISTER_PULSE_THSX => 0x23,
  74. MMA845X_REGISTER_PULSE_THSY => 0x24,
  75. MMA845X_REGISTER_PULSE_THSZ => 0x25,
  76. MMA845X_REGISTER_PULSE_TMLT => 0x26,
  77. MMA845X_REGISTER_PULSE_LTCY => 0x27,
  78. MMA845X_REGISTER_PULSE_WIND => 0x28,
  79. MMA845X_REGISTER_CTRL_REG1 => 0x2A,
  80. MMA845X_REGISTER_CTRL_REG4 => 0x2D,
  81. MMA845X_REGISTER_CTRL_REG5 => 0x2E,
  82. MMA845X_REGISTER_OFF_X => 0x2F,
  83. MMA845X_REGISTER_OFF_Y => 0x30,
  84. MMA845X_REGISTER_OFF_Z => 0x31,
  85. MMA845X_BIT_XYZ_DATA_CFG_HPF_OUT => 0x10,
  86. MMA845X_BIT_PL_CFG_PL_EN => 0x40,
  87. MMA845X_BIT_HP_FILTER_PULSE_BYP => 0x20,
  88. MMA845X_BIT_PL_STATUS_BAFRO => 0x01,
  89. MMA845X_BIT_PL_STATUS_LO => 0x40,
  90. MMA845X_BIT_PL_STATUS_NEWLP => 0x80,
  91. MMA845X_BIT_FF_MT_CFG_EFE_X => 0x08,
  92. MMA845X_BIT_FF_MT_CFG_EFE_Y => 0x10,
  93. MMA845X_BIT_FF_MT_CFG_EFE_Z => 0x20,
  94. MMA845X_BIT_FF_MT_CFG_OAE => 0x40,
  95. MMA845X_BIT_FF_MT_CFG_ELE => 0x80,
  96. MMA845X_BIT_FF_MT_SRC_POL_X => 0x01,
  97. MMA845X_BIT_FF_MT_SRC_AX_X => 0x02,
  98. MMA845X_BIT_FF_MT_SRC_POL_Y => 0x04,
  99. MMA845X_BIT_FF_MT_SRC_AX_Y => 0x08,
  100. MMA845X_BIT_FF_MT_SRC_POL_Z => 0x10,
  101. MMA845X_BIT_FF_MT_SRC_AX_Z => 0x20,
  102. MMA845X_BIT_FF_MT_SRC_EA => 0x80,
  103. MMA845X_BIT_TRANSIENT_CFG_HPF_BYP => 0x01,
  104. MMA845X_BIT_TRANSIENT_CFG_EFE_X => 0x02,
  105. MMA845X_BIT_TRANSIENT_CFG_EFE_Y => 0x04,
  106. MMA845X_BIT_TRANSIENT_CFG_EFE_Z => 0x08,
  107. MMA845X_BIT_TRANSIENT_CFG_ELE => 0x10,
  108. MMA845X_BIT_TRANSIENT_SRC_POL_X => 0x01,
  109. MMA845X_BIT_TRANSIENT_SRC_AX_X => 0x02,
  110. MMA845X_BIT_TRANSIENT_SRC_POL_Y => 0x04,
  111. MMA845X_BIT_TRANSIENT_SRC_AX_Y => 0x08,
  112. MMA845X_BIT_TRANSIENT_SRC_POL_Z => 0x10,
  113. MMA845X_BIT_TRANSIENT_SRC_AX_Z => 0x20,
  114. MMA845X_BIT_TRANSIENT_SRC_EA => 0x40,
  115. MMA845X_BIT_PULSE_CFG_EFE_XS => 0x01,
  116. MMA845X_BIT_PULSE_CFG_EFE_XD => 0x02,
  117. MMA845X_BIT_PULSE_CFG_EFE_YS => 0x04,
  118. MMA845X_BIT_PULSE_CFG_EFE_YD => 0x08,
  119. MMA845X_BIT_PULSE_CFG_EFE_ZS => 0x10,
  120. MMA845X_BIT_PULSE_CFG_EFE_ZD => 0x20,
  121. MMA845X_BIT_PULSE_CFG_ELE => 0x40,
  122. MMA845X_BIT_PULSE_CFG_DPA => 0x80,
  123. MMA845X_BIT_PULSE_SRC_POL_X => 0x01,
  124. MMA845X_BIT_PULSE_SRC_POL_Y => 0x02,
  125. MMA845X_BIT_PULSE_SRC_POL_Z => 0x04,
  126. MMA845X_BIT_PULSE_SRC_DPE => 0x08,
  127. MMA845X_BIT_PULSE_SRC_AX_X => 0x10,
  128. MMA845X_BIT_PULSE_SRC_AX_Y => 0x20,
  129. MMA845X_BIT_PULSE_SRC_AX_Z => 0x40,
  130. MMA845X_BIT_PULSE_SRC_EA => 0x80,
  131. MMA845X_BIT_CTRL_REG1_ACTIVE => 0x01,
  132. MMA845X_BIT_CTRL_REG1_LNOISE => 0x04,
  133. # CTRL_REG4
  134. MMA845X_BIT_INT_EN_FF_MT => 0x04,
  135. MMA845X_BIT_INT_EN_PULSE => 0x08,
  136. MMA845X_BIT_INT_EN_LNDPRT => 0x10,
  137. MMA845X_BIT_INT_EN_TRANS => 0x20,
  138. # CTRL_REG5
  139. MMA845X_BIT_INT_CFG_FF_MT => 0x04,
  140. MMA845X_BIT_INT_CFG_PULSE => 0x08,
  141. MMA845X_BIT_INT_CFG_LNDPRT => 0x10,
  142. MMA845X_BIT_INT_CFG_TRANS => 0x20,
  143. # PL_STATUS
  144. MMA845X_BITS_PL_LAPO_PU => 0x00,
  145. MMA845X_BITS_PL_LAPO_PD => 0x02,
  146. MMA845X_BITS_PL_LAPO_LR => 0x04,
  147. MMA845X_BITS_PL_LAPO_LL => 0x06,
  148. # CTRL_REG1
  149. MMA845X_BITS_ODR_800HZ => 0x00, # default
  150. MMA845X_BITS_ODR_400HZ => 0x08,
  151. MMA845X_BITS_ODR_200HZ => 0x10,
  152. MMA845X_BITS_ODR_100HZ => 0x18,
  153. MMA845X_BITS_ODR_50HZ => 0x20,
  154. MMA845X_BITS_ODR_12_5HZ => 0x28,
  155. MMA845X_BITS_ODR_6_25HZ => 0x30,
  156. MMA845X_BITS_ODR_1_56HZ => 0x38,
  157. # XYZ_DATA
  158. MMA845X_BITS_FS_2G => 0x00, # default
  159. MMA845X_BITS_FS_4G => 0x01,
  160. MMA845X_BITS_FS_8G => 0x02,
  161. MMA845X_POLLING_INTERVAL_DEFAULT => 10, # [s]
  162. };
  163. my %MMA845X_ADDRESSES = (
  164. "0x1c" => MMA845X_ADDR_LOW,
  165. "0x1d" => MMA845X_ADDR_HIGH,
  166. );
  167. # -----------------------------------------------------------------------------
  168. =item I2C_MMA845X_Initialize()
  169. Parameters:
  170. $hash: hash reference of device instance
  171. Returns: nothing
  172. =cut
  173. sub I2C_MMA845X_Initialize($) {
  174. my ($hash) = @_;
  175. $hash->{AttrFn} = 'I2C_MMA845X_Attr';
  176. $hash->{DefFn} = 'I2C_MMA845X_Define';
  177. $hash->{InitFn} = 'I2C_MMA845X_Init';
  178. $hash->{I2CRecFn} = 'I2C_MMA845X_I2CRec';
  179. $hash->{SetFn} = 'I2C_MMA845X_Set';
  180. $hash->{GetFn} = 'I2C_MMA845X_Get';
  181. $hash->{UndefFn} = 'I2C_MMA845X_Undef';
  182. $hash->{AttrList} = 'IODev pollInterval pollAccelerations:0,1 pollOrientation:0,1 pollEventSources:0,1 '
  183. . 'outputDataRate:1.56,6.25,12.5,50,100,200,400,800 '
  184. . 'highPass:multiple-strict,outputData,jolt,pulse highPassCutoffFrequency:0,1,2,3 '
  185. . 'orientationDetection:0,1 orientationInterrupt:0,1,2 '
  186. . 'orientationDebounce orientationZLockThreshold orientationBFTripAngleThreshold orientationPLTripAngleHysteresis orientationPLTripAngleThreshold '
  187. . 'motionEvent:multiple-strict,X,Y,Z motionEventLatch:0,1 motionInterrupt:0,1,2 '
  188. . 'motionMode:motion,freefall motionThreshold motionDebounce '
  189. . 'joltEvent:multiple-strict,X,Y,Z joltEventLatch:0,1 joltInterrupt:0,1,2 '
  190. . 'joltThreshold joltDebounce '
  191. . 'pulseEvent:multiple-strict,XS,XD,YS,YD,ZS,ZD pulseEventLatch:0,1 pulseInterrupt:0,1,2 '
  192. . 'pulseThresholdX pulseThresholdY pulseThresholdZ pulseWindow pulseLatency pulseWindow2 '
  193. . 'disable:0,1 '
  194. . $readingFnAttributes;
  195. # device power on defaults
  196. $hash->{OFF_X} = 0;
  197. $hash->{OFF_Y} = 0;
  198. $hash->{OFF_Z} = 0;
  199. $hash->{XYZ_DATA_CFG} = 0;
  200. }
  201. # -----------------------------------------------------------------------------
  202. =item I2C_MMA845X_Attr()
  203. Parameters:
  204. @args: array of parameters
  205. Returns: undef on success or string with error message
  206. =cut
  207. sub I2C_MMA845X_Attr (@) {
  208. my ($cmd, $name, $attr, $val) = @_;
  209. my $hash = $defs{$name};
  210. my $result = undef;
  211. if ($cmd eq 'set') {
  212. if ($attr eq 'pollInterval') {
  213. my $pollInterval = (defined($val) && looks_like_number($val) && $val >= 0) ? $val : -1;
  214. if ($pollInterval > 0) {
  215. # start new measurement cycle
  216. RemoveInternalTimer($hash);
  217. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  218. } elsif ($pollInterval < 0) {
  219. $result = 'invalid polling interval, must be an number >= 0 [seconds]';
  220. }
  221. }
  222. elsif ($attr eq 'highPassCutoffFrequency') {
  223. my $cutoffFrequency = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 3) ? $val : -1;
  224. if ($cutoffFrequency >= 0) {
  225. # force device reinit
  226. $hash->{MODEL} = undef;
  227. RemoveInternalTimer($hash);
  228. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  229. } elsif ($cutoffFrequency < 0) {
  230. $result = 'invalid high-pass cutoff frequency selector, must be an integer number between 0 (higher frequency) and 3 (lower frequency)';
  231. }
  232. }
  233. elsif ($attr eq 'outputDataRate' || $attr eq 'highPass' ||
  234. $attr eq 'orientationDetection' || $attr eq 'orientationInterrupt' ||
  235. $attr eq 'pulseEvent' || $attr eq 'pulseEventLatch' || $attr eq 'pulseInterrupt' ||
  236. $attr eq 'motionEvent' || $attr eq 'motionEventLatch' || $attr eq 'motionInterrupt' || $attr eq 'motionMode' ||
  237. $attr eq 'joltEvent' || $attr eq 'joltEventLatch' || $attr eq 'joltInterrupt') {
  238. # cleanup readings
  239. if ($attr eq 'orientationDetection' && (!defined($val) || $val eq '0')) {
  240. delete $hash->{READINGS}{orientation};
  241. }
  242. elsif ($attr eq 'pulseEvent' && (!defined($val) || $val eq '1')) {
  243. delete $hash->{READINGS}{pulseEvent};
  244. }
  245. elsif ($attr eq 'motionEvent' && (!defined($val) || $val eq '1')) {
  246. delete $hash->{READINGS}{motionEvent};
  247. }
  248. elsif ($attr eq 'joltEvent' && (!defined($val) || $val eq '1')) {
  249. delete $hash->{READINGS}{joltEvent};
  250. }
  251. # force device reinit
  252. $hash->{MODEL} = undef;
  253. RemoveInternalTimer($hash);
  254. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  255. }
  256. elsif ($attr eq 'orientationDebounce') {
  257. my $orientationDebounce = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1;
  258. if ($orientationDebounce >= 0) {
  259. # force device reinit
  260. $hash->{MODEL} = undef;
  261. RemoveInternalTimer($hash);
  262. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  263. } elsif ($orientationDebounce < 0) {
  264. $result = 'invalid orientation debounce duration, must be an integer number between 0 and 255 [~ms]';
  265. }
  266. }
  267. elsif ($attr eq 'orientationZLockThreshold') {
  268. my $orientationZLockThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 7) ? $val : -1;
  269. if ($orientationZLockThreshold >= 0) {
  270. # force device reinit
  271. $hash->{MODEL} = undef;
  272. RemoveInternalTimer($hash);
  273. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  274. } elsif ($orientationZLockThreshold < 0) {
  275. $result = 'invalid orientation Z lockout threshold, must be an integer number between 0 and 7 [14 ... 42°]';
  276. }
  277. }
  278. elsif ($attr eq 'orientationBFTripAngleThreshold') {
  279. my $orientationBFTripAngleThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 3) ? $val : -1;
  280. if ($orientationBFTripAngleThreshold >= 0) {
  281. # force device reinit
  282. $hash->{MODEL} = undef;
  283. RemoveInternalTimer($hash);
  284. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  285. } elsif ($orientationBFTripAngleThreshold < 0) {
  286. $result = 'invalid orientation back/front trip angle threshold, must be an integer number between 0 and 3 [65 ... 80°]';
  287. }
  288. }
  289. elsif ($attr eq 'orientationPLTripAngleHysteresis') {
  290. my $orientationPLTripAngleHysteresis = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 7) ? $val : -1;
  291. if ($orientationPLTripAngleHysteresis >= 0) {
  292. # force device reinit
  293. $hash->{MODEL} = undef;
  294. RemoveInternalTimer($hash);
  295. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  296. } elsif ($orientationPLTripAngleHysteresis < 0) {
  297. $result = 'invalid orientation portrait/landscape trip angle hysteresis, must be an integer number between 0 and 7 [±0 ... ±14°]';
  298. }
  299. }
  300. elsif ($attr eq 'orientationPLTripAngleThreshold') {
  301. my $orientationPLTripAngleThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 31) ? $val : -1;
  302. if ($orientationPLTripAngleThreshold >= 0) {
  303. # force device reinit
  304. $hash->{MODEL} = undef;
  305. RemoveInternalTimer($hash);
  306. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  307. } elsif ($orientationPLTripAngleThreshold < 0) {
  308. $result = 'invalid orientation portrait/landscape trip angle threshold, must be an integer number between 0 and 31 [0 ... 75°]';
  309. }
  310. }
  311. elsif ($attr eq 'pulseThresholdX' || $attr eq 'pulseThresholdY' || $attr eq 'pulseThresholdZ') {
  312. my $pulseThreshold = (defined($val) && looks_like_number($val) && $val > 0 && $val <= 63) ? $val : -1;
  313. if ($pulseThreshold > 0) {
  314. # force device reinit
  315. $hash->{MODEL} = undef;
  316. RemoveInternalTimer($hash);
  317. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  318. } elsif ($pulseThreshold < 0) {
  319. $result = 'invalid pulse threshold, must be an integer number between 1 and 63 [0.063g]';
  320. }
  321. }
  322. elsif ($attr eq 'pulseWindow' || $attr eq 'pulseWindow2') {
  323. my $pulseThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1;
  324. if ($pulseThreshold >= 0) {
  325. # force device reinit
  326. $hash->{MODEL} = undef;
  327. RemoveInternalTimer($hash);
  328. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  329. } elsif ($pulseThreshold < 0) {
  330. $result = 'invalid pulse window duration, must be an integer number between 0 and 255 [~ms]';
  331. }
  332. }
  333. elsif ($attr eq 'pulseLatency') {
  334. my $pulseThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1;
  335. if ($pulseThreshold >= 0) {
  336. # force device reinit
  337. $hash->{MODEL} = undef;
  338. RemoveInternalTimer($hash);
  339. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  340. } elsif ($pulseThreshold < 0) {
  341. $result = 'invalid pulse latency duration, must be an integer number between 0 and 255 [~ms]';
  342. }
  343. }
  344. elsif ($attr eq 'motionThreshold') {
  345. my $motionThreshold = (defined($val) && looks_like_number($val) && $val > 0 && $val <= 63) ? $val : -1;
  346. if ($motionThreshold > 0) {
  347. # force device reinit
  348. $hash->{MODEL} = undef;
  349. RemoveInternalTimer($hash);
  350. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  351. } elsif ($motionThreshold < 0) {
  352. $result = 'invalid motion threshold, must be an integer number between 1 and 63 [0.063g]';
  353. }
  354. }
  355. elsif ($attr eq 'motionDebounce') {
  356. my $motionDebounce = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1;
  357. if ($motionDebounce >= 0) {
  358. # force device reinit
  359. $hash->{MODEL} = undef;
  360. RemoveInternalTimer($hash);
  361. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  362. } elsif ($motionDebounce < 0) {
  363. $result = 'invalid motion debounce duration, must be an integer number between 0 and 255 [~ms]';
  364. }
  365. }
  366. elsif ($attr eq 'joltThreshold') {
  367. my $joltThreshold = (defined($val) && looks_like_number($val) && $val > 0 && $val <= 63) ? $val : -1;
  368. if ($joltThreshold > 0) {
  369. # force device reinit
  370. $hash->{MODEL} = undef;
  371. RemoveInternalTimer($hash);
  372. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  373. } elsif ($joltThreshold < 0) {
  374. $result = 'invalid jolt threshold, must be an integer number between 1 and 63 [0.063g]';
  375. }
  376. }
  377. elsif ($attr eq 'joltDebounce') {
  378. my $joltDebounce = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1;
  379. if ($joltDebounce >= 0) {
  380. # force device reinit
  381. $hash->{MODEL} = undef;
  382. RemoveInternalTimer($hash);
  383. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  384. } elsif ($joltDebounce < 0) {
  385. $result = 'invalid jolt debounce duration, must be an integer number between 0 and 255 [~ms]';
  386. }
  387. }
  388. elsif ($attr eq 'disable') {
  389. my $disable = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 1) ? $val : -1;
  390. if ($disable > 0) {
  391. # stop timer and force reinit at next start
  392. $hash->{MODEL} = undef;
  393. RemoveInternalTimer($hash);
  394. readingsSingleUpdate($hash, 'state', MMA845X_STATE_DISABLED, 1);
  395. } elsif ($disable == 0) {
  396. # restart timer
  397. RemoveInternalTimer($hash);
  398. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  399. } elsif ($disable < 0) {
  400. $result = 'invalid disable value, must be 0 or 1';
  401. }
  402. }
  403. } elsif ($cmd eq 'del') {
  404. if ($attr eq 'disable') {
  405. # restart timer
  406. RemoveInternalTimer($hash);
  407. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  408. }
  409. elsif ($attr eq 'orientationDetection') {
  410. delete $hash->{READINGS}{orientation};
  411. }
  412. elsif ($attr eq 'pulseEvent') {
  413. delete $hash->{READINGS}{pulseEvent};
  414. }
  415. elsif ($attr eq 'motionEvent') {
  416. delete $hash->{READINGS}{motionEvent};
  417. }
  418. elsif ($attr eq 'joltEvent') {
  419. delete $hash->{READINGS}{joltEvent};
  420. }
  421. }
  422. return $result;
  423. }
  424. # -----------------------------------------------------------------------------
  425. =item I2C_MMA845X_Define()
  426. Parameters:
  427. $hash: hash reference of device instance
  428. $def: string device definition (device name, module name, I2C address)
  429. Returns: undef on success or string with error message
  430. =cut
  431. sub I2C_MMA845X_Define($$) {
  432. my ($hash, $def) = @_;
  433. my $name = $hash->{NAME};
  434. my @a = split('[ \t][ \t]*', $def);
  435. my $result = undef;
  436. if (@a == 3) {
  437. my $address = lc($a[2]);
  438. $address = $MMA845X_ADDRESSES{$address};
  439. if (defined($address)) {
  440. $hash->{I2C_Address} = hex($address);
  441. readingsSingleUpdate($hash, 'state', MMA845X_STATE_DEFINED, 1);
  442. } else {
  443. $result = "I2C_MMA845X: invalid I2C address, must be one of " . join(', ', keys %MMA845X_ADDRESSES);
  444. }
  445. } elsif (@a < 3) {
  446. $result = "I2C_MMA845X: missing parameters, usage: define <name> I2C_MMA845X <I2C address>";
  447. } else {
  448. $result = "I2C_MMA845X: too many parameters in define";
  449. }
  450. if (!defined($result)) {
  451. # create default attributes
  452. if (AttrVal($name, 'pollInterval', '?') eq '?') {
  453. $attr{$name}{pollInterval} = MMA845X_POLLING_INTERVAL_DEFAULT;
  454. }
  455. # init immediately if FHEM is already up
  456. if ($main::init_done) {
  457. eval { I2C_MMA845X_Init($hash, undef); };
  458. }
  459. }
  460. return $result;
  461. }
  462. # -----------------------------------------------------------------------------
  463. =item I2C_MMA845X_Init()
  464. Parameters:
  465. $hash: hash reference of device instance
  466. \@args: string array reference of initialization parameters
  467. Returns: undef on success
  468. =cut
  469. sub I2C_MMA845X_Init($$) {
  470. my ($hash, $args) = @_;
  471. AssignIoPort($hash);
  472. # reset state
  473. $hash->{MODEL} = undef;
  474. $hash->{XYZ_DATA_CFG} = 0;
  475. $hash->{I2C_Blocking} = 0;
  476. # start timer
  477. RemoveInternalTimer($hash);
  478. InternalTimer(gettimeofday() + 8, 'I2C_MMA845X_Poll', $hash, 0);
  479. return undef;
  480. }
  481. # -----------------------------------------------------------------------------
  482. =item I2C_MMA845X_Poll()
  483. Parameters:
  484. $hash: hash reference of device instance
  485. $cmd: optional string, may be one of "pulseSource"
  486. Returns: undef on success
  487. =cut
  488. sub I2C_MMA845X_Poll($;$) {
  489. my ($hash, $cmd) = @_;
  490. my $name = $hash->{NAME};
  491. # disable timer
  492. if (!defined($cmd)) {
  493. RemoveInternalTimer($hash);
  494. }
  495. my $pollDelay = AttrVal($hash->{NAME}, "pollInterval", MMA845X_POLLING_INTERVAL_DEFAULT) ;
  496. my $state = ReadingsVal($name, 'state', '?');
  497. if (!AttrVal($hash->{NAME}, "disable", 0)) {
  498. if ($state eq MMA845X_STATE_INVALID_DEVICE || $state eq MMA845X_STATE_DISABLED || $state eq MMA845X_STATE_I2C_ERROR) {
  499. # error state, clear model registration to force new setup
  500. $hash->{MODEL} = undef;
  501. }
  502. if (!defined($hash->{MODEL})) {
  503. # reset state
  504. if ($state ne MMA845X_STATE_DEFINED && $state ne MMA845X_STATE_INVALID_DEVICE) {
  505. readingsSingleUpdate($hash, 'state', MMA845X_STATE_DEFINED, 1);
  506. }
  507. $hash->{setup} = 0;
  508. $hash->{I2C_PendingRequests} = 0;
  509. # detect device model and IO mode
  510. $hash->{operationInProgress} = 1;
  511. I2C_MMA845X_Read($hash, MMA845X_REGISTER_WHO_AM_I, 1);
  512. delete $hash->{operationInProgress};
  513. }
  514. if (defined($hash->{setup})) {
  515. if ($hash->{I2C_Blocking}) {
  516. # start/continue with blocking setup
  517. I2C_MMA845X_Setup($hash);
  518. # yield to FHEM for 100 ms
  519. $pollDelay = 0.1;
  520. } else {
  521. # monitor non-blocking setup
  522. $hash->{I2C_PendingRequests}++;
  523. if ($hash->{I2C_PendingRequests} > 10) {
  524. # non-blocking setup timeout
  525. readingsSingleUpdate($hash, 'state', MMA845X_STATE_I2C_ERROR, 1);
  526. }
  527. }
  528. } else {
  529. # polling
  530. if (defined($hash->{I2C_PendingRequests})) {
  531. $hash->{I2C_PendingRequests}++;
  532. } else {
  533. $hash->{I2C_PendingRequests} = 1;
  534. }
  535. # get acceleration values
  536. if (AttrVal($name, 'pollAccelerations', 1) || $state eq MMA845X_STATE_CALIBRATING || (defined($cmd) && $cmd eq 'accelerations')) {
  537. I2C_MMA845X_Read($hash, MMA845X_REGISTER_OUT_X_MSB, 6);
  538. }
  539. # get orientation
  540. if ((AttrVal($name, 'pollOrientation', 1) && $state ne MMA845X_STATE_CALIBRATING) || (defined($cmd) && $cmd eq 'orientation')) {
  541. my $orientationDetection = AttrVal($name, 'orientationDetection', 0);
  542. if ($orientationDetection) {
  543. I2C_MMA845X_Read($hash, MMA845X_REGISTER_PL_STATUS, 1);
  544. }
  545. }
  546. # get event sources
  547. if ((AttrVal($name, 'pollEventSources', 1) && $state ne MMA845X_STATE_CALIBRATING) || (defined($cmd) && $cmd eq 'eventSource')) {
  548. # get motion source
  549. my $motionEvent = AttrVal($name, 'motionEvent', '');
  550. if (length($motionEvent) > 0 && $motionEvent ne '1') {
  551. I2C_MMA845X_Read($hash, MMA845X_REGISTER_FF_MT_SRC, 1);
  552. }
  553. # get jolt source
  554. my $joltEvent = AttrVal($name, 'joltEvent', '');
  555. if (length($joltEvent) > 0 && $joltEvent ne '1') {
  556. I2C_MMA845X_Read($hash, MMA845X_REGISTER_TRANSIENT_SRC, 1);
  557. }
  558. # get pulse source
  559. my $pulseEvent = AttrVal($name, 'pulseEvent', '');
  560. if (length($pulseEvent) > 0 && $pulseEvent ne '1') {
  561. I2C_MMA845X_Read($hash, MMA845X_REGISTER_PULSE_SRC, 1);
  562. }
  563. }
  564. # monitor non-blocking read of acceleration values
  565. if (!$hash->{I2C_Blocking} && $hash->{I2C_PendingRequests} >= 3) {
  566. # non-blocking read timeout
  567. readingsSingleUpdate($hash, 'state', MMA845X_STATE_I2C_ERROR, 1);
  568. }
  569. }
  570. }
  571. elsif ($state ne MMA845X_STATE_DISABLED) {
  572. readingsSingleUpdate($hash, 'state', MMA845X_STATE_DISABLED, 1);
  573. }
  574. # schedule next poll
  575. if (!defined($cmd)) {
  576. #Log3($name, 5, "I2C_MMA845X_Poll: $pollDelay s");
  577. if ($pollDelay > 0) {
  578. InternalTimer(gettimeofday() + $pollDelay, 'I2C_MMA845X_Poll', $hash, 0);
  579. }
  580. }
  581. return undef;
  582. }
  583. # -----------------------------------------------------------------------------
  584. =item I2C_MMA845X_I2CRec()
  585. Parameters:
  586. $hash: hash reference of device instance
  587. $clientmsg: hash reference from I2C receiver
  588. Returns: nothing
  589. =cut
  590. sub I2C_MMA845X_I2CRec($) {
  591. my ($hash, $clientmsg) = @_;
  592. my $name = $hash->{NAME};
  593. # copy all elements of clientmsg starting with IODev name into internal (debug data)
  594. my $phash = $hash->{IODev};
  595. my $pname = $phash->{NAME};
  596. while (my ($k, $v) = each %$clientmsg) {
  597. $hash->{$k} = $v if $k =~ /^$pname/;
  598. }
  599. # last send was OK?
  600. if ($clientmsg->{direction} && $clientmsg->{reg} && $pname && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") {
  601. # data was received?
  602. if ($clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received})) {
  603. my $register = $clientmsg->{reg} & 0xFF;
  604. Log3($hash, 5, "$name RX register $register, $clientmsg->{nbyte} bytes: $clientmsg->{received}");
  605. my $byte = undef;
  606. my $word = undef;
  607. my @raw = split(" ", $clientmsg->{received});
  608. if ($clientmsg->{nbyte} == 1) {
  609. $byte = $raw[0];
  610. } elsif ($clientmsg->{nbyte} == 2) {
  611. $word = $raw[0] << 8 | $raw[1];
  612. }
  613. # process reply
  614. if ($register == MMA845X_REGISTER_WHO_AM_I) {
  615. I2C_MMA845X_WHO_AM_I($hash, $byte);
  616. } elsif ($register == MMA845X_REGISTER_OUT_X_MSB && $clientmsg->{nbyte} == 6) {
  617. I2C_MMA845X_OUT_XYZ($hash, \@raw);
  618. $hash->{I2C_PendingRequests} = 0;
  619. } elsif ($register == MMA845X_REGISTER_PL_STATUS) {
  620. I2C_MMA845X_PL_STATUS($hash, $byte);
  621. $hash->{I2C_PendingRequests} = 0;
  622. } elsif ($register == MMA845X_REGISTER_FF_MT_SRC) {
  623. I2C_MMA845X_FF_MT_SRC($hash, $byte);
  624. $hash->{I2C_PendingRequests} = 0;
  625. } elsif ($register == MMA845X_REGISTER_TRANSIENT_SRC) {
  626. I2C_MMA845X_TRANSIENT_SRC($hash, $byte);
  627. $hash->{I2C_PendingRequests} = 0;
  628. } elsif ($register == MMA845X_REGISTER_PULSE_SRC) {
  629. I2C_MMA845X_PULSE_SRC($hash, $byte);
  630. $hash->{I2C_PendingRequests} = 0;
  631. } else {
  632. Log3($hash, 2, "$name RX register $register not implemented");
  633. }
  634. }
  635. }
  636. }
  637. # -----------------------------------------------------------------------------
  638. =item I2C_MMA845X_WHO_AM_I()
  639. Parameters:
  640. $hash: hash reference of device instance
  641. $byte: I2C register value
  642. Returns: nothing
  643. =cut
  644. sub I2C_MMA845X_WHO_AM_I($$) {
  645. my ($hash, $byte) = @_;
  646. my $name = $hash->{NAME};
  647. if ($byte == MMA845X_DEVICE_CODE_MMA8451 || $byte == MMA845X_DEVICE_CODE_MMA8452 || $byte == MMA845X_DEVICE_CODE_MMA8453) {
  648. # save model code
  649. $hash->{MODEL} = $byte;
  650. # save IO mode
  651. if (defined($hash->{operationInProgress})) {
  652. $hash->{I2C_Blocking} = 1;
  653. }
  654. if (defined($hash->{setup} && !$hash->{I2C_Blocking})) {
  655. # start/continue with non-blocking setup
  656. I2C_MMA845X_Setup($hash);
  657. # perform read to ensure non-blocking setup stage was completed
  658. if (defined($hash->{setup})) {
  659. I2C_MMA845X_Read($hash, MMA845X_REGISTER_WHO_AM_I, 1);
  660. }
  661. }
  662. } else {
  663. Log3($hash, 1, "$name I2C device at address " . $hash->{I2C_Address} . " is not MMA845X compatible");
  664. $hash->{MODEL} = undef; # incompatible
  665. if (ReadingsVal($name, 'state', '?') ne MMA845X_STATE_INVALID_DEVICE) {
  666. readingsSingleUpdate($hash, 'state', MMA845X_STATE_INVALID_DEVICE, 1);
  667. }
  668. }
  669. }
  670. # -----------------------------------------------------------------------------
  671. =item I2C_MMA845X_Setup()
  672. Parameters:
  673. $hash: hash reference of device instance
  674. Returns: nothing
  675. =cut
  676. sub I2C_MMA845X_Setup($) {
  677. my ($hash) = @_;
  678. my $name = $hash->{NAME};
  679. my $stage = 0;
  680. if (defined($hash->{setup}) && $hash->{setup} >= 0 && $hash->{setup} <= 6) {
  681. $stage = $hash->{setup};
  682. }
  683. #Log3($hash, 1, "$name setup stage $stage");
  684. if ($stage == 0)
  685. {
  686. # disable measurement
  687. I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, 0);
  688. # activate 4 g full scale range and optionally the high-pass filter
  689. $hash->{XYZ_DATA_CFG} = MMA845X_BITS_FS_4G;
  690. $hash->{XYZ_DATA_CFG} |= MMA845X_BIT_XYZ_DATA_CFG_HPF_OUT if (index(AttrVal($name, 'highPass', ''), 'outputData') >= 0);
  691. I2C_MMA845X_Write($hash, MMA845X_REGISTER_XYZ_DATA_CFG, $hash->{XYZ_DATA_CFG});
  692. # set the high-pass filter cutoff frequency and optionally disable the high-pass filter
  693. $hash->{HP_FILTER_CUTOFF} = AttrVal($name, 'highPassCutoffFrequency', 0);
  694. $hash->{HP_FILTER_CUTOFF} |= MMA845X_BIT_HP_FILTER_PULSE_BYP if (index(AttrVal($name, 'highPass', 'pulse'), 'pulse') < 0);
  695. I2C_MMA845X_Write($hash, MMA845X_REGISTER_HP_FILTER_CUTOFF, $hash->{HP_FILTER_CUTOFF});
  696. }
  697. elsif ($stage == 1)
  698. {
  699. # prepare control registers 4 and 5 (interrupt configuration)
  700. $hash->{CTRL_REG4} = 0;
  701. $hash->{CTRL_REG5} = 0;
  702. # enable orientation detection
  703. my $orientatonEvent = AttrVal($name, 'orientatonEvent', 1);
  704. if ($orientatonEvent) {
  705. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_CFG, MMA845X_BIT_PL_CFG_PL_EN);
  706. # configure interrupt
  707. my $orientationInterrupt = AttrVal($name, 'orientationInterrupt', 0);
  708. $hash->{CTRL_REG4} |= MMA845X_BIT_INT_EN_LNDPRT if ($orientationInterrupt);
  709. $hash->{CTRL_REG5} |= MMA845X_BIT_INT_CFG_LNDPRT if ($orientationInterrupt == 1);
  710. # configure parameters
  711. my $orientationDebounce = AttrVal($name, 'orientationDebounce', 100); # 500 ms default with MODS=Normal ODR=200Hz = 5 ms steps
  712. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_COUNT, $orientationDebounce);
  713. my $orientationZLockThreshold = AttrVal($name, 'orientationZLockThreshold', 4); # Z 29° default
  714. my $orientationBFTripAngleThreshold = AttrVal($name, 'orientationBFTripAngleThreshold', 1) << 6; # Z < 75° or Z > 285° default
  715. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_BF_ZCOMP, $orientationBFTripAngleThreshold + $orientationZLockThreshold);
  716. my $orientationPLTripAngleHysteresis = AttrVal($name, 'orientationPLTripAngleHysteresis', 4); # ±14° default
  717. my $orientationPLTripAngleThreshold = AttrVal($name, 'orientationPLTripAngleThreshold', 0x10) << 3; # 45° default
  718. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_THS_REG, $orientationPLTripAngleHysteresis + $orientationPLTripAngleThreshold);
  719. } else {
  720. # disable orientation detection
  721. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_CFG, 0);
  722. }
  723. }
  724. elsif ($stage == 2)
  725. {
  726. # enable freefall/motion detection
  727. my $motionEvent = AttrVal($name, 'motionEvent', '');
  728. if (length($motionEvent) > 0 && $motionEvent ne '1') {
  729. my $motionCfg = 0;
  730. if (index($motionEvent, 'X') >= 0) {
  731. $motionCfg |= MMA845X_BIT_FF_MT_CFG_EFE_X;
  732. }
  733. if (index($motionEvent, 'Y') >= 0) {
  734. $motionCfg |= MMA845X_BIT_FF_MT_CFG_EFE_Y;
  735. }
  736. if (index($motionEvent, 'Z') >= 0) {
  737. $motionCfg |= MMA845X_BIT_FF_MT_CFG_EFE_Z;
  738. }
  739. if ($motionCfg) {
  740. # configure motion detection
  741. if (AttrVal($name, 'motionEventLatch', 0)) { # default: interrupt will be automatically reset
  742. $motionCfg |= MMA845X_BIT_FF_MT_CFG_ELE;
  743. }
  744. if (AttrVal($name, 'motionMode', 'motion') eq 'motion') {
  745. $motionCfg |= MMA845X_BIT_FF_MT_CFG_OAE;
  746. }
  747. I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_CFG, $motionCfg);
  748. # configure interrupt
  749. my $motionInterrupt = AttrVal($name, 'motionInterrupt', 0);
  750. $hash->{CTRL_REG4} |= MMA845X_BIT_INT_EN_FF_MT if ($motionInterrupt);
  751. $hash->{CTRL_REG5} |= MMA845X_BIT_INT_CFG_FF_MT if ($motionInterrupt == 1);
  752. # configure parameters
  753. my $motionThreshold = AttrVal($name, 'motionThreshold', 0x01); # 0.063g default
  754. I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_THS, $motionThreshold);
  755. my $motionDebounce = AttrVal($name, 'motionDebounce', 0x00); # 0 ms default with MODS=Normal ODR=200Hz = 5 ms steps
  756. I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_COUNT, $motionDebounce);
  757. } else {
  758. # disable motion detection
  759. I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_CFG, 0);
  760. }
  761. } else {
  762. # disable motion detection
  763. I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_CFG, 0);
  764. }
  765. }
  766. elsif ($stage == 3)
  767. {
  768. # enable jolt detection
  769. my $joltEvent = AttrVal($name, 'joltEvent', '');
  770. if (length($joltEvent) > 0 && $joltEvent ne '1') {
  771. my $joltCfg = 0;
  772. if (index($joltEvent, 'X') >= 0) {
  773. $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_EFE_X;
  774. }
  775. if (index($joltEvent, 'Y') >= 0) {
  776. $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_EFE_Y;
  777. }
  778. if (index($joltEvent, 'Z') >= 0) {
  779. $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_EFE_Z;
  780. }
  781. if ($joltCfg) {
  782. # configure jolt detection and optionally disable the high-pass filter
  783. $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_HPF_BYP if (index(AttrVal($name, 'highPass', 'jolt'), 'jolt') < 0);
  784. if (AttrVal($name, 'joltEventLatch', 0)) { # default: interrupt will be automatically reset
  785. $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_ELE;
  786. }
  787. I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_CFG, $joltCfg);
  788. # configure interrupt
  789. my $joltInterrupt = AttrVal($name, 'joltInterrupt', 0);
  790. $hash->{CTRL_REG4} |= MMA845X_BIT_INT_EN_TRANS if ($joltInterrupt);
  791. $hash->{CTRL_REG5} |= MMA845X_BIT_INT_CFG_TRANS if ($joltInterrupt == 1);
  792. # configure parameters
  793. my $joltThreshold = AttrVal($name, 'joltThreshold', 0x01); # 0.063g default
  794. I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_THS, $joltThreshold);
  795. my $joltDebounce = AttrVal($name, 'joltDebounce', 0x00); # 0 ms default with MODS=Normal ODR=200Hz = 5 ms steps
  796. I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_COUNT, $joltDebounce);
  797. } else {
  798. # disable jolt detection
  799. I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_CFG, 0);
  800. }
  801. } else {
  802. # disable jolt detection
  803. I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_CFG, 0);
  804. }
  805. }
  806. elsif ($stage == 4)
  807. {
  808. # enable pulse detection
  809. my $pulseEvent = AttrVal($name, 'pulseEvent', '');
  810. if (length($pulseEvent) > 0 && $pulseEvent ne '1') {
  811. my $pulseCfg = 0;
  812. if (index($pulseEvent, 'XS') >= 0) {
  813. $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_XS;
  814. }
  815. if (index($pulseEvent, 'XD') >= 0) {
  816. $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_XD;
  817. }
  818. if (index($pulseEvent, 'YS') >= 0) {
  819. $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_YS;
  820. }
  821. if (index($pulseEvent, 'YD') >= 0) {
  822. $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_YD;
  823. }
  824. if (index($pulseEvent, 'ZS') >= 0) {
  825. $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_ZS;
  826. }
  827. if (index($pulseEvent, 'ZD') >= 0) {
  828. $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_ZD;
  829. }
  830. if ($pulseCfg) {
  831. # configure pulse detection
  832. if (AttrVal($name, 'pulseEventLatch', 0)) { # default: interrupt will be automatically reset after latency duration
  833. $pulseCfg |= MMA845X_BIT_PULSE_CFG_ELE;
  834. }
  835. $pulseCfg |= MMA845X_BIT_PULSE_CFG_DPA; # enable single and double pulse detection abort on timing mismatch
  836. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_CFG, $pulseCfg);
  837. # configure interrupt
  838. my $pulseInterrupt = AttrVal($name, 'pulseInterrupt', 0);
  839. $hash->{CTRL_REG4} |= MMA845X_BIT_INT_EN_PULSE if ($pulseInterrupt);
  840. $hash->{CTRL_REG5} |= MMA845X_BIT_INT_CFG_PULSE if ($pulseInterrupt == 1);
  841. # configure parameters
  842. my $pulseThresholdX = AttrVal($name, 'pulseThresholdX', 0x10); # 1g default
  843. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_THSX, $pulseThresholdX);
  844. my $pulseThresholdY = AttrVal($name, 'pulseThresholdY', 0x10); # 1g default
  845. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_THSY, $pulseThresholdY);
  846. my $pulseThresholdZ = AttrVal($name, 'pulseThresholdZ', 0x30); # 3g default
  847. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_THSZ, $pulseThresholdZ);
  848. my $pulseWindow = AttrVal($name, 'pulseWindow', 0x30); # 60 ms default with MODS=Normal ODR=200Hz = 1.25 ms steps
  849. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_TMLT, $pulseWindow);
  850. my $pulseLatency = AttrVal($name, 'pulseLatency', 0x50); # 200 ms default with MODS=Normal ODR=200Hz = 2.5 ms steps
  851. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_LTCY, $pulseLatency);
  852. my $pulseWindow2 = AttrVal($name, 'pulseWindow2', 0x78); # 300 ms default with MODS=Normal ODR=200Hz = 2.5 ms steps
  853. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_WIND, $pulseWindow2);
  854. } else {
  855. # disable pulse detection
  856. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_CFG, 0);
  857. }
  858. } else {
  859. # disable pulse detection
  860. I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_CFG, 0);
  861. }
  862. }
  863. elsif ($stage == 5)
  864. {
  865. # write control registers 4 and 5 (interrupt configuration)
  866. I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG4, $hash->{CTRL_REG4});
  867. I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG5, $hash->{CTRL_REG5});
  868. # rewrite last calibration
  869. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_X, I2C_MMA845X_G_TO_OFF($hash, ReadingsVal($name, 'offX', 0)));
  870. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Y, I2C_MMA845X_G_TO_OFF($hash, ReadingsVal($name, 'offY', 0)));
  871. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Z, I2C_MMA845X_G_TO_OFF($hash, ReadingsVal($name, 'offZ', 0)));
  872. # activate measurement (low noise filter = max. 4g, MODS=Normal)
  873. $hash->{CTRL_REG1} = MMA845X_BIT_CTRL_REG1_ACTIVE | MMA845X_BIT_CTRL_REG1_LNOISE;
  874. my $outputDataRate = AttrVal($name, 'outputDataRate', 200);
  875. if ($outputDataRate == 1.56) {
  876. $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_1_56HZ;
  877. } elsif ($outputDataRate == 6.25) {
  878. $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_6_25HZ;
  879. } elsif ($outputDataRate == 12.5) {
  880. $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_12_5HZ;
  881. } elsif ($outputDataRate == 50) {
  882. $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_50HZ;
  883. } elsif ($outputDataRate == 100) {
  884. $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_100HZ;
  885. } elsif ($outputDataRate == 200) {
  886. $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_200HZ;
  887. } elsif ($outputDataRate == 400) {
  888. $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_400HZ;
  889. } else {
  890. $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_800HZ;
  891. }
  892. I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1});
  893. }
  894. elsif ($stage >= 6)
  895. {
  896. # setup completed
  897. delete $hash->{setup};
  898. $hash->{I2C_PendingRequests} = 0;
  899. readingsSingleUpdate($hash, 'state', MMA845X_STATE_INITIALIZED, 1);
  900. return;
  901. }
  902. # prepare next setup stage
  903. $stage++;
  904. $hash->{setup} = $stage;
  905. }
  906. # -----------------------------------------------------------------------------
  907. =item I2C_MMA845X_OUT_XYZ()
  908. Parameters:
  909. $hash: hash reference of device instance
  910. $bytes: byte array reference of I2C register values
  911. Returns: nothing
  912. =cut
  913. sub I2C_MMA845X_OUT_XYZ($$) {
  914. my ($hash, $bytes) = @_;
  915. my $name = $hash->{NAME};
  916. # extract sample values assuming max. resolution (CTRL_REG1:F_READ=0)
  917. readingsBeginUpdate($hash);
  918. for (my $i=0; $i<3; $i++) {
  919. my $sample = $$bytes[2*$i] << 8 | $$bytes[2*$i + 1]; # combine MSB/LSB
  920. $sample = ($sample >> 2) & 0x3FFF if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit adjust
  921. $sample = ($sample >> 4) & 0x0FFF if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit adjust
  922. $sample = ($sample >> 6) & 0x03FF if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit adjust
  923. if ($$bytes[2*$i] & 0x80) {
  924. $sample -= 0x4000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit 2's complement negative value transform
  925. $sample -= 0x1000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit 2's complement negative value transform
  926. $sample -= 0x0400 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit 2's complement negative value transform
  927. }
  928. my $range = 2 << ($hash->{XYZ_DATA_CFG} & 0x3); # [1 g = 9.80665 m/s2]
  929. $hash->{accelerations}[$i] = $range * $sample;
  930. $hash->{accelerations}[$i] /= 0x2000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit scale to [g]
  931. $hash->{accelerations}[$i] /= 0x0800 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit scale to [g]
  932. $hash->{accelerations}[$i] /= 0x0200 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit scale to [g]
  933. Log3($hash, 5, $hash->{NAME} . " i $i sample $sample range $range acceleration $hash->{accelerations}[$i]");
  934. readingsBulkUpdate($hash, "outX", sprintf('%0.3f', $hash->{accelerations}[$i])) if ($i == 0);
  935. readingsBulkUpdate($hash, "outY", sprintf('%0.3f', $hash->{accelerations}[$i])) if ($i == 1);
  936. readingsBulkUpdate($hash, "outZ", sprintf('%0.3f', $hash->{accelerations}[$i])) if ($i == 2);
  937. }
  938. readingsEndUpdate($hash, 1);
  939. # calibrate offset
  940. if (ReadingsVal($name, 'state', '?') eq MMA845X_STATE_CALIBRATING) {
  941. I2C_MMA845X_Calibrate($hash);
  942. }
  943. }
  944. # -----------------------------------------------------------------------------
  945. =item I2C_MMA845X_PL_STATUS()
  946. Parameters:
  947. $hash: hash reference of device instance
  948. $byte: I2C register value
  949. Returns: nothing
  950. =cut
  951. sub I2C_MMA845X_PL_STATUS($$) {
  952. my ($hash, $byte) = @_;
  953. my $name = $hash->{NAME};
  954. my $orientation = '';
  955. if ($byte & MMA845X_BIT_PL_STATUS_NEWLP) {
  956. my $lapo = $byte & MMA845X_BITS_PL_LAPO_LL;
  957. if ($lapo == MMA845X_BITS_PL_LAPO_PU) {
  958. $orientation .= 'PU';
  959. } elsif ($lapo == MMA845X_BITS_PL_LAPO_PD) {
  960. $orientation .= 'PD';
  961. } elsif ($lapo == MMA845X_BITS_PL_LAPO_LR) {
  962. $orientation .= 'LR';
  963. } else {
  964. $orientation .= 'LL';
  965. }
  966. if ($byte & MMA845X_BIT_PL_STATUS_BAFRO) {
  967. $orientation .= 'B';
  968. } else {
  969. $orientation .= 'F';
  970. }
  971. if ($byte & MMA845X_BIT_PL_STATUS_LO) {
  972. $orientation .= 'X';
  973. }
  974. if (ReadingsVal($name, 'orientation', '') ne $orientation) {
  975. readingsSingleUpdate($hash, 'orientation', $orientation, 1);
  976. }
  977. }
  978. }
  979. # -----------------------------------------------------------------------------
  980. =item I2C_MMA845X_PULSE_SRC()
  981. Parameters:
  982. $hash: hash reference of device instance
  983. $byte: I2C register value
  984. Returns: nothing
  985. =cut
  986. sub I2C_MMA845X_PULSE_SRC($$) {
  987. my ($hash, $byte) = @_;
  988. my $name = $hash->{NAME};
  989. my $eventSource = '';
  990. if ($byte & MMA845X_BIT_PULSE_SRC_EA) {
  991. if ($byte & MMA845X_BIT_PULSE_SRC_AX_X) {
  992. if ($byte & MMA845X_BIT_PULSE_SRC_POL_X) {
  993. $eventSource .= '-X';
  994. } else {
  995. $eventSource .= '+X';
  996. }
  997. if ($byte & MMA845X_BIT_PULSE_SRC_DPE) {
  998. $eventSource .= 'D';
  999. } else {
  1000. $eventSource .= 'S';
  1001. }
  1002. }
  1003. if ($byte & MMA845X_BIT_PULSE_SRC_AX_Y) {
  1004. if ($byte & MMA845X_BIT_PULSE_SRC_POL_Y) {
  1005. $eventSource .= '-Y';
  1006. } else {
  1007. $eventSource .= '+Y';
  1008. }
  1009. if ($byte & MMA845X_BIT_PULSE_SRC_DPE) {
  1010. $eventSource .= 'D';
  1011. } else {
  1012. $eventSource .= 'S';
  1013. }
  1014. }
  1015. if ($byte & MMA845X_BIT_PULSE_SRC_AX_Z) {
  1016. if ($byte & MMA845X_BIT_PULSE_SRC_POL_Z) {
  1017. $eventSource .= '-Z';
  1018. } else {
  1019. $eventSource .= '+Z';
  1020. }
  1021. if ($byte & MMA845X_BIT_PULSE_SRC_DPE) {
  1022. $eventSource .= 'D';
  1023. } else {
  1024. $eventSource .= 'S';
  1025. }
  1026. }
  1027. }
  1028. if (ReadingsVal($name, 'pulseEvent', '') ne $eventSource) {
  1029. readingsSingleUpdate($hash, 'pulseEvent', $eventSource, 1);
  1030. }
  1031. }
  1032. # -----------------------------------------------------------------------------
  1033. =item I2C_MMA845X_FF_MT_SRC()
  1034. Parameters:
  1035. $hash: hash reference of device instance
  1036. $byte: I2C register value
  1037. Returns: nothing
  1038. =cut
  1039. sub I2C_MMA845X_FF_MT_SRC($$) {
  1040. my ($hash, $byte) = @_;
  1041. my $name = $hash->{NAME};
  1042. my $eventSource = '';
  1043. if ($byte & MMA845X_BIT_FF_MT_SRC_EA) {
  1044. if ($byte & MMA845X_BIT_FF_MT_SRC_AX_X) {
  1045. if ($byte & MMA845X_BIT_FF_MT_SRC_POL_X) {
  1046. $eventSource .= '-X';
  1047. } else {
  1048. $eventSource .= '+X';
  1049. }
  1050. }
  1051. if ($byte & MMA845X_BIT_FF_MT_SRC_AX_Y) {
  1052. if ($byte & MMA845X_BIT_FF_MT_SRC_POL_Y) {
  1053. $eventSource .= '-Y';
  1054. } else {
  1055. $eventSource .= '+Y';
  1056. }
  1057. }
  1058. if ($byte & MMA845X_BIT_FF_MT_SRC_AX_Z) {
  1059. if ($byte & MMA845X_BIT_FF_MT_SRC_POL_Z) {
  1060. $eventSource .= '-Z';
  1061. } else {
  1062. $eventSource .= '+Z';
  1063. }
  1064. }
  1065. }
  1066. if (ReadingsVal($name, 'motionEvent', '') ne $eventSource) {
  1067. readingsSingleUpdate($hash, 'motionEvent', $eventSource, 1);
  1068. }
  1069. }
  1070. # -----------------------------------------------------------------------------
  1071. =item I2C_MMA845X_TRANSIENT_SRC()
  1072. Parameters:
  1073. $hash: hash reference of device instance
  1074. $byte: I2C register value
  1075. Returns: nothing
  1076. =cut
  1077. sub I2C_MMA845X_TRANSIENT_SRC($$) {
  1078. my ($hash, $byte) = @_;
  1079. my $name = $hash->{NAME};
  1080. my $eventSource = '';
  1081. if ($byte & MMA845X_BIT_TRANSIENT_SRC_EA) {
  1082. if ($byte & MMA845X_BIT_TRANSIENT_SRC_AX_X) {
  1083. if ($byte & MMA845X_BIT_TRANSIENT_SRC_POL_X) {
  1084. $eventSource .= '-X';
  1085. } else {
  1086. $eventSource .= '+X';
  1087. }
  1088. }
  1089. if ($byte & MMA845X_BIT_TRANSIENT_SRC_AX_Y) {
  1090. if ($byte & MMA845X_BIT_TRANSIENT_SRC_POL_Y) {
  1091. $eventSource .= '-Y';
  1092. } else {
  1093. $eventSource .= '+Y';
  1094. }
  1095. }
  1096. if ($byte & MMA845X_BIT_TRANSIENT_SRC_AX_Z) {
  1097. if ($byte & MMA845X_BIT_TRANSIENT_SRC_POL_Z) {
  1098. $eventSource .= '-Z';
  1099. } else {
  1100. $eventSource .= '+Z';
  1101. }
  1102. }
  1103. }
  1104. if (ReadingsVal($name, 'joltEvent', '') ne $eventSource) {
  1105. readingsSingleUpdate($hash, 'joltEvent', $eventSource, 1);
  1106. }
  1107. }
  1108. # -----------------------------------------------------------------------------
  1109. =item I2C_MMA845X_Get()
  1110. Parameters:
  1111. $hash: hash reference of device instance
  1112. @args: array of arguments
  1113. Returns: undef on success or string with error message
  1114. =cut
  1115. sub I2C_MMA845X_Get($@) {
  1116. my ($hash, $name, $cmd, @a) = @_;
  1117. my $result = undef;
  1118. if ($cmd eq 'accelerations') {
  1119. I2C_MMA845X_Poll($hash, 'accelerations');
  1120. if ($hash->{I2C_Blocking}) {
  1121. return "$name $cmd => x:$hash->{accelerations}[0]g y:$hash->{accelerations}[1]g z:$hash->{accelerations}[2]g";
  1122. }
  1123. } elsif ($cmd eq 'orientation') {
  1124. I2C_MMA845X_Poll($hash, 'orientation');
  1125. if ($hash->{I2C_Blocking}) {
  1126. return "$name $cmd => orientation:" . ReadingsVal($name, 'orientation', '?');
  1127. }
  1128. } elsif ($cmd eq 'eventSources') {
  1129. I2C_MMA845X_Poll($hash, 'eventSources');
  1130. if ($hash->{I2C_Blocking}) {
  1131. return "$name $cmd => pulse:" . ReadingsVal($name, 'pulseEvent', '') .
  1132. " motion:" . ReadingsVal($name, 'motionEvent', '') .
  1133. " jolt:" . ReadingsVal($name, 'joltEvent', '');
  1134. }
  1135. } elsif ($cmd eq 'update') {
  1136. I2C_MMA845X_Poll($hash, 'update');
  1137. } else {
  1138. $result = "I2C_MMA845X: unknown get command " . $cmd . ", choose one of accelerations:noArg orientation:noArg eventSources:noArg update:noArg";
  1139. }
  1140. return $result;
  1141. }
  1142. # -----------------------------------------------------------------------------
  1143. =item I2C_MMA845X_Set()
  1144. Parameters:
  1145. $hash: hash reference of device instance
  1146. @args: array of arguments
  1147. Returns: undef on success or string with error message
  1148. =cut
  1149. sub I2C_MMA845X_Set($@) {
  1150. my ($hash, $name, $cmd, @args) = @_;
  1151. my $result = undef;
  1152. if ($cmd eq 'calibrate') {
  1153. if ($hash->{XYZ_DATA_CFG} & MMA845X_BIT_XYZ_DATA_CFG_HPF_OUT) {
  1154. $result = "I2C_MMA845X: calibration only available with high-pass filter disabled";
  1155. } else {
  1156. I2C_MMA845X_StartCalibration($hash);
  1157. }
  1158. } else {
  1159. $result = "I2C_MMA845X: unknown set command " . $cmd . ", choose one of calibrate:noArg";
  1160. }
  1161. return $result;
  1162. }
  1163. # -----------------------------------------------------------------------------
  1164. =item I2C_MMA845X_G_TO_OFF()
  1165. Parameters:
  1166. $hash: hash reference of device instance
  1167. $accel: decimal number, acceleration [g]
  1168. Returns: raw offset value
  1169. =cut
  1170. sub I2C_MMA845X_G_TO_OFF($$) {
  1171. my ($hash, $result) = @_;
  1172. Log3($hash, 5, $hash->{NAME} . " to byte 1 $result");
  1173. $result *= 0x2000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit scale from [g]
  1174. $result *= 0x0800 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit scale from [g]
  1175. $result *= 0x0200 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit scale from [g]
  1176. my $range = 2 << ($hash->{XYZ_DATA_CFG} & 0x3); # [1 g = 9.80665 m/s2]
  1177. $result /= $range;
  1178. $range = 0.125 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit offset scale
  1179. $range = 0.5 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit offset scale
  1180. $range = 2.0 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit offset scale
  1181. $result *= $range;
  1182. $result = -127 if ($result < -127); # byte range limiter
  1183. $result = 127 if ($result > 127); # byte range limiter
  1184. $result += 0x0100 if ($result < 0); # 2's complement transform
  1185. Log3($hash, 5, $hash->{NAME} . " to byte 4 $result");
  1186. return $result;
  1187. }
  1188. # -----------------------------------------------------------------------------
  1189. =item I2C_MMA845X_OFF_TO_G()
  1190. Parameters:
  1191. $hash: hash reference of device instance
  1192. $offset: raw offset value
  1193. Returns: decimal number, acceleration [g]
  1194. =cut
  1195. sub I2C_MMA845X_OFF_TO_G($$) {
  1196. my ($hash, $result) = @_;
  1197. $result -= 0x0100 if ($result > 0x7F); # 2's complement transform
  1198. my $range = 2 << ($hash->{XYZ_DATA_CFG} & 0x3); # [1 g = 9.80665 m/s2]
  1199. $result *= $range;
  1200. $range = 0.125 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit offset scale
  1201. $range = 0.5 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit offset scale
  1202. $range = 2.0 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit offset scale
  1203. $result /= $range;
  1204. $result /= 0x2000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit scale to [g]
  1205. $result /= 0x0800 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit scale to [g]
  1206. $result /= 0x0200 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit scale to [g]
  1207. Log3($hash, 5, $hash->{NAME} . " to g 5 $result");
  1208. return $result;
  1209. }
  1210. # -----------------------------------------------------------------------------
  1211. =item I2C_MMA845X_StartCalibration()
  1212. Parameters:
  1213. $hash: hash reference of device instance
  1214. Returns: nothing
  1215. =cut
  1216. sub I2C_MMA845X_StartCalibration($) {
  1217. my ($hash) = @_;
  1218. my $name = $hash->{NAME};
  1219. if (!AttrVal($hash->{NAME}, "disable", 0)) {
  1220. # disable measurement
  1221. $hash->{CTRL_REG1} &= ~MMA845X_BIT_CTRL_REG1_ACTIVE;
  1222. I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1});
  1223. # clear current calibration values
  1224. for (my $i=0; $i<3; $i++) {
  1225. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_X, 0) if ($i == 0);
  1226. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Y, 0) if ($i == 1);
  1227. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Z, 0) if ($i == 2);
  1228. }
  1229. # reenable measurement
  1230. $hash->{CTRL_REG1} |= MMA845X_BIT_CTRL_REG1_ACTIVE;
  1231. I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1});
  1232. # trigger new measurement
  1233. RemoveInternalTimer($hash);
  1234. InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0);
  1235. readingsSingleUpdate($hash, 'state', MMA845X_STATE_CALIBRATING, 1);
  1236. }
  1237. }
  1238. # -----------------------------------------------------------------------------
  1239. =item I2C_MMA845X_Calibrate()
  1240. Parameters:
  1241. $hash: hash reference of device instance
  1242. Returns: nothing
  1243. =cut
  1244. sub I2C_MMA845X_Calibrate($) {
  1245. my ($hash) = @_;
  1246. my $name = $hash->{NAME};
  1247. my @cal;
  1248. # disable measurement
  1249. $hash->{CTRL_REG1} &= ~MMA845X_BIT_CTRL_REG1_ACTIVE;
  1250. I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1});
  1251. # detect gravity axis and direction by highest absolute output value
  1252. my $gravityIndex = 0;
  1253. for (my $i=1; $i<3; $i++) {
  1254. if (abs($hash->{accelerations}[$i]) > abs($hash->{accelerations}[$gravityIndex])) {
  1255. $gravityIndex = $i;
  1256. }
  1257. }
  1258. my $gravityDirection = $hash->{accelerations}[$gravityIndex]<=>0;
  1259. Log3($hash, 5, $hash->{NAME} . " Calibrate gravity index $gravityIndex, gravity direction $gravityDirection");
  1260. readingsBeginUpdate($hash);
  1261. for (my $i=0; $i<3; $i++) {
  1262. # calculate calibration values based on current samples assuming orthogonal orientation
  1263. $cal[$i] = -$hash->{accelerations}[$i] if ($i != $gravityIndex);
  1264. $cal[$i] = -$hash->{accelerations}[$i] + $gravityDirection if ($i == $gravityIndex);
  1265. $cal[$i] = I2C_MMA845X_G_TO_OFF($hash, $cal[$i]);
  1266. # write new calibration values
  1267. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_X, $cal[$i]) if ($i == 0);
  1268. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Y, $cal[$i]) if ($i == 1);
  1269. I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Z, $cal[$i]) if ($i == 2);
  1270. # save offsets
  1271. readingsBulkUpdate($hash, "offX", I2C_MMA845X_OFF_TO_G($hash, $cal[$i])) if ($i == 0);
  1272. readingsBulkUpdate($hash, "offY", I2C_MMA845X_OFF_TO_G($hash, $cal[$i])) if ($i == 1);
  1273. readingsBulkUpdate($hash, "offZ", I2C_MMA845X_OFF_TO_G($hash, $cal[$i])) if ($i == 2);
  1274. # modify readings
  1275. readingsBulkUpdate($hash, "outX", sprintf('%0.3f', $hash->{accelerations}[$i] + ReadingsVal($name, 'offX', 0))) if ($i == 0);
  1276. readingsBulkUpdate($hash, "outY", sprintf('%0.3f', $hash->{accelerations}[$i] + ReadingsVal($name, 'offY', 0))) if ($i == 1);
  1277. readingsBulkUpdate($hash, "outZ", sprintf('%0.3f', $hash->{accelerations}[$i] + ReadingsVal($name, 'offZ', 0))) if ($i == 2);
  1278. }
  1279. readingsEndUpdate($hash, 1);
  1280. # reenable measurement
  1281. $hash->{CTRL_REG1} |= MMA845X_BIT_CTRL_REG1_ACTIVE;
  1282. I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1});
  1283. readingsSingleUpdate($hash, 'state', MMA845X_STATE_INITIALIZED, 1);
  1284. }
  1285. # -----------------------------------------------------------------------------
  1286. =item I2C_MMA845X_Undef()
  1287. Parameters:
  1288. $hash: hash reference of device instance
  1289. $name: string name of device
  1290. Returns: undef on success
  1291. =cut
  1292. sub I2C_MMA845X_Undef($$) {
  1293. my ($hash, $name) = @_;
  1294. RemoveInternalTimer($hash);
  1295. return undef;
  1296. }
  1297. # -----------------------------------------------------------------------------
  1298. =item I2C_MMA845X_Read()
  1299. Parameters:
  1300. $hash: hash reference of device instance
  1301. $reg: integer, I2C register to read
  1302. $nbytes: integer, number of bytes to read
  1303. Returns: 1 on success, 0 on error
  1304. =cut
  1305. sub I2C_MMA845X_Read($$$) {
  1306. my ($hash, $reg, $nbytes) = @_;
  1307. local $SIG{__WARN__} = sub {
  1308. my $message = shift;
  1309. # turn warnings from RPII2C_HWACCESS_ioctl into exception
  1310. if ($message =~ /Exiting subroutine via last at.*00_RPII2C.pm/) {
  1311. die;
  1312. } else {
  1313. warn($message);
  1314. }
  1315. };
  1316. my $success = 1;
  1317. if (defined (my $iodev = $hash->{IODev})) {
  1318. eval {
  1319. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  1320. direction => "i2cread",
  1321. i2caddress => $hash->{I2C_Address},
  1322. reg => $reg,
  1323. nbyte => $nbytes
  1324. });
  1325. };
  1326. my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
  1327. if (defined($sendStat) && $sendStat eq 'error') {
  1328. readingsSingleUpdate($hash, 'state', MMA845X_STATE_I2C_ERROR, 1);
  1329. Log3($hash, 5, $hash->{NAME} . " I2C read on $iodev->{NAME} failed");
  1330. $success = 0;
  1331. }
  1332. } else {
  1333. Log3($hash, 1, $hash->{NAME} . " no IODev assigned");
  1334. $success = 0;
  1335. }
  1336. return $success;
  1337. }
  1338. # -----------------------------------------------------------------------------
  1339. =item I2C_MMA845X_Write()
  1340. Parameters:
  1341. $hash: hash reference of device instance
  1342. $reg: integer, I2C register to write
  1343. @data: array of byte to write
  1344. Returns: 1 on success, 0 on error
  1345. =cut
  1346. sub I2C_MMA845X_Write($$$) {
  1347. my ($hash, $reg, @data) = @_;
  1348. my $success = 1;
  1349. if (defined (my $iodev = $hash->{IODev})) {
  1350. eval {
  1351. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  1352. direction => "i2cwrite",
  1353. i2caddress => $hash->{I2C_Address},
  1354. reg => $reg,
  1355. data => join (' ',@data),
  1356. });
  1357. };
  1358. my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
  1359. if (defined($sendStat) && $sendStat eq 'error') {
  1360. readingsSingleUpdate($hash, 'state', MMA845X_STATE_I2C_ERROR, 1);
  1361. Log3($hash, 5, $hash->{NAME} . " I2C write on $iodev->{NAME} failed");
  1362. $success = 0;
  1363. }
  1364. } else {
  1365. Log3($hash, 1, $hash->{NAME} . " no IODev assigned");
  1366. $success = 0;
  1367. }
  1368. return $success;
  1369. }
  1370. # -----------------------------------------------------------------------------
  1371. 1;
  1372. # -----------------------------------------------------------------------------
  1373. =pod
  1374. CHANGES
  1375. 19.03.2016
  1376. - orientation detection added
  1377. 01.03.2016
  1378. - high-pass filter attributes added
  1379. 27.02.2016
  1380. - event latch support added
  1381. - freefall/motion detection added
  1382. - jolt detection added
  1383. 21.02.2016
  1384. - pulse detection added
  1385. 19.02.2016 created
  1386. - acceleration measurement and calibration added
  1387. =cut
  1388. # -----------------------------------------------------------------------------
  1389. =pod
  1390. =begin html
  1391. <a name="I2C_MMA845X"></a>
  1392. <h3>I2C_MMA845X</h3>
  1393. <ul>
  1394. This modules is a driver for using the Freescale/NXP MMA8451/MMA84512/MMA84513 accelerometer with I2C bus interface (see the <a href="http://www.nxp.com/products/sensors/accelerometers/3-axis-accelerometers/2g-4g-8g-low-g-14-bit-digital-accelerometer:MMA8451Q">NXP product description</a> for full specifications).
  1395. Note that the Freescale/NXP MMA8450 accelerometer, though similar, has a different register set and cannot be addressed by this module.
  1396. <br><br>
  1397. The I2C messages are sent through an interface module like <a href="#RPII2C">RPII2C</a> or <a href="#FRM">FRM</a>,
  1398. so this device must be defined first and assigned as IODev attribute.
  1399. <br><br>
  1400. This module supports the following features:
  1401. <ul>
  1402. <li>read current acceleration (x, y, z)</li>
  1403. <li>calibrate acceleration offsets</li>
  1404. <li>orientation detection</li>
  1405. <li>motion detection (at least one axis above threshold) or freefall detection (all axes below threshold)</li>
  1406. <li>jolt detection (at least one axis change above threshold)</li>
  1407. <li>single and/or double pulse (tap) detection</li>
  1408. <li>detection event latching</li>
  1409. <li>hardware interrupt signalling of detection events</li>
  1410. </ul>
  1411. <br>
  1412. The accelerometer is configured for an output data rate of 200 Hz in normal oversampling mode with the low noise
  1413. filter enabled providing a full scale range of +/-4 g as default. This output data rate can be changed if required.
  1414. <br><br>
  1415. The detection events (orientation, motion/freefall, jolt, pulse) can be signaled by one or two hardware outputs that can
  1416. be used for interrupt driven operations without need for continuous polling. If the event latch is enabled, the events
  1417. and the interrupt signals will remain set until the event source register is read, providing additional event details
  1418. (e.g. axis, direction). With orientation detection the event latch is always enabled.
  1419. <br><br>
  1420. The acceleration measurement output can optionally be passed through a high-pass filter with a selectable cutoff frequency
  1421. effectively eliminating the gravity offset of 1g to provide change detection instead of orientation detection.
  1422. The motion/freefall detection will always bypass the high-pass filter while the jolt and pulse detections will always
  1423. use the high-pass filter with default settings. When using motion detection you would typically not enable the gravity
  1424. axis or set a threshold higher than 1 g.
  1425. <br><br>
  1426. While the orientation detection works well with the default settings the other detection modes typically require fine tuning
  1427. of their parameters. To understand the detection modes and their parameters in detail please refer to the Freescale annotations
  1428. AN4068 (orientation), AN4070 (freefall/motion), AN4071 (jolt) and AN4072 (pulse).
  1429. <br><br>
  1430. Several of the parameters represent a frequency [Hz], a threshold [g]/[°] or a duration [ms]. Their absolute values often
  1431. depend on a combination of register settings requiring lookup tables. This module uses the raw binary values for these
  1432. attributes making fine tuning easier because value granularity is always 1. If you need to translate between binary
  1433. values and absolute values please refer to the device documentation.
  1434. <br><br>
  1435. <a name="I2C_MMA845Xdefine"></a>
  1436. <b>Define</b>
  1437. <ul>
  1438. <code>define &lt;device name&gt I2C_MMA845X &lt;I2C address&gt</code>
  1439. <br><br>
  1440. <code>&lt;I2C address&gt;</code> may be 0x1C or 0x1D
  1441. <br><br>
  1442. Example:
  1443. <pre>
  1444. define MMA8452 I2C_MMA845X 0x1D
  1445. attr MMA8452 IODev I2CModule
  1446. attr MMA8452 pollInterval 5
  1447. </pre>
  1448. Notes:
  1449. <ul>
  1450. <br>
  1451. <li>The I2C bus connection must be kept active between write and read with the MMA8451/MMA84512/MMA84513 devices
  1452. (repeated start alias combined write/read). This communication mode is not the default on most platforms:
  1453. <br><br>
  1454. <u>Raspberry</u>:
  1455. <ul>
  1456. <li>Change parameter 'combined' of BCM2708 driver from N to Y.<br>
  1457. Temporary: <code>sudo su - echo -n 1 > /sys/module/i2c_bcm2708/parameters/combined exit</code>.<br>
  1458. Permanent: add <code>echo -n 1 > /sys/module/i2c_bcm2708/parameters/combined</code> to script
  1459. <code>/etc/init.d/rc.local</code>.
  1460. </li>
  1461. <li>Set attribute <code>useHWLib</code> of your RPII2C device to <code>SMBus</code>
  1462. (RPII2C's ioctl mode currently does not support combined write/read mode).
  1463. </li>
  1464. </ul>
  1465. <br>
  1466. <u>Firmata</u>:
  1467. <ul>
  1468. <li>Make sure to call <code>Wire.endTransmission(false)</code>. Currently requires manually changing the
  1469. <code>ino</code> file (Standard Firmata) or <code>I2CFirmata.h</code> (Configurable Firmata).
  1470. </li>
  1471. </ul>
  1472. </li>
  1473. </ul>
  1474. </ul>
  1475. <br>
  1476. <a name="I2C_MMA845Xset"></a>
  1477. <b>Set</b><br>
  1478. <ul>
  1479. <ul>
  1480. <li><code>set &lt;device name&gt calibrate</code><br>
  1481. Calibrate the acceleration offset based on the next sample assuming 1g gravity on any one axis.<br>
  1482. Prerequisites: Align one axis with gravity and keep device stationary during calibration.
  1483. </li>
  1484. </ul>
  1485. </ul>
  1486. <br>
  1487. <a name="I2C_MMA845Xget"></a>
  1488. <b>Get</b><br>
  1489. <ul>
  1490. <ul>
  1491. <li><code>get &lt;device name&gt update</code><br>
  1492. Request an update of the acceleration readings.
  1493. </li>
  1494. <br>
  1495. <li><code>get &lt;device name&gt orientation</code><br>
  1496. Request an update of the orientation reading.
  1497. </li>
  1498. <li><code>get &lt;device name&gt eventSources</code><br>
  1499. Request an update of the event source readings.
  1500. </li>
  1501. <br>
  1502. <li><code>get &lt;device name&gt update</code><br>
  1503. Perform manual polling, e.g. when attribute <code>pollInterval</code> is set to zero.
  1504. At least one of <code>pollAccelerations</code>, <code>pollOrientation</code> or <code>pollEventSources</code> should be enabled.
  1505. </li>
  1506. </ul>
  1507. </ul>
  1508. <br>
  1509. <a name="I2C_MMA845Xattr"></a>
  1510. <b>Attributes</b>
  1511. <ul>
  1512. <code>attr &lt;device name&gt &lt;attribute name&gt &lt;value&gt</code>
  1513. <br><br>
  1514. Attributes:
  1515. <ul>
  1516. <li><i>IODev</i> &lt;IODev device name&gt<br>
  1517. I2C IODev device name, <i>no default</i>, required
  1518. </li>
  1519. <li><i>pollInterval</i> &lt;seconds&gt<br>
  1520. period for updating acceleration and event source readings, <i>default 10 s</i><br>
  1521. fractional seconds are supported, use 0 to disable polling
  1522. </li>
  1523. <li><i>pollAccelerations</i> 0|1<br>
  1524. include reading of accelerations when polling, <i>default 1</i><br>
  1525. </li>
  1526. <li><i>pollOrientation</i> 0|1<br>
  1527. include reading of orientation when polling, <i>default 1</i><br>
  1528. </li>
  1529. <li><i>pollEventSources</i> 0|1<br>
  1530. include reading of event sources when polling, <i>default 1</i><br>
  1531. </li>
  1532. <li><i>disable</i> 0|1<br>
  1533. disables device (I2C operations), <i>default 0</i>
  1534. </li>
  1535. <li><i>outputDataRate</i> &lt;frequency&gt<br>
  1536. device internal acceleration value output rate, may be one of 1.56, 6.25, 12.5, 50, 100, 200, 400 or 800 Hz, <i>default 200 Hz</i><br>
  1537. affects all timing parameters, is independent of pollInterval
  1538. </li>
  1539. <li><i>highPass</i> &lt;function&gt[,&lt;function&gt]<br>
  1540. select which function should use the high-pass filter, may be any of outputData, jolt or pulse, <i>default jolt,pulse</i><br>
  1541. activating the high-pass filter will remove the 1g offset in the gravity direction
  1542. </li>
  1543. <li><i>highPassCutoffFrequency</i> 0 ... 3<br>
  1544. set the high-pass filter cutoff frequency, changes with on output data rate, <i>default 0</i><br>
  1545. 0 is a higher cutoff frequency (up to 16 Hz) and 3 is a lower cutoff frequency (down to 0.25 Hz), see device manual for details
  1546. </li>
  1547. <li><i>orientation...</i><br>
  1548. orientation detection parameters, see device manual for details
  1549. </li>
  1550. <li><i>motion...</i><br>
  1551. motion/freefall detection parameters, see device manual for details
  1552. </li>
  1553. <li><i>jolt...</i><br>
  1554. jolt detection parameters, see device manual for details
  1555. </li>
  1556. <li><i>pulse...</i><br>
  1557. pulse (tap) detection parameters, see device manual for details
  1558. </li>
  1559. <li><i>...EventLatch</i> 0|1<br>
  1560. if enabled an event (and the hardware output) will stay latched until the event source register is read, <i>default 0</i><br>
  1561. the corresponding event source reading will provide additional information about the event
  1562. </li>
  1563. <li><i>...Interrupt</i> 0|1|2<br>
  1564. an event will also raise one of two hardware outputs, <i>default 0</i><br>
  1565. use 0 to disable linking an event with an hardware outputs
  1566. </li>
  1567. </ul>
  1568. </ul>
  1569. <br>
  1570. <b>Readings</b>
  1571. <ul>
  1572. <ul>
  1573. <li><i>out...</i><br>
  1574. acceleration for x, y and z axes [g]<br>
  1575. the number of decimal places is limited to 3 to remove a significant amount of noise
  1576. </li>
  1577. <li><i>off...</i><br>
  1578. acceleration offset for x, y and z axes from last calibration [g]<br>
  1579. to adjust offsets manually at runtime, change offset readings and toggle disable attribute
  1580. </li>
  1581. <li><i>orientation</i><br>
  1582. current orientation, orientation detection must be enabled, the reading is only updated on change<br>
  1583. P=portrait + U=up/D=down or L=landscape + L=left/R=right, B=back or F=front, X=z-lockout
  1584. </li>
  1585. <li><i>...Event</i><br>
  1586. source of last event, event and event latch must be enabled, the reading is only updated on change<br>
  1587. motion/jolt/pulse: X, Y or Z for the affected axis preceded by a sign for the direction of the the event<br>
  1588. pulse: additional pulse type indicator postfix S=single or D=double
  1589. </li>
  1590. </ul>
  1591. </ul>
  1592. </ul>
  1593. =end html
  1594. =cut