package com.openvehicles.OVMS.entities; import android.content.Context; import android.util.Log; import com.openvehicles.OVMS.BaseApp; import com.openvehicles.OVMS.api.ApiService; import com.openvehicles.OVMS.api.OnResultCommandListener; import java.util.ArrayList; /** * Created by balzer on 09.03.15. * * This class takes care of execution of a series of commands. * All results will be stored in the Cmd.results arrays. * Execution stops on command errors (except empty history records). * * The Listener can react to progress and finish events. * Return code -1 on finish means execution was cancelled. * * See LogsFragment and BatteryFragment for complete usage examples. * */ public class CmdSeries implements OnResultCommandListener { private static final transient String TAG = "CmdSeries"; private transient static final Context context = BaseApp.getApp(); public interface Listener { /** * Progress callback. * * @param message -- user message for current command * @param pos -- position of current command * @param posCnt -- size of command series * @param step -- on multiple results: record position (else 0) * @param stepCnt -- on multiple results: record count (else 0) */ void onCmdSeriesProgress(String message, int pos, int posCnt, int step, int stepCnt); /** * Success / abort callback. * * @param cmdSeries -- the series * @param returnCode -- last result code or -1 on abort */ void onCmdSeriesFinish(CmdSeries cmdSeries, int returnCode); } /** * Single command entry of the series */ public class Cmd { private CmdSeries series; /** User message & command specification */ public String message; public String command; public int commandCode; /** Last return code */ public int returnCode; /** All results collected */ public ArrayList<String[]> results; public Cmd(CmdSeries series) { this.series = series; this.results = new ArrayList<String[]>(); } public int pos() { return series.current; } public int posCnt() { return series.cmdList.size(); } } private ApiService mService; private Listener mListener; private ArrayList<Cmd> cmdList; private int current; /** * Create new CmdSeries * * @param pService -- the ApiService (i.e. getService()) * @param pListener -- the Listener (optional) */ public CmdSeries(ApiService pService, Listener pListener) { mService = pService; mListener = pListener; cmdList = new ArrayList<Cmd>(); current = -1; } public int size() { return cmdList.size(); } public Cmd get(int i) { return cmdList.get(i); } /** * Add a command to be executed to the series. * The command will be added at the end. * * Example: * CmdSeries series = new CmdSeries(...) * .add("Setting feature #8 to 1...", "2,8,1") * .add("Setting param #11 to abc...", "4,11,abc"); * * @param pMessage -- user message to display & log * @param pCommand -- command string * @return -- CmdSeries for daisy chaining */ public CmdSeries add(String pMessage, String pCommand) { Cmd cmd = new Cmd(this); cmd.message = pMessage; cmd.command = pCommand; String code = pCommand.split(",", 2)[0]; cmd.commandCode = Integer.parseInt(code); cmdList.add(cmd); return this; // for daisy chaining } public CmdSeries add(int pMessageId, String pCommand) { return add(context.getString(pMessageId), pCommand); } /** * Get current command. * * @return -- current command or null if series is not running */ public Cmd getCurrent() { if (current >= 0 && current < cmdList.size()) return cmdList.get(current); else return null; } /** * Advance execution to next command in series. * * @return -- next command or null if end of series */ public Cmd getNext() { current += 1; return getCurrent(); } /** * Start execution of series at first command scheduled. * * @return -- this CmdSeries for daisy chaining */ public CmdSeries start() { Log.v(TAG, "started"); current = -1; executeNext(); return this; } /** * Execution handler: sends the current command to the server. * The command position in the series is told to the listener as the main progress. */ private void executeNext() { if (mService == null || !mService.isLoggedIn()) return; Cmd cmd = getNext(); if (cmd != null) { // send next command: Log.v(TAG, "executeNext: " + cmd.message + ": cmd=" + cmd.command); if (mListener != null) mListener.onCmdSeriesProgress(cmd.message, cmd.pos(), cmd.posCnt(), 0, 0); mService.sendCommand(cmd.command, this); } else { // series finished: if (mListener != null) mListener.onCmdSeriesFinish(this, 0); } } /** * Result handler. Adds a cmd result matching the current command * to the command results. On success, the next command will be executed, * on failure the series aborts. * * Commands 30-32: multiple results are handled by checking the * result records count and position. Error "no historical messages" * is handled as a normal result (no failure = no abort). The record * count/position are told to the listener as sub step progress. * * @param result -- return string received from server */ @Override public void onResultCommand(String[] result) { if (result.length < 2) return; int commandCode = Integer.parseInt(result[0]); int returnCode = Integer.parseInt(result[1]); // check command: Cmd cmd = getCurrent(); if (cmd == null) { // we're not active, cancel subscription: mService.cancelCommand(); return; } else if (cmd.commandCode != commandCode) { // not for us: return; } // store result: cmd.returnCode = returnCode; cmd.results.add(result); Log.v(TAG, "onResult: " + cmd.message + " / key=" + cmd.commandCode + " => returnCode=" + cmd.returnCode); if (commandCode == 30 || commandCode == 31 || commandCode == 32) { // multiple result command: if (result[2].equals("No historical data available")) { // no records: continue without error executeNext(); } else if (returnCode != 0) { // error: stop execution Log.e(TAG, "ABORT: cmd failed: key=" + cmd.commandCode + " => returnCode=" + cmd.returnCode); mService.cancelCommand(); if (mListener != null) mListener.onCmdSeriesFinish(this, returnCode); } else { // success: check record count int recNr = Integer.parseInt(result[2]); int recCnt = Integer.parseInt(result[3]); if (recNr == recCnt) { // got all records executeNext(); } else { // update progress sub step: if (mListener != null) mListener.onCmdSeriesProgress(cmd.message, cmd.pos(), cmd.posCnt(), recNr, recCnt); } } } else if (returnCode != 0) { // single result command error: stop execution Log.e(TAG, "ABORT: cmd failed: key=" + cmd.commandCode + " => returnCode=" + cmd.returnCode); mService.cancelCommand(); if (mListener != null) mListener.onCmdSeriesFinish(this, returnCode); } else { // single result command success: executeNext(); } } /** * Cancel series execution. Pending results will be ignored. * Triggers Listener callback onCmdSeriesFinish with result code -1. */ public void cancel() { Log.v(TAG, "cancelled"); mService.cancelCommand(); if (mListener != null) mListener.onCmdSeriesFinish(this, -1); } /** * Get return code of current command * @return -- command return code (0..3) */ public int getReturnCode() { Cmd cmd = getCurrent(); return (cmd != null) ? cmd.returnCode : 0; } /** * Get user message for current command * @return -- message string */ public String getMessage() { Cmd cmd = getCurrent(); return (cmd != null) ? cmd.message : ""; } /** * Get optional error detail string returned by the module if available. * (On return code 1-3 the command may supply a detail message, see protocol) * @return -- error detail description or "" */ public String getErrorDetail() { Cmd cmd = getCurrent(); if (cmd == null) return ""; String result[] = (cmd.results.size() > 0) ? cmd.results.get(cmd.results.size()-1) : null; if (result != null && result.length >= 3) return result[2]; else return ""; } }