diff --git a/fhem/CHANGED b/fhem/CHANGED index 7789b2310..f328e1ca9 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: new module 52_I2C_MMA845X.pm added - change: 49_SSCAM: change to new RemoveInternalTimer for functions - feature: new module 52_I2C_K30.pm added - bugfix: 98_weekprofile: send reference profile to device diff --git a/fhem/FHEM/52_I2C_MMA845X.pm b/fhem/FHEM/52_I2C_MMA845X.pm new file mode 100644 index 000000000..cc9a77335 --- /dev/null +++ b/fhem/FHEM/52_I2C_MMA845X.pm @@ -0,0 +1,1888 @@ +################################################################################# +# +# $Id$ +# +# 52_I2C_MMA845X.pm +# +# (c) 2016 Copyright Jens Beyer < jensb at forum dot fhem dot de > +# +# This script is part of FHEM. +# +# This script is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the text file GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# This copyright notice MUST APPEAR in all copies of the script! +# +################################################################################# + +# encoding: UTF-8 (äöüÄÖÜߧ€) + +# ----------------------------------------------------------------------------- + +=pod + +TODO + +- configurable max. measurement range? +- return values with get even if using non-blocking I2C IO? +- support power saving? + +=cut + +# ----------------------------------------------------------------------------- + +package main; + +use strict; +use warnings; + +# ----------------------------------------------------------------------------- + +use constant { + MMA845X_STATE_DEFINED => 'defined', + MMA845X_STATE_INITIALIZED => 'initialized', + MMA845X_STATE_CALIBRATING => 'calibrating', + MMA845X_STATE_INVALID_DEVICE => 'invalid device', + MMA845X_STATE_DISABLED => 'disabled', + MMA845X_STATE_I2C_ERROR => 'I2C error', + + MMA845X_DEVICE_CODE_MMA8451 => 0x1A, # sensitivity 4096, FIFO, programmable orientation detection + MMA845X_DEVICE_CODE_MMA8452 => 0x2A, # sensitivity 1024 + MMA845X_DEVICE_CODE_MMA8453 => 0x3A, # sensitivity 256 + + MMA845X_ADDR_LOW => '0x1C', + MMA845X_ADDR_HIGH => '0x1D', + + MMA845X_REGISTER_OUT_X_MSB => 0x01, # r/o + MMA845X_REGISTER_WHO_AM_I => 0x0D, # r/o + MMA845X_REGISTER_XYZ_DATA_CFG => 0x0E, + MMA845X_REGISTER_HP_FILTER_CUTOFF => 0x0F, + MMA845X_REGISTER_PL_STATUS => 0x10, # r/o + MMA845X_REGISTER_PL_CFG => 0x11, + MMA845X_REGISTER_PL_COUNT => 0x12, + MMA845X_REGISTER_PL_BF_ZCOMP => 0x13, + MMA845X_REGISTER_PL_THS_REG => 0x14, + MMA845X_REGISTER_FF_MT_CFG => 0x15, + MMA845X_REGISTER_FF_MT_SRC => 0x16, # r/o + MMA845X_REGISTER_FF_MT_THS => 0x17, + MMA845X_REGISTER_FF_MT_COUNT => 0x18, + MMA845X_REGISTER_TRANSIENT_CFG => 0x1D, + MMA845X_REGISTER_TRANSIENT_SRC => 0x1E, # r/o + MMA845X_REGISTER_TRANSIENT_THS => 0x1F, + MMA845X_REGISTER_TRANSIENT_COUNT => 0x20, + MMA845X_REGISTER_PULSE_CFG => 0x21, + MMA845X_REGISTER_PULSE_SRC => 0x22, # r/o + MMA845X_REGISTER_PULSE_THSX => 0x23, + MMA845X_REGISTER_PULSE_THSY => 0x24, + MMA845X_REGISTER_PULSE_THSZ => 0x25, + MMA845X_REGISTER_PULSE_TMLT => 0x26, + MMA845X_REGISTER_PULSE_LTCY => 0x27, + MMA845X_REGISTER_PULSE_WIND => 0x28, + MMA845X_REGISTER_CTRL_REG1 => 0x2A, + MMA845X_REGISTER_CTRL_REG4 => 0x2D, + MMA845X_REGISTER_CTRL_REG5 => 0x2E, + MMA845X_REGISTER_OFF_X => 0x2F, + MMA845X_REGISTER_OFF_Y => 0x30, + MMA845X_REGISTER_OFF_Z => 0x31, + + MMA845X_BIT_XYZ_DATA_CFG_HPF_OUT => 0x10, + + MMA845X_BIT_PL_CFG_PL_EN => 0x40, + + MMA845X_BIT_HP_FILTER_PULSE_BYP => 0x20, + + MMA845X_BIT_PL_STATUS_BAFRO => 0x01, + MMA845X_BIT_PL_STATUS_LO => 0x40, + MMA845X_BIT_PL_STATUS_NEWLP => 0x80, + + MMA845X_BIT_FF_MT_CFG_EFE_X => 0x08, + MMA845X_BIT_FF_MT_CFG_EFE_Y => 0x10, + MMA845X_BIT_FF_MT_CFG_EFE_Z => 0x20, + MMA845X_BIT_FF_MT_CFG_OAE => 0x40, + MMA845X_BIT_FF_MT_CFG_ELE => 0x80, + + MMA845X_BIT_FF_MT_SRC_POL_X => 0x01, + MMA845X_BIT_FF_MT_SRC_AX_X => 0x02, + MMA845X_BIT_FF_MT_SRC_POL_Y => 0x04, + MMA845X_BIT_FF_MT_SRC_AX_Y => 0x08, + MMA845X_BIT_FF_MT_SRC_POL_Z => 0x10, + MMA845X_BIT_FF_MT_SRC_AX_Z => 0x20, + MMA845X_BIT_FF_MT_SRC_EA => 0x80, + + MMA845X_BIT_TRANSIENT_CFG_HPF_BYP => 0x01, + MMA845X_BIT_TRANSIENT_CFG_EFE_X => 0x02, + MMA845X_BIT_TRANSIENT_CFG_EFE_Y => 0x04, + MMA845X_BIT_TRANSIENT_CFG_EFE_Z => 0x08, + MMA845X_BIT_TRANSIENT_CFG_ELE => 0x10, + + MMA845X_BIT_TRANSIENT_SRC_POL_X => 0x01, + MMA845X_BIT_TRANSIENT_SRC_AX_X => 0x02, + MMA845X_BIT_TRANSIENT_SRC_POL_Y => 0x04, + MMA845X_BIT_TRANSIENT_SRC_AX_Y => 0x08, + MMA845X_BIT_TRANSIENT_SRC_POL_Z => 0x10, + MMA845X_BIT_TRANSIENT_SRC_AX_Z => 0x20, + MMA845X_BIT_TRANSIENT_SRC_EA => 0x40, + + MMA845X_BIT_PULSE_CFG_EFE_XS => 0x01, + MMA845X_BIT_PULSE_CFG_EFE_XD => 0x02, + MMA845X_BIT_PULSE_CFG_EFE_YS => 0x04, + MMA845X_BIT_PULSE_CFG_EFE_YD => 0x08, + MMA845X_BIT_PULSE_CFG_EFE_ZS => 0x10, + MMA845X_BIT_PULSE_CFG_EFE_ZD => 0x20, + MMA845X_BIT_PULSE_CFG_ELE => 0x40, + MMA845X_BIT_PULSE_CFG_DPA => 0x80, + + MMA845X_BIT_PULSE_SRC_POL_X => 0x01, + MMA845X_BIT_PULSE_SRC_POL_Y => 0x02, + MMA845X_BIT_PULSE_SRC_POL_Z => 0x04, + MMA845X_BIT_PULSE_SRC_DPE => 0x08, + MMA845X_BIT_PULSE_SRC_AX_X => 0x10, + MMA845X_BIT_PULSE_SRC_AX_Y => 0x20, + MMA845X_BIT_PULSE_SRC_AX_Z => 0x40, + MMA845X_BIT_PULSE_SRC_EA => 0x80, + + MMA845X_BIT_CTRL_REG1_ACTIVE => 0x01, + MMA845X_BIT_CTRL_REG1_LNOISE => 0x04, + + # CTRL_REG4 + MMA845X_BIT_INT_EN_FF_MT => 0x04, + MMA845X_BIT_INT_EN_PULSE => 0x08, + MMA845X_BIT_INT_EN_LNDPRT => 0x10, + MMA845X_BIT_INT_EN_TRANS => 0x20, + + # CTRL_REG5 + MMA845X_BIT_INT_CFG_FF_MT => 0x04, + MMA845X_BIT_INT_CFG_PULSE => 0x08, + MMA845X_BIT_INT_CFG_LNDPRT => 0x10, + MMA845X_BIT_INT_CFG_TRANS => 0x20, + + # PL_STATUS + MMA845X_BITS_PL_LAPO_PU => 0x00, + MMA845X_BITS_PL_LAPO_PD => 0x02, + MMA845X_BITS_PL_LAPO_LR => 0x04, + MMA845X_BITS_PL_LAPO_LL => 0x06, + + # CTRL_REG1 + MMA845X_BITS_ODR_800HZ => 0x00, # default + MMA845X_BITS_ODR_400HZ => 0x08, + MMA845X_BITS_ODR_200HZ => 0x10, + MMA845X_BITS_ODR_100HZ => 0x18, + MMA845X_BITS_ODR_50HZ => 0x20, + MMA845X_BITS_ODR_12_5HZ => 0x28, + MMA845X_BITS_ODR_6_25HZ => 0x30, + MMA845X_BITS_ODR_1_56HZ => 0x38, + + # XYZ_DATA + MMA845X_BITS_FS_2G => 0x00, # default + MMA845X_BITS_FS_4G => 0x01, + MMA845X_BITS_FS_8G => 0x02, + + MMA845X_POLLING_INTERVAL_DEFAULT => 10, # [s] +}; + +my %MMA845X_ADDRESSES = ( + "0x1c" => MMA845X_ADDR_LOW, + "0x1d" => MMA845X_ADDR_HIGH, +); + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Initialize() + + Parameters: + $hash: hash reference of device instance + + Returns: nothing + +=cut + +sub I2C_MMA845X_Initialize($) { + my ($hash) = @_; + + $hash->{AttrFn} = 'I2C_MMA845X_Attr'; + $hash->{DefFn} = 'I2C_MMA845X_Define'; + $hash->{InitFn} = 'I2C_MMA845X_Init'; + $hash->{I2CRecFn} = 'I2C_MMA845X_I2CRec'; + $hash->{SetFn} = 'I2C_MMA845X_Set'; + $hash->{GetFn} = 'I2C_MMA845X_Get'; + $hash->{UndefFn} = 'I2C_MMA845X_Undef'; + + $hash->{AttrList} = 'IODev pollInterval pollAccelerations:0,1 pollOrientation:0,1 pollEventSources:0,1 ' + . 'outputDataRate:1.56,6.25,12.5,50,100,200,400,800 ' + . 'highPass:multiple-strict,outputData,jolt,pulse highPassCutoffFrequency:0,1,2,3 ' + . 'orientationDetection:0,1 orientationInterrupt:0,1,2 ' + . 'orientationDebounce orientationZLockThreshold orientationBFTripAngleThreshold orientationPLTripAngleHysteresis orientationPLTripAngleThreshold ' + . 'motionEvent:multiple-strict,X,Y,Z motionEventLatch:0,1 motionInterrupt:0,1,2 ' + . 'motionMode:motion,freefall motionThreshold motionDebounce ' + . 'joltEvent:multiple-strict,X,Y,Z joltEventLatch:0,1 joltInterrupt:0,1,2 ' + . 'joltThreshold joltDebounce ' + . 'pulseEvent:multiple-strict,XS,XD,YS,YD,ZS,ZD pulseEventLatch:0,1 pulseInterrupt:0,1,2 ' + . 'pulseThresholdX pulseThresholdY pulseThresholdZ pulseWindow pulseLatency pulseWindow2 ' + . 'disable:0,1 ' + . $readingFnAttributes; + + # device power on defaults + $hash->{OFF_X} = 0; + $hash->{OFF_Y} = 0; + $hash->{OFF_Z} = 0; + $hash->{XYZ_DATA_CFG} = 0; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Attr() + + Parameters: + @args: array of parameters + + Returns: undef on success or string with error message + +=cut + +sub I2C_MMA845X_Attr (@) { + my ($cmd, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $result = undef; + + if ($cmd eq 'set') { + if ($attr eq 'pollInterval') { + my $pollInterval = (defined($val) && looks_like_number($val) && $val >= 0) ? $val : -1; + if ($pollInterval > 0) { + # start new measurement cycle + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($pollInterval < 0) { + $result = 'invalid polling interval, must be an number >= 0 [seconds]'; + } + } + elsif ($attr eq 'highPassCutoffFrequency') { + my $cutoffFrequency = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 3) ? $val : -1; + if ($cutoffFrequency >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($cutoffFrequency < 0) { + $result = 'invalid high-pass cutoff frequency selector, must be an integer number between 0 (higher frequency) and 3 (lower frequency)'; + } + } + elsif ($attr eq 'outputDataRate' || $attr eq 'highPass' || + $attr eq 'orientationDetection' || $attr eq 'orientationInterrupt' || + $attr eq 'pulseEvent' || $attr eq 'pulseEventLatch' || $attr eq 'pulseInterrupt' || + $attr eq 'motionEvent' || $attr eq 'motionEventLatch' || $attr eq 'motionInterrupt' || $attr eq 'motionMode' || + $attr eq 'joltEvent' || $attr eq 'joltEventLatch' || $attr eq 'joltInterrupt') { + # cleanup readings + if ($attr eq 'orientationDetection' && (!defined($val) || $val eq '0')) { + delete $hash->{READINGS}{orientation}; + } + elsif ($attr eq 'pulseEvent' && (!defined($val) || $val eq '1')) { + delete $hash->{READINGS}{pulseEvent}; + } + elsif ($attr eq 'motionEvent' && (!defined($val) || $val eq '1')) { + delete $hash->{READINGS}{motionEvent}; + } + elsif ($attr eq 'joltEvent' && (!defined($val) || $val eq '1')) { + delete $hash->{READINGS}{joltEvent}; + } + + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } + elsif ($attr eq 'orientationDebounce') { + my $orientationDebounce = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1; + if ($orientationDebounce >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($orientationDebounce < 0) { + $result = 'invalid orientation debounce duration, must be an integer number between 0 and 255 [~ms]'; + } + } + elsif ($attr eq 'orientationZLockThreshold') { + my $orientationZLockThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 7) ? $val : -1; + if ($orientationZLockThreshold >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($orientationZLockThreshold < 0) { + $result = 'invalid orientation Z lockout threshold, must be an integer number between 0 and 7 [14 ... 42°]'; + } + } + elsif ($attr eq 'orientationBFTripAngleThreshold') { + my $orientationBFTripAngleThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 3) ? $val : -1; + if ($orientationBFTripAngleThreshold >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($orientationBFTripAngleThreshold < 0) { + $result = 'invalid orientation back/front trip angle threshold, must be an integer number between 0 and 3 [65 ... 80°]'; + } + } + elsif ($attr eq 'orientationPLTripAngleHysteresis') { + my $orientationPLTripAngleHysteresis = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 7) ? $val : -1; + if ($orientationPLTripAngleHysteresis >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($orientationPLTripAngleHysteresis < 0) { + $result = 'invalid orientation portrait/landscape trip angle hysteresis, must be an integer number between 0 and 7 [±0 ... ±14°]'; + } + } + elsif ($attr eq 'orientationPLTripAngleThreshold') { + my $orientationPLTripAngleThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 31) ? $val : -1; + if ($orientationPLTripAngleThreshold >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($orientationPLTripAngleThreshold < 0) { + $result = 'invalid orientation portrait/landscape trip angle threshold, must be an integer number between 0 and 31 [0 ... 75°]'; + } + } + elsif ($attr eq 'pulseThresholdX' || $attr eq 'pulseThresholdY' || $attr eq 'pulseThresholdZ') { + my $pulseThreshold = (defined($val) && looks_like_number($val) && $val > 0 && $val <= 63) ? $val : -1; + if ($pulseThreshold > 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($pulseThreshold < 0) { + $result = 'invalid pulse threshold, must be an integer number between 1 and 63 [0.063g]'; + } + } + elsif ($attr eq 'pulseWindow' || $attr eq 'pulseWindow2') { + my $pulseThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1; + if ($pulseThreshold >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($pulseThreshold < 0) { + $result = 'invalid pulse window duration, must be an integer number between 0 and 255 [~ms]'; + } + } + elsif ($attr eq 'pulseLatency') { + my $pulseThreshold = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1; + if ($pulseThreshold >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($pulseThreshold < 0) { + $result = 'invalid pulse latency duration, must be an integer number between 0 and 255 [~ms]'; + } + } + elsif ($attr eq 'motionThreshold') { + my $motionThreshold = (defined($val) && looks_like_number($val) && $val > 0 && $val <= 63) ? $val : -1; + if ($motionThreshold > 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($motionThreshold < 0) { + $result = 'invalid motion threshold, must be an integer number between 1 and 63 [0.063g]'; + } + } + elsif ($attr eq 'motionDebounce') { + my $motionDebounce = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1; + if ($motionDebounce >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($motionDebounce < 0) { + $result = 'invalid motion debounce duration, must be an integer number between 0 and 255 [~ms]'; + } + } + elsif ($attr eq 'joltThreshold') { + my $joltThreshold = (defined($val) && looks_like_number($val) && $val > 0 && $val <= 63) ? $val : -1; + if ($joltThreshold > 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($joltThreshold < 0) { + $result = 'invalid jolt threshold, must be an integer number between 1 and 63 [0.063g]'; + } + } + elsif ($attr eq 'joltDebounce') { + my $joltDebounce = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 255) ? $val : -1; + if ($joltDebounce >= 0) { + # force device reinit + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($joltDebounce < 0) { + $result = 'invalid jolt debounce duration, must be an integer number between 0 and 255 [~ms]'; + } + } + elsif ($attr eq 'disable') { + my $disable = (defined($val) && looks_like_number($val) && $val >= 0 && $val <= 1) ? $val : -1; + if ($disable > 0) { + # stop timer and force reinit at next start + $hash->{MODEL} = undef; + RemoveInternalTimer($hash); + readingsSingleUpdate($hash, 'state', MMA845X_STATE_DISABLED, 1); + } elsif ($disable == 0) { + # restart timer + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } elsif ($disable < 0) { + $result = 'invalid disable value, must be 0 or 1'; + } + } + + } elsif ($cmd eq 'del') { + if ($attr eq 'disable') { + # restart timer + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + } + elsif ($attr eq 'orientationDetection') { + delete $hash->{READINGS}{orientation}; + } + elsif ($attr eq 'pulseEvent') { + delete $hash->{READINGS}{pulseEvent}; + } + elsif ($attr eq 'motionEvent') { + delete $hash->{READINGS}{motionEvent}; + } + elsif ($attr eq 'joltEvent') { + delete $hash->{READINGS}{joltEvent}; + } + } + + return $result; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Define() + + Parameters: + $hash: hash reference of device instance + $def: string device definition (device name, module name, I2C address) + + Returns: undef on success or string with error message + +=cut + +sub I2C_MMA845X_Define($$) { + my ($hash, $def) = @_; + my $name = $hash->{NAME}; + my @a = split('[ \t][ \t]*', $def); + + my $result = undef; + if (@a == 3) { + my $address = lc($a[2]); + $address = $MMA845X_ADDRESSES{$address}; + if (defined($address)) { + $hash->{I2C_Address} = hex($address); + readingsSingleUpdate($hash, 'state', MMA845X_STATE_DEFINED, 1); + } else { + $result = "I2C_MMA845X: invalid I2C address, must be one of " . join(', ', keys %MMA845X_ADDRESSES); + } + } elsif (@a < 3) { + $result = "I2C_MMA845X: missing parameters, usage: define I2C_MMA845X "; + } else { + $result = "I2C_MMA845X: too many parameters in define"; + } + + if (!defined($result)) { + # create default attributes + if (AttrVal($name, 'pollInterval', '?') eq '?') { + $attr{$name}{pollInterval} = MMA845X_POLLING_INTERVAL_DEFAULT; + } + + # init immediately if FHEM is already up + if ($main::init_done) { + eval { I2C_MMA845X_Init($hash, undef); }; + } + } + + return $result; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Init() + + Parameters: + $hash: hash reference of device instance + \@args: string array reference of initialization parameters + + Returns: undef on success + +=cut + +sub I2C_MMA845X_Init($$) { + my ($hash, $args) = @_; + + AssignIoPort($hash); + + # reset state + $hash->{MODEL} = undef; + $hash->{XYZ_DATA_CFG} = 0; + $hash->{I2C_Blocking} = 0; + + # start timer + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 8, 'I2C_MMA845X_Poll', $hash, 0); + + return undef; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Poll() + + Parameters: + $hash: hash reference of device instance + $cmd: optional string, may be one of "pulseSource" + + Returns: undef on success + +=cut + +sub I2C_MMA845X_Poll($;$) { + my ($hash, $cmd) = @_; + my $name = $hash->{NAME}; + + # disable timer + if (!defined($cmd)) { + RemoveInternalTimer($hash); + } + + my $pollDelay = AttrVal($hash->{NAME}, "pollInterval", MMA845X_POLLING_INTERVAL_DEFAULT) ; + my $state = ReadingsVal($name, 'state', '?'); + if (!AttrVal($hash->{NAME}, "disable", 0)) { + if ($state eq MMA845X_STATE_INVALID_DEVICE || $state eq MMA845X_STATE_DISABLED || $state eq MMA845X_STATE_I2C_ERROR) { + # error state, clear model registration to force new setup + $hash->{MODEL} = undef; + } + if (!defined($hash->{MODEL})) { + # reset state + if ($state ne MMA845X_STATE_DEFINED && $state ne MMA845X_STATE_INVALID_DEVICE) { + readingsSingleUpdate($hash, 'state', MMA845X_STATE_DEFINED, 1); + } + $hash->{setup} = 0; + $hash->{I2C_PendingRequests} = 0; + + # detect device model and IO mode + $hash->{operationInProgress} = 1; + I2C_MMA845X_Read($hash, MMA845X_REGISTER_WHO_AM_I, 1); + delete $hash->{operationInProgress}; + } + if (defined($hash->{setup})) { + if ($hash->{I2C_Blocking}) { + # start/continue with blocking setup + I2C_MMA845X_Setup($hash); + + # yield to FHEM for 100 ms + $pollDelay = 0.1; + } else { + # monitor non-blocking setup + $hash->{I2C_PendingRequests}++; + if ($hash->{I2C_PendingRequests} > 10) { + # non-blocking setup timeout + readingsSingleUpdate($hash, 'state', MMA845X_STATE_I2C_ERROR, 1); + } + } + } else { + # polling + if (defined($hash->{I2C_PendingRequests})) { + $hash->{I2C_PendingRequests}++; + } else { + $hash->{I2C_PendingRequests} = 1; + } + + # get acceleration values + if (AttrVal($name, 'pollAccelerations', 1) || $state eq MMA845X_STATE_CALIBRATING || (defined($cmd) && $cmd eq 'accelerations')) { + I2C_MMA845X_Read($hash, MMA845X_REGISTER_OUT_X_MSB, 6); + } + + # get orientation + if ((AttrVal($name, 'pollOrientation', 1) && $state ne MMA845X_STATE_CALIBRATING) || (defined($cmd) && $cmd eq 'orientation')) { + my $orientationDetection = AttrVal($name, 'orientationDetection', 0); + if ($orientationDetection) { + I2C_MMA845X_Read($hash, MMA845X_REGISTER_PL_STATUS, 1); + } + } + + # get event sources + if ((AttrVal($name, 'pollEventSources', 1) && $state ne MMA845X_STATE_CALIBRATING) || (defined($cmd) && $cmd eq 'eventSource')) { + # get motion source + my $motionEvent = AttrVal($name, 'motionEvent', ''); + if (length($motionEvent) > 0 && $motionEvent ne '1') { + I2C_MMA845X_Read($hash, MMA845X_REGISTER_FF_MT_SRC, 1); + } + # get jolt source + my $joltEvent = AttrVal($name, 'joltEvent', ''); + if (length($joltEvent) > 0 && $joltEvent ne '1') { + I2C_MMA845X_Read($hash, MMA845X_REGISTER_TRANSIENT_SRC, 1); + } + # get pulse source + my $pulseEvent = AttrVal($name, 'pulseEvent', ''); + if (length($pulseEvent) > 0 && $pulseEvent ne '1') { + I2C_MMA845X_Read($hash, MMA845X_REGISTER_PULSE_SRC, 1); + } + } + + # monitor non-blocking read of acceleration values + if (!$hash->{I2C_Blocking} && $hash->{I2C_PendingRequests} >= 3) { + # non-blocking read timeout + readingsSingleUpdate($hash, 'state', MMA845X_STATE_I2C_ERROR, 1); + } + } + } + elsif ($state ne MMA845X_STATE_DISABLED) { + readingsSingleUpdate($hash, 'state', MMA845X_STATE_DISABLED, 1); + } + + # schedule next poll + if (!defined($cmd)) { + #Log3($name, 5, "I2C_MMA845X_Poll: $pollDelay s"); + if ($pollDelay > 0) { + InternalTimer(gettimeofday() + $pollDelay, 'I2C_MMA845X_Poll', $hash, 0); + } + } + + return undef; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_I2CRec() + + Parameters: + $hash: hash reference of device instance + $clientmsg: hash reference from I2C receiver + + Returns: nothing + +=cut + +sub I2C_MMA845X_I2CRec($) { + my ($hash, $clientmsg) = @_; + my $name = $hash->{NAME}; + + # copy all elements of clientmsg starting with IODev name into internal (debug data) + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + while (my ($k, $v) = each %$clientmsg) { + $hash->{$k} = $v if $k =~ /^$pname/; + } + + # last send was OK? + if ($clientmsg->{direction} && $clientmsg->{reg} && $pname && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") { + # data was received? + if ($clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received})) { + my $register = $clientmsg->{reg} & 0xFF; + Log3($hash, 5, "$name RX register $register, $clientmsg->{nbyte} bytes: $clientmsg->{received}"); + my $byte = undef; + my $word = undef; + my @raw = split(" ", $clientmsg->{received}); + if ($clientmsg->{nbyte} == 1) { + $byte = $raw[0]; + } elsif ($clientmsg->{nbyte} == 2) { + $word = $raw[0] << 8 | $raw[1]; + } + + # process reply + if ($register == MMA845X_REGISTER_WHO_AM_I) { + I2C_MMA845X_WHO_AM_I($hash, $byte); + } elsif ($register == MMA845X_REGISTER_OUT_X_MSB && $clientmsg->{nbyte} == 6) { + I2C_MMA845X_OUT_XYZ($hash, \@raw); + $hash->{I2C_PendingRequests} = 0; + } elsif ($register == MMA845X_REGISTER_PL_STATUS) { + I2C_MMA845X_PL_STATUS($hash, $byte); + $hash->{I2C_PendingRequests} = 0; + } elsif ($register == MMA845X_REGISTER_FF_MT_SRC) { + I2C_MMA845X_FF_MT_SRC($hash, $byte); + $hash->{I2C_PendingRequests} = 0; + } elsif ($register == MMA845X_REGISTER_TRANSIENT_SRC) { + I2C_MMA845X_TRANSIENT_SRC($hash, $byte); + $hash->{I2C_PendingRequests} = 0; + } elsif ($register == MMA845X_REGISTER_PULSE_SRC) { + I2C_MMA845X_PULSE_SRC($hash, $byte); + $hash->{I2C_PendingRequests} = 0; + } else { + Log3($hash, 2, "$name RX register $register not implemented"); + } + } + } +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_WHO_AM_I() + + Parameters: + $hash: hash reference of device instance + $byte: I2C register value + + Returns: nothing + +=cut + +sub I2C_MMA845X_WHO_AM_I($$) { + my ($hash, $byte) = @_; + my $name = $hash->{NAME}; + + if ($byte == MMA845X_DEVICE_CODE_MMA8451 || $byte == MMA845X_DEVICE_CODE_MMA8452 || $byte == MMA845X_DEVICE_CODE_MMA8453) { + # save model code + $hash->{MODEL} = $byte; + + # save IO mode + if (defined($hash->{operationInProgress})) { + $hash->{I2C_Blocking} = 1; + } + + if (defined($hash->{setup} && !$hash->{I2C_Blocking})) { + # start/continue with non-blocking setup + I2C_MMA845X_Setup($hash); + + # perform read to ensure non-blocking setup stage was completed + if (defined($hash->{setup})) { + I2C_MMA845X_Read($hash, MMA845X_REGISTER_WHO_AM_I, 1); + } + } + } else { + Log3($hash, 1, "$name I2C device at address " . $hash->{I2C_Address} . " is not MMA845X compatible"); + $hash->{MODEL} = undef; # incompatible + if (ReadingsVal($name, 'state', '?') ne MMA845X_STATE_INVALID_DEVICE) { + readingsSingleUpdate($hash, 'state', MMA845X_STATE_INVALID_DEVICE, 1); + } + } +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Setup() + + Parameters: + $hash: hash reference of device instance + + Returns: nothing + +=cut + +sub I2C_MMA845X_Setup($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $stage = 0; + if (defined($hash->{setup}) && $hash->{setup} >= 0 && $hash->{setup} <= 6) { + $stage = $hash->{setup}; + } + + #Log3($hash, 1, "$name setup stage $stage"); + + if ($stage == 0) + { + # disable measurement + I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, 0); + + # activate 4 g full scale range and optionally the high-pass filter + $hash->{XYZ_DATA_CFG} = MMA845X_BITS_FS_4G; + $hash->{XYZ_DATA_CFG} |= MMA845X_BIT_XYZ_DATA_CFG_HPF_OUT if (index(AttrVal($name, 'highPass', ''), 'outputData') >= 0); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_XYZ_DATA_CFG, $hash->{XYZ_DATA_CFG}); + + # set the high-pass filter cutoff frequency and optionally disable the high-pass filter + $hash->{HP_FILTER_CUTOFF} = AttrVal($name, 'highPassCutoffFrequency', 0); + $hash->{HP_FILTER_CUTOFF} |= MMA845X_BIT_HP_FILTER_PULSE_BYP if (index(AttrVal($name, 'highPass', 'pulse'), 'pulse') < 0); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_HP_FILTER_CUTOFF, $hash->{HP_FILTER_CUTOFF}); + } + + elsif ($stage == 1) + { + # prepare control registers 4 and 5 (interrupt configuration) + $hash->{CTRL_REG4} = 0; + $hash->{CTRL_REG5} = 0; + + # enable orientation detection + my $orientatonEvent = AttrVal($name, 'orientatonEvent', 1); + if ($orientatonEvent) { + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_CFG, MMA845X_BIT_PL_CFG_PL_EN); + + # configure interrupt + my $orientationInterrupt = AttrVal($name, 'orientationInterrupt', 0); + $hash->{CTRL_REG4} |= MMA845X_BIT_INT_EN_LNDPRT if ($orientationInterrupt); + $hash->{CTRL_REG5} |= MMA845X_BIT_INT_CFG_LNDPRT if ($orientationInterrupt == 1); + + # configure parameters + my $orientationDebounce = AttrVal($name, 'orientationDebounce', 100); # 500 ms default with MODS=Normal ODR=200Hz = 5 ms steps + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_COUNT, $orientationDebounce); + + my $orientationZLockThreshold = AttrVal($name, 'orientationZLockThreshold', 4); # Z 29° default + my $orientationBFTripAngleThreshold = AttrVal($name, 'orientationBFTripAngleThreshold', 1) << 6; # Z < 75° or Z > 285° default + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_BF_ZCOMP, $orientationBFTripAngleThreshold + $orientationZLockThreshold); + + my $orientationPLTripAngleHysteresis = AttrVal($name, 'orientationPLTripAngleHysteresis', 4); # ±14° default + my $orientationPLTripAngleThreshold = AttrVal($name, 'orientationPLTripAngleThreshold', 0x10) << 3; # 45° default + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_THS_REG, $orientationPLTripAngleHysteresis + $orientationPLTripAngleThreshold); + } else { + # disable orientation detection + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PL_CFG, 0); + } + } + + elsif ($stage == 2) + { + # enable freefall/motion detection + my $motionEvent = AttrVal($name, 'motionEvent', ''); + if (length($motionEvent) > 0 && $motionEvent ne '1') { + my $motionCfg = 0; + if (index($motionEvent, 'X') >= 0) { + $motionCfg |= MMA845X_BIT_FF_MT_CFG_EFE_X; + } + if (index($motionEvent, 'Y') >= 0) { + $motionCfg |= MMA845X_BIT_FF_MT_CFG_EFE_Y; + } + if (index($motionEvent, 'Z') >= 0) { + $motionCfg |= MMA845X_BIT_FF_MT_CFG_EFE_Z; + } + if ($motionCfg) { + # configure motion detection + if (AttrVal($name, 'motionEventLatch', 0)) { # default: interrupt will be automatically reset + $motionCfg |= MMA845X_BIT_FF_MT_CFG_ELE; + } + if (AttrVal($name, 'motionMode', 'motion') eq 'motion') { + $motionCfg |= MMA845X_BIT_FF_MT_CFG_OAE; + } + I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_CFG, $motionCfg); + + # configure interrupt + my $motionInterrupt = AttrVal($name, 'motionInterrupt', 0); + $hash->{CTRL_REG4} |= MMA845X_BIT_INT_EN_FF_MT if ($motionInterrupt); + $hash->{CTRL_REG5} |= MMA845X_BIT_INT_CFG_FF_MT if ($motionInterrupt == 1); + + # configure parameters + my $motionThreshold = AttrVal($name, 'motionThreshold', 0x01); # 0.063g default + I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_THS, $motionThreshold); + + my $motionDebounce = AttrVal($name, 'motionDebounce', 0x00); # 0 ms default with MODS=Normal ODR=200Hz = 5 ms steps + I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_COUNT, $motionDebounce); + } else { + # disable motion detection + I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_CFG, 0); + } + } else { + # disable motion detection + I2C_MMA845X_Write($hash, MMA845X_REGISTER_FF_MT_CFG, 0); + } + } + + elsif ($stage == 3) + { + # enable jolt detection + my $joltEvent = AttrVal($name, 'joltEvent', ''); + if (length($joltEvent) > 0 && $joltEvent ne '1') { + my $joltCfg = 0; + if (index($joltEvent, 'X') >= 0) { + $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_EFE_X; + } + if (index($joltEvent, 'Y') >= 0) { + $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_EFE_Y; + } + if (index($joltEvent, 'Z') >= 0) { + $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_EFE_Z; + } + if ($joltCfg) { + # configure jolt detection and optionally disable the high-pass filter + $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_HPF_BYP if (index(AttrVal($name, 'highPass', 'jolt'), 'jolt') < 0); + if (AttrVal($name, 'joltEventLatch', 0)) { # default: interrupt will be automatically reset + $joltCfg |= MMA845X_BIT_TRANSIENT_CFG_ELE; + } + I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_CFG, $joltCfg); + + # configure interrupt + my $joltInterrupt = AttrVal($name, 'joltInterrupt', 0); + $hash->{CTRL_REG4} |= MMA845X_BIT_INT_EN_TRANS if ($joltInterrupt); + $hash->{CTRL_REG5} |= MMA845X_BIT_INT_CFG_TRANS if ($joltInterrupt == 1); + + # configure parameters + my $joltThreshold = AttrVal($name, 'joltThreshold', 0x01); # 0.063g default + I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_THS, $joltThreshold); + + my $joltDebounce = AttrVal($name, 'joltDebounce', 0x00); # 0 ms default with MODS=Normal ODR=200Hz = 5 ms steps + I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_COUNT, $joltDebounce); + } else { + # disable jolt detection + I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_CFG, 0); + } + } else { + # disable jolt detection + I2C_MMA845X_Write($hash, MMA845X_REGISTER_TRANSIENT_CFG, 0); + } + } + + elsif ($stage == 4) + { + # enable pulse detection + my $pulseEvent = AttrVal($name, 'pulseEvent', ''); + if (length($pulseEvent) > 0 && $pulseEvent ne '1') { + my $pulseCfg = 0; + if (index($pulseEvent, 'XS') >= 0) { + $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_XS; + } + if (index($pulseEvent, 'XD') >= 0) { + $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_XD; + } + if (index($pulseEvent, 'YS') >= 0) { + $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_YS; + } + if (index($pulseEvent, 'YD') >= 0) { + $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_YD; + } + if (index($pulseEvent, 'ZS') >= 0) { + $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_ZS; + } + if (index($pulseEvent, 'ZD') >= 0) { + $pulseCfg |= MMA845X_BIT_PULSE_CFG_EFE_ZD; + } + if ($pulseCfg) { + # configure pulse detection + if (AttrVal($name, 'pulseEventLatch', 0)) { # default: interrupt will be automatically reset after latency duration + $pulseCfg |= MMA845X_BIT_PULSE_CFG_ELE; + } + $pulseCfg |= MMA845X_BIT_PULSE_CFG_DPA; # enable single and double pulse detection abort on timing mismatch + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_CFG, $pulseCfg); + + # configure interrupt + my $pulseInterrupt = AttrVal($name, 'pulseInterrupt', 0); + $hash->{CTRL_REG4} |= MMA845X_BIT_INT_EN_PULSE if ($pulseInterrupt); + $hash->{CTRL_REG5} |= MMA845X_BIT_INT_CFG_PULSE if ($pulseInterrupt == 1); + + # configure parameters + my $pulseThresholdX = AttrVal($name, 'pulseThresholdX', 0x10); # 1g default + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_THSX, $pulseThresholdX); + my $pulseThresholdY = AttrVal($name, 'pulseThresholdY', 0x10); # 1g default + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_THSY, $pulseThresholdY); + my $pulseThresholdZ = AttrVal($name, 'pulseThresholdZ', 0x30); # 3g default + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_THSZ, $pulseThresholdZ); + + my $pulseWindow = AttrVal($name, 'pulseWindow', 0x30); # 60 ms default with MODS=Normal ODR=200Hz = 1.25 ms steps + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_TMLT, $pulseWindow); + + my $pulseLatency = AttrVal($name, 'pulseLatency', 0x50); # 200 ms default with MODS=Normal ODR=200Hz = 2.5 ms steps + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_LTCY, $pulseLatency); + + my $pulseWindow2 = AttrVal($name, 'pulseWindow2', 0x78); # 300 ms default with MODS=Normal ODR=200Hz = 2.5 ms steps + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_WIND, $pulseWindow2); + } else { + # disable pulse detection + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_CFG, 0); + } + } else { + # disable pulse detection + I2C_MMA845X_Write($hash, MMA845X_REGISTER_PULSE_CFG, 0); + } + } + + elsif ($stage == 5) + { + # write control registers 4 and 5 (interrupt configuration) + I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG4, $hash->{CTRL_REG4}); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG5, $hash->{CTRL_REG5}); + + # rewrite last calibration + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_X, I2C_MMA845X_G_TO_OFF($hash, ReadingsVal($name, 'offX', 0))); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Y, I2C_MMA845X_G_TO_OFF($hash, ReadingsVal($name, 'offY', 0))); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Z, I2C_MMA845X_G_TO_OFF($hash, ReadingsVal($name, 'offZ', 0))); + + # activate measurement (low noise filter = max. 4g, MODS=Normal) + $hash->{CTRL_REG1} = MMA845X_BIT_CTRL_REG1_ACTIVE | MMA845X_BIT_CTRL_REG1_LNOISE; + my $outputDataRate = AttrVal($name, 'outputDataRate', 200); + if ($outputDataRate == 1.56) { + $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_1_56HZ; + } elsif ($outputDataRate == 6.25) { + $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_6_25HZ; + } elsif ($outputDataRate == 12.5) { + $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_12_5HZ; + } elsif ($outputDataRate == 50) { + $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_50HZ; + } elsif ($outputDataRate == 100) { + $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_100HZ; + } elsif ($outputDataRate == 200) { + $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_200HZ; + } elsif ($outputDataRate == 400) { + $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_400HZ; + } else { + $hash->{CTRL_REG1} |= MMA845X_BITS_ODR_800HZ; + } + I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1}); + } + + elsif ($stage >= 6) + { + # setup completed + delete $hash->{setup}; + $hash->{I2C_PendingRequests} = 0; + readingsSingleUpdate($hash, 'state', MMA845X_STATE_INITIALIZED, 1); + return; + } + + # prepare next setup stage + $stage++; + $hash->{setup} = $stage; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_OUT_XYZ() + + Parameters: + $hash: hash reference of device instance + $bytes: byte array reference of I2C register values + + Returns: nothing + +=cut + +sub I2C_MMA845X_OUT_XYZ($$) { + my ($hash, $bytes) = @_; + my $name = $hash->{NAME}; + + # extract sample values assuming max. resolution (CTRL_REG1:F_READ=0) + readingsBeginUpdate($hash); + for (my $i=0; $i<3; $i++) { + my $sample = $$bytes[2*$i] << 8 | $$bytes[2*$i + 1]; # combine MSB/LSB + $sample = ($sample >> 2) & 0x3FFF if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit adjust + $sample = ($sample >> 4) & 0x0FFF if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit adjust + $sample = ($sample >> 6) & 0x03FF if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit adjust + if ($$bytes[2*$i] & 0x80) { + $sample -= 0x4000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit 2's complement negative value transform + $sample -= 0x1000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit 2's complement negative value transform + $sample -= 0x0400 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit 2's complement negative value transform + } + my $range = 2 << ($hash->{XYZ_DATA_CFG} & 0x3); # [1 g = 9.80665 m/s2] + $hash->{accelerations}[$i] = $range * $sample; + $hash->{accelerations}[$i] /= 0x2000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit scale to [g] + $hash->{accelerations}[$i] /= 0x0800 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit scale to [g] + $hash->{accelerations}[$i] /= 0x0200 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit scale to [g] + Log3($hash, 5, $hash->{NAME} . " i $i sample $sample range $range acceleration $hash->{accelerations}[$i]"); + readingsBulkUpdate($hash, "outX", sprintf('%0.3f', $hash->{accelerations}[$i])) if ($i == 0); + readingsBulkUpdate($hash, "outY", sprintf('%0.3f', $hash->{accelerations}[$i])) if ($i == 1); + readingsBulkUpdate($hash, "outZ", sprintf('%0.3f', $hash->{accelerations}[$i])) if ($i == 2); + } + readingsEndUpdate($hash, 1); + + # calibrate offset + if (ReadingsVal($name, 'state', '?') eq MMA845X_STATE_CALIBRATING) { + I2C_MMA845X_Calibrate($hash); + } +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_PL_STATUS() + + Parameters: + $hash: hash reference of device instance + $byte: I2C register value + + Returns: nothing + +=cut + +sub I2C_MMA845X_PL_STATUS($$) { + my ($hash, $byte) = @_; + my $name = $hash->{NAME}; + + my $orientation = ''; + if ($byte & MMA845X_BIT_PL_STATUS_NEWLP) { + my $lapo = $byte & MMA845X_BITS_PL_LAPO_LL; + if ($lapo == MMA845X_BITS_PL_LAPO_PU) { + $orientation .= 'PU'; + } elsif ($lapo == MMA845X_BITS_PL_LAPO_PD) { + $orientation .= 'PD'; + } elsif ($lapo == MMA845X_BITS_PL_LAPO_LR) { + $orientation .= 'LR'; + } else { + $orientation .= 'LL'; + } + if ($byte & MMA845X_BIT_PL_STATUS_BAFRO) { + $orientation .= 'B'; + } else { + $orientation .= 'F'; + } + if ($byte & MMA845X_BIT_PL_STATUS_LO) { + $orientation .= 'X'; + } + + if (ReadingsVal($name, 'orientation', '') ne $orientation) { + readingsSingleUpdate($hash, 'orientation', $orientation, 1); + } + } +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_PULSE_SRC() + + Parameters: + $hash: hash reference of device instance + $byte: I2C register value + + Returns: nothing + +=cut + +sub I2C_MMA845X_PULSE_SRC($$) { + my ($hash, $byte) = @_; + my $name = $hash->{NAME}; + + my $eventSource = ''; + if ($byte & MMA845X_BIT_PULSE_SRC_EA) { + if ($byte & MMA845X_BIT_PULSE_SRC_AX_X) { + if ($byte & MMA845X_BIT_PULSE_SRC_POL_X) { + $eventSource .= '-X'; + } else { + $eventSource .= '+X'; + } + if ($byte & MMA845X_BIT_PULSE_SRC_DPE) { + $eventSource .= 'D'; + } else { + $eventSource .= 'S'; + } + } + if ($byte & MMA845X_BIT_PULSE_SRC_AX_Y) { + if ($byte & MMA845X_BIT_PULSE_SRC_POL_Y) { + $eventSource .= '-Y'; + } else { + $eventSource .= '+Y'; + } + if ($byte & MMA845X_BIT_PULSE_SRC_DPE) { + $eventSource .= 'D'; + } else { + $eventSource .= 'S'; + } + } + if ($byte & MMA845X_BIT_PULSE_SRC_AX_Z) { + if ($byte & MMA845X_BIT_PULSE_SRC_POL_Z) { + $eventSource .= '-Z'; + } else { + $eventSource .= '+Z'; + } + if ($byte & MMA845X_BIT_PULSE_SRC_DPE) { + $eventSource .= 'D'; + } else { + $eventSource .= 'S'; + } + } + } + + if (ReadingsVal($name, 'pulseEvent', '') ne $eventSource) { + readingsSingleUpdate($hash, 'pulseEvent', $eventSource, 1); + } +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_FF_MT_SRC() + + Parameters: + $hash: hash reference of device instance + $byte: I2C register value + + Returns: nothing + +=cut + +sub I2C_MMA845X_FF_MT_SRC($$) { + my ($hash, $byte) = @_; + my $name = $hash->{NAME}; + + my $eventSource = ''; + if ($byte & MMA845X_BIT_FF_MT_SRC_EA) { + if ($byte & MMA845X_BIT_FF_MT_SRC_AX_X) { + if ($byte & MMA845X_BIT_FF_MT_SRC_POL_X) { + $eventSource .= '-X'; + } else { + $eventSource .= '+X'; + } + } + if ($byte & MMA845X_BIT_FF_MT_SRC_AX_Y) { + if ($byte & MMA845X_BIT_FF_MT_SRC_POL_Y) { + $eventSource .= '-Y'; + } else { + $eventSource .= '+Y'; + } + } + if ($byte & MMA845X_BIT_FF_MT_SRC_AX_Z) { + if ($byte & MMA845X_BIT_FF_MT_SRC_POL_Z) { + $eventSource .= '-Z'; + } else { + $eventSource .= '+Z'; + } + } + } + + if (ReadingsVal($name, 'motionEvent', '') ne $eventSource) { + readingsSingleUpdate($hash, 'motionEvent', $eventSource, 1); + } +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_TRANSIENT_SRC() + + Parameters: + $hash: hash reference of device instance + $byte: I2C register value + + Returns: nothing + +=cut + +sub I2C_MMA845X_TRANSIENT_SRC($$) { + my ($hash, $byte) = @_; + my $name = $hash->{NAME}; + + my $eventSource = ''; + if ($byte & MMA845X_BIT_TRANSIENT_SRC_EA) { + if ($byte & MMA845X_BIT_TRANSIENT_SRC_AX_X) { + if ($byte & MMA845X_BIT_TRANSIENT_SRC_POL_X) { + $eventSource .= '-X'; + } else { + $eventSource .= '+X'; + } + } + if ($byte & MMA845X_BIT_TRANSIENT_SRC_AX_Y) { + if ($byte & MMA845X_BIT_TRANSIENT_SRC_POL_Y) { + $eventSource .= '-Y'; + } else { + $eventSource .= '+Y'; + } + } + if ($byte & MMA845X_BIT_TRANSIENT_SRC_AX_Z) { + if ($byte & MMA845X_BIT_TRANSIENT_SRC_POL_Z) { + $eventSource .= '-Z'; + } else { + $eventSource .= '+Z'; + } + } + } + + if (ReadingsVal($name, 'joltEvent', '') ne $eventSource) { + readingsSingleUpdate($hash, 'joltEvent', $eventSource, 1); + } +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Get() + + Parameters: + $hash: hash reference of device instance + @args: array of arguments + + Returns: undef on success or string with error message + +=cut + +sub I2C_MMA845X_Get($@) { + my ($hash, $name, $cmd, @a) = @_; + my $result = undef; + + if ($cmd eq 'accelerations') { + I2C_MMA845X_Poll($hash, 'accelerations'); + if ($hash->{I2C_Blocking}) { + return "$name $cmd => x:$hash->{accelerations}[0]g y:$hash->{accelerations}[1]g z:$hash->{accelerations}[2]g"; + } + } elsif ($cmd eq 'orientation') { + I2C_MMA845X_Poll($hash, 'orientation'); + if ($hash->{I2C_Blocking}) { + return "$name $cmd => orientation:" . ReadingsVal($name, 'orientation', '?'); + } + } elsif ($cmd eq 'eventSources') { + I2C_MMA845X_Poll($hash, 'eventSources'); + if ($hash->{I2C_Blocking}) { + return "$name $cmd => pulse:" . ReadingsVal($name, 'pulseEvent', '') . + " motion:" . ReadingsVal($name, 'motionEvent', '') . + " jolt:" . ReadingsVal($name, 'joltEvent', ''); + } + } elsif ($cmd eq 'update') { + I2C_MMA845X_Poll($hash, 'update'); + } else { + $result = "I2C_MMA845X: unknown get command " . $cmd . ", choose one of accelerations:noArg orientation:noArg eventSources:noArg update:noArg"; + } + + return $result; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Set() + + Parameters: + $hash: hash reference of device instance + @args: array of arguments + + Returns: undef on success or string with error message + +=cut + +sub I2C_MMA845X_Set($@) { + my ($hash, $name, $cmd, @args) = @_; + my $result = undef; + + if ($cmd eq 'calibrate') { + if ($hash->{XYZ_DATA_CFG} & MMA845X_BIT_XYZ_DATA_CFG_HPF_OUT) { + $result = "I2C_MMA845X: calibration only available with high-pass filter disabled"; + } else { + I2C_MMA845X_StartCalibration($hash); + } + } else { + $result = "I2C_MMA845X: unknown set command " . $cmd . ", choose one of calibrate:noArg"; + } + + return $result; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_G_TO_OFF() + + Parameters: + $hash: hash reference of device instance + $accel: decimal number, acceleration [g] + + Returns: raw offset value + +=cut + +sub I2C_MMA845X_G_TO_OFF($$) { + my ($hash, $result) = @_; + + Log3($hash, 5, $hash->{NAME} . " to byte 1 $result"); + + $result *= 0x2000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit scale from [g] + $result *= 0x0800 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit scale from [g] + $result *= 0x0200 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit scale from [g] + + my $range = 2 << ($hash->{XYZ_DATA_CFG} & 0x3); # [1 g = 9.80665 m/s2] + $result /= $range; + + $range = 0.125 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit offset scale + $range = 0.5 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit offset scale + $range = 2.0 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit offset scale + $result *= $range; + + $result = -127 if ($result < -127); # byte range limiter + $result = 127 if ($result > 127); # byte range limiter + + $result += 0x0100 if ($result < 0); # 2's complement transform + + Log3($hash, 5, $hash->{NAME} . " to byte 4 $result"); + + return $result; + } + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_OFF_TO_G() + + Parameters: + $hash: hash reference of device instance + $offset: raw offset value + + Returns: decimal number, acceleration [g] + +=cut + +sub I2C_MMA845X_OFF_TO_G($$) { + my ($hash, $result) = @_; + + $result -= 0x0100 if ($result > 0x7F); # 2's complement transform + + my $range = 2 << ($hash->{XYZ_DATA_CFG} & 0x3); # [1 g = 9.80665 m/s2] + $result *= $range; + + $range = 0.125 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit offset scale + $range = 0.5 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit offset scale + $range = 2.0 * (1 << ($hash->{XYZ_DATA_CFG} & 0x3)) if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit offset scale + $result /= $range; + + $result /= 0x2000 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8451); # 14-bit scale to [g] + $result /= 0x0800 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8452); # 12-bit scale to [g] + $result /= 0x0200 if ($hash->{MODEL} == MMA845X_DEVICE_CODE_MMA8453); # 10-bit scale to [g] + + Log3($hash, 5, $hash->{NAME} . " to g 5 $result"); + + return $result; + } + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_StartCalibration() + + Parameters: + $hash: hash reference of device instance + + Returns: nothing + +=cut + +sub I2C_MMA845X_StartCalibration($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + if (!AttrVal($hash->{NAME}, "disable", 0)) { + # disable measurement + $hash->{CTRL_REG1} &= ~MMA845X_BIT_CTRL_REG1_ACTIVE; + I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1}); + + # clear current calibration values + for (my $i=0; $i<3; $i++) { + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_X, 0) if ($i == 0); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Y, 0) if ($i == 1); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Z, 0) if ($i == 2); + } + + # reenable measurement + $hash->{CTRL_REG1} |= MMA845X_BIT_CTRL_REG1_ACTIVE; + I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1}); + + # trigger new measurement + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_MMA845X_Poll', $hash, 0); + readingsSingleUpdate($hash, 'state', MMA845X_STATE_CALIBRATING, 1); + } +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Calibrate() + + Parameters: + $hash: hash reference of device instance + + Returns: nothing + +=cut + +sub I2C_MMA845X_Calibrate($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my @cal; + + # disable measurement + $hash->{CTRL_REG1} &= ~MMA845X_BIT_CTRL_REG1_ACTIVE; + I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1}); + + # detect gravity axis and direction by highest absolute output value + my $gravityIndex = 0; + for (my $i=1; $i<3; $i++) { + if (abs($hash->{accelerations}[$i]) > abs($hash->{accelerations}[$gravityIndex])) { + $gravityIndex = $i; + } + } + my $gravityDirection = $hash->{accelerations}[$gravityIndex]<=>0; + + Log3($hash, 5, $hash->{NAME} . " Calibrate gravity index $gravityIndex, gravity direction $gravityDirection"); + + readingsBeginUpdate($hash); + for (my $i=0; $i<3; $i++) { + # calculate calibration values based on current samples assuming orthogonal orientation + $cal[$i] = -$hash->{accelerations}[$i] if ($i != $gravityIndex); + $cal[$i] = -$hash->{accelerations}[$i] + $gravityDirection if ($i == $gravityIndex); + $cal[$i] = I2C_MMA845X_G_TO_OFF($hash, $cal[$i]); + + # write new calibration values + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_X, $cal[$i]) if ($i == 0); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Y, $cal[$i]) if ($i == 1); + I2C_MMA845X_Write($hash, MMA845X_REGISTER_OFF_Z, $cal[$i]) if ($i == 2); + + # save offsets + readingsBulkUpdate($hash, "offX", I2C_MMA845X_OFF_TO_G($hash, $cal[$i])) if ($i == 0); + readingsBulkUpdate($hash, "offY", I2C_MMA845X_OFF_TO_G($hash, $cal[$i])) if ($i == 1); + readingsBulkUpdate($hash, "offZ", I2C_MMA845X_OFF_TO_G($hash, $cal[$i])) if ($i == 2); + + # modify readings + readingsBulkUpdate($hash, "outX", sprintf('%0.3f', $hash->{accelerations}[$i] + ReadingsVal($name, 'offX', 0))) if ($i == 0); + readingsBulkUpdate($hash, "outY", sprintf('%0.3f', $hash->{accelerations}[$i] + ReadingsVal($name, 'offY', 0))) if ($i == 1); + readingsBulkUpdate($hash, "outZ", sprintf('%0.3f', $hash->{accelerations}[$i] + ReadingsVal($name, 'offZ', 0))) if ($i == 2); + } + readingsEndUpdate($hash, 1); + + # reenable measurement + $hash->{CTRL_REG1} |= MMA845X_BIT_CTRL_REG1_ACTIVE; + I2C_MMA845X_Write($hash, MMA845X_REGISTER_CTRL_REG1, $hash->{CTRL_REG1}); + readingsSingleUpdate($hash, 'state', MMA845X_STATE_INITIALIZED, 1); +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Undef() + + Parameters: + $hash: hash reference of device instance + $name: string name of device + + Returns: undef on success + +=cut + +sub I2C_MMA845X_Undef($$) { + my ($hash, $name) = @_; + + RemoveInternalTimer($hash); + + return undef; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Read() + + Parameters: + $hash: hash reference of device instance + $reg: integer, I2C register to read + $nbytes: integer, number of bytes to read + + Returns: 1 on success, 0 on error + +=cut + +sub I2C_MMA845X_Read($$$) { + my ($hash, $reg, $nbytes) = @_; + + local $SIG{__WARN__} = sub { + my $message = shift; + # turn warnings from RPII2C_HWACCESS_ioctl into exception + if ($message =~ /Exiting subroutine via last at.*00_RPII2C.pm/) { + die; + } else { + warn($message); + } + }; + + my $success = 1; + if (defined (my $iodev = $hash->{IODev})) { + eval { + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cread", + i2caddress => $hash->{I2C_Address}, + reg => $reg, + nbyte => $nbytes + }); + }; + my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'}; + if (defined($sendStat) && $sendStat eq 'error') { + readingsSingleUpdate($hash, 'state', MMA845X_STATE_I2C_ERROR, 1); + Log3($hash, 5, $hash->{NAME} . " I2C read on $iodev->{NAME} failed"); + $success = 0; + } + } else { + Log3($hash, 1, $hash->{NAME} . " no IODev assigned"); + $success = 0; + } + + return $success; +} + +# ----------------------------------------------------------------------------- + +=item I2C_MMA845X_Write() + + Parameters: + $hash: hash reference of device instance + $reg: integer, I2C register to write + @data: array of byte to write + + Returns: 1 on success, 0 on error + +=cut + +sub I2C_MMA845X_Write($$$) { + my ($hash, $reg, @data) = @_; + + my $success = 1; + if (defined (my $iodev = $hash->{IODev})) { + eval { + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $hash->{I2C_Address}, + reg => $reg, + data => join (' ',@data), + }); + }; + my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'}; + if (defined($sendStat) && $sendStat eq 'error') { + readingsSingleUpdate($hash, 'state', MMA845X_STATE_I2C_ERROR, 1); + Log3($hash, 5, $hash->{NAME} . " I2C write on $iodev->{NAME} failed"); + $success = 0; + } + } else { + Log3($hash, 1, $hash->{NAME} . " no IODev assigned"); + $success = 0; + } + + return $success; +} + +# ----------------------------------------------------------------------------- + +1; + +# ----------------------------------------------------------------------------- + +=pod + +CHANGES + +19.03.2016 +- orientation detection added + +01.03.2016 +- high-pass filter attributes added + +27.02.2016 +- event latch support added +- freefall/motion detection added +- jolt detection added + +21.02.2016 +- pulse detection added + +19.02.2016 created +- acceleration measurement and calibration added + +=cut + +# ----------------------------------------------------------------------------- + +=pod +=begin html + + +

I2C_MMA845X

+ + +=end html +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 3195a6aba..6b3b825c6 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -195,6 +195,7 @@ FHEM/52_I2C_K30 yoda_gh http://forum.fhem.de Sonstige FHEM/52_I2C_MCP23008 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_MCP23017 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_MCP342x klausw http://forum.fhem.de Sonstige Systeme +FHEM/52_I2C_MMA845X jensb http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_PCA9532 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_PCA9685 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_PCF8574 klausw http://forum.fhem.de Sonstige Systeme