Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect calculation in shake() method #78

Open
BooleanMattock opened this issue Feb 8, 2023 · 0 comments
Open

Incorrect calculation in shake() method #78

BooleanMattock opened this issue Feb 8, 2023 · 0 comments

Comments

@BooleanMattock
Copy link

BooleanMattock commented Feb 8, 2023

The code for adafruit.lis3dh.shake() appears that it is sampling (x,y,z) acceleration values, and keeping a separate running total of x-acceleration, y-acceleration, and z-acceleration. It then divides the totals by the number of samples to get an average for each. It then computes sqrt(x * x + y * y + z * z) on the averages to give a magnitude, and compares it with a threshold.

I'm pretty sure this isn't correct. Imagine if you are shaking the accelerometer backwards and forwards parallel to the x-axis. That will give both positive and negative x-acceleration values: if you total them up, the positive and negative values will cancel out and you'll end up with approximately zero. That means when you compute the average and magnitude, you'll likely end up with a value below the threshold, no matter how vigorously you shake the accelerometer. Indeed when I shake the accelerometer and look at the values, sometimes total_accel is a lower value than when standing still.

The issue is the squaring is being delayed until after the summing. The tally should be the sum of the squares, not the square of the sums.

Can be demonstrated with this example code:

import math

# synthesize acceleration data for shaking that perfectly
# bounces between + and - 1G on all 3 axis
accel_data = (
    (-9.8, -9.8, -9.8),
    (9.8, 9.8, 9.8),
    (-9.8, -9.8, -9.8),
    (9.8, 9.8, 9.8),  
)

avg_count = len(accel_data)

shake_accel = (0, 0, 0)
for i in range(avg_count):
    shake_accel = tuple(map(sum, zip(shake_accel, accel_data[i])))
    print("shake_accel = ", shake_accel)

avg = tuple(value / avg_count for value in shake_accel)
print("avg = ", avg)
total_accel = math.sqrt(sum(map(lambda x: x * x, avg)))
print("total_accel = ", total_accel)

Which outputs:


$ python3 accel_test.py 
shake_accel =  (-9.8, -9.8, -9.8)
shake_accel =  (0.0, 0.0, 0.0)
shake_accel =  (-9.8, -9.8, -9.8)
shake_accel =  (0.0, 0.0, 0.0)
avg =  (0.0, 0.0, 0.0)
total_accel =  0.0


Probable Cause/Suggested Fix:

The code should calculate the magnitude of each acceleration (x,y,z) sample before averaging, not afterwards.

This can be done by evaluating magnitude = sqrt( x * x + y * y + z * z) for each sample, and then accumulating the total of the (scalar) magnitude values.

However, on platforms with limited CPU power and no FPU, this may be quite computationally expensive. One alternative is to evaluate (abs(x) + abs(y) + abs (z)) - this is guaranteed to be greater than the magnitude and less than sqrt(5)*magnitude, so (IMHO) it can be used as convenient proxy for the magnitude for shake detection (which doesn't need exact values).

Here is a code fragment that I have used and successfully tested as a substitute for the code in shake():


def ShakeCheck(shake_threshold, avg_count, total_delay):
    global lis3dh
    total_accel = 0
    sample_delay = total_delay / avg_count
    for _ in range(avg_count):
        # Sample the accelerometer, get the magnitude of acceleration,
        # and accumulate the total in total_accel
        # Magnitude should be calculated as sqrt(sum([x**2 for x in accel]))
        # but we can use sum of abs(x) as an approximation
        # to save CPU if needed
        accel = lis3dh.acceleration
        #print("Acceleration tuple= ", accel)
        magnitude = sum( [abs(x) for x in accel] )
        #print("Magnitude= ", magnitude)
        total_accel = total_accel + magnitude
        time.sleep(sample_delay)
    #print("Total shake_accel= ", total_accel)
    avg = total_accel/avg_count
    #print("Average accel= ", avg )
    return avg > shake_threshold

See https://forums.adafruit.com/viewtopic.php?t=198206

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant