phidgets_native 0.2.1 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,9 @@
3
3
  const char MSG_COMPASS_CORRECTION_NOT_ARRAY[] = "compass correction must be a 13 element array";
4
4
  const char MSG_COMPASS_CORRECTION_MUST_BE_FLOAT[] = "compass correction elements must be float or fixnum";
5
5
  const char MSG_DATA_RATE_MUST_BE_NUM[] = "data rate must be fixnum";
6
+ const char MSG_AROUND_MUST_BE_NUMERIC[] = "rotation angles must be fixnums or floats";
7
+ const char MSG_IN_ORDER_MUST_BE_STRING[] = "rotation order must be a string";
8
+ const char MSG_INVALID_ROTATION_AXIS[] = "invalid rotation axis in rotation order, must be x, y, or z";
6
9
 
7
10
  /*
8
11
  * Document-class: PhidgetsNative::Spatial < PhidgetsNative::Device
@@ -13,6 +16,10 @@ const char MSG_DATA_RATE_MUST_BE_NUM[] = "data rate must be fixnum";
13
16
  */
14
17
 
15
18
  void Init_phidgets_native_spatial(VALUE m_Phidget) {
19
+ // We need this for the orientation code mostly:
20
+ rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("matrix"));
21
+
22
+ // And now attach methods to the object:
16
23
  VALUE c_Device = rb_const_get(m_Phidget, rb_intern("Device"));
17
24
 
18
25
  VALUE c_Spatial = rb_define_class_under(m_Phidget,"Spatial",c_Device);
@@ -311,7 +318,134 @@ void Init_phidgets_native_spatial(VALUE m_Phidget) {
311
318
  */
312
319
  rb_define_method(c_Spatial, "data_rate", spatial_data_rate_get, 0);
313
320
 
321
+ /*
322
+ * call-seq:
323
+ * direction_cosine_matrix( FixNum around_x, FixNum around_y, FixNum around_z, String in_order = 'xyz' ) -> Matrix
324
+ * This private method is used internally to return a single direction cosine
325
+ * Matrix that represents the combined translations provided around the x,y,
326
+ * and z axis. The optional in_order will translate around the axis in the
327
+ * order provided. Angles are expected to be in radians.
328
+ */
329
+ rb_define_private_method(c_Spatial, "direction_cosine_matrix", spatial_direction_cosine_matrix, -1);
330
+
331
+ /*
332
+ * call-seq:
333
+ * accelerometer_to_roll_and_pitch -> Array
334
+ * This private method is used internally to return an array whose first double
335
+ * contains the roll and whose second contains the pitch component of the
336
+ * acceleration. Values are expressed in radians.
337
+ */
338
+ rb_define_private_method(c_Spatial, "accelerometer_to_roll_and_pitch", spatial_accelerometer_to_roll_and_pitch, 0);
339
+
340
+ /*
341
+ * call-seq:
342
+ * gravity_to_roll_and_pitch -> Array
343
+ * This private method is used to return an array whose first double
344
+ * contains the roll and whose second contains the pitch component of gravity.
345
+ * Currently, this method merely calls and returns the accelerometer_to_roll_and_pitch
346
+ * method. Feel free to override this method with your own, for more accurate
347
+ * compass results. Values are expressed in radians.
348
+ */
349
+ rb_define_private_method(c_Spatial, "gravity_to_roll_and_pitch", spatial_gravity_to_roll_and_pitch, 0);
350
+
351
+ /*
352
+ * Document-method: accelerometer_to_euler
353
+ * call-seq:
354
+ * accelerometer_to_euler -> Array
355
+ *
356
+ * This method returns a three element array of euler angles (yaw, roll, pitch)
357
+ * representing the vector of acceleration, as currently reflected by the
358
+ * spatial sensor.
359
+ */
360
+ rb_define_method(c_Spatial, "accelerometer_to_euler", spatial_accelerometer_to_euler, 0);
361
+
362
+ /*
363
+ * Document-method: compass_bearing
364
+ * call-seq:
365
+ * compass_bearing -> Float
366
+ *
367
+ * This method returns a float representing the compass' bearing, perpendicular to
368
+ * the ground plane, in radians. A radian of 'pi' is where the usb cable plugs in,
369
+ * and a radian of 0 is the opposite end (front) of the device. Note that this
370
+ * method returns the 'magnetic' North, and not 'true' North. Also note that
371
+ * accuracy is highly determined by the private method 'gravity_to_roll_and_pitch'.
372
+ * Currently, gravity is merely assumed to be the output of the
373
+ * accelerometer_to_roll_and_pitch method. Feel free to extend the Spatial class
374
+ * and override the gravity_to_roll_and_pitch method, if you feel you have
375
+ * a better way to determine gravity. Your override's return will be used
376
+ * automatically by this method, once defined.
377
+ */
378
+ rb_define_method(c_Spatial, "compass_bearing", spatial_compass_bearing, 0);
379
+
380
+ /*
381
+ * Document-method: compass_bearing_to_euler
382
+ * call-seq:
383
+ * compass_bearing_to_euler -> Float
384
+ *
385
+ * Probably this isn't too useful, but it returns a three element array, of which
386
+ * the third element is the compass_bearing.
387
+ */
388
+ rb_define_method(c_Spatial, "compass_bearing_to_euler", spatial_compass_bearing_to_euler, 0);
389
+
390
+ /*
391
+ * Document-method: accelerometer_to_dcm
392
+ * call-seq:
393
+ * accelerometer_to_dcm -> Matrix
394
+ *
395
+ * This method returns a 3x3 Matrix containing a direction cosine Matrix which
396
+ * represents the rotation of the current accelerometer value.
397
+ */
398
+ rb_define_method(c_Spatial, "accelerometer_to_dcm", spatial_accelerometer_to_dcm, 0);
399
+
400
+ /*
401
+ * Document-method: compass_bearing_to_dcm
402
+ * call-seq:
403
+ * compass_bearing_to_dcm -> Matrix
404
+ *
405
+ * This method returns a 3x3 direction cosine Matrix which represents the
406
+ * rotation of the current compass_bearing vector in 3d space.
407
+ */
408
+ rb_define_method(c_Spatial, "compass_bearing_to_dcm", spatial_compass_bearing_to_dcm, 0);
409
+
410
+ /*
411
+ * Document-method: gyro_to_dcm
412
+ * call-seq:
413
+ * gyro_to_dcm -> Matrix
414
+ *
415
+ * This method returns a 3x3 direction cosine Matrix which represents the
416
+ * rotation of the current gyroscope vector.
417
+ */
418
+ rb_define_method(c_Spatial, "gyro_to_dcm", spatial_gyro_to_dcm, 0);
419
+
420
+ /*
421
+ * Document-method: orientation_to_quaternion
422
+ * call-seq:
423
+ * orientation_to_quaternion -> Array
424
+ *
425
+ * This method returns a 4 element array consisting of floats, which represent
426
+ * the estimated AHRS orientation of the phidget.
427
+ */
428
+ rb_define_method(c_Spatial, "orientation_to_quaternion", spatial_orientation_to_quaternion, 0);
429
+
430
+ /*
431
+ * Document-method: orientation_to_dcm
432
+ * call-seq:
433
+ * orientation_to_dcm -> Array
434
+ *
435
+ * This method returns a 3x3 direction cosine Matrix, which represent
436
+ * the estimated AHRS orientation of the phidget in 3d space.
437
+ */
438
+ rb_define_method(c_Spatial, "orientation_to_dcm", spatial_orientation_to_dcm, 0);
314
439
 
440
+ /*
441
+ * Document-method: orientation_to_euler
442
+ * call-seq:
443
+ * orientation_to_euler -> Array
444
+ *
445
+ * This method returns a 3 element array, which represents the estimated
446
+ * AHRS orientation of the phidget in 3d space, in euler angles.
447
+ */
448
+ rb_define_method(c_Spatial, "orientation_to_euler", spatial_orientation_to_euler, 0);
315
449
  }
316
450
 
317
451
  VALUE spatial_initialize(VALUE self, VALUE serial) {
@@ -323,6 +457,12 @@ VALUE spatial_initialize(VALUE self, VALUE serial) {
323
457
  spatial_info->data_rate = DEFAULT_SPATIAL_DATA_RATE;
324
458
  spatial_info->sample_rate = sample_create();
325
459
 
460
+ // Ahrs Defaults:
461
+ spatial_info->orientation_q[0] = 1.0;
462
+ spatial_info->orientation_q[1] = 0.0;
463
+ spatial_info->orientation_q[2] = 0.0;
464
+ spatial_info->orientation_q[3] = 0.0;
465
+
326
466
  // Setup a spatial handle
327
467
  CPhidgetSpatialHandle spatial = 0;
328
468
  ensure(CPhidgetSpatial_create(&spatial));
@@ -446,6 +586,7 @@ VALUE spatial_zero_gyro(VALUE self) {
446
586
 
447
587
  ensure(CPhidgetSpatial_zeroGyro((CPhidgetSpatialHandle) info->handle));
448
588
  memset(spatial_info->gyroscope, 0, sizeof(double) * spatial_info->gyro_axes);
589
+ spatial_info->is_ahrs_initialized = false;
449
590
 
450
591
  return Qnil;
451
592
  }
@@ -481,9 +622,11 @@ VALUE spatial_compass_correction_set(VALUE self, VALUE compass_correction) {
481
622
  }
482
623
 
483
624
  spatial_info->is_compass_correction_known = true;
484
- if (info->is_attached)
625
+ if (info->is_attached) {
485
626
  spatial_set_compass_correction_by_array(
486
627
  (CPhidgetSpatialHandle)info->handle, spatial_info->compass_correction);
628
+ spatial_info->is_ahrs_initialized = false;
629
+ }
487
630
  }
488
631
 
489
632
  return compass_correction;
@@ -531,3 +674,266 @@ VALUE spatial_data_rate_get(VALUE self) {
531
674
 
532
675
  return INT2FIX(spatial_info->data_rate);
533
676
  }
677
+
678
+ VALUE spatial_direction_cosine_matrix(int argc, VALUE *argv, VALUE self) {
679
+ VALUE around_x;
680
+ VALUE around_y;
681
+ VALUE around_z;
682
+ VALUE in_order;
683
+
684
+ const char *in_order_str = NULL;
685
+ double dbl_around_x;
686
+ double dbl_around_y;
687
+ double dbl_around_z;
688
+
689
+ rb_scan_args(argc, argv, "31", &around_x, &around_y, &around_z, &in_order);
690
+
691
+ if (TYPE(in_order) == T_STRING)
692
+ in_order_str = StringValueCStr(in_order);
693
+ else if( TYPE(in_order) != T_NIL)
694
+ rb_raise(rb_eTypeError, MSG_IN_ORDER_MUST_BE_STRING);
695
+
696
+ // Validate the inputs:
697
+ if ( (TYPE(around_x) == T_FLOAT) || (TYPE(around_x) == T_FIXNUM ) )
698
+ dbl_around_x = NUM2DBL(around_x);
699
+ else
700
+ rb_raise(rb_eTypeError, MSG_AROUND_MUST_BE_NUMERIC);
701
+
702
+ if ( (TYPE(around_y) == T_FLOAT) || (TYPE(around_y) == T_FIXNUM) )
703
+ dbl_around_y = NUM2DBL(around_y);
704
+ else
705
+ rb_raise(rb_eTypeError, MSG_AROUND_MUST_BE_NUMERIC);
706
+
707
+ if ( (TYPE(around_z) == T_FLOAT) || (TYPE(around_z) == T_FIXNUM) )
708
+ dbl_around_z = NUM2DBL(around_z);
709
+ else
710
+ rb_raise(rb_eTypeError, MSG_AROUND_MUST_BE_NUMERIC);
711
+
712
+ // Perform the actual rotation:
713
+ double mRetDcm[3][3] = {{1.0,0.0,0.0}, {0.0,1.0,0.0}, {0.0,0.0,1.0}};
714
+
715
+ if (euler_to_3x3dcm((double *) &mRetDcm, dbl_around_x, dbl_around_y,
716
+ dbl_around_z, (in_order_str) ? in_order_str : NULL) != 0)
717
+ rb_raise(rb_eTypeError, MSG_INVALID_ROTATION_AXIS);
718
+
719
+ // Return our results:
720
+ return double3x3_to_matrix_rb((double (*)[3])&mRetDcm);
721
+ }
722
+
723
+ VALUE spatial_accelerometer_to_roll_and_pitch(VALUE self) {
724
+ SpatialInfo *spatial_info = device_type_info(self);
725
+
726
+ if (!spatial_info->is_acceleration_known) return Qnil;
727
+
728
+ double dOrientEuler[3];
729
+ spatial_orientation_from_accel_and_compass( (double *)&dOrientEuler,
730
+ spatial_info->acceleration[0], spatial_info->acceleration[1], spatial_info->acceleration[2],
731
+ spatial_info->compass[0], spatial_info->compass[1], spatial_info->compass[2]);
732
+
733
+ // Construct a splat-able return of roll and pitch:
734
+ VALUE aryRet = rb_ary_new2(2);
735
+
736
+ rb_ary_store(aryRet, 0, rb_float_new(dOrientEuler[1])); // Roll
737
+ rb_ary_store(aryRet, 1, rb_float_new(dOrientEuler[0])); // Pitch
738
+
739
+ return aryRet;
740
+ }
741
+
742
+ VALUE spatial_gravity_to_roll_and_pitch(VALUE self) {
743
+ VALUE aryRollPitch = rb_funcall(self, rb_intern("accelerometer_to_roll_and_pitch"), 0);
744
+
745
+ return aryRollPitch;
746
+ }
747
+
748
+ VALUE spatial_accelerometer_to_euler(VALUE self) {
749
+ SpatialInfo *spatial_info = device_type_info(self);
750
+
751
+ if (!spatial_info->is_acceleration_known)
752
+ return Qnil;
753
+
754
+ VALUE aryRollPitch = rb_funcall(self, rb_intern("accelerometer_to_roll_and_pitch"), 0);
755
+
756
+ VALUE roll = rb_ary_shift(aryRollPitch);
757
+ VALUE pitch = rb_ary_shift(aryRollPitch);
758
+
759
+ VALUE aryRet = rb_ary_new2(3);
760
+
761
+ rb_ary_store(aryRet, 0, rb_float_new(NUM2DBL(roll) * -1.00 + M_PI_2));
762
+ rb_ary_store(aryRet, 1, rb_float_new(0.0));
763
+ rb_ary_store(aryRet, 2, pitch);
764
+
765
+ return aryRet;
766
+ }
767
+
768
+ VALUE spatial_compass_bearing(VALUE self) {
769
+ // Get the Compass Values:
770
+ VALUE aryCompass = rb_funcall(self, rb_intern("compass"), 0);
771
+
772
+ if ( (TYPE(aryCompass) == T_NIL) || (RARRAY_LEN(aryCompass) != 3) )
773
+ return Qnil;
774
+
775
+ double dbl_comp_x = NUM2DBL(rb_ary_shift(aryCompass));
776
+ double dbl_comp_y = NUM2DBL(rb_ary_shift(aryCompass));
777
+ double dbl_comp_z = NUM2DBL(rb_ary_shift(aryCompass));
778
+
779
+ // Get the Acceleration Values (All we really need here is 'z'):
780
+ VALUE aryAccel = rb_funcall(self, rb_intern("accelerometer"), 0);
781
+
782
+ if ( (TYPE(aryAccel) == T_NIL) || (RARRAY_LEN(aryAccel) != 3) )
783
+ return Qnil;
784
+
785
+ double dbl_accel_x = NUM2DBL(rb_ary_shift(aryAccel));
786
+ double dbl_accel_y = NUM2DBL(rb_ary_shift(aryAccel));
787
+ double dbl_accel_z = NUM2DBL(rb_ary_shift(aryAccel));
788
+
789
+ // Get the Gravity Vector:
790
+ VALUE aryGrvRollPitch = rb_funcall(self, rb_intern("gravity_to_roll_and_pitch"), 0);
791
+
792
+ double dbl_grv_roll = NUM2DBL(rb_ary_shift(aryGrvRollPitch));
793
+ double dbl_grv_pitch = NUM2DBL(rb_ary_shift(aryGrvRollPitch));
794
+
795
+ /*
796
+ * Yaw Angle - about axis 2
797
+ * tan(yaw) = (mz * sin(roll) – my * cos(roll)) /
798
+ * (mx * cos(pitch) + my * sin(pitch) * sin(roll) + mz * sin(pitch) * cos(roll))
799
+ * Use Atan2 to get our range in (-180 - 180)
800
+ *
801
+ * Yaw angle == 0 degrees when axis 0 is pointing at magnetic north
802
+ */
803
+ double dbl_yaw = atan2( (dbl_comp_z * sin(dbl_grv_roll)) - (dbl_comp_y * cos(dbl_grv_roll)),
804
+ (dbl_comp_x * cos(dbl_grv_pitch)) +
805
+ (dbl_comp_y * sin(dbl_grv_pitch) * sin(dbl_grv_roll)) +
806
+ (dbl_comp_z * sin(dbl_grv_pitch) * cos(dbl_grv_roll)) );
807
+
808
+ // And we're done:
809
+ return rb_float_new( (dbl_accel_z < 0) ? ( dbl_yaw - M_PI_2 ) : ( dbl_yaw - M_PI * 1.5 ) );
810
+ }
811
+
812
+ VALUE spatial_compass_bearing_to_euler(VALUE self) {
813
+ VALUE compass_bearing = rb_funcall(self, rb_intern("compass_bearing"), 0);
814
+
815
+ if (TYPE(compass_bearing) == T_NIL)
816
+ return Qnil;
817
+
818
+ VALUE aryRet = rb_ary_new2(3);
819
+
820
+ rb_ary_store(aryRet, 0, rb_float_new(0.0));
821
+ rb_ary_store(aryRet, 1, rb_float_new(0.0));
822
+ rb_ary_store(aryRet, 2, compass_bearing);
823
+
824
+ return aryRet;
825
+ }
826
+
827
+ VALUE spatial_accelerometer_to_dcm(VALUE self) {
828
+ VALUE aryAccelEu = rb_funcall(self, rb_intern("accelerometer_to_euler"), 0);
829
+
830
+ if ( (TYPE(aryAccelEu) == T_NIL) || (RARRAY_LEN(aryAccelEu) != 3) )
831
+ return Qnil;
832
+
833
+ VALUE angles[3];
834
+ for(unsigned int i=0; i<3; i++)
835
+ angles[i] = rb_ary_shift(aryAccelEu);
836
+
837
+ return rb_funcall(self, rb_intern("direction_cosine_matrix"), 3, angles[0], angles[1], angles[2]);
838
+ }
839
+
840
+ VALUE spatial_compass_bearing_to_dcm(VALUE self) {
841
+ VALUE aryCompassEu = rb_funcall(self, rb_intern("compass_bearing_to_euler"), 0);
842
+
843
+ if ( (TYPE(aryCompassEu) == T_NIL) || (RARRAY_LEN(aryCompassEu) != 3) )
844
+ return Qnil;
845
+
846
+ VALUE angles[3];
847
+ for(unsigned int i=0; i<3; i++)
848
+ angles[i] = rb_ary_shift(aryCompassEu);
849
+
850
+ return rb_funcall(self, rb_intern("direction_cosine_matrix"), 3, angles[0], angles[1], angles[2]);
851
+ }
852
+
853
+ VALUE spatial_gyro_to_dcm(VALUE self) {
854
+ SpatialInfo *spatial_info = device_type_info(self);
855
+
856
+ if ( (!spatial_info->is_gyroscope_known) || (spatial_info->gyro_axes != 3) )
857
+ return Qnil;
858
+
859
+ double mRetDcm[3][3];
860
+ memcpy(&mRetDcm, &spatial_info->gyroscope_dcm, sizeof(mRetDcm));
861
+
862
+ // Return our results:
863
+ return double3x3_to_matrix_rb((double (*)[3])&mRetDcm);
864
+ }
865
+
866
+ VALUE spatial_orientation_to_quaternion(VALUE self) {
867
+ SpatialInfo *spatial_info = device_type_info(self);
868
+
869
+ // NOTE: I'm reusing this conditional from the dcm test. I think we don't need
870
+ // to implement a separated is_orientation_known function as the two conditions
871
+ // are identical:
872
+ if ( (!spatial_info->is_gyroscope_known) || (spatial_info->gyro_axes != 3) )
873
+ return Qnil;
874
+
875
+ float fOrientationQ[4];
876
+ float fTranslatedQ[4];
877
+ double dblRetQ[4];
878
+
879
+ // Grabe the magwick quaternion:
880
+ memcpy(&fOrientationQ, &spatial_info->orientation_q, sizeof(fOrientationQ));
881
+
882
+ // Translate it into 'our' (aka opengl's) cordinate space:
883
+ spatial_madgeq_to_openglq((float *) &fOrientationQ, (float *) &fTranslatedQ);
884
+
885
+ // The Madgwick algorithem uses floats, and ruby wants doubles. Let's cast!
886
+ for(int i=0; i<4; i++)
887
+ dblRetQ[i] = (double) fTranslatedQ[i];
888
+
889
+ // Return our results:
890
+ return double_array_to_rb((double *) &dblRetQ, 4);
891
+ }
892
+
893
+ VALUE spatial_orientation_to_dcm(VALUE self) {
894
+ SpatialInfo *spatial_info = device_type_info(self);
895
+
896
+ if ( (!spatial_info->is_gyroscope_known) || (spatial_info->gyro_axes != 3) )
897
+ return Qnil;
898
+
899
+ float fOrientationQ[4];
900
+ float fTranslatedQ[4];
901
+ double dblRetDcm[3][3];
902
+
903
+ // Grabe the magwick quaternion:
904
+ memcpy(&fOrientationQ, &spatial_info->orientation_q, sizeof(fOrientationQ));
905
+
906
+ // Translate it into 'our' (aka opengl's) cordinate space:
907
+ spatial_madgeq_to_openglq((float *) &fOrientationQ, (float *) &fTranslatedQ);
908
+
909
+ quat_to_dcm((float *)&fTranslatedQ, (double (*)[3]) &dblRetDcm);
910
+
911
+ return double3x3_to_matrix_rb((double (*)[3])&dblRetDcm);
912
+ }
913
+
914
+ VALUE spatial_orientation_to_euler(VALUE self) {
915
+ SpatialInfo *spatial_info = device_type_info(self);
916
+
917
+ if ( (!spatial_info->is_gyroscope_known) || (spatial_info->gyro_axes != 3) )
918
+ return Qnil;
919
+
920
+ float fOrientationQ[4];
921
+ float fTranslatedQ[4];
922
+ float fEuler[3];
923
+ double dblRetEuler[3];
924
+
925
+ // Grabe the magwick quaternion:
926
+ memcpy(&fOrientationQ, &spatial_info->orientation_q, sizeof(fOrientationQ));
927
+
928
+ // Translate it into 'our' (aka opengl's) cordinate space:
929
+ spatial_madgeq_to_openglq((float *) &fOrientationQ, (float *) &fTranslatedQ);
930
+
931
+ quat_to_euler((float *)&fTranslatedQ, (float (*))&fEuler);
932
+
933
+ // The Madgwick algorithem uses floats, and ruby wants doubles. Let's cast!
934
+ for(int i=0; i<3; i++)
935
+ dblRetEuler[i] = (double) fEuler[i];
936
+
937
+ // Return our results:
938
+ return double_array_to_rb((double *) &dblRetEuler, 3);
939
+ }