All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rapplogic.aru.uploader.wifi.WifiSketchUploader Maven / Gradle / Ivy

The newest version!
/**
 * 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 .
 */

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 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 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 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 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 --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);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy