/**
******************************************************************************
* @file UAVObject.java
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
* @brief Base object for UAVDataObject and UAVMetaObject.
* @see The GNU Public License (GPL) Version 3
*
*****************************************************************************/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.openpilot_nonag.uavtalk;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Observable;
import java.util.Observer;
public abstract class UAVObject {
public class CallbackListener extends Observable {
private final UAVObject parent;
public CallbackListener(UAVObject parent) {
this.parent = parent;
}
public void event () {
synchronized(this) {
setChanged();
notifyObservers(parent);
}
}
public void event (Object data) {
synchronized(this) {
setChanged();
notifyObservers(data);
}
}
}
public class TransactionResult {
public UAVObject obj;
public boolean success;
public TransactionResult(UAVObject obj, boolean success) {
this.obj = obj;
this.success = success;
}
}
private final CallbackListener transactionCompletedListeners = new CallbackListener(this);
public void addTransactionCompleted(Observer o) {
synchronized(transactionCompletedListeners) {
transactionCompletedListeners.addObserver(o);
}
}
public void removeTransactionCompleted(Observer o) {
synchronized(transactionCompletedListeners) {
transactionCompletedListeners.deleteObserver(o);
}
}
void transactionCompleted(boolean status) {
synchronized(transactionCompletedListeners) {
transactionCompletedListeners.event(new TransactionResult(this,status));
}
}
private final CallbackListener updatedListeners = new CallbackListener(this);
public void removeUpdatedObserver(Observer o) {
synchronized(updatedListeners) {
updatedListeners.deleteObserver(o);
}
}
public void addUpdatedObserver(Observer o) {
synchronized(updatedListeners) {
updatedListeners.addObserver(o);
}
}
void updated(boolean manually) {
synchronized(updatedListeners) {
updatedListeners.event();
}
if(manually)
updatedManual();
}
public void updated() { updated(true); };
private final CallbackListener unpackedListeners = new CallbackListener(this);
public void addUnpackedObserver(Observer o) {
synchronized(unpackedListeners) {
unpackedListeners.addObserver(o);
}
}
public void removeUnpackedObserver(Observer o) {
synchronized(unpackedListeners) {
unpackedListeners.deleteObserver(o);
}
}
void unpacked() {
synchronized(unpackedListeners) {
unpackedListeners.event();
}
}
private final CallbackListener updatedAutoListeners = new CallbackListener(this);
public void addUpdatedAutoObserver(Observer o) {
synchronized(updatedAutoListeners) {
updatedAutoListeners.addObserver(o);
}
}
public void removeUpdatedAutoObserver(Observer o) {
synchronized(updatedAutoListeners) {
updatedAutoListeners.deleteObserver(o);
}
}
void updatedAuto() {
synchronized(updatedAutoListeners) {
updatedAutoListeners.event();
}
}
private final CallbackListener updatedManualListeners = new CallbackListener(this);
public void addUpdatedManualObserver(Observer o) {
synchronized(updatedManualListeners) {
updatedManualListeners.addObserver(o);
}
}
public void removeUpdatedManualObserver(Observer o) {
synchronized(updatedManualListeners) {
updatedManualListeners.deleteObserver(o);
}
}
void updatedManual() {
synchronized(updatedManualListeners) {
updatedManualListeners.event();
}
}
private final CallbackListener updatedPeriodicListeners = new CallbackListener(this);
public void addUpdatedPeriodicObserver(Observer o) {
synchronized(updatedPeriodicListeners) {
updatedPeriodicListeners.addObserver(o);
}
}
public void removeUpdatedPeriodicObserver(Observer o) {
synchronized(updatedPeriodicListeners) {
updatedPeriodicListeners.deleteObserver(o);
}
}
void updatedPeriodic() {
synchronized(updatedPeriodicListeners) {
updatedPeriodicListeners.event();
}
}
private final CallbackListener updateRequestedListeners = new CallbackListener(this);
public void addUpdateRequestedObserver(Observer o) {
synchronized(updateRequestedListeners) {
updateRequestedListeners.addObserver(o);
}
}
public void removeUpdateRequestedObserver(Observer o) {
synchronized(updateRequestedListeners) {
updateRequestedListeners.deleteObserver(o);
}
}
public void updateRequested() {
synchronized(updateRequestedListeners) {
updateRequestedListeners.event();
}
}
public abstract boolean isMetadata();
/**
* Object update mode
*/
public enum UpdateMode {
UPDATEMODE_MANUAL, /** Manually update object, by calling the updated() function */
UPDATEMODE_PERIODIC, /** Automatically update object at periodic intervals */
UPDATEMODE_ONCHANGE, /** Only update object when its data changes */
UPDATEMODE_THROTTLED /** Object is updated on change, but not more often than the interval time */
};
/**
* Access mode
*/
public enum AccessMode {
ACCESS_READWRITE, ACCESS_READONLY
};
public final static int UAVOBJ_ACCESS_SHIFT = 0;
public final static int UAVOBJ_GCS_ACCESS_SHIFT = 1;
public final static int UAVOBJ_TELEMETRY_ACKED_SHIFT = 2;
public final static int UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT = 3;
public final static int UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT = 4;
public final static int UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT = 6;
public final static int UAVOBJ_LOGGING_UPDATE_MODE_SHIFT = 8;
public final static int UAVOBJ_UPDATE_MODE_MASK = 0x3;
public final static class Metadata {
/**
* Object metadata, each object has a meta object that holds its metadata. The metadata define
* properties for each object and can be used by multiple modules (e.g. telemetry and logger)
*
* The object metadata flags are packed into a single 16 bit integer.
* The bits in the flag field are defined as:
*
* Bit(s) Name Meaning
* ------ ---- -------
* 0 access Defines the access level for the local transactions (readonly=0 and readwrite=1)
* 1 gcsAccess Defines the access level for the local GCS transactions (readonly=0 and readwrite=1), not used in the flight s/w
* 2 telemetryAcked Defines if an ack is required for the transactions of this object (1:acked, 0:not acked)
* 3 gcsTelemetryAcked Defines if an ack is required for the transactions of this object (1:acked, 0:not acked)
* 4-5 telemetryUpdateMode Update mode used by the telemetry module (UAVObjUpdateMode)
* 6-7 gcsTelemetryUpdateMode Update mode used by the GCS (UAVObjUpdateMode)
* 8-9 loggingUpdateMode Update mode used by the logging module (UAVObjUpdateMode)
*/
public int flags; /** Defines flags for update and logging modes and whether an update should be ACK'd (bits defined above) */
/** Update period used by the telemetry module (only if telemetry mode is PERIODIC) */
public int flightTelemetryUpdatePeriod;
/** Update period used by the GCS (only if telemetry mode is PERIODIC) */
public int gcsTelemetryUpdatePeriod;
/** Update period used by the GCS (only if telemetry mode is PERIODIC) */
public int loggingUpdatePeriod;
/**
* Update period used by the logging module (only if logging mode is
* PERIODIC)
*/
/**
* @brief Helper method for metadata accessors
* @param var The starting value
* @param shift The offset of these bits
* @param value The new value
* @param mask The mask of these bits
* @return
*/
private void SET_BITS(int shift, int value, int mask) {
this.flags = (this.flags & ~(mask << shift)) | (value << shift);
}
/**
* Get the UAVObject metadata access member
* \return the access type
*/
public AccessMode GetFlightAccess()
{
return AccessModeEnum((this.flags >> UAVOBJ_ACCESS_SHIFT) & 1);
}
/**
* Set the UAVObject metadata access member
* \param[in] mode The access mode
*/
public void SetFlightAccess(Metadata metadata, AccessMode mode)
{
// Need to convert java enums which have no numeric value to bits
SET_BITS(UAVOBJ_ACCESS_SHIFT, AccessModeNum(mode), 1);
}
/**
* Get the UAVObject metadata GCS access member
* \return the GCS access type
*/
public AccessMode GetGcsAccess()
{
return AccessModeEnum((this.flags >> UAVOBJ_GCS_ACCESS_SHIFT) & 1);
}
/**
* Set the UAVObject metadata GCS access member
* \param[in] mode The access mode
*/
public void SetGcsAccess(Metadata metadata, AccessMode mode) {
// Need to convert java enums which have no numeric value to bits
SET_BITS(UAVOBJ_GCS_ACCESS_SHIFT, AccessModeNum(mode), 1);
}
/**
* Get the UAVObject metadata telemetry acked member
* \return the telemetry acked boolean
*/
public boolean GetFlightTelemetryAcked() {
return (((this.flags >> UAVOBJ_TELEMETRY_ACKED_SHIFT) & 1) == 1);
}
/**
* Set the UAVObject metadata telemetry acked member
* \param[in] val The telemetry acked boolean
*/
public void SetFlightTelemetryAcked(boolean val) {
SET_BITS(UAVOBJ_TELEMETRY_ACKED_SHIFT, val ? 1 : 0, 1);
}
/**
* Get the UAVObject metadata GCS telemetry acked member
* \return the telemetry acked boolean
*/
public boolean GetGcsTelemetryAcked() {
return ((this.flags >> UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT) & 1) == 1;
}
/**
* Set the UAVObject metadata GCS telemetry acked member
* \param[in] val The GCS telemetry acked boolean
*/
public void SetGcsTelemetryAcked(boolean val) {
SET_BITS(UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT, val ? 1 : 0, 1);
}
/**
* Maps from the bitfield number to the symbolic java enumeration
* @param num The value in the bitfield after shifting
* @return The update mode
*/
public static AccessMode AccessModeEnum(int num) {
switch(num) {
case 0:
return AccessMode.ACCESS_READONLY;
case 1:
return AccessMode.ACCESS_READWRITE;
}
return AccessMode.ACCESS_READONLY;
}
/**
* Maps from the java symbolic enumeration of update mode to the bitfield value
* @param e The update mode
* @return The numeric value to use on the wire
*/
public static int AccessModeNum(AccessMode e) {
switch(e) {
case ACCESS_READONLY:
return 0;
case ACCESS_READWRITE:
return 1;
}
return 0;
}
/**
* Maps from the bitfield number to the symbolic java enumeration
* @param num The value in the bitfield after shifting
* @return The update mode
*/
public static UpdateMode UpdateModeEnum(int num) {
switch(num) {
case 0:
return UpdateMode.UPDATEMODE_MANUAL;
case 1:
return UpdateMode.UPDATEMODE_PERIODIC;
case 2:
return UpdateMode.UPDATEMODE_ONCHANGE;
default:
return UpdateMode.UPDATEMODE_THROTTLED;
}
}
/**
* Maps from the java symbolic enumeration of update mode to the bitfield value
* @param e The update mode
* @return The numeric value to use on the wire
*/
public static int UpdateModeNum(UpdateMode e) {
switch(e) {
case UPDATEMODE_MANUAL:
return 0;
case UPDATEMODE_PERIODIC:
return 1;
case UPDATEMODE_ONCHANGE:
return 2;
case UPDATEMODE_THROTTLED:
return 3;
}
return 0;
}
/**
* Get the UAVObject metadata telemetry update mode
* \return the telemetry update mode
*/
public UpdateMode GetFlightTelemetryUpdateMode() {
return UpdateModeEnum((this.flags >> UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT) & UAVOBJ_UPDATE_MODE_MASK);
}
/**
* Set the UAVObject metadata telemetry update mode member
* \param[in] metadata The metadata object
* \param[in] val The telemetry update mode
*/
public void SetFlightTelemetryUpdateMode(UpdateMode val) {
SET_BITS(UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT, UpdateModeNum(val), UAVOBJ_UPDATE_MODE_MASK);
}
/**
* Get the UAVObject metadata GCS telemetry update mode
* \param[in] metadata The metadata object
* \return the GCS telemetry update mode
*/
public UpdateMode GetGcsTelemetryUpdateMode() {
return UpdateModeEnum((this.flags >> UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT) & UAVOBJ_UPDATE_MODE_MASK);
}
/**
* Set the UAVObject metadata GCS telemetry update mode member
* \param[in] metadata The metadata object
* \param[in] val The GCS telemetry update mode
*/
public void SetGcsTelemetryUpdateMode(UpdateMode val) {
SET_BITS(UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT, UpdateModeNum(val), UAVOBJ_UPDATE_MODE_MASK);
}
};
public UAVObject(long objID, Boolean isSingleInst, String name) {
this.objID = objID;
this.instID = 0;
this.isSingleInst = isSingleInst;
this.name = name;
// this.mutex = new QMutex(QMutex::Recursive);
};
public synchronized void initialize(long instID) {
this.instID = instID;
}
/**
* Initialize objects' data fields
*
* @param fields
* List of fields held by the object
* @param data
* Pointer to that actual object data, this is needed by the
* fields to access the data
* @param numBytes
* Number of bytes in the object (total, including all fields)
* @throws Exception
* When unable to unpack a field
*/
public synchronized void initializeFields(List<UAVObjectField> fields, ByteBuffer data,
int numBytes) {
this.numBytes = numBytes;
this.fields = fields;
// Initialize fields
for (int n = 0; n < fields.size(); ++n) {
fields.get(n).initialize(this);
}
unpack(data);
}
/**
* Get the object ID
*/
public long getObjID() {
return objID;
}
/**
* Get the instance ID
*/
public long getInstID() {
return instID;
}
/**
* Returns true if this is a single instance object
*/
public boolean isSingleInstance() {
return isSingleInst;
}
/**
* Get the name of the object
*/
public String getName() {
return name;
}
/**
* Get the description of the object
*
* @return The description of the object
*/
public String getDescription() {
return description;
}
/**
* Set the description of the object
*
* @param The
* description of the object
* @return
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Get the total number of bytes of the object's data
*/
public int getNumBytes() {
return numBytes;
}
// /**
// * Request that this object is updated with the latest values from the
// autopilot
// */
// /* void UAVObject::requestUpdate()
// {
// emit updateRequested(this);
// } */
//
// /**
// * Signal that the object has been updated
// */
// /* void UAVObject::updated()
// {
// emit objectUpdatedManual(this);
// emit objectUpdated(this);
// } */
//
// /**
// * Lock mutex of this object
// */
// /* void UAVObject::lock()
// {
// mutex->lock();
// } */
//
// /**
// * Lock mutex of this object
// */
// /* void UAVObject::lock(int timeoutMs)
// {
// mutex->tryLock(timeoutMs);
// } */
//
// /**
// * Unlock mutex of this object
// */
// /* void UAVObject::unlock()
// {
// mutex->unlock();
// } */
//
// /**
// * Get object's mutex
// */
// QMutex* UAVObject::getMutex()
// {
// return mutex;
// }
/**
* Get the number of fields held by this object
*/
public int getNumFields() {
return fields.size();
}
/**
* Get the object's fields
*/
public synchronized List<UAVObjectField> getFields() {
return fields;
}
/**
* Get a specific field
*
* @throws Exception
* @returns The field or NULL if not found
*/
public UAVObjectField getField(String name) {
// Look for field
ListIterator<UAVObjectField> li = fields.listIterator();
while (li.hasNext()) {
UAVObjectField field = li.next();
if (field.getName().equals(name))
return field;
}
//throw new Exception("Field not found");
return null;
}
/**
* Pack the object data into a byte array
*
* @param dataOut
* ByteBuffer to receive the data.
* @throws Exception
* @returns The number of bytes copied
* @note The array must already have enough space allocated for the object
*/
public synchronized int pack(ByteBuffer dataOut) throws Exception {
if (dataOut.remaining() < getNumBytes())
throw new Exception("Not enough bytes in ByteBuffer to pack object");
int numBytes = 0;
ListIterator<UAVObjectField> li = fields.listIterator();
while (li.hasNext()) {
UAVObjectField field = li.next();
numBytes += field.pack(dataOut);
}
return numBytes;
}
/**
* Unpack the object data from a byte array
*
* @param dataIn
* The ByteBuffer to pull data from
* @throws Exception
* @returns The number of bytes copied
*/
public synchronized int unpack(ByteBuffer dataIn) {
if( dataIn == null )
return 0;
// QMutexLocker locker(mutex);
int numBytes = 0;
ListIterator<UAVObjectField> li = fields.listIterator();
while (li.hasNext()) {
UAVObjectField field = li.next();
numBytes += field.unpack(dataIn);
}
// Trigger all the listeners for the unpack event
unpacked();
updated(false);
return numBytes;
}
// /**
// * Save the object data to the file.
// * The file will be created in the current directory
// * and its name will be the same as the object with
// * the .uavobj extension.
// * @returns True on success, false on failure
// */
// bool UAVObject::save()
// {
// QMutexLocker locker(mutex);
//
// // Open file
// QFile file(name + ".uavobj");
// if (!file.open(QFile::WriteOnly))
// {
// return false;
// }
//
// // Write object
// if ( !save(file) )
// {
// return false;
// }
//
// // Close file
// file.close();
// return true;
// }
//
// /**
// * Save the object data to the file.
// * The file is expected to be already open for writting.
// * The data will be appended and the file will not be closed.
// * @returns True on success, false on failure
// */
// bool UAVObject::save(QFile& file)
// {
// QMutexLocker locker(mutex);
// quint8 buffer[numBytes];
// quint8 tmpId[4];
//
// // Write the object ID
// qToLittleEndian<quint32>(objID, tmpId);
// if ( file.write((const char*)tmpId, 4) == -1 )
// {
// return false;
// }
//
// // Write the instance ID
// if (!isSingleInst)
// {
// qToLittleEndian<quint16>(instID, tmpId);
// if ( file.write((const char*)tmpId, 2) == -1 )
// {
// return false;
// }
// }
//
// // Write the data
// pack(buffer);
// if ( file.write((const char*)buffer, numBytes) == -1 )
// {
// return false;
// }
//
// // Done
// return true;
// }
//
// /**
// * Load the object data from a file.
// * The file will be openned in the current directory
// * and its name will be the same as the object with
// * the .uavobj extension.
// * @returns True on success, false on failure
// */
// bool UAVObject::load()
// {
// QMutexLocker locker(mutex);
//
// // Open file
// QFile file(name + ".uavobj");
// if (!file.open(QFile::ReadOnly))
// {
// return false;
// }
//
// // Load object
// if ( !load(file) )
// {
// return false;
// }
//
// // Close file
// file.close();
// return true;
// }
//
// /**
// * Load the object data from file.
// * The file is expected to be already open for reading.
// * The data will be read and the file will not be closed.
// * @returns True on success, false on failure
// */
// bool UAVObject::load(QFile& file)
// {
// QMutexLocker locker(mutex);
// quint8 buffer[numBytes];
// quint8 tmpId[4];
//
// // Read the object ID
// if ( file.read((char*)tmpId, 4) != 4 )
// {
// return false;
// }
//
// // Check that the IDs match
// if (qFromLittleEndian<quint32>(tmpId) != objID)
// {
// return false;
// }
//
// // Read the instance ID
// if ( file.read((char*)tmpId, 2) != 2 )
// {
// return false;
// }
//
// // Check that the IDs match
// if (qFromLittleEndian<quint16>(tmpId) != instID)
// {
// return false;
// }
//
// // Read and unpack the data
// if ( file.read((char*)buffer, numBytes) != numBytes )
// {
// return false;
// }
// unpack(buffer);
//
// // Done
// return true;
// }
/**
* Return a string with the object information
*/
@Override
public String toString() {
return toStringBrief(); // + toStringData();
}
/**
* Return a string with the object information (only the header)
*/
public String toStringBrief() {
return getName() + " (" + Long.toHexString(getObjID()) + ":" + getInstID() + " " + getNumBytes() + ")";
}
/**
* Return a string with the object information (only the data)
*/
public String toStringData() {
String s = new String();
ListIterator<UAVObjectField> li = fields.listIterator();
while (li.hasNext()) {
UAVObjectField field = li.next();
s += field.toString();
}
return s;
}
// /**
// * Emit the transactionCompleted event (used by the UAVTalk plugin)
// */
// void UAVObject::emitTransactionCompleted(bool success)
// {
// emit transactionCompleted(this, success);
// }
/**
* Java specific functions
*/
@Override
public synchronized UAVObject clone() {
UAVObject newObj = clone();
List<UAVObjectField> newFields = new ArrayList<UAVObjectField>();
ListIterator<UAVObjectField> li = fields.listIterator();
while(li.hasNext()) {
UAVObjectField nf = li.next().clone();
nf.initialize(newObj);
newFields.add(nf);
}
newObj.initializeFields(newFields, ByteBuffer.allocate(numBytes), numBytes);
return newObj;
}
/**
* Abstract functions
*/
public abstract void setMetadata(Metadata mdata);
public abstract Metadata getMetadata();
public abstract Metadata getDefaultMetadata();
/**
* Private data for the object, common to all
*/
protected long objID;
protected long instID;
protected boolean isSingleInst;
protected String name;
protected String description;
protected int numBytes;
// TODO: QMutex* mutex;
// protected ByteBuffer data;
protected List<UAVObjectField> fields;
}