Description
I am fetching real-time data via WebSockets from ThingsBoard and have fixed time window (10s), the timewindow is not depended to data points due to live it scrolling from right to left continuously without any interruption , if any data comes it should show in time window if not it will not affect the time window and time window will runs as usual without data.
ISSUE I AM FACING
The background time window is working correctly but while having the data ( as I am geeting the 200 of data in 1 chunk and it is also the time between the 2 chunks is not fixed so may be in 10sec i am getting 2 chunks * 200 data = 400 data or sometime i am getting 1 chunk * 200 data = 200 data ), So while the first chunk of data is coming looks good but while next chunk is coming and the 1st chunk is moving towards Y -axis while touching the Y-axis it looks like the 1st chunk is throwing the lines of graph to the 2nd chunk of graph to feed it (Looks like 2nd chunk is Pulling back the graphs from the 1st chunk which is actually not in window, I don't know may be the issue is in UI or backend)
Below is the actual flutter code implementation where i am getting the values and the issue i have explained above i captured a video of that, Please see the video and my code and review it where is the actuall issue Please suggest me I am waiting for the reply from you guys.
screen-20250325-201100.mp4
CODE
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:smarty/features/login/thingsboard_service.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:intl/intl.dart';
import 'package:web_socket_channel/io.dart';
class StatsHomeScreen extends StatefulWidget {
@OverRide
_StatsHomeScreenState createState() => _StatsHomeScreenState();
}
class _StatsHomeScreenState extends State {
List dataPoints = [];
late Timer timer;
int visibleDuration = 10; // Keep last 10 seconds visible
DateTime startTime = DateTime.now(); // Base time to move timeline
final ThingsBoardAuthService authService = ThingsBoardAuthService();
late IOWebSocketChannel channel; // WebSocket connection
@OverRide
void initState() {
super.initState();
// Initialize WebSocket connection
connectWebSocket();
// Timer for moving timeline every 500ms
timer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
setState(() {
// Shift the start time forward to create a scrolling effect
startTime = startTime.add(const Duration(milliseconds: 500));
// Remove old data beyond visible range
// dataPoints.removeWhere((point) =>
// point.time.isBefore(startTime.subtract(Duration(seconds: visibleDuration)))
// );
dataPoints.removeWhere((point) =>
DateTime.now().difference(point.time).inSeconds > visibleDuration);
});
});
}
Future connectWebSocket() async {
print("🔌 Connecting to WebSocket...");
String? accesstoken = await authService.getValidToken();
channel = IOWebSocketChannel.connect(
"ws://localhost/api/ws/plugins/telemetry?token=$accesstoken",
);
// Send subscription request to ThingsBoard
channel.sink.add(jsonEncode({
"tsSubCmds": [
{
"entityType": "DEVICE",
"entityId": "707970f0-ef6f-11ef-b7bb-93f364633065",
"scope": "LATEST_TELEMETRY",
"cmdId": 1
}
],
"historyCmds": [],
"attrSubCmds": []
}));
// Listen for new telemetry data
channel.stream.listen(
(message) {
// print("📩 Received WebSocket Message: $message");
try {
var data = jsonDecode(message);
var adcData = data["data"]["ADC Values CH_0"];
if (adcData != null && adcData is List && adcData.isNotEmpty) {
var timestamp = int.parse(
adcData[0][0].toString()); // First item in list is timestamp
var value = double.parse(
adcData[0][1].toString()); // Second item is the value (string)
setState(() {
dataPoints.add(LiveData(
DateTime.fromMillisecondsSinceEpoch(timestamp), value));
});
} else {
print("⚠️ No valid ADC Values CH_0 data found.");
}
} catch (e) {
print("❌ Error parsing WebSocket data: $e");
}
},
onDone: () {
print("❌ WebSocket Connection Closed.");
},
onError: (error) {
print("🚨 WebSocket Error: $error");
},
);
}
@OverRide
void dispose() {
timer.cancel();
channel.sink.close(); // Close WebSocket connection
print("🔌 WebSocket Disconnected.");
super.dispose();
}
@OverRide
Widget build(BuildContext context) {
DateTime minTime = startTime.subtract(Duration(seconds: visibleDuration));
DateTime maxTime = startTime;
return Scaffold(
appBar: AppBar(title: const Text("Live Timeline Chart")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: SfCartesianChart(
zoomPanBehavior: ZoomPanBehavior(
enablePinching: true,
zoomMode: ZoomMode.xy,
enablePanning: true,
),
trackballBehavior: TrackballBehavior(
enable: true,
activationMode: ActivationMode.singleTap,
),
primaryXAxis: DateTimeAxis(
minimum: minTime,
maximum: maxTime,
intervalType: DateTimeIntervalType.seconds,
interval: 1, // Keep 1-second grid lines
dateFormat: DateFormat.Hms(),
// majorGridLines: const MajorGridLines(width: 1, color: Colors.grey),
),
primaryYAxis: NumericAxis(
isVisible: dataPoints.isNotEmpty, // Hide Y-axis if no data
),
series: <LineSeries<LiveData, DateTime>>[
LineSeries<LiveData, DateTime>(
dataSource: dataPoints,
xValueMapper: (LiveData data, _) => data.time,
yValueMapper: (LiveData data, _) => data.value,
color: Colors.blue,
width: 3,
),
],
),
),
);
}
}
// Data model for time-series points
class LiveData {
final DateTime time;
final double? value;
LiveData(this.time, this.value);
}