[smsmodem] Initial contribution (#12250)
* [smsmodem] Initial contribution This binding connects to a USB serial GSM modem (or a network exposed one, a.k.a ser2net) and allows openHAB to send and receive SMS through it. Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] README fix Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] build/spotless fix Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] compliance with 3rd party license And long running thread naming convention And treated some code warning Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] i18n Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Small fixes update channel rename action to avoid colision with other binding and a too generic name Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Use of standard Thing properties Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Fix sender identifier error with special character Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Add encoding parameter For non latin character in SMS Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Apply review Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Split local and remote modem in two thing-types Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Apply review Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Apply review Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Apply code review (removing unnecessary method) Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> Co-authored-by: Gwendal Roulleau <gwendal.roulleau@gmail.com>
This commit is contained in:
49
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/Capabilities.java
vendored
Normal file
49
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/Capabilities.java
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package org.smslib;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Capabilities {
|
||||
BitSet caps = new BitSet();
|
||||
|
||||
public enum Caps {
|
||||
CanSendMessage,
|
||||
CanSendBinaryMessage,
|
||||
CanSendUnicodeMessage,
|
||||
CanSendWapMessage,
|
||||
CanSendFlashMessage,
|
||||
CanSendPortInfo,
|
||||
CanSetSenderId,
|
||||
CanSplitMessages,
|
||||
CanRequestDeliveryStatus,
|
||||
CanQueryDeliveryStatus,
|
||||
CanQueryCreditBalance,
|
||||
CanQueryCoverage,
|
||||
CanSetValidityPeriod
|
||||
}
|
||||
|
||||
public void set(Caps c) {
|
||||
this.caps.set(c.ordinal());
|
||||
}
|
||||
|
||||
public BitSet getCapabilities() {
|
||||
return (BitSet) this.caps.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
BitSet bs = (BitSet) getCapabilities().clone();
|
||||
StringBuffer b = new StringBuffer();
|
||||
for (Caps c : Caps.values()) {
|
||||
b.append(String.format("%-30s : ", c.toString()));
|
||||
b.append(bs.get(c.ordinal()) ? "YES" : "NO");
|
||||
b.append("\n");
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
20
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/CommunicationException.java
vendored
Normal file
20
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/CommunicationException.java
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package org.smslib;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Wrapper for communication exception
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CommunicationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -5175636461754717860L;
|
||||
|
||||
public CommunicationException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CommunicationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
161
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/DeviceInformation.java
vendored
Normal file
161
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/DeviceInformation.java
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package org.smslib;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.callback.IDeviceInformationListener;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceInformation {
|
||||
|
||||
@Nullable
|
||||
private IDeviceInformationListener deviceInformationListener;
|
||||
|
||||
public enum Modes {
|
||||
PDU,
|
||||
TEXT
|
||||
}
|
||||
|
||||
String manufacturer = "N/A";
|
||||
String model = "N/A";
|
||||
String swVersion = "N/A";
|
||||
String serialNo = "N/A";
|
||||
String imsi = "N/A";
|
||||
int rssi = 0;
|
||||
|
||||
@Nullable
|
||||
Modes mode;
|
||||
|
||||
int totalSent = 0;
|
||||
int totalFailed = 0;
|
||||
int totalReceived = 0;
|
||||
int totalFailures = 0;
|
||||
|
||||
public void setDeviceInformationListener(@Nullable IDeviceInformationListener deviceInformationListener) {
|
||||
this.deviceInformationListener = deviceInformationListener;
|
||||
}
|
||||
|
||||
public synchronized void increaseTotalSent() {
|
||||
this.totalSent++;
|
||||
IDeviceInformationListener dil = deviceInformationListener;
|
||||
if (dil != null) {
|
||||
dil.setTotalSent(Integer.toString(totalSent));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void increaseTotalFailed() {
|
||||
this.totalFailed++;
|
||||
IDeviceInformationListener dil = deviceInformationListener;
|
||||
if (dil != null) {
|
||||
dil.setTotalFailed(Integer.toString(totalFailed));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void increaseTotalReceived() {
|
||||
this.totalReceived++;
|
||||
IDeviceInformationListener dil = deviceInformationListener;
|
||||
if (dil != null) {
|
||||
dil.setTotalReceived(Integer.toString(totalReceived));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void increaseTotalFailures() {
|
||||
this.totalFailures++;
|
||||
IDeviceInformationListener dil = deviceInformationListener;
|
||||
if (dil != null) {
|
||||
dil.setTotalFailures(Integer.toString(totalFailures));
|
||||
}
|
||||
}
|
||||
|
||||
public String getManufacturer() {
|
||||
return this.manufacturer;
|
||||
}
|
||||
|
||||
public void setManufacturer(String manufacturer) {
|
||||
this.manufacturer = manufacturer;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setManufacturer(manufacturer);
|
||||
}
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setModel(model);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSwVersion() {
|
||||
return this.swVersion;
|
||||
}
|
||||
|
||||
public void setSwVersion(String swVersion) {
|
||||
this.swVersion = swVersion;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setSwVersion(swVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSerialNo() {
|
||||
return this.serialNo;
|
||||
}
|
||||
|
||||
public void setSerialNo(String serialNo) {
|
||||
this.serialNo = serialNo;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setSerialNo(serialNo);
|
||||
}
|
||||
}
|
||||
|
||||
public String getImsi() {
|
||||
return this.imsi;
|
||||
}
|
||||
|
||||
public void setImsi(String imsi) {
|
||||
this.imsi = imsi;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setImsi(imsi);
|
||||
}
|
||||
}
|
||||
|
||||
public int getRssi() {
|
||||
return this.rssi;
|
||||
}
|
||||
|
||||
public void setRssi(int rssi) {
|
||||
this.rssi = rssi;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setRssi(Integer.toString(rssi));
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Modes getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
public void setMode(Modes mode) {
|
||||
this.mode = mode;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setMode(mode.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("MANUF:%s, MODEL:%s, SERNO:%s, IMSI:%s, SW:%s, RSSI:%ddBm, MODE:%s", getManufacturer(),
|
||||
getModel(), getSerialNo(), getImsi(), getSwVersion(), getRssi(), getMode());
|
||||
}
|
||||
}
|
||||
387
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/MessageReader.java
vendored
Normal file
387
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/MessageReader.java
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
package org.smslib;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.DeviceInformation.Modes;
|
||||
import org.smslib.Modem.Status;
|
||||
import org.smslib.message.DeliveryReportMessage;
|
||||
import org.smslib.message.InboundBinaryMessage;
|
||||
import org.smslib.message.InboundMessage;
|
||||
import org.smslib.message.Payload;
|
||||
import org.smslib.pduUtils.gsm3040.Pdu;
|
||||
import org.smslib.pduUtils.gsm3040.PduParser;
|
||||
import org.smslib.pduUtils.gsm3040.PduUtils;
|
||||
import org.smslib.pduUtils.gsm3040.SmsDeliveryPdu;
|
||||
import org.smslib.pduUtils.gsm3040.SmsStatusReportPdu;
|
||||
|
||||
/**
|
||||
*
|
||||
* Poll the modem to check for new received messages
|
||||
* (sms or delivery report)
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageReader extends Thread {
|
||||
static Logger logger = LoggerFactory.getLogger(MessageReader.class);
|
||||
|
||||
Modem modem;
|
||||
|
||||
private static int HOURS_TO_RETAIN_ORPHANED_MESSAGE_PARTS = 72;
|
||||
|
||||
public MessageReader(Modem modem) {
|
||||
this.modem = modem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("Started!");
|
||||
if (this.modem.getStatus() == Status.Started) {
|
||||
try {
|
||||
this.modem.getModemDriver().lock();
|
||||
ArrayList<InboundMessage> messageList = new ArrayList<InboundMessage>();
|
||||
try {
|
||||
for (int i = 0; i < (this.modem.getModemDriver().getMemoryLocations().length() / 2); i++) {
|
||||
String memLocation = this.modem.getModemDriver().getMemoryLocations().substring((i * 2),
|
||||
(i * 2) + 2);
|
||||
String data = this.modem.getModemDriver().atGetMessages(memLocation).getResponseData();
|
||||
if (data.length() > 0) {
|
||||
messageList.addAll((this.modem.getDeviceInformation().getMode() == Modes.PDU
|
||||
? parsePDU(data, memLocation)
|
||||
: parseTEXT(data, memLocation)));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.modem.getModemDriver().unlock();
|
||||
}
|
||||
for (InboundMessage message : messageList) {
|
||||
processMessage(message);
|
||||
}
|
||||
|
||||
} catch (CommunicationException | IOException e) {
|
||||
logger.error("Unhandled exception while trying to read new messages", e);
|
||||
modem.error();
|
||||
}
|
||||
}
|
||||
logger.debug("Stopped!");
|
||||
}
|
||||
|
||||
private ArrayList<InboundMessage> parsePDU(String data, String memLocation) throws IOException {
|
||||
ArrayList<InboundMessage> messageList = new ArrayList<>();
|
||||
List<List<InboundMessage>> mpMsgList = new ArrayList<>();
|
||||
BufferedReader reader = new BufferedReader(new StringReader(data));
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
PduParser parser = new PduParser();
|
||||
int i = line.indexOf(':');
|
||||
int j = line.indexOf(',');
|
||||
if (j == -1) {
|
||||
logger.error("Bad PDU announce : {}", line);
|
||||
continue;
|
||||
}
|
||||
int memIndex = Integer.parseInt(line.substring(i + 1, j).trim());
|
||||
i = line.lastIndexOf(',');
|
||||
j = line.length();
|
||||
int pduSize = Integer.parseInt(line.substring(i + 1, j).trim());
|
||||
String pduString = reader.readLine().trim();
|
||||
if ((pduSize > 0) && ((pduSize * 2) == pduString.length())) {
|
||||
pduString = "00" + pduString;
|
||||
}
|
||||
Pdu pdu = parser.parsePdu(pduString);
|
||||
if (pdu instanceof SmsDeliveryPdu) {
|
||||
logger.debug("PDU = {}", pdu.toString());
|
||||
InboundMessage msg = null;
|
||||
if (pdu.isBinary()) {
|
||||
msg = new InboundBinaryMessage((SmsDeliveryPdu) pdu, memLocation, memIndex);
|
||||
} else {
|
||||
msg = new InboundMessage((SmsDeliveryPdu) pdu, memLocation, memIndex);
|
||||
}
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
logger.debug("IN-DTLS: MI:{} REF:{} MAX:{} SEQ:{}", msg.getMemIndex(), msg.getMpRefNo(),
|
||||
msg.getMpMaxNo(), msg.getMpSeqNo());
|
||||
if (msg.getMpRefNo() == 0) {
|
||||
messageList.add(msg);
|
||||
} else {
|
||||
// multi-part message
|
||||
int k, l;
|
||||
List<InboundMessage> tmpList;
|
||||
InboundMessage listMsg;
|
||||
boolean found, duplicate;
|
||||
found = false;
|
||||
for (k = 0; k < mpMsgList.size(); k++) {
|
||||
// List of List<InboundMessage>
|
||||
tmpList = mpMsgList.get(k);
|
||||
listMsg = tmpList.get(0);
|
||||
// check if current message list is for this message
|
||||
if (listMsg.getMpRefNo() == msg.getMpRefNo()) {
|
||||
duplicate = false;
|
||||
// check if the message is already in the message list
|
||||
for (l = 0; l < tmpList.size(); l++) {
|
||||
listMsg = tmpList.get(l);
|
||||
if (listMsg.getMpSeqNo() == msg.getMpSeqNo()) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!duplicate) {
|
||||
tmpList.add(msg);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// no existing list present for this message
|
||||
// add one
|
||||
tmpList = new ArrayList<>();
|
||||
tmpList.add(msg);
|
||||
mpMsgList.add(tmpList);
|
||||
}
|
||||
}
|
||||
} else if (pdu instanceof SmsStatusReportPdu) {
|
||||
DeliveryReportMessage msg;
|
||||
msg = new DeliveryReportMessage((SmsStatusReportPdu) pdu, memLocation, memIndex);
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
messageList.add(msg);
|
||||
}
|
||||
}
|
||||
checkMpMsgList(messageList, mpMsgList);
|
||||
List<InboundMessage> tmpList;
|
||||
for (int k = 0; k < mpMsgList.size(); k++) {
|
||||
tmpList = mpMsgList.get(k);
|
||||
tmpList.clear();
|
||||
}
|
||||
mpMsgList.clear();
|
||||
return messageList;
|
||||
}
|
||||
|
||||
private ArrayList<InboundMessage> parseTEXT(String data, String memLocation) throws IOException {
|
||||
ArrayList<InboundMessage> messageList = new ArrayList<>();
|
||||
BufferedReader reader;
|
||||
String line;
|
||||
Calendar cal1 = Calendar.getInstance();
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
String myData = data;
|
||||
myData = myData.replaceAll("\\s+OK\\s+", "\nOK");
|
||||
myData = myData.replaceAll("$", "\n");
|
||||
logger.debug(myData);
|
||||
reader = new BufferedReader(new StringReader(myData));
|
||||
for (;;) {
|
||||
line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
line = line.trim();
|
||||
if (line.length() > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (line.length() <= 0 || "OK".equalsIgnoreCase(line)) {
|
||||
break;
|
||||
}
|
||||
int i = line.indexOf(':');
|
||||
int j = line.indexOf(',');
|
||||
int memIndex = Integer.parseInt(line.substring(i + 1, j).trim());
|
||||
StringTokenizer tokens = new StringTokenizer(line, ",");
|
||||
tokens.nextToken();
|
||||
tokens.nextToken();
|
||||
String tmpLine = "";
|
||||
if (Character.isDigit(tokens.nextToken().trim().charAt(0))) {
|
||||
line = line.replaceAll(",,", ", ,");
|
||||
tokens = new StringTokenizer(line, ",");
|
||||
tokens.nextToken();
|
||||
tokens.nextToken();
|
||||
tokens.nextToken();
|
||||
String messageId = tokens.nextToken();
|
||||
String recipient = tokens.nextToken().replaceAll("\"", "");
|
||||
String dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
if (dateStr.indexOf('/') == -1) {
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
}
|
||||
cal1.set(Calendar.YEAR, 2000 + Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal1.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(3, 5)) - 1);
|
||||
cal1.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal1.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal1.set(Calendar.MINUTE, Integer.parseInt(dateStr.substring(3, 5)));
|
||||
cal1.set(Calendar.SECOND, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal2.set(Calendar.YEAR, 2000 + Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal2.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(3, 5)) - 1);
|
||||
cal2.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal2.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal2.set(Calendar.MINUTE, Integer.parseInt(dateStr.substring(3, 5)));
|
||||
cal2.set(Calendar.SECOND, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
DeliveryReportMessage msg;
|
||||
msg = new DeliveryReportMessage(messageId, recipient, memLocation, memIndex, cal1.getTime(),
|
||||
cal2.getTime());
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
messageList.add(msg);
|
||||
} else {
|
||||
line = line.replaceAll(",,", ", ,");
|
||||
tokens = new StringTokenizer(line, ",");
|
||||
tokens.nextToken();
|
||||
tokens.nextToken();
|
||||
String originator = tokens.nextToken().replaceAll("\"", "");
|
||||
tokens.nextToken();
|
||||
String dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal1.set(Calendar.YEAR, 2000 + Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal1.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(3, 5)) - 1);
|
||||
cal1.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal1.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal1.set(Calendar.MINUTE, Integer.parseInt(dateStr.substring(3, 5)));
|
||||
cal1.set(Calendar.SECOND, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
String msgText = "";
|
||||
while (true) {
|
||||
tmpLine = reader.readLine();
|
||||
if (tmpLine == null) {
|
||||
break;
|
||||
}
|
||||
if (tmpLine.startsWith("+CMGL")) {
|
||||
break;
|
||||
}
|
||||
if (tmpLine.startsWith("+CMGR")) {
|
||||
break;
|
||||
}
|
||||
msgText += (msgText.length() == 0 ? "" : "\n") + tmpLine;
|
||||
}
|
||||
InboundMessage msg = new InboundMessage(originator, msgText.trim(), cal1.getTime(), memLocation,
|
||||
memIndex);
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
messageList.add(msg);
|
||||
}
|
||||
while (true) {
|
||||
// line = reader.readLine();
|
||||
line = ((tmpLine == null || tmpLine.length() == 0) ? reader.readLine() : tmpLine);
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
line = line.trim();
|
||||
if (line.length() > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
return messageList;
|
||||
}
|
||||
|
||||
private void checkMpMsgList(Collection<InboundMessage> msgList, List<List<InboundMessage>> mpMsgList) {
|
||||
int k, l, m;
|
||||
List<InboundMessage> tmpList;
|
||||
InboundMessage listMsg, mpMsg;
|
||||
boolean found;
|
||||
mpMsg = null;
|
||||
logger.debug("CheckMpMsgList(): MAINLIST: {}", mpMsgList.size());
|
||||
for (k = 0; k < mpMsgList.size(); k++) {
|
||||
tmpList = mpMsgList.get(k);
|
||||
logger.debug("CheckMpMsgList(): SUBLIST[{}]: ", tmpList.size());
|
||||
listMsg = tmpList.get(0);
|
||||
found = false;
|
||||
if (listMsg.getMpMaxNo() == tmpList.size()) {
|
||||
found = true;
|
||||
for (l = 0; l < tmpList.size(); l++) {
|
||||
for (m = 0; m < tmpList.size(); m++) {
|
||||
listMsg = tmpList.get(m);
|
||||
if (listMsg.getMpSeqNo() == (l + 1)) {
|
||||
if (listMsg.getMpSeqNo() == 1) {
|
||||
mpMsg = listMsg;
|
||||
mpMsg.setMpMemIndex(mpMsg.getMemIndex());
|
||||
if (listMsg.getMpMaxNo() == 1) {
|
||||
msgList.add(mpMsg);
|
||||
}
|
||||
} else {
|
||||
if (mpMsg != null) {
|
||||
String textToAdd = listMsg.getPayload().getText();
|
||||
if (mpMsg.getEndsWithMultiChar()) {
|
||||
if (textToAdd == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot add text to message");
|
||||
}
|
||||
// adjust first char of textToAdd
|
||||
logger.debug("Adjusting dangling multi-char: {} --> {}", textToAdd.charAt(0),
|
||||
PduUtils.getMultiCharFor(textToAdd.charAt(0)));
|
||||
textToAdd = PduUtils.getMultiCharFor(textToAdd.charAt(0))
|
||||
+ textToAdd.substring(1);
|
||||
}
|
||||
mpMsg.setEndsWithMultiChar(listMsg.getEndsWithMultiChar());
|
||||
mpMsg.setPayload(new Payload(mpMsg.getPayload().getText() + textToAdd));
|
||||
// }
|
||||
mpMsg.setMpSeqNo(listMsg.getMpSeqNo());
|
||||
mpMsg.setMpMemIndex(listMsg.getMemIndex());
|
||||
if (listMsg.getMpSeqNo() == listMsg.getMpMaxNo()) {
|
||||
mpMsg.setMemIndex(-1);
|
||||
msgList.add(mpMsg);
|
||||
mpMsg = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
tmpList.clear();
|
||||
tmpList = null;
|
||||
}
|
||||
if (found) {
|
||||
mpMsgList.remove(k);
|
||||
k--;
|
||||
}
|
||||
}
|
||||
// Check the remaining parts for "orphaned" status
|
||||
for (List<InboundMessage> remainingList : mpMsgList) {
|
||||
for (InboundMessage msg : remainingList) {
|
||||
Date sentDate = msg.getSentDate();
|
||||
if (sentDate == null || getAgeInHours(sentDate) > HOURS_TO_RETAIN_ORPHANED_MESSAGE_PARTS) {
|
||||
try {
|
||||
this.modem.delete(msg);
|
||||
} catch (CommunicationException e) {
|
||||
logger.error("Could not delete orphaned message: {}", msg.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getAgeInHours(Date fromDate) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(new java.util.Date());
|
||||
long now = cal.getTimeInMillis();
|
||||
cal.setTime(fromDate);
|
||||
long past = cal.getTimeInMillis();
|
||||
return (int) ((now - past) / (60 * 60 * 1000));
|
||||
}
|
||||
|
||||
private void processMessage(InboundMessage message) {
|
||||
String messageSignature = message.getSignature();
|
||||
if (!this.modem.getReadMessagesSet().contains(messageSignature)) {
|
||||
this.modem.getDeviceInformation().increaseTotalReceived();
|
||||
if (message instanceof DeliveryReportMessage) {
|
||||
modem.processDeliveryReport((DeliveryReportMessage) message);
|
||||
} else {
|
||||
modem.processMessage(message);
|
||||
}
|
||||
this.modem.getReadMessagesSet().add(messageSignature);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/MessageSender.java
vendored
Normal file
78
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/MessageSender.java
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package org.smslib;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.message.OutboundMessage;
|
||||
import org.smslib.message.OutboundMessage.FailureCause;
|
||||
import org.smslib.message.OutboundMessage.SentStatus;
|
||||
|
||||
/**
|
||||
* Poll the modem queue and send messages
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageSender extends Thread {
|
||||
static Logger logger = LoggerFactory.getLogger(MessageSender.class);
|
||||
|
||||
Queue<OutboundMessage> messageQueue;
|
||||
|
||||
Modem modem;
|
||||
|
||||
private int gatewayDispatcherYield;
|
||||
|
||||
private AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||
private boolean interrupt = false;
|
||||
|
||||
public MessageSender(String name, Queue<OutboundMessage> messageQueue, Modem modem, int gatewayDispatcherYield) {
|
||||
setName(name);
|
||||
setDaemon(false);
|
||||
this.messageQueue = messageQueue;
|
||||
this.modem = modem;
|
||||
this.gatewayDispatcherYield = gatewayDispatcherYield;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isRunning.getAndSet(true)) {
|
||||
interrupt = false; // reset interruption status
|
||||
try {
|
||||
logger.debug("Started!");
|
||||
while (!interrupt && messageQueue.size() > 0) {
|
||||
try {
|
||||
OutboundMessage message = messageQueue.poll();
|
||||
if (message != null) {
|
||||
try {
|
||||
this.modem.send(message);
|
||||
} catch (CommunicationException e) {
|
||||
logger.error("Send failed!", e);
|
||||
message.setSentStatus(SentStatus.Failed);
|
||||
message.setFailureCause(FailureCause.None);
|
||||
} finally {
|
||||
this.modem.processMessageSent(message);
|
||||
sleep(this.gatewayDispatcherYield);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Message dispatcher thread interrupted", e);
|
||||
}
|
||||
}
|
||||
logger.debug("Ended!");
|
||||
} finally {
|
||||
this.isRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setInterrupt() {
|
||||
this.interrupt = true;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return isRunning.get();
|
||||
}
|
||||
}
|
||||
444
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/Modem.java
vendored
Normal file
444
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/Modem.java
vendored
Normal file
@@ -0,0 +1,444 @@
|
||||
package org.smslib;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.Capabilities.Caps;
|
||||
import org.smslib.DeviceInformation.Modes;
|
||||
import org.smslib.callback.IDeviceInformationListener;
|
||||
import org.smslib.callback.IInboundOutboundMessageListener;
|
||||
import org.smslib.callback.IModemStatusListener;
|
||||
import org.smslib.driver.AbstractModemDriver;
|
||||
import org.smslib.driver.IPModemDriver;
|
||||
import org.smslib.driver.JSerialModemDriver;
|
||||
import org.smslib.message.DeliveryReportMessage;
|
||||
import org.smslib.message.InboundMessage;
|
||||
import org.smslib.message.MsIsdn;
|
||||
import org.smslib.message.OutboundMessage;
|
||||
import org.smslib.message.OutboundMessage.FailureCause;
|
||||
import org.smslib.message.OutboundMessage.SentStatus;
|
||||
import org.smslib.message.Payload;
|
||||
import org.smslib.message.Payload.Type;
|
||||
|
||||
/**
|
||||
* The Modem class is an abstraction, central to all operations
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Modem {
|
||||
static Logger logger = LoggerFactory.getLogger(Modem.class);
|
||||
|
||||
public enum Status {
|
||||
Starting,
|
||||
Started,
|
||||
Stopping,
|
||||
Stopped,
|
||||
Error
|
||||
}
|
||||
|
||||
AbstractModemDriver modemDriver;
|
||||
|
||||
String simPin;
|
||||
MsIsdn smscNumber;
|
||||
protected String operatorId = "";
|
||||
String gatewayId = "";
|
||||
String description = "";
|
||||
|
||||
private ScheduledExecutorService scheduledService;
|
||||
@Nullable
|
||||
ScheduledFuture<?> messageReader;
|
||||
MessageSender messageSender;
|
||||
Queue<OutboundMessage> messageQueue = new ConcurrentLinkedQueue<>();
|
||||
HashSet<String> readMessagesSet;
|
||||
|
||||
Status status = Status.Stopped;
|
||||
Lock startAndStoplock = new ReentrantLock();
|
||||
|
||||
int multipartReferenceNo = 0;
|
||||
|
||||
Capabilities capabilities = new Capabilities();
|
||||
DeviceInformation deviceInformation = new DeviceInformation();
|
||||
private @Nullable IModemStatusListener modemStatusCallback = null;
|
||||
private @Nullable IInboundOutboundMessageListener messageCallback = null;
|
||||
|
||||
private Random randomizer = new Random();
|
||||
|
||||
private AtomicBoolean isStopping = new AtomicBoolean(false);
|
||||
private AtomicBoolean isStarting = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Time between sending messages (ms)
|
||||
*/
|
||||
private int gatewayDispatcherYield = 100;
|
||||
|
||||
/**
|
||||
* Time between polling for new messages (ms)
|
||||
*/
|
||||
public int modemPollingInterval = 15;
|
||||
|
||||
public Modem(SerialPortManager serialPortManager, String address, int port, String simPin,
|
||||
ScheduledExecutorService scheduledService, Integer pollingInterval, Integer delayBetweenSend) {
|
||||
this.gatewayId = address + "-" + port;
|
||||
this.scheduledService = scheduledService;
|
||||
this.modemPollingInterval = pollingInterval;
|
||||
this.gatewayDispatcherYield = delayBetweenSend;
|
||||
setDescription("GSM Modem " + address + "/" + port);
|
||||
|
||||
Capabilities caps = new Capabilities();
|
||||
caps.set(Caps.CanSendMessage);
|
||||
caps.set(Caps.CanSendBinaryMessage);
|
||||
caps.set(Caps.CanSendUnicodeMessage);
|
||||
caps.set(Caps.CanSendWapMessage);
|
||||
caps.set(Caps.CanSendFlashMessage);
|
||||
caps.set(Caps.CanSendPortInfo);
|
||||
caps.set(Caps.CanSplitMessages);
|
||||
caps.set(Caps.CanRequestDeliveryStatus);
|
||||
setCapabilities(caps);
|
||||
if (isPortAnIpAddress(address)) {
|
||||
this.modemDriver = new IPModemDriver(this, address, port);
|
||||
} else {
|
||||
this.modemDriver = new JSerialModemDriver(serialPortManager, this, address, port);
|
||||
}
|
||||
this.simPin = simPin;
|
||||
this.smscNumber = new MsIsdn();
|
||||
this.readMessagesSet = new HashSet<>();
|
||||
this.messageSender = new MessageSender(String.format("Gateway Dispatcher 1 [%s]", this.gatewayId), messageQueue,
|
||||
this, gatewayDispatcherYield);
|
||||
}
|
||||
|
||||
final public boolean start() {
|
||||
if (!isStarting.getAndSet(true)) {
|
||||
this.startAndStoplock.lock();
|
||||
try {
|
||||
if ((getStatus() == Status.Stopped) || (getStatus() == Status.Error)) {
|
||||
try {
|
||||
setStatus(Status.Starting);
|
||||
logger.debug("Starting gateway: {}", toShortString());
|
||||
this.modemDriver.lock();
|
||||
try {
|
||||
this.modemDriver.openPort();
|
||||
this.modemDriver.initializeModem();
|
||||
ScheduledFuture<?> messageReaderFinal = this.messageReader;
|
||||
if (messageReaderFinal != null) {
|
||||
messageReaderFinal.cancel(true);
|
||||
}
|
||||
this.messageReader = scheduledService.scheduleWithFixedDelay(new MessageReader(this), 15,
|
||||
modemPollingInterval, TimeUnit.SECONDS);
|
||||
this.modemDriver.refreshRssi();
|
||||
this.messageSender = new MessageSender(
|
||||
String.format("Gateway Dispatcher 1 [%s]", this.gatewayId), messageQueue, this,
|
||||
gatewayDispatcherYield);
|
||||
startSendingQueue();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Gateway: {}: {}, SL:{}, SIG: {} / {}", toShortString(),
|
||||
getDeviceInformation().toString(), this.modemDriver.getMemoryLocations(),
|
||||
this.modemDriver.getSignature(true), this.modemDriver.getSignature(false));
|
||||
}
|
||||
} finally {
|
||||
this.modemDriver.unlock();
|
||||
}
|
||||
setStatus(Status.Started);
|
||||
} catch (CommunicationException e) {
|
||||
logger.error("Communication exception when trying to start", e);
|
||||
try {
|
||||
stop();
|
||||
} finally {
|
||||
setStatus(Status.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.startAndStoplock.unlock();
|
||||
this.isStarting.set(false);
|
||||
}
|
||||
}
|
||||
return (getStatus() == Status.Started);
|
||||
}
|
||||
|
||||
final public boolean stop() {
|
||||
if (!isStopping.getAndSet(true)) {
|
||||
this.startAndStoplock.lock();
|
||||
try {
|
||||
if ((getStatus() == Status.Started) || (getStatus() == Status.Error)) {
|
||||
setStatus(Status.Stopping);
|
||||
logger.debug("Stopping gateway: {}", toShortString());
|
||||
if (messageSender.isRunning()) {
|
||||
this.messageSender.setInterrupt();
|
||||
}
|
||||
logger.warn("Gateway stopping, message not delivered : {}", this.messageQueue.size());
|
||||
ScheduledFuture<?> messageReaderFinal = this.messageReader;
|
||||
if (messageReaderFinal != null) {
|
||||
messageReaderFinal.cancel(true);
|
||||
}
|
||||
this.modemDriver.lock();
|
||||
try {
|
||||
this.modemDriver.closePort();
|
||||
} finally {
|
||||
this.modemDriver.unlock();
|
||||
}
|
||||
setStatus(Status.Stopped);
|
||||
}
|
||||
} finally {
|
||||
this.startAndStoplock.unlock();
|
||||
isStopping.set(false);
|
||||
}
|
||||
}
|
||||
return (getStatus() == Status.Stopped);
|
||||
}
|
||||
|
||||
final public void error() {
|
||||
this.stop();
|
||||
this.status = Status.Error;
|
||||
}
|
||||
|
||||
final public boolean send(OutboundMessage message) throws CommunicationException {
|
||||
try {
|
||||
if (getStatus() != Status.Started) {
|
||||
logger.debug("Outbound message routed via non-started gateway: {} ({})", message.toShortString(),
|
||||
getStatus());
|
||||
return false;
|
||||
}
|
||||
this.modemDriver.lock();
|
||||
try {
|
||||
if (getDeviceInformation().getMode() == Modes.PDU) {
|
||||
List<String> pdus = message.getPdus(getSmscNumber(), getNextMultipartReferenceNo());
|
||||
for (String pdu : pdus) {
|
||||
int j = pdu.length() / 2 - 1;
|
||||
int refNo = this.modemDriver.atSendPDUMessage(j, pdu);
|
||||
if (refNo >= 0) {
|
||||
message.setGatewayId(getGatewayId());
|
||||
message.setSentDate(new Date());
|
||||
message.getOperatorMessageIds().add(String.valueOf(refNo));
|
||||
message.setSentStatus(SentStatus.Sent);
|
||||
message.setFailureCause(FailureCause.None);
|
||||
} else {
|
||||
message.setSentStatus(SentStatus.Failed);
|
||||
message.setFailureCause(FailureCause.GatewayFailure);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MsIsdn recipientAddress = message.getRecipientAddress();
|
||||
Payload payload = message.getPayload();
|
||||
if (recipientAddress == null) {
|
||||
throw new IllegalArgumentException("Recipient is null");
|
||||
}
|
||||
String text = payload.getText();
|
||||
if (payload.getType() == Type.Binary || text == null) {
|
||||
throw new IllegalArgumentException("Cannot send sms in binary format");
|
||||
}
|
||||
int refNo = this.modemDriver.atSendTEXTMessage(recipientAddress.getAddress(), text);
|
||||
if (refNo >= 0) {
|
||||
message.setGatewayId(getGatewayId());
|
||||
message.setSentDate(new Date());
|
||||
message.getOperatorMessageIds().add(String.valueOf(refNo));
|
||||
message.setSentStatus(SentStatus.Sent);
|
||||
message.setFailureCause(FailureCause.None);
|
||||
} else {
|
||||
message.setSentStatus(SentStatus.Failed);
|
||||
message.setFailureCause(FailureCause.GatewayFailure);
|
||||
}
|
||||
}
|
||||
if (message.getSentStatus() == SentStatus.Sent) {
|
||||
getDeviceInformation().increaseTotalSent();
|
||||
} else {
|
||||
getDeviceInformation().increaseTotalFailed();
|
||||
}
|
||||
} finally {
|
||||
this.modemDriver.unlock();
|
||||
}
|
||||
return message.getSentStatus() == SentStatus.Sent;
|
||||
} catch (CommunicationException e) {
|
||||
getDeviceInformation().increaseTotalFailures();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
final public boolean delete(InboundMessage message) throws CommunicationException {
|
||||
if (getStatus() != Status.Started) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Delete message via non-started gateway: {} ({})", message.toShortString(), getStatus());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.modemDriver.lock();
|
||||
try {
|
||||
this.readMessagesSet.remove(message.getSignature());
|
||||
if (message.getMemIndex() >= 0) {
|
||||
return this.modemDriver.atDeleteMessage(message.getMemLocation(), message.getMemIndex()).isResponseOk();
|
||||
}
|
||||
if ((message.getMemIndex() == -1) && (message.getMpMemIndex().length() > 0)) {
|
||||
StringTokenizer tokens = new StringTokenizer(message.getMpMemIndex(), ",");
|
||||
while (tokens.hasMoreTokens()) {
|
||||
this.modemDriver.atDeleteMessage(message.getMemLocation(), Integer.valueOf(tokens.nextToken()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
this.modemDriver.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean queue(OutboundMessage message) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Queue: {}", message.toShortString());
|
||||
}
|
||||
boolean added = messageQueue.add(message);
|
||||
IInboundOutboundMessageListener messageCallbackFinal = messageCallback;
|
||||
if (messageCallbackFinal != null) {
|
||||
messageCallbackFinal.messageSent(message);
|
||||
}
|
||||
startSendingQueue();
|
||||
return added;
|
||||
}
|
||||
|
||||
private void startSendingQueue() {
|
||||
if (messageQueue.size() > 0 && (!this.messageSender.isRunning())) {
|
||||
this.scheduledService.execute(messageSender);
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceInformation getDeviceInformation() {
|
||||
return this.deviceInformation;
|
||||
}
|
||||
|
||||
public AbstractModemDriver getModemDriver() {
|
||||
return this.modemDriver;
|
||||
}
|
||||
|
||||
public String getSimPin() {
|
||||
return this.simPin;
|
||||
}
|
||||
|
||||
public MsIsdn getSmscNumber() {
|
||||
return this.smscNumber;
|
||||
}
|
||||
|
||||
public void setSmscNumber(MsIsdn smscNumber) {
|
||||
this.smscNumber = smscNumber;
|
||||
}
|
||||
|
||||
public HashSet<String> getReadMessagesSet() {
|
||||
return this.readMessagesSet;
|
||||
}
|
||||
|
||||
private void setStatus(Status status) {
|
||||
Status oldStatus = this.status;
|
||||
this.status = status;
|
||||
Status newStatus = this.status;
|
||||
IModemStatusListener modemStatusCallbackFinal = modemStatusCallback;
|
||||
if (modemStatusCallbackFinal != null) {
|
||||
modemStatusCallbackFinal.processStatusCallback(oldStatus, newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getNextMultipartReferenceNo() {
|
||||
if (this.multipartReferenceNo == 0) {
|
||||
this.multipartReferenceNo = this.randomizer.nextInt();
|
||||
if (this.multipartReferenceNo < 0) {
|
||||
this.multipartReferenceNo *= -1;
|
||||
}
|
||||
this.multipartReferenceNo %= 65536;
|
||||
}
|
||||
this.multipartReferenceNo = (this.multipartReferenceNo + 1) % 65536;
|
||||
return this.multipartReferenceNo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer b = new StringBuffer(1024);
|
||||
b.append("== GATEWAY ========================================================================%n");
|
||||
b.append(String.format("Gateway ID: %s%n", getGatewayId()));
|
||||
b.append(String.format("-- Capabilities --%n"));
|
||||
b.append(capabilities.toString());
|
||||
b.append(String.format("-- Settings --%n"));
|
||||
b.append("== GATEWAY END ========================================================================%n");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return getGatewayId() + String.format(" [%s]", this.modemDriver.getPortInfo());
|
||||
}
|
||||
|
||||
private boolean isPortAnIpAddress(String address) {
|
||||
try {
|
||||
InetAddress.getByName(address);
|
||||
return true;
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void registerStatusListener(@Nullable IModemStatusListener smsModemStatusCallback) {
|
||||
this.modemStatusCallback = smsModemStatusCallback;
|
||||
}
|
||||
|
||||
public void registerMessageListener(@Nullable IInboundOutboundMessageListener messageCallback) {
|
||||
this.messageCallback = messageCallback;
|
||||
}
|
||||
|
||||
public void registerInformationListener(@Nullable IDeviceInformationListener deviceInformationListener) {
|
||||
this.deviceInformation.setDeviceInformationListener(deviceInformationListener);
|
||||
}
|
||||
|
||||
public void processMessage(InboundMessage message) {
|
||||
IInboundOutboundMessageListener messageCallbackFinal = this.messageCallback;
|
||||
if (messageCallbackFinal != null) {
|
||||
messageCallbackFinal.messageReceived(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void processMessageSent(OutboundMessage message) {
|
||||
IInboundOutboundMessageListener messageCallbackFinal = this.messageCallback;
|
||||
if (messageCallbackFinal != null) {
|
||||
messageCallbackFinal.messageSent(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void processDeliveryReport(DeliveryReportMessage message) {
|
||||
IInboundOutboundMessageListener messageCallbackFinal = this.messageCallback;
|
||||
if (messageCallbackFinal != null) {
|
||||
messageCallbackFinal.messageDelivered(message);
|
||||
}
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public final String getGatewayId() {
|
||||
return this.gatewayId;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setCapabilities(Capabilities capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
}
|
||||
26
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/ModemResponse.java
vendored
Normal file
26
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/ModemResponse.java
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.smslib;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModemResponse {
|
||||
String responseData;
|
||||
|
||||
boolean responseOk;
|
||||
|
||||
public ModemResponse(String responseData, boolean responseOk) {
|
||||
this.responseData = responseData;
|
||||
this.responseOk = responseOk;
|
||||
}
|
||||
|
||||
public String getResponseData() {
|
||||
return this.responseData;
|
||||
}
|
||||
|
||||
public boolean isResponseOk() {
|
||||
return this.responseOk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.smslib;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* Exception class for internal SMSLib unrecoverable error
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UnrecoverableSmslibException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 7649578885702261759L;
|
||||
|
||||
public UnrecoverableSmslibException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnrecoverableSmslibException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.smslib.callback;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link IDeviceInformationListener} will receive informations
|
||||
* and statistics
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IDeviceInformationListener {
|
||||
|
||||
void setManufacturer(String manufacturer);
|
||||
|
||||
void setModel(String string);
|
||||
|
||||
void setSwVersion(String swVersion);
|
||||
|
||||
void setSerialNo(String serialNo);
|
||||
|
||||
void setImsi(String imsi);
|
||||
|
||||
void setRssi(String rssi);
|
||||
|
||||
void setMode(String mode);
|
||||
|
||||
public void setTotalSent(String totalSent);
|
||||
|
||||
public void setTotalFailed(String totalFailed);
|
||||
|
||||
public void setTotalReceived(String totalReceived);
|
||||
|
||||
public void setTotalFailures(String totalFailure);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.smslib.callback;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.message.DeliveryReportMessage;
|
||||
import org.smslib.message.InboundMessage;
|
||||
import org.smslib.message.OutboundMessage;
|
||||
|
||||
/**
|
||||
*
|
||||
* Interface to implement to get messages and reports
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IInboundOutboundMessageListener {
|
||||
|
||||
/**
|
||||
* Implement this method to get incoming messages
|
||||
*
|
||||
* @param message The inbound message received
|
||||
*/
|
||||
public void messageReceived(InboundMessage message);
|
||||
|
||||
/**
|
||||
* Implement this method to get warned when
|
||||
* a message is sent on the network
|
||||
*
|
||||
* @param message the message sent
|
||||
*/
|
||||
public void messageSent(OutboundMessage message);
|
||||
|
||||
/**
|
||||
* Implement this method to get warned when
|
||||
* a message previously sent is received by the recipient
|
||||
*
|
||||
* @param message the delivery report message
|
||||
*/
|
||||
public void messageDelivered(DeliveryReportMessage message);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.smslib.callback;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.Modem.Status;
|
||||
|
||||
/**
|
||||
* Implement this interface to get status change
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IModemStatusListener {
|
||||
|
||||
boolean processStatusCallback(Status oldStatus, Status newStatus);
|
||||
}
|
||||
540
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/AbstractModemDriver.java
vendored
Normal file
540
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/AbstractModemDriver.java
vendored
Normal file
@@ -0,0 +1,540 @@
|
||||
package org.smslib.driver;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringReader;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.Capabilities;
|
||||
import org.smslib.CommunicationException;
|
||||
import org.smslib.Capabilities.Caps;
|
||||
import org.smslib.DeviceInformation.Modes;
|
||||
import org.smslib.Modem;
|
||||
import org.smslib.ModemResponse;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractModemDriver {
|
||||
static Logger logger = LoggerFactory.getLogger(AbstractModemDriver.class);
|
||||
|
||||
private Lock lock = new ReentrantLock();
|
||||
|
||||
Properties modemProperties;
|
||||
|
||||
@NonNullByDefault({})
|
||||
InputStream in;
|
||||
|
||||
@NonNullByDefault({})
|
||||
OutputStream out;
|
||||
|
||||
StringBuffer buffer = new StringBuffer(4096);
|
||||
|
||||
PollReader pollReader = new PollReader(this, "undefined");
|
||||
|
||||
Modem modem;
|
||||
|
||||
boolean responseOk;
|
||||
|
||||
String memoryLocations = "";
|
||||
|
||||
int atATHCounter = 0;
|
||||
|
||||
public abstract void openPort() throws CommunicationException;
|
||||
|
||||
public abstract void closePort();
|
||||
|
||||
public abstract String getPortInfo();
|
||||
|
||||
public AbstractModemDriver(Modem modem) {
|
||||
modemProperties = new Properties();
|
||||
try {
|
||||
ClassLoader classLoader = this.getClass().getClassLoader();
|
||||
if (classLoader != null) {
|
||||
try (InputStream inputStream = classLoader.getResourceAsStream("modem.properties")) {
|
||||
modemProperties.load(inputStream);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot instantiate modem driver", e);
|
||||
}
|
||||
this.modem = modem;
|
||||
}
|
||||
|
||||
public ModemResponse write(String data) throws CommunicationException {
|
||||
return write(data, false);
|
||||
}
|
||||
|
||||
public ModemResponse write(String data, boolean skipResponse) throws CommunicationException {
|
||||
this.lock.lock();
|
||||
try {
|
||||
logger.debug("{} <== {}", getPortInfo(), data);
|
||||
write(data.getBytes());
|
||||
countSheeps(Integer.valueOf(getModemSettings("command_wait_unit")));
|
||||
return (new ModemResponse((skipResponse ? "" : getResponse()), (skipResponse ? true : this.responseOk)));
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasData() throws IOException {
|
||||
return ((this.in != null) && (this.in.available() > 0));
|
||||
}
|
||||
|
||||
protected int read() throws IOException {
|
||||
return this.in.read();
|
||||
}
|
||||
|
||||
protected void write(byte[] s) throws CommunicationException {
|
||||
int charDelay = Integer.valueOf(getModemSettings("char_wait_unit"));
|
||||
try {
|
||||
if (charDelay == 0) {
|
||||
this.out.write(s);
|
||||
} else {
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
byte b = s[i];
|
||||
this.out.write(b);
|
||||
countSheeps(charDelay);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new CommunicationException("Cannot write to device", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void write(byte s) throws CommunicationException {
|
||||
try {
|
||||
this.out.write(s);
|
||||
} catch (IOException e) {
|
||||
throw new CommunicationException("Cannot write data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getResponse() throws CommunicationException {
|
||||
StringBuffer raw = new StringBuffer(256);
|
||||
StringBuffer b = new StringBuffer(256);
|
||||
try {
|
||||
while (true) {
|
||||
String line = getLineFromBuffer();
|
||||
logger.debug("{} >>> {}", getPortInfo(), line);
|
||||
this.buffer.delete(0, line.length() + 2);
|
||||
if (line.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
if (line.charAt(0) == '^') {
|
||||
continue;
|
||||
}
|
||||
if (line.charAt(0) == '*') {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("RING")) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("+STIN:")) {
|
||||
continue;
|
||||
}
|
||||
if (Integer.valueOf(getModemSettings("cpin_without_ok")) == 1) {
|
||||
if (line.startsWith("+CPIN:")) {
|
||||
raw.append(line);
|
||||
raw.append("$");
|
||||
b.append(line);
|
||||
this.responseOk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (line.startsWith("+CLIP:")) {
|
||||
write("+++", true);
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
write("ATH\r", true);
|
||||
logger.debug("+++ INCREASE ATH");
|
||||
this.atATHCounter++;
|
||||
// no need for a call handler. discard
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
continue;
|
||||
}
|
||||
if (line.indexOf("OK") == 0) {
|
||||
if (this.atATHCounter > 0) {
|
||||
logger.debug("--- DECREASE ATH");
|
||||
this.atATHCounter--;
|
||||
continue;
|
||||
}
|
||||
this.responseOk = true;
|
||||
break;
|
||||
}
|
||||
if ((line.indexOf("ERROR") == 0) || (line.indexOf("+CMS ERROR") == 0)
|
||||
|| (line.indexOf("+CME ERROR") == 0)) {
|
||||
logger.warn("{} ERR==> {}", getPortInfo(), line);
|
||||
this.responseOk = false;
|
||||
break;
|
||||
}
|
||||
if (b.length() > 0) {
|
||||
b.append('\n');
|
||||
}
|
||||
raw.append(line);
|
||||
raw.append("$");
|
||||
b.append(line);
|
||||
}
|
||||
} catch (IOException | TimeoutException e) {
|
||||
throw new CommunicationException("Cannot get response", e);
|
||||
}
|
||||
logger.debug("{} ==> {}", getPortInfo(), raw.toString());
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private String getLineFromBuffer() throws TimeoutException, IOException {
|
||||
long startTimeout = System.currentTimeMillis();
|
||||
long endTimeout = startTimeout;
|
||||
while (this.buffer.indexOf("\r") == -1) {
|
||||
endTimeout += Integer.valueOf(getModemSettings("wait_unit"));
|
||||
if ((endTimeout - startTimeout) > Integer.valueOf(getModemSettings("timeout"))) {
|
||||
throw new TimeoutException("Timeout elapsed for " + getPortInfo());
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
}
|
||||
BufferedReader r = new BufferedReader(new StringReader(this.buffer.toString()));
|
||||
String line = r.readLine();
|
||||
r.close();
|
||||
return line;
|
||||
}
|
||||
|
||||
public void clearResponses() {
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")) * 1);
|
||||
while (this.buffer.length() > 0) {
|
||||
this.buffer.delete(0, this.buffer.length());
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")) * 1);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMemoryLocations() {
|
||||
return this.memoryLocations;
|
||||
}
|
||||
|
||||
public void initializeModem() throws CommunicationException {
|
||||
int counter = 0;
|
||||
this.lock.lock();
|
||||
try {
|
||||
atAT();
|
||||
atAT();
|
||||
atAT();
|
||||
atAT();
|
||||
atEchoOff();
|
||||
clearResponses();
|
||||
this.modem.getDeviceInformation().setManufacturer(atGetManufacturer().getResponseData());
|
||||
this.modem.getDeviceInformation().setModel(atGetModel().getResponseData());
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
atFromModemSettings("init1");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_after_init1")));
|
||||
atFromModemSettings("init2");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_after_init2")));
|
||||
clearResponses();
|
||||
atEchoOff();
|
||||
clearResponses();
|
||||
atFromModemSettings("pre_pin");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_after_pre_pin")));
|
||||
while (true) {
|
||||
counter++;
|
||||
if (counter == 5) {
|
||||
throw new CommunicationException("Modem does not correspond correctly, giving up...");
|
||||
}
|
||||
ModemResponse simStatus = atGetSimStatus();
|
||||
if (simStatus.getResponseData().indexOf("SIM PIN") >= 0) {
|
||||
if (this.modem.getSimPin().isBlank()) {
|
||||
throw new CommunicationException("SIM PIN requested but not defined!");
|
||||
}
|
||||
atEnterPin(this.modem.getSimPin());
|
||||
} else if (simStatus.getResponseData().indexOf("READY") >= 0) {
|
||||
break;
|
||||
} else if (simStatus.getResponseData().indexOf("OK") >= 0) {
|
||||
break;
|
||||
} else if (simStatus.getResponseData().indexOf("ERROR") >= 0) {
|
||||
logger.error("SIM PIN error!");
|
||||
}
|
||||
logger.debug("SIM PIN Not ok, waiting for a while...");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_on_sim_error")));
|
||||
}
|
||||
atFromModemSettings("post_pin");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_after_post_pin")));
|
||||
atEnableClip();
|
||||
if (!atNetworkRegistration().isResponseOk()) {
|
||||
throw new CommunicationException("Network registration failed!");
|
||||
}
|
||||
atVerboseOff();
|
||||
if (atSetPDUMode().isResponseOk()) {
|
||||
this.modem.getDeviceInformation().setMode(Modes.PDU);
|
||||
} else {
|
||||
logger.debug("Modem does not support PDU, trying to switch to TEXT...");
|
||||
if (atSetTEXTMode().isResponseOk()) {
|
||||
Capabilities caps = new Capabilities();
|
||||
caps.set(Caps.CanSendMessage);
|
||||
this.modem.setCapabilities(caps);
|
||||
this.modem.getDeviceInformation().setMode(Modes.TEXT);
|
||||
} else {
|
||||
throw new CommunicationException("Neither PDU nor TEXT mode are supported by this modem!");
|
||||
}
|
||||
}
|
||||
atCnmiOff();
|
||||
retrieveMemoryLocations();
|
||||
refreshDeviceInformation();
|
||||
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshDeviceInformation() throws CommunicationException {
|
||||
this.modem.getDeviceInformation().setManufacturer(atGetManufacturer().getResponseData());
|
||||
this.modem.getDeviceInformation().setModel(atGetModel().getResponseData());
|
||||
this.modem.getDeviceInformation().setSerialNo(atGetSerialNo().getResponseData());
|
||||
this.modem.getDeviceInformation().setImsi(atGetImsi().getResponseData());
|
||||
this.modem.getDeviceInformation().setSwVersion(atGetSWVersion().getResponseData());
|
||||
this.refreshRssi();
|
||||
}
|
||||
|
||||
public void refreshRssi() throws CommunicationException {
|
||||
String s = atGetSignalStrengh().getResponseData();
|
||||
if (this.responseOk) {
|
||||
String s1 = s.split("\\R")[0]; // ensure to get first line only
|
||||
s1 = s1.substring(s.indexOf(':') + 1).trim();
|
||||
StringTokenizer tokens = new StringTokenizer(s1, ",");
|
||||
int rssi = Integer.valueOf(tokens.nextToken().trim());
|
||||
this.modem.getDeviceInformation().setRssi(rssi == 99 ? 99 : (-113 + 2 * rssi));
|
||||
}
|
||||
}
|
||||
|
||||
void retrieveMemoryLocations() throws CommunicationException {
|
||||
if (this.memoryLocations.isBlank()) {
|
||||
this.memoryLocations = getModemSettings("memory_locations");
|
||||
if (this.memoryLocations.isBlank()) {
|
||||
this.memoryLocations = "";
|
||||
}
|
||||
if (this.memoryLocations.isBlank()) {
|
||||
try {
|
||||
String response = atGetMemoryLocations().getResponseData();
|
||||
if (response.indexOf("+CPMS:") >= 0) {
|
||||
int i, j;
|
||||
i = response.indexOf('(');
|
||||
while (response.charAt(i) == '(') {
|
||||
i++;
|
||||
}
|
||||
j = i;
|
||||
while (response.charAt(j) != ')') {
|
||||
j++;
|
||||
}
|
||||
response = response.substring(i, j);
|
||||
StringTokenizer tokens = new StringTokenizer(response, ",");
|
||||
while (tokens.hasMoreTokens()) {
|
||||
String loc = tokens.nextToken().replaceAll("\"", "");
|
||||
if (!"MT".equalsIgnoreCase(loc) && this.memoryLocations.indexOf(loc) < 0) {
|
||||
this.memoryLocations += loc;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.memoryLocations = "SM";
|
||||
logger.debug("CPMS detection failed, proceeding with default memory 'SM'.");
|
||||
}
|
||||
} catch (CommunicationException e) {
|
||||
this.memoryLocations = "SM";
|
||||
logger.debug("CPMS detection failed, proceeding with default memory 'SM'.", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Using given memory locations: {}", this.memoryLocations);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSignature(boolean complete) {
|
||||
String manufacturer = this.modem.getDeviceInformation().getManufacturer().toLowerCase().replaceAll(" ", "")
|
||||
.replaceAll(" ", "").replaceAll(" ", "");
|
||||
String model = this.modem.getDeviceInformation().getModel().toLowerCase().replaceAll(" ", "")
|
||||
.replaceAll(" ", "").replaceAll(" ", "");
|
||||
return (complete ? manufacturer + "_" + model : manufacturer);
|
||||
}
|
||||
|
||||
protected ModemResponse atAT() throws CommunicationException {
|
||||
return write("AT\r", true);
|
||||
}
|
||||
|
||||
protected ModemResponse atATWithResponse() throws CommunicationException {
|
||||
return write("AT\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atEchoOff() throws CommunicationException {
|
||||
return write("ATE0\r", true);
|
||||
}
|
||||
|
||||
protected ModemResponse atGetSimStatus() throws CommunicationException {
|
||||
return write("AT+CPIN?\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atEnterPin(String pin) throws CommunicationException {
|
||||
return write(String.format("AT+CPIN=\"%s\"\r", pin));
|
||||
}
|
||||
|
||||
protected ModemResponse atNetworkRegistration() throws CommunicationException {
|
||||
write("AT+CREG=1\r");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_network_registration")));
|
||||
return write("AT+CREG?\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atEnableClip() throws CommunicationException {
|
||||
return write("AT+CLIP=1\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atVerboseOff() throws CommunicationException {
|
||||
return write("AT+CMEE=0\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atSetPDUMode() throws CommunicationException {
|
||||
return write("AT+CMGF=0\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atSetTEXTMode() throws CommunicationException {
|
||||
return write("AT+CMGF=1\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atCnmiOff() throws CommunicationException {
|
||||
return write("AT+CNMI=2,0,0,0,0\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetManufacturer() throws CommunicationException {
|
||||
return write("AT+CGMI\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetModel() throws CommunicationException {
|
||||
return write("AT+CGMM\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetImsi() throws CommunicationException {
|
||||
return write("AT+CIMI\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetSerialNo() throws CommunicationException {
|
||||
return write("AT+CGSN\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetSWVersion() throws CommunicationException {
|
||||
return write("AT+CGMR\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetSignalStrengh() throws CommunicationException {
|
||||
return write("AT+CSQ\r");
|
||||
}
|
||||
|
||||
public int atSendPDUMessage(int size, String pdu) throws CommunicationException {
|
||||
write(String.format("AT+CMGS=%d\r", size), true);
|
||||
while (this.buffer.length() == 0) {
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_before_send_pdu")));
|
||||
clearResponses();
|
||||
write(pdu, true);
|
||||
write((byte) 26);
|
||||
String response = getResponse();
|
||||
if (this.responseOk && response.contains(":")) {
|
||||
return Integer.parseInt(response.substring(response.indexOf(":") + 1).trim());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int atSendTEXTMessage(String recipient, String text) throws CommunicationException {
|
||||
write(String.format("AT+CSCS=\"%s\"\r", "UTF-8"), true);
|
||||
if (!this.responseOk) {
|
||||
throw new CommunicationException("Unsupported encoding: UTF-8");
|
||||
}
|
||||
write(String.format("AT+CMGS=\"%s\"\r", recipient), true);
|
||||
while (this.buffer.length() == 0) {
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_before_send_pdu")));
|
||||
clearResponses();
|
||||
write(text, true);
|
||||
write((byte) 26);
|
||||
String response = getResponse();
|
||||
if (this.responseOk) {
|
||||
return Integer.parseInt(response.substring(response.indexOf(":") + 1).trim());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public ModemResponse atGetMemoryLocations() throws CommunicationException {
|
||||
return write("AT+CPMS=?\r");
|
||||
}
|
||||
|
||||
public ModemResponse atSwitchMemoryLocation(String memoryLocation) throws CommunicationException {
|
||||
return write(String.format("AT+CPMS=\"%s\"\r", memoryLocation));
|
||||
}
|
||||
|
||||
public ModemResponse atGetMessages(String memoryLocation) throws CommunicationException {
|
||||
if (atSwitchMemoryLocation(memoryLocation).isResponseOk()) {
|
||||
return (this.modem.getDeviceInformation().getMode() == Modes.PDU ? write("AT+CMGL=4\r")
|
||||
: write("AT+CMGL=\"ALL\"\r"));
|
||||
}
|
||||
return new ModemResponse("", false);
|
||||
}
|
||||
|
||||
public ModemResponse atDeleteMessage(String memoryLocation, int memoryIndex) throws CommunicationException {
|
||||
if (atSwitchMemoryLocation(memoryLocation).isResponseOk()) {
|
||||
return write(String.format("AT+CMGD=%d\r", memoryIndex));
|
||||
}
|
||||
return new ModemResponse("", false);
|
||||
}
|
||||
|
||||
public ModemResponse atFromModemSettings(String key) throws CommunicationException {
|
||||
String atCommand = getModemSettings(key);
|
||||
if (!atCommand.isBlank()) {
|
||||
return write(atCommand);
|
||||
}
|
||||
return new ModemResponse("", true);
|
||||
}
|
||||
|
||||
public String getModemSettings(String key) {
|
||||
String fullSignature = getSignature(true);
|
||||
String shortSignature = getSignature(false);
|
||||
String value = "";
|
||||
if (!fullSignature.isBlank()) {
|
||||
value = modemProperties.getProperty(fullSignature + "." + key);
|
||||
}
|
||||
if ((value == null || value.isBlank()) && !shortSignature.isBlank()) {
|
||||
value = modemProperties.getProperty(shortSignature + "." + key);
|
||||
}
|
||||
if (value == null || value.isBlank()) {
|
||||
value = modemProperties.getProperty("default" + "." + key);
|
||||
}
|
||||
return ((value == null || value.isBlank()) ? "" : value);
|
||||
}
|
||||
|
||||
public void lock() {
|
||||
this.lock.lock();
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
protected static void countSheeps(int n) {
|
||||
try {
|
||||
Thread.sleep(n);
|
||||
} catch (InterruptedException e) {
|
||||
// Nothing here...
|
||||
}
|
||||
}
|
||||
}
|
||||
85
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/IPModemDriver.java
vendored
Normal file
85
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/IPModemDriver.java
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
package org.smslib.driver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.CommunicationException;
|
||||
import org.smslib.Modem;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
* Manage communication with ser2net (or equivalent)
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IPModemDriver extends AbstractModemDriver {
|
||||
static Logger logger = LoggerFactory.getLogger(IPModemDriver.class);
|
||||
|
||||
String address;
|
||||
|
||||
int port;
|
||||
|
||||
@Nullable
|
||||
Socket socket;
|
||||
|
||||
public IPModemDriver(Modem modem, String address, int port) {
|
||||
super(modem);
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openPort() throws CommunicationException {
|
||||
logger.debug("Opening IP port: {}", getPortInfo());
|
||||
try {
|
||||
Socket openSocket = new Socket(this.address, this.port);
|
||||
openSocket.setReceiveBufferSize(Integer.valueOf(getModemSettings("port_buffer")));
|
||||
openSocket.setSendBufferSize(Integer.valueOf(getModemSettings("port_buffer")));
|
||||
openSocket.setSoTimeout(30000);
|
||||
openSocket.setTcpNoDelay(true);
|
||||
this.in = openSocket.getInputStream();
|
||||
this.out = openSocket.getOutputStream();
|
||||
this.socket = openSocket;
|
||||
} catch (IOException e) {
|
||||
throw new CommunicationException("Cannot open port", e);
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("after_ip_connect_wait_unit")));
|
||||
this.pollReader = new PollReader(this, getPortInfo());
|
||||
this.pollReader.setDaemon(true);
|
||||
this.pollReader.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePort() {
|
||||
logger.debug("Closing IP port: {}", getPortInfo());
|
||||
try {
|
||||
this.pollReader.cancel();
|
||||
this.pollReader.join();
|
||||
if (in != null) {
|
||||
this.in.close();
|
||||
this.in = null;
|
||||
}
|
||||
if (out != null) {
|
||||
this.out.close();
|
||||
this.out = null;
|
||||
}
|
||||
Socket finalSocket = socket;
|
||||
if (finalSocket != null) {
|
||||
finalSocket.close();
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
logger.debug("Cannot close port");
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("after_ip_connect_wait_unit")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPortInfo() {
|
||||
return this.address + ":" + this.port;
|
||||
}
|
||||
}
|
||||
101
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/JSerialModemDriver.java
vendored
Normal file
101
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/JSerialModemDriver.java
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package org.smslib.driver;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.CommunicationException;
|
||||
import org.smslib.Modem;
|
||||
|
||||
/**
|
||||
* Manage communications with a serial modem
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JSerialModemDriver extends AbstractModemDriver {
|
||||
|
||||
private static final int ONE_STOP_BIT = 1;
|
||||
static final public int NO_PARITY = 0;
|
||||
static final public int FLOW_CONTROL_RTS_ENABLED = 0x00000001;
|
||||
static final public int FLOW_CONTROL_CTS_ENABLED = 0x00000010;
|
||||
|
||||
static Logger logger = LoggerFactory.getLogger(JSerialModemDriver.class);
|
||||
|
||||
String portName;
|
||||
|
||||
int baudRate;
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
@Nullable
|
||||
SerialPort serialPort;
|
||||
|
||||
public JSerialModemDriver(SerialPortManager serialPortManager, Modem modem, String port, int baudRate) {
|
||||
super(modem);
|
||||
this.portName = port;
|
||||
this.baudRate = baudRate;
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openPort() throws CommunicationException {
|
||||
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(portName);
|
||||
if (portIdentifier == null) {
|
||||
throw new CommunicationException("SMSModem cannot use serial port " + portName);
|
||||
}
|
||||
try {
|
||||
SerialPort openedSerialPort = portIdentifier.open("org.openhab.binding.smsmodem", 2000);
|
||||
openedSerialPort.setSerialPortParams(baudRate, 8, ONE_STOP_BIT, NO_PARITY);
|
||||
openedSerialPort.setFlowControlMode(FLOW_CONTROL_RTS_ENABLED | FLOW_CONTROL_CTS_ENABLED);
|
||||
this.in = openedSerialPort.getInputStream();
|
||||
this.out = openedSerialPort.getOutputStream();
|
||||
serialPort = openedSerialPort;
|
||||
this.pollReader = new PollReader(this, getPortInfo());
|
||||
this.pollReader.setDaemon(true);
|
||||
this.pollReader.start();
|
||||
|
||||
} catch (PortInUseException | UnsupportedCommOperationException | IOException e) {
|
||||
throw new CommunicationException("Cannot open port", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePort() {
|
||||
try {
|
||||
logger.debug("Closing comm port: {}", getPortInfo());
|
||||
this.pollReader.cancel();
|
||||
try {
|
||||
this.pollReader.join();
|
||||
} catch (InterruptedException ex) {
|
||||
logger.debug("PollReader closing exception", ex);
|
||||
}
|
||||
if (in != null) {
|
||||
this.in.close();
|
||||
this.in = null;
|
||||
}
|
||||
if (out != null) {
|
||||
this.out.close();
|
||||
this.out = null;
|
||||
}
|
||||
final SerialPort finalSerialPort = serialPort;
|
||||
if (finalSerialPort != null) {
|
||||
finalSerialPort.close();
|
||||
serialPort = null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Closing port exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPortInfo() {
|
||||
return this.portName + ":" + this.baudRate;
|
||||
}
|
||||
}
|
||||
75
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/PollReader.java
vendored
Normal file
75
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/PollReader.java
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package org.smslib.driver;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.CommunicationException;
|
||||
|
||||
/**
|
||||
* Manage communications with a serial modem
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
public class PollReader extends Thread {
|
||||
|
||||
static Logger logger = LoggerFactory.getLogger(AbstractModemDriver.class);
|
||||
|
||||
private boolean shouldCancel = false;
|
||||
|
||||
private boolean foundClip = false;
|
||||
|
||||
public PollReader(AbstractModemDriver modemDriver, String threadId) {
|
||||
super();
|
||||
this.modemDriver = modemDriver;
|
||||
this.threadId = threadId;
|
||||
}
|
||||
|
||||
private AbstractModemDriver modemDriver;
|
||||
|
||||
private String threadId;
|
||||
|
||||
public void cancel() {
|
||||
this.shouldCancel = true;
|
||||
this.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("Started!");
|
||||
currentThread().setName("OH-binding-smsmodem-" + threadId);
|
||||
while (!this.shouldCancel) {
|
||||
try {
|
||||
while (modemDriver.hasData()) {
|
||||
char c = (char) modemDriver.read();
|
||||
modemDriver.buffer.append(c);
|
||||
if (modemDriver.buffer.indexOf("+CLIP") >= 0) {
|
||||
if (!this.foundClip) {
|
||||
this.foundClip = true;
|
||||
new ClipReader().start();
|
||||
}
|
||||
} else {
|
||||
this.foundClip = false;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Cannot proceed to poll device", e);
|
||||
modemDriver.modem.error();
|
||||
}
|
||||
AbstractModemDriver.countSheeps(Integer.valueOf(modemDriver.getModemSettings("poll_reader")));
|
||||
}
|
||||
logger.debug("Stopped!");
|
||||
}
|
||||
|
||||
public class ClipReader extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
modemDriver.atATWithResponse();
|
||||
} catch (InterruptedException | CommunicationException e) {
|
||||
logger.debug("Cannot proceed to read clip", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
226
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/AbstractMessage.java
vendored
Normal file
226
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/AbstractMessage.java
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.message.OutboundMessage.SentStatus;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractMessage implements Serializable {
|
||||
public enum Encoding {
|
||||
Enc7,
|
||||
Enc8,
|
||||
EncUcs2,
|
||||
EncCustom;
|
||||
}
|
||||
|
||||
public enum DcsClass {
|
||||
None,
|
||||
Flash,
|
||||
Me,
|
||||
Sim,
|
||||
Te
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
Inbound,
|
||||
Outbound,
|
||||
StatusReport
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
Date creationDate = new Date();
|
||||
|
||||
String id = UUID.randomUUID().toString();
|
||||
|
||||
MsIsdn originatorAddress = new MsIsdn();
|
||||
|
||||
@Nullable
|
||||
MsIsdn recipientAddress = new MsIsdn();
|
||||
|
||||
Payload payload = new Payload("");
|
||||
|
||||
Type type = Type.Inbound;
|
||||
|
||||
Encoding encoding = Encoding.Enc7;
|
||||
|
||||
DcsClass dcsClass = DcsClass.Sim;
|
||||
|
||||
String gatewayId = "";
|
||||
|
||||
int sourcePort = -1;
|
||||
|
||||
int destinationPort = -1;
|
||||
|
||||
@Nullable
|
||||
Date sentDate;
|
||||
|
||||
public AbstractMessage() {
|
||||
}
|
||||
|
||||
public AbstractMessage(Type type, MsIsdn originatorAddress, @Nullable MsIsdn recipientAddress,
|
||||
@Nullable Payload payload) {
|
||||
this.type = type;
|
||||
this.originatorAddress = originatorAddress;
|
||||
this.recipientAddress = recipientAddress;
|
||||
if (payload != null) {
|
||||
setPayload(payload);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getCreationDate() {
|
||||
return this.creationDate;
|
||||
}
|
||||
|
||||
public MsIsdn getOriginatorAddress() {
|
||||
return this.originatorAddress;
|
||||
}
|
||||
|
||||
public @Nullable MsIsdn getRecipientAddress() {
|
||||
return this.recipientAddress;
|
||||
}
|
||||
|
||||
public Payload getPayload() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
public void setPayload(Payload payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public Encoding getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
public void setEncoding(Encoding encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public DcsClass getDcsClass() {
|
||||
return this.dcsClass;
|
||||
}
|
||||
|
||||
public void setDcsClass(DcsClass dcsClass) {
|
||||
this.dcsClass = dcsClass;
|
||||
}
|
||||
|
||||
public String getGatewayId() {
|
||||
return this.gatewayId;
|
||||
}
|
||||
|
||||
public void setGatewayId(String gatewayId) {
|
||||
this.gatewayId = gatewayId;
|
||||
}
|
||||
|
||||
public int getSourcePort() {
|
||||
return this.sourcePort;
|
||||
}
|
||||
|
||||
public void setSourcePort(int sourcePort) {
|
||||
this.sourcePort = sourcePort;
|
||||
}
|
||||
|
||||
public int getDestinationPort() {
|
||||
return this.destinationPort;
|
||||
}
|
||||
|
||||
public void setDestinationPort(int destinationPort) {
|
||||
this.destinationPort = destinationPort;
|
||||
}
|
||||
|
||||
public @Nullable Date getSentDate() {
|
||||
Date sentDateFinal = this.sentDate;
|
||||
return (sentDateFinal != null ? (Date) sentDateFinal.clone() : null);
|
||||
}
|
||||
|
||||
public void setSentDate(Date sentDate) {
|
||||
this.sentDate = new Date(sentDate.getTime());
|
||||
}
|
||||
|
||||
public abstract String getSignature();
|
||||
|
||||
public abstract String toShortString();
|
||||
|
||||
public String hashSignature(String s) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(s.getBytes(), 0, s.length());
|
||||
BigInteger i = new BigInteger(1, md.digest());
|
||||
return String.format("%1$032x", i);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot find hash algorithm", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer b = new StringBuffer(1024);
|
||||
b.append(String
|
||||
.format("%n== MESSAGE START ======================================================================%n"));
|
||||
b.append(String.format("CLASS: %s%n", this.getClass().toString()));
|
||||
b.append(String.format("Message ID: %s%n", getId()));
|
||||
b.append(String.format("Message Signature: %s%n", getSignature()));
|
||||
b.append(String.format("Via Gateway: %s%n", getGatewayId()));
|
||||
b.append(String.format("Creation Date: %s%n", getCreationDate()));
|
||||
b.append(String.format("Type: %s%n", getType()));
|
||||
b.append(String.format("Encoding: %s%n", getEncoding()));
|
||||
b.append(String.format("DCS Class: %s%n", getDcsClass()));
|
||||
b.append(String.format("Source Port: %s%n", getSourcePort()));
|
||||
b.append(String.format("Destination Port: %s%n", getDestinationPort()));
|
||||
b.append(String.format("Originator Address: %s%n", getOriginatorAddress()));
|
||||
b.append(String.format("Recipient Address: %s%n", getRecipientAddress()));
|
||||
b.append(String.format("Payload Type: %s%n", payload.getType()));
|
||||
b.append(String.format("Text payload: %s%n", payload.getText() == null ? "null" : payload.getText()));
|
||||
if (this instanceof InboundMessage) {
|
||||
b.append(String.format("Sent Date: %s%n", getSentDate()));
|
||||
b.append(String.format("Memory Storage Location: %s%n", ((InboundMessage) this).getMemLocation()));
|
||||
b.append(String.format("Memory Index: %d%n", ((InboundMessage) this).getMemIndex()));
|
||||
b.append(String.format("Memory MP Index: %s%n", ((InboundMessage) this).getMpMemIndex()));
|
||||
}
|
||||
if (this instanceof OutboundMessage) {
|
||||
b.append(String.format("Sent Date: %s%n",
|
||||
(((OutboundMessage) this).getSentStatus() == SentStatus.Sent ? getSentDate() : "N/A")));
|
||||
String ids = "";
|
||||
for (String opId : ((OutboundMessage) this).getOperatorMessageIds()) {
|
||||
ids += (ids.length() == 0 ? opId : "," + opId);
|
||||
}
|
||||
b.append(String.format("Operator Message IDs: %s%n", ids));
|
||||
b.append(String.format("Status: %s%n", ((OutboundMessage) this).getSentStatus().toString()));
|
||||
b.append(String.format("Failure: %s%n", ((OutboundMessage) this).getFailureCause().toString()));
|
||||
b.append(String.format("Request Delivery Reports: %b%n",
|
||||
((OutboundMessage) this).getRequestDeliveryReport()));
|
||||
}
|
||||
if (this instanceof DeliveryReportMessage) {
|
||||
b.append(String.format("Original Operator Message Id: %s%n",
|
||||
((DeliveryReportMessage) this).getOriginalOperatorMessageId()));
|
||||
b.append(String.format("Delivery Date: %s%n", ((DeliveryReportMessage) this).getOriginalReceivedDate()));
|
||||
b.append(String.format("Delivery Status: %s%n", ((DeliveryReportMessage) this).getDeliveryStatus()));
|
||||
}
|
||||
b.append(String
|
||||
.format("== MESSAGE END ========================================================================%n"));
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
122
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/DeliveryReportMessage.java
vendored
Normal file
122
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/DeliveryReportMessage.java
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.pduUtils.gsm3040.SmsStatusReportPdu;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeliveryReportMessage extends InboundMessage {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum DeliveryStatus {
|
||||
Unknown("U"),
|
||||
Pending("P"),
|
||||
Failed("F"),
|
||||
Delivered("D"),
|
||||
Expired("X"),
|
||||
Error("E");
|
||||
|
||||
private final String shortString;
|
||||
|
||||
private DeliveryStatus(String shortString) {
|
||||
this.shortString = shortString;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return this.shortString;
|
||||
}
|
||||
}
|
||||
|
||||
DeliveryStatus deliveryStatus = DeliveryStatus.Unknown;
|
||||
|
||||
@Nullable
|
||||
String originalOperatorMessageId;
|
||||
|
||||
@Nullable
|
||||
Date originalReceivedDate;
|
||||
|
||||
public DeliveryReportMessage() {
|
||||
super(Type.StatusReport, "", 0);
|
||||
}
|
||||
|
||||
public DeliveryReportMessage(SmsStatusReportPdu pdu, String memLocation, int memIndex) {
|
||||
super(Type.StatusReport, memLocation, memIndex);
|
||||
setOriginalOperatorMessageId(String.valueOf(pdu.getMessageReference()));
|
||||
String address = pdu.getAddress();
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Recipient address cannot be null");
|
||||
}
|
||||
this.recipientAddress = new MsIsdn(address);
|
||||
Date timestamp = pdu.getTimestamp();
|
||||
if (timestamp == null) {
|
||||
throw new IllegalArgumentException("Cannot get timestamp for delivery report message");
|
||||
}
|
||||
setSentDate(timestamp);
|
||||
Date dischargeTime = pdu.getDischargeTime();
|
||||
if (dischargeTime == null) {
|
||||
throw new IllegalArgumentException("Cannot get discharge time for delivery report message");
|
||||
}
|
||||
setOriginalReceivedDate(dischargeTime);
|
||||
int i = pdu.getStatus();
|
||||
setPayload(new Payload(""));
|
||||
if ((i & 0x60) == 0) {
|
||||
this.deliveryStatus = DeliveryStatus.Delivered;
|
||||
} else if ((i & 0x20) == 0x20) {
|
||||
this.deliveryStatus = DeliveryStatus.Pending;
|
||||
} else if ((i & 0x40) == 0x40) {
|
||||
this.deliveryStatus = DeliveryStatus.Expired;
|
||||
} else if ((i & 0x60) == 0x60) {
|
||||
this.deliveryStatus = DeliveryStatus.Expired;
|
||||
} else {
|
||||
this.deliveryStatus = DeliveryStatus.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public DeliveryReportMessage(String messageId, String recipientAddress, String memLocation, int memIndex,
|
||||
Date originalSentDate, Date receivedDate) {
|
||||
super(Type.StatusReport, memLocation, memIndex);
|
||||
setOriginalOperatorMessageId(messageId);
|
||||
this.recipientAddress = new MsIsdn(recipientAddress);
|
||||
setSentDate(originalSentDate);
|
||||
setOriginalReceivedDate(receivedDate);
|
||||
this.deliveryStatus = DeliveryStatus.Unknown;
|
||||
}
|
||||
|
||||
public DeliveryStatus getDeliveryStatus() {
|
||||
return this.deliveryStatus;
|
||||
}
|
||||
|
||||
public @Nullable String getOriginalOperatorMessageId() {
|
||||
return this.originalOperatorMessageId;
|
||||
}
|
||||
|
||||
public void setOriginalOperatorMessageId(String originalOperatorMessageId) {
|
||||
this.originalOperatorMessageId = originalOperatorMessageId;
|
||||
}
|
||||
|
||||
public @Nullable Date getOriginalReceivedDate() {
|
||||
Date finalOriginalReceivedDate = originalReceivedDate;
|
||||
return finalOriginalReceivedDate == null ? null : new Date(finalOriginalReceivedDate.getTime());
|
||||
}
|
||||
|
||||
public void setOriginalReceivedDate(Date originalReceivedDate) {
|
||||
this.originalReceivedDate = new Date(originalReceivedDate.getTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSignature() {
|
||||
return hashSignature(String.format("%s-%s-%s-%s", getOriginatorAddress(), getOriginalOperatorMessageId(),
|
||||
getOriginalReceivedDate(), getDeliveryStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return String.format("[%s @ %s = %s @ %s]", getId(), getRecipientAddress(), getDeliveryStatus(),
|
||||
getOriginalReceivedDate());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.pduUtils.gsm3040.SmsDeliveryPdu;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InboundBinaryMessage extends InboundMessage {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InboundBinaryMessage(SmsDeliveryPdu pdu, String memLocation, int memIndex) {
|
||||
super(pdu, memLocation, memIndex);
|
||||
setPayload(new Payload(pdu.getUserDataAsBytes()));
|
||||
}
|
||||
}
|
||||
164
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/InboundMessage.java
vendored
Normal file
164
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/InboundMessage.java
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.pduUtils.gsm3040.PduUtils;
|
||||
import org.smslib.pduUtils.gsm3040.SmsDeliveryPdu;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InboundMessage extends AbstractMessage {
|
||||
static Logger logger = LoggerFactory.getLogger(InboundMessage.class);
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
int memIndex;
|
||||
|
||||
String memLocation;
|
||||
|
||||
int mpRefNo;
|
||||
|
||||
int mpMaxNo;
|
||||
|
||||
int mpSeqNo;
|
||||
|
||||
String mpMemIndex = "";
|
||||
|
||||
@Nullable
|
||||
MsIsdn smscNumber;
|
||||
|
||||
boolean endsWithMultiChar;
|
||||
|
||||
public InboundMessage(SmsDeliveryPdu pdu, String memLocation, int memIndex) {
|
||||
super(Type.Inbound, new MsIsdn(pdu.getAddress()), null, null);
|
||||
this.memLocation = memLocation;
|
||||
this.memIndex = memIndex;
|
||||
this.mpRefNo = 0;
|
||||
this.mpMaxNo = 0;
|
||||
this.mpSeqNo = 0;
|
||||
setMpMemIndex(-1);
|
||||
int dcsEncoding = PduUtils.extractDcsEncoding(pdu.getDataCodingScheme());
|
||||
switch (dcsEncoding) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
setEncoding(Encoding.Enc7);
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
setEncoding(Encoding.Enc8);
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
setEncoding(Encoding.EncUcs2);
|
||||
break;
|
||||
default:
|
||||
logger.error("Unknown DCS Encoding: {}", dcsEncoding);
|
||||
}
|
||||
Date timestamp = pdu.getTimestamp();
|
||||
if (timestamp != null) {
|
||||
setSentDate(timestamp);
|
||||
}
|
||||
this.smscNumber = new MsIsdn(pdu.getSmscAddress());
|
||||
setPayload(new Payload(pdu.getDecodedText()));
|
||||
if (pdu.isConcatMessage()) {
|
||||
this.mpRefNo = pdu.getMpRefNo();
|
||||
this.mpMaxNo = pdu.getMpMaxNo();
|
||||
this.mpSeqNo = pdu.getMpSeqNo();
|
||||
}
|
||||
if (pdu.isPortedMessage()) {
|
||||
setSourcePort(pdu.getSrcPort());
|
||||
setDestinationPort(pdu.getDestPort());
|
||||
}
|
||||
if (getEncoding() == Encoding.Enc7) {
|
||||
byte[] udData = pdu.getUDData();
|
||||
if (udData == null) {
|
||||
throw new IllegalArgumentException("Cannot encode udData to construct message");
|
||||
}
|
||||
byte[] temp = PduUtils.encodedSeptetsToUnencodedSeptets(udData);
|
||||
if (temp.length == 0) {
|
||||
this.endsWithMultiChar = false;
|
||||
} else if (temp[temp.length - 1] == 0x1b) {
|
||||
this.endsWithMultiChar = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InboundMessage(String originator, String text, Date sentDate, String memLocation, int memIndex) {
|
||||
super(Type.Inbound, new MsIsdn(originator), null, new Payload(text));
|
||||
this.memLocation = memLocation;
|
||||
this.memIndex = memIndex;
|
||||
this.sentDate = new Date(sentDate.getTime());
|
||||
}
|
||||
|
||||
public InboundMessage(Type type, String memLocation, int memIndex) {
|
||||
super(type, new MsIsdn(), null, null);
|
||||
this.memIndex = memIndex;
|
||||
this.memLocation = memLocation;
|
||||
this.mpRefNo = 0;
|
||||
this.mpMaxNo = 0;
|
||||
this.mpSeqNo = 0;
|
||||
setMpMemIndex(-1);
|
||||
this.smscNumber = new MsIsdn();
|
||||
}
|
||||
|
||||
public int getMemIndex() {
|
||||
return this.memIndex;
|
||||
}
|
||||
|
||||
public void setMemIndex(int memIndex) {
|
||||
this.memIndex = memIndex;
|
||||
}
|
||||
|
||||
public String getMemLocation() {
|
||||
return this.memLocation;
|
||||
}
|
||||
|
||||
public int getMpMaxNo() {
|
||||
return this.mpMaxNo;
|
||||
}
|
||||
|
||||
public String getMpMemIndex() {
|
||||
return this.mpMemIndex;
|
||||
}
|
||||
|
||||
public void setMpMemIndex(int myMpMemIndex) {
|
||||
if (myMpMemIndex == -1) {
|
||||
this.mpMemIndex = "";
|
||||
} else {
|
||||
this.mpMemIndex += (this.mpMemIndex.length() == 0 ? "" : ",") + myMpMemIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMpRefNo() {
|
||||
return this.mpRefNo;
|
||||
}
|
||||
|
||||
public int getMpSeqNo() {
|
||||
return this.mpSeqNo;
|
||||
}
|
||||
|
||||
public void setMpSeqNo(int myMpSeqNo) {
|
||||
this.mpSeqNo = myMpSeqNo;
|
||||
}
|
||||
|
||||
public boolean getEndsWithMultiChar() {
|
||||
return this.endsWithMultiChar;
|
||||
}
|
||||
|
||||
public void setEndsWithMultiChar(boolean b) {
|
||||
this.endsWithMultiChar = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSignature() {
|
||||
return hashSignature(String.format("%s-%s-%s", getOriginatorAddress(), getSentDate(), payload.getText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return String.format("[%s @ %s]", getId(), getOriginatorAddress());
|
||||
}
|
||||
}
|
||||
89
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/MsIsdn.java
vendored
Normal file
89
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/MsIsdn.java
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MsIsdn {
|
||||
public enum Type {
|
||||
National,
|
||||
International,
|
||||
Text,
|
||||
Void
|
||||
}
|
||||
|
||||
String address;
|
||||
|
||||
Type type = Type.International;
|
||||
|
||||
public MsIsdn() {
|
||||
this("", Type.Void);
|
||||
}
|
||||
|
||||
public MsIsdn(@Nullable String number) {
|
||||
if (number == null) {
|
||||
throw new IllegalArgumentException("Number cannot be null");
|
||||
}
|
||||
if (number.length() > 0 && number.charAt(0) == '+') {
|
||||
this.address = number.substring(1);
|
||||
this.type = Type.International;
|
||||
} else {
|
||||
this.address = number;
|
||||
this.type = typeOf(number);
|
||||
}
|
||||
}
|
||||
|
||||
public MsIsdn(String address, Type type) {
|
||||
this.address = address;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public MsIsdn(MsIsdn msisdn) {
|
||||
this.type = msisdn.getType();
|
||||
this.address = msisdn.getAddress();
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof MsIsdn)) {
|
||||
return false;
|
||||
}
|
||||
return (this.address.equalsIgnoreCase(((MsIsdn) o).getAddress()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%s / %s]", getType(), getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.address.hashCode() + (15 * this.type.hashCode());
|
||||
}
|
||||
|
||||
private static Type typeOf(String number) {
|
||||
if (number.trim().length() == 0) {
|
||||
return Type.Void;
|
||||
}
|
||||
for (int i = 0; i < number.length(); i++) {
|
||||
if (!Character.isDigit(number.charAt(i))) {
|
||||
return Type.Text;
|
||||
}
|
||||
}
|
||||
return Type.International;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OutboundBinaryMessage extends OutboundMessage {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public OutboundBinaryMessage() {
|
||||
}
|
||||
|
||||
public OutboundBinaryMessage(MsIsdn originatorAddress, MsIsdn recipientAddress, byte[] data) {
|
||||
super(originatorAddress, recipientAddress, new Payload(data));
|
||||
setEncoding(Encoding.Enc8);
|
||||
}
|
||||
}
|
||||
193
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/OutboundMessage.java
vendored
Normal file
193
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/OutboundMessage.java
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.pduUtils.gsm3040.PduFactory;
|
||||
import org.smslib.pduUtils.gsm3040.PduGenerator;
|
||||
import org.smslib.pduUtils.gsm3040.PduUtils;
|
||||
import org.smslib.pduUtils.gsm3040.SmsSubmitPdu;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElementFactory;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OutboundMessage extends AbstractMessage {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum SentStatus {
|
||||
Sent("S"),
|
||||
Unsent("U"),
|
||||
Queued("Q"),
|
||||
Failed("F");
|
||||
|
||||
private final String shortString;
|
||||
|
||||
private SentStatus(String shortString) {
|
||||
this.shortString = shortString;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return this.shortString;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FailureCause {
|
||||
None("00"),
|
||||
BadNumber("01"),
|
||||
BadFormat("02"),
|
||||
GatewayFailure("03"),
|
||||
AuthFailure("04"),
|
||||
NoCredit("05"),
|
||||
OverQuota("06"),
|
||||
NoRoute("07"),
|
||||
Unavailable("08"),
|
||||
HttpError("09"),
|
||||
UnknownFailure("10"),
|
||||
Cancelled("11"),
|
||||
NoService("12"),
|
||||
MissingParms("13");
|
||||
|
||||
private final String shortString;
|
||||
|
||||
private FailureCause(String shortString) {
|
||||
this.shortString = shortString;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return this.shortString;
|
||||
}
|
||||
}
|
||||
|
||||
SentStatus sentStatus = SentStatus.Unsent;
|
||||
|
||||
FailureCause failureCause = FailureCause.None;
|
||||
|
||||
List<String> operatorMessageIds = new ArrayList<>();
|
||||
|
||||
boolean requestDeliveryReport = false;
|
||||
|
||||
public OutboundMessage() {
|
||||
}
|
||||
|
||||
public OutboundMessage(MsIsdn originatorAddress, MsIsdn recipientAddress, Payload payload) {
|
||||
super(Type.Outbound, originatorAddress, recipientAddress, payload);
|
||||
}
|
||||
|
||||
public OutboundMessage(String recipientAddress, String text) {
|
||||
this(new MsIsdn(""), new MsIsdn(recipientAddress), new Payload(text));
|
||||
}
|
||||
|
||||
public SentStatus getSentStatus() {
|
||||
return this.sentStatus;
|
||||
}
|
||||
|
||||
public void setSentStatus(SentStatus sentStatus) {
|
||||
this.sentStatus = sentStatus;
|
||||
}
|
||||
|
||||
public FailureCause getFailureCause() {
|
||||
return this.failureCause;
|
||||
}
|
||||
|
||||
public void setFailureCause(FailureCause failureCode) {
|
||||
this.failureCause = failureCode;
|
||||
}
|
||||
|
||||
public List<String> getOperatorMessageIds() {
|
||||
return this.operatorMessageIds;
|
||||
}
|
||||
|
||||
public boolean getRequestDeliveryReport() {
|
||||
return this.requestDeliveryReport;
|
||||
}
|
||||
|
||||
public void setRequestDeliveryReport(boolean requestDeliveryReport) {
|
||||
this.requestDeliveryReport = requestDeliveryReport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return String.format("[%s @ %s]", getId(), getRecipientAddress());
|
||||
}
|
||||
|
||||
public List<String> getPdus(MsIsdn smscNumber, int mpRefNo) {
|
||||
PduGenerator pduGenerator = new PduGenerator();
|
||||
SmsSubmitPdu pdu = createPduObject(getRequestDeliveryReport());
|
||||
initPduObject(pdu, smscNumber);
|
||||
return pduGenerator.generatePduList(pdu, mpRefNo);
|
||||
}
|
||||
|
||||
protected SmsSubmitPdu createPduObject(boolean extRequestDeliveryReport) {
|
||||
return (extRequestDeliveryReport ? PduFactory.newSmsSubmitPdu(PduUtils.TP_SRR_REPORT | PduUtils.TP_VPF_INTEGER)
|
||||
: PduFactory.newSmsSubmitPdu());
|
||||
}
|
||||
|
||||
protected void initPduObject(SmsSubmitPdu pdu, MsIsdn smscNumber) {
|
||||
if ((getSourcePort() > -1) && (getDestinationPort() > -1)) {
|
||||
pdu.addInformationElement(
|
||||
InformationElementFactory.generatePortInfo(getDestinationPort(), getSourcePort()));
|
||||
}
|
||||
String smscNumberForLengthCheck = smscNumber.getAddress();
|
||||
pdu.setSmscInfoLength(
|
||||
1 + (smscNumberForLengthCheck.length() / 2) + ((smscNumberForLengthCheck.length() % 2 == 1) ? 1 : 0));
|
||||
pdu.setSmscAddress(smscNumber.getAddress());
|
||||
pdu.setSmscAddressType(PduUtils.getAddressTypeFor(smscNumber));
|
||||
pdu.setMessageReference(0);
|
||||
MsIsdn finalRecipientAddress = recipientAddress;
|
||||
if (finalRecipientAddress == null) {
|
||||
throw new UnrecoverableSmslibException("Recipient adress cannot be null");
|
||||
}
|
||||
pdu.setAddress(finalRecipientAddress);
|
||||
MsIsdn recipientAddressFinal = this.recipientAddress;
|
||||
if (recipientAddressFinal == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot set address type with no recipient");
|
||||
}
|
||||
pdu.setAddressType(PduUtils.getAddressTypeFor(recipientAddressFinal));
|
||||
pdu.setProtocolIdentifier(0);
|
||||
if (!pdu.isBinary()) {
|
||||
int dcs = 0;
|
||||
if (getEncoding() == Encoding.Enc7) {
|
||||
dcs = PduUtils.DCS_ENCODING_7BIT;
|
||||
} else if (getEncoding() == Encoding.Enc8) {
|
||||
dcs = PduUtils.DCS_ENCODING_8BIT;
|
||||
} else if (getEncoding() == Encoding.EncUcs2) {
|
||||
dcs = PduUtils.DCS_ENCODING_UCS2;
|
||||
} else if (getEncoding() == Encoding.EncCustom) {
|
||||
dcs = PduUtils.DCS_ENCODING_7BIT;
|
||||
}
|
||||
if (getDcsClass() == DcsClass.Flash) {
|
||||
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_FLASH;
|
||||
} else if (getDcsClass() == DcsClass.Me) {
|
||||
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_ME;
|
||||
} else if (getDcsClass() == DcsClass.Sim) {
|
||||
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_SIM;
|
||||
} else if (getDcsClass() == DcsClass.Te) {
|
||||
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_TE;
|
||||
}
|
||||
pdu.setDataCodingScheme(dcs);
|
||||
}
|
||||
pdu.setValidityPeriod(0);
|
||||
if (getEncoding() == Encoding.Enc8) {
|
||||
byte[] bytes = getPayload().getBytes();
|
||||
if (bytes == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot init pdu object, wrong payload");
|
||||
}
|
||||
pdu.setDataBytes(bytes);
|
||||
} else {
|
||||
String text = getPayload().getText();
|
||||
if (text == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot init pdu object, wrong payload");
|
||||
}
|
||||
pdu.setDecodedText(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSignature() {
|
||||
return hashSignature(String.format("%s-%s", getRecipientAddress(), getId()));
|
||||
}
|
||||
}
|
||||
54
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/Payload.java
vendored
Normal file
54
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/Payload.java
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Payload {
|
||||
public enum Type {
|
||||
Text,
|
||||
Binary
|
||||
}
|
||||
|
||||
private @Nullable String textData;
|
||||
|
||||
private byte @Nullable [] binaryData;
|
||||
|
||||
private Type type;
|
||||
|
||||
public Payload(String data) {
|
||||
this.type = Type.Text;
|
||||
this.textData = data;
|
||||
}
|
||||
|
||||
public Payload(byte[] data) {
|
||||
this.type = Type.Binary;
|
||||
this.binaryData = data.clone();
|
||||
}
|
||||
|
||||
public Payload(Payload p) {
|
||||
this.type = p.getType();
|
||||
this.textData = (this.type == Type.Text ? p.getText() : "");
|
||||
byte[] bytes = p.getBytes();
|
||||
this.binaryData = (this.type == Type.Binary && bytes != null ? bytes.clone() : null);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public @Nullable String getText() {
|
||||
return (this.type == Type.Text ? this.textData : null);
|
||||
}
|
||||
|
||||
public byte @Nullable [] getBytes() {
|
||||
return (this.type == Type.Binary ? this.binaryData : null);
|
||||
}
|
||||
|
||||
public boolean isMultipart() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
520
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/Pdu.java
vendored
Normal file
520
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/Pdu.java
vendored
Normal file
@@ -0,0 +1,520 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.message.MsIsdn;
|
||||
import org.smslib.message.MsIsdn.Type;
|
||||
import org.smslib.pduUtils.gsm3040.ie.ConcatInformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.PortInformationElement;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class Pdu {
|
||||
// PDU class
|
||||
// this class holds directly usable data only
|
||||
// - all lengths are ints
|
||||
// - dates and strings are already decoded
|
||||
// - byte[] for binary data that can interpreted later
|
||||
// an object of this type is created via a PduParser
|
||||
// or created raw, has its field set and supplied to a PduGenerator
|
||||
// ==================================================
|
||||
// SMSC INFO
|
||||
// ==================================================
|
||||
private int smscInfoLength;
|
||||
|
||||
private int smscAddressType;
|
||||
|
||||
@Nullable
|
||||
private String smscAddress;
|
||||
|
||||
public int getSmscInfoLength() {
|
||||
return this.smscInfoLength;
|
||||
}
|
||||
|
||||
public void setSmscInfoLength(int smscInfoLength) {
|
||||
this.smscInfoLength = smscInfoLength;
|
||||
}
|
||||
|
||||
public void setSmscAddressType(int smscAddressType) {
|
||||
this.smscAddressType = PduUtils.createAddressType(smscAddressType);
|
||||
}
|
||||
|
||||
public int getSmscAddressType() {
|
||||
return this.smscAddressType;
|
||||
}
|
||||
|
||||
public void setSmscAddress(@Nullable String smscAddress) {
|
||||
if (smscAddress == null || "".equals(smscAddress)) {
|
||||
this.smscAddress = null;
|
||||
this.smscAddressType = 0;
|
||||
this.smscInfoLength = 0;
|
||||
return;
|
||||
}
|
||||
// strip the + since it is not needed
|
||||
if (smscAddress.startsWith("+")) {
|
||||
this.smscAddress = smscAddress.substring(1);
|
||||
} else {
|
||||
this.smscAddress = smscAddress;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getSmscAddress() {
|
||||
return this.smscAddress;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// FIRST OCTET
|
||||
// ==================================================
|
||||
private int firstOctet = 0;
|
||||
|
||||
public int getFirstOctet() {
|
||||
return this.firstOctet;
|
||||
}
|
||||
|
||||
public void setFirstOctet(int value) {
|
||||
this.firstOctet = value;
|
||||
}
|
||||
|
||||
protected void setFirstOctetField(int fieldName, int fieldValue, int[] allowedValues) {
|
||||
for (int value : allowedValues) {
|
||||
if (value == fieldValue) {
|
||||
// clear the bits for this field
|
||||
this.firstOctet &= fieldName;
|
||||
// copy the new bits on to it
|
||||
this.firstOctet |= fieldValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid value for fieldName.");
|
||||
}
|
||||
|
||||
protected int getFirstOctetField(int fieldName) {
|
||||
return this.firstOctet & ~fieldName;
|
||||
}
|
||||
|
||||
protected void checkTpMti(int[] allowedTypes) {
|
||||
int tpMti = getTpMti();
|
||||
for (int type : allowedTypes) {
|
||||
if (tpMti == type) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid message type : " + getTpMti());
|
||||
}
|
||||
|
||||
public int getTpMti() {
|
||||
return getFirstOctetField(PduUtils.TP_MTI_MASK);
|
||||
}
|
||||
|
||||
public void setTpUdhi(int value) {
|
||||
setFirstOctetField(PduUtils.TP_UDHI_MASK, value,
|
||||
new int[] { PduUtils.TP_UDHI_NO_UDH, PduUtils.TP_UDHI_WITH_UDH });
|
||||
}
|
||||
|
||||
public boolean hasTpUdhi() {
|
||||
return getFirstOctetField(PduUtils.TP_UDHI_MASK) == PduUtils.TP_UDHI_WITH_UDH;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// PROTOCOL IDENTIFIER
|
||||
// ==================================================
|
||||
// usually just 0x00 for regular SMS
|
||||
private int protocolIdentifier = 0x00;
|
||||
|
||||
public void setProtocolIdentifier(int protocolIdentifier) {
|
||||
this.protocolIdentifier = protocolIdentifier;
|
||||
}
|
||||
|
||||
public int getProtocolIdentifier() {
|
||||
return this.protocolIdentifier;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// DATA CODING SCHEME
|
||||
// ==================================================
|
||||
// usually just 0x00 for default GSM alphabet, phase 2
|
||||
private int dataCodingScheme = 0x00;
|
||||
|
||||
public void setDataCodingScheme(int encoding) {
|
||||
switch (encoding & ~PduUtils.DCS_ENCODING_MASK) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid encoding value: " + PduUtils.byteToPdu(encoding));
|
||||
}
|
||||
this.dataCodingScheme = encoding;
|
||||
}
|
||||
|
||||
public int getDataCodingScheme() {
|
||||
return this.dataCodingScheme;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// TYPE-OF-ADDRESS
|
||||
// ==================================================
|
||||
private int addressType;
|
||||
|
||||
public int getAddressType() {
|
||||
return this.addressType;
|
||||
}
|
||||
|
||||
public void setAddressType(int addressType) {
|
||||
// insure last bit is always set
|
||||
this.addressType = PduUtils.createAddressType(addressType);
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// ADDRESS
|
||||
// ==================================================
|
||||
// swapped BCD-format for numbers
|
||||
// 7-bit GSM string for alphanumeric
|
||||
private @Nullable String address;
|
||||
|
||||
public void setAddress(MsIsdn address) {
|
||||
if (address.getType() == Type.Void) {
|
||||
this.address = "";
|
||||
} else {
|
||||
this.address = address.getAddress();
|
||||
}
|
||||
setAddressType(PduUtils.getAddressTypeFor(address));
|
||||
}
|
||||
|
||||
public @Nullable String getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// USER DATA SECTION
|
||||
// ==================================================
|
||||
// this is still needs to be stored since it does not always represent
|
||||
// length in octets, for 7-bit encoding this is length in SEPTETS
|
||||
// NOTE: udData.length may not equal udLength if 7-bit encoding is used
|
||||
private int udLength;
|
||||
|
||||
private byte @Nullable [] udData;
|
||||
|
||||
public int getUDLength() {
|
||||
return this.udLength;
|
||||
}
|
||||
|
||||
public void setUDLength(int udLength) {
|
||||
this.udLength = udLength;
|
||||
}
|
||||
|
||||
public byte @Nullable [] getUDData() {
|
||||
return this.udData;
|
||||
}
|
||||
|
||||
// NOTE: udData DOES NOT include the octet with the length
|
||||
public void setUDData(byte[] udData) {
|
||||
this.udData = udData;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// USER DATA HEADER
|
||||
// ==================================================
|
||||
// all methods accessing UDH specific methods require the UDHI to be set
|
||||
// or else an exception will result
|
||||
private static final int UDH_CHECK_MODE_ADD_IF_NONE = 0;
|
||||
|
||||
private static final int UDH_CHECK_MODE_EXCEPTION_IF_NONE = 1;
|
||||
|
||||
private static final int UDH_CHECK_MODE_IGNORE_IF_NONE = 2;
|
||||
|
||||
private void checkForUDHI(int udhCheckMode) {
|
||||
if (!hasTpUdhi()) {
|
||||
switch (udhCheckMode) {
|
||||
case UDH_CHECK_MODE_EXCEPTION_IF_NONE:
|
||||
throw new IllegalStateException("PDU does not have a UDHI in the first octet");
|
||||
case UDH_CHECK_MODE_ADD_IF_NONE:
|
||||
setTpUdhi(PduUtils.TP_UDHI_WITH_UDH);
|
||||
break;
|
||||
case UDH_CHECK_MODE_IGNORE_IF_NONE:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid UDH check mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalUDHLength() {
|
||||
int udhLength = getUDHLength();
|
||||
if (udhLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
// also takes into account the field holding the length
|
||||
// it self
|
||||
return udhLength + 1;
|
||||
}
|
||||
|
||||
public int getUDHLength() {
|
||||
// compute based on the IEs
|
||||
int udhLength = 0;
|
||||
for (InformationElement ie : this.ieMap.values()) {
|
||||
// length + 2 to account for the octet holding the IE length and id
|
||||
udhLength = udhLength + ie.getLength() + 2;
|
||||
}
|
||||
return udhLength;
|
||||
}
|
||||
|
||||
public byte @Nullable [] getUDHData() {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
int totalUdhLength = getTotalUDHLength();
|
||||
if (totalUdhLength == 0) {
|
||||
return null;
|
||||
}
|
||||
byte[] retVal = new byte[totalUdhLength];
|
||||
byte[] finalUdData = this.udData;
|
||||
if (finalUdData != null) {
|
||||
System.arraycopy(finalUdData, 0, retVal, 0, totalUdhLength);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot get udhd data because udData is null");
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// UDH portion of UD if UDHI is present
|
||||
// only Concat and Port info will be treated specially
|
||||
// other IEs will have to get extracted from the map manually and parsed
|
||||
private HashMap<Integer, InformationElement> ieMap = new HashMap<>();
|
||||
|
||||
private ArrayList<InformationElement> ieList = new ArrayList<>();
|
||||
|
||||
public void addInformationElement(InformationElement ie) {
|
||||
checkForUDHI(UDH_CHECK_MODE_ADD_IF_NONE);
|
||||
this.ieMap.put(ie.getIdentifier(), ie);
|
||||
this.ieList.add(ie);
|
||||
}
|
||||
|
||||
public @Nullable InformationElement getInformationElement(int iei) {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
return this.ieMap.get(iei);
|
||||
}
|
||||
|
||||
// this is only used in the parser generator
|
||||
public Iterator<InformationElement> getInformationElements() {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
return this.ieList.iterator();
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// CONCAT INFO
|
||||
// ==================================================
|
||||
public boolean isConcatMessage() {
|
||||
// check if iei 0x00 or 0x08 is present
|
||||
return (getConcatInfo() != null);
|
||||
}
|
||||
|
||||
public @Nullable ConcatInformationElement getConcatInfo() {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
ConcatInformationElement concat = (ConcatInformationElement) getInformationElement(
|
||||
ConcatInformationElement.CONCAT_8BIT_REF);
|
||||
if (concat == null) {
|
||||
concat = (ConcatInformationElement) getInformationElement(ConcatInformationElement.CONCAT_16BIT_REF);
|
||||
}
|
||||
return concat;
|
||||
}
|
||||
|
||||
public int getMpRefNo() {
|
||||
ConcatInformationElement concat = getConcatInfo();
|
||||
if (concat != null) {
|
||||
return concat.getMpRefNo();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getMpMaxNo() {
|
||||
ConcatInformationElement concat = getConcatInfo();
|
||||
if (concat != null) {
|
||||
return concat.getMpMaxNo();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int getMpSeqNo() {
|
||||
ConcatInformationElement concat = getConcatInfo();
|
||||
if (concat != null) {
|
||||
return concat.getMpSeqNo();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// PORT DATA
|
||||
// ==================================================
|
||||
public boolean isPortedMessage() {
|
||||
// check if iei 0x05 is present
|
||||
return (getPortInfo() != null);
|
||||
}
|
||||
|
||||
private @Nullable PortInformationElement getPortInfo() {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
return (PortInformationElement) getInformationElement(PortInformationElement.PORT_16BIT);
|
||||
}
|
||||
|
||||
public int getDestPort() {
|
||||
PortInformationElement portIe = getPortInfo();
|
||||
if (portIe == null) {
|
||||
return -1;
|
||||
}
|
||||
return portIe.getDestPort();
|
||||
}
|
||||
|
||||
public int getSrcPort() {
|
||||
PortInformationElement portIe = getPortInfo();
|
||||
if (portIe == null) {
|
||||
return -1;
|
||||
}
|
||||
return portIe.getSrcPort();
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// NON-UDH DATA
|
||||
// ==================================================
|
||||
// UD minus the UDH portion, same as userData if
|
||||
// no UDH
|
||||
// these fields store data for the generation step
|
||||
private @Nullable String decodedText;
|
||||
|
||||
private byte @Nullable [] dataBytes;
|
||||
|
||||
public void setDataBytes(byte[] dataBytes) {
|
||||
this.dataBytes = dataBytes;
|
||||
this.decodedText = null;
|
||||
// clear the encoding bits for this field 8-bit/data
|
||||
// this.dataCodingScheme &= PduUtils.DCS_ENCODING_MASK;
|
||||
// this.dataCodingScheme |= PduUtils.DCS_ENCODING_8BIT;
|
||||
// this.dataCodingScheme |= PduUtils.DCS_CODING_GROUP_DATA;
|
||||
}
|
||||
|
||||
public byte @Nullable [] getDataBytes() {
|
||||
return this.dataBytes;
|
||||
}
|
||||
|
||||
public boolean isBinary() {
|
||||
// use the DCS coding group or 8bit encoding
|
||||
// Changed following line according to http://code.google.com/p/smslib/issues/detail?id=187
|
||||
// return ((this.dataCodingScheme & PduUtils.DCS_CODING_GROUP_DATA) == PduUtils.DCS_CODING_GROUP_DATA ||
|
||||
// (this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT);
|
||||
if ((this.dataCodingScheme & PduUtils.DCS_CODING_GROUP_DATA) == PduUtils.DCS_CODING_GROUP_DATA
|
||||
|| (this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT) {
|
||||
if ((this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT) {
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
public void setDecodedText(String decodedText) {
|
||||
this.decodedText = decodedText;
|
||||
this.dataBytes = null;
|
||||
// check if existing DCS indicates a flash message
|
||||
boolean flash = false;
|
||||
if (PduUtils.extractDcsFlash(this.dataCodingScheme) == PduUtils.DCS_MESSAGE_CLASS_FLASH) {
|
||||
flash = true;
|
||||
}
|
||||
// clears the coding group to be text again in case it was originally binary
|
||||
this.dataCodingScheme &= PduUtils.DCS_CODING_GROUP_MASK;
|
||||
// set the flash bit back since the above would clear it
|
||||
if (flash) {
|
||||
this.dataCodingScheme = this.dataCodingScheme | PduUtils.DCS_MESSAGE_CLASS_FLASH;
|
||||
}
|
||||
}
|
||||
|
||||
public String getDecodedText() {
|
||||
// this should be try-catched in case the ud data is
|
||||
// actually binary and might cause a decoding exception
|
||||
String decodedTextFinal = this.decodedText;
|
||||
if (decodedTextFinal != null) {
|
||||
return decodedTextFinal;
|
||||
}
|
||||
if (this.udData == null) {
|
||||
throw new UnrecoverableSmslibException("No udData to decode");
|
||||
}
|
||||
return decodeNonUDHDataAsString();
|
||||
}
|
||||
|
||||
public byte[] getUserDataAsBytes() {
|
||||
byte[] udDataFinal = this.udData;
|
||||
if (udDataFinal == null) {
|
||||
throw new UnrecoverableSmslibException("udData cannot be null");
|
||||
}
|
||||
int remainingLength = udDataFinal.length - (getTotalUDHLength());
|
||||
byte[] retVal = new byte[remainingLength];
|
||||
byte[] finalUdData = udData;
|
||||
if (finalUdData != null) {
|
||||
System.arraycopy(finalUdData, getTotalUDHLength(), retVal, 0, remainingLength);
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Cannot get user data because udData is null");
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private String decodeNonUDHDataAsString() {
|
||||
// convert PDU to text depending on the encoding
|
||||
// must also take into account the octet holding the length
|
||||
byte[] udhDataFinal = getUDHData();
|
||||
byte[] udDataFinal = this.udData;
|
||||
if (udDataFinal == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot decode with udData null");
|
||||
}
|
||||
switch (PduUtils.extractDcsEncoding(getDataCodingScheme())) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
// unpack all septets to octets with MSB holes
|
||||
byte[] septets = PduUtils.encodedSeptetsToUnencodedSeptets(udDataFinal);
|
||||
int septetUDHLength = 0;
|
||||
if (udhDataFinal != null) {
|
||||
// work out how much of the UD is UDH
|
||||
septetUDHLength = udhDataFinal.length * 8 / 7;
|
||||
if (udhDataFinal.length * 8 % 7 > 0) {
|
||||
septetUDHLength++;
|
||||
}
|
||||
}
|
||||
byte[] septetsNoUDH = new byte[this.udLength - septetUDHLength];
|
||||
// src, srcStart, dest, destStart, length
|
||||
System.arraycopy(septets, septetUDHLength, septetsNoUDH, 0, septetsNoUDH.length);
|
||||
return PduUtils.unencodedSeptetsToString(septetsNoUDH);
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
return PduUtils.decode8bitEncoding(udhDataFinal, udDataFinal);
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
return PduUtils.decodeUcs2Encoding(udhDataFinal, udDataFinal);
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid dataCodingScheme: " + getDataCodingScheme());
|
||||
}
|
||||
|
||||
protected String formatTimestamp(Calendar timestamp) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat();
|
||||
sdf.applyPattern("EEE dd-MMM-yyyy HH:mm:ss z");
|
||||
sdf.setTimeZone(timestamp.getTimeZone());
|
||||
return sdf.format(timestamp.getTime());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PduFactory {
|
||||
public static SmsSubmitPdu newSmsSubmitPdu() {
|
||||
// apply defaults
|
||||
int additionalFields = PduUtils.TP_RD_ACCEPT_DUPLICATES | PduUtils.TP_VPF_INTEGER;
|
||||
return newSmsSubmitPdu(additionalFields);
|
||||
}
|
||||
|
||||
public static SmsSubmitPdu newSmsSubmitPdu(int additionalFields) {
|
||||
// remove any TP_MTI values
|
||||
int firstOctet = PduUtils.TP_MTI_SMS_SUBMIT | additionalFields;
|
||||
return (SmsSubmitPdu) createPdu(firstOctet);
|
||||
}
|
||||
|
||||
private static int getFirstOctetField(int firstOctet, int fieldName) {
|
||||
return firstOctet & ~fieldName;
|
||||
}
|
||||
|
||||
// used to determine what Pdu to use based on the first octet
|
||||
// this is the only way to instantiate a Pdu object
|
||||
public static Pdu createPdu(int firstOctet) {
|
||||
Pdu pdu = null;
|
||||
int messageType = getFirstOctetField(firstOctet, PduUtils.TP_MTI_MASK);
|
||||
switch (messageType) {
|
||||
case PduUtils.TP_MTI_SMS_DELIVER:
|
||||
pdu = new SmsDeliveryPdu();
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_STATUS_REPORT:
|
||||
pdu = new SmsStatusReportPdu();
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_SUBMIT:
|
||||
pdu = new SmsSubmitPdu();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid TP-MTI value: " + messageType);
|
||||
}
|
||||
// once set, you can't change it
|
||||
// this method is only available in this package
|
||||
pdu.setFirstOctet(firstOctet);
|
||||
return pdu;
|
||||
}
|
||||
}
|
||||
545
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduGenerator.java
vendored
Normal file
545
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduGenerator.java
vendored
Normal file
@@ -0,0 +1,545 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.pduUtils.gsm3040.ie.ConcatInformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElementFactory;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PduGenerator {
|
||||
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
private int firstOctetPosition = -1;
|
||||
|
||||
private boolean updateFirstOctet = false;
|
||||
|
||||
protected void writeSmscInfo(Pdu pdu) {
|
||||
String smscAddress = pdu.getSmscAddress();
|
||||
if (smscAddress != null) {
|
||||
writeBCDAddress(smscAddress, pdu.getSmscAddressType(), pdu.getSmscInfoLength());
|
||||
} else {
|
||||
writeByte(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeFirstOctet(Pdu pdu) {
|
||||
// store the position in case it will need to be updated later
|
||||
this.firstOctetPosition = pdu.getSmscInfoLength() + 1;
|
||||
writeByte(pdu.getFirstOctet());
|
||||
}
|
||||
|
||||
// validity period conversion from hours to the proper integer
|
||||
protected void writeValidityPeriodInteger(int validityPeriod) {
|
||||
if (validityPeriod == -1) {
|
||||
this.baos.write(0xFF);
|
||||
} else {
|
||||
int validityInt;
|
||||
if (validityPeriod <= 12) {
|
||||
validityInt = (validityPeriod * 12) - 1;
|
||||
} else if (validityPeriod <= 24) {
|
||||
validityInt = (((validityPeriod - 12) * 2) + 143);
|
||||
} else if (validityPeriod <= 720) {
|
||||
validityInt = (validityPeriod / 24) + 166;
|
||||
} else {
|
||||
validityInt = (validityPeriod / 168) + 192;
|
||||
}
|
||||
this.baos.write(validityInt);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeTimeStampStringForDate(Date timestamp) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(timestamp);
|
||||
int year = cal.get(Calendar.YEAR) - 2000;
|
||||
int month = cal.get(Calendar.MONTH) + 1;
|
||||
int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
|
||||
int hourOfDay = cal.get(Calendar.HOUR_OF_DAY);
|
||||
int minute = cal.get(Calendar.MINUTE);
|
||||
int sec = cal.get(Calendar.SECOND);
|
||||
TimeZone tz = cal.getTimeZone();
|
||||
int offset = tz.getOffset(timestamp.getTime());
|
||||
int minOffset = offset / 60000;
|
||||
int tzValue = minOffset / 15;
|
||||
// for negative offsets, add 128 to the absolute value
|
||||
if (tzValue < 0) {
|
||||
tzValue = 128 - tzValue;
|
||||
}
|
||||
// note: the nibbles are written as BCD style
|
||||
this.baos.write(PduUtils.createSwappedBCD(year));
|
||||
this.baos.write(PduUtils.createSwappedBCD(month));
|
||||
this.baos.write(PduUtils.createSwappedBCD(dayOfMonth));
|
||||
this.baos.write(PduUtils.createSwappedBCD(hourOfDay));
|
||||
this.baos.write(PduUtils.createSwappedBCD(minute));
|
||||
this.baos.write(PduUtils.createSwappedBCD(sec));
|
||||
this.baos.write(PduUtils.createSwappedBCD(tzValue));
|
||||
}
|
||||
|
||||
protected void writeAddress(String address, int addressType, int addressLength) throws IOException {
|
||||
switch (PduUtils.extractAddressType(addressType)) {
|
||||
case PduUtils.ADDRESS_TYPE_ALPHANUMERIC:
|
||||
byte[] textSeptets = PduUtils.stringToUnencodedSeptets(address);
|
||||
byte[] alphaNumBytes = PduUtils.encode7bitUserData(null, textSeptets);
|
||||
// ADDRESS LENGTH - should be the semi-octet count
|
||||
// - this type is not used for SMSCInfo
|
||||
this.baos.write(alphaNumBytes.length * 2);
|
||||
// ADDRESS TYPE
|
||||
this.baos.write(addressType);
|
||||
// ADDRESS TEXT
|
||||
this.baos.write(alphaNumBytes);
|
||||
break;
|
||||
default:
|
||||
// BCD-style
|
||||
writeBCDAddress(address, addressType, addressLength);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeBCDAddress(String address, int addressType, int addressLength) {
|
||||
// BCD-style
|
||||
// ADDRESS LENGTH - either an octet count or semi-octet count
|
||||
this.baos.write(addressLength);
|
||||
// ADDRESS TYPE
|
||||
this.baos.write(addressType);
|
||||
// ADDRESS NUMBERS
|
||||
// if address.length is not even, pad the string an with F at the end
|
||||
String myaddress = address;
|
||||
if (myaddress.length() % 2 == 1) {
|
||||
myaddress = myaddress + "F";
|
||||
}
|
||||
int digit = 0;
|
||||
for (int i = 0; i < myaddress.length(); i++) {
|
||||
char c = myaddress.charAt(i);
|
||||
if (i % 2 == 1) {
|
||||
digit |= ((Integer.parseInt(Character.toString(c), 16)) << 4);
|
||||
this.baos.write(digit);
|
||||
// clear it
|
||||
digit = 0;
|
||||
} else {
|
||||
digit |= (Integer.parseInt(Character.toString(c), 16) & 0x0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeUDData(Pdu pdu, int mpRefNo, int partNo) {
|
||||
int dcs = pdu.getDataCodingScheme();
|
||||
try {
|
||||
switch (PduUtils.extractDcsEncoding(dcs)) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
writeUDData7bit(pdu, mpRefNo, partNo);
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
writeUDData8bit(pdu, mpRefNo, partNo);
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
writeUDDataUCS2(pdu, mpRefNo, partNo);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid DCS encoding: " + PduUtils.extractDcsEncoding(dcs));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot write uddata", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeUDH(Pdu pdu) throws IOException {
|
||||
// stream directly into the internal baos
|
||||
writeUDH(pdu, this.baos);
|
||||
}
|
||||
|
||||
protected void writeUDH(Pdu pdu, ByteArrayOutputStream udhBaos) throws IOException {
|
||||
// need to insure that proper concat info is inserted
|
||||
// before writing if needed
|
||||
// i.e. the reference number, maxseq and seq have to be set from
|
||||
// outside (OutboundMessage)
|
||||
udhBaos.write(pdu.getUDHLength());
|
||||
for (Iterator<InformationElement> ieIterator = pdu.getInformationElements(); ieIterator.hasNext();) {
|
||||
InformationElement ie = ieIterator.next();
|
||||
udhBaos.write(ie.getIdentifier());
|
||||
udhBaos.write(ie.getLength());
|
||||
udhBaos.write(ie.getData());
|
||||
}
|
||||
}
|
||||
|
||||
protected int computeOffset(Pdu pdu, int maxMessageLength, int partNo) {
|
||||
// computes offset to which part of the string is to be encoded into the PDU
|
||||
// also sets the MpMaxNo field of the concatInfo if message is multi-part
|
||||
int offset;
|
||||
int maxParts = 1;
|
||||
if (!pdu.isBinary()) {
|
||||
maxParts = pdu.getDecodedText().length() / maxMessageLength + 1;
|
||||
} else {
|
||||
byte[] pduDataBytes = pdu.getDataBytes();
|
||||
if (pduDataBytes == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot compute offset for empty data bytes");
|
||||
}
|
||||
maxParts = pduDataBytes.length / maxMessageLength + 1;
|
||||
}
|
||||
if (pdu.hasTpUdhi()) {
|
||||
ConcatInformationElement concatInfoFinal = pdu.getConcatInfo();
|
||||
if (concatInfoFinal != null) {
|
||||
if (partNo > 0) {
|
||||
concatInfoFinal.setMpMaxNo(maxParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((maxParts > 1) && (partNo > 0)) {
|
||||
// - if partNo > maxParts
|
||||
// - error
|
||||
if (partNo > maxParts) {
|
||||
throw new IllegalArgumentException("Invalid partNo: " + partNo + ", maxParts=" + maxParts);
|
||||
}
|
||||
offset = ((partNo - 1) * maxMessageLength);
|
||||
} else {
|
||||
// just get from the start
|
||||
offset = 0;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
protected void checkForConcat(Pdu pdu, int lengthOfText, int maxLength, int maxLengthWithUdh, int mpRefNo,
|
||||
int partNo) {
|
||||
if ((lengthOfText <= maxLengthWithUdh) || ((lengthOfText > maxLengthWithUdh) && (lengthOfText <= maxLength))) {
|
||||
} else {
|
||||
// need concat
|
||||
ConcatInformationElement concatInfoFinal = pdu.getConcatInfo();
|
||||
if (concatInfoFinal != null) {
|
||||
// if concatInfo is already present then just replace the values with the supplied
|
||||
concatInfoFinal.setMpRefNo(mpRefNo);
|
||||
concatInfoFinal.setMpSeqNo(partNo);
|
||||
} else {
|
||||
// add concat info with the specified mpRefNo, bogus maxSeqNo, and partNo
|
||||
// bogus maxSeqNo will be replaced once it is known in the later steps
|
||||
// this just needs to be added since its presence is needed to compute
|
||||
// the UDH length
|
||||
ConcatInformationElement concatInfo = InformationElementFactory.generateConcatInfo(mpRefNo, partNo);
|
||||
pdu.addInformationElement(concatInfo);
|
||||
this.updateFirstOctet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int computePotentialUdhLength(Pdu pdu) {
|
||||
int currentUdhLength = pdu.getTotalUDHLength();
|
||||
if (currentUdhLength == 0) {
|
||||
// add 1 for the UDH Length field
|
||||
return ConcatInformationElement.getDefaultConcatLength() + 1;
|
||||
}
|
||||
// this already has the UDH Length field, no need to add 1
|
||||
return currentUdhLength + ConcatInformationElement.getDefaultConcatLength();
|
||||
}
|
||||
|
||||
protected void writeUDData7bit(Pdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
String decodedText = pdu.getDecodedText();
|
||||
// partNo states what part of the unencoded text will be used
|
||||
// - max length is based on the size of the UDH
|
||||
// for 7bit => maxLength = 160 - total UDH septets
|
||||
// check if this message needs a concat
|
||||
byte[] textSeptetsForDecodedText = PduUtils.stringToUnencodedSeptets(decodedText);
|
||||
int potentialUdhLength = PduUtils.getNumSeptetsForOctets(computePotentialUdhLength(pdu));
|
||||
checkForConcat(pdu, textSeptetsForDecodedText.length,
|
||||
160 - PduUtils.getNumSeptetsForOctets(pdu.getTotalUDHLength()), // CHANGED
|
||||
160 - potentialUdhLength, mpRefNo, partNo);
|
||||
// given the IEs in the pdu derive the max message body length
|
||||
// this length will include the potential concat added in the previous step
|
||||
int totalUDHLength = pdu.getTotalUDHLength();
|
||||
int maxMessageLength = 160 - PduUtils.getNumSeptetsForOctets(totalUDHLength);
|
||||
// get septets for part
|
||||
byte[] textSeptets = getUnencodedSeptetsForPart(pdu, maxMessageLength, partNo);
|
||||
// udlength is the sum of udh septet length and the text septet length
|
||||
int udLength = PduUtils.getNumSeptetsForOctets(totalUDHLength) + textSeptets.length;
|
||||
this.baos.write(udLength);
|
||||
// generate UDH byte[]
|
||||
// UDHL (sum of all IE lengths)
|
||||
// IE list
|
||||
byte[] udhBytes = null;
|
||||
if (pdu.hasTpUdhi()) {
|
||||
ByteArrayOutputStream udhBaos = new ByteArrayOutputStream();
|
||||
writeUDH(pdu, udhBaos);
|
||||
// buffer the udh since this needs to be 7-bit encoded with the text
|
||||
udhBytes = udhBaos.toByteArray();
|
||||
}
|
||||
// encode both as one unit
|
||||
byte[] udBytes = PduUtils.encode7bitUserData(udhBytes, textSeptets);
|
||||
// write combined encoded array
|
||||
this.baos.write(udBytes);
|
||||
}
|
||||
|
||||
private byte[] getUnencodedSeptetsForPart(Pdu pdu, int maxMessageLength, int partNo) {
|
||||
// computes offset to which part of the string is to be encoded into the PDU
|
||||
// also sets the MpMaxNo field of the concatInfo if message is multi-part
|
||||
int offset;
|
||||
int maxParts = 1;
|
||||
// must use the unencoded septets not the actual string since
|
||||
// it is possible that some special characters in string are multi-septet
|
||||
byte[] unencodedSeptets = PduUtils.stringToUnencodedSeptets(pdu.getDecodedText());
|
||||
maxParts = (unencodedSeptets.length / maxMessageLength) + 1;
|
||||
if (pdu.hasTpUdhi()) {
|
||||
ConcatInformationElement concatInfoFinal = pdu.getConcatInfo();
|
||||
if (concatInfoFinal != null) {
|
||||
if (partNo > 0) {
|
||||
concatInfoFinal.setMpMaxNo(maxParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((maxParts > 1) && (partNo > 0)) {
|
||||
// - if partNo > maxParts
|
||||
// - error
|
||||
if (partNo > maxParts) {
|
||||
throw new UnrecoverableSmslibException("Invalid partNo: " + partNo + ", maxParts=" + maxParts);
|
||||
}
|
||||
offset = ((partNo - 1) * maxMessageLength);
|
||||
} else {
|
||||
// just get from the start
|
||||
offset = 0;
|
||||
}
|
||||
// copy the portion of the full unencoded septet array for this part
|
||||
byte[] septetsForPart = new byte[Math.min(maxMessageLength, unencodedSeptets.length - offset)];
|
||||
System.arraycopy(unencodedSeptets, offset, septetsForPart, 0, septetsForPart.length);
|
||||
return septetsForPart;
|
||||
}
|
||||
|
||||
protected void writeUDData8bit(Pdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
// NOTE: binary messages are also handled here
|
||||
byte[] data;
|
||||
if (pdu.isBinary()) {
|
||||
// use the supplied bytes
|
||||
byte[] dataBytesFinal = pdu.getDataBytes();
|
||||
if (dataBytesFinal == null) {
|
||||
throw new UnrecoverableSmslibException("Data cannot be null");
|
||||
}
|
||||
data = dataBytesFinal;
|
||||
} else {
|
||||
// encode the text
|
||||
data = PduUtils.encode8bitUserData(pdu.getDecodedText());
|
||||
}
|
||||
// partNo states what part of the unencoded text will be used
|
||||
// - max length is based on the size of the UDH
|
||||
// for 8bit => maxLength = 140 - the total UDH bytes
|
||||
// check if this message needs a concat
|
||||
int potentialUdhLength = computePotentialUdhLength(pdu);
|
||||
checkForConcat(pdu, data.length, 140 - pdu.getTotalUDHLength(), // CHANGED
|
||||
140 - potentialUdhLength, mpRefNo, partNo);
|
||||
// given the IEs in the pdu derive the max message body length
|
||||
// this length will include the potential concat added in the previous step
|
||||
int totalUDHLength = pdu.getTotalUDHLength();
|
||||
int maxMessageLength = 140 - totalUDHLength;
|
||||
// compute which portion of the message will be part of the message
|
||||
int offset = computeOffset(pdu, maxMessageLength, partNo);
|
||||
byte[] dataToWrite = new byte[Math.min(maxMessageLength, data.length - offset)];
|
||||
System.arraycopy(data, offset, dataToWrite, 0, dataToWrite.length);
|
||||
// generate udlength
|
||||
// based on partNo
|
||||
// udLength is an octet count for 8bit/ucs2
|
||||
int udLength = totalUDHLength + dataToWrite.length;
|
||||
// write udlength
|
||||
this.baos.write(udLength);
|
||||
// write UDH to the stream directly
|
||||
if (pdu.hasTpUdhi()) {
|
||||
writeUDH(pdu, this.baos);
|
||||
}
|
||||
// write data
|
||||
this.baos.write(dataToWrite);
|
||||
}
|
||||
|
||||
protected void writeUDDataUCS2(Pdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
String decodedText = pdu.getDecodedText();
|
||||
// partNo states what part of the unencoded text will be used
|
||||
// - max length is based on the size of the UDH
|
||||
// for ucs2 => maxLength = (140 - the total UDH bytes)/2
|
||||
// check if this message needs a concat
|
||||
int potentialUdhLength = computePotentialUdhLength(pdu);
|
||||
checkForConcat(pdu, decodedText.length(), (140 - pdu.getTotalUDHLength()) / 2, // CHANGED
|
||||
(140 - potentialUdhLength) / 2, mpRefNo, partNo);
|
||||
// given the IEs in the pdu derive the max message body length
|
||||
// this length will include the potential concat added in the previous step
|
||||
int totalUDHLength = pdu.getTotalUDHLength();
|
||||
int maxMessageLength = (140 - totalUDHLength) / 2;
|
||||
// compute which portion of the message will be part of the message
|
||||
int offset = computeOffset(pdu, maxMessageLength, partNo);
|
||||
String textToEncode = decodedText.substring(offset, Math.min(offset + maxMessageLength, decodedText.length()));
|
||||
// generate udlength
|
||||
// based on partNo
|
||||
// udLength is an octet count for 8bit/ucs2
|
||||
int udLength = totalUDHLength + (textToEncode.length() * 2);
|
||||
// write udlength
|
||||
this.baos.write(udLength);
|
||||
// write UDH to the stream directly
|
||||
if (pdu.hasTpUdhi()) {
|
||||
writeUDH(pdu, this.baos);
|
||||
}
|
||||
// write encoded text
|
||||
this.baos.write(PduUtils.encodeUcs2UserData(textToEncode));
|
||||
}
|
||||
|
||||
protected void writeByte(int i) {
|
||||
this.baos.write(i);
|
||||
}
|
||||
|
||||
protected void writeBytes(byte[] b) throws IOException {
|
||||
this.baos.write(b);
|
||||
}
|
||||
|
||||
public List<String> generatePduList(Pdu pdu, int mpRefNo) {
|
||||
// generate all required PDUs for a given message
|
||||
// mpRefNo comes from the ModemGateway
|
||||
ArrayList<String> pduList = new ArrayList<>();
|
||||
for (int i = 1; i <= pdu.getMpMaxNo(); i++) {
|
||||
String pduString = generatePduString(pdu, mpRefNo, i);
|
||||
pduList.add(pduString);
|
||||
}
|
||||
return pduList;
|
||||
}
|
||||
|
||||
// NOTE: partNo indicates which part of a multipart message to generate
|
||||
// assuming that the message is multipart, this will be ignored if the
|
||||
// message is not a concat message
|
||||
public String generatePduString(Pdu pdu, int mpRefNo, int partNo) {
|
||||
try {
|
||||
this.baos = new ByteArrayOutputStream();
|
||||
this.firstOctetPosition = -1;
|
||||
this.updateFirstOctet = false;
|
||||
// process the PDU
|
||||
switch (pdu.getTpMti()) {
|
||||
case PduUtils.TP_MTI_SMS_DELIVER:
|
||||
generateSmsDeliverPduString((SmsDeliveryPdu) pdu, mpRefNo, partNo);
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_SUBMIT:
|
||||
generateSmsSubmitPduString((SmsSubmitPdu) pdu, mpRefNo, partNo);
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_STATUS_REPORT:
|
||||
generateSmsStatusReportPduString((SmsStatusReportPdu) pdu);
|
||||
break;
|
||||
}
|
||||
// in case concat is detected in the writeUD() method
|
||||
// and there was no UDHI at the time of detection
|
||||
// the old firstOctet must be overwritten with the new value
|
||||
byte[] pduBytes = this.baos.toByteArray();
|
||||
if (this.updateFirstOctet) {
|
||||
pduBytes[this.firstOctetPosition] = (byte) (pdu.getFirstOctet() & 0xFF);
|
||||
}
|
||||
return PduUtils.bytesToPdu(pduBytes);
|
||||
} catch (IOException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot generate pdu", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void generateSmsSubmitPduString(SmsSubmitPdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
String address = pdu.getAddress();
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("adress cannot be null");
|
||||
}
|
||||
// SMSC address info
|
||||
writeSmscInfo(pdu);
|
||||
// first octet
|
||||
writeFirstOctet(pdu);
|
||||
// message reference
|
||||
writeByte(pdu.getMessageReference());
|
||||
// destination address info
|
||||
writeAddress(address, pdu.getAddressType(), address.length());
|
||||
// protocol id
|
||||
writeByte(pdu.getProtocolIdentifier());
|
||||
// data coding scheme
|
||||
writeByte(pdu.getDataCodingScheme());
|
||||
// validity period
|
||||
switch (pdu.getTpVpf()) {
|
||||
case PduUtils.TP_VPF_INTEGER:
|
||||
writeValidityPeriodInteger(pdu.getValidityPeriod());
|
||||
break;
|
||||
case PduUtils.TP_VPF_TIMESTAMP:
|
||||
Date validityDate = pdu.getValidityDate();
|
||||
if (validityDate == null) {
|
||||
throw new IllegalArgumentException("Cannot get validity date for pdu");
|
||||
}
|
||||
writeTimeStampStringForDate(validityDate);
|
||||
break;
|
||||
}
|
||||
// user data
|
||||
// headers
|
||||
writeUDData(pdu, mpRefNo, partNo);
|
||||
}
|
||||
|
||||
// NOTE: the following are just for validation of the PduParser
|
||||
// - there is no normal scenario where these are used
|
||||
protected void generateSmsDeliverPduString(SmsDeliveryPdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
// SMSC address info
|
||||
writeSmscInfo(pdu);
|
||||
// first octet
|
||||
writeFirstOctet(pdu);
|
||||
// originator address info
|
||||
String address = pdu.getAddress();
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Address cannot be null");
|
||||
}
|
||||
writeAddress(address, pdu.getAddressType(), address.length());
|
||||
// protocol id
|
||||
writeByte(pdu.getProtocolIdentifier());
|
||||
// data coding scheme
|
||||
writeByte(pdu.getDataCodingScheme());
|
||||
// timestamp
|
||||
Date timestamp = pdu.getTimestamp();
|
||||
if (timestamp != null) {
|
||||
writeTimeStampStringForDate(timestamp);
|
||||
}
|
||||
// user data
|
||||
// headers
|
||||
writeUDData(pdu, mpRefNo, partNo);
|
||||
}
|
||||
|
||||
protected void generateSmsStatusReportPduString(SmsStatusReportPdu pdu) throws IOException {
|
||||
// SMSC address info
|
||||
writeSmscInfo(pdu);
|
||||
// first octet
|
||||
writeFirstOctet(pdu);
|
||||
// message reference
|
||||
writeByte(pdu.getMessageReference());
|
||||
// destination address info
|
||||
String address = pdu.getAddress();
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Address cannot be null");
|
||||
}
|
||||
writeAddress(address, pdu.getAddressType(), address.length());
|
||||
// timestamp
|
||||
Date timestamp = pdu.getTimestamp();
|
||||
if (timestamp == null) {
|
||||
throw new IllegalArgumentException("cannot write null timestamp");
|
||||
}
|
||||
writeTimeStampStringForDate(timestamp);
|
||||
// discharge time(timestamp)
|
||||
Date dischargeTime = pdu.getDischargeTime();
|
||||
if (dischargeTime == null) {
|
||||
throw new IllegalArgumentException("cannot write null dischargeTime");
|
||||
}
|
||||
writeTimeStampStringForDate(dischargeTime);
|
||||
// status
|
||||
writeByte(pdu.getStatus());
|
||||
}
|
||||
}
|
||||
343
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduParser.java
vendored
Normal file
343
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduParser.java
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.message.MsIsdn;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElementFactory;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PduParser {
|
||||
// ==================================================
|
||||
// RAW PDU PARSER
|
||||
// ==================================================
|
||||
// increments as methods are called
|
||||
private int position;
|
||||
|
||||
private byte @Nullable [] pduByteArray;
|
||||
|
||||
// possible types of data
|
||||
// BCD digits
|
||||
// byte
|
||||
// gsm-septets
|
||||
// timestamp info
|
||||
private int readByte() {
|
||||
// read 8-bits forward
|
||||
byte[] pduByteArrayFinal = this.pduByteArray;
|
||||
if (pduByteArrayFinal == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot read byte from null data");
|
||||
}
|
||||
int retVal = pduByteArrayFinal[this.position] & 0xFF;
|
||||
this.position++;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private int readSwappedNibbleBCDByte() {
|
||||
// read 8-bits forward, swap the nibbles
|
||||
int data = readByte();
|
||||
data = PduUtils.swapNibbles((byte) data);
|
||||
int retVal = 0;
|
||||
retVal += ((data >>> 4) & 0xF) * 10;
|
||||
retVal += ((data & 0xF));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Calendar readTimeStamp() {
|
||||
// reads timestamp info
|
||||
// 7 bytes in semi-octet(BCD) style
|
||||
int year = readSwappedNibbleBCDByte();
|
||||
int month = readSwappedNibbleBCDByte();
|
||||
int day = readSwappedNibbleBCDByte();
|
||||
int hour = readSwappedNibbleBCDByte();
|
||||
int minute = readSwappedNibbleBCDByte();
|
||||
int second = readSwappedNibbleBCDByte();
|
||||
// special treatment for timezone due to sign bit
|
||||
// swap nibbles, clear the sign bit, convert remaining bits to BCD
|
||||
int timestamp = readByte();
|
||||
boolean negative = (timestamp & 0x08) == 0x08; // check bit 3
|
||||
int timezone = PduUtils.swapNibbles(timestamp) & 0x7F; // remove last bit since this is just a sign
|
||||
// time zone computation
|
||||
TimeZone tz = null;
|
||||
if (negative) {
|
||||
// bit 3 of unswapped value represents the sign (1 == negative, 0 == positive)
|
||||
// when swapped this will now be bit 7 (128)
|
||||
int bcdTimeZone = 0;
|
||||
bcdTimeZone += (((timezone >>> 4) & 0xF) * 10);
|
||||
bcdTimeZone += ((timezone & 0xF));
|
||||
timezone = bcdTimeZone;
|
||||
int totalMinutes = timezone * 15;
|
||||
int hours = totalMinutes / 60;
|
||||
int minutes = totalMinutes % 60;
|
||||
String gmtString = "GMT-" + hours + ":" + (minutes < 10 ? "0" : "") + minutes;
|
||||
// System.out.println(gmtString);
|
||||
tz = TimeZone.getTimeZone(gmtString);
|
||||
} else {
|
||||
int bcdTimeZone = 0;
|
||||
bcdTimeZone += ((timezone >>> 4) & 0xF) * 10;
|
||||
bcdTimeZone += ((timezone & 0xF));
|
||||
timezone = bcdTimeZone;
|
||||
int totalMinutes = timezone * 15;
|
||||
int hours = totalMinutes / 60;
|
||||
int minutes = totalMinutes % 60;
|
||||
String gmtString = "GMT+" + hours + ":" + (minutes < 10 ? "0" : "") + minutes;
|
||||
// System.out.println(gmtString);
|
||||
tz = TimeZone.getTimeZone(gmtString);
|
||||
}
|
||||
Calendar cal = Calendar.getInstance(tz);
|
||||
cal.set(Calendar.YEAR, year + 2000);
|
||||
cal.set(Calendar.MONTH, month - 1);
|
||||
cal.set(Calendar.DAY_OF_MONTH, day);
|
||||
cal.set(Calendar.HOUR_OF_DAY, hour);
|
||||
cal.set(Calendar.MINUTE, minute);
|
||||
cal.set(Calendar.SECOND, second);
|
||||
return cal;
|
||||
}
|
||||
|
||||
private @Nullable String readAddress(int addressLength, int addressType) {
|
||||
// NOTE: the max number of octets on an address is 12 octets
|
||||
// this means that an address field need only be 12 octets long
|
||||
// what about for 7-bit? This would be 13 chars at 12 octets?
|
||||
if (addressLength > 0) {
|
||||
// length is a semi-octet count
|
||||
int addressDataOctetLength = addressLength / 2 + ((addressLength % 2 == 1) ? 1 : 0);
|
||||
// extract data and increment position
|
||||
byte[] addressData = new byte[addressDataOctetLength];
|
||||
byte[] pduByteArrayFinal = this.pduByteArray;
|
||||
if (pduByteArrayFinal != null) {
|
||||
System.arraycopy(pduByteArrayFinal, this.position, addressData, 0, addressDataOctetLength);
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Cannot read address because pdu data is null");
|
||||
}
|
||||
this.position = this.position + addressDataOctetLength;
|
||||
switch (PduUtils.extractAddressType(addressType)) {
|
||||
case PduUtils.ADDRESS_TYPE_ALPHANUMERIC:
|
||||
// extract and process encoded bytes
|
||||
byte[] uncompressed = PduUtils.encodedSeptetsToUnencodedSeptets(addressData);
|
||||
int septets = addressLength * 4 / 7;
|
||||
byte[] choppedAddressData = new byte[septets];
|
||||
System.arraycopy(uncompressed, 0, choppedAddressData, 0, septets);
|
||||
return PduUtils.unencodedSeptetsToString(choppedAddressData);
|
||||
default:
|
||||
// process BCD style data any other
|
||||
return PduUtils.readBCDNumbers(addressLength, addressData);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int readValidityPeriodInt() {
|
||||
// this will convert the VP to #MINUTES
|
||||
int validity = readByte();
|
||||
int minutes = 0;
|
||||
if ((validity > 0) && (validity <= 143)) {
|
||||
// groups of 5 min
|
||||
minutes = (validity + 1) * 5;
|
||||
} else if ((validity > 143) && (validity <= 167)) {
|
||||
// groups of 30 min + 12 hrs
|
||||
minutes = (12 * 60) + (validity - 143) * 30;
|
||||
} else if ((validity > 167) && (validity <= 196)) {
|
||||
// days
|
||||
minutes = (validity - 166) * 24 * 60;
|
||||
} else if ((validity > 197) && (validity <= 255)) {
|
||||
// weeks
|
||||
minutes = (validity - 192) * 7 * 24 * 60;
|
||||
}
|
||||
return minutes;
|
||||
}
|
||||
|
||||
public Pdu parsePdu(String rawPdu) {
|
||||
// encode pdu to byte[] for easier processing
|
||||
this.pduByteArray = PduUtils.pduToBytes(rawPdu);
|
||||
this.position = 0;
|
||||
// parse start and determine what type of pdu it is
|
||||
Pdu pdu = parseStart();
|
||||
// parse depending on the pdu type
|
||||
switch (pdu.getTpMti()) {
|
||||
case PduUtils.TP_MTI_SMS_DELIVER:
|
||||
parseSmsDeliverMessage((SmsDeliveryPdu) pdu);
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_SUBMIT:
|
||||
parseSmsSubmitMessage((SmsSubmitPdu) pdu);
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_STATUS_REPORT:
|
||||
parseSmsStatusReportMessage((SmsStatusReportPdu) pdu);
|
||||
break;
|
||||
}
|
||||
return pdu;
|
||||
}
|
||||
|
||||
private Pdu parseStart() {
|
||||
// SMSC info
|
||||
// length
|
||||
// address type
|
||||
// smsc data
|
||||
int addressLength = readByte();
|
||||
Pdu pdu = null;
|
||||
if (addressLength > 0) {
|
||||
int addressType = readByte();
|
||||
String smscAddress = readAddress((addressLength - 1) * 2, addressType);
|
||||
// first octet - determine how to parse and how to store
|
||||
int firstOctet = readByte();
|
||||
pdu = PduFactory.createPdu(firstOctet);
|
||||
// generic methods
|
||||
pdu.setSmscAddressType(addressType);
|
||||
pdu.setSmscAddress(smscAddress);
|
||||
pdu.setSmscInfoLength(addressLength);
|
||||
} else {
|
||||
// first octet - determine how to parse and how to store
|
||||
int firstOctet = readByte();
|
||||
pdu = PduFactory.createPdu(firstOctet);
|
||||
}
|
||||
return pdu;
|
||||
}
|
||||
|
||||
private void parseUserData(Pdu pdu) {
|
||||
// ud length
|
||||
// NOTE: - the udLength value is just stored, it is not used to determine the length
|
||||
// of the remaining data (it may be a septet length not an octet length)
|
||||
// - parser just assumes that the remaining PDU data is for the User-Data field
|
||||
int udLength = readByte();
|
||||
pdu.setUDLength(udLength);
|
||||
// user data
|
||||
// NOTE: UD Data does not contain the length octet
|
||||
byte[] pduByteArrayFinal = this.pduByteArray;
|
||||
if (pduByteArrayFinal != null) {
|
||||
int udOctetLength = pduByteArrayFinal.length - this.position;
|
||||
byte[] udData = new byte[udOctetLength];
|
||||
System.arraycopy(pduByteArrayFinal, this.position, udData, 0, udOctetLength);
|
||||
// save the UD data
|
||||
pdu.setUDData(udData);
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Cannot parse user data because pdu data is null");
|
||||
}
|
||||
// user data header (if present)
|
||||
// position is still at the start of the UD
|
||||
if (pdu.hasTpUdhi()) {
|
||||
// udh length
|
||||
int udhLength = readByte();
|
||||
// udh data (iterate till udh is consumed)
|
||||
// iei id
|
||||
// iei data length
|
||||
// iei data
|
||||
int endUdh = this.position + udhLength;
|
||||
while (this.position < endUdh) {
|
||||
int iei = readByte();
|
||||
int iedl = readByte();
|
||||
byte[] ieData = new byte[iedl];
|
||||
System.arraycopy(pduByteArrayFinal, this.position, ieData, 0, iedl);
|
||||
InformationElement ie = InformationElementFactory.createInformationElement(iei, ieData);
|
||||
pdu.addInformationElement(ie);
|
||||
this.position = this.position + iedl;
|
||||
if (this.position > endUdh) {
|
||||
// at the end, position after adding should be exactly at endUdh
|
||||
throw new UnrecoverableSmslibException(
|
||||
"UDH is shorter than expected endUdh=" + endUdh + ", position=" + this.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseSmsDeliverMessage(SmsDeliveryPdu pdu) {
|
||||
// originator address info
|
||||
// address length
|
||||
// type of address
|
||||
// address data
|
||||
int addressLength = readByte();
|
||||
int addressType = readByte();
|
||||
String originatorAddress = readAddress(addressLength, addressType);
|
||||
pdu.setAddressType(addressType);
|
||||
if (originatorAddress != null) {
|
||||
pdu.setAddress(new MsIsdn(originatorAddress));
|
||||
}
|
||||
// protocol id
|
||||
int protocolId = readByte();
|
||||
pdu.setProtocolIdentifier(protocolId);
|
||||
// data coding scheme
|
||||
int dcs = readByte();
|
||||
pdu.setDataCodingScheme(dcs);
|
||||
// timestamp
|
||||
Calendar timestamp = readTimeStamp();
|
||||
pdu.setTimestamp(timestamp);
|
||||
// user data
|
||||
parseUserData(pdu);
|
||||
}
|
||||
|
||||
private void parseSmsStatusReportMessage(SmsStatusReportPdu pdu) {
|
||||
// message reference
|
||||
int messageReference = readByte();
|
||||
pdu.setMessageReference(messageReference);
|
||||
// destination address info
|
||||
int addressLength = readByte();
|
||||
int addressType = readByte();
|
||||
String destinationAddress = readAddress(addressLength, addressType);
|
||||
pdu.setAddressType(addressType);
|
||||
pdu.setAddress(new MsIsdn(destinationAddress));
|
||||
// timestamp
|
||||
Calendar timestamp = readTimeStamp();
|
||||
pdu.setTimestamp(timestamp);
|
||||
// discharge time(timestamp)
|
||||
Calendar timestamp2 = readTimeStamp();
|
||||
pdu.setDischargeTime(timestamp2);
|
||||
// status
|
||||
int status = readByte();
|
||||
pdu.setStatus(status);
|
||||
}
|
||||
|
||||
// NOTE: the following is just for validation of the PduGenerator
|
||||
// - there is no normal scenario where this is used
|
||||
private void parseSmsSubmitMessage(SmsSubmitPdu pdu) {
|
||||
// message reference
|
||||
int messageReference = readByte();
|
||||
pdu.setMessageReference(messageReference);
|
||||
// destination address info
|
||||
int addressLength = readByte();
|
||||
int addressType = readByte();
|
||||
String destinationAddress = readAddress(addressLength, addressType);
|
||||
pdu.setAddressType(addressType);
|
||||
pdu.setAddress(new MsIsdn(destinationAddress));
|
||||
// protocol id
|
||||
int protocolId = readByte();
|
||||
pdu.setProtocolIdentifier(protocolId);
|
||||
// data coding scheme
|
||||
int dcs = readByte();
|
||||
pdu.setDataCodingScheme(dcs);
|
||||
// validity period
|
||||
switch (pdu.getTpVpf()) {
|
||||
case PduUtils.TP_VPF_NONE:
|
||||
break;
|
||||
case PduUtils.TP_VPF_INTEGER:
|
||||
int validityInt = readValidityPeriodInt();
|
||||
pdu.setValidityPeriod(validityInt / 60); // pdu assumes hours
|
||||
break;
|
||||
case PduUtils.TP_VPF_TIMESTAMP:
|
||||
Calendar validityDate = readTimeStamp();
|
||||
pdu.setValidityTimestamp(validityDate);
|
||||
break;
|
||||
}
|
||||
parseUserData(pdu);
|
||||
}
|
||||
}
|
||||
761
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduUtils.java
vendored
Normal file
761
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduUtils.java
vendored
Normal file
@@ -0,0 +1,761 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.message.MsIsdn;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PduUtils {
|
||||
// ==================================================
|
||||
// GSM ALPHABET
|
||||
// ==================================================
|
||||
private static final char[][] grcAlphabetRemapping = { { '\u0386', '\u0041' }, // GREEK CAPITAL LETTER ALPHA WITH
|
||||
// TONOS
|
||||
{ '\u0388', '\u0045' }, // GREEK CAPITAL LETTER EPSILON WITH TONOS
|
||||
{ '\u0389', '\u0048' }, // GREEK CAPITAL LETTER ETA WITH TONOS
|
||||
{ '\u038A', '\u0049' }, // GREEK CAPITAL LETTER IOTA WITH TONOS
|
||||
{ '\u038C', '\u004F' }, // GREEK CAPITAL LETTER OMICRON WITH TONOS
|
||||
{ '\u038E', '\u0059' }, // GREEK CAPITAL LETTER UPSILON WITH TONOS
|
||||
{ '\u038F', '\u03A9' }, // GREEK CAPITAL LETTER OMEGA WITH TONOS
|
||||
{ '\u0390', '\u0049' }, // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
|
||||
{ '\u0391', '\u0041' }, // GREEK CAPITAL LETTER ALPHA
|
||||
{ '\u0392', '\u0042' }, // GREEK CAPITAL LETTER BETA
|
||||
{ '\u0393', '\u0393' }, // GREEK CAPITAL LETTER GAMMA
|
||||
{ '\u0394', '\u0394' }, // GREEK CAPITAL LETTER DELTA
|
||||
{ '\u0395', '\u0045' }, // GREEK CAPITAL LETTER EPSILON
|
||||
{ '\u0396', '\u005A' }, // GREEK CAPITAL LETTER ZETA
|
||||
{ '\u0397', '\u0048' }, // GREEK CAPITAL LETTER ETA
|
||||
{ '\u0398', '\u0398' }, // GREEK CAPITAL LETTER THETA
|
||||
{ '\u0399', '\u0049' }, // GREEK CAPITAL LETTER IOTA
|
||||
{ '\u039A', '\u004B' }, // GREEK CAPITAL LETTER KAPPA
|
||||
{ '\u039B', '\u039B' }, // GREEK CAPITAL LETTER LAMDA
|
||||
{ '\u039C', '\u004D' }, // GREEK CAPITAL LETTER MU
|
||||
{ '\u039D', '\u004E' }, // GREEK CAPITAL LETTER NU
|
||||
{ '\u039E', '\u039E' }, // GREEK CAPITAL LETTER XI
|
||||
{ '\u039F', '\u004F' }, // GREEK CAPITAL LETTER OMICRON
|
||||
{ '\u03A0', '\u03A0' }, // GREEK CAPITAL LETTER PI
|
||||
{ '\u03A1', '\u0050' }, // GREEK CAPITAL LETTER RHO
|
||||
{ '\u03A3', '\u03A3' }, // GREEK CAPITAL LETTER SIGMA
|
||||
{ '\u03A4', '\u0054' }, // GREEK CAPITAL LETTER TAU
|
||||
{ '\u03A5', '\u0059' }, // GREEK CAPITAL LETTER UPSILON
|
||||
{ '\u03A6', '\u03A6' }, // GREEK CAPITAL LETTER PHI
|
||||
{ '\u03A7', '\u0058' }, // GREEK CAPITAL LETTER CHI
|
||||
{ '\u03A8', '\u03A8' }, // GREEK CAPITAL LETTER PSI
|
||||
{ '\u03A9', '\u03A9' }, // GREEK CAPITAL LETTER OMEGA
|
||||
{ '\u03AA', '\u0049' }, // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
|
||||
{ '\u03AB', '\u0059' }, // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
|
||||
{ '\u03AC', '\u0041' }, // GREEK SMALL LETTER ALPHA WITH TONOS
|
||||
{ '\u03AD', '\u0045' }, // GREEK SMALL LETTER EPSILON WITH TONOS
|
||||
{ '\u03AE', '\u0048' }, // GREEK SMALL LETTER ETA WITH TONOS
|
||||
{ '\u03AF', '\u0049' }, // GREEK SMALL LETTER IOTA WITH TONOS
|
||||
{ '\u03B0', '\u0059' }, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
|
||||
{ '\u03B1', '\u0041' }, // GREEK SMALL LETTER ALPHA
|
||||
{ '\u03B2', '\u0042' }, // GREEK SMALL LETTER BETA
|
||||
{ '\u03B3', '\u0393' }, // GREEK SMALL LETTER GAMMA
|
||||
{ '\u03B4', '\u0394' }, // GREEK SMALL LETTER DELTA
|
||||
{ '\u03B5', '\u0045' }, // GREEK SMALL LETTER EPSILON
|
||||
{ '\u03B6', '\u005A' }, // GREEK SMALL LETTER ZETA
|
||||
{ '\u03B7', '\u0048' }, // GREEK SMALL LETTER ETA
|
||||
{ '\u03B8', '\u0398' }, // GREEK SMALL LETTER THETA
|
||||
{ '\u03B9', '\u0049' }, // GREEK SMALL LETTER IOTA
|
||||
{ '\u03BA', '\u004B' }, // GREEK SMALL LETTER KAPPA
|
||||
{ '\u03BB', '\u039B' }, // GREEK SMALL LETTER LAMDA
|
||||
{ '\u03BC', '\u004D' }, // GREEK SMALL LETTER MU
|
||||
{ '\u03BD', '\u004E' }, // GREEK SMALL LETTER NU
|
||||
{ '\u03BE', '\u039E' }, // GREEK SMALL LETTER XI
|
||||
{ '\u03BF', '\u004F' }, // GREEK SMALL LETTER OMICRON
|
||||
{ '\u03C0', '\u03A0' }, // GREEK SMALL LETTER PI
|
||||
{ '\u03C1', '\u0050' }, // GREEK SMALL LETTER RHO
|
||||
{ '\u03C2', '\u03A3' }, // GREEK SMALL LETTER FINAL SIGMA
|
||||
{ '\u03C3', '\u03A3' }, // GREEK SMALL LETTER SIGMA
|
||||
{ '\u03C4', '\u0054' }, // GREEK SMALL LETTER TAU
|
||||
{ '\u03C5', '\u0059' }, // GREEK SMALL LETTER UPSILON
|
||||
{ '\u03C6', '\u03A6' }, // GREEK SMALL LETTER PHI
|
||||
{ '\u03C7', '\u0058' }, // GREEK SMALL LETTER CHI
|
||||
{ '\u03C8', '\u03A8' }, // GREEK SMALL LETTER PSI
|
||||
{ '\u03C9', '\u03A9' }, // GREEK SMALL LETTER OMEGA
|
||||
{ '\u03CA', '\u0049' }, // GREEK SMALL LETTER IOTA WITH DIALYTIKA
|
||||
{ '\u03CB', '\u0059' }, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA
|
||||
{ '\u03CC', '\u004F' }, // GREEK SMALL LETTER OMICRON WITH TONOS
|
||||
{ '\u03CD', '\u0059' }, // GREEK SMALL LETTER UPSILON WITH TONOS
|
||||
{ '\u03CE', '\u03A9' } // GREEK SMALL LETTER OMEGA WITH TONOS
|
||||
};
|
||||
|
||||
private static final char[] extAlphabet = { '\u000c', // FORM FEED
|
||||
'\u005e', // CIRCUMFLEX ACCENT
|
||||
'\u007b', // LEFT CURLY BRACKET
|
||||
'\u007d', // RIGHT CURLY BRACKET
|
||||
'\\', // REVERSE SOLIDUS
|
||||
'\u005b', // LEFT SQUARE BRACKET
|
||||
'\u007e', // TILDE
|
||||
'\u005d', // RIGHT SQUARE BRACKET
|
||||
'\u007c', // VERTICAL LINES
|
||||
'\u20ac', // EURO SIGN
|
||||
};
|
||||
|
||||
private static final String[] extBytes = { "1b0a", // FORM FEED
|
||||
"1b14", // CIRCUMFLEX ACCENT
|
||||
"1b28", // LEFT CURLY BRACKET
|
||||
"1b29", // RIGHT CURLY BRACKET
|
||||
"1b2f", // REVERSE SOLIDUS
|
||||
"1b3c", // LEFT SQUARE BRACKET
|
||||
"1b3d", // TILDE
|
||||
"1b3e", // RIGHT SQUARE BRACKET
|
||||
"1b40", // VERTICAL LINES
|
||||
"1b65", // EURO SIGN
|
||||
};
|
||||
|
||||
// NOTE: this is an adjustment required to compensate for
|
||||
// multi-byte characters split across the end of a pdu part
|
||||
// if the previous part is noted to be ending in a '1b'
|
||||
// call this method on the first char of the next part
|
||||
// to adjust it for the missing '1b'
|
||||
public static String getMultiCharFor(char c) {
|
||||
switch (c) {
|
||||
// GSM 0x0A (line feed) ==> form feed
|
||||
case '\n':
|
||||
return "'\u000c'";
|
||||
// GSM 0x14 (greek capital lamda) ==> circumflex
|
||||
case '\u039B':
|
||||
return "^";
|
||||
// GSM 0x28 (left parenthesis) ==> left curly brace
|
||||
case '(':
|
||||
return "{";
|
||||
// GSM 0x29 (right parenthesis) ==> right curly brace
|
||||
case ')':
|
||||
return "}";
|
||||
// GSM 0x2f (solidus or slash) ==> reverse solidus or backslash
|
||||
case '/':
|
||||
return "\\";
|
||||
// GSM 0x3c (less than sign) ==> left square bracket
|
||||
case '<':
|
||||
return "[";
|
||||
// GSM 0x3d (equals sign) ==> tilde
|
||||
case '=':
|
||||
return "~";
|
||||
// GSM 0x3e (greater than sign) ==> right square bracket
|
||||
case '>':
|
||||
return "]";
|
||||
// GSM 0x40 (inverted exclamation point) ==> pipe
|
||||
case '\u00A1':
|
||||
return "|";
|
||||
// GSM 0x65 (latin small e) ==> euro
|
||||
case 'e':
|
||||
return "\u20ac";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static final char[] stdAlphabet = { '\u0040', // COMMERCIAL AT
|
||||
'\u00A3', // POUND SIGN
|
||||
'\u0024', // DOLLAR SIGN
|
||||
'\u00A5', // YEN SIGN
|
||||
'\u00E8', // LATIN SMALL LETTER E WITH GRAVE
|
||||
'\u00E9', // LATIN SMALL LETTER E WITH ACUTE
|
||||
'\u00F9', // LATIN SMALL LETTER U WITH GRAVE
|
||||
'\u00EC', // LATIN SMALL LETTER I WITH GRAVE
|
||||
'\u00F2', // LATIN SMALL LETTER O WITH GRAVE
|
||||
'\u00E7', // LATIN SMALL LETTER C WITH CEDILLA
|
||||
'\n', // LINE FEED
|
||||
'\u00D8', // LATIN CAPITAL LETTER O WITH STROKE
|
||||
'\u00F8', // LATIN SMALL LETTER O WITH STROKE
|
||||
'\r', // CARRIAGE RETURN
|
||||
'\u00C5', // LATIN CAPITAL LETTER A WITH RING ABOVE
|
||||
'\u00E5', // LATIN SMALL LETTER A WITH RING ABOVE
|
||||
'\u0394', // GREEK CAPITAL LETTER DELTA
|
||||
'\u005F', // LOW LINE
|
||||
'\u03A6', // GREEK CAPITAL LETTER PHI
|
||||
'\u0393', // GREEK CAPITAL LETTER GAMMA
|
||||
'\u039B', // GREEK CAPITAL LETTER LAMDA
|
||||
'\u03A9', // GREEK CAPITAL LETTER OMEGA
|
||||
'\u03A0', // GREEK CAPITAL LETTER PI
|
||||
'\u03A8', // GREEK CAPITAL LETTER PSI
|
||||
'\u03A3', // GREEK CAPITAL LETTER SIGMA
|
||||
'\u0398', // GREEK CAPITAL LETTER THETA
|
||||
'\u039E', // GREEK CAPITAL LETTER XI
|
||||
'\u00A0', // ESCAPE TO EXTENSION TABLE (or displayed as NBSP, see
|
||||
// note
|
||||
// above)
|
||||
'\u00C6', // LATIN CAPITAL LETTER AE
|
||||
'\u00E6', // LATIN SMALL LETTER AE
|
||||
'\u00DF', // LATIN SMALL LETTER SHARP S (German)
|
||||
'\u00C9', // LATIN CAPITAL LETTER E WITH ACUTE
|
||||
'\u0020', // SPACE
|
||||
'\u0021', // EXCLAMATION MARK
|
||||
'\u0022', // QUOTATION MARK
|
||||
'\u0023', // NUMBER SIGN
|
||||
'\u00A4', // CURRENCY SIGN
|
||||
'\u0025', // PERCENT SIGN
|
||||
'\u0026', // AMPERSAND
|
||||
'\'', // APOSTROPHE
|
||||
'\u0028', // LEFT PARENTHESIS
|
||||
'\u0029', // RIGHT PARENTHESIS
|
||||
'\u002A', // ASTERISK
|
||||
'\u002B', // PLUS SIGN
|
||||
'\u002C', // COMMA
|
||||
'\u002D', // HYPHEN-MINUS
|
||||
'\u002E', // FULL STOP
|
||||
'\u002F', // SOLIDUS
|
||||
'\u0030', // DIGIT ZERO
|
||||
'\u0031', // DIGIT ONE
|
||||
'\u0032', // DIGIT TWO
|
||||
'\u0033', // DIGIT THREE
|
||||
'\u0034', // DIGIT FOUR
|
||||
'\u0035', // DIGIT FIVE
|
||||
'\u0036', // DIGIT SIX
|
||||
'\u0037', // DIGIT SEVEN
|
||||
'\u0038', // DIGIT EIGHT
|
||||
'\u0039', // DIGIT NINE
|
||||
'\u003A', // COLON
|
||||
'\u003B', // SEMICOLON
|
||||
'\u003C', // LESS-THAN SIGN
|
||||
'\u003D', // EQUALS SIGN
|
||||
'\u003E', // GREATER-THAN SIGN
|
||||
'\u003F', // QUESTION MARK
|
||||
'\u00A1', // INVERTED EXCLAMATION MARK
|
||||
'\u0041', // LATIN CAPITAL LETTER A
|
||||
'\u0042', // LATIN CAPITAL LETTER B
|
||||
'\u0043', // LATIN CAPITAL LETTER C
|
||||
'\u0044', // LATIN CAPITAL LETTER D
|
||||
'\u0045', // LATIN CAPITAL LETTER E
|
||||
'\u0046', // LATIN CAPITAL LETTER F
|
||||
'\u0047', // LATIN CAPITAL LETTER G
|
||||
'\u0048', // LATIN CAPITAL LETTER H
|
||||
'\u0049', // LATIN CAPITAL LETTER I
|
||||
'\u004A', // LATIN CAPITAL LETTER J
|
||||
'\u004B', // LATIN CAPITAL LETTER K
|
||||
'\u004C', // LATIN CAPITAL LETTER L
|
||||
'\u004D', // LATIN CAPITAL LETTER M
|
||||
'\u004E', // LATIN CAPITAL LETTER N
|
||||
'\u004F', // LATIN CAPITAL LETTER O
|
||||
'\u0050', // LATIN CAPITAL LETTER P
|
||||
'\u0051', // LATIN CAPITAL LETTER Q
|
||||
'\u0052', // LATIN CAPITAL LETTER R
|
||||
'\u0053', // LATIN CAPITAL LETTER S
|
||||
'\u0054', // LATIN CAPITAL LETTER T
|
||||
'\u0055', // LATIN CAPITAL LETTER U
|
||||
'\u0056', // LATIN CAPITAL LETTER V
|
||||
'\u0057', // LATIN CAPITAL LETTER W
|
||||
'\u0058', // LATIN CAPITAL LETTER X
|
||||
'\u0059', // LATIN CAPITAL LETTER Y
|
||||
'\u005A', // LATIN CAPITAL LETTER Z
|
||||
'\u00C4', // LATIN CAPITAL LETTER A WITH DIAERESIS
|
||||
'\u00D6', // LATIN CAPITAL LETTER O WITH DIAERESIS
|
||||
'\u00D1', // LATIN CAPITAL LETTER N WITH TILDE
|
||||
'\u00DC', // LATIN CAPITAL LETTER U WITH DIAERESIS
|
||||
'\u00A7', // SECTION SIGN
|
||||
'\u00BF', // INVERTED QUESTION MARK
|
||||
'\u0061', // LATIN SMALL LETTER A
|
||||
'\u0062', // LATIN SMALL LETTER B
|
||||
'\u0063', // LATIN SMALL LETTER C
|
||||
'\u0064', // LATIN SMALL LETTER D
|
||||
'\u0065', // LATIN SMALL LETTER E
|
||||
'\u0066', // LATIN SMALL LETTER F
|
||||
'\u0067', // LATIN SMALL LETTER G
|
||||
'\u0068', // LATIN SMALL LETTER H
|
||||
'\u0069', // LATIN SMALL LETTER I
|
||||
'\u006A', // LATIN SMALL LETTER J
|
||||
'\u006B', // LATIN SMALL LETTER K
|
||||
'\u006C', // LATIN SMALL LETTER L
|
||||
'\u006D', // LATIN SMALL LETTER M
|
||||
'\u006E', // LATIN SMALL LETTER N
|
||||
'\u006F', // LATIN SMALL LETTER O
|
||||
'\u0070', // LATIN SMALL LETTER P
|
||||
'\u0071', // LATIN SMALL LETTER Q
|
||||
'\u0072', // LATIN SMALL LETTER R
|
||||
'\u0073', // LATIN SMALL LETTER S
|
||||
'\u0074', // LATIN SMALL LETTER T
|
||||
'\u0075', // LATIN SMALL LETTER U
|
||||
'\u0076', // LATIN SMALL LETTER V
|
||||
'\u0077', // LATIN SMALL LETTER W
|
||||
'\u0078', // LATIN SMALL LETTER X
|
||||
'\u0079', // LATIN SMALL LETTER Y
|
||||
'\u007A', // LATIN SMALL LETTER Z
|
||||
'\u00E4', // LATIN SMALL LETTER A WITH DIAERESIS
|
||||
'\u00F6', // LATIN SMALL LETTER O WITH DIAERESIS
|
||||
'\u00F1', // LATIN SMALL LETTER N WITH TILDE
|
||||
'\u00FC', // LATIN SMALL LETTER U WITH DIAERESIS
|
||||
'\u00E0', // LATIN SMALL LETTER A WITH GRAVE
|
||||
};
|
||||
|
||||
// ==================================================
|
||||
// FIRST OCTET CONSTANTS
|
||||
// ==================================================
|
||||
// to add, use the & with MASK to clear bits on original value
|
||||
// and | this cleared value with constant specified
|
||||
// TP-MTI xxxxxx00 = SMS-DELIVER
|
||||
// xxxxxx10 = SMS-STATUS-REPORT
|
||||
// xxxxxx01 = SMS-SUBMIT
|
||||
public static final int TP_MTI_MASK = 0xFC;
|
||||
|
||||
public static final int TP_MTI_SMS_DELIVER = 0x00;
|
||||
|
||||
public static final int TP_MTI_SMS_SUBMIT = 0x01;
|
||||
|
||||
public static final int TP_MTI_SMS_STATUS_REPORT = 0x02;
|
||||
|
||||
// TP-RD xxxxx0xx = accept duplicate messages
|
||||
// xxxxx1xx = reject duplicate messages
|
||||
// for SMS-SUBMIT only
|
||||
public static final int TP_RD_ACCEPT_DUPLICATES = 0x00;
|
||||
|
||||
// TP-VPF xxx00xxx = no validity period
|
||||
// xxx10xxx = validity period integer-representation
|
||||
// xxx11xxx = validity period timestamp-representation
|
||||
public static final int TP_VPF_MASK = 0xE7;
|
||||
|
||||
public static final int TP_VPF_NONE = 0x00;
|
||||
|
||||
public static final int TP_VPF_INTEGER = 0x10;
|
||||
|
||||
public static final int TP_VPF_TIMESTAMP = 0x18;
|
||||
|
||||
// TP-SRI xx0xxxxx = no status report to SME (for SMS-DELIVER only)
|
||||
// xx1xxxxx = status report to SME
|
||||
public static final int TP_SRI_MASK = 0xDF;
|
||||
|
||||
// TP-SRR xx0xxxxx = no status report (for SMS-SUBMIT only)
|
||||
// xx1xxxxx = status report
|
||||
|
||||
public static final int TP_SRR_NO_REPORT = 0x00;
|
||||
|
||||
public static final int TP_SRR_REPORT = 0x20;
|
||||
|
||||
// TP-UDHI x0xxxxxx = no UDH
|
||||
// x1xxxxxx = UDH present
|
||||
public static final int TP_UDHI_MASK = 0xBF;
|
||||
|
||||
public static final int TP_UDHI_NO_UDH = 0x00;
|
||||
|
||||
public static final int TP_UDHI_WITH_UDH = 0x40;
|
||||
|
||||
// ==================================================
|
||||
// ADDRESS-TYPE CONSTANTS
|
||||
// ==================================================
|
||||
// some typical ones used for sending, though receiving may get other types
|
||||
// usually 1 001 0001 (0x91) international format
|
||||
// 1 000 0001 (0x81) (unknown) short number (e.g. access codes)
|
||||
// 1 101 0000 (0xD0) alphanumeric (e.g. access code names like PasaLoad)
|
||||
public static final int ADDRESS_NUMBER_PLAN_ID_TELEPHONE = 0x01;
|
||||
|
||||
public static final int ADDRESS_TYPE_MASK = 0x70;
|
||||
|
||||
public static final int ADDRESS_TYPE_UNKNOWN = 0x00;
|
||||
|
||||
public static final int ADDRESS_TYPE_INTERNATIONAL = 0x10;
|
||||
|
||||
public static final int ADDRESS_TYPE_NATIONAL = 0x20;
|
||||
|
||||
public static final int ADDRESS_TYPE_ALPHANUMERIC = 0x50;
|
||||
|
||||
public static int getAddressTypeFor(MsIsdn number) {
|
||||
switch (number.getType()) {
|
||||
case International:
|
||||
return createAddressType(ADDRESS_TYPE_INTERNATIONAL | ADDRESS_NUMBER_PLAN_ID_TELEPHONE);
|
||||
case National:
|
||||
return createAddressType(ADDRESS_TYPE_NATIONAL | ADDRESS_NUMBER_PLAN_ID_TELEPHONE);
|
||||
default:
|
||||
return createAddressType(ADDRESS_TYPE_UNKNOWN | ADDRESS_NUMBER_PLAN_ID_TELEPHONE);
|
||||
}
|
||||
}
|
||||
|
||||
public static int extractAddressType(int addressType) {
|
||||
return addressType & ADDRESS_TYPE_MASK;
|
||||
}
|
||||
|
||||
public static int createAddressType(int addressType) {
|
||||
// last bit is always set
|
||||
return 0x80 | addressType;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// DCS ENCODING CONSTANTS
|
||||
// ==================================================
|
||||
public static final int DCS_CODING_GROUP_MASK = 0x0F;
|
||||
|
||||
public static final int DCS_CODING_GROUP_DATA = 0xF0;
|
||||
|
||||
public static final int DCS_CODING_GROUP_GENERAL = 0xC0;
|
||||
|
||||
public static final int DCS_ENCODING_MASK = 0xF3;
|
||||
|
||||
public static final int DCS_ENCODING_7BIT = 0x00;
|
||||
|
||||
public static final int DCS_ENCODING_8BIT = 0x04;
|
||||
|
||||
public static final int DCS_ENCODING_UCS2 = 0x08;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_MASK = 0xEC;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_FLASH = 0x10;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_ME = 0x11;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_SIM = 0x12;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_TE = 0x13;
|
||||
|
||||
public static int extractDcsEncoding(int dataCodingScheme) {
|
||||
return dataCodingScheme & ~PduUtils.DCS_ENCODING_MASK;
|
||||
}
|
||||
|
||||
public static int extractDcsClass(int dataCodingScheme) {
|
||||
return dataCodingScheme & ~DCS_MESSAGE_CLASS_MASK;
|
||||
}
|
||||
|
||||
public static int extractDcsFlash(int dataCodingScheme) {
|
||||
// this is only useful if DCS != 0
|
||||
return dataCodingScheme & ~DCS_MESSAGE_CLASS_MASK;
|
||||
}
|
||||
|
||||
public static String decodeDataCodingScheme(Pdu pdu) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
switch (PduUtils.extractDcsEncoding(pdu.getDataCodingScheme())) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
sb.append("7-bit GSM Alphabet");
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
sb.append("8-bit encoding");
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
sb.append("UCS2 encoding");
|
||||
break;
|
||||
}
|
||||
// are flash messages are only applicable to general coding group?
|
||||
if ((pdu.getDataCodingScheme() & ~PduUtils.DCS_CODING_GROUP_GENERAL) == 0) {
|
||||
switch (PduUtils.extractDcsClass(pdu.getDataCodingScheme())) {
|
||||
case PduUtils.DCS_MESSAGE_CLASS_FLASH:
|
||||
sb.append(", (Flash Message)");
|
||||
break;
|
||||
case PduUtils.DCS_MESSAGE_CLASS_ME:
|
||||
sb.append(", (Class1 ME Message)");
|
||||
break;
|
||||
case PduUtils.DCS_MESSAGE_CLASS_SIM:
|
||||
sb.append(", (Class2 SIM Message)");
|
||||
break;
|
||||
case PduUtils.DCS_MESSAGE_CLASS_TE:
|
||||
sb.append(", (Class3 TE Message)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static byte[] encode8bitUserData(String text) {
|
||||
try {
|
||||
return text.getBytes("ISO8859_1");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot encode user data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] encodeUcs2UserData(String text) {
|
||||
try {
|
||||
// UTF-16 Big-Endian, no Byte Order Marker at start
|
||||
return text.getBytes("UTF-16BE");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot encode user data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] encode7bitUserData(byte @Nullable [] udhOctets, byte[] textSeptets) {
|
||||
// UDH octets and text have to be encoded together in a single pass
|
||||
// UDH octets will need to be converted to unencoded septets in order
|
||||
// to properly pad the data
|
||||
if (udhOctets == null) {
|
||||
// convert string to uncompressed septets
|
||||
return unencodedSeptetsToEncodedSeptets(textSeptets);
|
||||
}
|
||||
// convert UDH octets as if they were encoded septets
|
||||
// NOTE: DO NOT DISCARD THE LAST SEPTET IF IT IS ZERO
|
||||
byte[] udhSeptets = PduUtils.encodedSeptetsToUnencodedSeptets(udhOctets, false);
|
||||
// combine the two arrays and encode them as a whole
|
||||
byte[] combined = new byte[udhSeptets.length + textSeptets.length];
|
||||
System.arraycopy(udhSeptets, 0, combined, 0, udhSeptets.length);
|
||||
System.arraycopy(textSeptets, 0, combined, udhSeptets.length, textSeptets.length);
|
||||
// convert encoded byte[] to a PDU string
|
||||
return unencodedSeptetsToEncodedSeptets(combined);
|
||||
}
|
||||
|
||||
public static String decode8bitEncoding(byte @Nullable [] udhData, byte[] pduData) {
|
||||
// standard 8-bit characters
|
||||
try {
|
||||
int udhLength = ((udhData == null) ? 0 : udhData.length);
|
||||
return new String(pduData, udhLength, pduData.length - udhLength, "ISO8859_1");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot decode user data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String decodeUcs2Encoding(byte @Nullable [] udhData, byte[] pduData) {
|
||||
try {
|
||||
int udhLength = ((udhData == null) ? 0 : udhData.length);
|
||||
// standard unicode
|
||||
return new String(pduData, udhLength, pduData.length - udhLength, "UTF-16");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot decode user data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte swapNibbles(int b) {
|
||||
return (byte) (((b << 4) & 0xF0) | ((b >>> 4) & 0x0F));
|
||||
}
|
||||
|
||||
public static String readBCDNumbers(int numDigits, byte[] addressData) {
|
||||
// reads length BCD numbers from the current position
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < addressData.length; i++) {
|
||||
int b = addressData[i];
|
||||
int num1 = b & 0x0F;
|
||||
sb.append(num1);
|
||||
int num2 = (b >>> 4) & 0x0F;
|
||||
if (num2 != 0x0F) {
|
||||
// check if fillbits
|
||||
sb.append(num2);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static int createSwappedBCD(int decimal) {
|
||||
// creates a swapped BCD representation of a 2-digit decimal
|
||||
int tens = (decimal & 0xFF) / 10;
|
||||
int ones = (decimal & 0xFF) - (tens * 10);
|
||||
return (ones << 4) | tens;
|
||||
}
|
||||
|
||||
// from Java String to uncompressed septets (GSM characters)
|
||||
public static byte[] stringToUnencodedSeptets(String s) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int i, j, index;
|
||||
char ch;
|
||||
String myS = s;
|
||||
myS = myS.replace('\u00C7', // LATIN CAPITAL LETTER C WITH CEDILLA
|
||||
'\u00E7' // LATIN SMALL LETTER C WITH CEDILLA
|
||||
);
|
||||
for (i = 0; i < myS.length(); i++) {
|
||||
ch = myS.charAt(i);
|
||||
index = -1;
|
||||
for (j = 0; j < extAlphabet.length; j++) {
|
||||
if (extAlphabet[j] == ch) {
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index != -1) // An extended char...
|
||||
{
|
||||
baos.write((byte) Integer.parseInt(extBytes[index].substring(0, 2), 16));
|
||||
baos.write((byte) Integer.parseInt(extBytes[index].substring(2, 4), 16));
|
||||
} else
|
||||
// Maybe a standard char...
|
||||
{
|
||||
index = -1;
|
||||
for (j = 0; j < stdAlphabet.length; j++) {
|
||||
if (stdAlphabet[j] == ch) {
|
||||
index = j;
|
||||
baos.write((byte) j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index == -1) // Maybe a Greek Char...
|
||||
{
|
||||
for (j = 0; j < grcAlphabetRemapping.length; j++) {
|
||||
if (grcAlphabetRemapping[j][0] == ch) {
|
||||
index = j;
|
||||
ch = grcAlphabetRemapping[j][1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index != -1) {
|
||||
for (j = 0; j < stdAlphabet.length; j++) {
|
||||
if (stdAlphabet[j] == ch) {
|
||||
index = j;
|
||||
baos.write((byte) j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
// Unknown char replacement...
|
||||
{
|
||||
baos.write((byte) ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
// from compress unencoded septets
|
||||
public static byte[] unencodedSeptetsToEncodedSeptets(byte[] septetBytes) {
|
||||
byte[] txtBytes;
|
||||
byte[] txtSeptets;
|
||||
int txtBytesLen;
|
||||
BitSet bits;
|
||||
int i, j;
|
||||
txtBytes = septetBytes;
|
||||
txtBytesLen = txtBytes.length;
|
||||
bits = new BitSet();
|
||||
for (i = 0; i < txtBytesLen; i++) {
|
||||
for (j = 0; j < 7; j++) {
|
||||
if ((txtBytes[i] & (1 << j)) != 0) {
|
||||
bits.set((i * 7) + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
// big diff here
|
||||
int encodedSeptetByteArrayLength = txtBytesLen * 7 / 8 + ((txtBytesLen * 7 % 8 != 0) ? 1 : 0);
|
||||
txtSeptets = new byte[encodedSeptetByteArrayLength];
|
||||
for (i = 0; i < encodedSeptetByteArrayLength; i++) {
|
||||
for (j = 0; j < 8; j++) {
|
||||
txtSeptets[i] |= (byte) ((bits.get((i * 8) + j) ? 1 : 0) << j);
|
||||
}
|
||||
}
|
||||
return txtSeptets;
|
||||
}
|
||||
|
||||
// from GSM characters to java string
|
||||
public static String unencodedSeptetsToString(byte[] bytes) {
|
||||
StringBuffer text;
|
||||
String extChar;
|
||||
int i, j;
|
||||
text = new StringBuffer();
|
||||
for (i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i] == 0x1b) {
|
||||
// NOTE: - ++i can be a problem if the '1b'
|
||||
// is right at the end of a PDU
|
||||
// - this will be an issue for displaying
|
||||
// partial PDUs e.g. via toString()
|
||||
if (i < bytes.length - 1) {
|
||||
extChar = "1b" + Integer.toHexString(bytes[++i]);
|
||||
for (j = 0; j < extBytes.length; j++) {
|
||||
if (extBytes[j].equalsIgnoreCase(extChar)) {
|
||||
text.append(extAlphabet[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
text.append(stdAlphabet[bytes[i]]);
|
||||
}
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
public static int getNumSeptetsForOctets(int numOctets) {
|
||||
return numOctets * 8 / 7 + ((numOctets * 8 % 7 != 0) ? 1 : 0);
|
||||
// return numOctets + (numOctets/7);
|
||||
}
|
||||
|
||||
// decompress encoded septets to unencoded form
|
||||
public static byte[] encodedSeptetsToUnencodedSeptets(byte[] octetBytes) {
|
||||
return encodedSeptetsToUnencodedSeptets(octetBytes, true);
|
||||
}
|
||||
|
||||
public static byte[] encodedSeptetsToUnencodedSeptets(byte[] octetBytes, boolean discardLast) {
|
||||
byte newBytes[];
|
||||
BitSet bitSet;
|
||||
int i, j, value1, value2;
|
||||
bitSet = new BitSet(octetBytes.length * 8);
|
||||
value1 = 0;
|
||||
for (i = 0; i < octetBytes.length; i++) {
|
||||
for (j = 0; j < 8; j++) {
|
||||
value1 = (i * 8) + j;
|
||||
if ((octetBytes[i] & (1 << j)) != 0) {
|
||||
bitSet.set(value1);
|
||||
}
|
||||
}
|
||||
}
|
||||
value1++;
|
||||
// this is a bit count NOT a byte count
|
||||
value2 = value1 / 7 + ((value1 % 7 != 0) ? 1 : 0); // big diff here
|
||||
// System.out.println(octetBytes.length);
|
||||
// System.out.println(value1+" --> "+value2);
|
||||
if (value2 == 0) {
|
||||
value2++;
|
||||
}
|
||||
newBytes = new byte[value2];
|
||||
for (i = 0; i < value2; i++) {
|
||||
for (j = 0; j < 7; j++) {
|
||||
if ((value1 + 1) > (i * 7 + j)) {
|
||||
if (bitSet.get(i * 7 + j)) {
|
||||
newBytes[i] |= (byte) (1 << j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (discardLast && octetBytes.length * 8 % 7 > 0) {
|
||||
// when decoding a 7bit encoded string
|
||||
// the last septet may become 0, this should be discarded
|
||||
// since this is an artifact of the encoding not part of the
|
||||
// original string
|
||||
// this is only done for decoding 7bit encoded text NOT for
|
||||
// reversing octets to septets (e.g. for the encoding the UDH)
|
||||
if (newBytes[newBytes.length - 1] == 0) {
|
||||
byte[] retVal = new byte[newBytes.length - 1];
|
||||
System.arraycopy(newBytes, 0, retVal, 0, retVal.length);
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
return newBytes;
|
||||
}
|
||||
|
||||
// converts a PDU style string to a byte array
|
||||
public static byte[] pduToBytes(String s) {
|
||||
byte[] bytes = new byte[s.length() / 2];
|
||||
for (int i = 0; i < s.length(); i += 2) {
|
||||
bytes[i / 2] = (byte) (Integer.parseInt(s.substring(i, i + 2), 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// converts a byte array to PDU style string
|
||||
public static String bytesToPdu(byte[] bytes) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
sb.append(byteToPdu(bytes[i] & 0xFF));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String byteToBits(byte b) {
|
||||
String bits = Integer.toBinaryString(b & 0xFF);
|
||||
while (bits.length() < 8) {
|
||||
bits = "0" + bits;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static String byteToPdu(int b) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
String s = Integer.toHexString(b & 0xFF);
|
||||
if (s.length() == 1) {
|
||||
sb.append("0");
|
||||
}
|
||||
sb.append(s);
|
||||
return sb.toString().toUpperCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmsDeliveryPdu extends Pdu {
|
||||
// can only create via the factory
|
||||
SmsDeliveryPdu() {
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// TIMESTAMP
|
||||
// ==================================================
|
||||
private @Nullable Calendar timestamp;
|
||||
|
||||
public void setTimestamp(Calendar timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public @Nullable Date getTimestamp() {
|
||||
Calendar timestampFinal = this.timestamp;
|
||||
return timestampFinal == null ? null : timestampFinal.getTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmsStatusReportPdu extends Pdu {
|
||||
// can only create via the factory
|
||||
SmsStatusReportPdu() {
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// MESSAGE REFERENCE
|
||||
// ==================================================
|
||||
// usually just 0x00 to let MC supply
|
||||
private int messageReference = 0x00;
|
||||
|
||||
public void setMessageReference(int reference) {
|
||||
this.messageReference = reference;
|
||||
}
|
||||
|
||||
public int getMessageReference() {
|
||||
return this.messageReference;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// STATUS
|
||||
// ==================================================
|
||||
private int status = 0x00;
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// TIMESTAMP
|
||||
// ==================================================
|
||||
private @Nullable Calendar timestamp;
|
||||
|
||||
public void setTimestamp(Calendar timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public @Nullable Date getTimestamp() {
|
||||
Calendar timestampFinal = this.timestamp;
|
||||
return timestampFinal == null ? null : timestampFinal.getTime();
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// DISCHARGE TIME
|
||||
// ==================================================
|
||||
private @Nullable Calendar dischargeTime;
|
||||
|
||||
public void setDischargeTime(Calendar myDischargeTime) {
|
||||
this.dischargeTime = myDischargeTime;
|
||||
}
|
||||
|
||||
public @Nullable Date getDischargeTime() {
|
||||
Calendar dischargeTimeFinal = this.dischargeTime;
|
||||
return dischargeTimeFinal == null ? null : dischargeTimeFinal.getTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmsSubmitPdu extends Pdu {
|
||||
// ==================================================
|
||||
// FIRST OCTET UTILITIES
|
||||
// ==================================================
|
||||
|
||||
public int getTpVpf() {
|
||||
return getFirstOctetField(PduUtils.TP_VPF_MASK);
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// MESSAGE REFERENCE
|
||||
// ==================================================
|
||||
// usually just 0x00 to let MC supply
|
||||
private int messageReference = 0x00;
|
||||
|
||||
public void setMessageReference(int reference) {
|
||||
this.messageReference = reference;
|
||||
}
|
||||
|
||||
public int getMessageReference() {
|
||||
return this.messageReference;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// VALIDITY PERIOD
|
||||
// ==================================================
|
||||
// which one is used depends of validity period format (TP-VPF)
|
||||
private int validityPeriod = -1;
|
||||
|
||||
@Nullable
|
||||
private Calendar validityPeriodTimeStamp;
|
||||
|
||||
public int getValidityPeriod() {
|
||||
return this.validityPeriod;
|
||||
}
|
||||
|
||||
public void setValidityPeriod(int validityPeriod) {
|
||||
this.validityPeriod = validityPeriod;
|
||||
}
|
||||
|
||||
public void setValidityTimestamp(Calendar date) {
|
||||
this.validityPeriodTimeStamp = date;
|
||||
}
|
||||
|
||||
public @Nullable Date getValidityDate() {
|
||||
Calendar validityPeriodTimeStampFinal = this.validityPeriodTimeStamp;
|
||||
return validityPeriodTimeStampFinal == null ? null : validityPeriodTimeStampFinal.getTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package org.smslib.pduUtils.gsm3040.ie;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConcatInformationElement extends InformationElement {
|
||||
|
||||
private static final int CONCAT_IE_LENGTH_8BIT = 5;
|
||||
|
||||
public static final int CONCAT_8BIT_REF = 0x00;
|
||||
|
||||
public static final int CONCAT_16BIT_REF = 0x08;
|
||||
|
||||
private static int defaultConcatType = CONCAT_8BIT_REF;
|
||||
|
||||
private static int defaultConcatLength = CONCAT_IE_LENGTH_8BIT;
|
||||
|
||||
public static int getDefaultConcatLength() {
|
||||
return defaultConcatLength;
|
||||
}
|
||||
|
||||
public static int getDefaultConcatType() {
|
||||
return defaultConcatType;
|
||||
}
|
||||
|
||||
ConcatInformationElement(byte identifier, byte[] data) {
|
||||
super(identifier, data);
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
// iei
|
||||
// iel
|
||||
// ref
|
||||
// max
|
||||
// seq
|
||||
if (data.length != 3) {
|
||||
throw new IllegalArgumentException("Invalid data length in: " + getClass().getSimpleName());
|
||||
}
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
// iei
|
||||
// iel
|
||||
// ref(2 bytes)
|
||||
// max
|
||||
// seq
|
||||
if (data.length != 4) {
|
||||
throw new IllegalArgumentException("Invalid data length in: " + getClass().getSimpleName());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid identifier in data in: " + getClass().getSimpleName());
|
||||
}
|
||||
validate();
|
||||
}
|
||||
|
||||
ConcatInformationElement(int identifier, int mpRefNo, int mpMaxNo, int mpSeqNo) {
|
||||
super((byte) (identifier & 0xFF), getData(identifier, mpRefNo, mpMaxNo, mpSeqNo));
|
||||
validate();
|
||||
}
|
||||
|
||||
private static byte[] getData(int identifier, int mpRefNo, int mpMaxNo, int mpSeqNo) {
|
||||
byte[] data = null;
|
||||
switch (identifier) {
|
||||
case CONCAT_8BIT_REF:
|
||||
data = new byte[3];
|
||||
data[0] = (byte) (mpRefNo & 0xFF);
|
||||
data[1] = (byte) (mpMaxNo & 0xFF);
|
||||
data[2] = (byte) (mpSeqNo & 0xFF);
|
||||
break;
|
||||
case CONCAT_16BIT_REF:
|
||||
data = new byte[4];
|
||||
data[0] = (byte) ((mpRefNo & 0xFF00) >>> 8);
|
||||
data[1] = (byte) (mpRefNo & 0xFF);
|
||||
data[2] = (byte) (mpMaxNo & 0xFF);
|
||||
data[3] = (byte) (mpSeqNo & 0xFF);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid identifier for ConcatInformationElement");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getMpRefNo() {
|
||||
// this is 8-bit in 0x00 and 16-bit in 0x08
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
return (data[0] & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
return ((data[0] << 8) | data[1]) & (0xFFFF);
|
||||
}
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
|
||||
public void setMpRefNo(int mpRefNo) {
|
||||
// this is 8-bit in 0x00 and 16-bit in 0x08
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
data[0] = (byte) (mpRefNo & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
data[0] = (byte) ((mpRefNo >>> 8) & (0xFF));
|
||||
data[1] = (byte) ((mpRefNo) & (0xFF));
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
}
|
||||
|
||||
public int getMpMaxNo() {
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
return (data[1] & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
return (data[2] & (0xFF));
|
||||
}
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
|
||||
public void setMpMaxNo(int mpMaxNo) {
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
data[1] = (byte) (mpMaxNo & 0xFF);
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
data[2] = (byte) (mpMaxNo & 0xFF);
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
}
|
||||
|
||||
public int getMpSeqNo() {
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
return (data[2] & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
return (data[3] & (0xFF));
|
||||
}
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
|
||||
public void setMpSeqNo(int mpSeqNo) {
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
data[2] = (byte) (mpSeqNo & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
data[3] = (byte) (mpSeqNo & (0xFF));
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(super.toString());
|
||||
sb.append("[MpRefNo: ");
|
||||
sb.append(getMpRefNo());
|
||||
sb.append(", MpMaxNo: ");
|
||||
sb.append(getMpMaxNo());
|
||||
sb.append(", MpSeqNo: ");
|
||||
sb.append(getMpSeqNo());
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
if (getMpMaxNo() == 0) {
|
||||
throw new IllegalArgumentException("mpMaxNo must be > 0");
|
||||
}
|
||||
if (getMpSeqNo() == 0) {
|
||||
throw new IllegalArgumentException("mpSeqNo must be > 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.smslib.pduUtils.gsm3040.ie;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.pduUtils.gsm3040.PduUtils;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InformationElement {
|
||||
private byte identifier;
|
||||
|
||||
private byte[] data;
|
||||
|
||||
// iei
|
||||
// iel (implicit length of data)
|
||||
// ied (raw ie data)
|
||||
InformationElement(byte id, byte[] ieData) {
|
||||
this.identifier = id;
|
||||
this.data = ieData;
|
||||
}
|
||||
|
||||
// for outgoing messages
|
||||
void initialize(byte id, byte[] ieData) {
|
||||
this.identifier = id;
|
||||
this.data = ieData;
|
||||
}
|
||||
|
||||
public int getIdentifier() {
|
||||
return (this.identifier & 0xFF);
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName() + "[");
|
||||
sb.append(PduUtils.byteToPdu(this.identifier));
|
||||
sb.append(", ");
|
||||
sb.append(PduUtils.byteToPdu(this.data.length));
|
||||
sb.append(", ");
|
||||
sb.append(PduUtils.bytesToPdu(this.data));
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.smslib.pduUtils.gsm3040.ie;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InformationElementFactory {
|
||||
// used to determine what InformationElement to use based on bytes from a UDH
|
||||
// assumes the supplied bytes are correct
|
||||
public static InformationElement createInformationElement(int id, byte[] data) {
|
||||
byte iei = (byte) (id & 0xFF);
|
||||
switch (iei) {
|
||||
case ConcatInformationElement.CONCAT_8BIT_REF:
|
||||
case ConcatInformationElement.CONCAT_16BIT_REF:
|
||||
return new ConcatInformationElement(iei, data);
|
||||
case PortInformationElement.PORT_16BIT:
|
||||
return new PortInformationElement(iei, data);
|
||||
default:
|
||||
return new InformationElement(iei, data);
|
||||
}
|
||||
}
|
||||
|
||||
public static ConcatInformationElement generateConcatInfo(int mpRefNo, int partNo) {
|
||||
ConcatInformationElement concatInfo = new ConcatInformationElement(
|
||||
ConcatInformationElement.getDefaultConcatType(), mpRefNo, 1, partNo);
|
||||
return concatInfo;
|
||||
}
|
||||
|
||||
public static PortInformationElement generatePortInfo(int destPort, int srcPort) {
|
||||
PortInformationElement portInfo = new PortInformationElement(PortInformationElement.PORT_16BIT, destPort,
|
||||
srcPort);
|
||||
return portInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.smslib.pduUtils.gsm3040.ie;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//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.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PortInformationElement extends InformationElement {
|
||||
public static final int PORT_16BIT = 0x05;
|
||||
|
||||
PortInformationElement(byte id, byte[] data) {
|
||||
super(id, data);
|
||||
if (getIdentifier() != PORT_16BIT) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid identifier " + getIdentifier() + " in data in: " + getClass().getSimpleName());
|
||||
}
|
||||
// iei
|
||||
// iel
|
||||
// dest(2 bytes)
|
||||
// src (2 bytes)
|
||||
if (data.length != 4) {
|
||||
throw new IllegalArgumentException("Invalid data length in: " + getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
PortInformationElement(int identifier, int destPort, int srcPort) {
|
||||
super((byte) (identifier & 0xFF), getData(identifier, destPort, srcPort));
|
||||
}
|
||||
|
||||
private static byte[] getData(int identifier, int destPort, int srcPort) {
|
||||
byte[] data = null;
|
||||
switch (identifier) {
|
||||
case PORT_16BIT:
|
||||
data = new byte[4];
|
||||
data[0] = (byte) ((destPort & 0xFF00) >>> 8);
|
||||
data[1] = (byte) (destPort & 0xFF);
|
||||
data[2] = (byte) ((srcPort & 0xFF00) >>> 8);
|
||||
data[3] = (byte) (srcPort & 0xFF);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid identifier for PortInformationElement");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getDestPort() {
|
||||
// first 2 bytes of data
|
||||
byte[] data = getData();
|
||||
return (((data[0] & 0xFF) << 8) | (data[1] & 0xFF));
|
||||
}
|
||||
|
||||
public int getSrcPort() {
|
||||
// next 2 bytes of data
|
||||
byte[] data = getData();
|
||||
return (((data[2] & 0xFF) << 8) | (data[3] & 0xFF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(super.toString());
|
||||
sb.append("[Dst Port: ");
|
||||
sb.append(getDestPort());
|
||||
sb.append(", Src Port: ");
|
||||
sb.append(getSrcPort());
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.smsmodem-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-smsmodem" description="SMSModem Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-serial</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.smsmodem/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SMSConversationConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMSConversationConfiguration {
|
||||
|
||||
public String recipient = "";
|
||||
public boolean deliveryReport = false;
|
||||
public String encoding = "Enc7";
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smsmodem.internal.handler.SMSConversationHandler;
|
||||
import org.openhab.binding.smsmodem.internal.handler.SMSModemBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
|
||||
/**
|
||||
* This class implements a discovery service for SMSConversation
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMSConversationDiscoveryService extends AbstractDiscoveryService
|
||||
implements DiscoveryService, ThingHandlerService {
|
||||
|
||||
private @NonNullByDefault({}) SMSModemBridgeHandler bridgeHandler;
|
||||
private @NonNullByDefault({}) ThingUID bridgeUid;
|
||||
|
||||
public SMSConversationDiscoveryService() {
|
||||
super(0);
|
||||
}
|
||||
|
||||
public SMSConversationDiscoveryService(int timeout) throws IllegalArgumentException {
|
||||
super(timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
for (String msisdn : bridgeHandler.getAllSender()) {
|
||||
buildDiscovery(msisdn);
|
||||
}
|
||||
}
|
||||
|
||||
public void buildDiscovery(String sender) {
|
||||
String senderSanitized = sender.replaceAll("[^a-zA-Z0-9+]", "_");
|
||||
|
||||
ThingUID thingUID = new ThingUID(SMSModemBindingConstants.SMSCONVERSATION_THING_TYPE, senderSanitized,
|
||||
bridgeUid.getId());
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(SMSModemBindingConstants.SMSCONVERSATION_PARAMETER_RECIPIENT, senderSanitized)
|
||||
.withLabel("Conversation with " + sender).withBridge(bridgeUid)
|
||||
.withThingType(SMSModemBindingConstants.SMSCONVERSATION_THING_TYPE)
|
||||
.withRepresentationProperty(SMSModemBindingConstants.SMSCONVERSATION_PARAMETER_RECIPIENT).build();
|
||||
thingDiscovered(result);
|
||||
}
|
||||
|
||||
public void buildByAutoDiscovery(String sender) {
|
||||
if (isBackgroundDiscoveryEnabled()) {
|
||||
buildDiscovery(sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return Set.of(SMSConversationHandler.SUPPORTED_THING_TYPES_UIDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
this.bridgeHandler = (SMSModemBridgeHandler) handler;
|
||||
this.bridgeUid = handler.getThing().getUID();
|
||||
this.bridgeHandler.setDiscoveryService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SMSModemBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMSModemBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "smsmodem";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID SMSCONVERSATION_THING_TYPE = new ThingTypeUID(BINDING_ID, "smsconversation");
|
||||
public static final ThingTypeUID SMSMODEMBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "smsmodembridge");
|
||||
public static final ThingTypeUID SMSMODEMREMOTEBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID,
|
||||
"smsmodemremotebridge");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_RECEIVED = "receive";
|
||||
public static final String CHANNEL_SEND = "send";
|
||||
public static final String CHANNEL_DELIVERYSTATUS = "deliverystatus";
|
||||
public static final String CHANNEL_TRIGGER_MODEM_RECEIVE = "receivetrigger";
|
||||
|
||||
// parameter
|
||||
public static final String SMSCONVERSATION_PARAMETER_RECIPIENT = "recipient";
|
||||
|
||||
// List of all properties
|
||||
public static final String PROPERTY_MANUFACTURER = Thing.PROPERTY_VENDOR;
|
||||
public static final String PROPERTY_MODEL = Thing.PROPERTY_MODEL_ID;
|
||||
public static final String PROPERTY_SWVERSION = Thing.PROPERTY_FIRMWARE_VERSION;
|
||||
public static final String PROPERTY_SERIALNO = Thing.PROPERTY_SERIAL_NUMBER;
|
||||
public static final String PROPERTY_IMSI = "imsi";
|
||||
public static final String PROPERTY_RSSI = "rssi";
|
||||
public static final String PROPERTY_MODE = "mode";
|
||||
public static final String PROPERTY_TOTALSENT = "sent";
|
||||
public static final String PROPERTY_TOTALFAILED = "failed";
|
||||
public static final String PROPERTY_TOTALRECEIVED = "received";
|
||||
public static final String PROPERTY_TOTALFAILURE = "failure";
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SMSModemBridgeConfiguration} class contains fields mapping bridge configuration parameters.
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMSModemBridgeConfiguration {
|
||||
|
||||
public String serialPort = "";
|
||||
public Integer baud = 9600;
|
||||
public String simPin = "";
|
||||
public Integer pollingInterval = 15;
|
||||
public Integer delayBetweenSend = 0;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smsmodem.internal.handler.SMSConversationHandler;
|
||||
import org.openhab.binding.smsmodem.internal.handler.SMSModemBridgeHandler;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link SMSModemHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@Component(configurationPid = "binding.smsmodem", service = ThingHandlerFactory.class)
|
||||
@NonNullByDefault
|
||||
public class SMSModemHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>();
|
||||
{
|
||||
SUPPORTED_THING_TYPES_UIDS.add(SMSConversationHandler.SUPPORTED_THING_TYPES_UIDS);
|
||||
SUPPORTED_THING_TYPES_UIDS.addAll(SMSModemBridgeHandler.SUPPORTED_THING_TYPES_UIDS);
|
||||
}
|
||||
|
||||
private @NonNullByDefault({}) SerialPortManager serialPortManager;
|
||||
|
||||
@Reference
|
||||
protected void setSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (SMSModemBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new SMSModemBridgeHandler((Bridge) thing, serialPortManager);
|
||||
} else if (SMSConversationHandler.SUPPORTED_THING_TYPES_UIDS.equals(thingTypeUID)) {
|
||||
return new SMSConversationHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SMSModemRemoteBridgeConfiguration} class contains fields mapping bridge configuration parameters.
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMSModemRemoteBridgeConfiguration {
|
||||
|
||||
public String ip = "";
|
||||
public Integer networkPort = 2000;
|
||||
public String simPin = "";
|
||||
public Integer pollingInterval = 15;
|
||||
public Integer delayBetweenSend = 0;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal.actions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smsmodem.internal.handler.SMSModemBridgeHandler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.message.AbstractMessage.Encoding;
|
||||
|
||||
/**
|
||||
* The {@link SMSModemActions} exposes some actions
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "smsmodem")
|
||||
@NonNullByDefault
|
||||
public class SMSModemActions implements ThingActions {
|
||||
|
||||
private @NonNullByDefault({}) SMSModemBridgeHandler handler;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SMSModemActions.class);
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
this.handler = (SMSModemBridgeHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@RuleAction(label = "Send Message With Special Encoding", description = "Send a message and specify encoding")
|
||||
public void sendSMS(
|
||||
@ActionInput(name = "recipient", label = "recipient", description = "Recipient of the message") @Nullable String recipient,
|
||||
@ActionInput(name = "message", label = "message", description = "Message to send") @Nullable String message,
|
||||
@ActionInput(name = "encoding", label = "encoding", description = "Encoding") @Nullable String encoding) {
|
||||
if (recipient != null && !recipient.isEmpty() && message != null) {
|
||||
handler.send(recipient, message, false, encoding);
|
||||
} else {
|
||||
logger.warn("SMSModem cannot send a message with no recipient or text");
|
||||
}
|
||||
}
|
||||
|
||||
@RuleAction(label = "Send Message", description = "Send a message")
|
||||
public void sendSMS(
|
||||
@ActionInput(name = "recipient", label = "recipient", description = "Recipient of the message") @Nullable String recipient,
|
||||
@ActionInput(name = "message", label = "message", description = "Message to send") @Nullable String message) {
|
||||
sendSMS(recipient, message, Encoding.Enc7.toString());
|
||||
}
|
||||
|
||||
public static void sendSMS(@Nullable ThingActions actions, @Nullable String recipient, @Nullable String message,
|
||||
@Nullable String encoding) {
|
||||
if (actions instanceof SMSModemActions) {
|
||||
((SMSModemActions) actions).sendSMS(recipient, message, encoding);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Instance is not an SMSModemActions class.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendSMS(@Nullable ThingActions actions, @Nullable String recipient, @Nullable String message) {
|
||||
sendSMS(actions, recipient, message, Encoding.Enc7.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* DeliveryStatus enum for delivery report status
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum DeliveryStatus {
|
||||
UNKNOWN,
|
||||
QUEUED,
|
||||
SENT,
|
||||
PENDING,
|
||||
DELIVERED,
|
||||
EXPIRED,
|
||||
FAILED
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* Exception class for SMSLib configuration
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModemConfigurationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -3455806333751297448L;
|
||||
|
||||
public ModemConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ModemConfigurationException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smsmodem.internal.SMSConversationConfiguration;
|
||||
import org.openhab.binding.smsmodem.internal.SMSModemBindingConstants;
|
||||
import org.openhab.core.i18n.ConfigurationException;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SMSConversationHandler} is responsible for managing
|
||||
* discussion channels.
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMSConversationHandler extends BaseThingHandler {
|
||||
|
||||
public static final ThingTypeUID SUPPORTED_THING_TYPES_UIDS = SMSModemBindingConstants.SMSCONVERSATION_THING_TYPE;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SMSConversationHandler.class);
|
||||
|
||||
private @Nullable SMSModemBridgeHandler bridgeHandler;
|
||||
|
||||
private SMSConversationConfiguration config;
|
||||
|
||||
public SMSConversationHandler(Thing thing) {
|
||||
super(thing);
|
||||
this.config = new SMSConversationConfiguration();
|
||||
}
|
||||
|
||||
public String getRecipient() {
|
||||
return config.recipient.trim();
|
||||
}
|
||||
|
||||
private synchronized void checkBridgeHandler() {
|
||||
if (this.bridgeHandler == null) {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
throw new ConfigurationException("Required bridge not defined for SMSconversation {} with {}.",
|
||||
thing.getUID(), getRecipient());
|
||||
}
|
||||
ThingHandler handler = bridge.getHandler();
|
||||
if (handler instanceof SMSModemBridgeHandler) {
|
||||
this.bridgeHandler = (SMSModemBridgeHandler) handler;
|
||||
} else {
|
||||
throw new ConfigurationException("No available bridge handler found for SMSConversation {} bridge {} .",
|
||||
thing.getUID(), bridge.getUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkAndReceive(String sender, String text) {
|
||||
String conversationRecipient = config.recipient.trim();
|
||||
// is the recipient the one handled by this conversation ? :
|
||||
if (conversationRecipient.equals(sender)) {
|
||||
updateState(SMSModemBindingConstants.CHANNEL_RECEIVED, new StringType(text));
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkAndUpdateDeliveryStatus(String messageRecipient, DeliveryStatus sentStatus) {
|
||||
String conversationRecipient = config.recipient.trim();
|
||||
// is the recipient the one handled by this conversation ? :
|
||||
if (conversationRecipient.equals(messageRecipient)) {
|
||||
updateState(SMSModemBindingConstants.CHANNEL_DELIVERYSTATUS, new StringType(sentStatus.name()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
return;
|
||||
}
|
||||
if (channelUID.getId().equals(SMSModemBindingConstants.CHANNEL_SEND)) {
|
||||
send(command.toString());
|
||||
updateState(SMSModemBindingConstants.CHANNEL_SEND, new StringType(command.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public void send(String text) {
|
||||
SMSModemBridgeHandler bridgeHandlerFinal = bridgeHandler;
|
||||
if (bridgeHandlerFinal != null) {
|
||||
bridgeHandlerFinal.send(getRecipient(), text, config.deliveryReport, config.encoding);
|
||||
} else {
|
||||
logger.warn("Only channel 'send' in SMSConversation can receive command");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(SMSConversationConfiguration.class);
|
||||
try {
|
||||
checkBridgeHandler();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (ConfigurationException confe) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, confe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smsmodem.internal.handler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smsmodem.internal.SMSConversationDiscoveryService;
|
||||
import org.openhab.binding.smsmodem.internal.SMSModemBindingConstants;
|
||||
import org.openhab.binding.smsmodem.internal.SMSModemBridgeConfiguration;
|
||||
import org.openhab.binding.smsmodem.internal.SMSModemRemoteBridgeConfiguration;
|
||||
import org.openhab.binding.smsmodem.internal.actions.SMSModemActions;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.CommunicationException;
|
||||
import org.smslib.Modem;
|
||||
import org.smslib.Modem.Status;
|
||||
import org.smslib.callback.IDeviceInformationListener;
|
||||
import org.smslib.callback.IInboundOutboundMessageListener;
|
||||
import org.smslib.callback.IModemStatusListener;
|
||||
import org.smslib.message.AbstractMessage.Encoding;
|
||||
import org.smslib.message.DeliveryReportMessage;
|
||||
import org.smslib.message.InboundMessage;
|
||||
import org.smslib.message.MsIsdn;
|
||||
import org.smslib.message.OutboundMessage;
|
||||
import org.smslib.message.Payload;
|
||||
import org.smslib.message.Payload.Type;
|
||||
|
||||
/**
|
||||
* The {@link SMSModemBridgeHandler} is responsible for handling
|
||||
* communication with the modem.
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMSModemBridgeHandler extends BaseBridgeHandler
|
||||
implements IModemStatusListener, IInboundOutboundMessageListener, IDeviceInformationListener {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(
|
||||
SMSModemBindingConstants.SMSMODEMBRIDGE_THING_TYPE,
|
||||
SMSModemBindingConstants.SMSMODEMREMOTEBRIDGE_THING_TYPE);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SMSModemBridgeHandler.class);
|
||||
|
||||
private SerialPortManager serialPortManager;
|
||||
|
||||
/**
|
||||
* The smslib object responsible for the serial communication with the modem
|
||||
*/
|
||||
private @Nullable Modem modem;
|
||||
|
||||
/**
|
||||
* A scheduled watchdog check
|
||||
*/
|
||||
private @Nullable ScheduledFuture<?> checkScheduled;
|
||||
|
||||
// we keep a list of msisdn sender for autodiscovery
|
||||
private Set<String> senderMsisdn = new HashSet<String>();
|
||||
private @Nullable SMSConversationDiscoveryService discoveryService;
|
||||
|
||||
private boolean shouldRun = false;
|
||||
|
||||
public SMSModemBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
super(bridge);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
shouldRun = false;
|
||||
ScheduledFuture<?> checkScheduledFinal = checkScheduled;
|
||||
if (checkScheduledFinal != null) {
|
||||
checkScheduledFinal.cancel(true);
|
||||
}
|
||||
Modem finalModem = modem;
|
||||
if (finalModem != null) {
|
||||
scheduler.execute(finalModem::stop);
|
||||
finalModem.registerStatusListener(null);
|
||||
finalModem.registerMessageListener(null);
|
||||
finalModem.registerInformationListener(null);
|
||||
}
|
||||
modem = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateConfiguration(Configuration configuration) {
|
||||
super.updateConfiguration(configuration);
|
||||
scheduler.execute(() -> {
|
||||
Modem finalModem = modem;
|
||||
if (finalModem != null) {
|
||||
finalModem.stop();
|
||||
}
|
||||
checkAndStartModemIfNeeded();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
shouldRun = true;
|
||||
ScheduledFuture<?> checkScheduledFinal = checkScheduled;
|
||||
if (checkScheduledFinal == null || (checkScheduledFinal.isDone()) && this.shouldRun) {
|
||||
checkScheduled = scheduler.scheduleWithFixedDelay(this::checkAndStartModemIfNeeded, 0, 15,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void checkAndStartModemIfNeeded() {
|
||||
try {
|
||||
if (shouldRun && !isRunning()) {
|
||||
logger.debug("Initializing smsmodem");
|
||||
// ensure the underlying modem is stopped before trying to (re)starting it :
|
||||
Modem finalModem = modem;
|
||||
if (finalModem != null) {
|
||||
finalModem.stop();
|
||||
}
|
||||
String logName;
|
||||
if (getThing().getThingTypeUID().equals(SMSModemBindingConstants.SMSMODEMBRIDGE_THING_TYPE)) {
|
||||
SMSModemBridgeConfiguration config = getConfigAs(SMSModemBridgeConfiguration.class);
|
||||
modem = new Modem(serialPortManager, resolveEventualSymbolicLink(config.serialPort),
|
||||
Integer.valueOf(config.baud), config.simPin, scheduler, config.pollingInterval,
|
||||
config.delayBetweenSend);
|
||||
checkParam(config);
|
||||
logName = config.serialPort + " | " + config.baud;
|
||||
} else if (getThing().getThingTypeUID()
|
||||
.equals(SMSModemBindingConstants.SMSMODEMREMOTEBRIDGE_THING_TYPE)) {
|
||||
SMSModemRemoteBridgeConfiguration config = getConfigAs(SMSModemRemoteBridgeConfiguration.class);
|
||||
modem = new Modem(serialPortManager, resolveEventualSymbolicLink(config.ip),
|
||||
Integer.valueOf(config.networkPort), config.simPin, scheduler, config.pollingInterval,
|
||||
config.delayBetweenSend);
|
||||
checkRemoteParam(config);
|
||||
logName = config.ip + ":" + config.networkPort;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid thing type");
|
||||
}
|
||||
logger.debug("Now trying to start SMSModem {}", logName);
|
||||
finalModem = modem;
|
||||
if (finalModem != null) {
|
||||
finalModem.registerStatusListener(this);
|
||||
finalModem.registerMessageListener(this);
|
||||
finalModem.registerInformationListener(this);
|
||||
finalModem.start();
|
||||
}
|
||||
logger.debug("SMSModem {} started", logName);
|
||||
}
|
||||
} catch (ModemConfigurationException e) {
|
||||
String message = e.getMessage();
|
||||
if (e.getCause() != null && e.getCause() instanceof IOException) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkParam(SMSModemBridgeConfiguration config) throws ModemConfigurationException {
|
||||
String realSerialPort = resolveEventualSymbolicLink(config.serialPort);
|
||||
SerialPortIdentifier identifier = serialPortManager.getIdentifier(realSerialPort);
|
||||
if (identifier == null) {
|
||||
// no serial port
|
||||
throw new ModemConfigurationException(
|
||||
realSerialPort + " with " + config.baud + " is not a valid serial port | baud");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRemoteParam(SMSModemRemoteBridgeConfiguration config) throws ModemConfigurationException {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getByName(config.ip);
|
||||
String ip = inetAddress.getHostAddress();
|
||||
|
||||
// test reachable address :
|
||||
try (Socket s = new Socket(ip, config.networkPort)) {
|
||||
}
|
||||
} catch (IOException | NumberFormatException ex) {
|
||||
// no ip
|
||||
throw new ModemConfigurationException(
|
||||
config.ip + ":" + config.networkPort + " is not a reachable address:port", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveEventualSymbolicLink(String serialPortOrIp) {
|
||||
String keepResult = serialPortOrIp;
|
||||
Path maybePath = Paths.get(serialPortOrIp);
|
||||
File maybeFile = maybePath.toFile();
|
||||
if (maybeFile.exists() && Files.isSymbolicLink(maybePath)) {
|
||||
try {
|
||||
maybePath = maybePath.toRealPath();
|
||||
keepResult = maybePath.toAbsolutePath().toString();
|
||||
} catch (IOException e) {
|
||||
} // nothing to do, not a valid symbolic link, return
|
||||
}
|
||||
return keepResult;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
Modem finalModem = modem;
|
||||
return finalModem != null
|
||||
&& (finalModem.getStatus() == Status.Started || finalModem.getStatus() == Status.Starting);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(InboundMessage message) {
|
||||
String sender = message.getOriginatorAddress().getAddress();
|
||||
Payload payload = message.getPayload();
|
||||
String messageText;
|
||||
if (payload.getType().equals(Type.Text)) {
|
||||
String text = payload.getText();
|
||||
if (text != null) {
|
||||
messageText = text;
|
||||
} else {
|
||||
logger.warn("Message has no payload !");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
byte[] bytes = payload.getBytes();
|
||||
if (bytes != null) {
|
||||
logger.warn("Message payload in binary format. Don't know how to handle it. Please report it.");
|
||||
messageText = bytes.toString();
|
||||
} else {
|
||||
logger.warn("Message has no payload !");
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger.debug("Receiving new message from {} : {}", sender, messageText);
|
||||
|
||||
// dispatch to conversation :
|
||||
for (SMSConversationHandler child : getChildHandlers()) {
|
||||
child.checkAndReceive(sender, messageText);
|
||||
}
|
||||
|
||||
// channel trigger
|
||||
String recipientAndMessage = sender + "|" + messageText;
|
||||
triggerChannel(SMSModemBindingConstants.CHANNEL_TRIGGER_MODEM_RECEIVE, recipientAndMessage);
|
||||
|
||||
// prepare discovery service
|
||||
senderMsisdn.add(sender);
|
||||
final SMSConversationDiscoveryService finalDiscoveryService = discoveryService;
|
||||
if (finalDiscoveryService != null) {
|
||||
finalDiscoveryService.buildByAutoDiscovery(sender);
|
||||
}
|
||||
try { // delete message on the sim
|
||||
Modem finalModem = modem;
|
||||
if (finalModem != null) {
|
||||
finalModem.delete(message);
|
||||
}
|
||||
} catch (CommunicationException e) {
|
||||
logger.warn("Cannot delete message after receiving it !", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message
|
||||
*
|
||||
* @param recipient The recipient for the message
|
||||
* @param text The message content
|
||||
* @param deliveryReport If we should ask the network for a delivery report
|
||||
*/
|
||||
public void send(String recipient, String text, boolean deliveryReport, @Nullable String encoding) {
|
||||
OutboundMessage out = new OutboundMessage(recipient, text);
|
||||
try {
|
||||
if (encoding != null && !encoding.isEmpty()) {
|
||||
Encoding encoding2 = Encoding.valueOf(encoding);
|
||||
out.setEncoding(encoding2);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Encoding {} is not supported. Use Enc7, Enc8, EncUcs2, or EncCustom", encoding);
|
||||
}
|
||||
out.setRequestDeliveryReport(deliveryReport);
|
||||
logger.debug("Sending message to {}", recipient);
|
||||
Modem finalModem = modem;
|
||||
if (finalModem != null) {
|
||||
finalModem.queue(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the scanning discovery service to create conversation
|
||||
*
|
||||
* @return All senders of the received messages since the last start
|
||||
*/
|
||||
public Set<String> getAllSender() {
|
||||
return new HashSet<>(senderMsisdn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(SMSModemActions.class, SMSConversationDiscoveryService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processStatusCallback(Modem.Status oldStatus, Modem.Status newStatus) {
|
||||
switch (newStatus) {
|
||||
case Error:
|
||||
String finalDescription = "unknown";
|
||||
Modem finalModem = modem;
|
||||
if (finalModem != null) {
|
||||
finalDescription = finalModem.getDescription();
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"SMSLib reported an error on the underlying modem " + finalDescription);
|
||||
break;
|
||||
case Started:
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
break;
|
||||
case Starting:
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
break;
|
||||
case Stopped:
|
||||
if (thing.getStatus() != ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
break;
|
||||
case Stopping:
|
||||
if (thing.getStatus() != ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setDiscoveryService(SMSConversationDiscoveryService smsConversationDiscoveryService) {
|
||||
this.discoveryService = smsConversationDiscoveryService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageSent(OutboundMessage message) {
|
||||
DeliveryStatus sentStatus;
|
||||
switch (message.getSentStatus()) {
|
||||
case Failed:
|
||||
sentStatus = DeliveryStatus.FAILED;
|
||||
break;
|
||||
case Unsent:
|
||||
case Queued:
|
||||
sentStatus = DeliveryStatus.QUEUED;
|
||||
break;
|
||||
case Sent:
|
||||
sentStatus = DeliveryStatus.SENT;
|
||||
break;
|
||||
default: // shoult not happened
|
||||
sentStatus = DeliveryStatus.UNKNOWN;
|
||||
break;
|
||||
}
|
||||
// dispatch to conversation :
|
||||
MsIsdn recipientAddress = message.getRecipientAddress();
|
||||
if (recipientAddress != null) {
|
||||
String recipient = recipientAddress.getAddress();
|
||||
for (SMSConversationHandler child : getChildHandlers()) {
|
||||
child.checkAndUpdateDeliveryStatus(recipient, sentStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageDelivered(DeliveryReportMessage message) {
|
||||
DeliveryStatus sentStatus;
|
||||
switch (message.getDeliveryStatus()) {
|
||||
case Delivered:
|
||||
sentStatus = DeliveryStatus.DELIVERED;
|
||||
break;
|
||||
case Error:
|
||||
case Failed:
|
||||
sentStatus = DeliveryStatus.FAILED;
|
||||
break;
|
||||
case Expired:
|
||||
sentStatus = DeliveryStatus.EXPIRED;
|
||||
break;
|
||||
case Pending:
|
||||
sentStatus = DeliveryStatus.PENDING;
|
||||
break;
|
||||
case Unknown:
|
||||
default:
|
||||
sentStatus = DeliveryStatus.UNKNOWN;
|
||||
break;
|
||||
}
|
||||
MsIsdn recipientAddress = message.getRecipientAddress();
|
||||
if (recipientAddress != null) {
|
||||
String recipient = recipientAddress.getAddress();
|
||||
for (SMSConversationHandler child : getChildHandlers()) {
|
||||
child.checkAndUpdateDeliveryStatus(recipient, sentStatus);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Modem finalModem = modem;
|
||||
if (finalModem != null) {
|
||||
finalModem.delete(message);
|
||||
}
|
||||
} catch (CommunicationException e) {
|
||||
logger.warn("Cannot delete delivery report after receiving it !", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<SMSConversationHandler> getChildHandlers() {
|
||||
return getThing().getThings().stream().map(Thing::getHandler).filter(Objects::nonNull)
|
||||
.map(handler -> (SMSConversationHandler) handler).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManufacturer(String manufacturer) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_MANUFACTURER, manufacturer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(String model) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_MODEL, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSwVersion(String swVersion) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_SWVERSION, swVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSerialNo(String serialNo) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_SERIALNO, serialNo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImsi(String imsi) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_IMSI, imsi);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRssi(String rssi) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_RSSI, rssi);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMode(String mode) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_MODE, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTotalSent(String totalSent) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_TOTALSENT, totalSent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTotalFailed(String totalFailed) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_TOTALFAILED, totalFailed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTotalReceived(String totalReceived) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_TOTALRECEIVED, totalReceived);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTotalFailures(String totalFailure) {
|
||||
thing.setProperty(SMSModemBindingConstants.PROPERTY_TOTALFAILURE, totalFailure);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="smsmodem" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>SMSModem Binding</name>
|
||||
<description>This binding handles a GSM modem connected to the openHAB server (Serial), or exposed on the network. It
|
||||
can send and receive SMS.</description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,49 @@
|
||||
# binding
|
||||
|
||||
binding.smsmodem.name = SMSModem Binding
|
||||
binding.smsmodem.description = This binding handle a GSM modem connected to the openHAB server (Serial), or exposed on the network. It can send and receive SMS.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.smsmodem.smsconversation.label = SMS Conversation
|
||||
thing-type.smsmodem.smsconversation.description = Represents a conversation with a SMS recipient.
|
||||
thing-type.smsmodem.smsmodembridge.label = SMSModem Bridge
|
||||
thing-type.smsmodem.smsmodembridge.description = This bridge represents a serial modem.
|
||||
thing-type.smsmodem.smsmodemremotebridge.label = SMSModem Remote Bridge
|
||||
thing-type.smsmodem.smsmodemremotebridge.description = This bridge represents a modem exposed over the network.
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.smsmodem.smsconversation.deliveryReport.label = Delivery Report
|
||||
thing-type.config.smsmodem.smsconversation.deliveryReport.description = Ask network for delivery report.
|
||||
thing-type.config.smsmodem.smsconversation.encoding.label = Encoding
|
||||
thing-type.config.smsmodem.smsconversation.encoding.description = Encoding for the message to send. Default Enc7.
|
||||
thing-type.config.smsmodem.smsconversation.recipient.label = Recipient Number
|
||||
thing-type.config.smsmodem.smsconversation.recipient.description = The SMS number of the recipient.
|
||||
thing-type.config.smsmodem.smsmodembridge.baud.label = Baud
|
||||
thing-type.config.smsmodem.smsmodembridge.baud.description = Baud rate.
|
||||
thing-type.config.smsmodem.smsmodembridge.delayBetweenSend.description = Delay between two messages (in milliseconds). Useful for slow modem.
|
||||
thing-type.config.smsmodem.smsmodembridge.pollingInterval.description = Delay between polling for new messages (in seconds).
|
||||
thing-type.config.smsmodem.smsmodembridge.serialPort.label = Serial Port
|
||||
thing-type.config.smsmodem.smsmodembridge.serialPort.description = Serial port of the modem (usually /dev/ttyUSB0).
|
||||
thing-type.config.smsmodem.smsmodembridge.simPin.label = Pin Code
|
||||
thing-type.config.smsmodem.smsmodembridge.simPin.description = The pin (if set) for the sim card.
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.delayBetweenSend.description = Delay between two messages (in milliseconds). Useful for slow modem.
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.ip.label = Address
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.ip.description = IP address of the remote computer.
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.networkPort.label = Network Port
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.networkPort.description = Network port to join the remote service (a.k.a. ser2net).
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.pollingInterval.description = Delay between polling for new messages (in seconds).
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.simPin.label = Pin Code
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.simPin.description = The pin (if set) for the sim card.
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.smsmodem.deliverystatus.label = Delivery Status
|
||||
channel-type.smsmodem.deliverystatus.description = Last message delivery status (either UNKNOWN, QUEUED, SENT, PENDING, DELIVERED, EXPIRED, or FAILED)
|
||||
channel-type.smsmodem.receive.label = Message Received
|
||||
channel-type.smsmodem.receive.description = Last message received
|
||||
channel-type.smsmodem.send.label = Send Message
|
||||
channel-type.smsmodem.send.description = Message to send to the recipient.
|
||||
channel-type.smsmodem.smsmodemreceivetrigger.label = Message Received
|
||||
channel-type.smsmodem.smsmodemreceivetrigger.description = Triggered when a message is received, in the form "<msisdn_sender>|<text>"
|
||||
@@ -0,0 +1,49 @@
|
||||
# binding
|
||||
|
||||
binding.smsmodem.name = Extension SMSModem
|
||||
binding.smsmodem.description = Cette extension gère un modem GSM supportant les messages AT et connecté en série, ou exposé sur le réseau. Elle peut envoyer et recevoir des SMS
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.smsmodem.smsconversation.label = Conversation SMS
|
||||
thing-type.smsmodem.smsconversation.description = Représente une conversation avec un correspondant.
|
||||
thing-type.smsmodem.smsmodembridge.label = SMS Modem
|
||||
thing-type.smsmodem.smsmodembridge.description = Un modem connecté en série.
|
||||
thing-type.smsmodem.smsmodemremotebridge.label = SMS Remote Modem
|
||||
thing-type.smsmodem.smsmodemremotebridge.description = Un modem connecté par le réseau.
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.smsmodem.smsconversation.deliveryReport.label = Accusé de réception
|
||||
thing-type.config.smsmodem.smsconversation.deliveryReport.description = Demande au réseau un accusé de réception.
|
||||
thing-type.config.smsmodem.smsconversation.encoding.label = Encodage
|
||||
thing-type.config.smsmodem.smsconversation.encoding.description = Encodage du message à envoyer. Défaut Enc7.
|
||||
thing-type.config.smsmodem.smsconversation.recipient.label = Numéro Du Correspondant
|
||||
thing-type.config.smsmodem.smsconversation.recipient.description = Le numéro SMS du correspondant.
|
||||
thing-type.config.smsmodem.smsmodembridge.baud.label = Taux (Baud)
|
||||
thing-type.config.smsmodem.smsmodembridge.baud.description = Taux de transmission.
|
||||
thing-type.config.smsmodem.smsmodembridge.delayBetweenSend.description = Délai entre deux envois (en millisecondes). Peut être utile pour les modems lents.
|
||||
thing-type.config.smsmodem.smsmodembridge.pollingInterval.description = Délai entre deux essais de récupération de message (in seconds).
|
||||
thing-type.config.smsmodem.smsmodembridge.serialPort.label = Port Série
|
||||
thing-type.config.smsmodem.smsmodembridge.serialPort.description = Port série du modem (habituellement /dev/ttyUSB0).
|
||||
thing-type.config.smsmodem.smsmodembridge.simPin.label = Code PIN
|
||||
thing-type.config.smsmodem.smsmodembridge.simPin.description = Le code PIN (si nécessaire) de la carte SIM.
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.delayBetweenSend.description = Délai entre deux envois (en millisecondes). Peut être utile pour les modems lents.
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.ip.label = Addresse
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.ip.description = Addresse IP.
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.networkPort.label = Port Réseau
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.networkPort.description = Port réseau pour joindre le serveur (i.e. ser2net)
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.pollingInterval.description = Délai entre deux essais de récupération de message (in seconds).
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.simPin.label = Code PIN
|
||||
thing-type.config.smsmodem.smsmodemremotebridge.simPin.description = Le code PIN (si nécessaire) de la carte SIM.
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.smsmodem.deliverystatus.label = Accusé De Réception
|
||||
channel-type.smsmodem.deliverystatus.description = Dernier statut de message (soit UNKNOWN, QUEUED, SENT, PENDING, DELIVERED, EXPIRED, ou FAILED)
|
||||
channel-type.smsmodem.receive.label = Message Reçu
|
||||
channel-type.smsmodem.receive.description = Dernier message reçu
|
||||
channel-type.smsmodem.send.label = Message Envoyé
|
||||
channel-type.smsmodem.send.description = Message à envoyer au correspondant
|
||||
channel-type.smsmodem.smsmodemreceivetrigger.label = Message Reçu
|
||||
channel-type.smsmodem.smsmodemreceivetrigger.description = Déclenché quand un message est réceptionné, sous la forme "<msisdn_sender>|<text>"
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="smsmodem"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="smsconversation">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="smsmodembridge"/>
|
||||
<bridge-type-ref id="smsmodemremotebridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>SMS Conversation</label>
|
||||
<description>Represents a conversation with a SMS recipient.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="send" typeId="send"/>
|
||||
<channel id="receive" typeId="receive"/>
|
||||
<channel id="deliverystatus" typeId="deliverystatus"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>recipient</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="recipient" type="text" required="true">
|
||||
<label>Recipient Number</label>
|
||||
<description>The SMS number of the recipient.</description>
|
||||
</parameter>
|
||||
<parameter name="deliveryReport" type="boolean" required="false">
|
||||
<label>Delivery Report</label>
|
||||
<description>Ask network for delivery report.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="encoding" type="text" required="false">
|
||||
<label>Encoding</label>
|
||||
<options>
|
||||
<option value="Enc7">Enc7</option>
|
||||
<option value="Enc8">Enc8</option>
|
||||
<option value="EncUcs2">EncUcs2</option>
|
||||
<option value="EncCustom">EncCustom</option>
|
||||
</options>
|
||||
<description>Encoding for the message to send. Default Enc7</description>
|
||||
<default>Enc7</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="send">
|
||||
<item-type>String</item-type>
|
||||
<label>Send Message</label>
|
||||
<description>Message to send to the recipient.</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="receive">
|
||||
<item-type>String</item-type>
|
||||
<label>Message Received</label>
|
||||
<description>Last message received</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deliverystatus">
|
||||
<item-type>String</item-type>
|
||||
<label>Delivery Status</label>
|
||||
<description>Last message delivery status (either UNKNOWN, QUEUED, SENT, PENDING, DELIVERED, EXPIRED, or FAILED)</description>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="smsmodem"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="smsmodembridge">
|
||||
<label>SMSModem Bridge</label>
|
||||
<description>This bridge represents a modem.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="receivetrigger" typeId="smsmodemreceivetrigger"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serialPort" type="text" required="true">
|
||||
<label>Serial Port</label>
|
||||
<description>Serial port of the modem (usually /dev/ttyUSB0).</description>
|
||||
<context>serial-port</context>
|
||||
<default></default>
|
||||
</parameter>
|
||||
<parameter name="baud" type="integer" required="true">
|
||||
<label>Baud</label>
|
||||
<description>Baud rate.</description>
|
||||
<default>19200</default>
|
||||
</parameter>
|
||||
<parameter name="simPin" type="text" required="false">
|
||||
<label>Pin Code</label>
|
||||
<description>The pin (if set) for the sim card.</description>
|
||||
<default></default>
|
||||
</parameter>
|
||||
<parameter name="pollingInterval" type="integer">
|
||||
<advanced>true</advanced>
|
||||
<default>15</default>
|
||||
<description>Delay between polling for new messages (in seconds).</description>
|
||||
</parameter>
|
||||
<parameter name="delayBetweenSend" type="integer">
|
||||
<advanced>true</advanced>
|
||||
<default>100</default>
|
||||
<description>Delay between two messages (in milliseconds). Useful for slow modem.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<bridge-type id="smsmodemremotebridge">
|
||||
<label>SMSModem Remote Bridge</label>
|
||||
<description>This bridge represents a modem on a network controlled computer.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="receivetrigger" typeId="smsmodemreceivetrigger"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ip" type="text" required="true">
|
||||
<label>IP Address</label>
|
||||
<description>IP address of the remote computer.</description>
|
||||
<default></default>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="networkPort" type="integer" required="true">
|
||||
<label>Network Port</label>
|
||||
<description>Network port to join the remote service (a.k.a. ser2net).</description>
|
||||
<default>2000</default>
|
||||
</parameter>
|
||||
<parameter name="simPin" type="text" required="false">
|
||||
<label>Pin Code</label>
|
||||
<description>The pin (if set) for the sim card.</description>
|
||||
<default></default>
|
||||
</parameter>
|
||||
<parameter name="pollingInterval" type="integer">
|
||||
<advanced>true</advanced>
|
||||
<default>15</default>
|
||||
<description>Delay between polling for new messages (in seconds).</description>
|
||||
</parameter>
|
||||
<parameter name="delayBetweenSend" type="integer">
|
||||
<advanced>true</advanced>
|
||||
<default>100</default>
|
||||
<description>Delay between two messages (in milliseconds). Useful for slow modem.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<channel-type id="smsmodemreceivetrigger">
|
||||
<kind>trigger</kind>
|
||||
<label>Message Received</label>
|
||||
<description>Triggered when a message is received, in the form "<msisdn_sender>|<text>"</description>
|
||||
<event/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,22 @@
|
||||
default.poll_reader=100
|
||||
default.command_wait_unit=700
|
||||
default.after_ip_connect_wait_unit=5000
|
||||
default.wait_unit=200
|
||||
default.char_wait_unit=10
|
||||
default.timeout=30000
|
||||
default.port_buffer=8192
|
||||
default.delay_after_init1=10
|
||||
default.delay_after_init2=10
|
||||
default.delay_on_sim_error=5
|
||||
default.delay_network_registration=10
|
||||
default.delay_before_send_pdu=1
|
||||
default.delay_after_pre_pin=1
|
||||
default.delay_after_post_pin=1
|
||||
default.cpin_without_ok=0
|
||||
default.flowcontrol=IN
|
||||
huawei.init1=AT+CFUN=1\r
|
||||
huawei.init2=AT^CURC=0\r
|
||||
huawei.memory_locations=SMSR
|
||||
huawei_e3131.memory_locations=SM
|
||||
wavecommodem.memory_locations=SMSR
|
||||
wavecommodem.cpin_without_ok=1
|
||||
Reference in New Issue
Block a user