
/*---------------------------------------------------------------------------
 * 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.*;
import com.dalsemi.onewire.adapter.*;
import com.dalsemi.onewire.container.*;
import java.io.*;
import com.dalsemi.onewire.adapter.OneWireIOException;


/**
 * iButton container for iButton family type 1F (hex), DS2409
 *
 * @version    0.00, 13 Sept 2000
 * @author     DSS
 */
public class OneWireContainer1F
   extends OneWireContainer
   implements SwitchContainer
{

   //--------
   //-------- Static Final Variables
   //--------

   /** Offset of BITMAP in array returned from read state */
   public static final int BITMAP_OFFSET = 3;

   /** Offset of Status in array returned from read state */
   public static final int STATUS_OFFSET = 0;

   /** Offset of Main channel flag in array returned from read state */
   public static final int MAIN_OFFSET = 1;

   /** Offset of Main channel flag in array returned from read state */
   public static final int AUX_OFFSET = 2;

   /** Main Channel number */
   public static final int CHANNEL_MAIN = 0;

   /** Aux Channel number */
   public static final int CHANNEL_AUX = 1;

   /** Channel flag to indicate turn off */
   public static final int SWITCH_OFF = 0;

   /** Channel flag to indicate turn on */
   public static final int SWITCH_ON = 1;

   /** Channel flag to indicate smart on  */
   public static final int SWITCH_SMART = 2;

   /** Read Write Status register commmand. */
   public static final byte READ_WRITE_STATUS_COMMAND = ( byte ) 0x5A;

   /** All lines off command. */
   public static final byte ALL_LINES_OFF_COMMAND = ( byte ) 0x66;

   /** Discharge command. */
   public static final byte DISCHARGE_COMMAND = ( byte ) 0x99;

   /** Direct on main command. */
   public static final byte DIRECT_ON_MAIN_COMMAND = ( byte ) 0xA5;

   /** Smart on main command. */
   public static final byte SMART_ON_MAIN_COMMAND = ( byte ) 0xCC;

   /** Smart on aux command. */
   public static final byte SMART_ON_AUX_COMMAND = ( byte ) 0x33;

   //--------
   //-------- Variables
   //--------
   private boolean clearActivityOnWrite;

   //--------
   //-------- Constructors
   //--------

   /**
    * Default constructor
    */
   public OneWireContainer1F ()
   {
      super();

      clearActivityOnWrite = false;
   }

   /**
    * 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 OneWireContainer1F (DSPortAdapter sourceAdapter, byte[] newAddress)
   {
      super(sourceAdapter, newAddress);

      clearActivityOnWrite = false;
   }

   /**
    * 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 OneWireContainer1F (DSPortAdapter sourceAdapter, long newAddress)
   {
      super(sourceAdapter, newAddress);

      clearActivityOnWrite = false;
   }

   /**
    * 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 OneWireContainer1F (DSPortAdapter sourceAdapter, String newAddress)
   {
      super(sourceAdapter, newAddress);

      clearActivityOnWrite = false;
   }

   //--------
   //-------- Methods
   //--------

   /** Retrieve the Dallas Semiconductor part number of the iButton
    *  as a string.  For example 'DS1992'.
    *
    *  @return string represetation of the iButton name.
    */
   public String getName ()
   {
      return "DS2409";
   }

   /** 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 string represetation of the alternate names.
    */
   public String getAlternateNames ()
   {
      return "Coupler";
   }

   /** Retrieve a short description of the function of the iButton type.
    *
    *  @return string represetation of the function description.
    */
   public String getDescription ()
   {
      return "1-Wire Network Coupler with dual addressable "
             + "switches and a general purpose open drain control "
             + "output.  Provides a common ground for all connected"
             + "multi-level MicroLan networks.  Keeps inactive branches"
             + "Pulled to 5V.";
   }

   //--------
   //-------- Sensor I/O methods
   //--------

   /**
    * 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.
    *
    * Format: BYTE[0] -> Status info byte,  BYTE[1] -> Main switch flag,
    *         BYTE[2] -> Aux switch flag,   BYTE[3] -> bitmap of changes
    *
    * @return <code>byte[]<\code> 1-Wire device sensor state    *
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public byte[] readDevice ()
      throws OneWireIOException, OneWireException
   {
      byte[] ret_buf = new byte [4];

      // read the status byte
      byte[] tmp_buf = deviceOperation(READ_WRITE_STATUS_COMMAND,
                                       ( byte ) 0x00FF, 2);

      // extract the status byte
      ret_buf [0] = tmp_buf [2];

      return ret_buf;
   }

   /**
    * 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
   {
      int    extra = 0;
      byte   command, first_byte;
      byte[] tmp_buf;

      // check for both switches set to on
      if ((Bit.arrayReadBit(MAIN_OFFSET, BITMAP_OFFSET, state) == 1)
              && (Bit.arrayReadBit(AUX_OFFSET, BITMAP_OFFSET, state) == 1))
      {
         if ((state [MAIN_OFFSET] != SWITCH_OFF)
                 && (state [AUX_OFFSET] != SWITCH_OFF))
            throw new OneWireException(
               "Attempting to set both channels on, only single channel on at a time");
      }

      // check if need to set control
      if (Bit.arrayReadBit(STATUS_OFFSET, BITMAP_OFFSET, state) == 1)
      {

         // create a command based on bit 6/7 of status
         first_byte = 0;

         // mode bit
         if (Bit.arrayReadBit(7, STATUS_OFFSET, state) == 1)
            first_byte |= ( byte ) 0x20;

         // Control output 
         if (Bit.arrayReadBit(6, STATUS_OFFSET, state) == 1)
            first_byte |= ( byte ) 0xC0;

         tmp_buf   = deviceOperation(READ_WRITE_STATUS_COMMAND, first_byte,
                                     2);
         state [0] = ( byte ) tmp_buf [2];
      }

      // check for AUX state change
      command = 0;

      if (Bit.arrayReadBit(AUX_OFFSET, BITMAP_OFFSET, state) == 1)
      {
         if ((state [AUX_OFFSET] == SWITCH_ON)
                 || (state [AUX_OFFSET] == SWITCH_SMART))
         {
            command = SMART_ON_AUX_COMMAND;
            extra   = 2;
         }
         else
         {
            command = ALL_LINES_OFF_COMMAND;
            extra   = 0;
         }
      }

      // check for MAIN state change
      if (Bit.arrayReadBit(MAIN_OFFSET, BITMAP_OFFSET, state) == 1)
      {
         if (state [MAIN_OFFSET] == SWITCH_ON)
         {
            command = DIRECT_ON_MAIN_COMMAND;
            extra   = 0;
         }
         else if (state [MAIN_OFFSET] == SWITCH_SMART)
         {
            command = SMART_ON_MAIN_COMMAND;
            extra   = 2;
         }
         else
         {
            command = ALL_LINES_OFF_COMMAND;
            extra   = 0;
         }
      }

      // check if there are events to clear and not about to do clear anyway
      if ((clearActivityOnWrite) && (command != ALL_LINES_OFF_COMMAND))
      {
         if ((Bit.arrayReadBit(4, STATUS_OFFSET, state) == 1)
                 || (Bit.arrayReadBit(5, STATUS_OFFSET, state) == 1))
         {

            // clear the events
            deviceOperation(ALL_LINES_OFF_COMMAND, ( byte ) 0xFF, 0);

            // set the channels back to the correct state
            if (command == 0)
            {
               if (Bit.arrayReadBit(0, STATUS_OFFSET, state) == 0)
                  command = SMART_ON_MAIN_COMMAND;
               else if (Bit.arrayReadBit(2, STATUS_OFFSET, state) == 0)
                  command = SMART_ON_AUX_COMMAND;

               extra = 2;
            }
         }
      }

      // check if there is a command to send
      if (command != 0)
         tmp_buf = deviceOperation(command, ( byte ) 0xFF, extra);

      // clear the bitmap
      state [BITMAP_OFFSET] = 0;
   }

   /**
    * This method is used to force a power-on reset for parasitically powered 1-Wire
    * devices connected to the main or auziliary output of the DS2409.
    *
    * IMPORTANT: the duration of the discharge time should be 100ms minimum.
    *
    *
    * @param time - <code>int</code> This is the number of milliseconds the lines are
    *      to be discharged for, must be >= 100 ms.
    *
    * @throws OneWireIOException
    * @throws OneWireException
    */
   public void dischargeLines (int time)
      throws OneWireIOException, OneWireException
   {

      // Error checking
      if (time < 100)
         time = 100;

      // discharge the lines
      deviceOperation(DISCHARGE_COMMAND, ( byte ) 0xFF, 0);

      // wait for desired time and return.
      try
      {
         Thread.sleep(time);
      }
      catch (InterruptedException e)
      {

         // DRAIN
      }

      // clear the discharge
      deviceOperation(READ_WRITE_STATUS_COMMAND, ( byte ) 0x00FF, 2);
   }

   //--------
   //-------- Switch Feature methods
   //--------

   /**
    * Query to see if the channels of this switch are 'high side'
    * switches.  This indicates that when 'on' or TRUE, the switch output is
    * connect to the 1-Wire data.  If this method returns  FALSE
    * then when the switch is 'on' or TRUE, the switch is connected
    * to ground.
    *
    * @return boolean, true if switch is a 'high side' switch.  false
    *                  if switch is a 'low side' switch.
    */
   public boolean isHighSideSwitch ()
   {
      return true;
   }

   /**
    * Query to see if the channels of this switch support
    * activity sensing.  If this method returns true then the
    * method 'getActivity()' can be used.
    *
    * @return boolean, true if channels support activity sensing
    */
   public boolean hasActivitySensing ()
   {
      return true;
   }

   /**
    * Query to see if the channels of this switch support
    * level sensing.  If this method returns true then the
    * method 'getLevel()' can be used.
    *
    * @return boolean, true if channels support level sensing
    */
   public boolean hasLevelSensing ()
   {
      return true;
   }

   /**
    * Query to see if the channels of this switch support
    * smart on. Smart on is the ability to turn on a channel
    * such that only 1-Wire device on this channel are awake
    * and ready to do an operation.  This greatly reduces the
    * the time to discover the device down a branch.
    * If this method returns true then the
    * method 'setSwitch()' can be used with the 'doSmart' parameter
    * true.
    *
    * @return boolean, true if channels support smart on
    */
   public boolean hasSmartOn ()
   {
      return true;
   }

   /**
    * Query to see if the channels of this switch require that only
    * channel is on at any one time.  If this method returns true then the
    * method 'setSwitch(channel)' will effect the state of the given
    * channel but may effect the state of the other channels as well
    * to insure that only one channel is on at a time.
    *
    * @return boolean, true if only 1 channel can be on at a time.
    */
   public boolean onlySingleChannelOn ()
   {
      return true;
   }

   //--------
   //-------- Switch 'get' Methods
   //--------

   /**
    * Query to get the number of channels supported by this switch.
    * Channel specific methods will use a channel number specified
    * by an integer from [0 to (getNumberChannels() - 1)].
    *
    * @param  state - byte array of device state
    *
    * @return int containing the number of channels
    */
   public int getNumberChannels (byte[] state)
   {
      return 2;
   }

   /**
    * This method checks the sensed level on the indicated channel
    * from the provided state data retrieved from the
    * 'readDevice()' method.  Note, to avoid an exception, verify
    * the features of this switch with the method 'hasLevelSensing()'.
    *
    *
    * @param channel
    * @param  state - byte array of device state
    *
    * @return <code>boolean<\code> true if level sensed is 'high'
    * and false if level sensed is 'low'.
    */
   public boolean getLevel (int channel, byte[] state)
      throws OneWireException
   {
      return (Bit.arrayReadBit(1 + channel * 2, STATUS_OFFSET, state) == 1);
   }

   /**
    * This method checks the latch state of the indicated channel
    * from the provided state data retrieved from the
    * 'readDevice()' method.
    *
    *
    * @param channel
    * @param  state - byte array of device state
    *
    * @return <code>boolean<\code> true if channel latch is 'on'
    * or conducting and false if channel latch is 'off' and not
    * conducting.  Note that the actual output then the latch is 'on'
    * is returned from the 'isHighSideSwitch()' method.
    */
   public boolean getLatchState (int channel, byte[] state)
   {
      return (Bit.arrayReadBit(channel * 2, STATUS_OFFSET, state) == 0);
   }

   /**
    * This method checks if the indicated channel had activity
    * from the provided state data retrieved from the
    * 'readDevice()' method.   Note, to avoid an exception, verify
    * the features of this switch with the method 'hasActivitySensing()'.
    *
    *
    * @param channel
    * @param  state - byte array of device state
    *
    * @return <code>boolean<\code> true if activity was detected
    * and false if no activity was detected.
    *
    * @throws OneWireException
    */
   public boolean getSensedActivity (int channel, byte[] state)
      throws OneWireException
   {
      return (Bit.arrayReadBit(4 + channel, STATUS_OFFSET, state) == 1);
   }

   //--------
   //-------- DS2409 Specific Switch 'get' Methods
   //--------

   /**
    * This method checks if the control mode
    * from the provided state data retrieved from the
    * 'readDevice()' method.
    *
    * @param  state - byte array of device state
    *
    * @return <code>boolean<\code> true if control mode is automatic
    * (see DS2409 data sheet).
    */
   public boolean isModeAuto (byte[] state)
   {
      return (Bit.arrayReadBit(7, STATUS_OFFSET, state) == 0);
   }

   /**
    * This method checks the channel association of the control pin
    * from the provided state data retrieved from the
    * 'readDevice()' method.  This value only makes sense if
    * the control mode is automatic (see isModeAuto()).
    *
    * @param  state - byte array of device state
    *
    * @return <code>int<\code> the channel number that is associated
    * with the control pin (see DS2409 data sheet).
    */
   public int getControlChannelAssociation (byte[] state)
   {
      return Bit.arrayReadBit(6, STATUS_OFFSET, state);
   }

   /**
    * This method checks the control data value
    * from the provided state data retrieved from the
    * 'readDevice()' method.  This value only makes sense if
    * the control mode is manual (see !isModeAuto()).
    *
    * @param  state - byte array of device state
    *
    * @return <code>int<\code> the channel number that is associated
    * with the control pin (see DS2409 data sheet).
    */
   public int getControlData (byte[] state)
   {
      return Bit.arrayReadBit(6, STATUS_OFFSET, state);
   }

   //--------
   //-------- Switch 'set' Methods
   //--------

   /**
    * This method sets the latch state of the indicated channel in the
    * state data.  Use the method 'writeDevice()' with
    * this data to finalize the change to the device.
    *
    * @param channel - integer indicated channel to do operation on
    *                  in the range [0 to (getNumberChannels() - 1)]
    * @param latchState
    * @param doSmart - if latchState is 'on'/true then doSmart indicates
    *                  if a 'smart on' is to be done.  To avoid an exception
    *                  check the cababilities of the device using the
    *                  'hasSmartOn()' method.
    * @param state - byte array of device state
    */
   public void setLatchState (int channel, boolean latchState,
                              boolean doSmart, byte[] state)
   {

      // set the state flag
      if (latchState)
         state [channel + 1] = ( byte ) ((doSmart) ? SWITCH_SMART
                                                   : SWITCH_ON);
      else
         state [channel + 1] = ( byte ) SWITCH_OFF;

      // indicate in bitmap the the state has changed
      Bit.arrayWriteBit(1, channel + 1, BITMAP_OFFSET, state);
   }

   /**
    * Clears the activity latches the next time possible.  For
    * example, on a DS2406/07, this happens the next time the
    * status is read.  On the DS2409 this will happen on the next
    * writeDevice().
    */
   public void clearActivity ()
      throws OneWireException
   {
      clearActivityOnWrite = true;
   }

   /**
    * This method sets the control pin mode to the indicated value in the
    * state data.  Use the method 'writeDevice()' with
    * this data to finalize the change to the device.
    *
    * @param makeAuto - true to set to auto mode, false for manual mode
    * @param state - byte array of device state
    */
   public void setModeAuto (boolean makeAuto, byte[] state)
   {

      // set the bit
      Bit.arrayWriteBit((makeAuto ? 0
                                  : 1), 7, STATUS_OFFSET, state);

      // indicate in bitmap the the state has changed
      Bit.arrayWriteBit(1, STATUS_OFFSET, BITMAP_OFFSET, state);
   }

   /**
    * This method sets the control pin mode to the indicated value in the
    * state data.  Use the method 'writeDevice()' with
    * this data to finalize the change to the device.
    *
    * @param channel - integer indicated channel to do operation on
    *                  in the range [0 to (getNumberChannels() - 1)]
    * @param state - byte array of device state
    *
    * @throws OneWireException
    */
   public void setControlChannelAssociation (int channel, byte[] state)
      throws OneWireException
   {

      // check for invalid mode
      if (!isModeAuto(state))
         throw new OneWireException(
            "Trying to set channel association in manual mode");

      // set the bit
      Bit.arrayWriteBit(channel, 6, STATUS_OFFSET, state);

      // indicate in bitmap the the state has changed
      Bit.arrayWriteBit(1, STATUS_OFFSET, BITMAP_OFFSET, state);
   }

   /**
    * This method sets the control pin data to the indicated value in the
    * state data.  Use the method 'writeDevice()' with
    * this data to finalize the change to the device.  Note this
    * method works if the control pin is in manual mode.
    *
    * @param data - boolean indicated the contol data state, true for
    *              on and false for off
    * @param state - byte array of device state
    *
    * @throws OneWireException
    */
   public void setControlData (boolean data, byte[] state)
      throws OneWireException
   {

      // check for invalid mode
      if (!isModeAuto(state))
         throw new OneWireException(
            "Trying to set control data when control is in automatic mode");

      // set the bit
      Bit.arrayWriteBit((data ? 1
                              : 0), 6, STATUS_OFFSET, state);

      // indicate in bitmap the the state has changed
      Bit.arrayWriteBit(1, STATUS_OFFSET, BITMAP_OFFSET, state);
   }

   //--------
   //-------- Private methods
   //--------

   /**
    *
    * @throws OneWireIOException
    * @throws OneWireException
    *
    */
   private byte[] deviceOperation (byte command, byte sendByte, int extra)
      throws OneWireIOException, OneWireException
   {

      // Variables.
      byte[] raw_buf = new byte [extra + 2];

      // build block.
      raw_buf [0] = ( byte ) command;
      raw_buf [1] = ( byte ) sendByte;

      for (int i = 2; i < raw_buf.length; i++)
         raw_buf [i] = ( byte ) 0xFF;

      // Select the device.
      if (adapter.select(address))
      {

         // send the block
         adapter.dataBlock(raw_buf, 0, raw_buf.length);

         /*???????????????
         //?????????????????????
         System.out.println();
         for (int j = 0; j < raw_buf.length; j++)
         {
            if ((raw_buf[j] & 0x000000FF) < 0x00000010)
            {
               System.out.print("0");
               System.out.print(Integer.toHexString((int)raw_buf[j] & 0x0000000F).toUpperCase());
            }
            else
               System.out.print(Integer.toHexString((int)raw_buf[j] & 0x000000FF).toUpperCase());
         }
         //?????????????????????
         ???????????*/

         // verify 
         if (command == READ_WRITE_STATUS_COMMAND)
         {
            if (( byte ) raw_buf [raw_buf.length - 1]
                    != ( byte ) raw_buf [raw_buf.length - 2])
               throw new OneWireIOException(
                  "OneWireContainer1F verify on command incorrect");
         }
         else
         {
            if (( byte ) raw_buf [raw_buf.length - 1] != ( byte ) command)
               throw new OneWireIOException(
                  "OneWireContainer1F verify on command incorrect");
         }

         return raw_buf;
      }
      else
         throw new OneWireIOException(
            "OneWireContainer1F failure - Device not found.");
   }
}
