// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Drawing; using Iot.Device.PiJuiceDevice.Models; using UnitsNet; namespace Iot.Device.PiJuiceDevice { /// /// PiJuiceConfig class to support status of the PiJuice /// public class PiJuiceConfig { private readonly PiJuice _piJuice; private readonly List _usbMicroCurrentLimits = new List { ElectricCurrent.FromAmperes(1.5), ElectricCurrent.FromAmperes(2.5) }; private readonly List _usbMicroDpm = new List(); private List _batteryProfiles = new List(); /// /// PiJuiceConfig constructor /// /// The PiJuice class public PiJuiceConfig(PiJuice piJuice) { _piJuice = piJuice; for (int i = 0; i < 8; i++) { _usbMicroDpm.Add(ElectricPotential.FromVolts(4.2 + 0.8 * i)); } } /// /// Get battery profile /// /// Battery profile public BatteryProfile GetBatteryProfile() { byte[] response = _piJuice.ReadCommand(PiJuiceCommand.BatteryProfile, 14); return new BatteryProfile( ElectricCharge.FromMilliampereHours(BinaryPrimitives.ReadInt16LittleEndian(response)), ElectricCurrent.FromMilliamperes(response[2] * 75 + 550), ElectricCurrent.FromMilliamperes(response[3] * 50 + 50), ElectricPotential.FromMillivolts(response[4] * 20 + 3500), ElectricPotential.FromMillivolts(response[5] * 20), Temperature.FromDegreesCelsius(response[6]), Temperature.FromDegreesCelsius(response[7]), Temperature.FromDegreesCelsius(response[8]), Temperature.FromDegreesCelsius(response[9]), (response[11] << 8) | response[10], ElectricResistance.FromOhms(((response[13] << 8) | response[12]) * 10)); } /// /// Set a custom battery profile /// /// Custom battery profile public void SetCustomBatteryProfile(BatteryProfile batteryProfile) { var data = new byte[14]; data[0] = (byte)((int)(batteryProfile.Capacity.MilliampereHours) & 0xFF); data[1] = (byte)(((int)(batteryProfile.Capacity.MilliampereHours) >> 8) & 0xFF); data[2] = (byte)Math.Round((batteryProfile.ChargeCurrent.Milliamperes - 550) / 75, 0, MidpointRounding.AwayFromZero); data[3] = (byte)Math.Round((batteryProfile.TerminationCurrent.Milliamperes - 50) / 50, 0, MidpointRounding.AwayFromZero); data[4] = (byte)Math.Round((batteryProfile.RegulationVoltage.Millivolts - 3500) / 20, 0, MidpointRounding.AwayFromZero); data[5] = (byte)Math.Round(batteryProfile.CutOffVoltage.Millivolts / 20, 0, MidpointRounding.AwayFromZero); data[6] = (byte)batteryProfile.TemperatureCold.DegreesCelsius; data[7] = (byte)batteryProfile.TemperatureCool.DegreesCelsius; data[8] = (byte)batteryProfile.TemperatureWarm.DegreesCelsius; data[9] = (byte)batteryProfile.TemperatureHot.DegreesCelsius; data[10] = (byte)(batteryProfile.NegativeTemperatureCoefficientB & 0xFF); data[11] = (byte)((batteryProfile.NegativeTemperatureCoefficientB >> 8) & 0xFF); int ntcResistance = (int)batteryProfile.NegativeTemperatureCoefficientResistance.Ohms / 10; data[12] = (byte)(ntcResistance & 0xFF); data[13] = (byte)((ntcResistance >> 8) & 0xFF); _piJuice.WriteCommandVerify(PiJuiceCommand.BatteryProfile, data); } /// /// Get Battery extended profile /// /// Battery extended profile public BatteryExtendedProfile GetBatteryExtProfile() { byte[] response = _piJuice.ReadCommand(PiJuiceCommand.BatteryExtendedProfile, 17); BatteryChemistry chemistry; if (response[0] < 2) { chemistry = (BatteryChemistry)response[0]; } else { chemistry = BatteryChemistry.Unknown; } return new BatteryExtendedProfile( chemistry, ElectricPotential.FromMillivolts((response[2] << 8) | response[1]), ElectricPotential.FromMillivolts((response[4] << 8) | response[3]), ElectricPotential.FromMillivolts((response[6] << 8) | response[5]), ElectricResistance.FromMilliohms(((response[8] << 8) | response[7]) / 100.0), ElectricResistance.FromMilliohms(((response[10] << 8) | response[9]) / 100.0), ElectricResistance.FromMilliohms(((response[12] << 8) | response[11]) / 100.0)); } /// /// Set custom battery extended profile /// /// Custom battery extended profile public void SetCustomBatteryExtProfile(BatteryExtendedProfile batteryProfile) { var data = new byte[17]; data[0] = (byte)batteryProfile.BatteryChemistry; data[1] = (byte)((int)batteryProfile.OpenCircuitVoltage10Percent.Millivolts & 0xFF); data[2] = (byte)(((int)batteryProfile.OpenCircuitVoltage10Percent.Millivolts >> 8) & 0xFF); data[3] = (byte)((int)batteryProfile.OpenCircuitVoltage50Percent.Millivolts & 0xFF); data[4] = (byte)(((int)batteryProfile.OpenCircuitVoltage50Percent.Millivolts >> 8) & 0xFF); data[5] = (byte)((int)batteryProfile.OpenCircuitVoltage90Percent.Millivolts & 0xFF); data[6] = (byte)(((int)batteryProfile.OpenCircuitVoltage90Percent.Millivolts >> 8) & 0xFF); data[7] = (byte)((int)batteryProfile.InternalResistance10Percent.Milliohms & 0xFF); data[8] = (byte)(((int)batteryProfile.InternalResistance10Percent.Milliohms >> 8) & 0xFF); data[9] = (byte)((int)batteryProfile.InternalResistance50Percent.Milliohms & 0xFF); data[10] = (byte)(((int)batteryProfile.InternalResistance50Percent.Milliohms >> 8) & 0xFF); data[11] = (byte)((int)batteryProfile.InternalResistance90Percent.Milliohms & 0xFF); data[12] = (byte)(((int)batteryProfile.InternalResistance90Percent.Milliohms >> 8) & 0xFF); data[13] = 0xFF; data[14] = 0xFF; data[15] = 0xFF; data[16] = 0xFF; _piJuice.WriteCommandVerify(PiJuiceCommand.BatteryExtendedProfile, data); } /// /// Get battery profile status /// /// Battery profile status public BatteryProfileStatus GetBatteryProfileStatus() { byte[] response = _piJuice.ReadCommand(PiJuiceCommand.BatteryProfileStatus, 1); SelectBatteryProfiles(); int profileIndex = response[0] & 0x0F; string batteryProfile; if (profileIndex < _batteryProfiles.Count) { batteryProfile = _batteryProfiles[profileIndex]; } else { batteryProfile = "Unknown"; } return new BatteryProfileStatus( batteryProfile, (BatteryProfileSource)((response[0] >> 4) & 0x03), ((response[0] >> 6) & 0x01) == 0, (response[0] & 0x0F) == 0x0F ? BatteryOrigin.Custom : BatteryOrigin.Predefined); } /// /// Get how the battery temperature is taken /// /// Battery temperature sensor configuration public BatteryTemperatureSense GetBatteryTemperatureSenseConfig() { var response = _piJuice.ReadCommand(PiJuiceCommand.BatteryTemperatureSensorConfig, 1); if ((response[0] & 0x07) < 0 || (response[0] & 0x07) > 3) { throw new ArgumentOutOfRangeException("Battery temperature sensor configuration out of range"); } return (BatteryTemperatureSense)(response[0] & 0x07); } /// /// Set how the battery temperature is taken /// /// Determine how the battery temperature is taken public void SetBatteryTemperatureSenseConfig(BatteryTemperatureSense batteryTemperatureSense) { var response = _piJuice.ReadCommand(PiJuiceCommand.BatteryTemperatureSensorConfig, 1); _piJuice.WriteCommandVerify(PiJuiceCommand.BatteryTemperatureSensorConfig, new byte[] { (byte)((response[0] & (~0x07)) | (int)batteryTemperatureSense) }); } /// /// Get battery relative state-of-health estimation type /// /// Battery relative state-of-health estimation type public RelativeStateOfChangeEstimationType GetRelativeStateOfChangeEstimation() { var response = _piJuice.ReadCommand(PiJuiceCommand.BatteryTemperatureSensorConfig, 1); return (RelativeStateOfChangeEstimationType)((response[0] & 0x30) >> 4); } /// /// Set battery relative state-of-health estimation type /// /// Battery relative state-of-health estimation type public void SetRelativeStateOfChangeEstimation(RelativeStateOfChangeEstimationType estimationType) { var response = _piJuice.ReadCommand(PiJuiceCommand.BatteryTemperatureSensorConfig, 1); _piJuice.WriteCommandVerify(PiJuiceCommand.BatteryTemperatureSensorConfig, new byte[] { (byte)((response[0] & (~0x30)) | ((int)estimationType) << 4) }); } /// /// Get current battery charging state /// /// Battery charging configuration public ChargingConfig GetChargingConfig() { byte[] response = _piJuice.ReadCommand(PiJuiceCommand.ChargingConfig, 1); return new ChargingConfig( (response[0] & 0x01) == 0x01, (response[0] & 0x80) == 0x80); } /// /// Set current battery charging state /// /// Battery charging configuration public void SetChargingConfig(ChargingConfig config) { var data = new byte[1]; if (config.Enabled) { data[0] |= 0x01; } if (config.NonVolatile) { data[0] |= 0x80; } _piJuice.WriteCommandVerify(PiJuiceCommand.ChargingConfig, data); } /// /// Get power inputs /// /// Power inputs public PowerInput GetPowerInputs() { byte[] response = _piJuice.ReadCommand(PiJuiceCommand.PowerInputsConfig, 1); return new PowerInput( (PowerInputType)(response[0] & 0x01), (response[0] & 0x02) == 0x02, (response[0] & 0x04) == 0x04, _usbMicroCurrentLimits[(response[0] >> 3) & 0x01], _usbMicroDpm[(response[0] >> 4) & 0x07], (response[0] & 0x80) == 0x80); } /// /// Set power inputs /// /// Power inputs public void SetPowerInputs(PowerInput powerInput) { byte nonVolatile = powerInput.NonVolatile ? (byte)0x80 : (byte)0x00; byte precedence = powerInput.Precedence == PowerInputType.Gpio5Volt ? (byte)0x01 : (byte)0x00; byte gpioIn = powerInput.GpioIn ? (byte)0x02 : (byte)0x00; byte noBatteryTurnOn = powerInput.NoBatteryTurnOn ? (byte)0x04 : (byte)0x00; int index = _usbMicroCurrentLimits.IndexOf(powerInput.UsbMicroCurrentLimit); if (index == -1) { throw new ArgumentOutOfRangeException(nameof(powerInput.UsbMicroCurrentLimit)); } byte usbMicroLimit = (byte)(index << 3); index = _usbMicroDpm.IndexOf(powerInput.UsbMicroDynamicPowerManagement); if (index == -1) { throw new ArgumentOutOfRangeException(nameof(powerInput.UsbMicroDynamicPowerManagement)); } byte usbMicroDPM = (byte)((index & 0x07) << 3); var data = new byte[1]; data[0] = (byte)(nonVolatile | precedence | gpioIn | noBatteryTurnOn | usbMicroLimit | usbMicroDPM); _piJuice.WriteCommandVerify(PiJuiceCommand.PowerInputsConfig, data); } /// /// Get the configuration for the specific Led /// /// Led to get configuration for /// Led Configuration public LedConfig GetLedConfiguration(Led led) { var response = _piJuice.ReadCommand(PiJuiceCommand.LedConfig + (int)led, 4); if (response[0] < 0 || response[0] > 2) { throw new ArgumentOutOfRangeException("Led function type out of range"); } return new LedConfig( led, (LedFunction)response[0], Color.FromArgb(0, response[1], response[2], response[3])); } /// /// Set the configuration for the specific Led /// /// Led configuration public void SetLedConfiguration(LedConfig ledConfig) { _piJuice.WriteCommandVerify(PiJuiceCommand.LedConfig + (int)ledConfig.Led, new byte[] { (byte)ledConfig.LedFunction, ledConfig.Color.R, ledConfig.Color.G, ledConfig.Color.B }, 200); } /// /// Get power regulator mode /// /// Power regulator mode public PowerRegulatorMode GetPowerRegulatorMode() { var response = _piJuice.ReadCommand(PiJuiceCommand.PowerRegulatorConfig, 1); return (PowerRegulatorMode)response[0]; } /// /// Set power regulator mode /// /// Power regulator mode public void SetPowerRegulatorMode(PowerRegulatorMode powerMode) { _piJuice.WriteCommandVerify(PiJuiceCommand.PowerRegulatorConfig, new byte[] { (byte)powerMode }); } /// /// Get run pin configuration /// /// Run pin configuration public RunPin GetRunPinConfig() { var response = _piJuice.ReadCommand(PiJuiceCommand.RunPinConfig, 1); if (response[0] < 0 || response[0] > 1) { throw new ArgumentOutOfRangeException("Run pin out of range."); } return (RunPin)response[0]; } /// /// Set run pin configuration /// /// Run pin configuration public void SetRunPinConfig(RunPin runPin) { _piJuice.WriteCommandVerify(PiJuiceCommand.RunPinConfig, new byte[] { (byte)runPin }); } /// /// Selects which preset battery profiles to use based on the PiJuice firmware version /// private void SelectBatteryProfiles() { var firmware = _piJuice.GetFirmwareVersion(); var version = (firmware.Major << 4) + firmware.Minor; if (version >= 0x14) { _batteryProfiles = new List { "PJZERO_1000", "BP7X_1820", "SNN5843_2300", "PJLIPO_12000", "PJLIPO_5000", "PJBP7X_1600", "PJSNN5843_1300", "PJZERO_1200", "BP6X_1400", "PJLIPO_600", "PJLIPO_500" }; } else if (version == 0x13) { _batteryProfiles = new List { "BP6X_1400", "BP7X_1820", "SNN5843_2300", "PJLIPO_12000", "PJLIPO_5000", "PJBP7X_1600", "PJSNN5843_1300", "PJZERO_1200", "PJZERO_1000", "PJLIPO_600", "PJLIPO_500" }; } else { _batteryProfiles = new List { "BP6X", "BP7X", "SNN5843", "LIPO8047109" }; } } } }