Skip to content

Commit

Permalink
Merge 7a6edf5 into 874bbf5
Browse files Browse the repository at this point in the history
  • Loading branch information
barbeau authored Feb 27, 2017
2 parents 874bbf5 + 7a6edf5 commit d597c2e
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 22 deletions.
3 changes: 3 additions & 0 deletions interaction model/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
},
{
"intent": "DisableClockTime"
},
{
"intent": "SetRouteFilter"
}
]
}
7 changes: 7 additions & 0 deletions interaction model/utterances.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ DisableClockTime disable clock times
DisableClockTime turn off clock time
DisableClockTime turn off clock times

SetRouteFilter set a route filter
SetRouteFilter set my route filter
SetRouteFilter create a route filter
SetRouteFilter create my route filter
SetRouteFilter filter routes
SetRouteFilter filter out routes

GetArrivalsIntent where is my {TransitMode}
GetArrivalsIntent where's my {TransitMode}
GetArrivalsIntent when is my {TransitMode}
Expand Down
31 changes: 21 additions & 10 deletions src/main/java/org/onebusaway/alexa/AnonSpeechlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public SpeechletResponse onIntent(IntentRequest request, Session session)
GET_STOP_NUMBER.equals(intent.getName()) ||
ENABLE_CLOCK_TIME.equals(intent.getName()) ||
DISABLE_CLOCK_TIME.equals(intent.getName()) ||
SET_ROUTE_FILTER.equals(intent.getName()) ||
REPEAT.equals(intent.getName())) {
// User asked for help, or we don't yet have enough information to respond. Return welcome message.
return askForCity(Optional.empty());
Expand Down Expand Up @@ -208,7 +209,7 @@ private SpeechletResponse handleNoIntent(Session session, AskState askState) thr
return askForCity(Optional.empty());
}

private AskState getAskState(Session session) {
AskState getAskState(Session session) {
AskState askState = AskState.NONE;
String savedAskState = (String)session.getAttribute(ASK_STATE);
if (savedAskState != null) {
Expand All @@ -218,7 +219,7 @@ private AskState getAskState(Session session) {
}

private SpeechletResponse handleVerifyStopResponse(Session session, boolean stopFound) throws SpeechletException {
ArrayList<ObaStop> stops = (ArrayList<ObaStop>) session.getAttribute(FOUND_STOPS);
ArrayList<ObaStop> stops = (ArrayList<ObaStop>) session.getAttribute(DIALOG_FOUND_STOPS);
if (stops != null) {
if (stopFound && stops.size() > 0) {
String cityName = (String)session.getAttribute(CITY_NAME);
Expand Down Expand Up @@ -250,7 +251,7 @@ private SpeechletResponse handleVerifyStopResponse(Session session, boolean stop
return finishOnboard(session, cityName, stopData.get("id"), stopData.get("stopCode"), region.get(), obaUserClient);
} else if (!stopFound && stops.size() > 1) {
stops.remove(0);
session.setAttribute(FOUND_STOPS, stops);
session.setAttribute(DIALOG_FOUND_STOPS, stops);
return askToVerifyStop(session, null);
}
}
Expand Down Expand Up @@ -341,11 +342,11 @@ private SpeechletResponse askToVerifyStop(Session session, ObaStop[] stops) {
String stopName = "";

if (stops != null && stops.length > 0) {
session.setAttribute(FOUND_STOPS, stops);
session.setAttribute(DIALOG_FOUND_STOPS, stops);
stopName = stops[0].getName();
askForVerifyStop.setText(String.format("We found %d stops associated with the stop number. Did you mean the %s stop?", stops.length, stopName));
} else {
ArrayList<ObaStop> foundStops = (ArrayList<ObaStop>) session.getAttribute(FOUND_STOPS);
ArrayList<ObaStop> foundStops = (ArrayList<ObaStop>) session.getAttribute(DIALOG_FOUND_STOPS);
LinkedHashMap<String, String> stopData = (LinkedHashMap<String, String>) foundStops.get(0);
stopName = stopData.get("name");
askForVerifyStop.setText(String.format("Ok, what about the %s stop?", stopName));
Expand Down Expand Up @@ -395,17 +396,25 @@ private SpeechletResponse finishOnboard(Session session, String cityName, String
throw new SpeechletException(e);
}

HashMap<String, HashSet<String>> routeFilters = (HashMap<String, HashSet<String>>) session.getAttribute(ROUTES_TO_FILTER);
if (routeFilters == null) {
routeFilters = new HashMap<>();
}
HashSet routesToFilter = routeFilters.get((String) session.getAttribute(STOP_NUMBER));

String arrivalInfoText = SpeechUtil.getArrivalText(response.getArrivalInfo(), ARRIVALS_SCAN_MINS,
response.getCurrentTime(), speakClockTime, timeZone);
response.getCurrentTime(), speakClockTime, timeZone, routesToFilter);

log.info("Full arrival text output: " + arrivalInfoText);
String outText = String.format("Ok, your stop number is %s in the %s region. " +
"Great. I am ready to tell you about the next bus. You can always ask me for arrival times " +
"by saying 'open One Bus Away'. Right now, %s",
"by saying 'open One Bus Away', and filter routes for your currently selected stop by saying 'filter routes'. " +
"You can learn more about other features by asking me for help. " +
"Right now, %s",
stopCode, region.getName(), arrivalInfoText);

createOrUpdateUser(session, cityName, stopId, region.getId(), region.getName(), region.getObaBaseUrl(), outText,
System.currentTimeMillis(), speakClockTime, timeZone);
System.currentTimeMillis(), speakClockTime, timeZone, routeFilters);

PlainTextOutputSpeech out = new PlainTextOutputSpeech();
out.setText(outText);
Expand All @@ -414,7 +423,7 @@ private SpeechletResponse finishOnboard(Session session, String cityName, String

private void createOrUpdateUser(Session session, String cityName, String stopId, long regionId, String regionName,
String regionObaBaseUrl, String previousResponse, long lastAccessTime,
long speakClockTime, TimeZone timeZone) {
long speakClockTime, TimeZone timeZone, HashMap<String, HashSet<String>> routeFilters) {
Optional<ObaUserDataItem> optUserData = obaDao.getUserData(session);
if (optUserData.isPresent()) {
ObaUserDataItem userData = optUserData.get();
Expand All @@ -427,6 +436,7 @@ private void createOrUpdateUser(Session session, String cityName, String stopId,
userData.setLastAccessTime(lastAccessTime);
userData.setSpeakClockTime(speakClockTime);
userData.setTimeZone(timeZone.getID());
userData.setRoutesToFilter(routeFilters);
obaDao.saveUserData(userData);
} else {
ObaUserDataItem userData = new ObaUserDataItem(
Expand All @@ -440,13 +450,14 @@ private void createOrUpdateUser(Session session, String cityName, String stopId,
lastAccessTime,
speakClockTime,
timeZone.getID(),
new HashMap<>(),
null
);
obaDao.saveUserData(userData);
}
}

private SpeechletResponse askForCity(Optional<String> currentCityName) {
SpeechletResponse askForCity(Optional<String> currentCityName) {
PlainTextOutputSpeech out = new PlainTextOutputSpeech();
if (!currentCityName.isPresent()) {
out.setText("Welcome to OneBusAway! Let's set you up. " +
Expand Down
182 changes: 175 additions & 7 deletions src/main/java/org/onebusaway/alexa/AuthedSpeechlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.amazon.speech.slu.Intent;
import com.amazon.speech.speechlet.*;
import com.amazon.speech.ui.PlainTextOutputSpeech;
import com.amazon.speech.ui.Reprompt;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.log4j.Log4j;
Expand All @@ -27,13 +28,15 @@
import org.onebusaway.alexa.storage.ObaDao;
import org.onebusaway.alexa.storage.ObaUserDataItem;
import org.onebusaway.alexa.util.SpeechUtil;
import org.onebusaway.io.client.elements.ObaRoute;
import org.onebusaway.io.client.request.ObaArrivalInfoResponse;
import org.onebusaway.io.client.request.ObaStopResponse;
import org.onebusaway.io.client.util.UIUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.TimeZone;
import java.util.*;

import static org.onebusaway.alexa.ObaIntent.*;
import static org.onebusaway.alexa.SessionAttribute.*;
Expand Down Expand Up @@ -63,6 +66,8 @@ public SpeechletResponse onIntent(final IntentRequest request,
final Session session)
throws SpeechletException {
populateAttributes(session);
AskState askState = anonSpeechlet.getAskState(session);
session.setAttribute(ASK_STATE, AskState.NONE.toString());

Intent intent = request.getIntent();
if (HELP.equals(intent.getName())) {
Expand All @@ -84,17 +89,19 @@ public SpeechletResponse onIntent(final IntentRequest request,
} else if (SET_STOP_NUMBER.equals(intent.getName())) {
return anonSpeechlet.onIntent(request, session);
} else if (YES.equals(intent.getName())) {
return anonSpeechlet.onIntent(request, session);
return handleYesIntent(request, session, askState);
} else if (NO.equals(intent.getName())) {
return anonSpeechlet.onIntent(request, session);
return handleNoIntent(request, session, askState);
} else if (GET_STOP_NUMBER.equals(intent.getName())) {
return getStopDetails();
} else if (GET_ARRIVALS.equals(intent.getName())) {
return tellArrivals();
return tellArrivals(session);
} else if (ENABLE_CLOCK_TIME.equals(intent.getName())) {
return enableClockTime(session);
} else if (DISABLE_CLOCK_TIME.equals(intent.getName())) {
return disableClockTime(session);
} else if (SET_ROUTE_FILTER.equals(intent.getName())) {
return setRouteFilter(session);
} else if (STOP.equals(intent.getName()) || CANCEL.equals(intent.getName())) {
return goodbye();
} else {
Expand All @@ -107,7 +114,7 @@ public SpeechletResponse onLaunch(final LaunchRequest request,
final Session session)
throws SpeechletException {
populateAttributes(session);
return tellArrivals();
return tellArrivals(session);
}

@Override
Expand Down Expand Up @@ -154,6 +161,9 @@ private void populateAttributes(Session session) {
if (session.getAttribute(TIME_ZONE) == null) {
session.setAttribute(TIME_ZONE, userData.getTimeZone());
}
if (session.getAttribute(ROUTES_TO_FILTER) == null) {
session.setAttribute(ROUTES_TO_FILTER, userData.getRoutesToFilter());
}
}

private SpeechletResponse getCity() {
Expand All @@ -178,7 +188,7 @@ private SpeechletResponse getStopDetails() throws SpeechletException {
return SpeechletResponse.newTellResponse(out);
}

private SpeechletResponse tellArrivals() throws SpeechletException {
private SpeechletResponse tellArrivals(Session session) throws SpeechletException {
ObaArrivalInfoResponse response = null;
try {
response = obaUserClient.getArrivalsAndDeparturesForStop(
Expand All @@ -196,8 +206,14 @@ private SpeechletResponse tellArrivals() throws SpeechletException {
timeZone = TimeZone.getTimeZone(timeZoneText);
}

HashMap<String, HashSet<String>> routeFilters = (HashMap<String, HashSet<String>>) session.getAttribute(ROUTES_TO_FILTER);
if (routeFilters == null) {
routeFilters = new HashMap<>();
}
HashSet routesToFilter = routeFilters.get(userData.getStopId());

String output = SpeechUtil.getArrivalText(response.getArrivalInfo(), ARRIVALS_SCAN_MINS,
response.getCurrentTime(), userData.getSpeakClockTime(), timeZone);
response.getCurrentTime(), userData.getSpeakClockTime(), timeZone, routesToFilter);

log.info("Full text output: " + output);
saveOutputForRepeat(output);
Expand Down Expand Up @@ -251,6 +267,158 @@ private void saveOutputForRepeat(String output) {
obaDao.saveUserData(userData);
}

/**
* User asked to filter routes for the currently selected stop
*
* @param session
* @return
*/
private SpeechletResponse setRouteFilter(final Session session) {
// Make sure we clear any existing routes to filter for this session (but leave any persisted)
session.setAttribute(DIALOG_ROUTES_TO_FILTER, null);

String stopId = (String) session.getAttribute(STOP_NUMBER);
String regionName = (String) session.getAttribute(REGION_NAME);
log.debug(String.format(
"Asked to set a route filter for stop ID %s in region %s...", stopId, regionName));

ObaStopResponse response;
try {
response = obaUserClient.getStop(stopId);
} catch (IOException e) {
log.error("Error getting details for stop + " + stopId + " in region " + regionName + ": " + e.getMessage());
return SpeechUtil.getCommunicationErrorMessage();
}

List<ObaRoute> routes = response.getRoutes();
if (routes.size() <= 1) {
String output = String.format("There is only one route for this stop, so I can't filter out any routes.");
saveOutputForRepeat(output);
PlainTextOutputSpeech out = new PlainTextOutputSpeech();
out.setText(output);
return SpeechletResponse.newTellResponse(out);
}
// There is more than one route - ask user which they want to hear arrivals for
return askToFilterRoute(session, routes);
}

/**
* Ask the user if they want to hear arrivals for the first route, from the provided list of routes
*
* @param session
* @param routes list of routes from which the first route will be taken
* @return response to be read to the user asking if they want to hear arrivals for the first route in the provided list of routes
*/
private SpeechletResponse askToFilterRoute(Session session, List<ObaRoute> routes) {
PlainTextOutputSpeech askForRouteFilter = new PlainTextOutputSpeech();
String stopId = (String) session.getAttribute(STOP_NUMBER);
String routeName = "";

if (routes != null && routes.size() > 0) {
session.setAttribute(DIALOG_ROUTES_TO_ASK_ABOUT, routes);
routeName = UIUtils.getRouteDisplayName(routes.get(0));
askForRouteFilter.setText(String.format("Do you want to hear arrivals for %s for stop %s?", routeName, stopId));
} else {
ArrayList<ObaRoute> routesToAskAbout = (ArrayList<ObaRoute>) session.getAttribute(DIALOG_ROUTES_TO_ASK_ABOUT);
LinkedHashMap<String, String> routeData = (LinkedHashMap<String, String>) routesToAskAbout.get(0);
routeName = UIUtils.getRouteDisplayName(routeData.get("shortName"), routeData.get("longName"));
askForRouteFilter.setText(String.format("Ok, how about %s?", routeName));
}

Reprompt askForRouteFilterReprompt = new Reprompt();
PlainTextOutputSpeech repromptText = new PlainTextOutputSpeech();
repromptText.setText(String.format("Did you want to hear arrivals for %s for stop %s?", routeName, stopId));
askForRouteFilterReprompt.setOutputSpeech(repromptText);

session.setAttribute(ASK_STATE, AskState.FILTER_INDIVIDUAL_ROUTE.toString());
return SpeechletResponse.newAskResponse(askForRouteFilter, askForRouteFilterReprompt);
}

private SpeechletResponse handleYesIntent(final IntentRequest request,
final Session session, AskState askState) throws SpeechletException {
if (askState == AskState.VERIFYSTOP) {
// User confirmed that they want to select a particular stop - pass to anonSpeechlet to finish dialog
return anonSpeechlet.onIntent(request, session);
}

if (askState == AskState.FILTER_INDIVIDUAL_ROUTE) {
return handleFilterIndividualRoute(session, true);
}

log.error("Received yes intent without a question.");
return anonSpeechlet.askForCity(Optional.empty());
}

private SpeechletResponse handleNoIntent(final IntentRequest request,
Session session, AskState askState) throws SpeechletException {
if (askState == AskState.VERIFYSTOP) {
return anonSpeechlet.onIntent(request, session);
}

if (askState == AskState.FILTER_INDIVIDUAL_ROUTE) {
return handleFilterIndividualRoute(session, false);
}

log.error("Received no intent without a question.");
return anonSpeechlet.askForCity(Optional.empty());
}

/**
* User responded saying they did (hearArrivals==true) or did not (hearArrivals==false) want to hear arrivals for a
* particular stop (STOP_NUMBER of session) and a paricular route (the 0 index route in the SessionAttribute
* DIALOG_ROUTES_TO_ASK_ABOUT ArrayList)
*
* @param session
* @param hearArrivals true if the user wanted to hear arrivals about the 0 index route, false if they did not
* @return
* @throws SpeechletException
*/
private SpeechletResponse handleFilterIndividualRoute(Session session, boolean hearArrivals) throws SpeechletException {
ArrayList<ObaRoute> routes = (ArrayList<ObaRoute>) session.getAttribute(DIALOG_ROUTES_TO_ASK_ABOUT);
if (routes == null) {
// Something went wrong
return SpeechUtil.getGeneralErrorMessage();
}
HashSet<String> routesToFilter = (HashSet<String>) session.getAttribute(DIALOG_ROUTES_TO_FILTER);
if (routesToFilter == null) {
routesToFilter = new HashSet<>();
}

// Get the last route we asked about - there should be at least one
LinkedHashMap<String, String> routeData = (LinkedHashMap<String, String>) routes.get(0);

if (!hearArrivals) {
// The user doesn't want to hear arrivals for this route, so add the routeId to set of route filters
routesToFilter.add(routeData.get("id"));
session.setAttribute(DIALOG_ROUTES_TO_FILTER, routesToFilter);
}

// Remove route we just asked about, so we can ask about the next one (if there are any left)
routes.remove(0);

if (routes.size() > 0) {
// Ask about the next route
session.setAttribute(DIALOG_ROUTES_TO_ASK_ABOUT, routes);
return askToFilterRoute(session, null);
}

// We've asked about all routes for this stop, so persist the route filter
String stopId = (String) session.getAttribute(STOP_NUMBER);
HashMap<String, HashSet<String>> persistedRouteFilter = userData.getRoutesToFilter();
if (persistedRouteFilter == null) {
persistedRouteFilter = new HashMap<>();
}
persistedRouteFilter.put(stopId, routesToFilter);
userData.setRoutesToFilter(persistedRouteFilter);
obaDao.saveUserData(userData);

String output = String.format("Alright, I've saved your route filter for your current stop %s.", stopId);
saveOutputForRepeat(output);
PlainTextOutputSpeech out = new PlainTextOutputSpeech();
out.setText(output);
return SpeechletResponse.newTellResponse(out);
}

/**
* Gets a previously cached response to repeat to the user
* @param session
Expand Down
Loading

0 comments on commit d597c2e

Please sign in to comment.