Managing States in SUSI MagicMirror Module

SUSI MagicMirror Module is a module for MagicMirror project by which you can use SUSI directly on MagicMirror. While developing the module, a problem I faced was that we need to manage the flow between the various stages of processing of voice input by the user and displaying SUSI output to the user. This was solved by making state management flow between various states of SUSI MagicMirror Module namely, Idle State: When SUSI MagicMirror Module is actively listening for a hotword. Listening State: In this state, the user’s speech input from the microphone is recorded to a file. Busy State: The user has finished speaking or timed out. Now, we need to transcribe the audio spoken by the user, send the response to SUSI server and speak out the SUSI response. The flow between these states can be explained by the following diagram: As clear from the above diagram, transitions are not possible from a state to all other states. Only some transitions are allowed. Thus, we need a mechanism to guarantee only allowed transitions and ensure it triggers on the right time. For achieving this, we first implement an abstract class State with common properties of a state. We store the information whether a state can transition into some other state in a map allowedTransitions which maps state names “idle”, “listening” and “busy” to their corresponding states. The transition method to transition from one state to another is implemented in the following way. protected transition(state: State): void { if (!this.canTransition(state)) { console.error(`Invalid transition to state: ${state}`); return; } this.onExit(); state.onEnter(); } private canTransition(state: State): boolean { return this.allowedStateTransitions.has(state.name); } Here we first check if a transition is valid. Then we exit one state and enter into the supplied state.  We also define a state machine that initializes the default state of the Mirror and define valid transitions for each state. Here is the constructor for state machine. constructor(components: IStateMachineComponents) { this.idleState = new IdleState(components); this.listeningState = new ListeningState(components); this.busyState = new BusyState(components); this.idleState.AllowedStateTransitions = new Map<StateName, State>([["listening", this.listeningState]]); this.listeningState.AllowedStateTransitions = new Map<StateName, State>([["busy", this.busyState], ["idle", this.idleState]]); this.busyState.AllowedStateTransitions = new Map<StateName, State>([["idle", this.idleState]]); this.currentState = this.idleState; this.currentState.onEnter(); } Now, the question arises that how do we detect when we need to transition from one state to another. For that we subscribe on the Snowboy Detector Observable. We are using Snowboy library for Hotword Detection. Snowboy detects whether an audio stream is silent, has some sound or whether hotword was spoken. We bind all this information to an observable using the ReactiveX Observable pattern. This gives us a stream of events to which we can subscribe and get the results. It can be understood in the following code snippet. detector.on("silence", () => { this.subject.next(DETECTOR.Silence); }); detector.on("sound", () => {}); detector.on("error", (error) => { console.error(error); }); detector.on("hotword", (index, hotword) => { this.subject.next(DETECTOR.Hotword); }); public get Observable(): Observable<DETECTOR> { return this.subject.asObservable(); } Now, in the idle state, we subscribe to the values emitted by the observable of the detector to know when a hotword…

Continue ReadingManaging States in SUSI MagicMirror Module

Adding Unit Test for Reducer in loklak search

Ngrx/store components are an integral part of the loklak search. All the components are dependent on how the data is received from the reducers. Reducer is like a client-side database which stores up all the data received from the API response. It is responsible for changing the state of the application. Reducers also supplies data to the Angular components from the central Store. If correct data is not received by the components, the application would crash. Therefore, we need to test if the reducer is storing the data in a correct way and changes the state of the application as expected. Reducer also stores the current state properties which are fetched from the APIs. We need to check if reducers store the data in a correct way and if the data is received from the reducer when called from the angular components. In this blog, I would explain how to build up different components for unit testing reducers. Reducer to test This reducer is used for store the data from suggest.json API from the loklak server.The data received from the server is further classified into three properties which can be used by the components to show up auto- suggestions related to current search query. metadata: - This property stores the metadata from API suggestion response. entities: - This property stores the array of suggestions corresponding to the particular query received from the server. valid: - This is a boolean which keeps a check if the suggestions are valid or not. We also have two actions corresponding to this reducer. These actions, when called, changes the state properties which , further, supplies data to the components in a more classified manner. Moreover, state properties also causes change in the UI of the component according to the action dispatched. SUGGEST_COMPLETE_SUCCESS: - This action is called when the data is received successfully from the server. SUGGEST_COMPLETE_FAIL: - This action is called when the retrieving data from the server fails. export interface State { metadata: SuggestMetadata; entities: SuggestResults[]; valid: boolean; }export const initialState: State = { metadata: null, entities: [], valid: true };export function reducer(state: State = initialState, action: suggestAction.Actions): State { switch (action.type) { case suggestAction.ActionTypes.SUGGEST_COMPLETE_SUCCESS: { const suggestResponse = action.payload;return { metadata: suggestResponse.suggest_metadata, entities: suggestResponse.queries, valid: true }; }case suggestAction.ActionTypes.SUGGEST_COMPLETE_FAIL: { return Object.assign({}, state, { valid: false }); }default: { return state; } } } Unit tests for reducers Import all the actions, reducers and mocks import * as fromSuggestionResponse from './suggest-response'; import * as suggestAction from '../actions/suggest'; import { SuggestResponse } from '../models/api-suggest'; import { MockSuggestResponse } from '../shared/mocks/suggestResponse.mock';   Next, we are going to test if the undefined action doesn’t a cause change in the state and returns the initial state properties. We will be creating an action by const action = {} as any;  and call the reducer by const result = fromSuggestionResponse.reducer(undefined, action);. Now we will be making assertions with expect() block to check if the result is equal to initialState and all the initial state properties are…

Continue ReadingAdding Unit Test for Reducer in loklak search