|
8 | 8 | # included in the file licenses/APL.txt. |
9 | 9 |
|
10 | 10 | import synnax as sy |
| 11 | +from synnax import ni |
11 | 12 |
|
12 | 13 | """ |
13 | | -This examples demonstrates how to configure and start an Analog Read Task on a National |
14 | | -Instruments USB-6289 device. |
| 14 | +This example demonstrates how to configure and start a multi-device Analog Read Task |
| 15 | +on National Instruments hardware. The task reads voltage from one device, current from |
| 16 | +another, and temperature (thermocouple) from a third — all in a single task. |
15 | 17 |
|
16 | 18 | To run this example, you'll need to have your Synnax cluster properly configured to |
17 | | -detect National Instruments devices: https://docs.synnaxlabs.com/reference/driver/ni/get-started |
| 19 | +detect National Instruments devices: |
| 20 | +https://docs.synnaxlabs.com/reference/driver/ni/get-started |
18 | 21 |
|
19 | | -You'll also need to have either a physical USB-6289 device or create a simulated device |
20 | | -via the NI-MAX software. |
| 22 | +You'll also need physical NI devices or simulated devices via NI-MAX. |
21 | 23 | """ |
22 | 24 |
|
23 | 25 | # We've logged in via the CLI, so there's no need to provide credentials here. |
24 | 26 | # See https://docs.synnaxlabs.com/reference/client/quick-start for more information. |
25 | 27 | client = sy.Synnax() |
26 | 28 |
|
27 | | -dev = client.devices.retrieve(model="USB-6289") |
| 29 | +# Retrieve devices — each module is a separate device in Synnax. |
| 30 | +v_dev = client.devices.retrieve(name="Mod1_Voltage") |
| 31 | +c_dev = client.devices.retrieve(name="Mod2_Current") |
| 32 | +tc_dev = client.devices.retrieve(name="Mod1_TC") |
28 | 33 |
|
29 | | -# Create an index channel that will be used to store the timestamps |
30 | | -# for the analog read data. |
| 34 | +# Create an index channel for timestamps. |
31 | 35 | ai_time = client.channels.create( |
32 | 36 | name="ai_time", |
33 | 37 | is_index=True, |
34 | 38 | data_type=sy.DataType.TIMESTAMP, |
35 | 39 | retrieve_if_name_exists=True, |
36 | 40 | ) |
37 | 41 |
|
38 | | -# Create two synnax channels that will be used to store the input data. Notice |
39 | | -# how these channels aren't specifically bound to the device. You'll do that in a |
40 | | -# later step when you create the Analog Read Task. |
41 | | -ai_0 = client.channels.create( |
42 | | - name="ai_0", |
43 | | - # Pass in the index key here to associate the channel with the index channel. |
| 42 | +# Create data channels — one per physical input. |
| 43 | +voltage_chan = client.channels.create( |
| 44 | + name="voltage_chan", |
44 | 45 | index=ai_time.key, |
45 | 46 | data_type=sy.DataType.FLOAT32, |
46 | 47 | retrieve_if_name_exists=True, |
47 | 48 | ) |
48 | | -ai_1 = client.channels.create( |
49 | | - name="ai_1", |
50 | | - # Pass in the index key here to associate the channel with the index channel. |
| 49 | +current_chan = client.channels.create( |
| 50 | + name="current_chan", |
| 51 | + index=ai_time.key, |
| 52 | + data_type=sy.DataType.FLOAT32, |
| 53 | + retrieve_if_name_exists=True, |
| 54 | +) |
| 55 | +temp_chan = client.channels.create( |
| 56 | + name="temp_chan", |
51 | 57 | index=ai_time.key, |
52 | 58 | data_type=sy.DataType.FLOAT32, |
53 | 59 | retrieve_if_name_exists=True, |
54 | 60 | ) |
55 | 61 |
|
56 | | -# Instantiate the task. A task is a background process that can be used to acquire data |
57 | | -# from, or write commands to a device. Tasks are the primary method for interacting with |
58 | | -# hardware in Synnax. |
59 | | -tsk = sy.ni.AnalogReadTask( |
60 | | - # A name to find and monitor the task via the Synnax Console. |
61 | | - name="Basic Analog Read", |
62 | | - # The rate at which the task will sample data from the device. |
| 62 | +# Create and configure the task. Each channel specifies its own device, allowing |
| 63 | +# a single task to read from multiple NI modules simultaneously. |
| 64 | +task = ni.AnalogReadTask( |
| 65 | + name="Analog Read Task", |
63 | 66 | sample_rate=sy.Rate.HZ * 100, |
64 | | - # The rate at which data will be streamed from the device into Synnax. |
65 | | - # Since we're sampling at 100hz and streaming at 25hz, we'll get 4 samples at a |
66 | | - # time. |
67 | | - # It's generally best to keep the stream rate under 100Hz. |
68 | 67 | stream_rate=sy.Rate.HZ * 25, |
69 | | - # Whether to save data acquired by the task to disk. If set to False, the data |
70 | | - # will be streamed into Synnax for real-time consumption but not saved to disk. |
71 | 68 | data_saving=True, |
72 | | - # The list of physical channels we'd like to acquire data from. |
73 | 69 | channels=[ |
74 | | - sy.ni.AIVoltageChan( |
75 | | - # The key of the Synnax channel we're acquiring data for. |
76 | | - channel=ai_0.key, |
77 | | - # The key of the device on which the channel is located. |
78 | | - device=dev.key, |
79 | | - # The port on the device the channel is connected to. |
| 70 | + ni.AIVoltageChan( |
| 71 | + channel=voltage_chan.key, |
| 72 | + device=v_dev.key, |
80 | 73 | port=0, |
81 | | - # A custom scale to apply to the data. This is optional, but can be useful |
82 | | - # for converting raw data into meaningful units. |
83 | | - custom_scale=sy.ni.LinScale( |
84 | | - slope=2e4, |
85 | | - y_intercept=50, |
86 | | - pre_scaled_units="Volts", |
87 | | - scaled_units="Volts", |
88 | | - ), |
| 74 | + min_val=-10.0, |
| 75 | + max_val=10.0, |
| 76 | + terminal_config="Diff", |
89 | 77 | ), |
90 | | - sy.ni.AIVoltageChan( |
91 | | - channel=ai_1.key, |
92 | | - device=dev.key, |
93 | | - port=1, |
94 | | - custom_scale=sy.ni.MapScale( |
95 | | - pre_scaled_min=0, |
96 | | - pre_scaled_max=10, |
97 | | - scaled_min=0, |
98 | | - scaled_max=200, |
99 | | - pre_scaled_units="Volts", |
100 | | - scaled_units="Degrees", |
101 | | - ), |
| 78 | + ni.AICurrentChan( |
| 79 | + channel=current_chan.key, |
| 80 | + device=c_dev.key, |
| 81 | + port=0, |
| 82 | + min_val=0.004, |
| 83 | + max_val=0.02, |
| 84 | + ), |
| 85 | + ni.AIThermoChan( |
| 86 | + channel=temp_chan.key, |
| 87 | + device=tc_dev.key, |
| 88 | + port=0, |
| 89 | + units="DegC", |
| 90 | + thermocouple_type="J", |
| 91 | + cjc_source="BuiltIn", |
102 | 92 | ), |
103 | 93 | ], |
104 | 94 | ) |
105 | 95 |
|
106 | 96 | # This will create the task in Synnax and wait for the driver to validate that the |
107 | 97 | # configuration is correct. |
108 | | -client.tasks.configure(tsk) |
| 98 | +client.tasks.configure(task) |
109 | 99 |
|
110 | 100 | # Stream 100 reads, which will accumulate a total of 400 samples |
111 | 101 | # for each channel over a period of 4 seconds. |
112 | 102 | total_reads = 100 |
113 | 103 |
|
114 | | -# Create a synnax frame to accumulate data. |
115 | 104 | frame = sy.Frame() |
116 | 105 |
|
117 | | -# Start the task under a context manager, which ensures the task gets stopped |
118 | | -# when the block exits. |
119 | | -with tsk.run(): |
120 | | - # Open a streamer on the analog input channels. |
121 | | - with client.open_streamer(["ai_0", "ai_1"]) as streamer: |
| 106 | +with task.run(): |
| 107 | + with client.open_streamer( |
| 108 | + ["voltage_chan", "current_chan", "temp_chan"] |
| 109 | + ) as streamer: |
122 | 110 | for i in range(total_reads): |
123 | 111 | frame.append(streamer.read()) |
124 | 112 |
|
125 | | -# Save the data to a CSV file. |
126 | 113 | frame.to_df().to_csv("analog_read_result.csv") |
0 commit comments