Trends¶
For data faster than polling can give, a trend has the controller itself sample tags at a fixed period (down to 1 ms) into its trend buffer, the trend object Studio 5000 uses. The driver uploads the samples in batches on a connection of its own, each with the controller's timestamp, so no sample depends on network timing.
- A pen is any atomic tag, member or array element of up to 32 bits (BOOL, SINT, INT, DINT, USINT, UINT, UDINT,
REAL) or a bit (
Tag.3). 64-bit types (LINT, LREAL) can't be trended. period_msis the sample period, 1 ms or more, in whole microseconds (default 10 ms).capacity(default 4096) bounds the samples waiting to be read. When it's full the oldest are dropped and counted as dropped.duration(seconds) stops the trend by itself; without it the trend runs until closed.- The controller deletes the trend when it's closed or when its connection drops.
Each sample has the controller time (timestamp_us, microseconds since 1970 UTC), the time since the trend started
(elapsed_us) and one value per pen.
import time
from ciplink import LogixDriver
with LogixDriver("192.0.2.10") as plc:
with plc.trend("Motor.Speed", "Tank.Level", "Motor.Running", period_ms=1, duration=5) as trend:
while trend.running:
time.sleep(0.1)
for sample in trend.read(): # TrendSample(timestamp_us, elapsed_us, values)
print(sample.timestamp, sample.values)
trend.pens, received_samples, dropped_samples and error describe the trend; leaving the with block
(or trend.close()) stops it.
use ciplink::logix::driver::{LogixDriver, LogixOptions};
use ciplink::logix::trend::TrendOptions;
use std::time::Duration;
let plc = LogixDriver::new("192.0.2.10", LogixOptions::default())?;
plc.open()?;
let trend = plc.trend(
&["Motor.Speed", "Tank.Level", "Motor.Running"],
TrendOptions { period: Duration::from_millis(1), duration: Some(Duration::from_secs(5)), ..TrendOptions::default() },
)?;
while trend.running() {
std::thread::sleep(Duration::from_millis(100));
for sample in trend.read()? {
println!("{} {:?}", sample.timestamp_us, sample.values);
}
}
trend.close()?;
using Ciplink;
using var plc = new LogixDriver("192.0.2.10");
plc.Open();
using Trend trend = plc.Trend(new[] { "Motor.Speed", "Tank.Level", "Motor.Running" },
new TrendOptions { PeriodMs = 1, Duration = TimeSpan.FromSeconds(5) });
while (trend.Running)
{
Thread.Sleep(100);
foreach (TrendSample sample in trend.Read())
Console.WriteLine($"{sample.Timestamp:O} {string.Join(", ", sample.Values)}");
}
trend.Close();
#include <chrono>
#include <iostream>
#include <thread>
#include "ciplink.hpp"
ciplink::Logix plc("192.0.2.10");
plc.open();
ciplink::Trend trend = plc.trend({"Motor.Speed", "Tank.Level", "Motor.Running"}, /*period_ms*/ 1,
/*capacity*/ 4096, /*duration*/ 5.0);
while (trend.running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
for (const ciplink::TrendSample& s : trend.read())
std::cout << s.timestamp_us << " " << ciplink::to_json(ciplink::Value(s.values)) << "\n";
}
trend.close();
#include <stdio.h>
#include <string.h>
#include <windows.h> /* Sleep; on Linux and macOS use <unistd.h> and usleep */
#include "ciplink.h"
/* plc: an open Logix driver */
const char *pens[] = {"Motor.Speed", "Tank.Level", "Motor.Running"};
CiplinkTrend *trend = NULL;
if (ciplink_trend_start(plc, pens, 3, "{\"period_ms\": 1, \"duration\": 5}", &trend) == CIPLINK_STATUS_OK) {
for (;;) {
char *json = NULL;
if (ciplink_trend_read(trend, &json) != CIPLINK_STATUS_OK)
break; /* the trend failed and every sample was read */
printf("%s\n", json); /* [{"timestamp_us": ..., "elapsed_us": ..., "values": [...]}, ...] */
ciplink_string_free(json);
char *info = NULL;
ciplink_trend_info(trend, &info);
bool running = strstr(info, "\"running\":true") != NULL;
ciplink_string_free(info);
if (!running)
break;
Sleep(100); /* usleep(100000) on Linux and macOS */
}
ciplink_trend_close(trend);
ciplink_trend_free(trend);
}
A trend needs a Logix controller that supports the trend object (ControlLogix and CompactLogix). The simulator simulates it too.