/* * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * 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 version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.javame.sensor; import java.io.*; import java.util.*; import javax.microedition.sensor.*; public class Sensor implements SensorInfo, SensorConnection, ChannelDataListener, AvailabilityNotifier { private class SensorEventQueue implements Runnable { /** Sensor message queue. */ private Vector messages = new Vector(); /** Stop flag. */ private boolean isStop = true; /** Flag of notification. */ private boolean isNotify; /** Sensor state */ private int state = SensorConnection.STATE_CLOSED; /** Sensor state for data collecting */ private int stateData = StatesEvents.SENSOR_IDLE; /** Thread of event queue. */ private Thread eventQueueThread; /** * Put message to queue. * * @param msg message code */ private synchronized void putMessage(int msg) { messages.addElement(new Integer(msg)); isNotify = true; notify(); } /** * Gets the current state. */ private int getState() { return state; } /** * Sets the current state. */ private synchronized void setState(int state) { this.state = state; } /** * Gets the current data state. */ private int getStateData() { return stateData; } /** * Sets the current data state. */ private synchronized void setStateData(int state) { stateData = state; } /** * Start process the queue. */ private synchronized void start() { isStop = false; eventQueueThread = new Thread(this); eventQueueThread.start(); } /** * Stop process the queue. */ private void stop() { if (!isStop) { synchronized (this) { isStop = true; isNotify = true; notify(); } try { eventQueueThread.join(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } /** * Process the queue. */ public void run() { while (!isStop) { synchronized (this) { if (isStop) { break; } if (messages.size() == 0) { isNotify = false; while (!isNotify) { try { wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } // Process messages while (!isStop && messages.size() > 0) { int msg = ((Integer)messages.firstElement()).intValue(); messages.removeElementAt(0); switch (msg) { case StatesEvents.IND_DATA: // data from channel was received if (stateData == StatesEvents.WAIT_DATA) { switch (state) { case SensorConnection.STATE_OPENED: // getData(...) setNotify(); stateData = StatesEvents.SENSOR_IDLE; break; case SensorConnection.STATE_LISTENING: // call listener.dataReceived(...) callDataListener(); break; } } break; case StatesEvents.IND_ERROR: // error from channel callDataErrorListener(); break; case StatesEvents.STOP_GET_DATA: // data collecting is need to stop if (stateData == StatesEvents.WAIT_DATA) { stopGetData(); stateData = StatesEvents.WAIT_CLOSE_DATA; } break; case StatesEvents.STOP_GET_DATA_CONF: // data collecting stop confirmation if (stateData == StatesEvents.WAIT_CLOSE_DATA) { if (isAllChannelsAnswered()) { stateData = StatesEvents.SENSOR_IDLE; if (state == SensorConnection.STATE_LISTENING) { setNotify(); } } } break; } } } } } // end of the private class declaration /* Sensor information */ private int number; private String description; private String contextType; private String model; private int maxBufferSize; private int connType; private String quantity; /** device-specific code relating the whole sensor */ private SensorDevice sensorDevice = null; /** Channel count. */ private int channelCount; /** Data array which read from sensor. */ private DataImpl[] retData; /** Listener for notification about data receiving. */ private DataListener listener; /** Last channel status. */ private int channelStatus; /** Channel number. */ private int channelNumber; /** Error timestamp. */ private long errorTimestamp; /** Sensor properties */ SensorProperties props; /** Channels */ private ChannelImpl[] channels; /** Availability push supporting flag. */ private boolean isSensorAvailabilityPushSupported; /** Condition push supporting flag. */ private boolean isSensorConditionPushSupported; /** Flag of notification. */ private boolean isNotify; /** Error codes table */ private Hashtable errorCodes; /** Sensor event queue. */ private SensorEventQueue eventQueue; /** Remove datallistener counter. */ private volatile int remDataListCount = 0; /** * Creates a new instance of Sensor. * * @param number number of the sensor */ Sensor(int number) { this.number = number; initFields(); eventQueue = new SensorEventQueue(); } /** * Gets the sensor's ChannelInfo array * representing channels of the sensor. * * @return ChannelInfo array */ public ChannelInfo[] getChannelInfos() { return channels; } /** * Gets the connection type. * * @return one of values: CONN_EMBEDDED, CONN_REMOTE, * CONN_SHORT_RANGE_WIRELESS or CONN_WIRED */ public int getConnectionType() { return connType; } /** * Gets the context type. * * @return one of values: CONTEXT_TYPE_USER, * CONTEXT_TYPE_DEVICE or CONTEXT_TYPE_AMBIENT */ public String getContextType() { return contextType; } /** * Gets the description of sensor. * * @return the readable description of sensor */ public String getDescription() { return description; } /** * Gets the maximal data buffer length of sensor. * * @return maximal data buffer length of sensor */ public int getMaxBufferSize() { return maxBufferSize; } /** * Gets the model name of sensor. * * @return the model name of sensor */ public String getModel() { return model; } /** * Gets the quantity of sensor. * * @return the quantity of sensor */ public String getQuantity() { return quantity; } /** * Gets URL needed to open SensorConnection. * * @return the URL needed to open SensorConnection */ public String getUrl() { return SensorUrl.createUrl(this); } /** * Checks is sensor supports availability push. * * @return true when sensor supports availability push */ public boolean isAvailabilityPushSupported() { return isSensorAvailabilityPushSupported; } /** * Checks is sensor available. * * @return true when sensor is available else false */ public boolean isAvailable() { return sensorDevice.isAvailable(); } /** * Checks is sensor supports condition push. * * @return true when sensor supports condition push */ public boolean isConditionPushSupported() { return isSensorConditionPushSupported; } /** * Gets the property of sensor by name. * * @param name the name of property * @return the quantity of sensor */ public Object getProperty(String name) { if (name == null) { throw new NullPointerException(); } if (!props.containsName(name)) { throw new IllegalArgumentException(); } return props.getProperty(name); } /** * Gets the array of property names of sensor. * * @return an array of property keys for the sensor */ public String[] getPropertyNames() { return props.getPropertyNames(); } /** * Checks is sensor contains given quantity and context type. * * @return true when sensor contains given quantity and context type */ public boolean matches(String quantity, String contextType) { return ((quantity == null) || quantity.equals(this.quantity)) && ((contextType == null) || contextType.equals(this.contextType)); } /** * Checks is sensor matches to given URL. * * @return true when sensor matches to given URL */ public boolean matches(SensorUrl url) { String location = (String)props.getProperty(PROP_LOCATION); return quantity.equals(url.getQuantity()) && ((url.getContextType() == null) || url.getContextType().equals(contextType)) && ((url.getLocation() == null) || url.getLocation().equals(location)) && ((url.getModel() == null) || url.getModel().equals(model)); } /** * Opens sensor. * * @throws IOException if the sensor has wrong state */ public void open() throws IOException { if (eventQueue.getState() != STATE_CLOSED) { // Decide later if we allow multiple connections to the same sensor // JSR 256 spec leaves this to implementation throw new IOException("Sensor is already opened"); } boolean isInitOk = true; isInitOk &= sensorDevice.initSensor(); for (int i = 0; isInitOk && i < channels.length; i++) { isInitOk &= channels[i].initChannel(); isInitOk &= channels[i].getChannelDevice().initChannel(); } if (!isInitOk) { throw new IOException("Sensor start fails"); } eventQueue.start(); eventQueue.setState(STATE_OPENED); } /* * SensorConnection methods */ public int getState() { return eventQueue.getState(); } public Channel getChannel(ChannelInfo channelInfo) { if (channelInfo == null) { throw new NullPointerException(); } /* In this implementation ChannelInfo is the same as Channel. So, we just * have to check that this specific sensor owns the channel. */ for (int i = 0; i < channels.length; i++) { if (channelInfo == channels[i]) { return channels[i]; } } /* This is not a channel from this sensor */ throw new IllegalArgumentException("This channel is not from this sensor"); } /** * Fetches data in the synchronous mode. * * @param bufferSize the size of the data buffer ( > 0) * @return the collected data of all the channels * of this sensor * @throws IllegalArgumentException - when bufferSize < 1 * or if bufferSize > the maximum size of the buffer * @throws java.io.IOException - if the state is STATE_CLOSED * or if any input/output problems are occured * @throws java.lang.IllegalStateException - in case of the * state is STATE_LISTENING */ public Data[] getData(int bufferSize) throws java.io.IOException { return getData(bufferSize, 0L, false, false, false); } /** * Retrieves data in the synchronous mode. * * @param bufferSize - the size of the data buffer * @param bufferingPeriod - the time to buffer values * @param isTimestampIncluded - if true timestamps should be * included in returned Data objects * @param isUncertaintyIncluded - if true uncertainties should be * included in returned Data objects * @param isValidityIncluded - if true validities should be * included in returned Data objects * @return collected data of all the channels of this sensor. * @throws java.lang.IllegalArgumentException - if the both, bufferSize * and bufferingPeriod, have values less than 1, or if bufferSize * exceeds the maximum size of the buffer * @throws java.lang.IllegalStateException - if the state is STATE_LISTENING * @throws java.io.IOException - if the state is STATE_CLOSED * or if any input/output problems are occured */ public synchronized Data[] getData(int bufferSize, long bufferingPeriod, boolean isTimestampIncluded, boolean isUncertaintyIncluded, boolean isValidityIncluded) throws java.io.IOException { if ((bufferSize < 1 && bufferingPeriod < 1) || bufferSize > maxBufferSize) { throw new IllegalArgumentException( "Wrong buffer size or/and period values"); } int state = eventQueue.getState(); if (state == STATE_LISTENING) { throw new IllegalStateException("Wrong state"); } if (state == STATE_CLOSED) { throw new IOException("Wrong state"); } if (bufferSize < 1) { bufferSize = maxBufferSize; } /* Sending signals to each channel to start getting data */ long startTime = System.currentTimeMillis(); this.listener = null; channelCount = 0; channelStatus = ValueListener.DATA_READ_OK; retData = new DataImpl[channels.length]; eventQueue.setStateData(StatesEvents.WAIT_DATA); for (int i = 0; i < channels.length; i++) { channels[i].startGetData(this, bufferSize, bufferingPeriod, isTimestampIncluded, isUncertaintyIncluded, isValidityIncluded, false, startTime); } isNotify = false; while (!isNotify) { try { wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } if (channelStatus != ValueListener.DATA_READ_OK) { throw new IOException("Read data error with code " + channelStatus + " on channel " + channelNumber); } return retData; } private native void doGetSensorModel(int n, SensorModel m); private void initFields() { SensorModel sensorModel = new SensorModel(); doGetSensorModel(number, sensorModel); description = sensorModel.description; quantity = sensorModel.quantity; contextType = sensorModel.contextType; model = sensorModel.model; maxBufferSize = sensorModel.maxBufferSize; connType = sensorModel.connectionType; isSensorAvailabilityPushSupported = sensorModel.availabilityPush; isSensorConditionPushSupported = sensorModel.conditionPush; sensorDevice = DeviceFactory.generateSensor(number); props = sensorModel.getProperties(); errorCodes = sensorModel.getErrorCodes(); channels = new ChannelImpl[sensorModel.channelCount]; for (int i=0; i<sensorModel.channelCount; ++i){ channels[i] = new ChannelImpl(number,i); channels[i].setSensor(this); } } /** * Notification about data from channel. * * @param number channel number * @param data data instance from channel */ public void channelDataReceived(int number, DataImpl data) { retData[number] = data; if (isAllChannelsAnswered()) { eventQueue.putMessage(StatesEvents.IND_DATA); channelCount = 0; } } /** * Notification about channel error. * * @param number channel number * @param errorCode code of channel error * @param timeStamp timestamp of error */ public void channelErrorReceived(int number, int errorCode, long timestamp) { if (eventQueue.getState() == STATE_LISTENING && eventQueue.getStateData() == StatesEvents.WAIT_DATA && listener instanceof DataAndErrorListener) { channelStatus = errorCode; channelNumber = number; errorTimestamp = timestamp; eventQueue.putMessage(StatesEvents.IND_ERROR); } } /** * Notification stop collecting data from channel. * * @param number channel number */ void confirmStopData(int number) { eventQueue.putMessage(StatesEvents.STOP_GET_DATA_CONF); } public SensorInfo getSensorInfo() { return this; } /** * Removes the DataListener registered to this SensorConnection. * * @throws java.lang.IllegalStateException - if this SensorConnection * is already closed */ public void removeDataListener() { int state = eventQueue.getState(); if (state == STATE_CLOSED) { throw new IllegalStateException("Connection is already closed"); } if (state == STATE_LISTENING) { if (remDataListCount > 0) { return; } remDataListCount++; synchronized (this) { eventQueue.putMessage(StatesEvents.STOP_GET_DATA); isNotify = false; while (!isNotify) { try { wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } listener = null; eventQueue.setState(STATE_OPENED); } remDataListCount--; } } /** * Registers a DataListener to receive collected data asynchronously. * * @param listener - DataListener to be registered * @param bufferSize - size of the buffer, value must be > 0 * @throws java.lang.NullPointerException - if the listener is null * @throws java.lang.IllegalArgumentException - if the bufferSize < 1, * or if bufferSize exceeds the maximum size of the buffer * @throws java.lang.IllegalStateException - if this SensorConnection * is already closed */ public void setDataListener(DataListener listener, int bufferSize) { setDataListener(listener, bufferSize, 0L, false, false, false); } /** * Registers a DataListener to receive collected data asynchronously. * * @param listener - the listener to be registered * @param bufferSize - the size of the buffer of the data values, bufferSize < 1 * means the size is left undefined * @param bufferingPeriod - the time in milliseconds to buffer values inside * one Data object. bufferingPeriod < 1 means the period is left undefined. * @param isTimestampIncluded - if true timestamps should be included in * returned Data objects * @param isUncertaintyIncluded - if true uncertainties should be included * in returned Data objects * @param isValidityIncluded - if true validities should be included in * returned Data objects * @throws java.lang.NullPointerException - if the listener is null * @throws java.lang.IllegalArgumentException - if the bufferSize * and the bufferingPeriod both are < 1 or if bufferSize exceeds * the maximum size of the buffer * @throws java.lang.IllegalStateException - if this SensorConnection is already closed */ public void setDataListener(DataListener listener, int bufferSize, long bufferingPeriod, boolean isTimestampIncluded, boolean isUncertaintyIncluded, boolean isValidityIncluded) { if ((bufferSize < 1 && bufferingPeriod < 1) || bufferSize > maxBufferSize) { throw new IllegalArgumentException( "Wrong buffer size or/and period values"); } int state = eventQueue.getState(); if (state == STATE_CLOSED) { throw new IllegalStateException("Connection is closed"); } if (listener == null) { throw new NullPointerException("Listener is null"); } if (state == STATE_LISTENING) { removeDataListener(); } eventQueue.setState(STATE_LISTENING); if (bufferSize < 1) { bufferSize = maxBufferSize; } /* Sending signals to each channel to start getting data */ long startTime = System.currentTimeMillis(); this.listener = listener; channelCount = 0; channelStatus = ValueListener.DATA_READ_OK; eventQueue.setStateData(StatesEvents.WAIT_DATA); retData = new DataImpl[channels.length]; for (int i = 0; i < channels.length; i++) { channels[i].startGetData(this, bufferSize, bufferingPeriod, isTimestampIncluded, isUncertaintyIncluded, isValidityIncluded, true, startTime); } } /** * Calls data listener. * */ void callDataListener() { new Thread(new CallDataListener(this, listener, retData)).start(); } /** * Calls data error listener. * */ void callDataErrorListener() { new Thread(new CallDataListener(this, errorTimestamp, listener, channelStatus)).start(); } public void close() throws IOException { sensorDevice.finishSensor(); // ignore the success flag if (eventQueue.getState() == STATE_LISTENING) { removeDataListener(); } for (int i = 0; i < channels.length; i++) { channels[i].stopChannel(); } eventQueue.stop(); eventQueue.setState(STATE_CLOSED); } /** * Gets ChannelDevice instance (i3tests only). * * @param number channel number * @return ChannelDevice instance */ ChannelDevice getChannelDevice(int number) { ChannelDevice device = null; if (0 <= number && number < channels.length) { device = channels[number].getChannelDevice(); } return device; } /** Smart conversion to String. It's a debugging means. * * @return human-readable representation */ public String toString() { // IMPL_NOTE: this is needed only for debugging.N return super.toString()+"{ quantity="+quantity+" contextType="+contextType+" model="+model +" prop:location="+props.getProperty(PROP_LOCATION)+"}"; } /** * Inform SensorDevice to start sending availability informations. * * @param listener which will receive the notifications */ public void startMonitoringAvailability(AvailabilityListener listener) { sensorDevice.startMonitoringAvailability(listener); } /** * Inform SensorDevice to stop sending availability informations. * * @param listener which will stop receiving the notifications */ public void stopMonitoringAvailability(AvailabilityListener listener) { sensorDevice.stopMonitoringAvailability(); } /** * Gets the sensor error codes. * * @return array of error codes specified for the given sensor */ public int[] getErrorCodes() { int[] retV = new int[errorCodes.size()]; if (retV.length > 0) { Enumeration enumErrCodes = errorCodes.keys(); for (int i = 0; enumErrCodes.hasMoreElements(); i++) { retV[i] = ((Integer)(enumErrCodes.nextElement())).intValue(); } } return retV; } /** * Gets the error description. * * @param errorCode code of the error * @return description of error */ public String getErrorText(int errorCode) { Integer errCodeObject = new Integer(errorCode); if (!errorCodes.containsKey(errCodeObject)) { throw new IllegalArgumentException("Wrong error code"); } return (String)errorCodes.get(errCodeObject); } /** * Sets the notify flag. */ synchronized void setNotify() { isNotify = true; notify(); } /** * Gets the sensor number. */ int getNumber() { return number; } /** * Stops getting data from channels. */ void stopGetData() { channelCount = 0; for (int i = 0; i < channels.length; i++) { channels[i].stopGetData(); } } /** * Checks if all channels sent data or confirmation. * * @retun true when all channels sent data else false */ boolean isAllChannelsAnswered() { return (++channelCount == channels.length); } } class CallDataListener implements Runnable { /** Error flag. */ private boolean isError = false; /** Sensor link. */ private Sensor sensor; /** Data array which read from sensor. */ private DataImpl[] retData; /** Error timestamp. */ private long errorTimestamp; /** Listener for notification about data receiving. */ private DataListener listener; /** Last channel status. */ private int channelStatus; /** * Initialization for data listening. */ CallDataListener(Sensor sensor, DataListener listener, DataImpl[] retData) { this.sensor = sensor; this.listener = listener; this.retData = retData; } /** * Initialization for error listening. */ CallDataListener(Sensor sensor, long errorTimestamp, DataListener listener, int channelStatus) { this.sensor = sensor; this.errorTimestamp = errorTimestamp; this.listener = listener; this.channelStatus = channelStatus; isError = true; } /** * Run the listener. */ public void run() { if (listener == null) { return; } try { if (isError) { ((DataAndErrorListener)listener).errorReceived((SensorConnection)sensor, channelStatus, errorTimestamp); } else { listener.dataReceived(sensor, retData, false); } } catch (Exception ex) { // user exception - ignore } } }