diff --git a/sample/simple-accelerometer/src/accel.c b/sample/simple-accelerometer/src/accel.c index 78bb0cc..d592e22 100644 --- a/sample/simple-accelerometer/src/accel.c +++ b/sample/simple-accelerometer/src/accel.c @@ -33,7 +33,7 @@ typedef struct { int **normalized_recording; moving_avg_values **moving_avg_values; - int *affinities; + int *offsets; } accel_gesture; typedef struct internalAccelState { @@ -59,7 +59,7 @@ typedef struct internalAccelState { if (state_$->state->gestures != NULL && state_$->state->num_gestures_saved == 0) { return ACCEL_INTERNAL_ERROR; } // Decay rate of values we choose to keep. 1.0 is no decay, 2.0 is a doubling every time we keep them. -// TODO: should we store the affinities as floats instead? +// TODO: should we store the offsets as floats instead? #define ALPHA 1.0 // TODO: include these from a header file? @@ -94,9 +94,9 @@ void accel_destroy_gesture(accel_gesture **gesture, int dimensions) { free(gest->normalized_recording); gest->normalized_recording = NULL; } - if (gest->affinities != NULL) { - free(gest->affinities); - gest->affinities = NULL; + if (gest->offsets != NULL) { + free(gest->offsets); + gest->offsets = NULL; } free(*gesture); @@ -258,6 +258,17 @@ int normalize(int sum) { return (int) cbrt(sum); } +int reset_gesture(accel_gesture *gest, const int dimensions) { + PRECONDITION_NOT_NULL(gest); + for (int i=0; irecording_size; ++i) { + gest->offsets[i] = INT16_MAX; + } + for (int d=0; dmoving_avg_values[d]); + } + return ACCEL_SUCCESS; +} + // TODO: does this work for zero recorded timestamps? int accel_end_record_gesture(accel_state *state, int gesture_id) { PRECONDITION_VALID_STATE(state); @@ -284,21 +295,21 @@ int accel_end_record_gesture(accel_state *state, int gesture_id) { return ACCEL_PARAM_ERROR; } - gesture->affinities = (int *) malloc(gesture->recording_size * sizeof(int)); - if (gesture->affinities == NULL) { + gesture->offsets = (int *) malloc(gesture->recording_size * sizeof(int)); + if (gesture->offsets == NULL) { return ACCEL_MALLOC_ERROR; } - gesture->is_recording = false; - gesture->is_recorded = true; - - for (int i=0; irecording_size; ++i) { - gesture->affinities[i] = INT16_MAX; - } - for (int d=0; ddimensions; ++d) { - reset_moving_avg(gesture->moving_avg_values[d]); + int reset_result = reset_gesture(gesture, state->dimensions); + if (reset_result != ACCEL_SUCCESS) { + free(gesture->offsets); + gesture->offsets = NULL; + } else { + gesture->is_recording = false; + gesture->is_recorded = true; } - return ACCEL_SUCCESS; + + return reset_result; } // TODO: check for malloc failure in this function. @@ -330,7 +341,7 @@ int handle_evaluation_tick(accel_gesture *gesture, int dimensions) { PRECONDITION_NOT_NULL(gesture); if (gesture->moving_avg_values == NULL || - gesture->affinities == NULL) { + gesture->offsets == NULL) { return ACCEL_INTERNAL_ERROR; } @@ -353,9 +364,9 @@ int handle_evaluation_tick(accel_gesture *gesture, int dimensions) { } } if (i == 0) { - gesture->affinities[i] = cost; + gesture->offsets[i] = cost; } else { - gesture->affinities[i] = MIN(ALPHA * gesture->affinities[i], cost+gesture->affinities[i-1]); + gesture->offsets[i] = MIN(ALPHA * gesture->offsets[i], cost+gesture->offsets[i-1]); } } for (i=1; irecording_size; ++i) { @@ -372,7 +383,7 @@ int handle_evaluation_tick(accel_gesture *gesture, int dimensions) { cost += input_i_d - recording_i_d; } } - gesture->affinities[i] = MIN(gesture->affinities[i], gesture->affinities[i-1] + cost); + gesture->offsets[i] = MIN(gesture->offsets[i], gesture->offsets[i-1] + cost); } return ACCEL_SUCCESS; } @@ -425,13 +436,13 @@ int accel_process_timer_tick(accel_state *state, int *accel_data) { return retcode; } -int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *affinity) { +int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *offset) { PRECONDITION_VALID_STATE(state); PRECONDITION_NOT_NULL(gesture_id); - PRECONDITION_NOT_NULL(affinity); + PRECONDITION_NOT_NULL(offset); *gesture_id = ACCEL_NO_VALID_GESTURE; - *affinity = ACCEL_NO_VALID_GESTURE; + *offset = ACCEL_NO_VALID_GESTURE; if (state->state->num_gestures_saved < 0) { return ACCEL_INTERNAL_ERROR; @@ -453,8 +464,8 @@ int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *aff return ACCEL_INTERNAL_ERROR; } - if ((*gesture_id == ACCEL_NO_VALID_GESTURE || *affinity == ACCEL_NO_VALID_GESTURE) && - *gesture_id != *affinity) { + if ((*gesture_id == ACCEL_NO_VALID_GESTURE || *offset == ACCEL_NO_VALID_GESTURE) && + *gesture_id != *offset) { return ACCEL_INTERNAL_ERROR; } @@ -468,15 +479,32 @@ int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *aff continue; } - if (*affinity == ACCEL_NO_VALID_GESTURE || - gesture->affinities[gesture->recording_size-1] < *affinity) { - *affinity = gesture->affinities[gesture->recording_size-1]; + if (*offset == ACCEL_NO_VALID_GESTURE || + gesture->offsets[gesture->recording_size-1] < *offset) { + *offset = gesture->offsets[gesture->recording_size-1]; *gesture_id = i; } } if (*gesture_id == ACCEL_NO_VALID_GESTURE || - *affinity == ACCEL_NO_VALID_GESTURE) { + *offset == ACCEL_NO_VALID_GESTURE) { return ACCEL_NO_VALID_GESTURE; } return ACCEL_SUCCESS; } + +int accel_reset_affinities_for_gesture(accel_state *state, int gesture_id) { + PRECONDITION_VALID_STATE(state); + internal_accel_state *ias = state->state; + if (ias->num_gestures_saved <= gesture_id || gesture_id < 0) { + return ACCEL_PARAM_ERROR; + } + accel_gesture *gest = ias->gestures[gesture_id]; + if (gest == NULL) { + return ACCEL_INTERNAL_ERROR; + } + if (!gest->is_recorded || gest->is_recording) { + // Gesture is in the wrong state for resetting. + return ACCEL_PARAM_ERROR; + } + return reset_gesture(gest, state->dimensions); +} diff --git a/sample/simple-accelerometer/src/accel.h b/sample/simple-accelerometer/src/accel.h index c601b21..19c7fcf 100644 --- a/sample/simple-accelerometer/src/accel.h +++ b/sample/simple-accelerometer/src/accel.h @@ -3,8 +3,6 @@ #include -#include "accel_consts.c" - #define ACCEL_SUCCESS 0 #define ACCEL_PARAM_ERROR -1 #define ACCEL_INTERNAL_ERROR -2 @@ -24,10 +22,49 @@ typedef struct { struct internalAccelState *state; } accel_state; +/** + * Callback called whenever a given gesture drops below the offset/length + * threshold specified when the state is initialized. + * + * A simple accel_callback is as follows: + * + * const int my_callback(accel_state *state, int gesture_id, int offset_found, bool *reset_gesture) { + * int retval = ACCEL_SUCCESS; + * if (gesture_id == 1) { + * *reset_gesture = true; + * ... + * } else { + * logger->info("unrecognized gesture %i ", gesture_id); + * retval = ACCEL_MIN_RESERVED - 1; + * } + * return retval; + * } + * + * For the callback method, the documentation is as follows: + * @param state A non-NULL pointer to a state variable that holds + * recording metadata. + * @param gesture_id The identifier of the gesture that has been + * triggered. + * @param offset_found The offset of the triggered gesture_id to the + * recorded gesture. + * @param reset_gesture Setting reset_gesture to be true will result in the + * gesture being reset after the callback is triggered, + * and setting it to false will prevent the gesture + * from being reset. No default value is promised. + * @return int Returns ACCEL_SUCCESS if successful. Values that are + * not ACCEL_SUCCESS will cause the calling method to + * immediately abort and proxy-return the value + * returned by the callback. + * Implementers wishing to return a custom value should + * refer to the ACCEL_MIN_RESERVED definition inside + * their implementations. + */ +typedef const int (*accel_callback)(accel_state *state, int gesture_id, int offset_found, bool *reset_gesture); + /** * Creates a state object, essentially a constructor. - * @param state Pointer-to-pointer of the state being generated, populated - * by the method. + * @param state Pointer-to-pointer of the state being generated, + * populated by the method. * The current value of the pointer's pointed (*state) must * be NULL. * @param dimensions The number of dimensions of input that the state @@ -88,4 +125,14 @@ int accel_process_timer_tick(accel_state *state, int *accel_data); */ int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *distance); +/** + * For a given state and recorded gesture, resets the gesture's offset state + * entirely. + * @param state A pointer to a non-NULL state variable that holds recording + * metadata. + * @param gesture_id Value that corresponds to a gesture currently being reset. + * @return ACCEL_SUCCESS if successful, an error code otherwise. + */ +int accel_reset_affinities_for_gesture(accel_state *state, int gesture_id); + #endif diff --git a/src/accel.c b/src/accel.c index 3ce5742..38f5bb6 100644 --- a/src/accel.c +++ b/src/accel.c @@ -33,7 +33,7 @@ typedef struct { int **normalized_recording; moving_avg_values **moving_avg_values; - int *affinities; + int *offsets; } accel_gesture; typedef struct internalAccelState { @@ -41,6 +41,9 @@ typedef struct internalAccelState { int num_gestures_saved; accel_gesture **gestures; + + accel_callback callback; + int threshold; } internal_accel_state; #define PRECONDITION_NOT_NULL(foo_$) \ @@ -59,7 +62,7 @@ typedef struct internalAccelState { if (state_$->state->gestures != NULL && state_$->state->num_gestures_saved == 0) { return ACCEL_INTERNAL_ERROR; } // Decay rate of values we choose to keep. 1.0 is no decay, 2.0 is a doubling every time we keep them. -// TODO: should we store the affinities as floats instead? +// TODO: should we store the offsets as floats instead? #define ALPHA 1.0 // TODO: include these from a header file? @@ -94,9 +97,9 @@ void accel_destroy_gesture(accel_gesture **gesture, int dimensions) { free(gest->normalized_recording); gest->normalized_recording = NULL; } - if (gest->affinities != NULL) { - free(gest->affinities); - gest->affinities = NULL; + if (gest->offsets != NULL) { + free(gest->offsets); + gest->offsets = NULL; } free(*gesture); @@ -143,7 +146,11 @@ int accel_generate_gesture(accel_state *state, accel_gesture **gesture) { } // TODO: needs direct testing with invalid objects. -int accel_generate_state(accel_state **state, int dimensions, int window_size) { +int accel_generate_state(accel_state **state, + int dimensions, + int window_size, + accel_callback callback, + const int threshold) { PRECONDITION_NOT_NULL(state); // TODO: write a test for this value. @@ -181,6 +188,8 @@ int accel_generate_state(accel_state **state, int dimensions, int window_size) { (*state)->dimensions = dimensions; (*state)->state->window_size = window_size > 0 ? window_size : 2; + (*state)->state->callback = callback; + (*state)->state->threshold = threshold; return ACCEL_SUCCESS; } @@ -261,7 +270,7 @@ int normalize(int sum) { int reset_gesture(accel_gesture *gest, const int dimensions) { PRECONDITION_NOT_NULL(gest); for (int i=0; irecording_size; ++i) { - gest->affinities[i] = INT16_MAX; + gest->offsets[i] = INT16_MAX; } for (int d=0; dmoving_avg_values[d]); @@ -295,15 +304,15 @@ int accel_end_record_gesture(accel_state *state, int gesture_id) { return ACCEL_PARAM_ERROR; } - gesture->affinities = (int *) malloc(gesture->recording_size * sizeof(int)); - if (gesture->affinities == NULL) { + gesture->offsets = (int *) malloc(gesture->recording_size * sizeof(int)); + if (gesture->offsets == NULL) { return ACCEL_MALLOC_ERROR; } int reset_result = reset_gesture(gesture, state->dimensions); if (reset_result != ACCEL_SUCCESS) { - free(gesture->affinities); - gesture->affinities = NULL; + free(gesture->offsets); + gesture->offsets = NULL; } else { gesture->is_recording = false; gesture->is_recorded = true; @@ -341,7 +350,7 @@ int handle_evaluation_tick(accel_gesture *gesture, int dimensions) { PRECONDITION_NOT_NULL(gesture); if (gesture->moving_avg_values == NULL || - gesture->affinities == NULL) { + gesture->offsets == NULL) { return ACCEL_INTERNAL_ERROR; } @@ -364,9 +373,9 @@ int handle_evaluation_tick(accel_gesture *gesture, int dimensions) { } } if (i == 0) { - gesture->affinities[i] = cost; + gesture->offsets[i] = cost; } else { - gesture->affinities[i] = MIN(ALPHA * gesture->affinities[i], cost+gesture->affinities[i-1]); + gesture->offsets[i] = MIN(ALPHA * gesture->offsets[i], cost+gesture->offsets[i-1]); } } for (i=1; irecording_size; ++i) { @@ -383,7 +392,7 @@ int handle_evaluation_tick(accel_gesture *gesture, int dimensions) { cost += input_i_d - recording_i_d; } } - gesture->affinities[i] = MIN(gesture->affinities[i], gesture->affinities[i-1] + cost); + gesture->offsets[i] = MIN(gesture->offsets[i], gesture->offsets[i-1] + cost); } return ACCEL_SUCCESS; } @@ -436,13 +445,13 @@ int accel_process_timer_tick(accel_state *state, int *accel_data) { return retcode; } -int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *affinity) { +int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *offset) { PRECONDITION_VALID_STATE(state); PRECONDITION_NOT_NULL(gesture_id); - PRECONDITION_NOT_NULL(affinity); + PRECONDITION_NOT_NULL(offset); *gesture_id = ACCEL_NO_VALID_GESTURE; - *affinity = ACCEL_NO_VALID_GESTURE; + *offset = ACCEL_NO_VALID_GESTURE; if (state->state->num_gestures_saved < 0) { return ACCEL_INTERNAL_ERROR; @@ -464,8 +473,8 @@ int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *aff return ACCEL_INTERNAL_ERROR; } - if ((*gesture_id == ACCEL_NO_VALID_GESTURE || *affinity == ACCEL_NO_VALID_GESTURE) && - *gesture_id != *affinity) { + if ((*gesture_id == ACCEL_NO_VALID_GESTURE || *offset == ACCEL_NO_VALID_GESTURE) && + *gesture_id != *offset) { return ACCEL_INTERNAL_ERROR; } @@ -479,14 +488,14 @@ int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *aff continue; } - if (*affinity == ACCEL_NO_VALID_GESTURE || - gesture->affinities[gesture->recording_size-1] < *affinity) { - *affinity = gesture->affinities[gesture->recording_size-1]; + if (*offset == ACCEL_NO_VALID_GESTURE || + gesture->offsets[gesture->recording_size-1] < *offset) { + *offset = gesture->offsets[gesture->recording_size-1]; *gesture_id = i; } } if (*gesture_id == ACCEL_NO_VALID_GESTURE || - *affinity == ACCEL_NO_VALID_GESTURE) { + *offset == ACCEL_NO_VALID_GESTURE) { return ACCEL_NO_VALID_GESTURE; } return ACCEL_SUCCESS; diff --git a/src/accel.h b/src/accel.h index 915b86d..5833641 100644 --- a/src/accel.h +++ b/src/accel.h @@ -22,19 +22,63 @@ typedef struct { struct internalAccelState *state; } accel_state; +/** + * Callback called whenever a given gesture drops below the offset/length + * threshold specified when the state is initialized. + * + * A simple accel_callback is as follows: + * + * const int my_callback(accel_state *state, int gesture_id, int offset_found, bool *reset_gesture) { + * int retval = ACCEL_SUCCESS; + * if (gesture_id == 1) { + * *reset_gesture = true; + * ... + * } else { + * logger->info("unrecognized gesture %i ", gesture_id); + * retval = ACCEL_MIN_RESERVED - 1; + * } + * return retval; + * } + * + * For the callback method, the documentation is as follows: + * @param state A non-NULL pointer to a state variable that holds + * recording metadata. + * @param gesture_id The identifier of the gesture that has been + * triggered. + * @param offset_found The offset of the triggered gesture_id to the + * recorded gesture. + * @param reset_gesture Setting reset_gesture to be true will result in the + * gesture being reset after the callback is triggered, + * and setting it to false will prevent the gesture + * from being reset. No default value is promised. + * @return int Returns ACCEL_SUCCESS if successful. Values that are + * not ACCEL_SUCCESS will cause the calling method to + * immediately abort and proxy-return the value + * returned by the callback. + * Implementers wishing to return a custom value should + * refer to the ACCEL_MIN_RESERVED definition inside + * their implementations. + */ +typedef const int (*accel_callback)(accel_state *state, int gesture_id, int offset_found, bool *reset_gesture); + /** * Creates a state object, essentially a constructor. - * @param state Pointer-to-pointer of the state being generated, populated - * by the method. + * @param state Pointer-to-pointer of the state being generated, + * populated by the method. * The current value of the pointer's pointed (*state) must * be NULL. * @param dimensions The number of dimensions of input that the state * represents. * @param window_size The size of the moving windows used to calculate smoothed * sensor readings. + * @param callback A callback that is triggered whenever a gesture passes a + * threshold. See the ``accel_callback`` typedef for more + * information. + * @param threshold The minimum threshold offset (divided by length) that all + * gestures must be before the callback is called. * @return ACCEL_SUCCESS if successful, an error code otherwise. */ -int accel_generate_state(accel_state **state, int dimensions, int window_size); +int accel_generate_state(accel_state **state, int dimensions, int window_size, accel_callback callback, const int threshold); /** * Destroys the state object at the pointer pointed to by the state pointer. @@ -87,7 +131,8 @@ int accel_process_timer_tick(accel_state *state, int *accel_data); int accel_find_most_likely_gesture(accel_state *state, int *gesture_id, int *distance); /** - * For a given state and recorded gesture, resets the gesture's affinity state entirely. + * For a given state and recorded gesture, resets the gesture's offset state + * entirely. * @param state A pointer to a non-NULL state variable that holds recording * metadata. * @param gesture_id Value that corresponds to a gesture currently being reset. diff --git a/test/accel_test.cc b/test/accel_test.cc index 745cc3a..c2ec6e4 100644 --- a/test/accel_test.cc +++ b/test/accel_test.cc @@ -6,7 +6,7 @@ const void * void_null = NULL; accel_state *test_fabricate_state(int dimensions) { accel_state *state = NULL; - int result = accel_generate_state(&state, dimensions, 1); + int result = accel_generate_state(&state, dimensions, 1, NULL, 0); EXPECT_EQ(0, result); EXPECT_NE(void_null, state); return state; @@ -28,23 +28,23 @@ void test_burn_state(accel_state ** state) { TEST(AccelFuzzTest, generate_state_null_state) { - int result = accel_generate_state(NULL, 3, 1); + int result = accel_generate_state(NULL, 3, 1, NULL, 0); EXPECT_EQ(result, ACCEL_PARAM_ERROR); } TEST(AccelFuzzTest, generate_state_negative_or_zero_dimensions) { accel_state *state = NULL; // 0 dimensions must fail - int result = accel_generate_state(&state, 0, 1); + int result = accel_generate_state(&state, 0, 1, NULL, 0); EXPECT_EQ(ACCEL_PARAM_ERROR, result); // -1 dimension must fail - result = accel_generate_state(&state, -1, 1); + result = accel_generate_state(&state, -1, 1, NULL, 0); EXPECT_EQ(ACCEL_PARAM_ERROR, result); // 1 dimension must succeed. state = NULL; - result = accel_generate_state(&state, 1, 1); + result = accel_generate_state(&state, 1, 1, NULL, 0); EXPECT_EQ(0, result); // TODO: result's memory is leaked :s } @@ -54,15 +54,15 @@ TEST(AccelFuzzTest, generate_state_invalid_window_size) { int result = 0; // Size 0 must fail - result = accel_generate_state(&state, 1, 0); + result = accel_generate_state(&state, 1, 0, NULL, 0); EXPECT_EQ(ACCEL_PARAM_ERROR, result); // Size -1 must fail - result = accel_generate_state(&state, 1, -1); + result = accel_generate_state(&state, 1, -1, NULL, 0); EXPECT_EQ(ACCEL_PARAM_ERROR, result); // Size 1 must succeed - result = accel_generate_state(&state, 1, 1); + result = accel_generate_state(&state, 1, 1, NULL, 0); EXPECT_EQ(0, result); EXPECT_NE(void_null, state); } @@ -186,7 +186,7 @@ TEST(AccelTest, accel_generate_and_destroy) { accel_state *state = NULL; for (int i=1; i<10; ++i) { EXPECT_EQ(void_null, state) << "i = " << i; - EXPECT_EQ(0, accel_generate_state(&state, 2*i, i)) << "i = " << i; + EXPECT_EQ(0, accel_generate_state(&state, 2*i, i, NULL, 0)) << "i = " << i; EXPECT_EQ(0, accel_destroy_state(&state)) << "i = " << i; EXPECT_EQ(void_null, state) << "i = " << i; }