com.rapplogic.aru.uploader.wifi.WifiSketchUploader.java Source code

Java tutorial

Introduction

Here is the source code for com.rapplogic.aru.uploader.wifi.WifiSketchUploader.java

Source

/**
 * Copyright (c) 2015 Andrew Rapp. All rights reserved.
 *
 * This file is part of arduino-remote-uploader
 *
 * arduino-remote-uploader is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * arduino-remote-uploader is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with arduino-remote-uploader.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.rapplogic.aru.uploader.wifi;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.text.ParseException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;

import com.google.common.collect.Maps;
import com.rapplogic.aru.uploader.CliOptions;
import com.rapplogic.aru.uploader.SketchUploader;
import com.rapplogic.xbee.api.XBeeException;

public class WifiSketchUploader extends SketchUploader {

    // this is up for debate. esp seems to drop more packets around low forties. and fails outright around 46
    public final int WIFI_PAGE_SIZE = 32;
    private Socket socket = null;
    private volatile boolean connected;

    public final static String host = "host";
    public final static String port = "port";
    public final static String connectionTimeoutSecs = "socket-connection-timeout-s";
    public final static String readTimeoutSecs = "socket-read-timeout-s";

    public WifiSketchUploader() {
        super();
    }

    public void flash(String file, String host, int port, int connectionTimeoutSecs, int readTimeoutSecs,
            final boolean verbose, int ackTimeoutMillis, int arduinoTimeoutSecs, int retriesPerPacket,
            int delayBetweenRetriesMillis) throws IOException {
        Map<String, Object> context = Maps.newHashMap();

        context.put("host", host);
        context.put("port", port);
        context.put("connectionTimeoutSecs", connectionTimeoutSecs);
        context.put("readTimeoutSecs", readTimeoutSecs);

        // determine max data we can send with each programming packet
        // NOTE remember to size the array on the Arduino accordingly to accomadate this size
        int pageSize = WIFI_PAGE_SIZE - getProgramPageHeader(0, 0).length;
        super.process(file, pageSize, ackTimeoutMillis, arduinoTimeoutSecs, retriesPerPacket,
                delayBetweenRetriesMillis, verbose, context);
    }

    //   private int[] toIntArray(List<Integer> list) {
    //      int[] reply = new int[list.size()];
    //      for (int i = 0; i < list.size(); i++) {
    //         reply[i] = list.get(i);
    //      }
    //      
    //      return reply;
    //   }   

    private String intArrayToString(int[] arr) {
        StringBuilder stringBuilder = new StringBuilder();

        for (int i = 0; i < arr.length; i++) {
            stringBuilder.append(arr[i] + ",");
        }

        return stringBuilder.toString();
    }

    private Thread t;

    @Override
    protected void open(final Map<String, Object> context) throws Exception {

        String host = (String) context.get("host");
        Integer port = (Integer) context.get("port");
        Integer connectionTimeoutSecs = (Integer) context.get("connectionTimeoutSecs");
        Integer readTimeoutSecs = (Integer) context.get("readTimeoutSecs");

        // open socket
        socket = new Socket();
        socket.connect(new InetSocketAddress(host, port), connectionTimeoutSecs * 1000);
        socket.setSoTimeout(readTimeoutSecs * 1000);

        connected = true;

        final int[] reply = new int[5];
        final AtomicInteger replyIndex = new AtomicInteger(0);

        // TODO unlike other wireless protocols, wifi is stateful so we need to handle situations where we lose the socket and reconnect

        t = new Thread(new Runnable() {
            @Override
            public void run() {
                int ch = 0;

                // reply always terminated with 13,10
                try {
                    while ((ch = socket.getInputStream().read()) > -1) {
                        if (replyIndex.get() < reply.length) {
                            reply[replyIndex.getAndIncrement()] = ch;
                        } else if (replyIndex.get() == 5 && ch == 13) {
                            replyIndex.getAndIncrement();
                            // discard
                        } else if (replyIndex.get() == 6 && ch == 10) {
                            //System.out.println("reply is " + stringBuilder.toString());
                            //                  stringBuilder = new StringBuilder();
                            handleReply(reply);
                            replyIndex.set(0);
                        } else {
                            //error
                            throw new RuntimeException("Expected CR/LF -- invalid reply at position "
                                    + replyIndex.get() + ", array: " + intArrayToString(reply));
                        }
                    }
                } catch (IOException e) {
                    if (!connected && e instanceof SocketException) {
                        // expected.. ignore
                    } else {
                        System.out.println("IO error in socket reader");
                        e.printStackTrace();
                    }
                } catch (Exception e) {
                    System.out.println("Unexpected error in socket reader");
                    e.printStackTrace();
                }

                connected = false;
            }
        });

        t.setDaemon(true);
        t.start();
    }

    protected boolean isConnected() {
        return connected;
    }

    private void handleReply(int[] reply) {
        //System.out.println("Received reply " + intArrayToString(reply));
        // { MAGIC_BYTE1, MAGIC_BYTE2, 0, 0, 0};
        addReply(reply);
    }

    @Override
    protected void writeData(int[] data, Map<String, Object> context) throws Exception {
        // ugh, convert to byte[] required or esp will receive multiple IPD commands
        byte[] b = new byte[data.length + 2];

        for (int i = 0; i < data.length; i++) {
            b[i] = (byte) (data[i] & 0xff);
        }

        // every transmission must end with CR/LF
        b[data.length] = (byte) 13;
        b[data.length + 1] = (byte) 10;

        socket.getOutputStream().write(b);
    }

    @Override
    protected void close() throws Exception {
        connected = false;
        // close socket
        socket.close();
    }

    @Override
    protected String getName() {
        return "wifi";
    }

    private void runFromCmdLine(String[] args) throws org.apache.commons.cli.ParseException, IOException {
        CliOptions cliOptions = getCliOptions();

        cliOptions.addOption(OptionBuilder.withLongOpt(host).hasArg().isRequired(true)
                .withDescription("Host ip address of wifi device. Required").create("x")); // single arg is optional

        cliOptions.addOption(OptionBuilder.withLongOpt(port).hasArg().isRequired(true).withType(Number.class)
                .withDescription("Port of wifi server. Required").create("p"));

        cliOptions.getDefaults().put(connectionTimeoutSecs, "10");

        cliOptions.addOption(OptionBuilder.withLongOpt(connectionTimeoutSecs).hasArg().isRequired(false)
                .withType(Number.class).withDescription("Connection timeout seconds").create("q")); // arbitrary -- running out of letters

        cliOptions.getDefaults().put(readTimeoutSecs, "10");

        cliOptions.addOption(OptionBuilder.withLongOpt(readTimeoutSecs).hasArg().isRequired(false)
                .withType(Number.class).withDescription("Connection read seconds").create("v"));

        cliOptions.build();

        CommandLine commandLine = cliOptions.parse(args);

        if (commandLine != null) {
            new WifiSketchUploader().flash(commandLine.getOptionValue(CliOptions.sketch),
                    commandLine.getOptionValue(host), cliOptions.getIntegerOption(port),
                    cliOptions.getIntegerOption(connectionTimeoutSecs),
                    cliOptions.getIntegerOption(readTimeoutSecs), commandLine.hasOption(CliOptions.verboseArg),
                    cliOptions.getIntegerOption(CliOptions.ackTimeoutMillisArg),
                    cliOptions.getIntegerOption(CliOptions.arduinoTimeoutArg),
                    cliOptions.getIntegerOption(CliOptions.retriesPerPacketArg),
                    cliOptions.getIntegerOption(CliOptions.delayBetweenRetriesMillisArg));
        }
    }

    /**
     * ex
    ceylon:arduino-remote-uploader-1.0-SNAPSHOT andrew$ ./wifi-uploader.sh  -d --host 192.168.1.115  --port 1111 -s ../../../resources/BlinkSlow.cpp.hex 
    Sending sketch to wifi radio, size 1102 bytes, md5 8e7a58576bdc732d3f9708dab9aea5b9, number of packets 43, and 26 bytes per packet, header ef,ac,10,a,4,4e,0,2b,1a,3c
    Sending page 1 of 43, with address 0, length 32, packet ef,ac,20,20,0,0,c,94,61,0,c,94,7e,0,c,94,7e,0,c,94,7e,0,c,94,7e,0,c,94,7e,0,c,94
    Sending page 2 of 43, with address 26, length 32, packet ef,ac,20,20,0,1a,7e,0,c,94,7e,0,c,94,7e,0,c,94,7e,0,c,94,7e,0,c,94,7e,0,c,94,7e,0
    ..
    Sending flash start packet ef,ac,40,6,4,4e
    Sending flash packet to radio ef,ac,40,6,4,4e
    Successfully flashed remote Arduino in 7s, with 0 retries
        
     * @param args
     * @throws NumberFormatException
     * @throws IOException
     * @throws XBeeException
     * @throws ParseException
     * @throws org.apache.commons.cli.ParseException
     */
    public static void main(String[] args)
            throws NumberFormatException, IOException, ParseException, org.apache.commons.cli.ParseException {
        initLog4j();
        WifiSketchUploader wifiSketchUploader = new WifiSketchUploader();
        wifiSketchUploader.runFromCmdLine(args);
        //      new WifiSketchUploader().flash(
        //            "/Users/andrew/Documents/dev/arduino-remote-uploader/resources/BlinkFast.cpp.hex", 
        //            "192.168.1.115", 
        //            1111,
        //            10,
        //            10,
        //            true,
        //            3000,
        //            5,
        //            3,
        //            0);
    }
}