
/*---------------------------------------------------------------------------
 * 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.adapter.*;
import com.dalsemi.onewire.OneWireException;


//----------------------------------------------------------------------------

/**
 * iButton container for iButton family type 2C (hex).  This family type is
 * a 'digital potentiometer' (DS2890).
 *
 * @version    0.00, 28 Aug 2000
 * @author     KLA
 */
public class OneWireContainer2C
   extends OneWireContainer
   implements PotentiometerContainer
{

   //--------
   //-------- Variables
   //--------
   private byte[] buffer = new byte [4];

   //--------
   //-------- Finals
   //--------

   /**
    * Useful constant for one wire communications.
    */
   public static final byte WRITE_CONTROL = ( byte ) 0x55;

   /**
    * Useful constant for one wire communications.
    */
   public static final byte READ_CONTROL = ( byte ) 0xaa;

   /**
    * Useful constant for one wire communications.
    */
   public static final byte WRITE_POSITION = ( byte ) 0x0f;

   /**
    * Useful constant for one wire communications.
    */
   public static final byte READ_POSITION = ( byte ) 0xf0;

   /**
    * Useful constant for one wire communications.
    */
   public static final byte INCREMENT = ( byte ) 0xc3;

   /**
    * Useful constant for one wire communications.
    */
   public static final byte DECREMENT = ( byte ) 0x99;

   //--------
   //-------- Constructors
   //--------

   /**
    * Default constructor
    */
   public OneWireContainer2C ()
   {
      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 OneWireContainer2C (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 OneWireContainer2C (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 OneWireContainer2C (DSPortAdapter sourceAdapter, String newAddress)
   {
      super(sourceAdapter, newAddress);
   }

   //--------
   //-------- Methods
   //--------

   /**
    * Retrieve the Dallas Semiconductor part number of the iButton
    * as a string.  For example 'DS1992'.
    *
    * @return  <code>String</code> representation of the iButton name.
    */
   public String getName ()
   {
      return "DS2890";
   }

   /**
    * 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 "Digital Potentiometer";
   }

   /**
    * Retrieve a short description of the function of the iButton type.
    *
    * @return  <code>String</code> representation of the function description.
    */
   public String getDescription ()
   {
      return "1-Wire linear taper digitally controlled potentiometer "
             + "with 256 wiper positions.  0-11 Volt working range.";
   }

   /**
    * Returns the maximum speed this iButton or 1-Wire device can
    * communicate at.
    */
   public int getMaxSpeed ()
   {
      return DSPortAdapter.SPEED_OVERDRIVE;
   }

   //--------
   //-------- Potentiometer Feature methods
   //--------

   /**
    * Query to see if this Potentiometer One Wire Device
    * has linear potentiometer element(s) or logarithmic
    * potentiometer element(s).
    *
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    * @return True if this device has linear potentiometer element(s).
    * False if this device has logarithmic potentiometer element(s).
    */
   public boolean isLinear (byte[] state)
   {
      return ((state [0] & 0x01) == 0x01);
   }

   /**
    * Query to see if this Potentiometer One Wire Device's
    * wiper settings are volatile or non-volatile.
    *
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    * @return True if the wiper settings are volatile.
    * False if the wiper settings are non-volatile.
    */
   public boolean wiperSettingsAreVolatile (byte[] state)
   {
      return ((state [0] & 0x02) == 0x02);
   }

   /**
    * Query to see how many potentiometers this
    * Potentiometer One Wire Device has.
    *
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    * @return The number of potentiometers on this device.
    */
   public int numberOfPotentiometers (byte[] state)
   {
      return (((state [0] >> 2) & 0x03) + 1);
   }

   /**
    * Query to find the number of wiper settings
    * that any wiper on this Potentiometer One Wire
    * Device can have.
    *
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    * @return Number of wiper positions available.
    */
   public int numberOfWiperSettings (byte[] state)
   {
      switch (state [0] & 0x30)
      {

         case 0x00 :
            return 32;
         case 0x10 :
            return 64;
         case 0x20 :
            return 128;
         default :
            return 256;
      }
   }

   /**
    * Query to find the resistance value of the potentiometer.
    *
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    * @return The resistance value in k-Ohms.
    */
   public int potentiometerResistance (byte[] state)
   {
      switch (state [0] & 0xc0)
      {

         case 0x00 :
            return 5;
         case 0x40 :
            return 10;
         case 0x80 :
            return 50;
         default :
            return 100;
      }
   }

   /**
    * Gets the currently selected wiper number.  All wiper actions
    * affect this wiper.  The number of wipers is the same as
    * numberOfPotentiometers().
    *
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    * @return The current wiper number.
    */
   public int getCurrentWiperNumber (byte[] state)
   {
      int wiper         = state [1] & 0x03;
      int wiper_inverse = (state [1] >> 2) & 0x03;

      if ((wiper + wiper_inverse) == 3)
         return wiper;

      return -1;
   }

   /**
    * Sets the currently selected wiper number.  All wiper actions
    * affect this wiper.  The number of wipers is the same as
    * numberOfPotentiometers().
    *
    * @param wiper_number Wiper number to select for communication.
    * Valid choices are 0 to 3.
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    */
   public void setCurrentWiperNumber (int wiper_number, byte[] state)
   {
      if (wiper_number != (wiper_number & 0x03))
         return;   //invalid, just skip it

      int wiper_inverse = ~wiper_number;

      wiper_number = wiper_number | ((wiper_inverse & 0x03) << 2);
      state [1]    = ( byte ) ((state [1] & 0xf0) | (wiper_number & 0x0f));
   }

   /**
    * Determines if the 2890's charge pump is enabled.
    *
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    * @return True if it is enabled, false if not.
    */
   public boolean isChargePumpOn (byte[] state)
   {
      return ((state [1] & 0x40) == 0x40);
   }

   /**
    * Set the 2890's charge pump.  This decreases the wiper's resistance,
    *  but increases the power consumption by the part.  Vdd must be
    * connected to use the charge pump (see the DS2890 datasheet for
    *  more information at www.dalsemi.com)
    *
    * @param charge_pump_on True if you want to enable the charge pump.
    * @param state State buffer of the Potentiometer One Wire Device (reaturned by readDevice()).
    * @return True if the operation was successful, false if there was an error.
    */
   public void setChargePump (boolean charge_pump_on, byte[] state)
   {
      state [1] = ( byte ) (state [1] & 0xbf);   //mask out the charge pump bit

      if (charge_pump_on)
         state [1] = ( byte ) (state [1] | 0x40);
   }

   /**
    * Get the current wiper position of the 2890.  The wiper position
    * is between 0 and 255, and describes the voltage output.  The
    * output lies between RH and RL.
    *
    * @return The wiper position between 0 and 255.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public int getWiperPosition ()
      throws OneWireIOException, OneWireException
   {
      return (readRegisters(READ_POSITION) & 0x0ff);
   }

   /**
    * Set the wiper position for the potentiometer.
    *
    * @param position The position to set the wiper.  This value will be cast
    *  to a byte, only the 8 least significant bits matter.
    * @return True if the operation was successful, false otherwise.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public boolean setWiperPosition (int position)
      throws OneWireIOException, OneWireException
   {
      return writeTransaction(WRITE_POSITION, ( byte ) position);
   }

   /**
    * Increments the wiper position.
    *
    * @param reselect Increment/Decrement can be called without resetting
    * the part if the last call was an Increment/Decrement.
    * True if you want to select the part (you must call
    * with true after any other one-wire method)
    * @return The new position of the wiper (0-255).
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public int increment (boolean reselect)
      throws OneWireIOException, OneWireException
   {
      return unitChange(INCREMENT, reselect);
   }

   /**
    * Decrements the wiper position.
    *
    * @param reselect Increment/Decrement can be called without resetting
    * the part if the last call was an Increment/Decrement.
    * True if you want to select the part (you must call
    * with true after any other one-wire method)
    * @return The new position of the wiper (0-255).
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public int decrement (boolean reselect)
      throws OneWireIOException, OneWireException
   {
      return unitChange(DECREMENT, reselect);
   }

   /**
    * Increments the wiper position after selecting the part.
    *
    * @return The new position of the wiper (0-255).
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public int increment ()
      throws OneWireIOException, OneWireException
   {
      return unitChange(INCREMENT, true);
   }

   /**
    * Decrements the wiper position after selecting the part.
    *
    * @return The new position of the wiper (0-255).
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public int decrement ()
      throws OneWireIOException, OneWireException
   {
      return unitChange(DECREMENT, true);
   }

   /**
    * 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
   {

      //format for the byte array is this:
      //byte 0: Feature register
      //  (msb) bit 7 : Potentiometer resistance msb
      //        bit 6 : Potentiometer resistance lsb
      //        bit 5 : Number of Wiper Positions msb
      //        bit 4 : Number of Wiper Positions lsb
      //        bit 3 : Number of Potentiometers msb
      //        bit 2 : Number of Potentiometers lsb
      //        bit 1 : Wiper Setting Volatility
      //  (lsb) bit 0 : Potentiometer Characteristic (lin/log)
      //byte 1: Control register
      //  (msb) bit 7 : Reserved
      //        bit 6 : Charge Pump Control
      //        bit 5 : Reserved
      //        bit 4 : Reserved
      //        bit 3 : Inverted Wiper Number msb
      //        bit 2 : Inverted Wiper Number lsb
      //        bit 1 : Wiper Number msb
      //  (lsb) bit 0 : Wiper Number lsb
      byte[] state = new byte [2];

      doSpeed();

      if (!adapter.select(address))
         throw new OneWireIOException("Could not select the part!");

      byte[] buf = new byte [3];

      buf [0] = READ_CONTROL;
      buf [1] = buf [2] = ( byte ) 0x0ff;

      adapter.dataBlock(buf, 0, 3);

      state [0] = buf [1];   //feature
      state [1] = buf [2];   //control

      return state;
   }

   /**
    * 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
   {

      //here we want to write the control register, just state[1]
      if (!writeTransaction(WRITE_CONTROL, state [1]))
         throw new OneWireIOException("Device may not have been present!");
   }

   //////////////////////////////////////////////////////////////////////
   //                          Private Methods                         //
   //////////////////////////////////////////////////////////////////////

   /*  This function handles reading of the registers for:
       1. Finding the state of the charge pump
       2. Finding the current location of the wiper

       Both of these operations send one command byte and receive two information bytes.
       The relevant information for both is stored in that second received byte.
   */
   private synchronized int readRegisters (byte COMMAND)
      throws OneWireIOException, OneWireException
   {
      doSpeed();

      if (!adapter.select(address))
         throw new OneWireIOException("Could not select the part!");

      buffer [0] = COMMAND;
      buffer [1] = buffer [2] = ( byte ) 0x0ff;

      adapter.dataBlock(buffer, 0, 3);

      return (0x0ff & buffer [2]);
   }

   /*  Handles the writing transactions, which are:
         1. Setting the control register (ie the charge pump state)
         2. Setting the wiper position

       Both of these operations have the same transaction process.  The command byte
       and a value parameter are passed in (either the new control register or the new
       position) and the part echo's the value parameter.  If the echo is correct (no
       transmission errors), the master sends a 96 (which means finish transaction).
       If the transaction succeeds, the part returns 0's, otherwise it returns 1's.

    */
   private synchronized boolean writeTransaction (byte COMMAND, byte value)
      throws OneWireIOException, OneWireException
   {
      doSpeed();

      if (adapter.select(address))
      {
         buffer [0] = COMMAND;
         buffer [1] = value;
         buffer [2] = ( byte ) 0x0ff;

         adapter.dataBlock(buffer, 0, 3);

         if (buffer [2] == value)
         {
            buffer [0] = ( byte ) 0x096;
            buffer [1] = ( byte ) 0x0ff;

            adapter.dataBlock(buffer, 0, 2);

            if (buffer [1] == 0)
               return true;
         }
      }

      return false;
   }

   /* This function handles the increment and decrement operations,
      including the contingent reset.  You do not need to call reset
      between consecutive unit change commands.  Both operations issue
      the command byte and then recieve the new wiper position.
   */
   private synchronized int unitChange (byte COMMAND, boolean reselect)
      throws OneWireIOException, OneWireException
   {
      if (reselect)
      {
         doSpeed();   //don't need to do this if we don't need to select
         adapter.select(address);
      }

      buffer [0] = COMMAND;
      buffer [1] = ( byte ) 0x0ff;

      adapter.dataBlock(buffer, 0, 2);

      return (0x0ff & buffer [1]);
   }
}
