====== 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]]