
/*---------------------------------------------------------------------------
 * 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.container;

// imports
import com.dalsemi.onewire.*;
import com.dalsemi.onewire.utils.CRC8;
import com.dalsemi.onewire.adapter.*;


//----------------------------------------------------------------------------

/**
 * iButton container for iButton family type 28 (hex).  This family type is
 * a 'temperature' 1-Wire chip device DS18B20 (plastic)).
 *
 * @version    1.00, 15 September 2000
 * @author     BH
 */
public class OneWireContainer28
   extends OneWireContainer
   implements TemperatureContainer
{

   //-------------------------------------------------------------------------
   //-------- Static Final Variables
   //-------------------------------------------------------------------------

   /**
    * DS18B20 write scratchpad command
    */
   public static final byte WRITE_SCRATCHPAD_COMMAND = ( byte ) 0x4E;

   /**
    * DS18B20 read scratchpad command
    */
   public static final byte READ_SCRATCHPAD_COMMAND = ( byte ) 0xBE;

   /**
    * DS18B20 copy scratchpad command
    */
   public static final byte COPY_SCRATCHPAD_COMMAND = ( byte ) 0x48;

   /**
    * DS18B20 convert temperature command
    */
   public static final byte CONVERT_TEMPERATURE_COMMAND = ( byte ) 0x44;

   /**
    * DS18B20 recall E-squared memory command
    */
   public static final byte RECALL_E2MEMORY_COMMAND = ( byte ) 0xB8;

   /**
    * DS18B20 write scratchpad command
    */
   public static final byte READ_POWER_SUPPLY_COMMAND = ( byte ) 0xB4;

   /**
    * DS18B20 12-bit resolution constant for CONFIG byte
    */
   public static final byte RESOLUTION_12_BIT = ( byte ) 0x7F;

   /**
    * DS18B20 11-bit resolution constant for CONFIG byte
    */
   public static final byte RESOLUTION_11_BIT = ( byte ) 0x5F;

   /**
    * DS18B20 10-bit resolution constant for CONFIG byte
    */
   public static final byte RESOLUTION_10_BIT = ( byte ) 0x3F;

   /**
    * DS18B20 9-bit resolution constant for CONFIG byte
    */
   public static final byte RESOLUTION_9_BIT = ( byte ) 0x1F;

   /**
    * Constructor OneWireContainer28
    *
    *
    */
   public OneWireContainer28 ()
   {
      super();
   }

   /**
    * Create a container with a provided adapter object
    * and the address of the iButton or 1-Wire device.
    *
    * @param  sourceAdapter     adapter object required to communicate with
    * this iButton.
    * @param  newAddress        address of this 1-Wire device
    */
   public OneWireContainer28 (DSPortAdapter sourceAdapter, byte[] newAddress)
   {
      super(sourceAdapter, newAddress);
   }

   /**
    * Create a container with a provided adapter object
    * and the address of the iButton or 1-Wire device.
    *
    * @param  sourceAdapter     adapter object required to communicate with
    * this iButton.
    * @param  newAddress        address of this 1-Wire device
    */
   public OneWireContainer28 (DSPortAdapter sourceAdapter, long newAddress)
   {
      super(sourceAdapter, newAddress);
   }

   /**
    * Create a container with a provided adapter object
    * and the address of the iButton or 1-Wire device.
    *
    * @param  sourceAdapter     adapter object required to communicate with
    * this iButton.
    * @param  newAddress        address of this 1-Wire device
    */
   public OneWireContainer28 (DSPortAdapter sourceAdapter, String newAddress)
   {
      super(sourceAdapter, newAddress);
   }

   //--------
   //-------- Information methods
   //--------

   /**
    * Retrieve the Dallas Semiconductor part number of the iButton
    * as a string.  For example 'DS1992'.
    *
    * @return string representation of the iButton name.
    */
   public String getName ()
   {
      return "DS18B20";
   }

   /**
    * Retrieve the alternate Dallas Semiconductor part numbers or names.
    * A 'family' of MicroLAN devices may have more than one part number
    * depending on packaging.  There can also be nicknames such as
    * 'Crypto iButton'.
    *
    * @return  <code>String</code> representation of the alternate names.
    */
   public String getAlternateNames ()
   {
      return "DS1820B, DS18B20X";
   }

   /**
    * Retrieve a short description of the function of the iButton type.
    *
    * @return string representation of the function description.
    */
   public String getDescription ()
   {
      return "Digital thermometer measures temperatures from "
             + "-55C to 125C in 0.75 seconds (max).  +/- 0.5C "
             + "accuracy between -10C and 85C. Thermometer "
             + "resolution is programmable at 9, 10, 11, and 12 bits. ";
   }

   //--------
   //-------- Temperature Feature methods
   //--------

   /**
    * Query to see if this temperature measuring device has high/low
    * trip alarms.
    *
    * @return boolean, true if has high/low trip alarms
    */
   public boolean hasTemperatureAlarms ()
   {
      return true;
   }

   /**
    * Query to see if this device has selectable resolution.
    *
    * @return boolean, true if has selectable resolution
    */
   public boolean hasSelectableTemperatureResolution ()
   {
      return true;
   }

   /**
    * Query to get an array of available resolutions in degrees C.
    *
    * @return double[], available of resolutions in degrees C
    */
   public double[] getTemperatureResolutions ()
   {
      double[] resolutions = new double [4];

      resolutions [0] = ( double ) 0.5;      // 9-bit
      resolutions [1] = ( double ) 0.25;     // 10-bit
      resolutions [2] = ( double ) 0.125;    // 11-bit
      resolutions [3] = ( double ) 0.0625;   // 12-bit

      return resolutions;
   }

   /**
    * Query to get the high/low resolution in degrees C.  This
    * value ONLY has to do with the temp high and temp low
    * trigger values.
    *
    * @return double, high/low resolution resolution in C
    */
   public double getTemperatureAlarmResolution ()
   {
      return 1.0;
   }

   /**
    * Query to get the maximum temperature in degrees C.
    *
    * @return double, maximum temperature in C
    */
   public double getMaxTemperature ()
   {
      return 125.0;
   }

   /**
    * Query to get the minimum temperature in degrees C.
    *
    * @return double, minimum temperature in C
    */
   public double getMinTemperature ()
   {
      return -55.0;
   }

   //--------
   //-------- Temperature I/O Methods
   //--------

   /**
    * Perform a temperature conversion.  Use this state information
    * to calculate the conversion time.
    *
    * @param state - byte array of device state
    *    device state looks like this:
    *    0 : temperature LSB
    *    1 : temperature MSB
    *    2 : trip high
    *    3 : trip low
    *    4 : configuration register (for resolution)
    *    5 : reserved
    *    6 : reserved
    *    7 : reserved
    *    8 : the final byte is an 8 bit CRC
    *
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public void doTemperatureConvert (byte[] state)
      throws OneWireIOException, OneWireException
   {
      int msDelay = 750;   // in milliseconds     

      // select the device 
      if (adapter.select(address))
      {

         // Setup Power Delivery
         // send the convert temperature command
         adapter.putByte(CONVERT_TEMPERATURE_COMMAND);
         adapter.setPowerDuration(adapter.DELIVERY_INFINITE);
         adapter.startPowerDelivery(adapter.CONDITION_NOW);

         // calculate duration of delay according to resolution desired
         switch (state [4])
         {

            case RESOLUTION_9_BIT :
               msDelay = 94;
               break;
            case RESOLUTION_10_BIT :
               msDelay = 188;
               break;
            case RESOLUTION_11_BIT :
               msDelay = 375;
               break;
            case RESOLUTION_12_BIT :
               msDelay = 750;
               break;
            default :
               msDelay = 750;
         }   // switch

         // delay for specified amount of time
         try
         {
            Thread.sleep(msDelay);
         }
         catch (InterruptedException e){}

         // Turn power back to normal.
         adapter.setPowerNormal();

         // check to see if the temperature conversion is over
         if (adapter.getByte() != 0xFF)
            throw new OneWireIOException(
               "OneWireContainer28-temperature conversion not complete");
      }
      else
      {

         // device must not have been present
         throw new OneWireIOException(
            "OneWireContainer28-device not present");
      }
   }

   //--------
   //-------- Temperature 'get' Methods
   //--------

   /**
    * This method extracts the Temperature Value in degrees C from the
    * state data retrieved from the 'readDevice()' method.
    *
    * @param state - byte array of device state
    *
    * @return double, temperature in degrees C from the last 'doTemperature()'
    */
   public double getTemperature (byte[] state)
   {

      // Take these three steps:
      // 1)  Make an 11-bit integer number out of MSB and LSB of the first 2 bytes from scratchpad
      // 2)  Divide final number by 16 to retrieve the floating point number.
      // 3)  Afterwards, test for the following temperatures:
      //     0x07D0 = 125.0C
      //     0x0550 = 85.0C
      //     0x0191 = 25.0625C          
      //     0x00A2 = 10.125C
      //     0x0008 = 0.5C    
      //     0x0000 = 0.0C    
      //     0xFFF8 = -0.5C   
      //     0xFF5E = -10.125C
      //     0xFE6F = -25.0625C
      //     0xFC90 = -55.0C
      double theTemperature = ( double ) 0.0;
      int    inttemperature = state [1];   // inttemperature is automatically sign extended here.

      inttemperature = (inttemperature << 8) | (state [0] & 0xFF);   // this converts 2 bytes into integer
      theTemperature = ( double ) (( double ) inttemperature / ( double ) 16);   // converts integer to a double

      return (theTemperature);
   }

   /**
    * This method extracts the specified Temperature Alarm value in degrees C from the
    * state data retrieved from the 'readDevice()' method.
    *
    * @param alarmType - integer, indicating trip type ALARM_HIGH (1)
    *               or ALARM_LOW (0)
    * @param state - byte array of device state
    *
    * @return double, alarm trip temperature in degrees C
    */
   public double getTemperatureAlarm (int alarmType, byte[] state)
   {
      return ( double ) state [alarmType == ALARM_LOW ? 3
                                                      : 2];
   }

   /**
    * This method extracts the current resolution in degrees C from the
    * state data retrieved from the 'readDevice()' method.
    *
    * @param state - byte array of device state
    *
    * @return double, temperature resolution in degrees C
    */
   public double getTemperatureResolution (byte[] state)
   {
      double tempres = ( double ) 0.0;

      // calculate temperature resolution according to configuration byte
      switch (state [4])
      {

         case RESOLUTION_9_BIT :
            tempres = ( double ) 0.5;
            break;
         case RESOLUTION_10_BIT :
            tempres = ( double ) 0.25;
            break;
         case RESOLUTION_11_BIT :
            tempres = ( double ) 0.125;
            break;
         case RESOLUTION_12_BIT :
            tempres = ( double ) 0.0625;
            break;
         default :
            tempres = ( double ) 0.0;
      }   // switch

      return tempres;
   }

   //--------
   //-------- Temperature 'set' Methods
   //--------

   /**
    * This method sets the alarm value in degrees C in the
    * provided state data.  Use the method 'writeDevice()' with
    * this data to finalize the change to the device.
    *
    * @param alarmType - integer, indicating trip type ALARM_HIGH (1)
    *               or ALARM_LOW (0)
    * @param alarmValue - double, high trip value in degrees C
    * @param state - byte array of device state
    */
   public void setTemperatureAlarm (int alarmType, double alarmValue,
                                    byte[] state)
      throws OneWireException, OneWireIOException
   {
      if ((alarmType != ALARM_LOW) && (alarmType != ALARM_HIGH))
         throw new IllegalArgumentException("Invalid alarm type.");

      if (alarmValue > 125.0 || alarmValue < -55.0)
         throw new IllegalArgumentException(
            "Value for alarm not in accepted range.  Must be -55 C <-> +125 C.");

      state [(alarmType == ALARM_LOW) ? 3
                                      : 2] = ( byte ) alarmValue;
   }

   /**
    * This method sets the current resolution in degrees C in the
    * provided state data.   Use the method 'writeDevice()' with
    * this data to finalize the change to the device.
    *
    * @param resolution - double, temperature resolution in degrees C
    * @param state - byte array of device state
    */
   public void setTemperatureResolution (double resolution, byte[] state)
      throws OneWireException, OneWireIOException
   {
      byte configbyte = RESOLUTION_12_BIT;

      synchronized (this)
      {

         // calculate configbyte from given resolution
         if (resolution == 0.5)
            configbyte = RESOLUTION_9_BIT;

         if (resolution == 0.25)
            configbyte = RESOLUTION_10_BIT;

         if (resolution == 0.125)
            configbyte = RESOLUTION_11_BIT;

         if (resolution == 0.0625)
            configbyte = RESOLUTION_12_BIT;

         state [4] = configbyte;
      }
   }

   /**
    * This method retrieves the 1-Wire device sensor state.  This state is
    * returned as a byte array.  Pass this byte array to the static query
    * and set methods.  If the device state needs to be changed then call
    * the 'writeDevice' to finalize the one or more change.
    *
    * @return <code>byte[]<\code> 1-Wire device sensor state    *
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public byte[] readDevice ()
      throws OneWireIOException, OneWireException
   {

      //device state looks like this:
      //0 : temperature LSB
      //1 : temperature MSB
      //2 : trip high
      //3 : trip low
      //4 : configuration register (for resolution)
      //5 : reserved
      //6 : reserved
      //7 : reserved
      //8 : the final byte is an 8 bit CRC
      byte[] data;

      data = recallE2();

      return data;
   }

   /**
    * This method writes the 1-Wire device sensor state that
    * has been changed by the 'set' methods.
    *
    * @param  state - byte array to write to the DS18B20
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public void writeDevice (byte[] state)
      throws OneWireIOException, OneWireException
   {
      byte[] temp = new byte [3];

      temp [0] = state [2];
      temp [1] = state [3];
      temp [2] = state [4];

      // Write it to the Scratchpad.
      writeScratchpad(temp);

      // Place in memory.
      copyScratchpad();
   }

   //--------
   //-------- Custom Methods for this iButton Type  
   //--------
   //-------------------------------------------------------------------------

   /** Read the Scratchpad of the DS18B20.
    *
    *  @return 9-byte buffer representing the scratchpad
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public byte[] readScratchpad ()
      throws OneWireIOException, OneWireException
   {
      byte[] result_block;

      // select the device 
      if (adapter.select(address))
      {

         // create a block to send that reads the scratchpad
         byte[] send_block = new byte [10];

         // read scratchpad command
         send_block [0] = ( byte ) READ_SCRATCHPAD_COMMAND;

         // now add the read bytes for data bytes and crc8
         for (int i = 1; i < 10; i++)
            send_block [i] = ( byte ) 0xFF;

         // send the block
         adapter.dataBlock(send_block, 0, send_block.length);

         // now, send_block contains the 9-byte Scratchpad plus READ_SCRATCHPAD_COMMAND byte
         // convert the block to a 9-byte array representing Scratchpad (get rid of first byte)
         result_block = new byte [9];

         for (int i = 0; i < 9; i++)
         {
            result_block [i] = send_block [i + 1];
         }

         // see if CRC8 is correct
         if (CRC8.compute(send_block, 1, 9) == 0)
            return (result_block);
         else
            throw new OneWireIOException(
               "OneWireContainer28-Error reading CRC8 from device.");
      }

      // device must not have been present
      throw new OneWireIOException(
         "OneWireContainer28-Device not found on 1-Wire Network");
   }

   //-------------------------------------------------------------------------

   /** Write to the Scratchpad of the DS18B20.
    *
    *  @param data <code>byte[]<\code> this is the data to be written to the
    *                      scratchpad.  First byte of data must be the temperature
    *                      High Trip Point, the second byte must be the temperature
    *                      Low Trip Point, and the third must be the Resolution
    *                      (configuration register).
    *
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public void writeScratchpad (byte[] data)
      throws OneWireIOException, OneWireException
   {

      // setup buffer to write to scratchpad
      byte[] writeBuffer = new byte [4];

      writeBuffer [0] = WRITE_SCRATCHPAD_COMMAND;
      writeBuffer [1] = data [0];
      writeBuffer [2] = data [1];
      writeBuffer [3] = data [2];

      // send command block to device    
      if (adapter.select(address))
      {
         adapter.dataBlock(writeBuffer, 0, writeBuffer.length);
      }
      else
      {

         // device must not have been present
         throw new OneWireIOException(
            "OneWireContainer28-Device not found on 1-Wire Network");
      }

      // double check by reading scratchpad
      byte[] readBuffer;

      readBuffer = readScratchpad();

      if ((readBuffer [2] != data [0]) || (readBuffer [3] != data [1])
              || (readBuffer [4] != data [2]))
      {

         // writing to scratchpad failed
         throw new OneWireIOException(
            "OneWireContainer28-Error writing to scratchpad");
      }

      return;
   }

   //-------------------------------------------------------------------------

   /** Copy the Scratchpad to the Esquared memory of the DS18B20.
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public void copyScratchpad ()
      throws OneWireIOException, OneWireException
   {

      // first, let's read the scratchpad to compare later.
      byte[] readfirstbuffer;

      readfirstbuffer = readScratchpad();

      // second, let's copy the scratchpad.
      if (adapter.select(address))
      {

         // apply the power delivery
         adapter.setPowerDuration(adapter.DELIVERY_INFINITE);
         adapter.startPowerDelivery(adapter.CONDITION_AFTER_BYTE);

         // send the convert temperature command
         adapter.putByte(COPY_SCRATCHPAD_COMMAND);

         // sleep for 10 milliseconds to allow copy to take place.
         try
         {
            Thread.sleep(10);
         }
         catch (InterruptedException e){}
         ;

         // Turn power back to normal.
         adapter.setPowerNormal();
      }
      else
      {

         // device must not have been present
         throw new OneWireIOException(
            "OneWireContainer28-Device not found on 1-Wire Network");
      }

      // third, let's read the scratchpad again with the recallE2 command and compare.
      byte[] readlastbuffer;

      readlastbuffer = recallE2();

      if ((readfirstbuffer [2] != readlastbuffer [2])
              || (readfirstbuffer [3] != readlastbuffer [3])
              || (readfirstbuffer [4] != readlastbuffer [4]))
      {

         // copying to scratchpad failed
         throw new OneWireIOException(
            "OneWireContainer28-Error copying scratchpad to E2 memory.");
      }
   }

   //-------------------------------------------------------------------------

   /** Recalls the DS18B20 temperature trigger values (th & tl) and the configuration
    *  register to the scratchpad and reads the scratchpad.
    *
    *  @return byte array representing the device's scratchpad.
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public byte[] recallE2 ()
      throws OneWireIOException, OneWireException
   {
      byte[] ScratchBuff;

      // select the device 
      if (adapter.select(address))
      {

         // send the Recall Esquared memory command
         adapter.putByte(RECALL_E2MEMORY_COMMAND);

         // read scratchpad
         ScratchBuff = readScratchpad();

         return (ScratchBuff);
      }

      // device must not have been present
      throw new OneWireIOException(
         "OneWireContainer28-Device not found on 1-Wire Network");
   }

   //-------------------------------------------------------------------------

   /** Reads the way power is supplied to the DS18B20.
    *
    *  @return true for external power, false for parasite power
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public boolean isExternalPowerSupplied ()
      throws OneWireIOException, OneWireException
   {
      int     intresult = 0;
      boolean result    = false;

      // select the device 
      if (adapter.select(address))
      {

         // send the "Read Power Supply" memory command
         adapter.putByte(READ_POWER_SUPPLY_COMMAND);

         // read results
         intresult = adapter.getByte();
      }
      else
      {

         // device must not have been present
         throw new OneWireIOException(
            "OneWireContainer28-Device not found on 1-Wire Network");
      }

      if (intresult != 0x00)
         result = true;   // reads 0xFF for true and 0x00 for false

      return result;
   }

   //-------------------------------------------------------------------------

   /** Convert a temperature from celsius to fahrenheit.
    *
    *  @parameter celsiusTemperature the temperature value to convert
    *
    * @param celsiusTemperature
    *
    *  @return the fahrenheit conversion of the supplied temperature
    */
   public float convertToFahrenheit (float celsiusTemperature)
   {
      float fahrenheitTemperature = ( float ) 0.0;

      fahrenheitTemperature = ( float ) (celsiusTemperature * 9.0 / 5.0
                                         + 32.0);

      return fahrenheitTemperature;
   }
}
