diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/MySmartHomeApp.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/MySmartHomeApp.java new file mode 100644 index 00000000..3b06c3cd --- /dev/null +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/MySmartHomeApp.java @@ -0,0 +1,219 @@ +/* + * 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 se.hal.plugin.assistant.google; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.google.actions.api.smarthome.*; +import com.google.gson.Gson; +import com.google.home.graph.v1.DeviceProto; +import com.google.protobuf.Struct; +import com.google.protobuf.util.JsonFormat; +import zutil.log.LogUtil; + + +public class MySmartHomeApp extends SmartHomeApp { + private static final Logger logger = LogUtil.getLogger(); + + + @Override + public SyncResponse onSync(SyncRequest syncRequest, Map headers) { + + SyncResponse res = new SyncResponse(); + res.setRequestId(syncRequest.requestId); + res.setPayload(new SyncResponse.Payload()); + + int numOfDevices = 0; + res.payload.devices = new SyncResponse.Payload.Device[numOfDevices]; + for (int i = 0; i < numOfDevices; i++) { + + SyncResponse.Payload.Device.Builder deviceBuilder = + new SyncResponse.Payload.Device.Builder() + .setId(device.getId()) + .setType((String) device.get("type")) + .setTraits((List) device.get("traits")) + .setName( + DeviceProto.DeviceNames.newBuilder() + .addAllDefaultNames((List) device.get("defaultNames")) + .setName((String) device.get("name")) + .addAllNicknames((List) 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 attributes = new HashMap<>(); + attributes.putAll((Map) 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 customData = new HashMap<>(); + customData.putAll((Map) device.get("customData")); + + 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; + } + + @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()); + + Map> deviceStates = new HashMap<>(); + for (QueryRequest.Inputs.Payload.Device device : devices) { + try { + Map deviceState = database.getState(userId, device.id); + deviceState.put("status", "SUCCESS"); + deviceStates.put(device.id, deviceState); + } catch (Exception e) { + logger.error("QUERY FAILED: {}", e); + Map failedDevice = new HashMap<>(); + failedDevice.put("status", "ERROR"); + failedDevice.put("errorCode", "deviceOffline"); + deviceStates.put(device.id, failedDevice); + } + } + res.payload.setDevices(deviceStates); + return res; + } + + @Override + public ExecuteResponse onExecute(ExecuteRequest executeRequest, Map headers) { + ExecuteResponse res = new ExecuteResponse(); + + List commandsResponse = new ArrayList<>(); + List successfulDevices = new ArrayList<>(); + Map 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() { + { + 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() { + { + 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() { + { + 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; + } + + @Override + public void onDisconnect(DisconnectRequest disconnectRequest, Map headers) { + + } +} diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/ReportState.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/ReportState.java new file mode 100644 index 00000000..e62e4597 --- /dev/null +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/ReportState.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 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 se.hal.plugin.assistant.google; + +import java.util.Map; +import java.util.logging.Logger; + +import com.google.actions.api.smarthome.SmartHomeApp; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.home.graph.v1.HomeGraphApiServiceProto; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.google.protobuf.util.JsonFormat; +import zutil.log.LogUtil; + +/** + * A singleton class to encapsulate state reporting behavior with changing ColorSetting state + * values. + */ +final class ReportState { + private static final Logger logger = LogUtil.getLogger(); + + + private ReportState() {} + + /** + * Creates and completes a ReportStateAndNotification request + * + * @param actionsApp The SmartHomeApp instance to use to make the gRPC request + * @param userId The agent user ID + * @param deviceId The device ID + * @param states A Map of state keys and their values for the provided device ID + */ + public static void makeRequest(SmartHomeApp actionsApp, String userId, String deviceId, Map states) { + // Convert a Map of states to a JsonObject + JsonObject jsonStates = (JsonObject) JsonParser.parseString(new Gson().toJson(states)); + ReportState.makeRequest(actionsApp, userId, deviceId, jsonStates); + } + + /** + * Creates and completes a ReportStateAndNotification request + * + * @param actionsApp The SmartHomeApp instance to use to make the gRPC request + * @param userId The agent user ID + * @param deviceId The device ID + * @param states A JSON object of state keys and their values for the provided device ID + */ + public static void makeRequest(SmartHomeApp actionsApp, String userId, String deviceId, JsonObject states) { + // Do state name replacement for ColorSetting trait + // See https://developers.google.com/assistant/smarthome/traits/colorsetting#device-states + JsonObject colorJson = states.getAsJsonObject("color"); + if (colorJson != null && colorJson.has("spectrumRgb")) { + colorJson.add("spectrumRGB", colorJson.get("spectrumRgb")); + colorJson.remove("spectrumRgb"); + } + Struct.Builder statesStruct = Struct.newBuilder(); + try { + JsonFormat.parser().ignoringUnknownFields().merge(new Gson().toJson(states), statesStruct); + } catch (Exception e) { + logger.severe("Failed to build json"); + e.printStackTrace(); + } + + HomeGraphApiServiceProto.ReportStateAndNotificationDevice.Builder deviceBuilder = + HomeGraphApiServiceProto.ReportStateAndNotificationDevice.newBuilder() + .setStates( + Struct.newBuilder() + .putFields(deviceId, Value.newBuilder().setStructValue(statesStruct).build())); + + HomeGraphApiServiceProto.ReportStateAndNotificationRequest request = + HomeGraphApiServiceProto.ReportStateAndNotificationRequest.newBuilder() + .setRequestId(String.valueOf(Math.random())) + .setAgentUserId(userId) // our single user's id + .setPayload( + HomeGraphApiServiceProto.StateAndNotificationPayload.newBuilder() + .setDevices(deviceBuilder)) + .build(); + + actionsApp.reportState(request); + } +} diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDeviceTrait.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDeviceTrait.java new file mode 100644 index 00000000..b91a8382 --- /dev/null +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDeviceTrait.java @@ -0,0 +1,79 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package se.hal.plugin.assistant.google; + +/** + * Enum for https://developers.google.com/assistant/smarthome/traits + */ +public enum SmartHomeDeviceTrait { + AppSelector("action.devices.traits.AppSelector"), + ArmDisarm("action.devices.traits.ArmDisarm"), + Brightness("action.devices.traits.Brightness"), + CameraStream("action.devices.traits.CameraStream"), + Channel("action.devices.traits.Channel"), + ColorSetting("action.devices.traits.ColorSetting"), + Cook("action.devices.traits.Cook"), + Dispense("action.devices.traits.Dispense"), + Dock("action.devices.traits.Dock"), + EnergyStorage("action.devices.traits.EnergyStorage"), + FanSpeed("action.devices.traits.FanSpeed"), + Fill("action.devices.traits.Fill"), + HumiditySetting("action.devices.traits.HumiditySetting"), + InputSelector("action.devices.traits.InputSelector"), + LightEffects("action.devices.traits.LightEffects"), + Locator("action.devices.traits.Locator"), + LockUnlock("action.devices.traits.LockUnlock"), + MediaState("action.devices.traits.MediaState"), + Modes("action.devices.traits.Modes"), + NetworkControl("action.devices.traits.NetworkControl"), + OnOff("action.devices.traits.OnOff"), + OpenClose("action.devices.traits.OpenClose"), + Reboot("action.devices.traits.Reboot"), + Rotation("action.devices.traits.Rotation"), + RunCycle("action.devices.traits.RunCycle"), + SensorState("action.devices.traits.SensorState"), + Scene("action.devices.traits.Scene"), + SoftwareUpdate("action.devices.traits.SoftwareUpdate"), + StartStop("action.devices.traits.StartStop"), + StatusReport("action.devices.traits.StatusReport"), + TemperatureControl("action.devices.traits.TemperatureControl"), + TemperatureSetting("action.devices.traits.TemperatureSetting"), + Timer("action.devices.traits.Timer"), + Toggles("action.devices.traits.Toggles"), + TransportControl("action.devices.traits.TransportControl"), + Volume("action.devices.traits.Volume"); + + + + private final String typeId; + + private SmartHomeDeviceTrait(String typeId) { + this.typeId = typeId; + } + + public String getString() { + return typeId; + } +} diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDeviceType.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDeviceType.java new file mode 100644 index 00000000..ed69f599 --- /dev/null +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDeviceType.java @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package se.hal.plugin.assistant.google; + +/** + * Enum for https://developers.google.com/assistant/smarthome/types + */ +public enum SmartHomeDeviceType { + AC_UNIT("action.devices.types.AC_UNIT"), + AIRCOOLER("action.devices.types.AIRCOOLER"), + AIRFRESHENER("action.devices.types.AIRFRESHENER"), + AIRPURIFIER("action.devices.types.AIRPURIFIER"), + AUDIO_VIDEO_RECEIVER("action.devices.types.AUDIO_VIDEO_RECEIVER"), + AWNING("action.devices.types.AWNING"), + BATHTUB("action.devices.types.BATHTUB"), + BED("action.devices.types.BED"), + BLENDER("action.devices.types.BLENDER"), + BLINDS("action.devices.types.BLINDS"), + BOILER("action.devices.types.BOILER"), + CAMERA("action.devices.types.CAMERA"), + CHARGER("action.devices.types.CHARGER"), + CLOSET("action.devices.types.CLOSET"), + COFFEE_MAKER("action.devices.types.COFFEE_MAKER"), + COOKTOP("action.devices.types.COOKTOP"), + CURTAIN("action.devices.types.CURTAIN"), + DEHUMIDIFIER("action.devices.types.DEHUMIDIFIER"), + DEHYDRATOR("action.devices.types.DEHYDRATOR"), + DISHWASHERv("action.devices.types.DISHWASHER"), + DOOR("action.devices.types.DOOR"), + DRAWER("action.devices.types.DRAWER"), + DRYER("action.devices.types.DRYER"), + FAN("action.devices.types.FAN"), + FAUCET("action.devices.types.FAUCET"), + FIREPLACE("action.devices.types.FIREPLACE"), + FREEZER("action.devices.types.FREEZER"), + FRYER("action.devices.types.FRYER"), + GARAGE("action.devices.types.GARAGE"), + GATE("action.devices.types.GATE"), + GRILL("action.devices.types.GRILL"), + HEATER("action.devices.types.HEATER"), + HOOD("action.devices.types.HOOD"), + HUMIDIFIER("action.devices.types.HUMIDIFIER"), + KETTLE("action.devices.types.KETTLE"), + LIGHT("action.devices.types.LIGHT"), + LOCK("action.devices.types.LOCK"), + REMOTECONTROL("action.devices.types.REMOTECONTROL"), + MOP("action.devices.types.MOP"), + MOWER("action.devices.types.MOWER"), + MICROWAVE("action.devices.types.MICROWAVE"), + MULTICOOKER("action.devices.types.MULTICOOKER"), + NETWORK("action.devices.types.NETWORK"), + OUTLET("action.devices.types.OUTLET"), + OVEN("action.devices.types.OVEN"), + PERGOLA("action.devices.types.PERGOLA"), + PETFEEDER("action.devices.types.PETFEEDER"), + PRESSURECOOKER("action.devices.types.PRESSURECOOKER"), + RADIATOR("action.devices.types.RADIATOR"), + REFRIGERATOR("action.devices.types.REFRIGERATOR"), + ROUTER("action.devices.types.ROUTER"), + SCENE("action.devices.types.SCENE"), + SENSOR("action.devices.types.SENSOR"), + SECURITYSYSTEM("action.devices.types.SECURITYSYSTEM"), + SETTOP("action.devices.types.SETTOP"), + SHUTTER("action.devices.types.SHUTTER"), + SHOWER("action.devices.types.SHOWER"), + SMOKE_DETECTOR("action.devices.types.SMOKE_DETECTOR"), + SOUSVIDE("action.devices.types.SOUSVIDE"), + SPEAKER("action.devices.types.SPEAKER"), + STREAMING_BOX("action.devices.types.STREAMING_BOX"), + STREAMING_STICK("action.devices.types.STREAMING_STICK"), + STREAMING_SOUNDBAR("action.devices.types.STREAMING_SOUNDBAR"), + SOUNDBAR("action.devices.types.SOUNDBAR"), + SPRINKLER("action.devices.types.SPRINKLER"), + STANDMIXER("action.devices.types.STANDMIXER"), + SWITCH("action.devices.types.SWITCH"), + TV("action.devices.types.TV"), + THERMOSTAT("action.devices.types.THERMOSTAT"), + VACUUM("action.devices.types.VACUUM"), + VALVE("action.devices.types.VALVE"), + WASHER("action.devices.types.WASHER"), + WATERHEATER("action.devices.types.WATERHEATER"), + WATERPURIFIER("action.devices.types.WATERPURIFIER"), + WATERSOFTENER("action.devices.types.WATERSOFTENER"), + WINDOW("action.devices.types.WINDOW"), + YOGURTMAKER("action.devices.types.YOGURTMAKER"); + + + private final String typeId; + + private SmartHomeDeviceType(String typeId) { + this.typeId = typeId; + } + + public String getString() { + return typeId; + } +} diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/FakeAuthServlet.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/FakeAuthServlet.java new file mode 100644 index 00000000..0b95d58e --- /dev/null +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/FakeAuthServlet.java @@ -0,0 +1,79 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * 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 se.hal.plugin.assistant.google.endpoint; + +import se.hal.intf.HalJsonPage; +import zutil.net.http.HttpHeader; +import zutil.net.http.HttpPrintStream; +import zutil.parser.DataNode; + +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; + + +public class FakeAuthServlet extends HalJsonPage { + + public FakeAuthServlet() { + super("api/assistant/google/auth"); + } + + @Override + protected DataNode jsonRespond( + HttpPrintStream out, + HttpHeader headers, + Map session, + Map cookie, + Map request) throws Exception { + + StringBuilder redirectURL = new StringBuilder(); + redirectURL.append(URLDecoder.decode(request.get("redirect_uri"), StandardCharsets.UTF_8)); + redirectURL.append("?"); + redirectURL.append("code=").append("xxxxxx"); + redirectURL.append("state=").append(request.get("state")); + + out.setStatusCode(302); + out.setHeader("Location", URLEncoder.encode("/login?responseurl=" + redirectURL.toString(), StandardCharsets.UTF_8)); + + return new DataNode(DataNode.DataType.Map); + } +} diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/FakeTokenServlet.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/FakeTokenServlet.java new file mode 100644 index 00000000..e32d579f --- /dev/null +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/FakeTokenServlet.java @@ -0,0 +1,79 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * 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 se.hal.plugin.assistant.google.endpoint; + +import java.util.Map; + +import se.hal.intf.HalJsonPage; +import zutil.net.http.HttpHeader; +import zutil.net.http.HttpPrintStream; +import zutil.parser.DataNode; + + +public class FakeTokenServlet extends HalJsonPage { + private static int secondsInDay = 86400; + + public FakeTokenServlet() { + super("api/assistant/google/auth_token"); + } + + @Override + protected DataNode jsonRespond( + HttpPrintStream out, + HttpHeader headers, + Map session, + Map cookie, + Map request) throws Exception { + + String grantType = request.get("grant_type"); + + DataNode jsonRes = new DataNode(DataNode.DataType.Map); + jsonRes.set("token_type", "bearer"); + jsonRes.set("access_token", "123access"); + jsonRes.set("expires_in", secondsInDay); + + if (grantType.equals("authorization_code")) { + jsonRes.set("refresh_token", "123refresh"); + } + + return jsonRes; + } +} diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomeServlet.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomeServlet.java new file mode 100644 index 00000000..47e671ad --- /dev/null +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomeServlet.java @@ -0,0 +1,108 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * 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 se.hal.plugin.assistant.google.endpoint; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import com.google.actions.api.smarthome.SmartHomeApp; +import com.google.auth.oauth2.GoogleCredentials; +import se.hal.intf.HalJsonPage; +import se.hal.plugin.assistant.google.MySmartHomeApp; +import zutil.io.IOUtil; +import zutil.log.LogUtil; +import zutil.net.http.HttpHeader; +import zutil.net.http.HttpPrintStream; +import zutil.parser.DataNode; + +/** + * Handles request received via HTTP POST and delegates it to your Actions app. See: [Request + * handling in Google App + * Engine](https://cloud.google.com/appengine/docs/standard/java/how-requests-are-handled). + */ +public class SmartHomeServlet extends HalJsonPage { + private static final Logger logger = LogUtil.getLogger(); + private final SmartHomeApp actionsApp = new MySmartHomeApp(); + + { + try { + GoogleCredentials credentials = + GoogleCredentials.fromStream(getClass().getResourceAsStream("/smart-home-key.json")); + actionsApp.setCredentials(credentials); + } catch (Exception e) { + logger.severe("couldn't load credentials"); + } + } + + + public SmartHomeServlet() { + super("api/assistant/google/smarthome"); + } + + + @Override + protected DataNode jsonRespond( + HttpPrintStream out, + HttpHeader headers, + Map session, + Map cookie, + Map request) throws Exception { + + String body = IOUtil.readContentAsString(headers.getInputStream()); + logger.info("doPost, body = " + body); + + try { + String response = actionsApp.handleRequest(body, request).get(); + + System.out.println("response = " + asJson); + res.getWriter().write(asJson); + res.getWriter().flush(); + } catch (ExecutionException | InterruptedException e) { + logger.log(Level.SEVERE, "Failed to handle fulfillment request", e); + } + } +} \ No newline at end of file diff --git a/plugins/hal-tellstick/src/se/hal/plugin/tellstick/TellstickParser.java b/plugins/hal-tellstick/src/se/hal/plugin/tellstick/TellstickParser.java index 2546f335..bb19d889 100644 --- a/plugins/hal-tellstick/src/se/hal/plugin/tellstick/TellstickParser.java +++ b/plugins/hal-tellstick/src/se/hal/plugin/tellstick/TellstickParser.java @@ -35,8 +35,6 @@ import java.util.logging.Level; import java.util.logging.Logger; /** - * Created by Ziver on 2015-02-18. - * * Protocol Specification: http://developer.telldus.com/doxygen/TellStick.html */ public class TellstickParser { @@ -53,6 +51,7 @@ public class TellstickParser { /** * Will decode the given data and return a list with device and data objects + * * @param data * @return a list with decoded objects or empty list if there was an error */ @@ -114,7 +113,7 @@ public class TellstickParser { public static void registerProtocol(Class protClass) { try { - registerProtocol( protClass.newInstance() ); + registerProtocol(protClass.newInstance()); } catch (Exception e) { logger.log(Level.SEVERE, null, e); } diff --git a/plugins/hal-zigbee/src/se/hal/plugin/zigbee/deconz/rest/DeConzRestConfig.java b/plugins/hal-zigbee/src/se/hal/plugin/zigbee/deconz/rest/DeConzRestConfig.java index 9fe27507..ca56cf04 100644 --- a/plugins/hal-zigbee/src/se/hal/plugin/zigbee/deconz/rest/DeConzRestConfig.java +++ b/plugins/hal-zigbee/src/se/hal/plugin/zigbee/deconz/rest/DeConzRestConfig.java @@ -79,18 +79,18 @@ public interface DeConzRestConfig { /** - * Change the Password of the Gateway. The parameter must be a Base64 encoded combination of “:”. + * Change the Password of the Gateway. The parameter must be a Base64 encoded combination of ":". * - * @param username The user name (currently only “delight” is supported). (required) - * @param oldHash String The Base64 encoded combination of “username:old password”. (required) - * @param newHash String The Base64 encoded combination of “username:new password”. (required) + * @param username The user name (currently only "delight" is supported). (required) + * @param oldHash String The Base64 encoded combination of "username:old password". (required) + * @param newHash String The Base64 encoded combination of "username:new password". (required) */ @WSRequestType(HTTP_PUT) @WSPath("/api/{{requestApiKey}}/config/password") void setPassword(String requestApiKey, String username, String oldHash, String newHash); /** - * Resets the username and password to default (“delight”,”delight”). Only possible within 10 minutes after gateway start. + * Resets the username and password to default ("delight","delight"). Only possible within 10 minutes after gateway start. */ @WSRequestType(HTTP_DELETE) @WSPath("/api/{{requestApiKey}}/config/password") diff --git a/src/se/hal/intf/HalJsonPage.java b/src/se/hal/intf/HalJsonPage.java index 68a10d4d..107693af 100755 --- a/src/se/hal/intf/HalJsonPage.java +++ b/src/se/hal/intf/HalJsonPage.java @@ -30,14 +30,14 @@ public abstract class HalJsonPage extends HalWebPage { Map cookie, Map request) throws IOException { - - out.setHeader("Content-Type", "application/json"); + out.setHeader("Access-Control-Allow-Origin", "*"); out.setHeader("Pragma", "no-cache"); + JSONWriter writer = new JSONWriter(out); try{ writer.write( - jsonRespond(session, cookie, request)); + jsonRespond(out, headers, session, cookie, request)); } catch (Exception e){ logger.log(Level.SEVERE, null, e); DataNode root = new DataNode(DataNode.DataType.Map); @@ -46,6 +46,8 @@ public abstract class HalJsonPage extends HalWebPage { } writer.close(); } + + @Override public Templator httpRespond( Map session, @@ -56,6 +58,8 @@ public abstract class HalJsonPage extends HalWebPage { protected abstract DataNode jsonRespond( + HttpPrintStream out, + HttpHeader headers, Map session, Map cookie, Map request) throws Exception; diff --git a/src/se/hal/intf/HalWebPage.java b/src/se/hal/intf/HalWebPage.java index d3671c07..2eeb1169 100644 --- a/src/se/hal/intf/HalWebPage.java +++ b/src/se/hal/intf/HalWebPage.java @@ -61,7 +61,7 @@ public abstract class HalWebPage implements HttpPage{ } /** - * Sets if the subnavigation should be shown on the page + * Defines if the sub-navigation should be shown on the page */ protected void showSubNav(boolean show) { this.showSubNav = show; diff --git a/src/se/hal/page/MapJsonPage.java b/src/se/hal/page/MapJsonPage.java index 3694d180..bbb07ccb 100755 --- a/src/se/hal/page/MapJsonPage.java +++ b/src/se/hal/page/MapJsonPage.java @@ -7,6 +7,8 @@ import se.hal.struct.Event; import se.hal.struct.Sensor; import zutil.db.DBConnection; import zutil.log.LogUtil; +import zutil.net.http.HttpHeader; +import zutil.net.http.HttpPrintStream; import zutil.parser.DataNode; import java.sql.SQLException; @@ -25,9 +27,11 @@ public class MapJsonPage extends HalJsonPage { @Override - public DataNode jsonRespond(Map session, - Map cookie, - Map request) throws Exception { + public DataNode jsonRespond(HttpPrintStream out, + HttpHeader headers, + Map session, + Map cookie, + Map request) throws Exception { DBConnection db = HalContext.getDB(); DataNode root = new DataNode(DataNode.DataType.Map); diff --git a/src/se/hal/page/SensorJsonPage.java b/src/se/hal/page/SensorJsonPage.java index 6eafedad..356e8743 100755 --- a/src/se/hal/page/SensorJsonPage.java +++ b/src/se/hal/page/SensorJsonPage.java @@ -9,6 +9,8 @@ import se.hal.util.UTCTimeUtility; import zutil.ArrayUtil; import zutil.db.DBConnection; import zutil.log.LogUtil; +import zutil.net.http.HttpHeader; +import zutil.net.http.HttpPrintStream; import zutil.parser.DataNode; import java.util.ArrayList; @@ -37,6 +39,8 @@ public class SensorJsonPage extends HalJsonPage { @Override public DataNode jsonRespond( + HttpPrintStream out, + HttpHeader headers, Map session, Map cookie, Map request) throws Exception{