/* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.actions.api.smarthome.*; import com.google.cloud.firestore.QueryDocumentSnapshot; import com.google.gson.Gson; import com.google.home.graph.v1.DeviceProto; import com.google.protobuf.Struct; import com.google.protobuf.util.JsonFormat; public class MySmartHomeApp extends SmartHomeApp { private static final Logger LOGGER = LoggerFactory.getLogger(MySmartHomeApp.class); private static MyDataStore database = MyDataStore.getInstance(); @NotNull @Override public SyncResponse onSync(SyncRequest syncRequest, Map<?, ?> headers) { SyncResponse res = new SyncResponse(); res.setRequestId(syncRequest.requestId); res.setPayload(new SyncResponse.Payload()); String token = (String) headers.get("authorization"); String userId = ""; try { userId = database.getUserId(token); } catch (Exception e) { // TODO(proppy): add errorCode when // https://github.com/actions-on-google/actions-on-google-java/issues/44 is fixed. LOGGER.error("failed to get user id for token: %d", token); return res; } res.payload.agentUserId = userId; database.setHomegraph(userId, true); List<QueryDocumentSnapshot> devices = new ArrayList<>(); try { devices = database.getDevices(userId); } catch (ExecutionException | InterruptedException e) { LOGGER.error("failed to get devices", e); return res; } int numOfDevices = devices.size(); res.payload.devices = new SyncResponse.Payload.Device[numOfDevices]; for (int i = 0; i < numOfDevices; i++) { QueryDocumentSnapshot device = devices.get(i); SyncResponse.Payload.Device.Builder deviceBuilder = new SyncResponse.Payload.Device.Builder() .setId(device.getId()) .setType((String) device.get("type")) .setTraits((List<String>) device.get("traits")) .setName( DeviceProto.DeviceNames.newBuilder() .addAllDefaultNames((List<String>) device.get("defaultNames")) .setName((String) device.get("name")) .addAllNicknames((List<String>) device.get("nicknames")) .build()) .setWillReportState((Boolean) device.get("willReportState")) .setRoomHint((String) device.get("roomHint")) .setDeviceInfo( DeviceProto.DeviceInfo.newBuilder() .setManufacturer((String) device.get("manufacturer")) .setModel((String) device.get("model")) .setHwVersion((String) device.get("hwVersion")) .setSwVersion((String) device.get("swVersion")) .build()); if (device.contains("attributes")) { Map<String, Object> attributes = new HashMap<>(); attributes.putAll((Map<String, Object>) device.get("attributes")); String attributesJson = new Gson().toJson(attributes); Struct.Builder attributeBuilder = Struct.newBuilder(); try { JsonFormat.parser().ignoringUnknownFields().merge(attributesJson, attributeBuilder); } catch (Exception e) { LOGGER.error("FAILED TO BUILD"); } deviceBuilder.setAttributes(attributeBuilder.build()); } if (device.contains("customData")) { Map<String, Object> customData = new HashMap<>(); customData.putAll((Map<String, Object>) device.get("customData")); // TODO(proppy): remove once // https://github.com/actions-on-google/actions-on-google-java/issues/43 is fixed. String customDataJson = new Gson().toJson(customData); deviceBuilder.setCustomData(customDataJson); } if (device.contains("otherDeviceIds")) { deviceBuilder.setOtherDeviceIds((List) device.get("otherDeviceIds")); } res.payload.devices[i] = deviceBuilder.build(); } return res; } @NotNull @Override public QueryResponse onQuery(QueryRequest queryRequest, Map<?, ?> headers) { QueryRequest.Inputs.Payload.Device[] devices = ((QueryRequest.Inputs) queryRequest.getInputs()[0]).payload.devices; QueryResponse res = new QueryResponse(); res.setRequestId(queryRequest.requestId); res.setPayload(new QueryResponse.Payload()); String token = (String) headers.get("authorization"); String userId = ""; try { userId = database.getUserId(token); } catch (Exception e) { LOGGER.error("failed to get user id for token: %d", headers.get("authorization")); res.payload.setErrorCode("authFailure"); return res; } Map<String, Map<String, Object>> deviceStates = new HashMap<>(); for (QueryRequest.Inputs.Payload.Device device : devices) { try { Map<String, Object> deviceState = database.getState(userId, device.id); deviceState.put("status", "SUCCESS"); deviceStates.put(device.id, deviceState); ReportState.makeRequest(this, userId, device.id, deviceState); } catch (Exception e) { LOGGER.error("QUERY FAILED: {}", e); Map<String, Object> failedDevice = new HashMap<>(); failedDevice.put("status", "ERROR"); failedDevice.put("errorCode", "deviceOffline"); deviceStates.put(device.id, failedDevice); } } res.payload.setDevices(deviceStates); return res; } @NotNull @Override public ExecuteResponse onExecute(ExecuteRequest executeRequest, Map<?, ?> headers) { ExecuteResponse res = new ExecuteResponse(); String token = (String) headers.get("authorization"); String userId = ""; try { userId = database.getUserId(token); } catch (Exception e) { LOGGER.error("failed to get user id for token: %d", headers.get("authorization")); res.setPayload(new ExecuteResponse.Payload()); res.payload.setErrorCode("authFailure"); return res; } List<ExecuteResponse.Payload.Commands> commandsResponse = new ArrayList<>(); List<String> successfulDevices = new ArrayList<>(); Map<String, Object> states = new HashMap<>(); ExecuteRequest.Inputs.Payload.Commands[] commands = ((ExecuteRequest.Inputs) executeRequest.inputs[0]).payload.commands; for (ExecuteRequest.Inputs.Payload.Commands command : commands) { for (ExecuteRequest.Inputs.Payload.Commands.Devices device : command.devices) { try { states = database.execute(userId, device.id, command.execution[0]); successfulDevices.add(device.id); ReportState.makeRequest(this, userId, device.id, states); } catch (Exception e) { if (e.getMessage().equals("PENDING")) { ExecuteResponse.Payload.Commands pendingDevice = new ExecuteResponse.Payload.Commands(); pendingDevice.ids = new String[] {device.id}; pendingDevice.status = "PENDING"; commandsResponse.add(pendingDevice); continue; } if (e.getMessage().equals("pinNeeded")) { ExecuteResponse.Payload.Commands failedDevice = new ExecuteResponse.Payload.Commands(); failedDevice.ids = new String[] {device.id}; failedDevice.status = "ERROR"; failedDevice.setErrorCode("challengeNeeded"); failedDevice.setChallengeNeeded( new HashMap<String, String>() { { put("type", "pinNeeded"); } }); failedDevice.setErrorCode(e.getMessage()); commandsResponse.add(failedDevice); continue; } if (e.getMessage().equals("challengeFailedPinNeeded")) { ExecuteResponse.Payload.Commands failedDevice = new ExecuteResponse.Payload.Commands(); failedDevice.ids = new String[] {device.id}; failedDevice.status = "ERROR"; failedDevice.setErrorCode("challengeNeeded"); failedDevice.setChallengeNeeded( new HashMap<String, String>() { { put("type", "challengeFailedPinNeeded"); } }); failedDevice.setErrorCode(e.getMessage()); commandsResponse.add(failedDevice); continue; } if (e.getMessage().equals("ackNeeded")) { ExecuteResponse.Payload.Commands failedDevice = new ExecuteResponse.Payload.Commands(); failedDevice.ids = new String[] {device.id}; failedDevice.status = "ERROR"; failedDevice.setErrorCode("challengeNeeded"); failedDevice.setChallengeNeeded( new HashMap<String, String>() { { put("type", "ackNeeded"); } }); failedDevice.setErrorCode(e.getMessage()); commandsResponse.add(failedDevice); continue; } ExecuteResponse.Payload.Commands failedDevice = new ExecuteResponse.Payload.Commands(); failedDevice.ids = new String[] {device.id}; failedDevice.status = "ERROR"; failedDevice.setErrorCode(e.getMessage()); commandsResponse.add(failedDevice); } } } ExecuteResponse.Payload.Commands successfulCommands = new ExecuteResponse.Payload.Commands(); successfulCommands.status = "SUCCESS"; successfulCommands.setStates(states); successfulCommands.ids = successfulDevices.toArray(new String[] {}); commandsResponse.add(successfulCommands); res.requestId = executeRequest.requestId; ExecuteResponse.Payload payload = new ExecuteResponse.Payload( commandsResponse.toArray(new ExecuteResponse.Payload.Commands[] {})); res.setPayload(payload); return res; } @NotNull @Override public void onDisconnect(DisconnectRequest disconnectRequest, Map<?, ?> headers) { String token = (String) headers.get("authorization"); try { String userId = database.getUserId(token); database.setHomegraph(userId, false); } catch (Exception e) { LOGGER.error("failed to get user id for token: %d", token); } } }