diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/IRedoxPotentialSensor.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/IRedoxPotentialSensor.cs new file mode 100644 index 0000000000..7486ad449b --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/IRedoxPotentialSensor.cs @@ -0,0 +1,15 @@ +using Meadow.Peripherals.Sensors; +using Meadow.Units; + +namespace Meadow.Foundation.Sensors.Environmental; + +/// +/// Represents a sensor for measuring oxidation/reduction potential +/// +public interface IRedoxPotentialSensor : ISamplingSensor +{ + /// + /// Last value read from the sensor + /// + Voltage? Potential { get; } +} diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/IWaterQualityConcentrationsSensor.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/IWaterQualityConcentrationsSensor.cs new file mode 100644 index 0000000000..528129849a --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/IWaterQualityConcentrationsSensor.cs @@ -0,0 +1,14 @@ +using Meadow.Peripherals.Sensors; + +namespace Meadow.Foundation.Sensors.Environmental; + +/// +/// Represents a sensor for measuring water quality concentrations +/// +public interface IWaterQualityConcentrationsSensor : ISamplingSensor +{ + /// + /// Last value read from the sensor + /// + WaterQualityConcentrations? Concentrations { get; } +} diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/WaterQualityConcentrations.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/WaterQualityConcentrations.cs new file mode 100644 index 0000000000..11362dd078 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/WaterQualityConcentrations.cs @@ -0,0 +1,22 @@ +using Meadow.Units; + +namespace Meadow.Foundation.Sensors.Environmental; + +/// +/// Represents concentrations indicating water quality +/// +public struct WaterQualityConcentrations +{ + /// + /// Concentration of dissolved Oxygen in water + /// + public ConcentrationInWater? DissolvedOxygen; + /// + /// Chlorophyll Concentration (CHL) + /// + public ConcentrationInWater? Chlorophyl; + /// + /// Salination (SAL) + /// + public ConcentrationInWater? BlueGreenAlgae; +} diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/Y4000.Structs.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/Y4000.Structs.cs index 85a37febc1..a0b44d9648 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/Y4000.Structs.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/Y4000.Structs.cs @@ -10,7 +10,7 @@ public partial class Y4000 //check for infinity and switch to zero //populate all of the properties - enum Measurement + private enum Measurement { DissolvedOxygen, //DO Turbidity, @@ -27,11 +27,6 @@ enum Measurement /// public struct Measurements { - /// - /// Concentration of dissolved Oxygen in water - /// - public ConcentrationInWater DissolvedOxygen { get; private set; } - /// /// Turbidity (NTU) /// @@ -54,14 +49,9 @@ public struct Measurements public Voltage OxidationReductionPotential { get; private set; } /// - /// Chlorophyll Concentration (CHL) - /// - public ConcentrationInWater Chlorophyl { get; private set; } - - /// - /// Salination (SAL) + /// Water quality concentraions /// - public ConcentrationInWater BlueGreenAlgae { get; private set; } + public WaterQualityConcentrations Concentrations { get; private set; } /// /// Temperature @@ -79,8 +69,8 @@ public Measurements(float[] data) throw new ArgumentException($"Measurements record expects 8 values, received {data.Length}"); } - float value = Normalize(data[(int)Measurement.DissolvedOxygen]); - DissolvedOxygen = new ConcentrationInWater(value, ConcentrationInWater.UnitType.MilligramsPerLiter); + var value = Normalize(data[(int)Measurement.Temperature]); + Temperature = new Units.Temperature(value, Units.Temperature.UnitType.Celsius); value = Normalize(data[(int)Measurement.Turbidity]); Turbidity = new Turbidity(value); @@ -95,16 +85,23 @@ public Measurements(float[] data) OxidationReductionPotential = new Voltage(value, Voltage.UnitType.Volts); value = Normalize(data[(int)Measurement.Chlorophyl]); - Chlorophyl = new ConcentrationInWater(value, ConcentrationInWater.UnitType.MicrogramsPerLiter); + var chlorophyl = new ConcentrationInWater(value, ConcentrationInWater.UnitType.MicrogramsPerLiter); value = Normalize(data[(int)Measurement.BlueGreenAlgae]); - BlueGreenAlgae = new ConcentrationInWater(value, ConcentrationInWater.UnitType.MilligramsPerLiter); + var blueGreenAlgae = new ConcentrationInWater(value, ConcentrationInWater.UnitType.MilligramsPerLiter); - value = Normalize(data[(int)Measurement.Temperature]); - Temperature = new Units.Temperature(value, Units.Temperature.UnitType.Celsius); + value = Normalize(data[(int)Measurement.DissolvedOxygen]); + var dissolvedOxygen = new ConcentrationInWater(value, ConcentrationInWater.UnitType.MilligramsPerLiter); + + Concentrations = new WaterQualityConcentrations + { + Chlorophyl = chlorophyl, + BlueGreenAlgae = blueGreenAlgae, + DissolvedOxygen = dissolvedOxygen + }; } - static float Normalize(float value) => float.IsNormal(value) ? value : 0; + private static float Normalize(float value) => float.IsNormal(value) ? value : 0; /// /// Returns a string that represents the current Y4000 measurement data. @@ -113,13 +110,13 @@ public Measurements(float[] data) public override string ToString() { StringBuilder sb = new StringBuilder(); - sb.AppendLine($"DissolvedOxygen: {DissolvedOxygen} mg/L"); + sb.AppendLine($"DissolvedOxygen: {Concentrations.DissolvedOxygen} mg/L"); sb.AppendLine($"Turbidity: {Turbidity} NTU"); sb.AppendLine($"ElectricalConductivity: {ElectricalConductivity.MilliSiemensPerCentimeter} mS/cm"); sb.AppendLine($"PH: {PH}"); sb.AppendLine($"OxidationReductionPotential: {OxidationReductionPotential.Millivolts} mV"); - sb.AppendLine($"Chlorophyll: {Chlorophyl.MicrogramsPerLiter} ug/L"); - sb.AppendLine($"BlueGreenAlgae: {BlueGreenAlgae.PartsPerMillion} ppm"); + sb.AppendLine($"Chlorophyll: {Concentrations.Chlorophyl.Value.MicrogramsPerLiter} ug/L"); + sb.AppendLine($"BlueGreenAlgae: {Concentrations.BlueGreenAlgae.Value.PartsPerMillion} ppm"); sb.AppendLine($"Temperature: {Temperature.Celsius} C"); return sb.ToString(); diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/Y4000.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/Y4000.cs index fd46c38bd7..dd40634216 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/Y4000.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Driver/Y4000.cs @@ -1,428 +1,459 @@ using Meadow.Hardware; using Meadow.Modbus; +using Meadow.Peripherals.Sensors; +using Meadow.Peripherals.Sensors.Environmental; using Meadow.Units; using System; +using System.Threading; using System.Threading.Tasks; -#nullable enable - -namespace Meadow.Foundation.Sensors.Environmental +namespace Meadow.Foundation.Sensors.Environmental; + +/// +/// Represents a Yosemitech Y4000 Multiparameter Sonde water quality sensor +/// for dissolved oxygen, conductivity, turbidity, pH, chlorophyll, +/// blue green algae, chlorophyll, and temperature +/// +public partial class Y4000 : + IDisposable, + IWaterQualityConcentrationsSensor, + IElectricalConductivitySensor, + IPotentialHydrogenSensor, + ITurbiditySensor, + ITemperatureSensor, + IRedoxPotentialSensor { + private Timer updateTimer; + private TimeSpan? updateInterval = null; + private Measurements? lastMeasurements = null; /// - /// Represents a Yosemitech Y4000 Multiparameter Sonde water quality sensor - /// for dissolved oxygen, conductivity, turbidity, pH, chlorophyll, - /// blue green algae, chlorophyll, and temperature + /// Did we create the port(s) used by the peripheral /// - public partial class Y4000 : PollingSensorBase<(ConcentrationInWater? DissolvedOxygen, - ConcentrationInWater? Chlorophyl, - ConcentrationInWater? BlueGreenAlgae, - Conductivity? ElectricalConductivity, - PotentialHydrogen? PH, - Turbidity? Turbidity, - Units.Temperature? Temperature, - Voltage? OxidationReductionPotential)>, IDisposable - { - /// - /// Raised when the DissolvedOxygen value changes - /// - public event EventHandler> DissolvedOxygenUpdated = default!; - - /// - /// Raised when the Chlorophyll value changes - /// - public event EventHandler> ChlorophylUpdated = default!; - - /// - /// Raised when the BlueGreenAlgae value changes - /// - public event EventHandler> BlueGreenAlgaeUpdated = default!; - - /// - /// Raised when the ElectricalConductivity value changes - /// - public event EventHandler> ElectricalConductivityUpdated = default!; - - /// - /// Raised when the PotentialHydrogen (pH) value changes - /// - public event EventHandler> PHUpdated = default!; - - /// - /// Raised when the Turbidity value changes - /// - public event EventHandler> TurbidityUpdated = default!; - - /// - /// Raised when the Temperature value changes - /// - public event EventHandler> TemperatureUpdated = default!; - - /// - /// Raised when the OxidationReductionPotential (redux) value changes - /// - public event EventHandler> OxidationReductionPotentialUpdated = default!; - - /// - /// The current Dissolved Oxygen concentration - /// - public ConcentrationInWater? DissolvedOxygen => Conditions.DissolvedOxygen; - - /// - /// The current Chlorophyll concentration - /// - public ConcentrationInWater? Chlorophyl => Conditions.Chlorophyl; - - /// - /// The current Blue Green Algae concentration - /// - public ConcentrationInWater? BlueGreenAlgae => Conditions.BlueGreenAlgae; - - /// - /// The current Electrical Conductivity - /// - public Conductivity? ElectricalConductivity => Conditions.ElectricalConductivity; - - /// - /// The current Potential Hydrogen (pH) - /// - public PotentialHydrogen? PH => Conditions.PH; - - /// - /// The current Turbidity - /// - public Turbidity? Turbidity => Conditions.Turbidity; - - /// - /// The current Oxidation Reduction Potential (redux) - /// - public Voltage? OxidationReductionPotential => Conditions.OxidationReductionPotential; - - /// - /// Is the object disposed - /// - public bool IsDisposed { get; private set; } - - /// - /// Did we create the port(s) used by the peripheral - /// - readonly bool createdPort = false; - - readonly IModbusBusClient modbusClient; - - /// - /// 9600 baud 8-N-1 - /// - readonly ISerialPort? serialPort; - - /// - /// The current modbus address - /// - public byte ModbusAddress { get; private set; } = 0x01; - - /// - /// Creates a new Y4000 object - /// - public Y4000( - IModbusBusClient modbusClient, - byte modbusAddress = 0x01) - { - this.modbusClient = modbusClient; - ModbusAddress = modbusAddress; - } + private readonly bool createdPort = false; + private readonly IModbusBusClient modbusClient; - /// - /// Creates a new Y4000 object - /// - public Y4000(IMeadowDevice device, - SerialPortName serialPortName, - byte modbusAddress = 0x01, - IPin? enablePin = null) - { - createdPort = true; + /// + /// 9600 baud 8-N-1 + /// + private readonly ISerialPort? serialPort; - serialPort = device.CreateSerialPort(serialPortName, 9600, 8, Parity.None, StopBits.One); - serialPort.WriteTimeout = serialPort.ReadTimeout = TimeSpan.FromSeconds(5); + /// + /// The default baud rate for communicating with the device + /// + public const int DefaultBaudRate = 9600; + + private EventHandler>? tempEvents; + private EventHandler>? turbidityEvents; + private EventHandler>? phEvents; + private EventHandler>? conductivityEvents; + private EventHandler>? redoxEvents; + private EventHandler>? concentrationEvents; + + Units.Temperature? ITemperatureSensor.Temperature => lastMeasurements?.Temperature; + Turbidity? ITurbiditySensor.Turbidity => lastMeasurements?.Turbidity; + PotentialHydrogen? IPotentialHydrogenSensor.pH => lastMeasurements?.PH; + Conductivity? IElectricalConductivitySensor.Conductivity => lastMeasurements?.ElectricalConductivity; + Voltage? IRedoxPotentialSensor.Potential => lastMeasurements?.OxidationReductionPotential; + WaterQualityConcentrations? IWaterQualityConcentrationsSensor.Concentrations => lastMeasurements?.Concentrations; + + event EventHandler> ISamplingSensor.Updated + { + add => tempEvents += value; + remove => tempEvents -= value; + } - if (enablePin != null) - { - var enablePort = device.CreateDigitalOutputPort(enablePin, false); - modbusClient = new ModbusRtuClient(serialPort, enablePort); - } - else - { - modbusClient = new ModbusRtuClient(serialPort); - } + event EventHandler> ISamplingSensor.Updated + { + add => turbidityEvents += value; + remove => turbidityEvents -= value; + } - ModbusAddress = modbusAddress; - } + event EventHandler> ISamplingSensor.Updated + { + add => phEvents += value; + remove => phEvents -= value; + } + + event EventHandler> ISamplingSensor.Updated + { + add => conductivityEvents += value; + remove => conductivityEvents -= value; + } - /// - /// Initialize sensor - /// - /// - public Task Initialize() + event EventHandler> ISamplingSensor.Updated + { + add => redoxEvents += value; + remove => redoxEvents -= value; + } + + event EventHandler> ISamplingSensor.Updated + { + add => concentrationEvents += value; + remove => concentrationEvents -= value; + } + + /// + public TimeSpan UpdateInterval => updateInterval ?? TimeSpan.FromSeconds(5); + + /// + public bool IsSampling => updateInterval != null; + + /// + public void StartUpdating(TimeSpan? updateInterval) + { + this.updateInterval = updateInterval ?? TimeSpan.FromSeconds(5); + updateTimer.Change(TimeSpan.Zero, this.updateInterval.Value); + } + + /// + public void StopUpdating() + { + this.updateInterval = null; + updateTimer.Change(Timeout.Infinite, Timeout.Infinite); + } + + /// + /// Creates a new Y4000 object + /// + public Y4000(IMeadowDevice device, + SerialPortName serialPortName, + byte modbusAddress = 0x01, + IPin? enablePin = null) + { + createdPort = true; + + serialPort = device.CreateSerialPort(serialPortName, 9600, 8, Parity.None, StopBits.One); + serialPort.WriteTimeout = serialPort.ReadTimeout = TimeSpan.FromSeconds(5); + + if (enablePin != null) { - return modbusClient.Connect(); + var enablePort = device.CreateDigitalOutputPort(enablePin, false); + modbusClient = new ModbusRtuClient(serialPort, enablePort); } - - /// - /// Get the device ISDN (address) of the sensor - /// Note this is a broadcast event so all Y4000 devices on the bus will respond - /// - /// The address as a byte - public async Task GetISDN() + else { - var data = await modbusClient.ReadHoldingRegisters(0xFF, Registers.ISDN.Offset, Registers.ISDN.Length); - - return (byte)(data[0] >> 8); + modbusClient = new ModbusRtuClient(serialPort); } - /// - /// Set the ISDN (address) of the sensor - /// - /// The address - /// - public async Task SetISDN(byte modbusAddress) - { - if (ModbusAddress == modbusAddress) { return; } + ModbusAddress = modbusAddress; - await modbusClient.WriteHoldingRegisters(ModbusAddress, - Registers.ISDN.Offset, - new ushort[] { (ushort)(modbusAddress << 8) }); + updateTimer = new Timer(UpdateTimerProc); + } - ModbusAddress = modbusAddress; - } + private async void UpdateTimerProc(object _) + { + var currentmeasurements = await ReadSensor(); + RaiseEventsAndNotify(currentmeasurements); + } + + /// + /// Reads all measurements of the sensor + /// + public async Task ReadSensor() + { + var values = await modbusClient.ReadHoldingRegistersFloat(ModbusAddress, Registers.Data.Offset, Registers.Data.Length / 2); + return new Measurements(values); + } - /// - /// Get the current supply voltage - /// - /// - public async Task GetSupplyVoltage() + async Task ISensor.Read() + { + Measurements measurements; + + if (!IsSampling || lastMeasurements == null) { - var voltage = await modbusClient.ReadHoldingRegistersFloat(ModbusAddress, Registers.SupplyVoltage.Offset, Registers.SupplyVoltage.Length / 2); - return new Voltage(voltage[0], Voltage.UnitType.Volts); + measurements = await ReadSensor(); } - - /// - /// Get the device serial number - /// - /// The serial number as a ushort array - public async Task GetSerialNumber() + else { - var data = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.SerialNumber.Offset, Registers.SerialNumber.Length); - - return data; + measurements = lastMeasurements.Value; } + return measurements.Temperature; + } - /// - /// Get the device version - /// - /// - public async Task GetVersion() + async Task ISensor.Read() + { + Measurements measurements; + + if (!IsSampling || lastMeasurements == null) { - var data = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.Version.Offset, Registers.Version.Length); - return data; + measurements = await ReadSensor(); } - - /// - /// Get the brush or wiper interval - /// - /// - public async Task GetBrushInterval() + else { - var value = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.BrushInterval.Offset, Registers.BrushInterval.Length); - return TimeSpan.FromMinutes(value[0]); + measurements = lastMeasurements.Value; } + return measurements.Turbidity; + } - /// - /// Set the brush or wiper interval (normalized to minutes) - /// - public Task SetBrushInterval(TimeSpan interval) + async Task ISensor.Read() + { + Measurements measurements; + + if (!IsSampling || lastMeasurements == null) { - ushort minutes = (ushort)interval.TotalMinutes; - return modbusClient.WriteHoldingRegister(ModbusAddress, Registers.BrushInterval.Offset, minutes); + measurements = await ReadSensor(); } - - /// - /// Start the brush or wiper - /// - /// - public Task StartBrush() + else { - return modbusClient.WriteHoldingRegister(ModbusAddress, Registers.StartBrush.Offset, 0); + measurements = lastMeasurements.Value; } + return measurements.PH; + } + + async Task ISensor.Read() + { + Measurements measurements; - /// - /// Read the error flag from the sensor - /// - /// - public async Task GetErrorFlag() + if (!IsSampling || lastMeasurements == null) { - var data = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.ErrorCode.Offset, 2); - return data[0]; + measurements = await ReadSensor(); } - - /* - * Get and Set time work but Get returns bad values - * Leaving code here for future investigation - */ - /// - /// Set the time on the device - /// Stores: year, month, day, hour, minute and second - /// - /// - /// - Task SetTime(DateTime time) + else { - byte second = 0x17;// (byte)time.Second; - byte minute = 0x05;//(byte)time.Minute; - byte hour = 0x13;//(byte)time.Hour; - byte day = 0x26;//(byte)time.Day; - byte month = 0x04;//(byte)time.Month; - //0 - byte year = 0x16; // (byte)time.Year; - //0 - - var data = new ushort[4]; - data[0] = (ushort)(minute | (second << 8)); - data[1] = (ushort)(day | (hour << 8)); - data[2] = (ushort)(month << 8 | 0x00); - data[3] = (ushort)(year << 8 | 0x00); - - return modbusClient.WriteHoldingRegisters(ModbusAddress, Registers.Time.Offset, data); + measurements = lastMeasurements.Value; } + return measurements.ElectricalConductivity; + } - /// - /// Get the time stored on the sensor - /// - /// - async Task GetTime() - { - var values = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.Time.Offset, 4); + async Task ISensor.Read() + { + Measurements measurements; - return DateTime.MinValue; + if (!IsSampling || lastMeasurements == null) + { + measurements = await ReadSensor(); } - - /// - /// Reads data from the sensor - /// - /// The latest sensor reading - protected override async Task<(ConcentrationInWater? DissolvedOxygen, - ConcentrationInWater? Chlorophyl, - ConcentrationInWater? BlueGreenAlgae, - Conductivity? ElectricalConductivity, - PotentialHydrogen? PH, - Turbidity? Turbidity, - Units.Temperature? Temperature, - Voltage? OxidationReductionPotential)> - ReadSensor() + else { - (ConcentrationInWater? DissolvedOxygen, - ConcentrationInWater? Chlorophyl, - ConcentrationInWater? BlueGreenAlgae, - Conductivity? ElectricalConductivity, - PotentialHydrogen? PH, - Turbidity? Turbidity, - Units.Temperature? Temperature, - Voltage? OxidationReductionPotential) conditions; - - var values = await modbusClient.ReadHoldingRegistersFloat(ModbusAddress, Registers.Data.Offset, Registers.Data.Length / 2); - var measurements = new Measurements(values); - - conditions.BlueGreenAlgae = measurements.BlueGreenAlgae; - conditions.Chlorophyl = measurements.Chlorophyl; - conditions.DissolvedOxygen = measurements.DissolvedOxygen; - conditions.ElectricalConductivity = measurements.ElectricalConductivity; - conditions.OxidationReductionPotential = measurements.OxidationReductionPotential; - conditions.PH = measurements.PH; - conditions.Temperature = measurements.Temperature; - conditions.Turbidity = measurements.Turbidity; - - return conditions; + measurements = lastMeasurements.Value; } + return measurements.OxidationReductionPotential; + } - /// - /// Raise events for subscribers and notify of value changes - /// - /// The updated sensor data - protected override void RaiseEventsAndNotify( - IChangeResult< - (ConcentrationInWater? DissolvedOxygen, - ConcentrationInWater? Chlorophyl, - ConcentrationInWater? BlueGreenAlgae, - Conductivity? ElectricalConductivity, - PotentialHydrogen? PH, - Turbidity? Turbidity, - Units.Temperature? Temperature, - Voltage? OxidationReductionPotential) - > changeResult) - { - if (changeResult.New.DissolvedOxygen is { } DO) - { - DissolvedOxygenUpdated?.Invoke(this, new ChangeResult(DO, changeResult.Old?.DissolvedOxygen)); - } - if (changeResult.New.Chlorophyl is { } Chl) - { - ChlorophylUpdated?.Invoke(this, new ChangeResult(Chl, changeResult.Old?.Chlorophyl)); - } - if (changeResult.New.BlueGreenAlgae is { } BGR) - { - BlueGreenAlgaeUpdated?.Invoke(this, new ChangeResult(BGR, changeResult.Old?.BlueGreenAlgae)); - } - if (changeResult.New.ElectricalConductivity is { } EC) - { - ElectricalConductivityUpdated?.Invoke(this, new ChangeResult(EC, changeResult.Old?.ElectricalConductivity)); - } - if (changeResult.New.PH is { } PH) - { - PHUpdated?.Invoke(this, new ChangeResult(PH, changeResult.Old?.PH)); - } - if (changeResult.New.Turbidity is { } Tur) - { - TurbidityUpdated?.Invoke(this, new ChangeResult(Tur, changeResult.Old?.Turbidity)); - } - if (changeResult.New.Temperature is { } Temp) - { - TemperatureUpdated?.Invoke(this, new ChangeResult(Temp, changeResult.Old?.Temperature)); - } - if (changeResult.New.OxidationReductionPotential is { } Redux) - { - OxidationReductionPotentialUpdated?.Invoke(this, new ChangeResult(Redux, changeResult.Old?.OxidationReductionPotential)); - } + async Task ISensor.Read() + { + Measurements measurements; - base.RaiseEventsAndNotify(changeResult); + if (!IsSampling || lastMeasurements == null) + { + measurements = await ReadSensor(); } - - /// - public void Dispose() + else { - Dispose(disposing: true); - GC.SuppressFinalize(this); + measurements = lastMeasurements.Value; } + return measurements.Concentrations; + } + + private void RaiseEventsAndNotify(Measurements currentmeasurements) + { + tempEvents?.Invoke(this, new ChangeResult(currentmeasurements.Temperature, lastMeasurements?.Temperature)); + turbidityEvents?.Invoke(this, new ChangeResult(currentmeasurements.Turbidity, lastMeasurements?.Turbidity)); + phEvents?.Invoke(this, new ChangeResult(currentmeasurements.PH, lastMeasurements?.PH)); + conductivityEvents?.Invoke(this, new ChangeResult(currentmeasurements.ElectricalConductivity, lastMeasurements?.ElectricalConductivity)); + redoxEvents?.Invoke(this, new ChangeResult(currentmeasurements.OxidationReductionPotential, lastMeasurements?.OxidationReductionPotential)); + concentrationEvents?.Invoke(this, new ChangeResult(currentmeasurements.Concentrations, lastMeasurements?.Concentrations)); + + lastMeasurements = currentmeasurements; + } + + /// + /// Is the object disposed + /// + public bool IsDisposed { get; private set; } + + /// + /// The current modbus address + /// + public byte ModbusAddress { get; private set; } = 0x01; + + /// + /// Creates a new Y4000 object + /// + public Y4000( + IModbusBusClient modbusClient, + byte modbusAddress = 0x01) + { + this.modbusClient = modbusClient; + ModbusAddress = modbusAddress; + } + + /// + /// Initialize sensor + /// + /// + public Task Initialize() + { + return modbusClient.Connect(); + } + + /// + /// Get the device ISDN (address) of the sensor + /// Note this is a broadcast event so all Y4000 devices on the bus will respond + /// + /// The address as a byte + public async Task GetISDN() + { + var data = await modbusClient.ReadHoldingRegisters(0xFF, Registers.ISDN.Offset, Registers.ISDN.Length); + + return (byte)(data[0] >> 8); + } + + /// + /// Set the ISDN (address) of the sensor + /// + /// The address + /// + public async Task SetISDN(byte modbusAddress) + { + if (ModbusAddress == modbusAddress) { return; } + + await modbusClient.WriteHoldingRegisters(ModbusAddress, + Registers.ISDN.Offset, + new ushort[] { (ushort)(modbusAddress << 8) }); + + ModbusAddress = modbusAddress; + } + + /// + /// Get the current supply voltage + /// + /// + public async Task GetSupplyVoltage() + { + var voltage = await modbusClient.ReadHoldingRegistersFloat(ModbusAddress, Registers.SupplyVoltage.Offset, Registers.SupplyVoltage.Length / 2); + return new Voltage(voltage[0], Voltage.UnitType.Volts); + } + + /// + /// Get the device serial number + /// + /// The serial number as a ushort array + public async Task GetSerialNumber() + { + var data = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.SerialNumber.Offset, Registers.SerialNumber.Length); + + return data; + } - /// - /// Dispose of the object - /// - /// Is disposing - protected virtual void Dispose(bool disposing) + /// + /// Get the device version + /// + /// + public async Task GetVersion() + { + var data = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.Version.Offset, Registers.Version.Length); + return data; + } + + /// + /// Get the brush or wiper interval + /// + /// + public async Task GetBrushInterval() + { + var value = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.BrushInterval.Offset, Registers.BrushInterval.Length); + return TimeSpan.FromMinutes(value[0]); + } + + /// + /// Set the brush or wiper interval (normalized to minutes) + /// + public Task SetBrushInterval(TimeSpan interval) + { + ushort minutes = (ushort)interval.TotalMinutes; + return modbusClient.WriteHoldingRegister(ModbusAddress, Registers.BrushInterval.Offset, minutes); + } + + /// + /// Start the brush or wiper + /// + /// + public Task StartBrush() + { + return modbusClient.WriteHoldingRegister(ModbusAddress, Registers.StartBrush.Offset, 0); + } + + /// + /// Read the error flag from the sensor + /// + /// + public async Task GetErrorFlag() + { + var data = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.ErrorCode.Offset, 2); + return data[0]; + } + + /* + * Get and Set time work but Get returns bad values + * Leaving code here for future investigation + */ + /// + /// Set the time on the device + /// Stores: year, month, day, hour, minute and second + /// + /// + /// + private Task SetTime(DateTime time) + { + byte second = 0x17;// (byte)time.Second; + byte minute = 0x05;//(byte)time.Minute; + byte hour = 0x13;//(byte)time.Hour; + byte day = 0x26;//(byte)time.Day; + byte month = 0x04;//(byte)time.Month; + //0 + byte year = 0x16; // (byte)time.Year; + //0 + + var data = new ushort[4]; + data[0] = (ushort)(minute | (second << 8)); + data[1] = (ushort)(day | (hour << 8)); + data[2] = (ushort)(month << 8 | 0x00); + data[3] = (ushort)(year << 8 | 0x00); + + return modbusClient.WriteHoldingRegisters(ModbusAddress, Registers.Time.Offset, data); + } + + /// + /// Get the time stored on the sensor + /// + /// + private async Task GetTime() + { + var values = await modbusClient.ReadHoldingRegisters(ModbusAddress, Registers.Time.Offset, 4); + + return DateTime.MinValue; + } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose of the object + /// + /// Is disposing + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) { - if (!IsDisposed) + if (disposing && createdPort) { - if (disposing && createdPort) + if (serialPort is { }) { - if (serialPort is { }) + if (serialPort.IsOpen) { - if (serialPort.IsOpen) - { - serialPort.Close(); - } - - serialPort.Dispose(); + serialPort.Close(); } - } - IsDisposed = true; + serialPort.Dispose(); + } } + + IsDisposed = true; } } } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Samples/Y4000_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Samples/Y4000_Sample/MeadowApp.cs index 13ff4bd465..8498550c8c 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Samples/Y4000_Sample/MeadowApp.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Y4000/Samples/Y4000_Sample/MeadowApp.cs @@ -9,9 +9,9 @@ public class MeadowApp : App { // - Y4000 sensor; + private Y4000 sensor; - public async override Task Initialize() + public override async Task Initialize() { Resolver.Log.Info("Initialize..."); await Task.Delay(2000); @@ -32,7 +32,7 @@ public override async Task Run() var supplyVoltage = await sensor.GetSupplyVoltage(); Resolver.Log.Info($"Supply voltage: {supplyVoltage}"); - var measurements = await sensor.Read(); + var measurements = await sensor.ReadSensor(); Resolver.Log.Info($"Sensor data: {measurements}"); }