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:
- Header property to identify the CFHEADER module that the IO module is associated with.
- Address property to uniquely identify the module from other modules of the same type.
- 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.
- AcknowledgeI2cFailure to acknowledge an I²C communication failure and attempt I2c communication again on the next call to Sync
- I2cFailed event to notify the application any time I²C communication with an IO module fails. Such events are especially useful in concert with BackgroundSync.
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(); } }
