2017-07-21 20:09:51 -03:00
|
|
|
from __future__ import print_function
|
|
|
|
|
2017-09-29 02:21:28 -03:00
|
|
|
from LogAnalyzer import Test, TestResult
|
2014-01-27 02:38:57 -04:00
|
|
|
import DataflashLog
|
2017-09-29 02:21:28 -03:00
|
|
|
from VehicleType import VehicleType
|
2014-01-27 02:38:57 -04:00
|
|
|
|
|
|
|
import numpy
|
|
|
|
|
|
|
|
|
|
|
|
class TestVibration(Test):
|
2014-08-12 12:54:15 -03:00
|
|
|
'''test for accelerometer vibration (accX/accY/accZ) within recommendations'''
|
2014-01-27 02:38:57 -04:00
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
def __init__(self):
|
|
|
|
Test.__init__(self)
|
|
|
|
self.name = "Vibration"
|
2014-01-27 02:38:57 -04:00
|
|
|
|
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
def run(self, logdata, verbose):
|
|
|
|
self.result = TestResult()
|
2014-01-27 02:38:57 -04:00
|
|
|
|
2017-09-23 22:51:51 -03:00
|
|
|
if logdata.vehicleType != VehicleType.Copter:
|
2014-08-12 12:54:15 -03:00
|
|
|
self.result.status = TestResult.StatusType.NA
|
|
|
|
return
|
2014-02-26 08:50:55 -04:00
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
# constants
|
|
|
|
gravity = -9.81
|
|
|
|
aimRangeWarnXY = 1.5
|
|
|
|
aimRangeFailXY = 3.0
|
|
|
|
aimRangeWarnZ = 2.0 # gravity +/- aim range
|
|
|
|
aimRangeFailZ = 5.0 # gravity +/- aim range
|
2014-01-27 02:38:57 -04:00
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
if not "IMU" in logdata.channels:
|
|
|
|
self.result.status = TestResult.StatusType.UNKNOWN
|
|
|
|
self.result.statusMessage = "No IMU log data"
|
|
|
|
return
|
2014-01-27 02:38:57 -04:00
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
# find some stable LOITER data to analyze, at least 10 seconds
|
|
|
|
chunks = DataflashLog.DataflashLogHelper.findLoiterChunks(logdata, minLengthSeconds=10, noRCInputs=True)
|
|
|
|
if not chunks:
|
|
|
|
self.result.status = TestResult.StatusType.UNKNOWN
|
|
|
|
self.result.statusMessage = "No stable LOITER log data found"
|
|
|
|
return
|
2014-01-27 02:38:57 -04:00
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
# for now we'll just use the first (largest) chunk of LOITER data
|
|
|
|
# TODO: ignore the first couple of secs to avoid bad data during transition - or can we check more analytically that we're stable?
|
|
|
|
# TODO: accumulate all LOITER chunks over min size, or just use the largest one?
|
|
|
|
startLine = chunks[0][0]
|
|
|
|
endLine = chunks[0][1]
|
2017-07-21 20:28:28 -03:00
|
|
|
#print("TestVibration using LOITER chunk from lines %s to %s" % (repr(startLine), repr(endLine)))
|
2014-01-27 02:38:57 -04:00
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
def getStdDevIMU(logdata, channelName, startLine,endLine):
|
|
|
|
loiterData = logdata.channels["IMU"][channelName].getSegment(startLine,endLine)
|
|
|
|
numpyData = numpy.array(loiterData.dictData.values())
|
|
|
|
return numpy.std(numpyData)
|
2014-01-27 02:38:57 -04:00
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
# use 2x standard deviations as the metric, so if 95% of samples lie within the aim range we're good
|
|
|
|
stdDevX = abs(2 * getStdDevIMU(logdata,"AccX",startLine,endLine))
|
|
|
|
stdDevY = abs(2 * getStdDevIMU(logdata,"AccY",startLine,endLine))
|
|
|
|
stdDevZ = abs(2 * getStdDevIMU(logdata,"AccZ",startLine,endLine))
|
|
|
|
if (stdDevX > aimRangeFailXY) or (stdDevY > aimRangeFailXY) or (stdDevZ > aimRangeFailZ):
|
|
|
|
self.result.status = TestResult.StatusType.FAIL
|
|
|
|
self.result.statusMessage = "Vibration too high (X:%.2fg, Y:%.2fg, Z:%.2fg)" % (stdDevX,stdDevY,stdDevZ)
|
|
|
|
elif (stdDevX > aimRangeWarnXY) or (stdDevY > aimRangeWarnXY) or (stdDevZ > aimRangeWarnZ):
|
|
|
|
self.result.status = TestResult.StatusType.WARN
|
|
|
|
self.result.statusMessage = "Vibration slightly high (X:%.2fg, Y:%.2fg, Z:%.2fg)" % (stdDevX,stdDevY,stdDevZ)
|
|
|
|
else:
|
|
|
|
self.result.status = TestResult.StatusType.GOOD
|
|
|
|
self.result.statusMessage = "Good vibration values (X:%.2fg, Y:%.2fg, Z:%.2fg)" % (stdDevX,stdDevY,stdDevZ)
|
2014-01-27 02:38:57 -04:00
|
|
|
|
2014-08-12 12:54:15 -03:00
|
|
|
|