
/*---------------------------------------------------------------------------
 * 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.utils.CRC16;
import com.dalsemi.onewire.*;
import com.dalsemi.onewire.adapter.*;
import com.dalsemi.onewire.container.*;
import java.util.*;


/**
 *
 *  The DS1921L-F5X Thermochron iButtons are rugged, self-sufficient systems
 *  that, once setup for a mission, measure temperature and record the result in
 *  a protected memory section.  The recording is doneat a user-defined rate,
 *  both as a direct storage of temperature values at incrementing memory
 *  addresses and as a histogram.  Up to 2048 periodic temperature readings
 *  may be taken on each mission. If a temperature value leaves a user specified
 *  range,  the thermochron will record when this happened and how long the
 *  condition occured for.
 *  <pre>
 *  Example of starting a mission with this container:
 *
 *       //  ds1921 previously setup as a OneWireContainer21
 *       ds1921.clearMemory();
 *       //  read the current state of the device
 *       byte[] state = ds1921.readDevice();
 *       //  enable rollover
 *       ds1921.setFlag(ds1921.CONTROL_REGISTER, ds1921.ROLLOVER_ENABLE_FLAG, true, state);
 *       //  set the high temperature alarm to 28 C
 *       ds1921.setTemperatureAlarm(ds1921.ALARM_HIGH, 28.0, state);
 *       //  set the low temperature alarm to 23 C
 *       ds1921.setTemperatureAlarm(ds1921.ALARM_LOW, 23.0, state);
 *       //  set the clock alarm to occur weekly, mondays at 12:30:45 pm
 *       ds1921.setClockAlarm(12, 30, 45, 2, ds1921.ONCE_PER_WEEK, state);
 *       //  set the real time clock to the system's current clock
 *       ds1921.setClock((new Date(System.currentTimeMillis())).getTime(), state);
 *       //  set the mission to start in 2 minutes
 *       ds1921.setMissionStartDelay(2,state);
 *       //  make sure the clock is set to run
 *       ds1921.setClockRunEnable(true, state);
 *       //  make sure the clock alarm is enabled
 *       ds1921.setClockAlarmEnable(true, state);
 *       //  write all that information out
 *       ds1921.writeDevice(state);
 *       //  now enable the mission with a sample rate of 1 minute
 *       ds1921.enableMission(1);
 *
 *
 *  </pre>
 *
 * @version    0.00, 28 Aug 2000
 * @author     COlmstea, KLA
 *
 */
public class OneWireContainer21
   extends OneWireContainer
   implements TemperatureContainer, ClockContainer
{
   private final byte FAMILY_CODE   = ( byte ) 0x21;
   private boolean    doSpeedEnable = true;

   /**
    * Memory commands.
    */
   private final byte WRITE_SCRATCHPAD_COMMAND    = ( byte ) 0x0F;
   private final byte READ_SCRATCHPAD_COMMAND     = ( byte ) 0xAA;
   private final byte COPY_SCRATCHPAD_COMMAND     = ( byte ) 0x55;
   private final byte READ_MEMORY_CRC_COMMAND     = ( byte ) 0xA5;
   private final byte CLEAR_MEMORY_COMMAND        = ( byte ) 0x3C;
   private final byte CONVERT_TEMPERATURE_COMMAND = ( byte ) 0x44;

   /**
    * Scratchpad access memory bank
    */
   private MemoryBankScratchCRC scratch;

   /**
    * Register control memory bank
    */
   private MemoryBankNVCRC register;

   /**
    * Alarms memory bank
    */
   private MemoryBankNVCRC alarm;

   /**
    * Histogram memory bank
    */
   private MemoryBankNVCRC histogram;

   /**
    * Log memory bank
    */
   private MemoryBankNVCRC log;

   /**
    * Buffer to hold the temperature log in
    */
   private byte[] read_log_buffer = new byte [64 * 32];   //64 pages X 32 bytes per page

   /*should we update the Real time clock? */
   private boolean updatertc = false;

   /**
    * Address of the status register. Used with getFlag/setFlag to set and
    * check flags indicating the thermochron's status.
    */
   public static final int STATUS_REGISTER = 0x214;

   /**
    * Address of the control register. Used with getFlag/setFlag to set and
    * check flags indicating the thermochron's status.
    */
   public static final int CONTROL_REGISTER = 0x20E;

   /**
    * Alarm frequency setting.
    */
   public static final byte ONCE_PER_SECOND = ( byte ) 0x1F;

   /**
    * Alarm frequency setting
    */
   public static final byte ONCE_PER_MINUTE = ( byte ) 0x17;

   /**
    * Alarm frequency setting.
    */
   public static final byte ONCE_PER_HOUR = ( byte ) 0x13;

   /**
    * Alarm frequency setting.
    */
   public static final byte ONCE_PER_DAY = ( byte ) 0x11;

   /**
    * Alarm frequency setting.
    */
   public static final byte ONCE_PER_WEEK = ( byte ) 0x10;

   /**
    * Type of alarm triggered when temperature goes below the temperature threshold.
    */
   public static final byte TEMPERATURE_LOW_ALARM = 4;

   /**
    * Type of alarm triggered when the temperature goes above the temperature threshold.
    */
   public static final byte TEMPERATURE_HIGH_ALARM = 2;

   /**
    * Type of alarm triggered when time reaches that set in alarm clock regs.
    */
   public static final byte TIMER_ALARM = 1;

   /**
    * CONTROL REGISTER FLAG: When enabled, the device will respond to conditional
    * search command if a timer alarm has occured.
    * To be used with getFlag/setFlag.
    */
   public static final byte TIMER_ALARM_SEARCH_FLAG = 1;

   /**
    * CONTROL REGISTER FLAG: When enabled, the device will respond to conditional
    * search command if the temperature has reached the high temperature threshold.
    * To be used with getFlag/setFlag.
    */
   public static final byte TEMP_HIGH_SEARCH_FLAG = 2;

   /**
    * CONTROL REGISTER FLAG: When enabled, the device will respond to conditional
    * search command if the temperature has reached the low temperature threshold.
    * To be used with getFlag/setFlag.
    */
   public static final byte TEMP_LOW_SEARCH_FLAG = 4;

   /**
    * CONTROL REGISTER FLAG: When enabled, the device will begin overwriting the earlier
    * temperature measurements when the memory becomes full.
    * To be used with getFlag/setFlag.
    */
   public static final byte ROLLOVER_ENABLE_FLAG = 8;

   /**
    * CONTROL REGISTER FLAG: When DISABLED, the mission will start as soon as the
    * sample rate is written.
    * To be used with getFlag/setFlag.
    */
   public static final byte MISSION_ENABLE_FLAG = 16;

   /**
    * CONTROL REGISTER FLAG: Must be enabled to enable the clear memory
    * function. Must be set immediately before the command is issued.
    * To be used with getFlag/setFlag.
    */
   public static final byte MEMORY_CLEAR_ENABLE_FLAG = 64;

   /**
    * CONTROL REGISTER FLAG: When DISABLED, the real time clock will start
    * working. Must be disabled for normal operation.
    * To be used with getFlag/setFlag.
    */
   public static final byte OSCILLATOR_ENABLE_FLAG = ( byte ) 128;

   /**
    * STATUS REGISTER FLAG: Will read back true when a clock alarm has occured.
    * To be used with getFlag.
    */
   public static final byte TIMER_ALARM_FLAG = 1;

   /**
    * STATUS REGISTER FLAG:  Will read back true when the temperature during a mission
    * reaches or exceeds the temperature high threshold.
    * To be used with getFlag.
    */
   public static final byte TEMPERATURE_HIGH_FLAG = 2;

   /**
    * STATUS REGISTER FLAG: Will read back true when a temperature equal to or below
    * the low temperature threshold was detected on a mission.
    * To be used with getFlag.
    */
   public static final byte TEMPERATURE_LOW_FLAG = 4;

   /**
    * STATUS REGISTER FLAG: Will read back true when a mission temperature conversion
    * is in progress
    * To be used with getFlag.
    */
   public static final byte SAMPLE_IN_PROGRESS_FLAG = 16;

   /**
    * STATUS REGISTER FLAG: Will read back true when a mission is in progress.
    * To be used with getFlag.
    */
   public static final byte MISSION_IN_PROGRESS_FLAG = 32;

   /**
    * STATUS REGISTER FLAG: Will read back true if the memory has been cleared.
    * To be used with getFlag.
    */
   public static final byte MEMORY_CLEARED_FLAG = 64;

   /**
    * STATUS REGISTER FLAG: Will read back true if a temperature conversion
    * of any kind is in progress.
    * To be used with getFlag.
    */
   public static final byte TEMP_CORE_BUSY_FLAG = ( byte ) 128;

   /**
    * Default constructor
    */
   public OneWireContainer21 ()
   {
      super();

      // initialize the memory banks
      initMem();
   }

   /**
    * 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 OneWireContainer21 (DSPortAdapter sourceAdapter, byte[] newAddress)
   {
      super(sourceAdapter, newAddress);

      // initialize the memory banks
      initMem();
   }

   /**
    * 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 OneWireContainer21 (DSPortAdapter sourceAdapter, long newAddress)
   {
      super(sourceAdapter, newAddress);

      // initialize the memory banks
      initMem();
   }

   /**
    * 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 OneWireContainer21 (DSPortAdapter sourceAdapter, String newAddress)
   {
      super(sourceAdapter, newAddress);

      // initialize the memory banks
      initMem();
   }

   /**
    * Return an enumeration of memory banks. Look at the
    * MemoryBank, PagedMemoryBank and OTPMemoryBank classes.
    */
   public Enumeration getMemoryBanks ()
   {
      Vector bank_vector = new Vector(7);

      // Address number in read-only-memory
      bank_vector.addElement(new MemoryBankROM(this));

      // scratchpad
      bank_vector.addElement(scratch);

      // NVRAM 
      bank_vector.addElement(new MemoryBankNVCRC(this, scratch));

      // Register page
      bank_vector.addElement(register);

      // Alarm time stamps and duration
      bank_vector.addElement(alarm);

      // Histogram
      bank_vector.addElement(histogram);

      // Log
      bank_vector.addElement(log);

      return bank_vector.elements();
   }

   //--------
   //-------- Private
   //--------

   /**
    * Construct the memory banks used for I/O.
    */
   private void initMem ()
   {

      // scratchpad
      scratch = new MemoryBankScratchCRC(this);

      // Register
      register                      = new MemoryBankNVCRC(this, scratch);
      register.numberPages          = 1;
      register.size                 = 32;
      register.bankDescription      = "Register control";
      register.startPhysicalAddress = 0x200;
      register.generalPurposeMemory = false;

      // Alarm registers
      alarm                      = new MemoryBankNVCRC(this, scratch);
      alarm.numberPages          = 3;
      alarm.size                 = 96;
      alarm.bankDescription      = "Alarm time stamps";
      alarm.startPhysicalAddress = 544;
      alarm.generalPurposeMemory = false;
      alarm.readOnly             = true;
      alarm.readWrite            = false;

      // Histogram
      histogram                      = new MemoryBankNVCRC(this, scratch);
      histogram.numberPages          = 4;
      histogram.size                 = 128;
      histogram.bankDescription      = "Temperature Histogram";
      histogram.startPhysicalAddress = 2048;
      histogram.generalPurposeMemory = false;
      histogram.readOnly             = true;
      histogram.readWrite            = false;

      // Log
      log                      = new MemoryBankNVCRC(this, scratch);
      log.numberPages          = 64;
      log.size                 = 2048;
      log.bankDescription      = "Temperature log";
      log.startPhysicalAddress = 4096;
      log.generalPurposeMemory = false;
      log.readOnly             = true;
      log.readWrite            = false;
   }

   /**
    * Grab the date from one of the time registers.
    * returns int[] = {year, month, date}
    */
   private int[] getDate (int timeReg, byte[] state)
   {
      byte  upper, lower;
      int[] result = new int [3];

      timeReg = timeReg & 31;

      /* extract the day of the month */
      lower      = state [timeReg++];
      upper      = ( byte ) ((lower >>> 4) & 0x0f);
      lower      = ( byte ) (lower & 0x0f);
      result [2] = 10 * upper + lower;

      /* extract the month */
      lower = state [timeReg++];
      upper = ( byte ) ((lower >>> 4) & 0x0f);
      lower = ( byte ) (lower & 0x0f);

      // the upper bit contains the century, so subdivide upper
      byte century = ( byte ) ((upper >>> 3) & 0x01);

      upper      = ( byte ) (upper & 0x01);
      result [1] = lower + upper * 10;

      /* grab the year */
      result [0] = 1900 + century * 100;
      lower      = state [timeReg++];
      upper      = ( byte ) ((lower >>> 4) & 0x0f);
      lower      = ( byte ) (lower & 0x0f);
      result [0] += upper * 10 + lower;

      return result;
   }

   /**
    * Gets the time of day fields in 24-hour time from button
    * returns int[] = {seconds, minutes, hours}
    */
   private int[] getTime (int timeReg, byte[] state)
   {
      byte  upper, lower;
      int[] result = new int [3];

      timeReg = timeReg & 31;

      // NOTE: The MSbit is ANDed out (with the 0x07) because alarm clock
      //       registers have an extra bit to indicate alarm frequency

      /* First grab the seconds. Upper half holds the 10's of seconds       */
      lower      = state [timeReg++];
      upper      = ( byte ) ((lower >>> 4) & 0x07);
      lower      = ( byte ) (lower & 0x0f);
      result [0] = ( int ) lower + ( int ) upper * 10;

      /* now grab minutes. The upper half holds the 10s of minutes          */
      lower      = state [timeReg++];
      upper      = ( byte ) ((lower >>> 4) & 0x07);
      lower      = ( byte ) (lower & 0x0f);
      result [1] = ( int ) lower + ( int ) upper * 10;

      /* now grab the hours. The lower half is single hours again, but the
         upper half of the byte is determined by the 2nd bit - specifying
         12/24 hour time. */
      lower = state [timeReg++];
      upper = ( byte ) ((lower >>> 4) & 0x07);
      lower = ( byte ) (lower & 0x0f);

      int hours;

      // if the 2nd bit is 1, convert 12 hour time to 24 hour time.
      if ((upper >>> 2) != 0)
      {

         // extract the AM/PM byte (PM is indicated by a 1)
         byte PM = ( byte ) (((upper << 6) >>> 7) & 0x01);

         // isolate the 10s place
         upper = ( byte ) (upper & 0x01);
         hours = upper * 10 + PM * 10;
      }
      else
         hours = upper * 10;   // already in 24 hour format

      hours      += lower;
      result [2] = hours;

      return result;
   }

   /**
    * Set the time in the DS1921 time register format.
    */
   private void setTime (int timeReg, int hours, int minutes, int seconds,
                         boolean AMPM, byte[] state)
   {
      byte upper, lower;

      /* format in bytes and write seconds */
      upper                = ( byte ) (seconds / 10);
      upper                = ( byte ) ((upper << 4) & 0xf0);
      lower                = ( byte ) (seconds % 10);
      lower                = ( byte ) (lower & 0x0f);
      state [timeReg & 31] = ( byte ) (upper | lower);

      timeReg++;

      /* format in bytes and write minutes */
      upper                = ( byte ) (minutes / 10);
      upper                = ( byte ) ((upper << 4) & 0xf0);
      lower                = ( byte ) (minutes % 10);
      lower                = ( byte ) (lower & 0x0f);
      state [timeReg & 31] = ( byte ) (upper | lower);

      timeReg++;

      /* format in bytes and write hours/(12/24) bit */
      if (AMPM)
      {
         upper = ( byte ) 0x04;

         if (hours > 11)
            upper = ( byte ) (upper | 0x02);

         // this next function simply checks for a decade hour
         if (((hours % 12) == 0) || ((hours % 12) > 9))
            upper = ( byte ) (upper | 0x01);

         if (hours > 12)
            hours = hours - 12;

         if (hours == 0)
            lower = ( byte ) 0x02;
         else
            lower = ( byte ) ((hours % 10) & 0x0f);
      }
      else
      {
         upper = ( byte ) (hours / 10);
         lower = ( byte ) (hours % 10);
      }

      upper                = ( byte ) ((upper << 4) & 0xf0);
      lower                = ( byte ) (lower & 0x0f);
      state [timeReg & 31] = ( byte ) (upper | lower);

      timeReg++;
   }

   /**
    * Set the current date in the DS1921's real time clock.
    *
    * year - The year to set to, i.e. 2001.
    * month - The month to set to, i.e. 1 for January, 12 for December.
    * day - The day of month to set to, i.e. 1 to 31 in January, 1 to 30 in April.
    */
   private void setDate (int year, int month, int day, byte[] state)
   {
      byte upper, lower;

      /* write the day byte (the upper holds 10s of days, lower holds single days) */
      upper        = ( byte ) (day / 10);
      upper        = ( byte ) ((upper << 4) & 0xf0);
      lower        = ( byte ) (day % 10);
      lower        = ( byte ) (lower & 0x0f);
      state [0x04] = ( byte ) (upper | lower);

      /* write the month bit in the same manner, with the MSBit indicating
         the century (1 for 2000, 0 for 1900) */
      upper = ( byte ) (month / 10);
      upper = ( byte ) ((upper << 4) & 0xf0);
      lower = ( byte ) (month % 10);
      lower = ( byte ) (lower & 0x0f);

      if (year > 1999)
      {
         upper = ( byte ) (upper | 128);

         //go ahead and fix up the year too while i'm at it
         year = year - 2000;
      }
      else
         year = year - 1900;

      state [0x05] = ( byte ) (upper | lower);

      // now write the year
      upper        = ( byte ) (year / 10);
      upper        = ( byte ) ((upper << 4) & 0xf0);
      lower        = ( byte ) (year % 10);
      lower        = ( byte ) (lower & 0x0f);
      state [0x06] = ( byte ) (upper | lower);
   }

   //////////////////////////////////////////////////////////////
   //
   //   Public methods
   //
   //////////////////////////////////////////////////////////////

   /**
 * To ensure that all parts can talk to the onewire bus
 * at their desired speed, each method contains a call
 * to doSpeed().  However, this is an expensive operation.
 * You can handle the speed in your
 * application without the expense of the doSpeed call
 * by calling this with false.  The default behaviour is
 * to call doSpeed().
 *
 * @param doSpeedCheck True if you want doSpeed() called before every
 * onewire bus access.  False to skip this expensive
 * call.
 */
   public synchronized void setSpeedCheck (boolean doSpeedCheck)
   {
      doSpeedEnable = doSpeedCheck;
   }

   /**
    * Returns the maximum speed this iButton or 1-Wire device can
    * communicate at.
    */
   public int getMaxSpeed ()
   {
      return DSPortAdapter.SPEED_OVERDRIVE;
   }

   /**
    *  Returns the Dallas Semiconductor part number of the iButton
    *  as a string.
    *
    *  @return String representation of the iButton name
    *
    */
   public String getName ()
   {
      return "DS1921";
   }

   /**
    *  Return the alternate Dallas Semiconductor part number or name.
    *
    *  @return String representation of the alternate names.
    */
   public String getAlternateNames ()
   {
      return "Thermochron";
   }

   /**
    *  Return a short description of the function of the iButton type.
    *
    *  @return String representation of the function description.
    */
   public String getDescription ()
   {
      return "Rugged, self-sufficient 1-Wire device that, once setup for "
             + "a mission, will measure the temperature and record the result in "
             + "a protected memory section. It can store up to 2048 temperature "
             + "measurements and will take these measurements at a rate specified "
             + "by the user.  The thermochron also keeps track of the number of times "
             + "the temperature falls on a given 1.5 degree range, and stores the "
             + "data in histogram format.";
   }

   /**
    * Convert a temperature from the DS1921 byte format to degrees celcius.
    * The raw temperature readings are just unsigned byte sized values that
    * need to be converted to a valid temperature in degreees celcius.
    *
    * @param tempByte raw DS1921 temperature reading.
    *
    * @return double representing a temperature, in degrees celcius.
    */
   public double decodeTemperature (byte tempByte)
   {
      return ((tempByte & 0x00ff) / 2.0) - 40.0;
   }

   /**
    * Convert a temperature (degrees celcius) to a byte formatted for the DS1921.
    *
    * @param temperature the temperature to convert.
    *
    * @return byte holding the temperature in raw DS1921 format.
    */
   public byte encodeTemperature (double temperature)
   {
      return ( byte ) ((( int ) (2 * temperature) + 80) & 0x000000ff);
   }

   /**
    * Write a byte of data into the DS1921's memory. Note that writing to
    * the register page while a mission is in progress ends that mission.
    *
    * @param memAddr the address to write the byte at. (Usually in
    * the range of 0x200-0x21F)
    * @param source the data byte to write to memory.
    *
    * @throws OneWireIOException Data wasnt written properly [ recoverable ]
    * @throws OneWireException Part could not be found [ fatal ]
    */
   public void writeByte (int memAddr, byte source)
      throws OneWireIOException, OneWireException
   {

      // User should only need to write to the 32 byte register page
      byte[] buffer = new byte [5];

      // break the address into its bytes
      byte msbAddress = ( byte ) ((memAddr >>> 8) & 0x0ff);
      byte lsbAddress = ( byte ) (memAddr & 0x0ff);

      /* check for valid parameters */
      if ((msbAddress > 0x1F) || (msbAddress < 0))
         throw new IllegalArgumentException(
            "OneWireContainer21-Address for write out of range.");

      /* perform the write and verification */
      if (doSpeedEnable)
         doSpeed();

      if (adapter.select(address))
      {

         /* write to the scratchpad first */
         buffer [0] = WRITE_SCRATCHPAD_COMMAND;
         buffer [1] = lsbAddress;
         buffer [2] = msbAddress;
         buffer [3] = source;

         adapter.dataBlock(buffer, 0, 4);

         /* read it back for the verification bytes required to copy it to mem */
         adapter.select(address);

         buffer [0] = READ_SCRATCHPAD_COMMAND;

         for (int i = 1; i < 5; i++)
            buffer [i] = ( byte ) 0x0ff;

         adapter.dataBlock(buffer, 0, 5);

         // check to see if the data was written correctly
         if (buffer [4] != source)
            throw new OneWireIOException(
               "OneWireContainer21-Error writing data byte.");

         /* now perform the copy from the scratchpad to memory */
         adapter.select(address);

         buffer [0] = COPY_SCRATCHPAD_COMMAND;

         // keep buffer[1]-buffer[3] because they contain the verification bytes
         buffer [4] = ( byte ) 0xff;

         adapter.dataBlock(buffer, 0, 5);

         /* now check to see that the part sent a 01010101 indicating a success */
         if ((buffer [4] != ( byte ) 0xAA) && (buffer [4] != ( byte ) 0xBB))
            throw new OneWireIOException(
               "OneWireContainer21-Error writing data byte.");
      }
      else
         throw new OneWireException("OneWireContainer21-Device not present.");
   }

   /**
    * Read a single byte from the DS1921.
    *
    * @param <code>int</code> representation of the address to read from.
    *
    * @param memAddr The address to read from.  Registers begin at address
    * 0x200 and end at address 0x21F.
    *
    * @return byte the data byte read.
    *
    * @throws OneWireIOException Data was not read correctly [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public byte readByte (int memAddr)
      throws OneWireIOException, OneWireException
   {
      byte[] buffer = new byte [4];

      // break the address up into bytes
      byte msbAddress = ( byte ) ((memAddr >> 8) & 0x000000ff);
      byte lsbAddress = ( byte ) (memAddr & 0x000000ff);

      /* check the validity of the address */
      if ((msbAddress > 0x1F) || (msbAddress < 0))
         throw new IllegalArgumentException(
            "OneWireContainer21-Address for read out of range.");

      /* read a user specified amount of memory and verify its validity */
      if (doSpeedEnable)
         doSpeed();

      if (adapter.select(address))
      {
         buffer [0] = READ_MEMORY_CRC_COMMAND;
         buffer [1] = lsbAddress;
         buffer [2] = msbAddress;
         buffer [3] = ( byte ) 0x0ff;

         adapter.dataBlock(buffer, 0, 4);

         return buffer [3];
      }
      else
         throw new OneWireException("OneWireContainer21-Device not present.");
   }

   /**
    * Get the flag located in the specified register and return its status.
    * This method actually communicates with the Thermocron.  To improve
    * performance if you intend to make multiple calls to this method,
    * first call readDevice and use the getFlag(int, byte, byte[])
    * method instead.
    *
    * @param register address of register containing the flag.<br>
    * Valid parameters: CONTROL_REGISTER, STATUS_REGISTER.
    * @param bitMask bitmask representing the flag. There are 7
    * valid parameters for each register. See "FIELD SUMMARY" for a listing.
    *
    * @return boolean indicating the status of the flag.
    * True signifies a "1" and false signifies a "0".
    *
    * @throws OneWireIOException Data was not read correctly [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public boolean getFlag (int register, byte bitMask)
      throws OneWireIOException, OneWireException
   {
      return ((readByte(register) & bitMask) != 0);
   }

   /**
    * Get the flag located in the specified register and return its status.
    *
    * @param register address of register containing the flag.<br>
    * Valid parameters: CONTROL_REGISTER, STATUS_REGISTER.
    * @param bitMask bitmask representing the flag. There are 7
    * valid parameters for each register. See "FIELD SUMMARY" for a listing.
    * @param state State of Thermocron returned from readDevice().
    *
    * @return boolean indicating the status of the flag.
    * True signifies a "1" and false signifies a "0".
    */
   public boolean getFlag (int register, byte bitMask, byte[] state)
   {
      return ((state [register & 31] & bitMask) != 0);
   }

   /**
    * Set the flag located in the specified register. If a mission is in
    * progress a OneWireIOException will be thrown (one cannot write to the
    * registers while a mission is commencing).
    *
    * @param register the address of the target register.
    *  <p>Valid parameters: CONTROL_REGISTER, STATUS_REGISTER.
    * @param bitMask bitmask representing the flag. There are 7
    * valid parameters for each register. See "FIELD SUMMARY" for details.
    * @param flagValue  value to set flag at.
    *
    * @throws OneWireIOException Data was not written correctly [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public void setFlag (int register, byte bitMask, boolean flagValue)
      throws OneWireIOException, OneWireException
   {

      // check for Mission in Progress flag
      if (getFlag(STATUS_REGISTER, MISSION_IN_PROGRESS_FLAG))
         throw new OneWireIOException(
            "OneWireContainer21-Cannot write to register while mission is in progress.");

      // read the current flag settings
      byte flags = readByte(register);

      if (flagValue)
         flags = ( byte ) (flags | bitMask);
      else
         flags = ( byte ) (flags & ~(bitMask));

      // write the regs back
      writeByte(register, flags);
   }

   //    * @param state State of Thermocron returned from readDevice().

   /**
    * Method setFlag
    *
    *
    * @param register
    * @param bitMask
    * @param flagValue
    * @param state
    *
    */
   public void setFlag (int register, byte bitMask, boolean flagValue,
                        byte[] state)
   {
      register = register & 31;

      byte flags = state [register];

      if (flagValue)
         flags = ( byte ) (flags | bitMask);
      else
         flags = ( byte ) (flags & ~(bitMask));

      // write the regs back
      state [register] = flags;
   }

   /**
    * Tells the DS1921 to begin its mission.
    * If a mission is already in progress, this will throw a OneWireIOException.
    * Note: The mission will wait the number of minutes specified by the
    * mission start delay (use setMissionStartDelay()) before beginning.
    *
    * @param sampleRate the number of minutes to wait in between temp readings.
    *
    * @throws OneWireIOException Mission was not started [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    *
    */
   public void enableMission (int sampleRate)
      throws OneWireIOException, OneWireException
   {

      /* check for valid parameters */
      if ((sampleRate > 255) || (sampleRate < 0))
         throw new IllegalArgumentException(
            "OneWireContainer21-Sample rate must be 255 minutes or less");

      if (getFlag(STATUS_REGISTER, MISSION_IN_PROGRESS_FLAG))
         throw new OneWireIOException(
            "OneWireContainer30-Unable to start mission (Mission already in Progress)");

      // read the current register status
      byte controlReg = readByte(CONTROL_REGISTER);

      // Set the enable mission byte to 0
      controlReg = ( byte ) (controlReg & 0xEF);

      writeByte(CONTROL_REGISTER, controlReg);

      // set the sample rate and let her rip
      writeByte(0x20D, ( byte ) (sampleRate & 0x000000ff));
   }

   /**
    * Disable a mission that is in progress.
    *
    * @throws OneWireIOException Mission was not stopped [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public void disableMission ()
      throws OneWireIOException, OneWireException
   {

      // first read the current register
      byte statusReg = readByte(STATUS_REGISTER);

      // Set the MIP bit to 0, regardless of whether a mission is commencing
      statusReg = ( byte ) (statusReg & 0xDF);   // set the MIP bit to 0;

      writeByte(STATUS_REGISTER, statusReg);
   }

   /**
    * Sets the time to wait before starting the mission.
    * The DS1921 will sleep this many minutes after the mission is enabled.
    * If a mission is in progress a OneWireIOException is thrown.
    *
    * @param missionStartDelay specifies the time, in minutes, to delay.
    * @param state State of Thermocron returned from readDevice().
    */
   public void setMissionStartDelay (int missionStartDelay, byte[] state)
   {
      state [0x12] = ( byte ) (missionStartDelay);
      state [0x13] = ( byte ) (missionStartDelay >> 8);
   }

   /**
    * Clears the memory - Must be run before setup for a new mission. If a
    * mission is in progress a OneWireIOException is thrown.
    *
    * @throws OneWireIOException Memory was not cleared [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public void clearMemory ()
      throws OneWireIOException, OneWireException
   {

      // first set the MCLRE bit to 1 in the control register
      setFlag(CONTROL_REGISTER, MEMORY_CLEAR_ENABLE_FLAG, true);

      // now send the memory clear command and wait 5 milliseconds
      if (doSpeedEnable)
         doSpeed();

      adapter.reset();

      if (adapter.select(address))
      {
         adapter.putByte(CLEAR_MEMORY_COMMAND);

         try
         {
            Thread.sleep(5);
         }
         catch (Exception e)
         {

            //drain it
         }
      }
      else
         throw new OneWireException("OneWireContainer21-Device not found.");
   }

   /**
    * Get the alarm clock time settings.  The alarm settings used by the
    * Thermocron are Hour, Minute, Second, and Day of Week.
    *
    * @param state State of Thermocron returned from readDevice().
    *
    * @return Calendar object holding the alarm clock time and day
    * of the week. The day of the week is stored in the DAY_OF_MONTH element.
    */
   public Calendar getAlarmTime (byte[] state)
   {

      // first get the time
      int[]    time   = getTime(0x207, state);
      Calendar result = new GregorianCalendar(0, 0, 0, time [2], time [1],
                                              time [0]);

      // now put the day of the week in there
      byte dayOfWeek = ( byte ) (state [0x0A] & 0x07);

      result.set(Calendar.DAY_OF_MONTH, dayOfWeek);

      return result;
   }

   /**
    * Set the DS1921's alarm clock's time.  Some of the parameters
    * might be unimportant depending on the alarm frequency setting.  For instance,
    * if the alarm frequency setting is ONCE_PER_MINUTE, then the hour
    * argument is irrelevant.
    *
    * @param hours The hour of the day for the clock alarm
    * @param minutes The minute setting for the clock alarm
    * @param seconds The second setting for the clock alarm
    * @param day The day of the week for the clock alarm.  The value
    * must be between 1 (Sunday) and 7 (Saturday).
    * @param alarmFrequency representation of the alarm frequency.
    * <p>
    * Valid parameters are: ONCE_PER_SECOND, ONCE_PER_MINUTE, ONCE_PER_HOUR,
    *                       ONCE_PER_DAY, ONCE_PER_WEEK.
    * @param state State of Thermocron returned from readDevice().
    */
   public void setClockAlarm (int hours, int minutes, int seconds, int day,
                              int alarmFrequency, byte[] state)
   {
      setTime(0x207, hours, minutes, seconds, false, state);

      state [0x0a] = ( byte ) day;

      int number_0_msb = 0;   //how many of the MS, MM, MH, MD bytes have 

      //0 as their ms bit???
      switch (alarmFrequency)
      {

         case ONCE_PER_SECOND :
            number_0_msb = 0;
            break;
         case ONCE_PER_MINUTE :
            number_0_msb = 1;
            break;
         case ONCE_PER_HOUR :
            number_0_msb = 2;
            break;
         case ONCE_PER_DAY :
            number_0_msb = 3;
            break;
         case ONCE_PER_WEEK :
            number_0_msb = 4;
            break;
      }

      for (int i = 0x07; i < 0x0b; i++)
      {
         if (number_0_msb > 0)
         {
            number_0_msb--;

            state [i] = ( byte ) (state [i] & 0x7f);   //make the leading bit 0
         }
         else
            state [i] = ( byte ) (state [i] | 0x80);   //make the laeding bit 1
      }
   }

   /**
    * Return the rate at which the DS1921 takes temperature measurements.
    *
    * @param state State of Thermocron returned from readDevice().
    *
    * @return int representing the time, in minutes, between temperature readings.
    */
   public int getSampleRate (byte[] state)
   {
      return ( int ) state [0x0D];
   }

   /**
    * Read the Mission Samples Counter and determine the number of samples
    * taken on this mission.
    *
    * @param state State of Thermocron returned from readDevice().
    *
    * @return int the number of samples taken on this mission.
    */
   public int getMissionSamplesCounter (byte[] state)
   {
      byte low    = state [0x1A];
      byte medium = state [0x1B];
      byte high   = state [0x1C];

      return (((high << 16) & 0x00ff0000) | ((medium << 8) & 0x0000ff00)
              | (low & 0x000000ff));
   }

   /**
    * Read the device samples counter and determine the number of samples taken
    * by this device.
    *
    * @param state State of Thermocron returned from readDevice().
    *
    * @return int the number of measurements, including forced,
    * taken by this device.
    */
   public int getDeviceSamplesCounter (byte[] state)
   {
      byte low    = state [0x1D];
      byte medium = state [0x1E];
      byte high   = state [0x1F];

      return (((high << 16) & 0x00ff0000) | ((medium << 8) & 0x0000ff00)
              | (low & 0x000000ff));
   }

   /**
    * Returns the date and time that the Mission was first started.
    *
    * @param state State of Thermocron returned from readDevice().
    *
    * @return Calendar object containing the date/time.
    */
   public Calendar getMissionTimeStamp (byte[] state)
   {
      byte upper, lower;

      /* i know here that the mission time stamp does not start at address 214,
       * however--the mission time stamp starts with minutes, and i have
       * a method to read the seconds.  since i can ignore that in this case,
       * i can go ahead and 'fake' read the seconds
       */
      int[] time_result = getTime(0x214, state);
      int[] date_result = getDate(0x217, state);
      int   year        = date_result [0] % 100;

      // determine the century based on the number of samples taken
      int numberOfCounts         = getMissionSamplesCounter(state);
      int timeBetweenCounts      = getSampleRate(state);
      int yearsSinceMissionStart =
         ( int ) ((numberOfCounts * timeBetweenCounts) / (525600));

      // get a rough estimate of how long ago this was
      //result = getDateTime(state);
      int[] offset_result = getDate(0x204, state);
      int   result_year   = offset_result [0];

      // add the century based on this calculation
      //if ((result.get(Calendar.YEAR) - yearsSinceMissionStart) > 1999)
      if ((result_year - yearsSinceMissionStart) > 1999)
         year += 2000;
      else
         year += 1900;

      // protect against deviations that may cause gross errors
      //if (year > result.get(Calendar.YEAR))
      if (year > result_year)
         year -= 100;

      return new GregorianCalendar(year, date_result [1] - 1,
                                   date_result [2], time_result [2],
                                   time_result [1]);
   }

   /**
    * This method is a convenience method to help determine
    * the times for values in a temperature log.  If rollover
    * is enabled, temperature log entries will over-write previous
    * entries once more than 2048 logs are written.  This value can be
    * added to the underlying millisecond value of getMissionTimeStamp()
    * to determine the time that the 'first' log entry actually occurred.
    * Take care with Java's Daylight Savings Time offsets when using this
    * function--if you use the Date or Calendar class to print this out,
    * Java may try to automatically format the string to handle
    * Daylight Savings Time, resulting in offset by 1 hour problems.
    *
    * @param state State of Thermocron returned from readDevice().
    * @return Milliseconds between the beginning of the mission
    * and the first log entry's time reported from
    * getTemperatureLog().
    */
   public long getFirstLogOffset (byte[] state)
   {
      long counter = getMissionSamplesCounter(state);

      if ((counter < 2049)
              || (!getFlag(CONTROL_REGISTER, ROLLOVER_ENABLE_FLAG, state)))
         return 0;

      //else we need to figure out when the first sample occurred
      //since there are counter entries, the first entry is (counter - 2048)
      //so if we multiply that times milliseconds between entry,
      //we should be OK
      counter -= 2048;

      //rate is the rate in minutes, must multiply by 60 to be seconds,
      //then by 1000 to be milliseconds
      int rate = this.getSampleRate(state);

      counter = counter * rate * 1000 * 60;

      return counter;
   }

   /**
    * Return the log of temperature measurements.
    * Reads through the entire temperature log, and returns an
    * array containing the temperatures recorded.
    *
    * @param state State of Thermocron returned from readDevice().
    *
    * @return byte[] the DS1921's log.
    * Use decodeTemperature(byte) to get the double value of the encoded temperature.
    * See the DS1921 datasheet for more on the data's encoding scheme.
    * The array's length equals the number of measurements taken thus far.
    *
    * @throws OneWireIOException Did not read the memory [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public synchronized byte[] getTemperatureLog (byte[] state)
      throws OneWireIOException, OneWireException
   {
      byte[] result;

      /* get the number of samples and the rate at which they were taken */
      int numberOfReadings = getMissionSamplesCounter(state);

      // used for rollover
      int offsetDepth = 0;

      /* this next line checks the rollover bit and whether a rollover occured */
      if ((getFlag(CONTROL_REGISTER, ROLLOVER_ENABLE_FLAG, state))
              && (numberOfReadings > 2048))
      {

         // offsetDepth holds the number of new readings before we hit older ones
         offsetDepth = numberOfReadings % 2048;
      }

      // the max number of readings STORED is 2048
      if (numberOfReadings > 2048)
         numberOfReadings = 2048;

      result = new byte [numberOfReadings];

      int offset = 0;

      while (offset < numberOfReadings)
      {
         log.readPageCRC(offset >> 5, false, read_log_buffer, offset);

         offset += 32;
      }

      //put the bytes into the output array, but careful for the case
      //where we rolled over that we start in the right place!
      System.arraycopy(read_log_buffer, offsetDepth, result, 0,
                       numberOfReadings - offsetDepth);
      System.arraycopy(read_log_buffer, 0, result,
                       numberOfReadings - offsetDepth, offsetDepth);

      return result;
   }

   /**
    * Return an array of int values representing the 63 counter bins holding
    * the DS1921 histogram data.<br>
    * For the temperature histogram the DS1921 provides 63 bins that each consist
    * of a 16-bit non rolling-over binary counter that is incremented each time
    * a temperature value acquired during a mission falls into the range of
    * the bin. The bin to be updated is determined by cutting off the
    * two least significant bits of the binary temperature value.  Thus bin 1
    * will hold the counter for temperatures ranging from -40 to -38.5 (celcius)
    * and lower. Bin 2 is associated with the range of -38 to 36.5 and so on.
    * Bin 63 is the final bin, and it holds temperature values of 84 degrees
    * and higher.
    *
    * @return int[] the 63 temperature counters.
    *
    * @throws OneWireIOException Did not read the memory [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public int[] getTemperatureHistogram ()
      throws OneWireIOException, OneWireException
   {
      int[]  result = new int [63];
      byte[] buffer = new byte [128];

      /* read the data first */
      int offset = 0;

      while (offset < 128)
      {
         histogram.readPageCRC(offset >> 5, false, buffer, offset);

         offset += 32;
      }

      int i = 0, j = 0;

      while (i < 63)
      {

         // get the 2 byte counter values
         result [i] = (buffer [j] & 0x00ff)
                      | ((buffer [j + 1] << 8) & 0xff00);

         i++;

         j += 2;
      }

      return result;
   }

   /**
    * Returns true if the specified alarm has been triggered.
    *
    * @param alarmBit a bitmask indicating the alarm to check.
    * Acceptable parameters: TEMPERATURE_LOW_ALARM, TEMPERATURE_HIGH_ALARM,
    *                        TIMER_ALARM.
    * @param state State of Thermocron returned from readDevice().
    *
    * @return boolean indicating whether the alarm has gone off.
    */
   public boolean getAlarmStatus (byte alarmBit, byte[] state)
      throws OneWireIOException, OneWireException
   {
      return ((state [STATUS_REGISTER & 31] & alarmBit) != 0);
   }

   /**
    * Returns an array containing the alarm log.
    * The DS1921 contains two seperate alarm logs. One for the temperature high
    * alarm and one for the temperature low alarm.  Each log can store
    * up to 12 log entries and each log entry will record up to 255
    * consecutive alarm violations.
    *
    * @param alarmBit representing the alarm log to get.
    * Acceptable parameters: TEMPERATURE_LOW_ALARM, TEMPERATURE_HIGH_ALARM.
    *
    * @return byte[] representing the time/duration of the alarm.
    * Structure of returned array:
    * The number of logs in this alarm history if equal to the array length
    * divided by 4, since each entry is 4 bytes.  To extract the starting
    * offset and number of violations from the array:
    * <code>
    *       byte[] data = ds1921.getAlarmHistory(TEMPERATURE_HIGH_ALARM);
    *       . . .
    *       for (int i=0;i < data.length/4; i++)
    *       {
    *           start_offset = (data [i * 4] & 0x0ff)
    *                     | ((data [i * 4 + 1] << 8) & 0x0ff00)
    *                     | ((data [i * 4 + 2] << 16) & 0x0ff0000);
    *           violation_count = 0x0ff & data[i*4+3];
    *
    *           . . .
    *
    *           // note: you may find it useful to multiply start_offset
    *           //       by getSampleRate() in order to get the number of
    *           //       minutes into the mission that the violation occurred
    *           //       on.  You can do the same with violation_count
    *           //       to determine how long the violation lasted.
    * </code>
    *
    * @throws OneWireIOException Did not read the memory [ recoverable ]
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public byte[] getAlarmHistory (byte alarmBit)
      throws OneWireIOException, OneWireException
   {
      int    counter   = 0;
      byte[] temp_data = new byte [96];
      int    offset    = 0;

      while (offset < 96)
      {
         alarm.readPageCRC(offset >> 5, false, temp_data, offset);

         offset += 32;
      }

      if (alarmBit == TEMPERATURE_LOW_ALARM)
         offset = 0;
      else
         offset = 48;

      /* check how many entries there are (each entry consists of 4 bytes)
         the fourth byte of each entry is the counter - check if its > 0 */
      while ((counter * 4 + 3 + offset < temp_data.length)
             && (temp_data [counter * 4 + 3 + offset] != 0))
         counter++;

      byte[] data = new byte [counter << 2];

      System.arraycopy(temp_data, offset, data, 0, counter << 2);

      return data;
   }

   /**
    * 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 byte[] 1-Wire device sensor state
    *
    * @throws OneWireIOException
    * @throws OneWireException Could not find the part [ fatal ]
    */
   public byte[] readDevice ()
      throws OneWireIOException, OneWireException
   {
      byte[] buffer = new byte [32];

      //going to return the register page, 32 bytes
      register.readPageCRC(0, false, buffer, 0);

      return buffer;
   }

   /**
    * 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 Could not find the part [ fatal ]
    */
   public void writeDevice (byte[] state)
      throws OneWireIOException, OneWireException
   {
      if (getFlag(STATUS_REGISTER, MISSION_IN_PROGRESS_FLAG))
         throw new OneWireIOException(
            "OneWireContainer21-Cannot write to registers while mission is in progress.");

      int start = updatertc ? 0
                            : 7;

      register.write(start, state, start, 20 - start);   //last 12 bytes are read only

      synchronized (this)
      {
         updatertc = false;
      }
   }

   ////////////////////////////////////////////////////////////////////////////////////////
   //
   //       Temperature Interface Functions
   //
   ////////////////////////////////////////////////////////////////////////////////////////

   /**
   * 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 false;
   }

   /**
    * Query to get an array of available resolutions in degrees C.
    *
    * @return double[], available of resolutions in degrees C
    */
   public double[] getTemperatureResolutions ()
   {
      double[] d = new double [1];

      d [0] = 1.5;

      return d;
   }

   /**
    * Query to get the high/low resolution in degrees C.
    *
    * @return double, high/low resolution resolution in C
    */
   public double getTemperatureAlarmResolution ()
   {
      return 1.5;
   }

   /**
    * Query to get the maximum temperature in degrees C.
    *
    * @return double, maximum temperature in C
    */
   public double getMaxTemperature ()
   {
      return 85.0;
   }

   /**
    * Query to get the minimum temperature in degrees C.
    *
    * @return double, minimum temperature in C
    */
   public double getMinTemperature ()
   {
      return -10.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 Could not find the part [ fatal ]
    */
   public void doTemperatureConvert (byte[] state)
      throws OneWireIOException, OneWireException
   {

      /* check for mission in progress */
      if (getFlag(STATUS_REGISTER, MISSION_IN_PROGRESS_FLAG))
         throw new OneWireIOException("OneWireContainer21-Cant force "
                                      + "temperature read during a mission.");

      /* get the temperature*/
      if (doSpeedEnable)
         doSpeed();   //we aren't worried about how long this takes...we're sleeping for 750 ms!

      adapter.reset();

      if (adapter.select(address))
      {

         // perform the temperature conversion
         adapter.putByte(CONVERT_TEMPERATURE_COMMAND);

         try
         {
            Thread.sleep(750);
         }
         catch (InterruptedException e){}

         // grab the temperature
         state [0x11] = readByte(0x211);
      }
      else
         throw new OneWireException("OneWireContainer21-Device not found!");
   }

   //--------
   //-------- 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)
   {
      return decodeTemperature(state [0x11]);
   }

   /**
    * 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)
   {
      if ((alarmType == TEMPERATURE_HIGH_ALARM) || (alarmType == ALARM_HIGH))
         return decodeTemperature(state [0x0c]);
      else
         return decodeTemperature(state [0x0b]);
   }

   /**
    * 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)
   {
      return 1.5;
   }

   //--------
   //-------- 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)
   {
      byte alarm = encodeTemperature(alarmValue);

      if (alarmValue < -40.0)
         alarm = 0;

      if (alarmValue > 85.0)
         alarm = ( byte ) 0xfa;   //maximum value stands for 85.0 C

      if ((alarmType == TEMPERATURE_HIGH_ALARM) || (alarmType == ALARM_HIGH))
      {
         state [0x0c] = alarm;
      }
      else
      {
         state [0x0b] = alarm;
      }
   }

   /**
    * 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
   {
      throw new OneWireException();
   }

   ////////////////////////////////////////////////////////////////////////////////////////
   //
   //       Clock Interface Functions
   //
   ////////////////////////////////////////////////////////////////////////////////////////

   /**
    * Query to see if the clock has an alarm feature.
    *
    * @return boolean, true if real-time-clock has an alarm
    */
   public boolean hasClockAlarm ()
   {
      return true;
   }

   /**
    * Query to see if the clock can be disabled.  See
    * the methods 'isClockRunning()' and 'setClockRunEnable()'.
    *
    * @return boolean, true if the clock can be enabled and
    * disabled.
    */
   public boolean canDisableClock ()
   {
      return true;
   }

   /**
    * Query to get the clock resolution in milliseconds
    *
    * @return long, get the clock resolution in milliseconds.
    */
   public long getClockResolution ()
   {
      return 1000;
   }

   //--------
   //-------- Clock 'get' Methods
   //--------

   /**
    * This method extracts the Clock Value in milliseconds from the
    * state data retrieved from the 'readDevice()' method.
    *
    * @param state byte array of device state
    *
    * @return long time - in milliseconds that have
    * occured since 1970.
    */
   public long getClock (byte[] state)
   {

      /* grab the time (at location 200, date at 204) */
      int[] time = getTime(0x200, state);
      int[] date = getDate(0x204, state);

      //date[1] - 1 because Java months are 0 offset
      //date[0] - 1900 because Java years are from 1900
      //Date d = new Date(date[0]-1900, date[1]-1, date[2], time[2], time[1], time[0]);
      Calendar d = new GregorianCalendar(date [0], date [1] - 1, date [2],
                                         time [2], time [1], time [0]);

      return d.getTime().getTime();
   }

   /**
    * This method extracts the Clock Alarm Value from the provided
    * state data retrieved from the 'readDevice()'
    * method.  In the case of the thermocron it reports the next time
    * the alarm will go off, since the Thermocron is set to
    * alarm 'every week monday at 4:30' or in some similar
    * manner.
    *
    * @param state byte array of device state
    *
    * @return long time - in milliseconds that have
    * the clock alarm is set to.
    */
   public long getClockAlarm (byte[] state)
   {

      //first get the normal real time clock
      int[] time = getTime(0x200, state);
      int[] date = getDate(0x204, state);

      //date[0] = year
      //date[1] = month
      //date[2] = date
      //time[2] = hour
      //time[1] = minute
      //time[0] = second
      //date[1] - 1 because Java does funky months from offset 0
      Calendar c = new GregorianCalendar(date [0], date [1] - 1, date [2],
                                         time [2], time [1], time [0]);

      //get the seconds into the day we are at
      int time_into_day = time [0] + 60 * time [1] + 60 * 60 * time [2];

      //now lets get the alarm specs
      int[] a_time = getTime(0x207, state);

      //get the seconds into the day the alarm is at
      int a_time_into_day = a_time [0] + 60 * a_time [1]
                            + 60 * 60 * a_time [2];

      // now put the day of the week in there
      byte dayOfWeek = ( byte ) (state [0x0A] & 0x07);

      if (dayOfWeek == 0)
         dayOfWeek++;

      byte MS = ( byte ) ((state [0x07] >>> 7) & 0x01);
      byte MM = ( byte ) ((state [0x08] >>> 7) & 0x01);
      byte MH = ( byte ) ((state [0x09] >>> 7) & 0x01);
      byte MD = ( byte ) ((state [0x0A] >>> 7) & 0x01);

      switch (MS + MM + MH + MD)
      {

         case 4 :                                       //ONCE_PER_SECOND
            c.add(c.SECOND, 1);
            break;
         case 3 :                                       //ONCE_PER_MINUTE
            if (!(a_time_into_day < time_into_day))     //alarm has occurred
               c.add(c.MINUTE, 1);

            c.set(c.SECOND, a_time [0]);
            break;
         case 2 :                                       //ONCE_PER_HOUR
            if (!(a_time_into_day < time_into_day))     //alarm has occurred
               c.add(c.HOUR, 1);                        //will occur again next hour

            c.set(c.SECOND, a_time [0]);
            c.set(c.MINUTE, a_time [1]);
            break;
         case 1 :                                       //ONCE_PER_DAY
            c.set(c.SECOND, a_time [0]);
            c.set(c.MINUTE, a_time [1]);
            c.set(c.HOUR, a_time [2]);

            if ((a_time_into_day < time_into_day))      //alarm has occurred
               c.add(c.DATE, 1);                        //will occur again tomorrow
            break;
         case 0 :                                       //ONCE_PER_WEEK
            c.set(c.SECOND, a_time [0]);
            c.set(c.MINUTE, a_time [1]);

            // c.set(c.AM_PM, (a_time[2] > 11) ? c.PM : c.AM);
            c.set(c.HOUR, a_time [2]);

            if (dayOfWeek == c.get(c.DAY_OF_WEEK))
            {

               //has alarm already occurred today?
               if ((a_time_into_day < time_into_day))   //alarm has occurred
                  c.add(c.DATE, 7);                     //will occur again next week
            }
            else
            {

               //roll the day of the week until it matches
               while (dayOfWeek != c.get(c.DAY_OF_WEEK))
                  c.roll(c.DATE, true);
            }
            break;
      }

      return c.getTime().getTime();   //c->getTime returns Date, Date->getTime returns long
   }

   /**
    * This method checks if the Clock Alarm flag has been set
    * from the state data retrieved from the
    * 'readDevice()' method.
    *
    * @param state byte array of device state
    *
    * @return boolean true if clock is alarming
    */
   public boolean isClockAlarming (byte[] state)
   {
      return ((state [STATUS_REGISTER & 31] & TIMER_ALARM) != 0);
   }

   /**
    * This method checks if the Clock Alarm is enabled
    * from the provided state data retrieved from the
    * 'readDevice()' method.
    *
    * @param state byte array of device state
    *
    * @return boolean true if clock alarm is enabled
    */
   public boolean isClockAlarmEnabled (byte[] state)
   {
      return ((state [CONTROL_REGISTER & 31] & TIMER_ALARM_SEARCH_FLAG) != 0);
   }

   /**
    * This method checks if the device's oscilator is enabled.  The clock
    * will not increment if the clock is not enabled.
    * This value is read from the provided state data retrieved from the
    * 'readDevice()' method.
    *
    * @param state byte array of device state
    *
    * @return boolean true
    */
   public boolean isClockRunning (byte[] state)
   {

      //checks for equal to 0 since active low means clock is running
      return ((state [CONTROL_REGISTER & 31] & OSCILLATOR_ENABLE_FLAG) == 0);
   }

   //--------
   //-------- Clock 'set' Methods
   //--------

   /**
    * This method sets the Clock time in the provided state data
    * Use the method 'writeDevice()' with
    * this data to finalize the change to the device.
    *
    * @param time millisecond value (from 1970) the user
    * wants the Clock set to.
    * @param state byte array of device state
    */
   public void setClock (long time, byte[] state)
   {
      Date     x = new Date(time);
      Calendar d = new GregorianCalendar();

      d.setTime(x);
      setTime(0x200, d.get(d.HOUR) + (d.get(d.AM_PM) == d.PM ? 12
                                                             : 0), d.get(d.MINUTE),
                                                                   d.get(d.SECOND),
                                                                   false,
                                                                   state);
      setDate(d.get(d.YEAR), d.get(d.MONTH) + 1, d.get(d.DATE), state);

      synchronized (this)
      {
         updatertc = true;
      }
   }

   /**
    * This method sets the Clock Alarm in the provided state
    * data.  Use the method 'writeDevice()' with
    * this data to finalize the change to the device.
    *
    * @param time millisecond value (from 1970) the user
    * wants the Clock alarm set to.
    * @param state byte array of device state
    */
   public void setClockAlarm (long time, byte[] state)
      throws OneWireException
   {

      //can't do this because we need more info on the alarm
      throw new OneWireException(
         "Cannot set the DS1921 Clock Alarm through the Clock interface.");
   }

   /**
    * This method sets the oscillator enable to the specified
    * value. Use the method 'writeDevice()' with this
    * data to finalize the change to the device.
    *
    * @param runEnable boolean, true if want the clock oscillator to
    *                 be enabled.
    * @param state byte array of device state
    */
   public void setClockRunEnable (boolean runEnable, byte[] state)
   {

      // the oscillator enable is active low
      setFlag(CONTROL_REGISTER, OSCILLATOR_ENABLE_FLAG, !runEnable, state);
   }

   /**
    * This method sets the Clock Alarm enable. Use the method
    * 'writeDevice()' with this data to finalize the
    * change to the device.
    *
    * @param  alarmEnable boolean, true to enable the clock alarm
    * @param state byte array of device state
    */
   public void setClockAlarmEnable (boolean alarmEnable, byte[] state)
   {
      setFlag(CONTROL_REGISTER, TIMER_ALARM_SEARCH_FLAG, alarmEnable, state);
   }
}
