All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pi4j.component.potentiometer.microchip.impl.MicrochipPotentiometerBase Maven / Gradle / Ivy

package com.pi4j.component.potentiometer.microchip.impl;

import com.pi4j.component.ComponentBase;
import com.pi4j.component.potentiometer.microchip.*;
import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CDevice;

import java.io.IOException;

/*
 * #%L
 * **********************************************************************
 * ORGANIZATION  :  Pi4J
 * PROJECT       :  Pi4J :: Device Abstractions
 * FILENAME      :  MicrochipPotentiometerBase.java  
 * 
 * This file is part of the Pi4J project. More information about 
 * this project can be found here:  http://www.pi4j.com/
 * **********************************************************************
 * %%
 * Copyright (C) 2012 - 2015 Pi4J
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

/**
 * Abstract Pi4J-device for MCP45XX and MCP46XX ICs.
 * 
 * @see com.pi4j.component.potentiometer.microchip.MCP4561
 * @see com.pi4j.component.potentiometer.microchip.MCP4651
 * @author Raspelikan
 */
public abstract class MicrochipPotentiometerBase
		extends ComponentBase implements MicrochipPotentiometer {
	
	/**
	 * An action which may be run for the volatile-wiper,
	 * the non-volatile-wiper or both.
	 * 
	 * @see MicrochipPotentiometerBase#doWiperAction(WiperAction)
	 */
	private static interface WiperAction {
		
		/**
		 * @param nonVolatile Whether to run for volatile- or non-volatile-wiper
		 * @throws IOException Thrown if communication fails or device returned a malformed result
		 */
		void run(final boolean nonVolatile) throws IOException;
		
	};
	
	/**
	 * The value which is used for address-bit if the device's package
	 * does not provide a matching address-pin (see 6.2.4)
	 */
	protected static final boolean PIN_NOT_AVAILABLE = true;
	
	/**
	 * The value which is used for devices capable of non-volatile wipers.
	 * For those devices the initial value is loaded from EEPROM. 
	 */
	protected static final int INITIALVALUE_LOADED_FROM_EEPROM = 0;
	
	/**
	 * The controller-instance
	 */
	private MicrochipPotentiometerDeviceController controller;
	
	/**
	 * The channel this instance is configured for
	 */
	private MicrochipPotentiometerChannel channel;
	
	/**
	 * Whether to use the volatile or the non-volatile wiper
	 */
	protected MicrochipPotentiometerNonVolatileMode nonVolatileMode;
	
	/**
	 * The wiper's current-value (volatile)
	 */
	private int currentValue;

	/**
	 * Builds an instance which is ready to use.
	 * 
	 * @param i2cBus The Pi4J-I2CBus to which the device is connected to
	 * @param pinA0 Whether the device's address pin A0 is high (true) or low (false)
	 * @param pinA1 Whether the device's address pin A1 (if available) is high (true) or low (false)
	 * @param pinA2 Whether the device's address pin A2 (if available) is high (true) or low (false)
	 * @param channel Which of the potentiometers provided by the device to control
	 * @param nonVolatileMode The way non-volatile reads or writes are done
	 * @param initialValueForVolatileWipers The value for devices which are not capable of non-volatile wipers
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
	protected MicrochipPotentiometerBase(
            final I2CBus i2cBus,
            final boolean pinA0,
            final boolean pinA1,
            final boolean pinA2,
            final MicrochipPotentiometerChannel channel,
            final MicrochipPotentiometerNonVolatileMode nonVolatileMode,
            final int initialValueForVolatileWipers)
			throws IOException {
		
		this(i2cBus, pinA0, pinA1, pinA2, channel,
				nonVolatileMode, initialValueForVolatileWipers,
				MicrochipPotentiometerDefaultDeviceControllerFactory.getInstance());
		
	}
	
	/**
	 * Builds an instance which is ready to use.
	 * 
	 * @param i2cBus The Pi4J-I2CBus to which the device is connected to
	 * @param pinA0 Whether the device's address pin A0 is high (true) or low (false)
	 * @param pinA1 Whether the device's address pin A1 (if available) is high (true) or low (false)
	 * @param pinA2 Whether the device's address pin A2 (if available) is high (true) or low (false)
	 * @param channel Which of the potentiometers provided by the device to control
	 * @param nonVolatileMode The way non-volatile reads or writes are done
	 * @param initialValueForVolatileWipers The value for devices which are not capable of non-volatile wipers
	 * @param controllerFactory builds new controllers
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 * @see MicrochipPotentiometerDefaultDeviceControllerFactory
	 */
	protected MicrochipPotentiometerBase(
            final I2CBus i2cBus,
            final boolean pinA0,
            final boolean pinA1,
            final boolean pinA2,
            final MicrochipPotentiometerChannel channel,
            final MicrochipPotentiometerNonVolatileMode nonVolatileMode,
            final int initialValueForVolatileWipers,
            final MicrochipPotentiometerDeviceControllerFactory controllerFactory)
			throws IOException {

		// input validation
		if (i2cBus == null) {
			throw new RuntimeException("Parameter 'i2cBus' must not be null!");
		}
		if (channel == null) {
			throw new RuntimeException(
					"For building a Microchip-potentiometer "
					+ "specifying a channel is mandatory! If the device "
					+ "knows more than one potentiometer/rheostat the "
					+ "channel defines which of them is controlled "
					+ "by this object-instance");
		}
		if (!isChannelSupportedByDevice(channel)) {
			throw new RuntimeException(
					"For building a Microchip-potentiometer "
					+ "only channels supported by the underlying device "
					+ "are valid parameters!");
		}
		if (controllerFactory == null) {
			throw new RuntimeException(
					"For building a Microchip-potentiometer "
					+ "providing a controllerFactory is mandatory! "
					+ "Use 'DefaultDeviceControllerFactory."
					+ "getInstance()'.");
		}
		if (nonVolatileMode == null) {
			throw new RuntimeException(
					"For building a Microchip-potentiometer "
					+ "providing a nonVolatileMode is mandatory!.");
		}
		
		// save channel to use
		this.channel = channel;
		
		// initial non-volatile-mode
		this.nonVolatileMode = nonVolatileMode;

		// build the address
		int i2cAddress = buildI2CAddress(pinA0, pinA1, pinA2);
		
		// build the underlying Pi4J-device
		final I2CDevice i2cDevice = i2cBus.getDevice(i2cAddress);
		
		// build the underlying controller-instance
		controller = controllerFactory.getController(i2cDevice);
		
		initialize(initialValueForVolatileWipers);
		
	}
	
	/**
	 * Initializes the wiper to a defined status. For devices capable of non-volatile-wipers
	 * the non-volatile-value is loaded. For devices not capable the given value is set
	 * in the device.
	 * 
	 * @param initialValueForVolatileWipers The initial value for devices not capable
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
	protected void initialize(final int initialValueForVolatileWipers) throws IOException {
		
		if (isCapableOfNonVolatileWiper()) {
			
			// the device's volatile-wiper will be set to the value stored
			// in the non-volatile memory. so for those devices the wiper's
			// current value has to be retrieved
			currentValue = controller.getValue(
                    DeviceControllerChannel.valueOf(channel), false);
			
		} else {
			
			// check boundaries
			final int newInitialValueForVolatileWipers
					= getValueAccordingBoundaries(initialValueForVolatileWipers);
			
			controller.setValue(DeviceControllerChannel.valueOf(channel),
					newInitialValueForVolatileWipers,
					MicrochipPotentiometerDeviceController.VOLATILE_WIPER);
			
			currentValue = newInitialValueForVolatileWipers;
			
		}
		
	}
	
	/**
	 * @return The channel this potentiometer is configured for
	 */
    @Override
	public MicrochipPotentiometerChannel getChannel() {
		return channel;
	}
	
	/**
	 * @return Whether device is capable of non volatile wipers
	 */
    @Override
	public abstract boolean isCapableOfNonVolatileWiper();
	
	/**
	 * @return All channels supported by the underlying device
	 */
    @Override
	public abstract MicrochipPotentiometerChannel[] getSupportedChannelsByDevice();
	
	/**
	 * @param pinA0 Whether the device's address pin A0 is high (true) or low (false)
	 * @param pinA1 Whether the device's address pin A1 (if available) is high (true) or low (false)
	 * @param pinA2 Whether the device's address pin A2 (if available) is high (true) or low (false)
	 * @return The I2C-address based on the address-pins given
	 */
	protected static int buildI2CAddress(
			final boolean pinA0,
			final boolean pinA1,
			final boolean pinA2) {
		
		// constant component
		int i2cAddress = 0b0101000;
		
		// dynamic component if device knows pin A0
		if (pinA0) {
			i2cAddress |= 0b0000001;
		}
		
		// dynamic component if device knows pin A1
		if (pinA1) {
			i2cAddress |= 0b0000010;
		}
		
		// dynamic component if device knows pin A2
		if (pinA2) {
			i2cAddress |= 0b0000100;
		}
		
		return i2cAddress;
		
	}
	
	/**
	 * @return The way non-volatile reads or writes are done
	 */
    @Override
	public MicrochipPotentiometerNonVolatileMode getNonVolatileMode() {
		
		return nonVolatileMode;
		
	}
	
	/**
	 * The visibility of this method is protected because not all
	 * devices support non-volatile wipers. Any derived class may
	 * publish this method.
	 * 
	 * @param nonVolatileMode The way non-volatile reads or writes are done
	 */
	protected void setNonVolatileMode(final MicrochipPotentiometerNonVolatileMode nonVolatileMode) {
		
		if (nonVolatileMode == null) {
			throw new RuntimeException("Setting a null-NonVolatileMode is not valid!");
		}
		
		if (!isCapableOfNonVolatileWiper()
				&& (nonVolatileMode != MicrochipPotentiometerNonVolatileMode.VOLATILE_ONLY)) {
			throw new RuntimeException("This device is not capable of non-volatile wipers."
					+ " Using another NonVolatileMode than '"
					+ MicrochipPotentiometerNonVolatileMode.VOLATILE_ONLY + "' is not valid!");
		}
		
		this.nonVolatileMode = nonVolatileMode;
		
	}

	/**
	 * Updates the cache to the wiper's value.
	 * 
	 * @return The wiper's current value
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
    @Override
	public int updateCacheFromDevice() throws IOException {
		
		currentValue = controller.getValue(DeviceControllerChannel.valueOf(channel), false);
		return currentValue;
		
	}
	
	/**
	 * The visibility of this method is protected because not all
	 * devices support non-volatile wipers. Any derived class may
	 * publish this method.
	 * 
	 * @return The non-volatile-wiper's value.
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
	protected int getNonVolatileValue() throws IOException {
		
		if (!isCapableOfNonVolatileWiper()) {
			throw new RuntimeException("This device is not capable of non-volatile wipers!");
		}
		
		return controller.getValue(DeviceControllerChannel.valueOf(channel), true);
		
	}
	
	/**
	 * The wiper's value read from cache. The cache is updated on any
	 * modifying action or the method 'updateCacheFromDevice'.
	 * 
	 * @return The wipers current value
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 * @see #updateCacheFromDevice()
	 */
	@Override
	public int getCurrentValue() throws IOException {
		
		return currentValue;
		
	}
	
	/**
	 * Adjusts the given value according to boundaries (0 and getMaxValue()).
	 * 
	 * @param value The wiper's value to be set
	 * 
	 * @return A valid wiper-value
	 */
	private int getValueAccordingBoundaries(final int value) {
		
		// check boundaries
		final int newValue;
		if (value < 0) {
			newValue = 0;
		}
		else if (value > getMaxValue()) {
			newValue = getMaxValue();
		}
		else {
			newValue = value;
		}
		return newValue;

	}
	
	/**
	 * @param value The wiper's value to be set
	 * 
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
	@Override
	public void setCurrentValue(final int value) throws IOException {
		
		// check boundaries
		final int newValue = getValueAccordingBoundaries(value);
		
		// set wipers according nonVolatileMode
		doWiperAction(new WiperAction() {
			
			@Override
			public void run(final boolean nonVolatile) throws IOException {
				
				controller.setValue(DeviceControllerChannel.valueOf(channel), newValue,
						nonVolatile);
				
			}
			
		});
		
		// set currentValue only if volatile-wiper is affected
		if (nonVolatileMode == MicrochipPotentiometerNonVolatileMode.NONVOLATILE_ONLY) {
			return;
		}
		
		// save current value
		currentValue = newValue;
		
	}
	
	/**
	 * Decreases the wiper's value for one step.
	 * 
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
	@Override
	public void decrease() throws IOException {
		
		decrease(1);
		
	}

	/**
	 * Decreases the wiper's value for n steps.
	 * 
	 * @param steps The number of steps to decrease
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
	@Override
	public void decrease(final int steps) throws IOException {
		
		if (currentValue == 0) {
			return;
		}
		if (steps < 0) {
			throw new RuntimeException("Only positive values for parameter 'steps' allowed!");
		}
		if (getNonVolatileMode() != MicrochipPotentiometerNonVolatileMode.VOLATILE_ONLY) {
			throw new RuntimeException("'decrease' is only valid for NonVolatileMode.VOLATILE_ONLY!");
		}

		// check boundaries
		final int actualSteps;
		if (steps > currentValue) {
			actualSteps = currentValue;
		} else {
			actualSteps = steps;
		}
		
		int newValue = currentValue - actualSteps;
		
		// if lower-boundary then set value in device to ensure sync
		// and for a large number of steps it is better to set a new value 
		if ((newValue == 0)
				|| (steps > 5)) {
			
			setCurrentValue(newValue);
			
		}
		// for a small number of steps use 'decrease'-method
		else {
			
			controller.decrease(DeviceControllerChannel.valueOf(channel), actualSteps);
			
			currentValue = newValue;
			
		}
		
	}
	
	/**
	 * Increases the wiper's value for one step.
	 * 
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
	@Override
	public void increase() throws IOException {
		
		increase(1);
		
	}
	
	/**
	 * Increases the wiper's value for n steps.
	 * 
	 * @param steps The number of steps to increase
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
	@Override
	public void increase(final int steps) throws IOException {
		
		int maxValue = getMaxValue();
		
		if (currentValue == maxValue) {
			return;
		}
		if (steps < 0) {
			throw new RuntimeException("only positive values for parameter 'steps' allowed!");
		}
		if (getNonVolatileMode() != MicrochipPotentiometerNonVolatileMode.VOLATILE_ONLY) {
			throw new RuntimeException("'increase' is only valid for NonVolatileMode.VOLATILE_ONLY!");
		}

		// check boundaries
		final int actualSteps;
		if ((steps + currentValue) > maxValue) {
			actualSteps = maxValue - currentValue;
		} else {
			actualSteps = steps;
		}
		
		int newValue = currentValue + actualSteps;
		
		// if upper-boundary then set value in device to ensure sync
		// and for a large number of steps it is better to set a new value
		if ((newValue == maxValue)
				|| (steps > 5)) {
			
			setCurrentValue(newValue);
			
		}
		// for a small number of step simply repeat 'increase'-commands
		else {
			
			controller.increase(DeviceControllerChannel.valueOf(channel), actualSteps);
			
			currentValue = newValue;
			
		}
		
	}
	
	/**
	 * @return The device- and wiper-status
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
    @Override
	public MicrochipPotentiometerDeviceStatus getDeviceStatus() throws IOException {
		
		DeviceControllerDeviceStatus deviceStatus
				= controller.getDeviceStatus();
		
		boolean wiperLockActive
				= channel == MicrochipPotentiometerChannel.A ?
						deviceStatus.isChannelALocked() : deviceStatus.isChannelBLocked();
		
		return new MicrochipPotentiometerDeviceStatusImpl(
				deviceStatus.isEepromWriteActive(),
				deviceStatus.isEepromWriteProtected(),
				channel,
				wiperLockActive);
		
	}
	
	/**
	 * @return The current terminal-configuration
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
    @Override
	public MicrochipPotentiometerTerminalConfiguration getTerminalConfiguration() throws IOException {
		
		DeviceControllerTerminalConfiguration tcon
				= controller.getTerminalConfiguration(DeviceControllerChannel.valueOf(channel));
		
		return new MicrochipPotentiometerTerminalConfiguration(channel,
				tcon.isChannelEnabled(), tcon.isPinAEnabled(),
				tcon.isPinWEnabled(), tcon.isPinBEnabled());
		
	}
	
	/**
	 * @param terminalConfiguration The new terminal-configuration
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
    @Override
	public void setTerminalConfiguration(
			final MicrochipPotentiometerTerminalConfiguration terminalConfiguration) throws IOException {
		
		if (terminalConfiguration == null) {
			throw new RuntimeException("Setting a null-terminalConfiguration is not valid!");
		}
		if (terminalConfiguration.getChannel() != channel) {
			throw new RuntimeException("Setting a terminalConfiguration with a channel "
					+ "other than the potentiometer's channel is not valid!");
		}
		
		DeviceControllerTerminalConfiguration tcon
				= new DeviceControllerTerminalConfiguration(
                DeviceControllerChannel.valueOf(channel),
				terminalConfiguration.isChannelEnabled(),
				terminalConfiguration.isPinAEnabled(),
				terminalConfiguration.isPinWEnabled(),
				terminalConfiguration.isPinBEnabled());
		
		controller.setTerminalConfiguration(tcon);
		
	}
	
	/**
	 * Enables or disables wiper-lock. See chapter 5.3.
	 * 
	 * @param enabled wiper-lock for the poti's channel has to be enabled
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
    @Override
	public void setWiperLock(final boolean enabled) throws IOException {
		
		controller.setWiperLock(DeviceControllerChannel.valueOf(channel), enabled);
		
	}
	
	/**
	 * Enables or disables write-protection for devices capable of non-volatile memory.
	 * Enabling write-protection does not only protect non-volatile wipers it also
	 * protects any other non-volatile information stored (f.e. wiper-locks).
	 * 
	 * @param enabled write-protection has to be enabled
	 * @throws IOException Thrown if communication fails or device returned a malformed result
	 */
    @Override
	public void setWriteProtection(final boolean enabled) throws IOException {
		
		controller.setWriteProtection(enabled);
		
	}
	
	/**
	 * Tests whether a given object equals to this object.
	 * 

* Especially to any class deriving PotentiometerImpl * this test does not take properties into account which * do not point to a specific device/channel. * * @param obj The other object * @return Whether the other object equals to this object. */ @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (!getClass().equals(obj.getClass())) { return false; } MicrochipPotentiometerBase other = (MicrochipPotentiometerBase) obj; if (channel != other.channel) { return false; } if (!controller.equals(other.controller)) { return false; } // nonVolatileMode and currentValue is not taken into account // because the do not point to a specific device/channel return true; } @Override public String toString() { final StringBuffer result = new StringBuffer(getClass().getName()); result.append("{\n"); result.append(" channel='").append(channel); result.append("',\n controller='").append(controller); result.append("',\n nonVolatileMode='").append(nonVolatileMode); result.append("',\n currentValue='").append(currentValue); result.append("'\n}"); return result.toString(); } /** * Runs a given 'wiperAction' for the volatile-wiper, the * non-volatile-wiper or both according the current nonVolatileMode. * * @param wiperAction The action to be run * @throws IOException Thrown if communication fails or device returned a malformed result * @see MicrochipPotentiometerBase#nonVolatileMode */ private void doWiperAction(final WiperAction wiperAction) throws IOException { // for volatile-wiper switch (nonVolatileMode) { case VOLATILE_ONLY: case VOLATILE_AND_NONVOLATILE: wiperAction.run(MicrochipPotentiometerDeviceController.VOLATILE_WIPER); break; case NONVOLATILE_ONLY: // do nothing } // for non-volatile-wiper switch (nonVolatileMode) { case NONVOLATILE_ONLY: case VOLATILE_AND_NONVOLATILE: wiperAction.run(MicrochipPotentiometerDeviceController.NONVOLATILE_WIPER); break; case VOLATILE_ONLY: // do nothing } } /** * @param channel A certain channel * @return Whether the given channel is supported by the underlying device */ @Override public boolean isChannelSupportedByDevice(final MicrochipPotentiometerChannel channel) { if (channel == null) { return false; } for (final MicrochipPotentiometerChannel supportedChannel : getSupportedChannelsByDevice()) { if (channel == supportedChannel) { return true; } } return false; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy