phidgets_native 0.2.1 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }