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