package com.dataart.android.devicehive.device;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import com.dataart.android.devicehive.DeviceData;
import com.dataart.android.devicehive.DeviceHive;
import com.dataart.android.devicehive.Notification;
import android.content.Context;
import android.util.Log;
/**
* Represents a device, a unit that executes commands and communicates to the
* server. This class is abstract and designed to be subclassed in order to
* represent specific device. Descendants should implement abstract methods of
* {@link CommandRunner} interface in order to execute commands. Also they may
* override various callbacks: {@link #onStartRegistration()},
* {@link #onFinishRegistration()}, {@link #onStartProcessingCommands()},
* {@link #onStopProcessingCommands()},
* {@link #onStartSendingNotification(Notification)},
* {@link #onFinishSendingNotification(Notification)}, etc.
*
*/
public abstract class Device implements CommandRunner {
private DeviceData deviceData;
private final DeviceServiceConnection serviceConnection;
private boolean isRegistered = false;
private final List<Equipment> equipmentList = new LinkedList<Equipment>();
/**
* Construct device with given {@link Context} and {@link DeviceData}
* objects.
*
* @param context
* {@link Context} object. In most cases this should be
* application context which stays alive during the entire life
* of an application.
* @param deviceData
* {@link DeviceData} object which describes various device
* parameters.
*/
public Device(Context context, DeviceData deviceData) {
this.serviceConnection = new DeviceServiceConnection(context);
serviceConnection.setDevice(this);
this.deviceData = deviceData;
}
/**
* Get {@link DeviceData} object which describes various device parameters.
*
* @return {@link DeviceData} object.
*/
public DeviceData getDeviceData() {
return deviceData;
}
/**
* Set Device Hive service URL. This method <b>MUST</b> be called before
* performing registration and other subsequent network communications.
*
* @param url
* URL of Device Hive service.
*/
public void setApiEnpointUrl(String url) {
serviceConnection.setApiEndpointUrl(url);
}
/**
* Get previously set Device Hive service URL.
*
* @return URL of Device Hive service.
*/
public String getApiEndpointUrl() {
return serviceConnection.getApiEndpointUrl();
}
/**
* Enable or disable debug log messages.
*
* @param enabled
* Whether debug log messages enabled or not.
*/
public void setDebugLoggingEnabled(boolean enabled) {
this.serviceConnection.setDebugLoggingEnabled(enabled);
}
/**
* Set timestamp of the last received command. This value is used to reduce
* amount of commands received from the server as a result of poll request
* to only those of them which were received by the server later than the
* time defined by given timestamp.
*
* @param timestamp
* Timestamp of the last received command.
*/
public void setLastCommandPollTimestamp(String timestamp) {
this.serviceConnection.setLastCommandPollTimestamp(timestamp);
}
/**
* Set command poll waiting timeout in seconds (default: 30 seconds,
* maximum: 60 seconds). Specify 0 to disable waiting.
*
* @param timeout
* Command poll waiting timeout in seconds.
*/
public void setCommandPollWaitTimeout(Integer timeout) {
this.serviceConnection.setCommandPollWaitTimeout(timeout);
}
/**
* Initiate device registration. You should set Device Hive service URL
* before performing device registration.
*
* @see {@link #setApiEnpointUrl(String)}, {@link #onStartRegistration()},
* {@link #onFinishRegistration()}, {@link #onFailRegistration()}.
*/
public void registerDevice() {
if (getApiEndpointUrl() == null) {
throw new IllegalStateException(
"API Endpoint URL should be set before registering device");
}
isRegistered = false;
onStartRegistration();
serviceConnection.registerDevice();
}
/**
* Unregister device. Also unregisters all attached equipment.
*/
public void unregisterDevice() {
if (isProcessingCommands()) {
stopProcessingCommands();
}
isRegistered = false;
serviceConnection.unregisterDevice();
}
/**
* Check if this device is registered.
*
* @return true, if this device is registered, otherwise returns false.
*/
public boolean isRegistered() {
return isRegistered;
}
/**
* Check if this device is processing command, i.e. performs commands
* polling and execution.
*
* @return true, if this device is performing commands polling and
* execution, otherwise returns false.
*/
public boolean isProcessingCommands() {
return serviceConnection.isProcessingCommands();
}
/**
* Send device notification. Device should registered to be able to send
* notifications.
*
* @param notification
* {@link Notification} to be sent.
* @see {@link #registerDevice()}.
*/
public void sendNotification(Notification notification) {
if (!isRegistered) {
Log.w(DeviceHive.TAG,
"Device should be registered before sending notifications");
} else {
serviceConnection.sendNotification(notification);
}
}
/**
* Start processing commands. Device will start performing commands polling
* and execution. Device should be registered before processing commands.
*
* @see {@link #registerDevice()}.
*/
public void startProcessingCommands() {
if (!isRegistered) {
Log.w(DeviceHive.TAG,
"Device should be registered before starting receiving commands");
} else {
onStartProcessingCommandsInternal();
serviceConnection.startProcessingCommands();
}
}
/**
* Stop processing commands.
*/
public void stopProcessingCommands() {
serviceConnection.stopProcessingCommands();
onStopProcessingCommandsInternal();
}
/**
* Reload device data. Current device data is updated with instance of
* {@link DeviceData} retrieved from the server.
*
* @see #onFinishReloadingDeviceData(DeviceData)
* @see #onFailReloadingDeviceData()
*/
public void reloadDeviceData() {
serviceConnection.reloadDeviceData();
}
/**
* Attach given {@link Equipment} to the device. {@link Equipment} should be
* attached to the device in order to be able to process commands, send
* notifications and receive various callback method calls. Equipment
* <b>MUST NOT</b> be attached to any other device. Device <b>MUST NOT</b>
* be registered. Changing device equipment after registration isn't
* supported.
*
* @param equipment
* {@link Equipment} to attach.
*/
public void attachEquipment(Equipment equipment) {
if (equipment.getDevice() != null) {
throw new IllegalArgumentException(
"Equiment has already been attached to other device");
} else if (isRegistered) {
throw new IllegalStateException(
"Device has already been registered. You cannot attach equipment after a device is registered");
} else {
equipment.setDevice(this);
this.equipmentList.add(equipment);
this.deviceData.addEquipment(equipment.getEquipmentData());
}
}
/**
* Get equipment list of the device.
*
* @return All previously attached {@link Equipment} objects.
*/
public List<Equipment> getEquipment() {
return equipmentList;
}
/**
* Whether this device should perform attached {@link Equipment}'s
* registration/unregistration callbacks on some other thread.
*
* @return true, if attached {@link Equipment}'s registration/unregistration
* callbacks should be performed on some other thread, otherwise
* return true.
* @see {@link Equipment#onRegisterEquipment()},
* {@link Equipment#onUnregisterEquipment()}.
*/
protected boolean performsEquipmentRegistrationCallbacksAsynchronously() {
return false;
}
/**
* Called at the very beginning of device registration process before
* sending network requests to the server. Override this method if you need
* to perform any additional actions right before registration.
*/
protected void onStartRegistration() {
// no op
}
/**
* Called right after device registration process is finished. Override this
* method if you need to perform any additional actions right after
* registration.
*/
protected void onFinishRegistration() {
// no op
}
/**
* Called if device fails to register. Override this method if you need to
* perform additional actions such as restarting registration.
*/
protected void onFailRegistration() {
// no op
}
/**
* Called right after {@link #startProcessingCommands()} method is called.
* Override this method to perform additional actions before the device
* starts processing commands.
*/
protected void onStartProcessingCommands() {
// no op
}
/**
* Called right after {@link #stopProcessingCommands()} method is called.
* Override this method to perform additional actions right after the device
* stops processing commands.
*/
protected void onStopProcessingCommands() {
// no op
}
/**
* Called when device finishes reloading device data from the server.
*
* @param deviceData
* {@link DeviceData} instance returned by the server.
*/
protected void onFinishReloadingDeviceData(DeviceData deviceData) {
// no op
}
/**
* Called when device to reload device data from the server.
*/
protected void onFailReloadingDeviceData() {
// no op
}
/**
* Called when device or equipment notification is about to be sent.
* Override this method to perform additional actions before a notification
* is sent.
*
* @param notification
* {@link Notification} object.
*/
protected void onStartSendingNotification(Notification notification) {
// no op
}
/**
* Called when device or equipment notification has been sent. Override this
* method to perform additional actions after a notification is sent.
*
* @param notification
* {@link Notification} object.
*/
protected void onFinishSendingNotification(Notification notification) {
// no op
}
/**
* Called when device failed to send notification. Override this method to
* perform any additional initialization or customization.
*
* @param notification
* {@link Notification} object.
*/
protected void onFailSendingNotification(Notification notification) {
// no op
}
/**
* Get {@link Context} which was used to create {@link Device} object.
*
* @return {@link Context} which was used to create {@link Device} object.
*/
protected Context getContext() {
return serviceConnection.getContext();
}
/**
* Run given {@link Runnable} on the main thread. Helper method.
*
* @param runnable
* {@link Runnable} to execute on the main thread.
*/
protected void runOnMainThread(Runnable runnable) {
serviceConnection.runOnMainThread(runnable);
}
/* package */Equipment getEquipmentWithCode(String equipmentCode) {
if (equipmentCode != null) {
for (Equipment eq : equipmentList) {
if (equipmentCode.equals(eq.getEquipmentData().getCode())) {
return eq;
}
}
}
return null;
}
/* package */void equipmentRegistrationFinished(boolean result) {
isRegistered = result;
if (result) {
onFinishRegistration();
} else {
onFailRegistration();
}
}
/* package */void equipmentUnregistrationFinished(boolean result) {
// no op
}
/* package */void onReloadDeviceDataFinishedInternal(DeviceData deviceData) {
updateDeviceData(deviceData);
onFinishReloadingDeviceData(deviceData);
}
private void updateDeviceData(DeviceData newDeviceData) {
this.deviceData = new DeviceData(deviceData.getId(),
deviceData.getKey(), newDeviceData.getName(),
newDeviceData.getStatus(), newDeviceData.getNetwork(),
newDeviceData.getDeviceClass());
this.deviceData.setData((Serializable) newDeviceData.getData());
for (Equipment equipment : equipmentList) {
this.deviceData.addEquipment(equipment.getEquipmentData());
}
}
/* package */void onReloadDeviceDataFailedInternal() {
onFailReloadingDeviceData();
}
private void onStartProcessingCommandsInternal() {
onStartProcessingCommands();
for (Equipment equipment : equipmentList) {
equipment.onStartProcessingCommands();
}
}
private void onStopProcessingCommandsInternal() {
for (Equipment equipment : equipmentList) {
equipment.onStopProcessingCommands();
}
onStopProcessingCommands();
}
}