
/*---------------------------------------------------------------------------
 * Copyright (C) 1999,2000 Dallas Semiconductor Corporation, All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Dallas Semiconductor
 * shall not be used except as stated in the Dallas Semiconductor
 * Branding Policy.
 *---------------------------------------------------------------------------
 */

package com.dalsemi.onewire.adapter;

// imports
import java.util.Vector;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import javax.comm.*;
import com.dalsemi.onewire.adapter.OneWireIOException;
import com.dalsemi.onewire.OneWireException;


/**
 *  The SerialService class provides serial IO services to the
 *  USerialAdapter class. <p>
 *
 *  @version    0.00, 8 September 2000
 *  @author     DS
 */
class SerialService
{

   //--------
   //-------- Variables
   //--------

   /** Hash of the current thread that owns the port   */
   private Integer currentThreadHash = new Integer(0);

   /** Flag to indicate we currently own the port      */
   private boolean portOpen = false;

   /** String name of the current opened port          */
   private String portName;

   /** Serial port object of the current opened port   */
   private SerialPort serialPort;

   /** Output stream to the current opened port        */
   private OutputStream outStream;

   /** Input stream from the current opened port       */
   private InputStream inStream;

   /** Vector of thread hash codes that have done an open but no close */
   private Vector users;

   /** Enable/disable debug messages                   */
   private static boolean doDebugMessages = false;

   //--------
   //-------- Constructor
   //--------

   /**
    * Constructs a Serial Services class
    *
    * @param  newPortName name of serial port that this will
    *         service.
    */
   public SerialService (String newPortName)
   {
      portName = newPortName;
      users    = new Vector(4);
   }

   /**
    * Open this serial port at 9600,N,8,1.
    *
    * @throws OneWireException - if port is in use by another application
    */
   public void openPort ()
      throws OneWireException
   {
      CommPortIdentifier port_id;

      // record this thread as an owner   
      Integer hash = new Integer(Thread.currentThread().hashCode());

      if (users.indexOf(hash) == -1)
         users.addElement(hash);

      // check if the port is already opened and setup
      if (portOpen)
         return;

      // get the port identifier object
      try
      {
         port_id = CommPortIdentifier.getPortIdentifier(portName);
      }
      catch (NoSuchPortException e)
      {
         throw new OneWireException("Could not open port(" + portName + ") :"
                                    + e);
      }

      // check if the port is currently used
      if (port_id.isCurrentlyOwned())
         throw new OneWireException("Port In Use (" + portName + ")");

      // try to aquire the port
      try
      {

         // get the port object
         serialPort = ( SerialPort ) port_id.open(
            "Dallas Semiconductor 1-Wire API System", 2000);

         // flow i/o
         serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);

         outStream = serialPort.getOutputStream();
         inStream  = serialPort.getInputStream();

         // settings
         serialPort.disableReceiveFraming();
         serialPort.disableReceiveThreshold();
         serialPort.enableReceiveTimeout(100);

         // set baud rate
         serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8,
                                        SerialPort.STOPBITS_1,
                                        SerialPort.PARITY_NONE);

         // provide power to the device
         serialPort.setDTR(true);
         serialPort.setRTS(true);

         // we own the port
         portOpen = true;
      }
      catch (Exception e)
      {

         // close the port if we have an object
         if (serialPort != null)
            serialPort.close();

         serialPort = null;
         portOpen   = false;

         // pass on the exception
         throw new OneWireException("Port In Use(" + portName + ") :" + e);
      }
   }

   /**
    * Close this serial port.
    *
    * @throws OneWireException - if port is in use by another application
    */
   public void closePort ()
   {

      // remove this thread as an owner   
      users.removeElement((new Integer(Thread.currentThread().hashCode())));

      // if this is the last owner then close the port
      if (users.isEmpty())
      {
         if (doDebugMessages)
            System.out.println("Closing the port (" + portName + ") by "
                               + Thread.currentThread().getName());

         // if don't own a port then just return
         if (!portOpen)
            return;

         // close the port
         serialPort.close();

         serialPort = null;
         portOpen   = false;
      }
   }

   /**
    * Return the current baud rate
    *
    * @return baud rate
    */
   public int getBaudRate ()
   {
      return serialPort.getBaudRate();
   }

   /**
    * Set the current baud rate
    *
    * @param  baud - desired baud rate.
    */
   public void setBaudRate (int baud)
   {
      try
      {

         // set baud rate
         serialPort.setSerialPortParams(baud, SerialPort.DATABITS_8,
                                        SerialPort.STOPBITS_1,
                                        SerialPort.PARITY_NONE);
      }
      catch (UnsupportedCommOperationException e)
      {
         System.err.println("Setting baud rate: " + e);
      }
   }

   /**
    * Send a break on this serial port
    *
    * @param  duration - break duration in ms
    */
   public void sendBreak (int duration)
   {
      serialPort.sendBreak(duration);
   }

   /**
    * Set the 'power' on or off.  The power for a DS2480 adapter
    * is DTR and RTS on.
    *
    * @param  powerOn - 'true' for power on
    */
   public void setPower (boolean powerOn)
   {
      serialPort.setDTR(powerOn);
      serialPort.setRTS(powerOn);
   }

   /**
    * Get the port name of this serial port.
    *
    * @return port name as string
    */
   public String getName ()
   {
      return new String(portName);
   }

   //--------
   //-------- Serial Methods
   //--------

   /**
    * Write a single character to the com port
    *
    * @param  writeVal  the data byte to write.
    *
    * @throws OneWireIOException
    */
   public void writeCom (char writeVal)
      throws OneWireIOException
   {

      // provided debug on standard out
      if (doDebugMessages)
         System.out.print("W(" + Integer.toHexString(writeVal) + ")");

      try
      {
         outStream.write(( int ) writeVal);
         outStream.flush();
      }
      catch (IOException e)
      {

         // drain IOExceptions that are 'Interrrupted' on Linux
         // convert the rest to OneWireIOExceptions
         if (!((System.getProperty("os.name").indexOf("Linux") != -1)
               && (e.toString().indexOf("Interrupted") != -1)))
            throw new OneWireIOException("writeCom(): " + e);
      }
   }

   /**
    * Write an array of characters to the com port
    *
    * @param  writeBuf  the data byte to write.
    *
    * @throws OneWireIOException
    */
   public void writeCom (char writeBuf [])
      throws OneWireIOException
   {
      try
      {
         byte[] temp_arr = new byte [writeBuf.length];

         for (int i = 0; i < writeBuf.length; i++)
         {
            temp_arr [i] = ( byte ) writeBuf [i];

            // provided debug on standard out
            if (doDebugMessages)
               System.out.print("W{"
                                + Integer.toHexString(writeBuf [i] & 0x00FF)
                                + "}");
         }

         outStream.write(temp_arr);
         outStream.flush();
      }
      catch (IOException e)
      {

         // drain IOExceptions that are 'Interrrupted' on Linux
         // convert the rest to OneWireIOExceptions
         if (!((System.getProperty("os.name").indexOf("Linux") != -1)
               && (e.toString().indexOf("Interrupted") != -1)))
            throw new OneWireIOException("writeCom([]): " + e);
      }
   }

   /**
    * Wait and read theor desired number of chars.
    *
    *
    * @param desiredLength
    *
    * @return
    * @throws OneWireIOException
    */
   public char[] readComWait (int desiredLength)
      throws OneWireIOException
   {
      int    count = 0;
      int    retVal, get_num;
      char[] temp_buf = new char [desiredLength];
      byte[] read_buf = new byte [desiredLength];
      long   max_timeout;

      // set the max timeout very long
      max_timeout = System.currentTimeMillis() + 800;

      if (doDebugMessages)
         System.out.println("readComWait " + desiredLength);

      try
      {
         do
         {
            if (inStream.available() > 0)
            {
               get_num = inStream.available();

               // check for block bigger then buffer
               if ((get_num + count) > desiredLength)
                  get_num = desiredLength - count;

               // read the block
               count += inStream.read(read_buf, count, get_num);
            }
            else
            {
               // check for timeout
               if (System.currentTimeMillis() > max_timeout)
                  break;

               // no bytes available yet so yield
               Thread.yield();

               if (doDebugMessages)
                  System.out.print("y");
            }
         }
         while (desiredLength > count);

         if (doDebugMessages)
            System.out.println("Got data size: " + count);

         // convert to char array
         for (int j = 0; j < count; j++)
            temp_buf[j] = (char)(read_buf[j] & 0x00FF); 

         if (doDebugMessages)
            for (int i = 0; i < count; i++)
               System.out.print("R["
                                + Integer.toHexString(temp_buf [i] & 0xFF)
                                + "]");

         // check for correct length
         if (desiredLength != count)
            throw new OneWireIOException(
               "readComWait, timeout waiting for return bytes");

         // return the characters found
         return temp_buf;
      }
      catch (IOException e)
      {
         throw new OneWireIOException("readComWait: " + e);
      }
   }

   /**
    * Flush the input and output buffer of any data
    */
   public void flushCom ()
   {

      // provided debug on standard out
      if (doDebugMessages)
         System.out.println("DEBUG: flush");

      try
      {

         // flush the output
         outStream.flush();

         // flush the input buffer
         while (inStream.available() > 0)
         {
            inStream.read();
         }
      }
      catch (IOException E)
      {

         // provided debug on standard out
         System.err.println("EXCEPTION: flush: " + E);
      }
   }

   //--------
   //-------- Port Semaphore methods
   //--------

   /**
    * Gets exclusive use of the 1-Wire to communicate with an iButton or
    * 1-Wire Device.
    * This method should be used for critical sections of code where a
    * sequence of commands must not be interrupted by communication of
    * threads with other iButtons, and it is permissible to sustain
    * a delay in the special case that another thread has already been
    * granted exclusive access and this access has not yet been
    * relinquished. <p>
    *
    * @param blocking <code>true</code> if want to block waiting
    *                 for an excluse access to the adapter
    * @return <code>true</code> if blocking was false and a
    *         exclusive session with the adapter was aquired
    *
    * @throws OneWireException
    */
   public boolean beginExclusive (boolean blocking)
      throws OneWireException
   {
      if (blocking)
      {
         while (!beginExclusive())
         {
         }

         return true;
      }
      else
         return beginExclusive();
   }

   /**
    * Relinquishes exclusive control of the 1-Wire Network.
    * This command dynamically marks the end of a critical section and
    * should be used when exclusive control is no longer needed.
    */
   public void endExclusive ()
   {
      synchronized (currentThreadHash)
      {

         // if own then release
         if (currentThreadHash.intValue()
                 == Thread.currentThread().hashCode())
         {
            currentThreadHash = new Integer(0);
         }
      }
   }

   /**
    * Check if this thread has exclusive control of the port.
    */
   public boolean haveExclusive ()
   {
      synchronized (currentThreadHash)
      {

         // if own then release
         return (currentThreadHash.intValue()
                 == Thread.currentThread().hashCode());
      }
   }

   /**
    * Gets exclusive use of the 1-Wire to communicate with an iButton or
    * 1-Wire Device.
    * This method should be used for critical sections of code where a
    * sequence of commands must not be interrupted by communication of
    * threads with other iButtons, and it is permissible to sustain
    * a delay in the special case that another thread has already been
    * granted exclusive access and this access has not yet been
    * relinquished. This is private and non blocking<p>
    *
    * @return <code>true</code> a exclusive session with the adapter was
    *         aquired
    *
    * @throws OneWireException
    */
   private boolean beginExclusive ()
      throws OneWireException
   {
      synchronized (currentThreadHash)
      {
         if (currentThreadHash.intValue() == 0)
         {

            // not owned so take
            currentThreadHash =
               new Integer(Thread.currentThread().hashCode());

            // provided debug on standard out
            if (doDebugMessages)
               System.out.println("DEBUG: beginExclusive, now owned by: "
                                  + Thread.currentThread().getName());

            return true;
         }
         else if (currentThreadHash.intValue()
                  == Thread.currentThread().hashCode())
         {

            // provided debug on standard out
            if (doDebugMessages)
               System.out.println("DEBUG: beginExclusive, already owned by: "
                                  + Thread.currentThread().getName());

            // already own
            return true;
         }
         else
         {

            // want port but don't own
            return false;
         }
      }
   }
}
