/** Copyright 2013 Luciano Zu project Ardulink http://www.ardulink.org/ 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 http://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. @author Luciano Zu */ package org.zu.ardulink.protocol; import static java.util.Collections.synchronizedMap; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zu.ardulink.Link; import org.zu.ardulink.event.AnalogReadChangeEvent; import org.zu.ardulink.event.DigitalReadChangeEvent; import org.zu.ardulink.event.IncomingMessageEvent; /** * [ardulinktitle] [ardulinkversion] * This class implements the native Arduino Link protocol.<br/> * With this class you are able to send messages to Arduino.<br/> * <br/> * Messages are in the format:<br/> * <br/> * alp://<request or response>/<variable data>?id=<numeric message id><br/> * <br/> * where<br/> * requests from ardulink to arduino are: <br/> * kprs - Key Pressed<br/> * ppin - Power Pin Intensity<br/> * ppsw - Power Pin Switch<br/> * tone - Tone square wave start<br/> * notn - Tone square wave stop<br/> * srld - Start Listening Digital Pin<br/> * spld - Stop Listening Digital Pin<br/> * srla - Start Listening Analog Pin<br/> * spla - Stop Listening Analog Pin<br/> * cust - Custom message * <br/> * requests from arduino to ardulink are:<br/> * ared - Analog Pin Read<br/> * dred - Digital Pin Read<br/> * <br/> * responses (only from arduino) are:<br/> * rply - reply message<br/> * <br/> * ?id=<numeric message id> is not mandatory (for requests). If is supplied then a asynchronous<br/> * rply response will send from arduino. Otherwise arduino will not send a response.<br/> * <br/> * Each message from ardulink to arduino terminate with a \n<br/> * <br/> * See methods about variable data.<br/> * <br/> * Variable data:<br/> * alp://kprs/chr<char pressed>cod<key code>loc<key location>mod<key modifiers>mex<key modifiers>?id=<message id><br/> * alp://ppin/<pin>/<intensity>?id=<message id> intensity:0-255<br/> * alp://ppsw/<pin>/<power>?id=<message id> power:0-1<br/> * alp://srld/<pin>?id=<message id><br/> * alp://spld/<pin>?id=<message id><br/> * alp://srla/<pin>?id=<message id><br/> * alp://spla/<pin>?id=<message id><br/> * alp://tone/<pin>/<frequency>?id=<message id><br/> * alp://tone/<pin>/<frequency>/<duration>?id=<message id><br/> * alp://notn/<pin>?id=<message id><br/> * alp://cust/<a custom message>?id=<message id><br/> * alp://ared/<pin>/<intensity> intensity:0-1023<br/> * alp://dred/<pin>/<power> power:0-1<br/> * alp://rply/ok|ko?id=<message id><br/> * <br/> * @author Luciano Zu * * [adsense] */ public class ALProtocol implements IProtocol { public static final String NAME = "ArdulinkProtocol"; private static final String OUTGOING_MESSAGE_DIVIDER = "\n"; private static final Logger logger = LoggerFactory.getLogger(ALProtocol.class); private static long nextId = 1; private final Map<Long, MessageInfo> messageInfos = synchronizedMap(new HashMap<Long, MessageInfo>()); public static final int NO_DURATION = -1; @Override public String getProtocolName() { return NAME; } @Override public MessageInfo sendKeyPressEvent(Link link, char keychar, int keycode, int keylocation, int keymodifiers, int keymodifiersex) { return sendKeyPressEvent(link, keychar, keycode, keylocation, keymodifiers, keymodifiersex, null); } @Override public MessageInfo sendPowerPinIntensity(Link link, int pin, int intensity) { return sendPowerPinIntensity(link, pin, intensity, null); } @Override public MessageInfo sendPowerPinSwitch(Link link, int pin, int power) { return sendPowerPinSwitch(link, pin, power, null); } @Override public MessageInfo sendToneMessage(Link link, int pin, int frequency) { return sendToneMessage(link, pin, frequency, null); } @Override public MessageInfo sendToneMessage(Link link, int pin, int frequency, int duration) { return sendToneMessage(link, pin, frequency, duration, null); } @Override public MessageInfo sendNoToneMessage(Link link, int pin) { return sendNoToneMessage(link, pin, null); } @Override public MessageInfo sendCustomMessage(Link link, String message) { return sendCustomMessage(link, message, null); } @Override public MessageInfo sendKeyPressEvent(Link link, char keychar, int keycode, int keylocation, int keymodifiers, int keymodifiersex, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://kprs/chr"); builder.append(keychar); builder.append("cod"); builder.append(keycode); builder.append("loc"); builder.append(keylocation); builder.append("mod"); builder.append(keymodifiers); builder.append("mex"); builder.append(keymodifiersex); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo sendPowerPinIntensity(Link link, int pin, int intensity, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://ppin/"); builder.append(pin); builder.append("/"); builder.append(intensity); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo sendPowerPinSwitch(Link link, int pin, int power, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected() && (power == POWER_HIGH || power == POWER_LOW)) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://ppsw/"); builder.append(pin); builder.append("/"); builder.append(power); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo sendToneMessage(Link link, int pin, int frequency, ReplyMessageCallback callback) { return sendToneMessage(link, pin, frequency, NO_DURATION, callback); } @Override public MessageInfo sendToneMessage(Link link, int pin, int frequency, int duration, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://tone/"); builder.append(pin); builder.append("/"); builder.append(frequency); builder.append("/"); builder.append(duration); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo sendNoToneMessage(Link link, int pin, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://notn/"); builder.append(pin); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo sendCustomMessage(Link link, String message, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://cust/"); builder.append(message); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo startListenDigitalPin(Link link, int pin) { return startListenDigitalPin(link, pin, null); } @Override public MessageInfo stopListenDigitalPin(Link link, int pin) { return stopListenDigitalPin(link, pin, null); } @Override public MessageInfo startListenAnalogPin(Link link, int pin) { return startListenAnalogPin(link, pin, null); } @Override public MessageInfo stopListenAnalogPin(Link link, int pin) { return stopListenAnalogPin(link, pin, null); } @Override public MessageInfo startListenDigitalPin(Link link, int pin, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://srld/"); builder.append(pin); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo stopListenDigitalPin(Link link, int pin, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://spld/"); builder.append(pin); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo startListenAnalogPin(Link link, int pin, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://srla/"); builder.append(pin); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public MessageInfo stopListenAnalogPin(Link link, int pin, ReplyMessageCallback callback) { MessageInfo retvalue = new MessageInfo(); synchronized(this) { if(link.isConnected()) { long currentId = nextId++; retvalue.setMessageID(currentId); if(callback != null) { retvalue.setCallback(callback); messageInfos.put(currentId, retvalue); } StringBuilder builder = new StringBuilder("alp://spla/"); builder.append(pin); if(callback != null) { builder.append("?id="); builder.append(currentId); } builder.append(OUTGOING_MESSAGE_DIVIDER); String mesg = builder.toString(); logger.debug(mesg); boolean result = link.writeSerial(mesg); retvalue.setSent(result); retvalue.setMessageSent(mesg); if(!result) { messageInfos.remove(currentId); } } } return retvalue; } @Override public IncomingMessageEvent parseMessage(int[] message) { IncomingMessageEvent retvalue = null; try { String msg = new String(message, 0, message.length).trim(); if(msg.startsWith("alp://")) { // OK is a message I know String cmd = msg.substring(6,10); if("rply".equals(cmd)) { // alp://rply/ok?id<messageid> alp://rply/ko?id<messageid> parseReplyMessage(msg); } else if("dred".equals(cmd)) { // alp://dred/<pin>/<value> retvalue = parseDigitalReadMessage(msg); } else if("ared".equals(cmd)) { // alp://ared/<pin>/<value> retvalue = parseAnalogReadMessage(msg); } else { // Message I don't recognize its very strange! logger.error( "Arduino sent to me a message in ALProtocol that I don't recognize. Msg: {}", msg); } } } catch(Exception e) { logger.error("Errror parsing message sent from Arduino.", e); e.printStackTrace(); } return retvalue; } private IncomingMessageEvent parseDigitalReadMessage(String msg) { int separatorPosition = msg.indexOf('/', 11 ); String pin = msg.substring(11,separatorPosition); String value = msg.substring(separatorPosition + 1); DigitalReadChangeEvent e = new DigitalReadChangeEvent(Integer.parseInt(pin), Integer.parseInt(value), msg); return e; } private IncomingMessageEvent parseAnalogReadMessage(String msg) { int separatorPosition = msg.indexOf('/', 11 ); String pin = msg.substring(11,separatorPosition); String value = msg.substring(separatorPosition + 1); AnalogReadChangeEvent e = new AnalogReadChangeEvent(Integer.parseInt(pin), Integer.parseInt(value), msg); return e; } private void parseReplyMessage(String msg) { String result = msg.substring(11,13); int idIndex = msg.indexOf("?id="); String tmpId = msg.substring(idIndex + 4).trim(); Long id = Long.parseLong(tmpId); MessageInfo messageInfo = messageInfos.get(id); if(messageInfo != null) { if("ok".equals(result)) { messageInfo.setReply(REPLY_OK); } else if("ko".equals(result)) { messageInfo.setReply(REPLY_KO); } messageInfo.setMessageReceived(msg); messageInfos.remove(id); messageInfo.getCallback().replyInfo(messageInfo); // Callback! } } @Override public ProtocolType getProtocolType() { return ProtocolType.TEXT; } @Override public int getIncomingMessageDivider() { return 255; } @Override public int getOutgoingMessageDivider() { return ALProtocol.OUTGOING_MESSAGE_DIVIDER.charAt(0); } }