magfit_motors.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #!/usr/bin/env python
  2. '''
  3. fit best estimate of magnetometer offsets, trying to take into account motor interference
  4. '''
  5. from __future__ import print_function
  6. from builtins import range
  7. from argparse import ArgumentParser
  8. parser = ArgumentParser(description=__doc__)
  9. parser.add_argument("--no-timestamps",dest="notimestamps", action='store_true', help="Log doesn't have timestamps")
  10. parser.add_argument("--condition",dest="condition", default=None, help="select packets by condition")
  11. parser.add_argument("--noise", type=float, default=0, help="noise to add")
  12. parser.add_argument("logs", metavar="LOG", nargs="+")
  13. args = parser.parse_args()
  14. from pymavlink import mavutil
  15. from pymavlink.rotmat import Vector3
  16. def noise():
  17. '''a noise vector'''
  18. from random import gauss
  19. v = Vector3(gauss(0, 1), gauss(0, 1), gauss(0, 1))
  20. v.normalize()
  21. return v * args.noise
  22. def select_data(data):
  23. ret = []
  24. counts = {}
  25. for d in data:
  26. (mag,motor) = d
  27. key = "%u:%u:%u" % (mag.x/20,mag.y/20,mag.z/20)
  28. if key in counts:
  29. counts[key] += 1
  30. else:
  31. counts[key] = 1
  32. if counts[key] < 3:
  33. ret.append(d)
  34. print(len(data), len(ret))
  35. return ret
  36. def radius(d, offsets, motor_ofs):
  37. '''return radius give data point and offsets'''
  38. (mag, motor) = d
  39. return (mag + offsets + motor*motor_ofs).length()
  40. def radius_cmp(a, b, offsets, motor_ofs):
  41. '''return radius give data point and offsets'''
  42. diff = radius(a, offsets, motor_ofs) - radius(b, offsets, motor_ofs)
  43. if diff > 0:
  44. return 1
  45. if diff < 0:
  46. return -1
  47. return 0
  48. def sphere_error(p, data):
  49. x,y,z,mx,my,mz,r = p
  50. ofs = Vector3(x,y,z)
  51. motor_ofs = Vector3(mx,my,mz)
  52. ret = []
  53. for d in data:
  54. (mag,motor) = d
  55. err = r - radius((mag,motor), ofs, motor_ofs)
  56. ret.append(err)
  57. return ret
  58. def fit_data(data):
  59. from scipy import optimize
  60. p0 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  61. p1, ier = optimize.leastsq(sphere_error, p0[:], args=(data))
  62. if not ier in [1, 2, 3, 4]:
  63. raise RuntimeError("Unable to find solution")
  64. return (Vector3(p1[0], p1[1], p1[2]), Vector3(p1[3], p1[4], p1[5]), p1[6])
  65. def magfit(logfile):
  66. '''find best magnetometer offset fit to a log file'''
  67. print("Processing log %s" % filename)
  68. mlog = mavutil.mavlink_connection(filename, notimestamps=args.notimestamps)
  69. data = []
  70. last_t = 0
  71. offsets = Vector3(0,0,0)
  72. motor_ofs = Vector3(0,0,0)
  73. motor = 0.0
  74. # now gather all the data
  75. while True:
  76. m = mlog.recv_match(condition=args.condition)
  77. if m is None:
  78. break
  79. if m.get_type() == "PARAM_VALUE" and m.param_id == "RC3_MIN":
  80. rc3_min = float(m.param_value)
  81. if m.get_type() == "SENSOR_OFFSETS":
  82. # update current offsets
  83. offsets = Vector3(m.mag_ofs_x, m.mag_ofs_y, m.mag_ofs_z)
  84. if m.get_type() == "SERVO_OUTPUT_RAW":
  85. motor_pwm = m.servo1_raw + m.servo2_raw + m.servo3_raw + m.servo4_raw
  86. motor_pwm *= 0.25
  87. rc3_min = mlog.param('RC3_MIN', 1100)
  88. rc3_max = mlog.param('RC3_MAX', 1900)
  89. motor = (motor_pwm - rc3_min) / (rc3_max - rc3_min)
  90. if motor > 1.0:
  91. motor = 1.0
  92. if motor < 0.0:
  93. motor = 0.0
  94. if m.get_type() == "RAW_IMU":
  95. mag = Vector3(m.xmag, m.ymag, m.zmag)
  96. # add data point after subtracting the current offsets
  97. data.append((mag - offsets + noise(), motor))
  98. print("Extracted %u data points" % len(data))
  99. print("Current offsets: %s" % offsets)
  100. data = select_data(data)
  101. # do an initial fit with all data
  102. (offsets, motor_ofs, field_strength) = fit_data(data)
  103. for count in range(3):
  104. # sort the data by the radius
  105. data.sort(lambda a,b : radius_cmp(a,b,offsets,motor_ofs))
  106. print("Fit %u : %s %s field_strength=%6.1f to %6.1f" % (
  107. count, offsets, motor_ofs,
  108. radius(data[0], offsets, motor_ofs), radius(data[-1], offsets, motor_ofs)))
  109. # discard outliers, keep the middle 3/4
  110. data = data[len(data)/8:-len(data)/8]
  111. # fit again
  112. (offsets, motor_ofs, field_strength) = fit_data(data)
  113. print("Final : %s %s field_strength=%6.1f to %6.1f" % (
  114. offsets, motor_ofs,
  115. radius(data[0], offsets, motor_ofs), radius(data[-1], offsets, motor_ofs)))
  116. print("mavgraph.py '%s' 'mag_field(RAW_IMU)' 'mag_field_motors(RAW_IMU,SENSOR_OFFSETS,(%f,%f,%f),SERVO_OUTPUT_RAW,(%f,%f,%f))'" % (
  117. filename,
  118. offsets.x,offsets.y,offsets.z,
  119. motor_ofs.x, motor_ofs.y, motor_ofs.z))
  120. total = 0.0
  121. for filename in args.logs:
  122. magfit(filename)