Getting Started
Tag API
Overview
The TagManager
class is the primary entry point of the Tag API.
When constructing a TagManager
, you can pass an
HttpConfiguration
(like one retrieved from the
HttpConfigurationManager
), or let TagManager
use the
default connection. The default connection depends on your SystemLink Client
settings.
With a TagManager
object, you can:
Query, create, modify, and delete tags from the server
Use
open()
to get a tag’sTagData
from the server when you know the tag’spath
.Use
query()
to get acollection
ofTagData
objects based on the tags’paths
,keywords
, and/orproperties
.Use
refresh()
to update a list ofTagData
objects with fresh metadata from the server.Use
update()
to modify the server metadata for a list of tags, using eitherTagData
objects to overwrite the server’s tag data orTagDataUpdate
objects to selectively update specific fields.Use
delete()
to delete one or more tags from the server.
Read and write tag values
Use
read()
to get a tag’s current value. Via method parameters, you can also request the timestamp indicating when that value was last written and/or the aggregate data stored for the tag (if the tag’scollect_aggregates
attribute is enabled on the server).Use
create_writer()
to get aBufferedTagWriter
that will buffer a set of writes, and automatically send the writes when a givenbuffer_size
is reached or whenmax_buffer_time
has elapsed (or whensend_buffered_writes()
is called).
Get a
TagSelection
that can help perform several of the above operations on several tags at onceUse
TagManager.create_selection()
if you already have a list ofTagData
objects that you want to perform a set of operations on.Use
TagManager.open_selection()
if you just have a list ofpaths
– optionally including glob-style wildcards! – with which to create the selection.
If you have a TagSelection
, you can use it to create
a TagSubscription
that will
trigger a tag_changed
event any time one of the tags’
values is changed.
Examples
Read and write individual tags
1from datetime import timedelta
2
3from nisystemlink.clients.tag import DataType, TagManager
4
5mgr = TagManager()
6tag = mgr.open("MyTags.Example Tag", DataType.DOUBLE, create=True)
7
8with mgr.create_writer(buffer_size=10, max_buffer_time=timedelta(seconds=3)) as writer:
9 writer.write(tag.path, tag.data_type, 3.5)
10# Note: Exiting the "with" block automatically calls writer.send_buffered_writes()
11
12read_result = mgr.read(tag.path)
13assert read_result is not None
14assert read_result.value == 3.5
Subscribe to tag changes
1from contextlib import ExitStack
2from time import sleep
3
4from nisystemlink.clients.tag import DataType, TagData, TagManager, TagValueReader
5
6SIMULATE_EXTERNAL_TAG_CHANGES = True
7
8
9def on_tag_changed(tag: TagData, reader: TagValueReader) -> None:
10 """Callback for tag_changed events."""
11 path = tag.path
12 data_type = tag.data_type.name
13
14 if reader is not None:
15 read_result = reader.read()
16 # A read_result of None means that the tag has no value, but it *must*
17 # have a value, because we got a tag_changed event!
18 assert read_result is not None
19 value = read_result.value
20 else:
21 value = "???" # tag has unknown data type
22
23 print(f'Tag changed: "{path}" = {value} ({data_type})')
24
25
26mgr = TagManager()
27if SIMULATE_EXTERNAL_TAG_CHANGES:
28 mgr.open("MyTags.Example Tag", DataType.DOUBLE, create=True)
29 writer = mgr.create_writer(buffer_size=1)
30
31with ExitStack() as stack:
32 # Notes:
33 # 1. The tags are assumed to already exist before this example is run, but
34 # setting SIMULATE_EXTERNAL_TAG_CHANGES to True will ensure there is one.
35 # 2. Any tags that get added later will NOT automatically appear in the
36 # selection just because the path matches the wildcard used below; you
37 # must call one of the selection's refresh methods to update the tag list
38 # from the server. But even if you do that:
39 # 3. The subscription will only contain the tags that were in the selection
40 # when the subscription was created. If you want the subscription to add
41 # new tags that were added to the selection, you must recreate it.
42 paths = ["MyTags.*"]
43 selection = stack.enter_context(mgr.open_selection(paths))
44 if not selection.metadata:
45 print(f"Found no tags that match {paths}")
46 else:
47 print("Matching tags:")
48 for path in selection.metadata.keys():
49 print(f" - {path}")
50 print()
51
52 subscription = stack.enter_context(selection.create_subscription())
53 subscription.tag_changed += on_tag_changed
54
55 # Wait forever, until a KeyboardInterrupt (Ctrl+C)
56 print("Watching for tag changes; hit Ctrl+C to stop")
57 try:
58 i = 0
59 while True:
60 sleep(1)
61 if SIMULATE_EXTERNAL_TAG_CHANGES:
62 writer.write("MyTags.Example Tag", DataType.DOUBLE, i)
63 i += 1
64 except KeyboardInterrupt:
65 pass
DataFrame API
Overview
The DataFrameClient
class is the primary entry point of the DataFrame API.
When constructing a DataFrameClient
, you can pass an
HttpConfiguration
(like one retrieved from the
HttpConfigurationManager
), or let DataFrameClient
use the
default connection. The default connection depends on your environment.
With a DataFrameClient
object, you can:
Create and delete data tables.
Modify table metadata and query for tables by their metadata.
Append rows of data to a table, query for rows of data from a table, and decimate table data.
Export table data in a comma-separated values (CSV) format.
Examples
Create and write data to a table
1import random
2from datetime import datetime
3
4from nisystemlink.clients.dataframe import DataFrameClient
5from nisystemlink.clients.dataframe.models import (
6 AppendTableDataRequest,
7 Column,
8 ColumnType,
9 CreateTableRequest,
10 DataFrame,
11 DataType,
12)
13
14client = DataFrameClient()
15
16# Create table
17table_id = client.create_table(
18 CreateTableRequest(
19 name="Example Table",
20 columns=[
21 Column(name="ix", data_type=DataType.Int32, column_type=ColumnType.Index),
22 Column(name="Float_Column", data_type=DataType.Float32),
23 Column(name="Timestamp_Column", data_type=DataType.Timestamp),
24 ],
25 )
26)
27
28# Generate example data
29frame = DataFrame(
30 data=[[i, random.random(), datetime.now().isoformat()] for i in range(100)]
31)
32
33# Write example data to table
34client.append_table_data(
35 table_id, data=AppendTableDataRequest(frame=frame, endOfData=True)
36)
Query and read data from a table
1from nisystemlink.clients.dataframe import DataFrameClient
2from nisystemlink.clients.dataframe.models import (
3 DecimationMethod,
4 DecimationOptions,
5 QueryDecimatedDataRequest,
6)
7
8client = DataFrameClient()
9
10# List a table
11response = client.list_tables(take=1)
12table = response.tables[0]
13
14# Get table metadata by table id
15client.get_table_metadata(table.id)
16
17# Query decimated table data
18request = QueryDecimatedDataRequest(
19 decimation=DecimationOptions(
20 x_column="index",
21 y_columns=["col1"],
22 intervals=1,
23 method=DecimationMethod.MaxMin,
24 )
25)
26client.query_decimated_data(table.id, request)
Export data from a table
1from shutil import copyfileobj
2
3from nisystemlink.clients.dataframe import DataFrameClient
4from nisystemlink.clients.dataframe.models import (
5 ColumnFilter,
6 ColumnOrderBy,
7 ExportFormat,
8 ExportTableDataRequest,
9 FilterOperation,
10)
11
12client = DataFrameClient()
13
14# List a table
15response = client.list_tables(take=1)
16table = response.tables[0]
17
18# Export table data with query options
19request = ExportTableDataRequest(
20 columns=["col1"],
21 order_by=[ColumnOrderBy(column="col2", descending=True)],
22 filters=[
23 ColumnFilter(column="col1", operation=FilterOperation.NotEquals, value="0")
24 ],
25 response_format=ExportFormat.CSV,
26)
27
28data = client.export_table_data(id=table.id, query=request)
29
30# Write the export data to a file
31with open(f"{table.name}.csv", "wb") as f:
32 copyfileobj(data, f)
33
34# Alternatively, load the export data into a pandas dataframe
35# import pandas as pd
36# df = pd.read_csv(data)
Spec API
Overview
The SpecClient
class is the primary entry point of the Specification Compliance API.
When constructing a SpecClient
, you can pass an
HttpConfiguration
(like one retrieved from the
HttpConfigurationManager
), or let SpecClient
use the
default connection. The default connection depends on your environment.
With a SpecClient
object, you can:
Create and delete specifications under a product.
Modify any fields of an existing specification
Query for specifications on any fields using DynamicLinq syntax.
Examples
Create and Query Specifications
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.spec import SpecClient
3from nisystemlink.clients.spec.models import (
4 Condition,
5 ConditionRange,
6 ConditionType,
7 CreateSpecificationsRequest,
8 NumericConditionValue,
9 QuerySpecificationsRequest,
10 SpecificationDefinition,
11 SpecificationLimit,
12 SpecificationType,
13)
14
15# Setup the server configuration to point to your instance of SystemLink Enterprise
16server_configuration = HttpConfiguration(
17 server_uri="https://yourserver.yourcompany.com",
18 api_key="YourAPIKeyGeneratedFromSystemLink",
19)
20client = SpecClient(configuration=server_configuration)
21
22# Create the spec requests
23product = "Amplifier"
24spec_requests = [
25 SpecificationDefinition(
26 product_id=product,
27 spec_id="spec1",
28 type=SpecificationType.PARAMETRIC,
29 category="Parametric Specs",
30 name="output voltage",
31 limit=SpecificationLimit(min=1.2, max=1.5),
32 unit="mV",
33 ),
34 SpecificationDefinition(
35 product_id=product,
36 spec_id="spec2",
37 type=SpecificationType.PARAMETRIC,
38 category="Parametric Specs",
39 name="input voltage",
40 limit=SpecificationLimit(min=0.02, max=0.15),
41 unit="mV",
42 conditions=[
43 Condition(
44 name="Temperature",
45 value=NumericConditionValue(
46 condition_type=ConditionType.NUMERIC,
47 range=[ConditionRange(min=-25, step=20, max=85)],
48 unit="C",
49 ),
50 ),
51 Condition(
52 name="Supply Voltage",
53 value=NumericConditionValue(
54 condition_type=ConditionType.NUMERIC,
55 discrete=[1.3, 1.5, 1.7],
56 unit="mV",
57 ),
58 ),
59 ],
60 ),
61 SpecificationDefinition(
62 product_id=product,
63 spec_id="spec3",
64 type=SpecificationType.FUNCTIONAL,
65 category="Noise Thresholds",
66 name="noise",
67 ),
68]
69
70# Create the specs on the server
71client.create_specs(CreateSpecificationsRequest(specs=spec_requests))
72
73# You can query specs based on any field using DynamicLinq syntax.
74# These are just some representative examples.
75
76response = client.query_specs(QuerySpecificationsRequest(product_ids=[product]))
77all_product_specs = response.specs
78
79# Query based on spec id
80response = client.query_specs(
81 QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec2"')
82)
83if response.specs:
84 spec2 = response.specs[0]
85
86# Query based on name
87response = client.query_specs(
88 QuerySpecificationsRequest(product_ids=[product], filter='name.Contains("voltage")')
89)
90voltage_specs = response.specs
91
92# Query based on Category
93response = client.query_specs(
94 QuerySpecificationsRequest(
95 product_ids=[product], filter='category == "Noise Thresholds"'
96 )
97)
98noise_category = response.specs
99print(noise_category)
Update and Delete Specifications
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.spec import SpecClient
3from nisystemlink.clients.spec.models import (
4 QuerySpecificationsRequest,
5 SpecificationDefinition,
6 SpecificationType,
7 UpdateSpecificationsRequest,
8)
9
10# Setup the server configuration to point to your instance of SystemLink Enterprise
11server_configuration = HttpConfiguration(
12 server_uri="https://yourserver.yourcompany.com",
13 api_key="YourAPIKeyGeneratedFromSystemLink",
14)
15client = SpecClient(configuration=server_configuration)
16
17# The query and delete examples assume you have created the specs from the query_specs example
18product = "Amplifier"
19
20# update spec1 to change the block to "modifiedBlock"
21# query the original spec
22response = client.query_specs(
23 QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec1"')
24)
25if response.specs:
26 original_spec1 = response.specs[0]
27 print(f"Original spec1 block: {original_spec1.block}")
28 print(f"Original spec1 version: {original_spec1.version}")
29
30# make the modifications
31modified_spec = SpecificationDefinition(
32 id=original_spec1.id,
33 product_id=original_spec1.product_id,
34 spec_id=original_spec1.spec_id,
35 type=SpecificationType.FUNCTIONAL,
36 keywords=["work", "reviewed"],
37 block="modifiedBlock",
38 version=original_spec1.version,
39 workspace=original_spec1.workspace,
40)
41update_response = client.update_specs(
42 specs=UpdateSpecificationsRequest(specs=[modified_spec])
43)
44if update_response and update_response.updated_specs:
45 print(f"New spec1 version: {update_response.updated_specs[0].version}")
46
47# query again to see new version
48response = client.query_specs(
49 QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec1"')
50)
51if response.specs:
52 original_spec1 = response.specs[0]
53 print(f"Modified spec1 block: {original_spec1.block}")
54
55# delete all the specs for the product
56# query all specs
57response = client.query_specs(QuerySpecificationsRequest(product_ids=[product]))
58if response.specs:
59 client.delete_specs(ids=[spec.id for spec in response.specs])