Table of Contents

Automating IO Modules

Obtaining an Instance of a CFHEADER Module

The root of the API is the static Cfheader.Instances collection. There can be as many as 8 CFHEADER modules connected to a single USB host.

// Detect all connected CFHEADER modules.
foreach(var cfheader in Cfheader.Instances)
{
    try
    {
        cfheader.Open();
        Console.WriteLine($"CFHEADER at address {cfheader.Address} was found connected to this host.");
    }
    catch
    {
        Console.WriteLine($"CFHEADER at address {cfheader.Address} was not found connected to this host.");
    }
}

The Cfheader.Instances collection is ordered by address, so to obtain a Cfheader instance at a specific address, simply use the address as an index.

var cfheaderAtAddress0 = Cfheader.Instances[0];
var CfheaderAtAddress1 = Cfheader.Instances[1];
// etc...

Or, use Linq syntax to find instances by the Cfheader.Address property.

var evenCfheaderInstances = Cfheader.Instances.Where(i => (i.Address % 2) == 0);
var oddCfheaderInstances = Cfheader.Instances.Where(i => (i.Address % 2) == 1);

USB Communication

Before communicating with a Cfheader instance over USB, it must first be Opened for USB communication.

Once opened, USB communication with the CFHEADER module, and consequently I²C communication with the IO modules, is performed using the Sync method. The Sync method effectively writes the current state of the output modules and reads the current state of the input modules. See mode of operation for more information.

To end communication with a Cfheader instance, call the Close method.

// Get the CFHEADER module at address 0
var cfheader0 = Cfheader.Instances[0];
 
// Try to open USB communication with the CFHEADER module.
try
{
    cfheader0.Open();
}
catch(Exception ex)
{
    Console.Error.WriteLine($"Could not open communication with CFHEADER module at address {cfheader0.Address}.");
    Environment.Exit(1);
}
 
// Try communicating over USB with the CFHEADER module
try
{
    while(true)
    {
        cfheader0.Sync();
    }
}
catch(Exception ex)
{
    Console.Error.WriteLine($"Communication with CFHEADER module at address {cfheader0.Address} failed.");
    Environment.Exit(1);
}
 
// Close USB communication with the CFHEADER module.
cfheader0.Close();

Multiple CFHEADER Modules

Up to 8 CFHEADER modules can be connected to a single USB host. It is therefore, possible to automate multiple arrays of IO modules simultaneously, either in separate threads or separate processes. The example below demonstrates running 3 CFHEADER arrays simultaneously, each in their own thread.

using ComfileTech.Cfnet.Cfheader;
 
// Get the CFHEADER instances
var cfheader0 = Cfheader.Instances[0];
var cfheader1 = Cfheader.Instances[1];
var cfheader2 = Cfheader.Instances[2];
 
// Open USB communication for each CFHEADER instance
cfheader0.Open();
cfheader1.Open();
cfheader2.Open();
 
// Create a thread for each CFHEADER instance
var thread0 = new Thread(() => Demo(cfheader0));
var thread1 = new Thread(() => Demo(cfheader1));
var thread2 = new Thread(() => Demo(cfheader2));
 
// Start each thread
thread0.Start();
thread1.Start();
thread2.Start();
 
// Wait for each thread to finish
thread0.Join();
thread1.Join();
thread2.Join();
 
void Demo(Cfheader cfheader)
{
    // Get the digital output module
    var cfdo_16n0 = cfheader.DigitalOutputModules[0];
 
    // Initialize all channels to 0
    cfdo_16n0.State = 0x00;
 
    while (true)
    {
        foreach (var channel in cfdo_16n0.Channels)
        {
            // Toggle the channel on
            channel.State = !channel.State;
            channel.Module.Header.Sync();
 
            // Delay for 50ms
            Thread.Sleep(50);
 
            // Toggle the channel off
            channel.State = !channel.State;
            channel.Module.Header.Sync();
        }
    }
}

Obtaining an Instance of an IO Module

An instance of each IO module connected to a Cfheader can be obtained though the Cfheader's AnalogInputModules, AnalogOutputModules, DigitalInputModules, and DigitalOutputModules collections.

The IO module collections are also ordered by address, so an instance can be obtained by simply indexing the collection by address.

var cfheader0 = Cfheader.Instances[0];
 
var analogInputModuleAtAddress0 = cfheader0.AnalogInputModules[0];
var analogOutputModuleAtAddress3 = cfheader0.AnalogOutputModules[3];
var digitalInputModuleAtAddress7 = cfheader0.DigitalInputModules[7];
var digitalOutputModuleAtAddress4 = cfheader0.DigitalOutputModules[4];

All IO modules have the following members:

For more information on how to utilize the I2cStatus, AcknowledgeI2cFailure, and I2cFailed members, see the error handling explanations in the mode of operation documentation.

Detecting Connected IO Modules

Using the properties listed above, an algorithm to detect connected modules can be created as illustrated below.

// Get the CFHEADER module at address 0
var cfheader0 = Cfheader.Instances[0];
 
// Open USB communcation with the CFHEADER module
cfheader0.Open();
 
// Get all possible IO module instances
var allIOModules = ((IEnumerable<IIOModule>)cfheader0.AnalogInputModules)
    .Concat(cfheader0.AnalogOutputModules)
    .Concat(cfheader0.DigitalInputModules)
    .Concat(cfheader0.DigitalOutputModules);
 
// Reset the IO module's I²C status so I²C communication will be attempted again on the next call to `Sync`.
foreach(var ioModule in allIOModules)
{
    ioModule.AcknowledgeI2cFailure();
}
 
// Test i2c communication for each IO module
cfheader0.Sync();
 
// Identify each IO module whose I²C communication succeeded.
foreach(var ioModule in allIOModules)
{
    if (ioModule.I2cStatus == I2cResult.Success)
    {
        Console.WriteLine($"Found {ioModule.GetType().Name} at address {ioModule.Address}");
    }
}
 
// Close USB communication with the CFHEADER module
cfheader0.Close();

IO Module Channels

Each IO module may have a collection of channels. The collection of channels will be ordered by the channel's Address, so an instance of the channel can be obtained by using the address as an index.

Each channel, being its own object, makes is very convenient to write self-documented code, by assigning channels to descriptive variable names.

var cfheader0 = Cfheader.Instances[0];
var digitalOutputModule0 = cfheader.DigitalOutputModules[0];
 
var coolingFan = digitalOutputModule0.Channels[0];
var flowSelenoid = digitalOutputModule0.Channels[1];
var alarmIndicator = digitalOutputModule0.Channels[2];

Given an instance of a channel, the IO module that it belongs to can be always be obtained through the channel's Module property.

AnalogInputModule

The AnalogInputModule class is used to automate a CFADC-A4L module. The CFADC-A4L has 4 channels, but only one channel can perform an analog to digital conversion at a time.

Each channel, on its turn, can perform multiple conversions, according to its NumberOfConversions property. The active channel will perform all of its conversions before advancing to the next channel. Each conversion will be performed independent of the I²C communication, but the active channel will only advance when an I²C communication is performed.

If a channel is not in use, set its NumberOfConversions property to 0 so that it will be skipped when advancing to the next channel. When NumberOfConversions is 0, the IsEnabled property will return false. Otherwise, it will return true.

var cfheader0 = Cfheader.Instances[0];
var analogInputModule0 = cfheader.AnalogInputModules[0];
 
// Channel 0 will perform 2 conversions first,
// then Channel 1 will perform 2 conversions,
// channels 2 and 3 will be immediately skipped,
// then back to channel 0.
analogInputModule0.Channels[0].NumberOfConversions = 2;
analogInputModule0.Channels[1].NumberOfConversions = 2;
analogInputModule0.Channels[2].NumberOfConversions = 0;
analogInputModule0.Channels[3].NumberOfConversions = 0;

A channel's state can be read as a raw digital value, RawValue, a Voltage, or a Current. Voltage and Current properties are calculated from the RawValue property. Calibration may be necessary.

var cfheader0 = Cfheader.Instances[0];
var analogInputModule0 = cfheader0.AnalogInputModules[0];
 
cfheader0.Open();
 
while (true)
{
    cfheader0.Sync();
 
    foreach (var c in analogInputModule0.Channels)
    {
        Console.WriteLine($"Channel {c.Address,2}: {c.RawValue,8} {c.Voltage,8:F2}V {c.Current,8:F2}A");
    }
}

Every time all NumberOfConversions of a channel are completed, and read via Sync, the ConversionCompleted event will be fired. This event will be fired regardless of whether the channel's state (i.e. RawValue) changes. Use the RawValueChanged event to be notified when the RawValue, and consequently the Voltage and Current properties, change.

var cfheader0 = Cfheader.Instances[0];
var analogInputModule0 = cfheader0.AnalogInputModules[0];
 
foreach(var channel in analogInputModule0.Channels)
{
    channel.ConversionCompleted += c =>
    {
        Console.WriteLine($"Channel {c.Address,2}: {c.RawValue,8} {c.Voltage,8:F2}V {c.Current,8:F2}A");
    };
}
 
cfheader0.Open();
 
while (true)
{
    cfheader0.Sync();
}

AnalogOutputModule

The AnalogOutputModule class is used to automate the CFDAC-2V CFNET module. It has 2 channels, each of which can be independently assigned an analog voltage output.

The voltage of each channel can be set as a raw digital value, via the RawValue property, or as an explicit voltage via the Voltage property.

var cfheader0 = Cfheader.Instances[0];
var analogOutputModule0 = cfheader0.AnalogOutputModules[0];
 
cfheader0.Open();
 
// Use a stopwatch for keeping time
var stopwatch = Stopwatch.StartNew();
 
while (true)
{
    // Get time in seconds
    var t = stopwatch.Elapsed.TotalSeconds;
 
    // Iterate through analog output channel
    foreach (var channel in analogOutputModule0.Channels)
    {
        // Set the channel's voltage as a sine function of `t`
        channel.Voltage = (float)(5.0 * Math.Sin(t * 2 * Math.PI * (channel.Address + 1) / 4) + 5.0);
    }
 
    cfheader0.Sync();
}

DigitalInputModule

The DigitalInputModule class is used to automate the CFDI-16B CFNET module. It has 16 channels, each of which can be independently read.

After a successful Sync, each channel's state can be examined by reading its State property. The state of all channels can also be collectively read through the module's State property.

var cfheader0 = Cfheader.Instances[0];
var digitalInputModule0 = cfheader0.DigitalInputModules[0];
 
cfheader0.Open();
 
// Clear console
Console.CursorVisible = false;
Console.Clear();
 
while(true)
{
    // Get the latest state
    cfheader0.Sync();
 
    // Overwrite last printed results
    Console.SetCursorPosition(0, 0);
 
    // Print label
    Console.ForegroundColor = ConsoleColor.White;
    Console.Write("Digital Inputs:   ");
 
    // Print the state of each channel
    foreach (var channel in digitalInputModule0.Channels)
    {
        Console.ForegroundColor = channel.State ? ConsoleColor.Green : ConsoleColor.Red;
        var state = channel.State ? "ON" : "OFF";
        Console.Write($"{state,-6}");
    }
}

The thread that calls Sync will notify an application of any changes to a channel's state via the channel's StateChanged event and the module's StateChanged event.

var cfheader0 = Cfheader.Instances[0];
var digitalInputModule0 = cfheader0.DigitalInputModules[0];
 
foreach (var channel in digitalInputModule0.Channels)
{
    channel.StateChanged += c =>
    {
        var state = channel.State ? "ON" : "OFF";
        Console.WriteLine($"Channel {c.Address} changed: {state}");
    };
}
 
cfheader0.Open();
 
while (true)
{
    cfheader0.Sync();
}

DigitalOutputModule

The DigitalOutputModule class is used to automate the CFDO-16N or CFDO-8R CFNET modules. It has 16 channels, each of which can be independently assigned a digital output. Only the first 8 channels are applicable to the CFDO-8R.

The state of each channel can be individually assigned through the channel's State property or collectively through the module's State property.

var cfheader0 = Cfheader.Instances[0];
var digitalOutputModule0 = cfheader0.DigitalOutputModules[0];
 
cfheader0.Open();
 
while (true)
{
    // Blink each output in increasing order
    foreach (var channel in digitalOutputModule0.Channels)
    {
        channel.Blink();
    }
 
    // Blink each output in decreasing order
    foreach (var channel in digitalOutputModule0.Channels.Reverse())
    {
        channel.Blink();
    }
}
 
static class Extensions
{
    public static void Blink(this DigitalOutputModule.Channel channel)
    {
        // Toggle the channel's state
        channel.Toggle();
 
        // Delay for 50ms
        Thread.Sleep(50);
 
        // Toggle the channel's state again
        channel.Toggle();
    }
 
    public static void Toggle(this DigitalOutputModule.Channel channel)
    {
        // Toggle state
        channel.State = !channel.State;
 
        // Write the state to the module
        channel.Module.Header.Sync();
    }
}

CFHEADER - USB Interface to CFNET IO Modules