Java tutorial
/** * Copyright (c) 2010-2019 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 */ package org.openhab.binding.robonect.internal; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.lang.StringUtils; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.AuthenticationStore; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.util.B64Code; import org.openhab.binding.robonect.internal.model.ErrorList; import org.openhab.binding.robonect.internal.model.ModelParser; import org.openhab.binding.robonect.internal.model.MowerInfo; import org.openhab.binding.robonect.internal.model.MowerMode; import org.openhab.binding.robonect.internal.model.Name; import org.openhab.binding.robonect.internal.model.RobonectAnswer; import org.openhab.binding.robonect.internal.model.VersionInfo; import org.openhab.binding.robonect.internal.model.cmd.Command; import org.openhab.binding.robonect.internal.model.cmd.ErrorCommand; import org.openhab.binding.robonect.internal.model.cmd.ModeCommand; import org.openhab.binding.robonect.internal.model.cmd.NameCommand; import org.openhab.binding.robonect.internal.model.cmd.StartCommand; import org.openhab.binding.robonect.internal.model.cmd.StatusCommand; import org.openhab.binding.robonect.internal.model.cmd.StopCommand; import org.openhab.binding.robonect.internal.model.cmd.VersionCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@link RobonectClient} class is responsible to communicate with the robonect module via it's HTTP interface. * * The API of the module is documented here: http://robonect.de/viewtopic.php?f=10&t=37 * * @author Marco Meyer - Initial contribution */ public class RobonectClient { private final Logger logger = LoggerFactory.getLogger(RobonectClient.class); private final String baseUrl; private final HttpClient httpClient; private final ModelParser parser; private boolean jobRunning; /** * The {@link JobSettings} class holds the values required for starting a job. */ public static class JobSettings { private static final String TIME_REGEX = "^[012]\\d:\\d\\d$"; private final Logger logger = LoggerFactory.getLogger(RobonectClient.class); private ModeCommand.RemoteStart remoteStart; private ModeCommand.Mode after; private int duration; /** * returns the 'remote start' setting for the job. See {@link ModeCommand.RemoteStart} for details. * * @return - the remote start settings for the job. */ public ModeCommand.RemoteStart getRemoteStart() { if (remoteStart != null) { return remoteStart; } else { logger.debug("No explicit remote start set. Return STANDARD."); return ModeCommand.RemoteStart.STANDARD; } } /** * Sets the desired 'remote start' settings for the job. * * @param remoteStart - The 'remote start' settings. See {@link ModeCommand.RemoteStart} for the allowed modes. */ public JobSettings withRemoteStart(ModeCommand.RemoteStart remoteStart) { this.remoteStart = remoteStart; return this; } /** * Returns the mode the mower should be set to after the job is complete. * * @return - the mode after compleness of the job. */ public ModeCommand.Mode getAfterMode() { return after; } /** * Sets the mode after the mower is complete with the job. * * @param after - the desired mode after job completeness. */ public JobSettings withAfterMode(ModeCommand.Mode after) { this.after = after; return this; } public int getDuration() { return duration; } public JobSettings withDuration(int duration) { this.duration = duration; return this; } } private static class BasicResult implements Authentication.Result { private final HttpHeader header; private final URI uri; private final String value; public BasicResult(HttpHeader header, URI uri, String value) { this.header = header; this.uri = uri; this.value = value; } public URI getURI() { return this.uri; } public void apply(Request request) { request.header(this.header, this.value); } public String toString() { return String.format("Basic authentication result for %s", this.uri); } } /** * Creates an instance of RobonectClient which allows to communicate with the specified endpoint via the passed * httpClient instance. * * @param httpClient - The HttpClient to use for the communication. * @param endpoint - The endpoint information for connecting and issuing commands. */ public RobonectClient(HttpClient httpClient, RobonectEndpoint endpoint) { this.httpClient = httpClient; this.baseUrl = "http://" + endpoint.getIpAddress() + "/json"; this.parser = new ModelParser(); if (endpoint.isUseAuthentication()) { addPreemptiveAuthentication(httpClient, endpoint); } } private void addPreemptiveAuthentication(HttpClient httpClient, RobonectEndpoint endpoint) { AuthenticationStore auth = httpClient.getAuthenticationStore(); URI uri = URI.create(baseUrl); auth.addAuthenticationResult(new BasicResult(HttpHeader.AUTHORIZATION, uri, "Basic " + B64Code.encode(endpoint.getUser() + ":" + endpoint.getPassword(), StandardCharsets.ISO_8859_1))); } /** * returns general mower information. See {@MowerInfo} for the detailed information. * * @return - the general mower information including a general success status. */ public MowerInfo getMowerInfo() { String responseString = sendCommand(new StatusCommand()); MowerInfo mowerInfo = parser.parse(responseString, MowerInfo.class); if (jobRunning) { // mode might have been changed on the mower. Also Mode JOB does not really exist on the mower, thus cannot be checked here if (mowerInfo.getStatus().getMode() == MowerMode.AUTO || mowerInfo.getStatus().getMode() == MowerMode.HOME) { jobRunning = false; } else if (mowerInfo.getError() != null) { jobRunning = false; } } return mowerInfo; } /** * sends a start command to the mower. * * @return - a general answer with success status. */ public RobonectAnswer start() { String responseString = sendCommand(new StartCommand()); return parser.parse(responseString, RobonectAnswer.class); } /** * sends a stop command to the mower. * * @return - a general answer with success status. */ public RobonectAnswer stop() { String responseString = sendCommand(new StopCommand()); return parser.parse(responseString, RobonectAnswer.class); } /** * resets the errors on the mower. * * @return - a general answer with success status. */ public RobonectAnswer resetErrors() { String responseString = sendCommand(new ErrorCommand().withReset(true)); return parser.parse(responseString, RobonectAnswer.class); } /** * returns the list of all errors happened since last reset. * * @return - the list of errors. */ public ErrorList errorList() { String responseString = sendCommand(new ErrorCommand()); return parser.parse(responseString, ErrorList.class); } /** * Sets the mode of the mower. See {@link ModeCommand.Mode} for details about the available modes. Not allowed is mode * {@link ModeCommand.Mode#JOB}. * * @param mode - the desired mower mode. * @return - a general answer with success status. */ public RobonectAnswer setMode(ModeCommand.Mode mode) { String responseString = sendCommand(createCommand(mode)); if (jobRunning) { jobRunning = false; } return parser.parse(responseString, RobonectAnswer.class); } private ModeCommand createCommand(ModeCommand.Mode mode) { return new ModeCommand(mode); } /** * Returns the name of the mower. * * @return - The name including a general answer with success status. */ public Name getName() { String responseString = sendCommand(new NameCommand()); return parser.parse(responseString, Name.class); } /** * Allows to set the name of the mower. * * @param name - the desired name. * @return - The resulting name including a general answer with success status. */ public Name setName(String name) { String responseString = sendCommand(new NameCommand().withNewName(name)); return parser.parse(responseString, Name.class); } private String sendCommand(Command command) { try { if (logger.isDebugEnabled()) { logger.debug("send HTTP GET to: {} ", command.toCommandURL(baseUrl)); } ContentResponse response = httpClient.newRequest(command.toCommandURL(baseUrl)).method(HttpMethod.GET) .timeout(30000, TimeUnit.MILLISECONDS).send(); String responseString = null; // jetty uses UTF-8 as default encoding. However, HTTP 1.1 specifies ISO_8859_1 if (StringUtils.isBlank(response.getEncoding())) { responseString = new String(response.getContent(), StandardCharsets.ISO_8859_1); } else { // currently v0.9e Robonect does not specifiy the encoding. But if later versions will // add, it should work with the default method to get the content as string. responseString = response.getContentAsString(); } if (logger.isDebugEnabled()) { logger.debug("Response body was: {} ", responseString); } return responseString; } catch (ExecutionException | TimeoutException | InterruptedException e) { throw new RobonectCommunicationException("Could not send command " + command.toCommandURL(baseUrl), e); } } /** * Retrieve the version information of the mower and module. See {@link VersionInfo} for details. * * @return - the Version Information including the successful status. */ public VersionInfo getVersionInfo() { String versionResponse = sendCommand(new VersionCommand()); return parser.parse(versionResponse, VersionInfo.class); } public boolean isJobRunning() { return jobRunning; } public RobonectAnswer startJob(JobSettings settings) { Command jobCommand = new ModeCommand(ModeCommand.Mode.JOB).withRemoteStart(settings.remoteStart) .withAfter(settings.after).withDuration(settings.duration); String responseString = sendCommand(jobCommand); RobonectAnswer answer = parser.parse(responseString, RobonectAnswer.class); if (answer.isSuccessful()) { jobRunning = true; } else { jobRunning = false; } return answer; } public RobonectAnswer stopJob(JobSettings settings) { RobonectAnswer answer = null; if (jobRunning) { answer = setMode(settings.after); if (answer.isSuccessful()) { jobRunning = false; } } else { answer = new RobonectAnswer(); // this is not an error, thus return success answer.setSuccessful(true); } return answer; } }