====== Automating IO Modules ======
===== Obtaining an Instance of a CFHEADER Module =====
The root of the API is the static [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Instances.html#ComfileTech_Cfnet_Cfheader_Cfheader_Instances|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 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Instances.html#ComfileTech_Cfnet_Cfheader_Cfheader_Instances|Cfheader.Instances]] collection is ordered by address, so to obtain a [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.html|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 [[https://learn.microsoft.com/en-us/dotnet/csharp/linq/|Linq]] syntax to find instances by the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Address.html|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 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Open.html#ComfileTech_Cfnet_Cfheader_Cfheader_Open|Open]]ed for USB communication.
Once opened, USB communication with the CFHEADER module, and consequently I²C communication with the IO modules, is performed using the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Sync.html|Sync]] method. The [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Sync.html|Sync]] method effectively writes the current state of the output modules and reads the current state of the input modules. See [[cfnet:cfheader:mode_of_operation:index|mode of operation]] for more information.
To end communication with a ''Cfheader'' instance, call the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Close.html|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();
}
}
}
{{ :cfnet:cfheader:automating_io_modules:multiple_cfheaders.mp4?900x506 }}
===== Obtaining an Instance of an IO Module =====
An instance of each IO module connected to a ''Cfheader'' can be obtained though the ''Cfheader'''s [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.AnalogInputModules.html#ComfileTech_Cfnet_Cfheader_Cfheader_AnalogInputModules|AnalogInputModules]], [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.AnalogOutputModules.html|AnalogOutputModules]], [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.DigitalInputModules.html|DigitalInputModules]], and [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.DigitalOutputModules.html|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:
* [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.IIOModule.Header.html#ComfileTech_Cfnet_Cfheader_IIOModule_Header|Header]] property to identify the CFHEADER module that the IO module is associated with.
* [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.IIOModule.Address.html|Address]] property to uniquely identify the module from other modules of the same type.
* [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.IIOModule.I2cStatus.html|I2cStatus]] property to indicate whether the last I²C communication with the module succeeded or not. This an also be used to determine if a specific module is connected to a specific CFHEADER module.
* [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.IIOModule.AcknowledgeI2cFailure.html|AcknowledgeI2cFailure]] to acknowledge an I²C communication failure and attempt I2c communication again on the next call to [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Sync.html#ComfileTech_Cfnet_Cfheader_Cfheader_Sync|Sync]]
* [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.IIOModule.I2cFailed.html|I2cFailed]] event to notify the application any time I²C communication with an IO module fails. Such events are especially useful in concert with [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.BackgroundSync.html|BackgroundSync]].
For more information on how to utilize the ''I2cStatus'', ''AcknowledgeI2cFailure'', and ''I2cFailed'' members, see the error handling explanations in the [[cfnet:cfheader:mode_of_operation:index|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)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 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.IIOChannel.html|channel]]s. The collection of channels will be ordered by the channel's [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.IIOChannel.Address.html#ComfileTech_Cfnet_Cfheader_IIOChannel_Address|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 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.IIOChannel.Module.html|Module]] property.
===== AnalogInputModule =====
The [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.html|AnalogInputModule]] class is used to automate a [[modularfaduino:cfadca4l|CFADC-A4L]] module. The [[modularfaduino:cfadca4l|CFADC-A4L]] has 4 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channels.html#ComfileTech_Cfnet_Cfheader_AnalogInputModule_Channels|channels]], but only one [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.html|channel]] can perform an analog to digital conversion at a time.
Each channel, on its turn, can perform multiple conversions, according to its [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.NumberOfConversions.html#ComfileTech_Cfnet_Cfheader_AnalogInputModule_Channel_NumberOfConversions|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 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.NumberOfConversions.html#ComfileTech_Cfnet_Cfheader_AnalogInputModule_Channel_NumberOfConversions|NumberOfConversions]] property to 0 so that it will be skipped when advancing to the next channel. When [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.NumberOfConversions.html#ComfileTech_Cfnet_Cfheader_AnalogInputModule_Channel_NumberOfConversions|NumberOfConversions]] is 0, the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.IsEnabled.html#ComfileTech_Cfnet_Cfheader_AnalogInputModule_Channel_IsEnabled|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, [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.RawValue.html|RawValue]], a [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.Voltage.html|Voltage]], or a [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.Current.html|Current]]. [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.Voltage.html|Voltage]] and [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.Current.html|Current]] properties are calculated from the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.RawValue.html|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 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.NumberOfConversions.html#ComfileTech_Cfnet_Cfheader_AnalogInputModule_Channel_NumberOfConversions|NumberOfConversions]] of a channel are completed, and read via [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Sync.html#ComfileTech_Cfnet_Cfheader_Cfheader_Sync|Sync]], the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.ConversionCompleted.html|ConversionCompleted]] event will be fired. This event will be fired regardless of whether the channel's state (i.e. [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.RawValue.html|RawValue]]) changes. Use the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.RawValueChanged.html|RawValueChanged]] event to be notified when the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.RawValue.html|RawValue]], and consequently the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.Voltage.html|Voltage]] and [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogInputModule.Channel.Current.html|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 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogOutputModule.html|AnalogOutputModule]] class is used to automate the [[modularfaduino:cfdac2v|CFDAC-2V]] CFNET module. It has 2 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogOutputModule.Channels.html#ComfileTech_Cfnet_Cfheader_AnalogOutputModule_Channels|channels]], each of which can be independently assigned an analog voltage output.
The voltage of each [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogOutputModule.Channel.html|channel]] can be set as a raw digital value, via the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogOutputModule.Channel.RawValue.html#ComfileTech_Cfnet_Cfheader_AnalogOutputModule_Channel_RawValue|RawValue]] property, or as an explicit voltage via the [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.AnalogOutputModule.Channel.Voltage.html|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();
}
{{ :cfnet:cfheader:automating_io_modules:cfdac_sine.mp4?900x620 |}}
===== DigitalInputModule =====
The [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalInputModule.html|DigitalInputModule]] class is used to automate the [[modularfaduino:cfdi16b|CFDI-16B]] CFNET module. It has 16 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalInputModule.Channels.html#ComfileTech_Cfnet_Cfheader_DigitalInputModule_Channels|channels]], each of which can be independently read.
After a successful [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Sync.html#ComfileTech_Cfnet_Cfheader_Cfheader_Sync|Sync]], each [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalInputModule.Channel.html|channel]]'s state can be examined by reading its [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalInputModule.Channel.State.html#ComfileTech_Cfnet_Cfheader_DigitalInputModule_Channel_State|State]] property. The state of all channels can also be collectively read through the module's [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalInputModule.State.html#ComfileTech_Cfnet_Cfheader_DigitalInputModule_State|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}");
}
}
{{ :cfnet:cfheader:automating_io_modules:cfdi_demo.mp4?900x500 }}
The thread that calls [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.Cfheader.Sync.html#ComfileTech_Cfnet_Cfheader_Cfheader_Sync|Sync]] will notify an application of any changes to a channel's state via the channel's [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalInputModule.Channel.StateChanged.html|StateChanged]] event and the module's [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalInputModule.StateChanged.html|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 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalOutputModule.html|DigitalOutputModule]] class is used to automate the [[modularfaduino:cfdo16n|CFDO-16N]] or [[modularfaduino:cfdo8r|CFDO-8R]] CFNET modules. It has 16 [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalOutputModule.Channels.html#ComfileTech_Cfnet_Cfheader_DigitalOutputModule_Channels|channels]], each of which can be independently assigned a digital output. Only the first 8 channels are applicable to the [[modularfaduino:cfdo8r|CFDO-8R]].
The state of each [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalOutputModule.Channel.html|channel]] can be individually assigned through the channel's [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalOutputModule.Channel.State.html#ComfileTech_Cfnet_Cfheader_DigitalOutputModule_Channel_State|State]] property or collectively through the module's [[https://api.comfiletech.com/csharp/api/ComfileTech.Cfnet.Cfheader.DigitalOutputModule.State.html#ComfileTech_Cfnet_Cfheader_DigitalOutputModule_State|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();
}
}
{{ :cfnet:cfheader:automating_io_modules:kitt.mp4?900x506 }}
[[..:index|CFHEADER - USB Interface to CFNET IO Modules]]