/*
* FrontlineSMS <http://www.frontlinesms.com>
* Copyright 2007, 2008 kiwanja
*
* This file is part of FrontlineSMS.
*
* FrontlineSMS is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* FrontlineSMS is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FrontlineSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package net.frontlinesms.messaging.sms.modem;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import serial.*;
import net.frontlinesms.FrontlineUtils;
import net.frontlinesms.data.domain.*;
import net.frontlinesms.data.domain.FrontlineMessage.Status;
import net.frontlinesms.listener.SmsListener;
import net.frontlinesms.messaging.CatHandlerAliasMatcher;
import net.frontlinesms.messaging.sms.SmsService;
import org.apache.log4j.Logger;
import org.smslib.*;
import org.smslib.CService.MessageClass;
/**
* Class for handling the serial connection to an individual SMS device.
*
* @author Ben Whitaker ben(at)masabi(dot)com
* @author Alex Anderson alex(at)masabi(dot)com
* @author Carlos Eduardo Genz kadu(at)masabi(dot)com
* @author james@tregaskis.org
*/
public class SmsModem extends Thread implements SmsService {
//> CONSTANTS
private static final int SMS_BULK_LIMIT = 10;
/** The time, in millis, that this phone handler must have been unresponsive for before it is deemed TIMED OUT
* As far as I know there is no basis for the time chosen for this timeout. */
private static final int TIMEOUT = 80 * 1000; // = 80 seconds;
/**
* The different baud rates that a PhoneHandler may connect at.
* We do not go above a baud rate of 115200bps here, as higher baud rates are not beneficial due to speed limits
* of the GSM network, and are also not recommended by the SMS Lib project for being unstable.
*/
private static final int[] COMM_SPEEDS = new int[] {
9600,
19200,
38400,
57600,
115200
};
/** Logging object */
private final Logger LOG = FrontlineUtils.getLogger(this.getClass());
//> PROPERTIES
/**
* Watchdog to monitor when a phone handler has lost communication with the phone
*/
private long timeOfLastResponseFromPhone;
private final ConcurrentLinkedQueue<CIncomingMessage> inbox = new ConcurrentLinkedQueue<CIncomingMessage>();
private final ConcurrentLinkedQueue<FrontlineMessage> outbox = new ConcurrentLinkedQueue<FrontlineMessage>();
/** The SmsListener to which this phone handler should report SMS Message events. */
private final SmsListener smsListener;
/** The name of the COM port that this PhoneHandler controls. */
private final String portName;
/**
* Indicates whether this PhoneHandler's serial communication
* thread is running. This should *only* be set false in run()
* when certain other conditions are fulfilled - when
* smsLibConnected is false AND autoReconnect is false.
*/
private boolean running;
private boolean phonePresent;
private boolean smsLibConnected;
private boolean tryToConnect;
/** The baud rate, in bps, that this phone handler will connect at. */
private int baudRate;
private CService cService;
private boolean autoDetect;
private boolean autoReconnect;
/** true if this is another port into a phone that is already discovered */
private boolean duplicate;
/** true when this PhoneHandler has been disconnected using disconnect() */
private boolean disconnected;
/** true if this phone is or will be used for sending SMS messages */
private boolean useForSending;
/** true if this phone is or will be used for receiving SMS messages */
private boolean useForReceiving;
/** true if this thread has timed out */
private boolean timedOut;
private boolean detecting;
private boolean disconnecting;
private boolean deleteMessagesAfterReceiving;
private boolean useDeliveryReports;
private String manufacturer = "";
private String model = "";
private String preferredCATHandler = "";
private String serialNumber = "";
private String imsiNumber;
private int batteryPercent;
private int signalPercent;
private String msisdn;
private String smscNumber;
private String simPin;
/** The status of this device */
private SmsModemStatus status = SmsModemStatus.DORMANT;
/** Extra info relating to the current status. */
private String statusDetail;
//> CONSTRUCTORS
/**
* Create a new instance {@link SmsModem}
* @param portName the name of the port which this modem is found on. Value for {@link #portName}
* @param smsListener the value for {@link #smsListener}
* @throws NoSuchPortException
*/
public SmsModem(String portName, SmsListener smsListener) throws NoSuchPortException {
super("SmsModem :: " + portName);
/*
* Make this into a daemon thread - we never know when it may get blocked in native code. Indeed this can be
* witnessed by connecting to a port without a device attached, which can then block as such (I think this one
* was a bluetooth port):
* Win32SerialPort.nwrite(byte[], int, int) line: not available [native method]
* Win32SerialPort.write(byte[], int, int) line: 672
* Win32SerialPort.write(int) line: 664
* Win32SerialOutputStream.write(int) line: 34
* ...
*/
super.setDaemon(true);
assert(smsListener != null);
this.smsListener = smsListener;
this.portName = portName;
resetWatchdog();
//sets up the phone handler on a certain port, it will attempt to auto-detect the phone
try {
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(this.portName);
if(portIdentifier.isCurrentlyOwned()) {
String currentOwner = portIdentifier.getCurrentOwner();
if(currentOwner == null) {
currentOwner = "?";
}
this.setStatus(SmsModemStatus.OWNED_BY_SOMEONE_ELSE, currentOwner);
}
} catch (NoSuchPortException ex) {
LOG.debug("Error getting owner from port", ex);
//doesn't matter if it doesn't get this message
throw ex;
}
}
//> ACCESSOR METHODS
/** @return {@link #status} */
public SmsModemStatus getStatus() {
return this.status;
}
/**
* Set the status of this {@link SmsModem}, and fires an event to {@link #smsListener}
* @param status the status
* @param detail detail relating to the status
*/
private void setStatus(SmsModemStatus status, String detail) {
this.status = status;
this.statusDetail = detail;
LOG.debug("Status [" + status.name()
+ (detail == null?"":": "+detail)
+ "]");
smsListener.smsDeviceEvent(this, this.status);
}
/** @return {@link #statusDetail} */
public String getStatusDetail() {
return this.statusDetail;
}
/** @return the next incoming message, or <code>null</code> if none is available */
public CIncomingMessage nextIncomingMessage() {
return inbox.poll();
}
public String getPort() {
return portName;
}
/**
* Checks if this PhoneHandler is currently active. Returns true if either the thread
* has stopped running OR the handler has been forcibly disconnected. This extra check
* is necessary as PhoneHandler threads can sometimes go to sleep and never wake up
* (it seems).
* @return true if this phone is currently active, false otherwise.
*/
public boolean isRunning() {
return !disconnected && running;
}
/**
* Checks if this instance of PhoneHandler has timed out. Once a PhoneHandler has
* timed out once, it becomes unusable.
* @return true if this phone has timeout, false otherwise.
*/
public boolean isTimedOut() {
if (!timedOut) timedOut = (System.currentTimeMillis() - timeOfLastResponseFromPhone) > TIMEOUT;
return timedOut;
}
public int getBaudRate() {
return baudRate;
}
public int getBatteryPercent() {
if (smsLibConnected) return cService.getDeviceInfo().getBatteryLevel();
else return batteryPercent;
}
/** @return the smscNumber */
public String getSmscNumber() {
if (smsLibConnected) this.smscNumber = cService.getSmscNumber();
return this.smscNumber;
}
/** @param smscNumber the smscNumber to set */
public void setSmscNumber(String smscNumber) {
if (smsLibConnected) cService.setSmscNumber(smscNumber);
this.smscNumber = smscNumber;
}
/** @return the SIM PIN */
public String getSimPin() {
if (smsLibConnected) this.simPin = cService.getSimPin();
return this.simPin;
}
/** @param simPin the SIM PIN to set */
public void setSimPin(String simPin) {
if (smsLibConnected) cService.setSimPin(simPin);
this.simPin = simPin;
}
public String getMsisdn() {
return msisdn;
}
public int getSignalPercent() {
if (smsLibConnected) return cService.getDeviceInfo().getSignalLevel();
else return signalPercent;
}
/**
* Checks if this instance of PhoneHandler is connected to a device.
* @return true if this phone is connected, false otherwise.
*/
public boolean isConnected() {
return smsLibConnected;
}
/** @see SmsService#isUseForSending() */
public boolean isUseForSending() {
return useForSending;
}
public void setUseForSending(boolean useForSend) {
useForSending = useForSend;
}
/** @see SmsService#isUseForReceiving() */
public boolean isUseForReceiving() {
return useForReceiving;
}
/**
* This throws a {@link RuntimeException}, so {@link SmsService#supportsReceive()} should be checked before calling this.
*/
public void setUseForReceiving(boolean useForReceive) {
if(!supportsReceive()) throw new ReceiveNotSupportedException();
useForReceiving = useForReceive;
}
public boolean supportsReceive() {
return cService.supportsReceive();
}
public boolean isDuplicate() {
return duplicate;
}
/**
* Sets the status of this modem. If the status is {@link SmsModemStatus#DUPLICATE}, an
* event will be triggered with {@link #smsListener}.
* @param newDuplicate new value for {@link #duplicate}
*/
public void setDuplicate(boolean newDuplicate) {
duplicate = newDuplicate;
if (duplicate) {
this.setStatus(SmsModemStatus.DUPLICATE, null);
}
}
/**
* Checks if this instance of PhoneHandler has a phone present.
* @return true if this has a phone present, false otherwise.
*/
public boolean isPhonePresent() {
return phonePresent;
}
/** @return {@link #manufacturer} */
public String getManufacturer() {
return manufacturer;
}
/** @return {@link #model} */
public String getModel() {
return model;
}
/** @return {@link #serialNumber} */
public String getSerial() {
return serialNumber;
}
public String getServiceName() {
return FrontlineUtils.getManufacturerAndModel(getManufacturer(), getModel());
}
public void connect(){
if (!phonePresent || duplicate || manufacturer.length() == 0) return;
tryToConnect = true;
resetWatchdog();
}
/**
* Try to connect to the device attached to this port, using the speed informed
* as parameter.
*
* @param maxSpeedRequested The max speed for this device.
* @param manufacturerName The device's manufacturer name.
* @param modelName The device's model name.
* @param preferredCATHandler TODO
* @return true if the connection was successful, false otherwise.
*/
private boolean connect(int maxSpeedRequested, String manufacturerName, String modelName, String preferredCATHandler) {
LOG.trace("ENTER");
LOG.debug("Attempting to connect:"
+ "\n - Speed [" + maxSpeedRequested + "]"
+ "\n - Manufacturer [" + manufacturerName + "]"
+ "\n - Model [" + modelName + "]"
+ "\n - CAT Handler Alias [" + preferredCATHandler + "]");
this.setStatus(SmsModemStatus.TRY_TO_CONNECT, Integer.toString(maxSpeedRequested));
resetWatchdog();
cService = new CService(this.portName, maxSpeedRequested, manufacturerName, modelName, preferredCATHandler);
LOG.debug("Created service [" + cService + "]");
try {
// If the GSM device is PIN protected, enter the PIN here.
// PIN information will be used only when the GSM device reports that it needs a PIN in order to continue.
if(this.simPin != null) {
cService.setSimPin(this.simPin);
} else {
// If we don't have a PIN, then don't set it!
}
// Some modems may require a SIM PIN 2 to unlock their full functionality.
// Like the Vodafone 3G/GPRS PCMCIA card.
// If you have such a modem, you should also define the SIM PIN 2.
// We don't have a SIM PIN2 set, so we don't set anything in the CService. Previously
// this code set PIN2 to 0000, but that seems foolish (see comments re: PIN1)
// Normally, you would want to set the SMSC number to blank. GSM
// devices normally get the SMSC number information from their SIM card.
cService.setSmscNumber(this.smscNumber == null ? "" : this.smscNumber);
FrontlineUtils.sleep_ignoreInterrupts(500);
resetWatchdog();
cService.connect();
resetWatchdog();
// Lets get info about the GSM device...
setManufacturer(cService.getManufacturer());
setModel(cService.getModel());
this.msisdn = cService.getMsisdn();
LOG.debug("Msisdn [" + this.msisdn + "]");
this.serialNumber = cService.getSerialNo();
LOG.debug("Serial Number [" + this.serialNumber + "]");
this.imsiNumber = cService.getImsi();
LOG.debug("Imsi Number [" + this.imsiNumber + "]");
LOG.debug("Mobile Device Information: "
+ "\n - Manufacturer [" + manufacturerName + "]"
+ "\n - Model [" + modelName + "]"
+ "\n - Preferred CAT Handler [" + preferredCATHandler + "]"
+ "\n - Used CAT Handler [" + cService.getAtHandlerName() + "]"
+ "\n - Serial Number [" + cService.getDeviceInfo().getSerialNo() + "]"
+ "\n - IMSI [" + cService.getDeviceInfo().getImsi() + "]"
+ "\n - S/W Version [" + cService.getDeviceInfo().getSwVersion() + "]"
+ "\n - Battery Level [" + cService.getDeviceInfo().getBatteryLevel() + "%]"
+ "\n - Signal Level [" + cService.getDeviceInfo().getSignalLevel() + "%]"
+ "\n - Baud Rate [" + baudRate + "]");
this.setStatus(SmsModemStatus.CONNECTING, null);
if (isDuplicate()) {
disconnect(false);
return false;
}
phonePresent = true;
autoReconnect = true;
smsLibConnected = true;
this.setStatus(SmsModemStatus.CONNECTED, Integer.toString(maxSpeedRequested));
resetWatchdog();
LOG.debug("Connection successful!");
LOG.trace("EXIT");
return true;
} catch (GsmNetworkRegistrationException e) {
this.setStatus(SmsModemStatus.GSM_REG_FAILED, null);
} catch (PortInUseException ex) {
this.setStatus(SmsModemStatus.OWNED_BY_SOMEONE_ELSE, ex.getClass().getSimpleName() + " : " + ex.getMessage());
} catch (Exception ex) {
this.setStatus(SmsModemStatus.FAILED_TO_CONNECT, ex.getClass().getSimpleName() + " : " + ex.getMessage());
}
LOG.debug("Connection failed!");
LOG.trace("EXIT");
return false;
}
/**
* Start the modem handler listening to the serial port with the requested connection settings.
* @param baudRate
* @param manufacturer
* @param model
* @param preferredCATHAndler
*/
public void start(int baudRate, String preferredCATHAndler) {
this.autoDetect = false;
this.tryToConnect = true;
this.baudRate = baudRate;
this.preferredCATHandler = preferredCATHAndler;
super.start();
}
/**
* Start the sms modem listening to the serial port, and autodetect the settings to use.
*/
@Override
public synchronized void start() {
this.autoDetect = true;
super.start();
}
public void run() {
LOG.trace("ENTER");
if(autoDetect) running = _doDetection();
else running = true;
while (running) {
boolean noActivity = true;
resetWatchdog();
if(tryToConnect) {
smsLibConnected = connect(baudRate, manufacturer, model, preferredCATHandler);
tryToConnect = false;
}
if(smsLibConnected) {
try {
//check for incoming messages
if (useForReceiving) {
long startTime = System.currentTimeMillis();
LOG.debug("Checking for received messages...");
int newMessages = checkForMessages();
if(newMessages > 0) noActivity = false;
LOG.debug("Check for messages took [" + (System.currentTimeMillis() - startTime) + "]");
}
// If there are any messages waiting to be sent, send them now.
if (useForSending) {
if(LOG.isDebugEnabled()) LOG.debug("Sending some pending messages. Outbox size is [" + outbox.size() + "]");
resetWatchdog();
long startTime = System.currentTimeMillis();
//create SMS list
LinkedList<FrontlineMessage> messageList = new LinkedList<FrontlineMessage>();
FrontlineMessage m;
while(messageList.size() < SMS_BULK_LIMIT
&& (m = outbox.poll()) != null) {
messageList.add(m);
}
if(messageList.size() > 0) {
LOG.debug("Sending bulk of [" + messageList.size() + "] message(s)");
sendSmsListDirect(messageList);
}
LOG.debug("Send messages took [" + (System.currentTimeMillis() - startTime) + "]");
resetWatchdog();
}
} catch(UnrecognizedHandlerProtocolException ex) {
LOG.debug("Invalid message protocol specified for device.", ex);
disconnect(true);
} catch(UnableToReconnectException ex) {
LOG.debug("Fatal exception in device communication.", ex);
this.setAutoReconnect(false);
setStatus(SmsModemStatus.DISCONNECT_FORCED, ex.getMessage());
disconnect(false);
} catch(SMSLibDeviceException ex) {
LOG.debug("Phone not connected", ex);
disconnect(true);
} catch(IOException ex) {
LOG.error("Communication failed", ex);
disconnect(true);
}
} else if (autoReconnect) {
LOG.debug("Trying to reconnect...");
// Disconnect and relinquish ownership of the COM port.
disconnect(true);
// Reconnect on the next loop.
tryToConnect = true;
} else {
running = false;
}
// If this thread is still running, we should have a little snooze as
// checking the phone continuously for messages will:
// - waste a lot of processor cycles on PC
// - waste phone battery
// - be unnecessary as phones are unlikely to be able to receive messages quicker than ~every 3s
if(running) {
if (noActivity) {
try {
if(smsLibConnected) cService.keepGsmLinkOpen();
FrontlineUtils.sleep_ignoreInterrupts(5000); /* 5 seconds */
} catch (Throwable t) {
LOG.debug("", t);
tryToConnect = false;
disconnect(true);
}
} else {
// Changed this from 100ms to 500ms in an attempt to improve modem stability. There was no explanation
// for the original duration.
FrontlineUtils.sleep_ignoreInterrupts(500);
}
}
}
LOG.trace("EXIT");
}
private final void setManufacturer(String manufacturer) {
LOG.debug("Manufacturer before translation [" + manufacturer + "]");
this.manufacturer = CatHandlerAliasMatcher.getInstance().translateManufacturer(manufacturer);
LOG.debug("Manufacturer after translation [" + this.manufacturer + "]");
}
private final void setModel(String model) {
LOG.debug("Model before translation [" + model + "]");
this.model = CatHandlerAliasMatcher.getInstance().translateModel(manufacturer, model);
LOG.debug("Model after translation [" + this.model + "]");
setPreferredCatHandler();
}
private final void setPreferredCatHandler() {
this.preferredCATHandler = CatHandlerAliasMatcher.getInstance().translateCATHandlerModel(manufacturer, model);
LOG.debug("Preferred CAT Handler [" + this.preferredCATHandler + "]");
}
/**
* Discover the fastest speed at which we can connect to the AT device on this port.
*
* N.B. THIS SHOULD ONLY BE CALLED FROM WITHIN run() - it is put here for readability.
*
* FIXME this needs to fire the correct events.
*
* @return true if a phone was found, or false otherwise
*/
private boolean _doDetection() {
LOG.trace("ENTER");
detecting = true;
int maxBaudRate = 0;
boolean phoneFound = false;
this.setStatus(SmsModemStatus.SEARCHING, null);
// Set this if there was a problem connecting and you'd like to report the detail.
String lastDetail = null;
for (int currentBaudRate : COMM_SPEEDS) {
if (!isDetecting()) {
disconnect(true);
return false;
}
LOG.debug("Testing baud rate [" + currentBaudRate + "]");
if (maxBaudRate == 0) {
this.setStatus(SmsModemStatus.SEARCHING, Integer.toString(currentBaudRate));
}
resetWatchdog();
cService = new CService(portName, currentBaudRate, "", "", "");
// set this flag to false if the status has been updated within the error handling. It is only
// checked if no phone is found.
try {
cService.serialDriver.open();
// wait for port to open and AT handler to awake
FrontlineUtils.sleep_ignoreInterrupts(500);
cService.serialDriver.send("AT\r");
FrontlineUtils.sleep_ignoreInterrupts(500); // Wait here just in case the phone does not respond very quickly, e.g Nokia 6310i throws IOException without this line.
String response = cService.serialDriver.getResponse();
// If the phone returns an OK, then it looks like it works at this baud
// rate. Save this as the fastest speed, and then search at the next speed.
if (response.contains("OK")) {
maxBaudRate = currentBaudRate;
setStatus(SmsModemStatus.DETECTED, Integer.toString(currentBaudRate));
phoneFound = true;
}
} catch(IOException ex) {
// TODO Here, we've caught an IOException. This could be something
// quite fatal, like the port disappearing, or perhaps it could be
// something less bad? Let's assume it's fatal.
LOG.error("Communication failed", ex);
phoneFound = false;
break;
} catch(TooManyListenersException ex) {
LOG.debug("Too Many Listeners", ex);
lastDetail = ex.getClass().getSimpleName();
if(ex.getMessage() != null) lastDetail += " :: " + ex.getMessage();
} catch(UnsupportedCommOperationException ex) {
LOG.debug("Unsupported Operation", ex);
lastDetail = ex.getClass().getSimpleName();
if(ex.getMessage() != null) lastDetail += " :: " + ex.getMessage();
} catch(NoSuchPortException ex) {
LOG.debug("Port does not exist", ex);
lastDetail = ex.getClass().getSimpleName();
if(ex.getMessage() != null) lastDetail += " :: " + ex.getMessage();
} catch(PortInUseException ex) {
LOG.debug("Port already in use", ex);
lastDetail = ex.getClass().getSimpleName();
if(ex.getMessage() != null) lastDetail += " :: " + ex.getMessage();
} finally {
disconnect(!phoneFound);
}
}
if (!phoneFound) {
disconnect(false);
setStatus(SmsModemStatus.NO_PHONE_DETECTED, lastDetail);
} else {
try {
baudRate = maxBaudRate;
phonePresent = true;
LOG.debug("Phone found, max speed is [" + baudRate + "]");
cService = new CService(portName, maxBaudRate, "", "", "");
cService.serialDriver.open();
// wait for port to open and AT handler to awake
FrontlineUtils.sleep_ignoreInterrupts(500);
setManufacturer(cService.getManufacturer());
setModel(cService.getModel());
this.msisdn = cService.getMsisdn();
LOG.debug("Msisdn [" + this.msisdn + "]");
this.serialNumber = cService.getSerialNo();
LOG.debug("Serial Number [" + this.serialNumber + "]");
this.imsiNumber = cService.getImsi();
LOG.debug("Imsi Number [" + this.imsiNumber + "]");
try {
this.batteryPercent = cService.getBatteryLevel();
LOG.debug("Battery Percent [" + this.batteryPercent + "]");
} catch(NumberFormatException ex) {
LOG.debug("Invalid Battery value [" + this.batteryPercent + "]", ex);
}
this.setStatus(SmsModemStatus.MAX_SPEED_FOUND, Integer.toString(maxBaudRate));
if(!duplicate) {
tryToConnect = true;
}
} catch (Exception ex) {
// It's surprising, but we failed to connect to the phone even though we detected it successfully!
LOG.error("Error while connecting to detected phone", ex);
baudRate = 0;
phonePresent = false;
tryToConnect = false;
this.setStatus(SmsModemStatus.FAILED_TO_CONNECT, ex.getClass().getSimpleName() + ": " + ex.getMessage());
} finally {
disconnect(false);
}
}
LOG.trace("EXIT");
detecting = false;
return phoneFound;
}
/** NB. Currently resets LAST ACTIVE time rather than the time left, or time to die */
/** Resets the watchdog timer - used for calculating timeouts. */
private final void resetWatchdog() {
timeOfLastResponseFromPhone = System.currentTimeMillis();
}
/**
* Checks if there is new messages ready to be read from the attached device.
*
* @throws IOException
* @return The number of new messages retrieved
* @throws SMSLibDeviceException
*/
private int checkForMessages() throws IOException, SMSLibDeviceException {
LOG.trace("ENTER");
resetWatchdog();
LinkedList<CIncomingMessage> messageList = new LinkedList<CIncomingMessage>();
cService.readMessages(messageList, MessageClass.UNREAD); // TODO make this changeable in settings - UNREAD vs ALL
resetWatchdog();
LOG.debug("[" + messageList.size() + "] message(s) received.");
for(CIncomingMessage msg : messageList) {
resetWatchdog();
log(msg);
setId(msg);
addToInboxIfAppropriate(msg);
deleteIfAppropriate(msg);
resetWatchdog();
}
LOG.trace("EXIT");
return messageList.size();
}
private void addToInboxIfAppropriate(CIncomingMessage msg) {
if (useDeliveryReports
|| msg.getType() != CIncomingMessage.MessageType.StatusReport) {
inbox.add(msg);
}
}
private void deleteIfAppropriate(CIncomingMessage msg) throws NotConnectedException, IOException {
if (isDeleteMessagesAfterReceiving()
|| msg.getType() == CIncomingMessage.MessageType.StatusReport) {
//delete msg if is supposed to do it, or if it is a delivery report.
LOG.debug("Removing message [" + msg.getId() + "] from phone.");
cService.deleteMessage(msg);
}
}
private void setId(CIncomingMessage msg) {
if (msisdn != null && msisdn.length() != 0) {
msg.setId(msisdn);
} else if (serialNumber != null && serialNumber.length() != 0) {
msg.setId(serialNumber);
} else if (imsiNumber != null) {
msg.setId(imsiNumber);
}
if(LOG.isDebugEnabled()) {
LOG.debug("Changed ID [" + msg.getId() + "]");
}
}
/** @see SmsService#sendSMS(net.frontlinesms.data.FrontlineMessage) */
public void sendSMS(FrontlineMessage outgoingMessage) {
LOG.trace("ENTER");
outgoingMessage.setStatus(Status.PENDING);
if (msisdn != null && !msisdn.equals("")) {
outgoingMessage.setSenderMsisdn(msisdn);
} else if (serialNumber != null && !serialNumber.equals("")) {
outgoingMessage.setSenderMsisdn(serialNumber);
} // Otherwise it will go with blank sender.
outbox.add(outgoingMessage);
smsListener.outgoingMessageEvent(this, outgoingMessage);
LOG.debug("Message added to outbox. Size is [" + outbox.size() + "]");
LOG.trace("EXIT");
}
/** @param msisdn new value for {@link #msisdn} */
public void setMsisdn(String msisdn) {
this.msisdn = msisdn;
}
public boolean isDeleteMessagesAfterReceiving() {
return deleteMessagesAfterReceiving;
}
public void setDeleteMessagesAfterReceiving(
boolean deleteMessagesAfterReceiving) {
this.deleteMessagesAfterReceiving = deleteMessagesAfterReceiving;
}
public boolean isUseDeliveryReports() {
return useDeliveryReports;
}
public void setUseDeliveryReports(boolean useDeliveryReports) {
this.useDeliveryReports = useDeliveryReports;
}
public void setAutoReconnect(boolean autoReconnect) {
this.autoReconnect = autoReconnect;
}
public boolean isDetecting() {
return detecting;
}
public void setDetecting(boolean detecting) {
this.detecting = detecting;
}
public boolean isTryToConnect() {
return tryToConnect;
}
/**
* Set the baud rate that this phone should connect at.
* @param baudRate new value for {@link #baudRate}
*/
public void setBaudRate(int baudRate) {
this.baudRate = baudRate;
}
/** @see SmsService#isBinarySendingSupported() */
public boolean isBinarySendingSupported() {
return cService.supportsBinarySmsSending();
}
/** @see SmsService#isUcs2SendingSupported() */
public boolean isUcs2SendingSupported() {
return cService.supportsUcs2SmsSending();
}
public boolean isDisconnecting() {
return disconnecting;
}
//> OTHER METHODS
protected void disconnecting() {
disconnecting = true;
if(this.supportsReceive())
this.setUseForReceiving(false);
setUseForSending(false);
this.setStatus(SmsModemStatus.DISCONNECTING, null);
disconnect(true);
disconnecting = false;
}
/**
* Forces the phone to disconnect from the COM port and close all listeners.
*/
public synchronized void disconnect() {
if (isConnected()) {
// Actually disconnecting the phone!
new Thread("Disconnecting [" + this.getServiceName() + "]") {
public void run() {
disconnecting();
}
}.start();
} else {
disconnect(true);
}
}
/**
* Forces the phone to disconnect from the COM port and close all listeners.
* @param setStatus set <code>true</code> if status should be updated. This will likely only be set <code>false</code> when doing phone detection.
*/
private void disconnect(boolean setStatus) {
LOG.trace("ENTER");
try {
cService.disconnect();
} catch(Throwable t) {
// If anything goes wrong in the disconnect, we want to make
// sure we still kill the serialDriver. Any throwables can
// be ignored.
LOG.debug("Error disconnecting", t);
try {
cService.serialDriver.close();
} catch(Throwable th) {
LOG.debug("Error disconnecting", th);
}
}
timeOfLastResponseFromPhone = 0;
disconnected = true;
smsLibConnected = false;
if(setStatus && !isDuplicate()) {
this.setStatus(SmsModemStatus.DISCONNECTED, null);
}
for (FrontlineMessage m : outbox) {
m.setStatus(Status.FAILED);
smsListener.outgoingMessageEvent(this, m);
}
LOG.trace("EXIT");
}
/**
* Sends directly the informed message list.
*
* @param smsMessages
* @return true if there was no problem sending messages, false otherwise.
* @throws IOException
*/
private void sendSmsListDirect(List<FrontlineMessage> smsMessages) throws IOException {
LOG.trace("ENTER");
try {
cService.keepGsmLinkOpen();
for (FrontlineMessage message : smsMessages) {
LOG.debug("Sending [" + message.getTextContent() + "] to [" + message.getRecipientMsisdn() + "]");
COutgoingMessage cMessage;
// If it's a binary message, we set the encoding to send it.
if (message.isBinaryMessage()) {
cMessage = new COutgoingMessage(message.getRecipientMsisdn(), message.getBinaryContent());
cMessage.setDestinationPort(message.getRecipientSmsPort());
} else {
cMessage = new COutgoingMessage(message.getRecipientMsisdn(), message.getTextContent());
}
// Do we require a Delivery Status Report?
cMessage.setStatusReport(this.useDeliveryReports);
// Ok, finished with the message parameters, now send it!
try {
cService.sendMessage(cMessage);
if (cMessage.getRefNo() != -1) {
message.setSmscReference(cMessage.getRefNo());
message.setStatus(Status.SENT);
if(LOG.isDebugEnabled()) LOG.debug("Message [" + message.getTextContent() + "] was sent to [" + message.getRecipientMsisdn() + "]");
} else {
//message not sent
//failed to send
message.setStatus(Status.FAILED);
if(LOG.isDebugEnabled()) LOG.debug("Message [" + message + "] failed to send to [" + message.getRecipientMsisdn() + "]");
}
} catch(Exception ex) {
message.setStatus(Status.FAILED);
if(LOG.isInfoEnabled()) LOG.info("Message [" + message + "] failed to send to [" + message.getRecipientMsisdn() + "]", ex);
} finally {
smsListener.outgoingMessageEvent(this, message);
}
}
} finally {
for (FrontlineMessage m : smsMessages) {
if (m.getStatus() == Status.PENDING) {
outbox.add(m);
}
}
}
LOG.trace("EXIT");
}
public String getServiceIdentification() {
return getMsisdn();
}
public String getDisplayPort() {
return this.getPort();
}
private void log(CIncomingMessage msg) {
if(LOG.isDebugEnabled()) {
LOG.debug("- From [" + msg.getOriginator() + "]"
+ "\n -Message [" + msg.getText() + "]"
+ "\n -ID [" + msg.getId() + "]"
+ "\n -Mem Index [" + msg.getMemIndex() + "]"
+ "\n -Mem Location [" + msg.getMemLocation() + "]"
+ "\n -Message Encoding [" + msg.getMessageEncoding() + "]"
+ "\n -Protocol ID [" + msg.getPid() + "]"
+ "\n -Reference Number [" + msg.getRefNo() + "]"
+ "\n -Type [" + msg.getType() + "]"
+ "\n -Date [" + msg.getDate() + "]");
}
}
//> STATIC HELPER METHODS
}