diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.DigitalOutputPort.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.DigitalOutputPort.cs index 357941cede..0b24af6930 100644 --- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.DigitalOutputPort.cs +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.DigitalOutputPort.cs @@ -45,4 +45,4 @@ public DigitalOutputPort( } } } -} +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.Enums.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.Enums.cs index 9a68acf6ea..5b3e46d0ce 100644 --- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.Enums.cs +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.Enums.cs @@ -169,7 +169,7 @@ public enum Adc2Gain : byte Gain_32 = 0x5, /// 64 V/V (resulting in ±39 mV range assuming Internal reference) Gain_64 = 0x6, - /// 128 V/V (resulting in ±19.5 mV range assuming Internal reference) + /// 128 V/V (resulting in ±19.5 mV range assuming Internal reference) Gain_128 = 0x7, } @@ -267,4 +267,4 @@ public enum Register : byte ADC2OSC1 = 0x1A, } } -} +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.PinDefinitions.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.PinDefinitions.cs index 6999cccd58..ef27d648e5 100644 --- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.PinDefinitions.cs +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.PinDefinitions.cs @@ -1,6 +1,6 @@ -using System.Collections; +using Meadow.Hardware; +using System.Collections; using System.Collections.Generic; -using Meadow.Hardware; namespace Meadow.Foundation.ICs.IOExpanders { @@ -37,35 +37,35 @@ public PinDefinitions(Ads1263 controller) new List { new AnalogChannelInfo(nameof(ADC2), 24, true, false), }); /// Pin representing GPIO0 (AIN3) - public IPin GPIO0 => new Pin(Controller, nameof(GPIO0), (byte)0x00, + public IPin GPIO0 => new Pin(Controller, nameof(GPIO0), (byte)0x00, new List { new DigitalChannelInfo(nameof(GPIO0), true, true, false, false, false) }); /// Pin representing GPIO1 (AIN4) - public IPin GPIO1 => new Pin(Controller, nameof(GPIO1), (byte)0x01, + public IPin GPIO1 => new Pin(Controller, nameof(GPIO1), (byte)0x01, new List { new DigitalChannelInfo(nameof(GPIO1), true, true, false, false, false) }); /// Pin representing GPIO2 (AIN5) - public IPin GPIO2 => new Pin(Controller, nameof(GPIO2), (byte)0x02, + public IPin GPIO2 => new Pin(Controller, nameof(GPIO2), (byte)0x02, new List { new DigitalChannelInfo(nameof(GPIO2), true, true, false, false, false) }); - + /// Pin representing GPIO3 (AIN6) - public IPin GPIO3 => new Pin(Controller, nameof(GPIO3), (byte)0x03, + public IPin GPIO3 => new Pin(Controller, nameof(GPIO3), (byte)0x03, new List { new DigitalChannelInfo(nameof(GPIO3), true, true, false, false, false) }); - + /// Pin representing GPIO4 (AIN7) - public IPin GPIO4 => new Pin(Controller, nameof(GPIO4), (byte)0x04, + public IPin GPIO4 => new Pin(Controller, nameof(GPIO4), (byte)0x04, new List { new DigitalChannelInfo(nameof(GPIO4), true, true, false, false, false) }); - + /// Pin representing GPIO5 (AIN8) - public IPin GPIO5 => new Pin(Controller, nameof(GPIO5), (byte)0x05, + public IPin GPIO5 => new Pin(Controller, nameof(GPIO5), (byte)0x05, new List { new DigitalChannelInfo(nameof(GPIO5), true, true, false, false, false) }); - + /// Pin representing GPIO6 (AIN9) - public IPin GPIO6 => new Pin(Controller, nameof(GPIO6), (byte)0x06, + public IPin GPIO6 => new Pin(Controller, nameof(GPIO6), (byte)0x06, new List { new DigitalChannelInfo(nameof(GPIO6), true, true, false, false, false) }); - + /// Pin representing GPIO7 (AINCOM) - public IPin GPIO7 => new Pin(Controller, nameof(GPIO7), (byte)0x07, + public IPin GPIO7 => new Pin(Controller, nameof(GPIO7), (byte)0x07, new List { new DigitalChannelInfo(nameof(GPIO7), true, true, false, false, false) }); /// @@ -86,4 +86,4 @@ protected void InitAllPins() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } -} +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.cs index 758c62a6c2..ec42613bac 100644 --- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.cs +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Ads1263.cs @@ -1,8 +1,8 @@ using Meadow.Hardware; using Meadow.Units; -using System.Linq; -using System; using Meadow.Utilities; +using System; +using System.Linq; namespace Meadow.Foundation.ICs.IOExpanders; @@ -20,7 +20,6 @@ public partial class Ads1263 : IAnalogInputController, IDigitalInputOutputContro private byte ioEnable, ioDir, ioOut, gain1, gain2; private double vRef1, vRef2; - #region SPI /// /// Gets the underlying ISpiCommunications instance /// @@ -40,9 +39,8 @@ public partial class Ads1263 : IAnalogInputController, IDigitalInputOutputContro /// Did we create the port(s) used by the peripheral private readonly bool createdPort = false; - + private readonly IDigitalOutputPort chipSelectPort; - #endregion /// /// object for using lock() while modifying GPIO outputs @@ -89,8 +87,6 @@ internal Voltage GetADCReferenceVoltage(IPin pin) var vRef = (byte)pin.Key switch { 0x00 => vRef1, _ => vRef2 }; return new Voltage(vRef, Voltage.UnitType.Volts); } - - #region Configuration private void Initialize() { // read Device ID to confirm communications @@ -127,19 +123,20 @@ public void ConfigureADC1(AdcSource positiveSource = AdcSource.AIN0, AdcSource n double referenceVoltage = 5.0, Adc1ReferenceP positiveReference = Adc1ReferenceP.Internal, Adc1ReferenceN negativeReference = Adc1ReferenceN.Internal) { // Verify combinations of filter and data rate settings - if (filter == Adc1Filter.FIR && ((byte)rate > (byte)Adc1DataRate.SPS_20 || rate == Adc1DataRate.SPS_16p6)) + if (filter == Adc1Filter.FIR && ((byte)rate > (byte)Adc1DataRate.SPS_20 || rate == Adc1DataRate.SPS_16p6)) throw new ArgumentException("FIR filter can only be used with 2.5, 5, 10, or 20 samples per second"); // Note: The three fastest data rates bypass the second filter stage, so filter may be ignored. WriteRegister(Register.MODE1, (byte)filter << 5); WriteRegister(Register.MODE2, (byte)gain << 4 | (byte)rate); WriteRegister(Register.INPMUX, (byte)positiveSource << 4 | (byte)negativeSource); - WriteRegister(Register.REFMUX, (byte)positiveReference << 3 | (byte) negativeReference); + WriteRegister(Register.REFMUX, (byte)positiveReference << 3 | (byte)negativeReference); gain1 = (byte)(1 << (byte)gain); - vRef1 = (positiveReference, negativeReference) switch { + vRef1 = (positiveReference, negativeReference) switch + { (Adc1ReferenceP.Internal, Adc1ReferenceN.Internal) => 2.5, - (_,_) => referenceVoltage + (_, _) => referenceVoltage }; } @@ -154,7 +151,7 @@ public void ConfigureADC1(AdcSource positiveSource = AdcSource.AIN0, AdcSource n /// Enumeration specifying the positive and negative reference source public void ConfigureADC2( AdcSource positiveSource = AdcSource.AIN0, AdcSource negativeSource = AdcSource.AIN1, - Adc2Gain gain = Adc2Gain.Gain_1, Adc2DataRate rate = Adc2DataRate.SPS_10, + Adc2Gain gain = Adc2Gain.Gain_1, Adc2DataRate rate = Adc2DataRate.SPS_10, double referenceVoltage = 5.0, Adc2Reference reference = Adc2Reference.Internal) { WriteRegister(Register.ADC2CFG, (byte)rate << 6 | (byte)reference << 3 | (byte)gain); @@ -192,9 +189,6 @@ public void WriteRegister(Register register, int data) SpiComms.Write(command); } - #endregion - - #region AnalogInputPort /// public IAnalogInputPort CreateAnalogInputPort(IPin pin, int sampleCount, TimeSpan sampleInterval, Voltage referenceVoltage) { @@ -251,7 +245,7 @@ private Voltage ReadAnalog(IPin pin) var gain = key switch { 0x00 => gain1, _ => gain2 }; // TODO: verify conversion/scaling - var result = (vRef / gain) * (rawValue / (double)0x7FFFFFFF); + var result = (vRef / gain) * (rawValue / (double)0x7FFFFFFF); return new Voltage(result, Voltage.UnitType.Volts); } @@ -264,9 +258,6 @@ public static Temperature ConvertTempSensor(Voltage tempSensorVoltage) return new Temperature(((tempSensorVoltage.Microvolts - 122400) / 420) + 25, Temperature.UnitType.Celsius); } - #endregion - - #region DigitalInputPort /// /// Creates a new DigitalInputPort using the specified GPIO pin /// @@ -294,9 +285,7 @@ private bool ReadPort(IPin pin) var ioInputs = ReadRegister(Register.GPIODAT); return BitHelpers.GetBitValue(ioInputs, (byte)pin.Key); } - #endregion - #region DigitalOutputPort /// /// Creates a new DigitalOutputPort using the specified pin and initial state /// @@ -324,9 +313,7 @@ public IDigitalOutputPort CreateDigitalOutputPort(IPin pin, bool initialState = return port; } - #endregion - #region Digital /// /// Sets the GPIO configuration of a port using pre-cached information. This overload /// assumes the pin has been pre-verified as valid. @@ -382,9 +369,7 @@ private void PreValidatedWriteToPort(IPin pin, bool value) WriteRegister(Register.GPIODAT, ioOut); } } - #endregion - #region IDisposable /// /// Is the object disposed /// @@ -413,5 +398,4 @@ protected virtual void Dispose(bool disposing) IsDisposed = true; } } - #endregion } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Readme.md b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Readme.md index 969aa83403..31669ff5c2 100644 --- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Readme.md +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Driver/Readme.md @@ -1,8 +1,8 @@ -# Meadow.Foundation.ICs.ADC.Ads1263 +# Meadow.Foundation.ICs.IOExpanders.Ads1263 -**TI ADS1263 SPI analog to digital converter** +**TI ADS1263 SPI analog to digital converter, IO expander** -The **Ads1263** library is included in the **Meadow.Foundation.ICs.ADC.Ads1263** nuget package and is designed for the [Wilderness Labs](www.wildernesslabs.co) Meadow .NET IoT platform. +The **Ads1263** library is included in the **Meadow.Foundation.ICs.IOExpanders.Ads1263** nuget package and is designed for the [Wilderness Labs](www.wildernesslabs.co) Meadow .NET IoT platform. This driver is part of the [Meadow.Foundation](https://developer.wildernesslabs.co/Meadow/Meadow.Foundation/) peripherals library, an open-source repository of drivers and libraries that streamline and simplify adding hardware to your C# .NET Meadow IoT applications. @@ -14,7 +14,69 @@ To view all Wilderness Labs open-source projects, including samples, visit [gith You can install the library from within Visual studio using the the NuGet Package Manager or from the command line using the .NET CLI: -`dotnet add package Meadow.Foundation.ICs.ADC.Ads1263` +`dotnet add package Meadow.Foundation.ICs.IOExpanders.Ads1263` +## Usage + +```csharp +private Ads1263 ads1263; +private Ads1263.AnalogInputPort adc1, adc2; +private Ads1263.DigitalInputPort gpio0, gpio1; +private Ads1263.DigitalOutputPort gpio6, gpio7; + +public override Task Initialize() +{ + Resolver.Log.Info("Initialize..."); + + var spiBus = Device.CreateSpiBus(); + ads1263 = new Ads1263(spiBus, Device.Pins.A05); + + // Setup ADC1 on the default inputs + ads1263.ConfigureADC1(positiveSource: Ads1263.AdcSource.AIN0, negativeSource: Ads1263.AdcSource.AIN1); + adc1 = (Ads1263.AnalogInputPort)ads1263.CreateAnalogInputPort(ads1263.Pins.ADC1, 1, TimeSpan.Zero, 2.5.Volts()); + adc1.StartConversions(); + adc1.Updated += Adc1_Updated; + adc1.StartUpdating(TimeSpan.FromSeconds(5)); + + // Setup ADC2 to read the internal temperature + ads1263.ConfigureADC2(positiveSource: Ads1263.AdcSource.TempSensor, negativeSource: Ads1263.AdcSource.TempSensor); + adc2 = (Ads1263.AnalogInputPort)ads1263.CreateAnalogInputPort(ads1263.Pins.ADC2, 1, TimeSpan.Zero, 2.5.Volts()); + adc2.StartConversions(); + adc2.Updated += Adc2_Updated; + adc2.StartUpdating(TimeSpan.FromSeconds(5)); + + // Setup digital inputs and outputs + gpio0 = (Ads1263.DigitalInputPort)ads1263.CreateDigitalInputPort(ads1263.Pins.GPIO0); + gpio1 = (Ads1263.DigitalInputPort)ads1263.CreateDigitalInputPort(ads1263.Pins.GPIO1); + gpio6 = (Ads1263.DigitalOutputPort)ads1263.CreateDigitalOutputPort(ads1263.Pins.GPIO6); + gpio7 = (Ads1263.DigitalOutputPort)ads1263.CreateDigitalOutputPort(ads1263.Pins.GPIO7); + + return base.Initialize(); +} + +private void Adc1_Updated(object sender, IChangeResult e) +{ + Resolver.Log.Info($"ADC1 {e.New.Volts:N6} V"); +} + +private void Adc2_Updated(object sender, IChangeResult e) +{ + Resolver.Log.Info($"ADC2 {e.New.Volts:N6} V = {(Ads1263.ConvertTempSensor(e.New)):N2} °C"); +} + +public override Task Run() +{ + Resolver.Log.Info("Run..."); + + while (true) + { + gpio7.State = gpio0.State; + gpio6.State = !gpio1.State; + + Thread.Sleep(TimeSpan.FromSeconds(1)); + } +} + +``` ## How to Contribute - **Found a bug?** [Report an issue](https://github.com/WildernessLabs/Meadow_Issues/issues) diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Samples/Ads1263_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Samples/Ads1263_Sample/MeadowApp.cs index fe58d92a6e..271f895296 100644 --- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Samples/Ads1263_Sample/MeadowApp.cs +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Ads1263/Samples/Ads1263_Sample/MeadowApp.cs @@ -1,15 +1,17 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Meadow; +using Meadow; using Meadow.Devices; using Meadow.Foundation.ICs.IOExpanders; using Meadow.Units; +using System; +using System.Threading; +using System.Threading.Tasks; namespace Ads1263_Sample { public class MeadowApp : App { + // + private Ads1263 ads1263; private Ads1263.AnalogInputPort adc1, adc2; private Ads1263.DigitalInputPort gpio0, gpio1; @@ -24,7 +26,7 @@ public override Task Initialize() // Setup ADC1 on the default inputs ads1263.ConfigureADC1(positiveSource: Ads1263.AdcSource.AIN0, negativeSource: Ads1263.AdcSource.AIN1); - adc1 = (Ads1263.AnalogInputPort)ads1263.CreateAnalogInputPort(ads1263.Pins.ADC1, 1, TimeSpan.Zero, 2.5.Volts() ); + adc1 = (Ads1263.AnalogInputPort)ads1263.CreateAnalogInputPort(ads1263.Pins.ADC1, 1, TimeSpan.Zero, 2.5.Volts()); adc1.StartConversions(); adc1.Updated += Adc1_Updated; adc1.StartUpdating(TimeSpan.FromSeconds(5)); @@ -66,9 +68,8 @@ public override Task Run() Thread.Sleep(TimeSpan.FromSeconds(1)); } - - return base.Run(); } + // } } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina219.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina219.cs index c27676a630..950ecc6ab0 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina219.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina219.cs @@ -147,7 +147,6 @@ internal override void ReadDeviceInfo() DeviceID = DeviceRevision = 0; } - #region Enumerations private enum Registers : byte { Config = 0x00, @@ -240,9 +239,6 @@ public enum Mode ContinuousAll = 0x7, // default at POR } - #endregion - - #region Shorthand [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteRegister(Registers register, byte value) => BusComms.WriteRegister((byte)register, value, ByteOrder.BigEndian); @@ -260,6 +256,4 @@ public enum Mode [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort ReadRegisterAsUShort(Registers register) => BusComms.ReadRegisterAsUShort((byte)register, ByteOrder.BigEndian); - #endregion - } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina228.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina228.cs index b261d358d7..060e07f0d4 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina228.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina228.cs @@ -117,7 +117,6 @@ internal override void ReadDeviceInfo() DeviceRevision = (byte)(deviceInfo & 0xF); } - #region Measurement /// public override Units.Current ReadCurrent() { @@ -191,9 +190,6 @@ public void ResetAccumulators() WriteRegister(Registers.Config, config); } - #endregion - - #region Enumerations /// /// Enumeration of supported operation modes. /// @@ -300,9 +296,7 @@ private enum Registers : byte ManufacturerID = 0x3E, DeviceID = 0x3F } - #endregion - #region Shorthand [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteRegister(Registers register, byte value) => BusComms.WriteRegister((byte)register, value, ByteOrder.BigEndian); @@ -320,6 +314,4 @@ private enum Registers : byte [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort ReadRegisterAsUShort(Registers register) => BusComms.ReadRegisterAsUShort((byte)register, ByteOrder.BigEndian); - #endregion - } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina260.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina260.cs index 8496bb6365..0cc16343ab 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina260.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Drivers/Ina260.cs @@ -94,7 +94,6 @@ internal override void ReadDeviceInfo() } private readonly IDigitalInterruptPort? _alertPort; - #region Alerts /// /// Raised when the Alert signal changes. /// @@ -195,7 +194,6 @@ public void AlertOnConversionComplete(bool activeHigh = false, bool latching = f WriteRegister(Registers.MaskEnable, (ushort)maskValue); } - #endregion /// /// Reads all status bits. @@ -211,7 +209,6 @@ public void GetStatus(out bool alert, out bool conversionReady, out bool overflo overflow = (maskValue & MaskEnable.MathOverFlow) != 0; } - #region Enumerations private enum Registers : byte { Config = 0x00, @@ -309,9 +306,7 @@ private enum MaskEnable : ushort AlertUnderCurrentLimit = 0x4000, AlertOverCurrentLimit = 0x8000, } - #endregion - #region Shorthand [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteRegister(Registers register, byte value) => BusComms.WriteRegister((byte)register, value, ByteOrder.BigEndian); @@ -329,9 +324,7 @@ private enum MaskEnable : ushort [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort ReadRegisterAsUShort(Registers register) => BusComms.ReadRegisterAsUShort((byte)register, ByteOrder.BigEndian); - #endregion - #region IDisposable extras /// protected override void Dispose(bool disposing) { @@ -342,5 +335,4 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } } - #endregion } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Ina2xx.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Ina2xx.cs index c9550cb192..b3b7c77676 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Ina2xx.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Power.Ina2xx/Driver/Ina2xx.cs @@ -99,7 +99,6 @@ public void Reset() BusComms.WriteRegister(ConfigRegister, ResetIna2xx, ByteOrder.BigEndian); } - #region Sensor Values and Events /// /// The value of the current (in Amps) flowing through the shunt resistor from the last reading. /// @@ -165,7 +164,6 @@ protected override void RaiseEventsAndNotify(IChangeResult<(Units.Current? Curre /// Read the Power measurement from the power monitor IC. public abstract Units.Power ReadPower(); - #endregion /// The manufacturer identification, if supported. Otherwise returns an empty string. public string ManufacturerID { get; internal set; } = string.Empty;