Skip to content

Commit

Permalink
Merge pull request #1084 from WildernessLabs/feature/y4000
Browse files Browse the repository at this point in the history
refactored Y4000 driver
  • Loading branch information
ctacke authored Dec 16, 2024
2 parents 206edf4 + d6473bb commit a01518c
Show file tree
Hide file tree
Showing 18 changed files with 816 additions and 401 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
</PropertyGroup>
<ItemGroup>
<None Include=".\Readme.md" Pack="true" PackagePath="" />
<PackageReference Include="Silk.NET" Version="2.21.0" />
<PackageReference Include="SkiaSharp" Version="2.88.8" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="Silk.NET" Version="2.22.0" />
<PackageReference Include="SkiaSharp" Version="2.88.9" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\icon.png" Link="icon.png" Pack="true" PackagePath="" />
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Meadow.Units;
using System.Threading.Tasks;

namespace Meadow.Foundation.Sensors.Environmental;

public interface IKellerTransducer
{
Task<Units.Temperature> ReadTemperature(TemperatureChannel channel);
Task<Pressure> ReadPressure(PressureChannel channel);
Task<byte> ReadModbusAddress();
Task<int> ReadSerialNumber();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Meadow.Modbus;
using Meadow.Units;
using System;
using System.Threading.Tasks;

namespace Meadow.Foundation.Sensors.Environmental;

public class KellerTransducer : IKellerTransducer
{
private ModbusRtuClient modbusClient;
private ushort? activePressureChannels;
private ushort? activeTemperatureChannels;
private byte communicationAddress;

public KellerTransducer(ModbusRtuClient modbus, byte modbusAddress = 1)
{
communicationAddress = modbusAddress;
this.modbusClient = modbus;

if (!modbus.IsConnected)
{
modbus.Connect();
}
}

private async Task ReadConfiguration()
{
// the device doesn't appear to like reading > 4 registers at a time
try
{
var registers = await modbusClient.ReadHoldingRegisters(communicationAddress, 0x0204, 4);
activePressureChannels = registers[0];
activeTemperatureChannels = registers[1];
}
catch (Exception ex)
{
Resolver.Log.Warn($"Transducer initialization failure: {ex.Message}", "keller xline");
}
}

/// <summary>
/// Reads the device's Modbus Address.
/// </summary>
/// <remarks>
/// The Keller Transducer can be discovered using an initial broadcast address of 250, then the actual sensor can be read using this method
/// </remarks>
public async Task<byte> ReadModbusAddress()
{
var registers = await modbusClient.ReadHoldingRegisters(communicationAddress, 0x020D, 1);
return (byte)registers[0];
}

public async Task<int> ReadSerialNumber()
{
var registers = await modbusClient.ReadHoldingRegisters(communicationAddress, 0x0202, 2);
return registers.ExtractInt32();
}

public Task WriteModbusAddress(byte address)
{
return modbusClient.WriteHoldingRegister(communicationAddress, 0x020D, address);
}

public async Task<Units.Temperature> ReadTemperature(TemperatureChannel channel)
{
var count = 6;

if (activeTemperatureChannels == null)
{
await ReadConfiguration();
}

if (((ushort)channel & activeTemperatureChannels) == 0)
{
throw new ArgumentException("Selected channel is not supported by the connected device");
}

ushort address = channel switch
{
TemperatureChannel.T => 0x006,
TemperatureChannel.TOB1 => 0x008,
TemperatureChannel.TOB2 => 0x00A,
_ => throw new ArgumentException()
};

var r = await modbusClient.ReadHoldingRegisters(communicationAddress, address, 2);
var temp = r.ExtractSingle();
return new Units.Temperature(temp, Units.Temperature.UnitType.Celsius);
}

public async Task<Pressure> ReadPressure(PressureChannel channel)
{
if (activePressureChannels == null)
{
await ReadConfiguration();
}

if (((ushort)channel & activePressureChannels) == 0)
{
throw new ArgumentException("Selected channel is not supported by the connected device");
}

ushort address = channel switch
{
PressureChannel.P1 => 0x0002,
PressureChannel.P2 => 0x004,
_ => throw new ArgumentException()
};

var r = await modbusClient.ReadHoldingRegisters(communicationAddress, address, 2);
var p = r.ExtractSingle();
return new Pressure(p, Pressure.UnitType.Bar);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Meadow.Foundation.Sensors.Environmental;

public enum PressureChannel
{
P1 = 1 << 1,
P2 = 1 << 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Meadow.Foundation.Sensors.Environmental.Ens160

**ENS160 I2C C02, Ethanol and AQI sensor**

The **Ens160** library is included in the **Meadow.Foundation.Sensors.Environmental.Ens160** 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.

For more information on developing for Meadow, visit [developer.wildernesslabs.co](http://developer.wildernesslabs.co/).

To view all Wilderness Labs open-source projects, including samples, visit [github.com/wildernesslabs](https://github.com/wildernesslabs/).

## Installation

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.Sensors.Environmental.Ens160`
## Usage

```csharp
Ens160 sensor;

public override Task Initialize()
{
Resolver.Log.Info("Initializing...");

var i2cBus = Device.CreateI2cBus(Meadow.Hardware.I2cBusSpeed.Standard);

sensor = new Ens160(i2cBus, (byte)Ens160.Addresses.Address_0x53);

var consumer = Ens160.CreateObserver(
handler: result =>
{
Resolver.Log.Info($"Observer: C02 concentration changed by threshold; new: {result.New.CO2Concentration?.PartsPerMillion:N0}ppm");
},
filter: result =>
{
if (result.Old?.CO2Concentration is { } oldCon &&
result.New.CO2Concentration is { } newCon)
{
return Math.Abs((newCon - oldCon).PartsPerMillion) > 10;
}
return false;
}
);

sensor?.Subscribe(consumer);

if (sensor != null)
{
sensor.Updated += (sender, result) =>
{
Resolver.Log.Info($" CO2 Concentration: {result.New.CO2Concentration?.PartsPerMillion:N0}ppm");
Resolver.Log.Info($" Ethanol Concentration: {result.New.EthanolConcentration?.PartsPerBillion:N0}ppb");
Resolver.Log.Info($" TVOC Concentration: {result.New.TVOCConcentration?.PartsPerBillion:N0}ppb");
Resolver.Log.Info($" AQI: {sensor.GetAirQualityIndex()}");
};
}

sensor?.StartUpdating(TimeSpan.FromSeconds(2));

return base.Initialize();
}

```
## How to Contribute

- **Found a bug?** [Report an issue](https://github.com/WildernessLabs/Meadow_Issues/issues)
- Have a **feature idea or driver request?** [Open a new feature request](https://github.com/WildernessLabs/Meadow_Issues/issues)
- Want to **contribute code?** Fork the [Meadow.Foundation](https://github.com/WildernessLabs/Meadow.Foundation) repository and submit a pull request against the `develop` branch


## Need Help?

If you have questions or need assistance, please join the Wilderness Labs [community on Slack](http://slackinvite.wildernesslabs.co/).
## About Meadow

Meadow is a complete, IoT platform with defense-grade security that runs full .NET applications on embeddable microcontrollers and Linux single-board computers including Raspberry Pi and NVIDIA Jetson.

### Build

Use the full .NET platform and tooling such as Visual Studio and plug-and-play hardware drivers to painlessly build IoT solutions.

### Connect

Utilize native support for WiFi, Ethernet, and Cellular connectivity to send sensor data to the Cloud and remotely control your peripherals.

### Deploy

Instantly deploy and manage your fleet in the cloud for OtA, health-monitoring, logs, command + control, and enterprise backend integrations.


Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Meadow.Sdk/1.1.0">
<PropertyGroup>
<Version>1.11.0</Version>
<PackageReadmeFile>Readme.md</PackageReadmeFile>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageIcon>icon.png</PackageIcon>
<Authors>Wilderness Labs, Inc</Authors>
<TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Library</OutputType>
<AssemblyName>Keller.XLine</AssemblyName>
<Company>Wilderness Labs, Inc</Company>
<PackageProjectUrl>http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/</PackageProjectUrl>
<PackageId>Meadow.Foundation.Sensors.Environmental.Keller.XLine</PackageId>
<RepositoryUrl>https://github.com/WildernessLabs/Meadow.Foundation</RepositoryUrl>
<PackageTags>Meadow,Meadow.Foundation,Environmental,Keller,Pressure</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>Driver for the line of Keller X-Line Modbus Pressure Transducers</Description>
</PropertyGroup>
<ItemGroup>
<None Include=".\Readme.md" Pack="true" PackagePath="" />
<None Include="..\..\..\icon.png" Pack="true" PackagePath="" />
<PackageReference Include="System.IO.Ports" Version="9.0.0" />
<ProjectReference Include="..\..\..\..\..\Meadow.Modbus\src\Meadow.Modbus\Meadow.Modbus.csproj" />
<ProjectReference Include="..\..\..\Meadow.Foundation.Core\Meadow.Foundation.Core.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Meadow.Units;
using System.Threading.Tasks;

namespace Meadow.Foundation.Sensors.Environmental;

public class SimulatedKellerTransducer : IKellerTransducer
{
public Task<byte> ReadModbusAddress()
{
return Task.FromResult((byte)255);
}

public Task<Pressure> ReadPressure(PressureChannel channel)
{
return Task.FromResult(new Pressure(123, Pressure.UnitType.Millibar));
}

public Task<int> ReadSerialNumber()
{
return Task.FromResult(12345678);
}

public Task<Units.Temperature> ReadTemperature(TemperatureChannel channel)
{
return Task.FromResult(new Units.Temperature(42.1, Units.Temperature.UnitType.Fahrenheit));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Meadow.Foundation.Sensors.Environmental;

public enum TemperatureChannel
{
T = 1 << 3,
TOB1 = 1 << 4,
TOB2 = 1 << 5,
Con = 1 << 7
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Meadow.Peripherals.Sensors;
using Meadow.Units;

namespace Meadow.Foundation.Sensors.Environmental;

/// <summary>
/// Represents a sensor for measuring oxidation/reduction potential
/// </summary>
public interface IRedoxPotentialSensor : ISamplingSensor<Voltage>
{
/// <summary>
/// Last value read from the sensor
/// </summary>
Voltage? Potential { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Meadow.Peripherals.Sensors;

namespace Meadow.Foundation.Sensors.Environmental;

/// <summary>
/// Represents a sensor for measuring water quality concentrations
/// </summary>
public interface IWaterQualityConcentrationsSensor : ISamplingSensor<WaterQualityConcentrations>
{
/// <summary>
/// Last value read from the sensor
/// </summary>
WaterQualityConcentrations? Concentrations { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<ItemGroup>
<None Include=".\Readme.md" Pack="true" PackagePath="" />
<None Include="..\..\..\icon.png" Pack="true" PackagePath="" />
<PackageReference Include="System.IO.Ports" Version="9.0.0" />
<ProjectReference Include="..\..\..\Meadow.Foundation.Core\Meadow.Foundation.Core.csproj" />
<ProjectReference Include="..\..\..\..\..\Meadow.Modbus\src\Meadow.Modbus\Meadow.Modbus.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Meadow.Units;

namespace Meadow.Foundation.Sensors.Environmental;

/// <summary>
/// Represents concentrations indicating water quality
/// </summary>
public struct WaterQualityConcentrations
{
/// <summary>
/// Concentration of dissolved Oxygen in water
/// </summary>
public ConcentrationInWater? DissolvedOxygen;
/// <summary>
/// Chlorophyll Concentration (CHL)
/// </summary>
public ConcentrationInWater? Chlorophyl;
/// <summary>
/// Salination (SAL)
/// </summary>
public ConcentrationInWater? BlueGreenAlgae;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@
{
public partial class Y4000
{
static class Registers
// DEV NOTE:
// for more info, see
// https://github.com/EnviroDIY/YosemitechModbus/blob/master/src/YosemitechModbus.cpp
private static class Registers
{
public static HoldingRegister Version = new HoldingRegister(0x0700, 0x02);
public static HoldingRegister ErrorCode = new HoldingRegister(0x0800, 0x01);
public static HoldingRegister BrushInterval = new HoldingRegister(0x0E00, 0x01);
public static HoldingRegister StartBrush = new HoldingRegister(0x2F00, 0x01);
public static HoldingRegister SupplyVoltage = new HoldingRegister(0x1E00, 0x02);
public static HoldingRegister Time = new HoldingRegister(0x1300, 0x04);
public static HoldingRegister SerialNumber = new HoldingRegister(0x1400, 0x06);//sample shows 14
public static HoldingRegister Data = new HoldingRegister(0x2601, 0x10);
public static HoldingRegister SerialNumber = new HoldingRegister(0x1400, 0x06);//CPP sample shows 14, buf you end up with unprintable ragbage at the end if you do
public static HoldingRegister Data = new HoldingRegister(0x2601, 0x10); // the CPP sample says the manual (which says 0x2600) is wrong
public static HoldingRegister ISDN = new HoldingRegister(0x3000, 0x01);
}

struct HoldingRegister
private struct HoldingRegister
{
public ushort Offset { get; private set; }
public int Length { get; private set; }
Expand Down
Loading

0 comments on commit a01518c

Please sign in to comment.