
/*---------------------------------------------------------------------------
 * 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 10 (hex).  This family type is
 * a 'temperature' iButton (DS1920 (can) DS1820 (plastic)).
 *
 * @version    0.01, 1 Sep 2000
 * @author     DS,JK
 *
 * Converted to use TemperatureContainer interface 9-1-2000 KLA
 */
public class OneWireContainer10
   extends OneWireContainer
   implements TemperatureContainer
{
   private boolean normalResolution = true;

   /**
    * Useful constant for resolution of temperature reading.
    * Pass this value to setResolution() to use normal,
    * 0.5 C resolution.
    */

   //--------
   //-------- Static Final Variables
   //--------
   public static final double RESOLUTION_NORMAL = 0.5;

   /**
    * Useful constant for resolution of temperature reading.
    * Pass this value to setResolution() to use a higher resolution
    * algorithm to calculate temperature.
    */
   public static final double RESOLUTION_MAXIMUM = 0.1;

   /**
    * DS1920 convert temperature command
    */
   private static final byte CONVERT_TEMPERATURE_COMMAND = 0x44;

   /**
    * DS1920 read scratchpad command
    */
   private static final byte READ_SCRATCHPAD_COMMAND = ( byte ) 0xBE;

   /**
    * DS1920 write scratchpad command
    */
   private static final byte WRITE_SCRATCHPAD_COMMAND = ( byte ) 0x4E;

   /**
    * DS1920 copy scratchpad command
    */
   private static final byte COPY_SCRATCHPAD_COMMAND = ( byte ) 0x48;

   /**
    * DS1920 recall eeprom command
    */
   private static final byte RECALL_EEPROM_COMMAND = ( byte ) 0xB8;

   /**
    * Constructor OneWireContainer10
    *
    *
    */
   public OneWireContainer10 ()
   {
      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 OneWireContainer10 (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 OneWireContainer10 (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 OneWireContainer10 (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 "DS1920";
   }

   /**
    * Retrieve the alternate Dallas Semiconductor part numbers or names.
    * A 'family' of 1-Wire Network 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 "DS1820";
   }

   /**
    * 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 100C in typically 0.2 seconds.  +/- 0.5C "
             + "Accuracy between 0C and 70C. 0.5C standard "
             + "resolution, higher resolution through interpolation."
             + "Contains high and low temperature set points for"
             + "generation of alarm.";
   }

   //--------
   //-------- Custom Methods for this iButton or 1-Wire Device Type  
   //--------
   //--------
   //-------- 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 [2];

      resolutions [0] = RESOLUTION_NORMAL;
      resolutions [1] = RESOLUTION_MAXIMUM;

      return resolutions;
   }

   /**
    * Query to get the high/low resolution in degrees C.
    *
    * @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 100.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 an temperature conversion.  Use this state information
    * to calculate the conversion time.
    *
    * @param state - byte array of device state
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public void doTemperatureConvert (byte[] state)
      throws OneWireIOException, OneWireException
   {
      doSpeed();

      // select the device 
      if (adapter.select(address))
      {

         // Setup Power Delivery
         adapter.setPowerDuration(adapter.DELIVERY_INFINITE);
         adapter.startPowerDelivery(adapter.CONDITION_AFTER_BYTE);

         // send the convert temperature command
         adapter.putByte(CONVERT_TEMPERATURE_COMMAND);

         // delay for 750 ms
         try
         {
            Thread.sleep(750);
         }
         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(
               "OneWireContainer10-temperature conversion not complete");

         // read the result
         byte mode = state [4];   //preserve the resolution in the state

         adapter.select(address);
         readScratch(state);

         state [4] = mode;
      }
      else

         // device must not have been present
         throw new OneWireIOException(
            "OneWireContainer10-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)
      throws OneWireIOException
   {

      //on some parts, namely the 18S20, you can get invalid readings.
      //basically, the detection is that all the upper 8 bits should
      //be the same by sign extension.  the error condition (DS18S20
      //returns 185.0+) violated that condition
      if (((state [1] & 0x0ff) != 0x00) && ((state [1] & 0x0ff) != 0xFF))
         throw new OneWireIOException("Invalid temperature data!");

      short temp = ( short ) ((state [0] & 0x0ff) | (state [1] << 8));

      if (state [4] == 1)
      {
         temp = ( short ) (temp >> 1);   //lop off the last bit

         //also takes care of the / 2.0
         double tmp = ( double ) temp;
         double cr  = (state [6] & 0x0ff);
         double cpc = (state [7] & 0x0ff);

         //just let the thing throw a divide by zero exception
         tmp = tmp - ( double ) 0.25 + (cpc - cr) / cpc;

         return tmp;
      }
      else
      {

         //do normal resolution 
         return temp / 2.0;
      }
   }

   /**
    * This method extracts the specified 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)
   {
      if (state [4] == 0)
         return RESOLUTION_NORMAL;

      return RESOLUTION_MAXIMUM;
   }

   //--------
   //-------- 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)
   {
      if ((alarmType != ALARM_LOW) && (alarmType != ALARM_HIGH))
         throw new IllegalArgumentException("Invalid alarm type.");

      if (alarmValue > 100.0 || alarmValue < -55.0)
         throw new IllegalArgumentException(
            "Value for alarm not in accepted range.  Must be -55 C <-> +100 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.  Use
    * RESOLUTION_NORMAL or RESOLUTION_MAXIMUM.
    * @param state - byte array of device state
    * @see RESOLUTION_NORMAL
    * @see RESOLUTION_MAXIMUM
    */
   public void setTemperatureResolution (double resolution, byte[] state)
   {
      synchronized (this)
      {
         if (resolution == RESOLUTION_NORMAL)
            normalResolution = true;
         else
            normalResolution = false;

         state [4] = ( byte ) (normalResolution ? 0
                                                : 1);
      }
   }

   /**
    * 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 : reserved (put the resolution here, 0 for normal, 1 for max)
      // 5 : reserved
      // 6 : count remain
      // 7 : count per degree celcius
      //the final byte is an 8 bit CRC
      // select the device 
      byte[] data = new byte [8];

      doSpeed();

      if (adapter.select(address))
      {

         // construct a block to read the scratchpad
         byte[] buffer = new byte [10];

         // read scratchpad command
         buffer [0] = ( byte ) READ_SCRATCHPAD_COMMAND;

         // now add the read bytes for data bytes and crc8
         for (int i = 1; i < 10; i++)
            buffer [i] = ( byte ) 0xFF;

         // send the block
         adapter.dataBlock(buffer, 0, buffer.length);

         // see if crc is correct
         if (CRC8.compute(buffer, 1, 9) == 0)
            System.arraycopy(buffer, 1, data, 0, 8);
         else
            throw new OneWireIOException(
               "OneWireContainer10-Error reading CRC8 from device.");
      }
      else
         throw new OneWireIOException(
            "OneWireContainer10-Device not found on 1-Wire Network");

      //we are just reading normalResolution here, no need to synchronize
      data [4] = ( byte ) (normalResolution ? 0
                                            : 1);

      return data;
   }

   /**
    * This method write the 1-Wire device sensor state that
    * have been changed by the 'set' methods.  It knows which registers have
    * changed by looking at the bitmap fields appended to the state
    * data.
    *
    * @param  state - byte array of clock register page contents
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public void writeDevice (byte[] state)
      throws OneWireIOException, OneWireException
   {
      doSpeed();

      byte[] temp = new byte [2];

      temp [0] = state [2];
      temp [1] = state [3];

      // Write it to the Scratchpad.
      writeScratchpad(temp);

      // Place in memory.
      copyScratchpad();
   }

   /**
    * Convert a temperature from Celsius to Fahrenheit.
    *
    * @param  celsiusTemperature     the temperature value to convert
    *
    * @return  the fahrenheit conversion of the supplied temperature
    */
   static public double convertToFahrenheit (double celsiusTemperature)
   {
      return ( double ) (celsiusTemperature * 9.0 / 5.0 + 32.0);
   }

   /**
    * Convert a temperature from Fahrenheit to Celsius.
    *
    * @param  fahrenheitTemperature     the temperature value to convert
    *
    * @return  the celsius conversion of the supplied temperature
    */
   static public double convertToCelsius (double fahrenheitTemperature)
   {
      return ( double ) ((fahrenheitTemperature - 32.0) * 5.0 / 9.0);
   }

   //--------
   //-------- Private Methods  
   //--------

   /**
    * Read the 8 bytes from the scratchpad and verify CRC8 returned.
    *
    * @param  data  buffer to store the scratchpad data
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   private void readScratch (byte[] data)
      throws OneWireIOException, OneWireException
   {

      // select the device 
      if (adapter.select(address))
      {

         // construct a block to read the scratchpad
         byte[] buffer = new byte [10];

         // read scratchpad command
         buffer [0] = ( byte ) READ_SCRATCHPAD_COMMAND;

         // now add the read bytes for data bytes and crc8
         for (int i = 1; i < 10; i++)
            buffer [i] = ( byte ) 0xFF;

         // send the block
         adapter.dataBlock(buffer, 0, buffer.length);

         // see if crc is correct
         if (CRC8.compute(buffer, 1, 9) == 0)
            System.arraycopy(buffer, 1, data, 0, 8);
         else
            throw new OneWireIOException(
               "OneWireContainer10-Error reading CRC8 from device.");
      }
      else
         throw new OneWireIOException(
            "OneWireContainer10-Device not found on 1-Wire Network");
   }

   /**
    * Writes to the Scratchpad.
    *
    * @param data <code>byte[]<\code> this is the data to be written to the
    *                      scratchpad.  Cannot be more than two bytes in size.
    *                      First byte of data must be the High Trip Point and
    *                      second byte must be Low Trip Point.
    *
    * @throws OneWireIOException
    * @throws OneWireException
    * @throws IllegalArgumentException
    */
   private void writeScratchpad (byte[] data)
      throws OneWireIOException, OneWireException, IllegalArgumentException
   {

      // Variables.
      byte[] write_block = new byte [3];
      byte[] buffer      = new byte [8];

      // First do some error checking.
      if (data.length != 2)
         throw new IllegalArgumentException(
            "Bad data.  Data must consist of only TWO bytes.");

      // Prepare the write_block to be sent.
      write_block [0] = WRITE_SCRATCHPAD_COMMAND;
      write_block [1] = data [0];
      write_block [2] = data [1];

      // Send the block of data to the DS1920.
      if (adapter.select(address))
         adapter.dataBlock(write_block, 0, 3);
      else
         throw new OneWireIOException(
            "OneWireContainer10 - Device not found");

      // Check data to ensure correctly recived.
      buffer = new byte [8];

      readScratch(buffer);

      // verify data
      if ((buffer [2] != data [0]) || (buffer [3] != data [1]))
         throw new OneWireIOException(
            "OneWireContainer10 - data read back incorrect");

      return;
   }

   /**
    * Copies the contents of the User bytes of the ScratchPad to the EEPROM.
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   private void copyScratchpad ()
      throws OneWireIOException, OneWireException
   {

      // select the device 
      if (adapter.select(address))
      {

         // send the copy command
         adapter.putByte(COPY_SCRATCHPAD_COMMAND);

         // Setup Power Delivery
         adapter.setPowerDuration(adapter.DELIVERY_INFINITE);
         adapter.startPowerDelivery(adapter.CONDITION_NOW);

         // delay for 10 ms
         try
         {
            Thread.sleep(10);
         }
         catch (InterruptedException e){}

         // Turn power back to normal.
         adapter.setPowerNormal();
      }
      else
         throw new OneWireIOException(
            "OneWireContainer10 - device not found");
   }
}
