/** jfDataLogger
*
* @author pquiring
*/
import java.io.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import javaforce.*;
import javaforce.controls.*;
public class App extends javax.swing.JFrame {
public static String version = "0.12";
public static int delays[] = new int[] {
25, 50, 100, 500, 1000, 3000, 5000, 10000, 30000, 60000, 300000
};
public static int ticks[] = new int[] {
40, 20, 10, 10, 10, 10, 10, 10, 10, 10, 10
};
/**
* Creates new form App
*/
public App() {
app = this;
initComponents();
JFImage icon = new JFImage();
icon.loadPNG(this.getClass().getClassLoader().getResourceAsStream("jfdatalogger.png"));
setIconImage(icon.getImage());
for(int a=0;a<delays.length;a++) {
speed.addItem(Integer.toString(delays[a]) + "ms");
}
table.setModel(tableModel);
list.setModel(listModel);
newProject();
setTitle("jfDataLogger/" + version);
JF.centerWindow(this);
setExtendedState(JFrame.MAXIMIZED_BOTH);
}
/**
* This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The content of this method is always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jToolBar1 = new javax.swing.JToolBar();
newProject = new javax.swing.JButton();
jSeparator1 = new javax.swing.JToolBar.Separator();
load = new javax.swing.JButton();
save = new javax.swing.JButton();
jSeparator2 = new javax.swing.JToolBar.Separator();
run = new javax.swing.JToggleButton();
clear = new javax.swing.JButton();
jSeparator3 = new javax.swing.JToolBar.Separator();
logBtn = new javax.swing.JButton();
jSeparator4 = new javax.swing.JToolBar.Separator();
jLabel1 = new javax.swing.JLabel();
speed = new javax.swing.JComboBox<>();
jSplitPane1 = new javax.swing.JSplitPane();
jPanel1 = new javax.swing.JPanel();
img = new javax.swing.JLabel() {
public void paint(Graphics g) {
if (logImage == null) return;
drawImage(g);
}
};
jScrollPane2 = new javax.swing.JScrollPane();
table = new javax.swing.JTable();
jPanel2 = new javax.swing.JPanel();
jToolBar2 = new javax.swing.JToolBar();
add = new javax.swing.JButton();
edit = new javax.swing.JButton();
delete = new javax.swing.JButton();
jScrollPane1 = new javax.swing.JScrollPane();
list = new javax.swing.JList<>();
setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
setTitle("jfDataLogger");
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
formWindowClosing(evt);
}
});
jToolBar1.setFloatable(false);
jToolBar1.setRollover(true);
newProject.setText("New");
newProject.setFocusable(false);
newProject.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
newProject.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
newProject.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
newProjectActionPerformed(evt);
}
});
jToolBar1.add(newProject);
jToolBar1.add(jSeparator1);
load.setText("Load");
load.setFocusable(false);
load.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
load.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
load.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
loadActionPerformed(evt);
}
});
jToolBar1.add(load);
save.setText("Save");
save.setFocusable(false);
save.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
save.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
save.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
saveActionPerformed(evt);
}
});
jToolBar1.add(save);
jToolBar1.add(jSeparator2);
run.setText("Run");
run.setFocusable(false);
run.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
run.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
run.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
runActionPerformed(evt);
}
});
jToolBar1.add(run);
clear.setText("Clear");
clear.setFocusable(false);
clear.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
clear.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
clear.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
clearActionPerformed(evt);
}
});
jToolBar1.add(clear);
jToolBar1.add(jSeparator3);
logBtn.setText("LogFile");
logBtn.setFocusable(false);
logBtn.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
logBtn.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
logBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
logBtnActionPerformed(evt);
}
});
jToolBar1.add(logBtn);
jToolBar1.add(jSeparator4);
jLabel1.setText("Speed");
jToolBar1.add(jLabel1);
speed.setMaximumSize(new java.awt.Dimension(100, 32767));
jToolBar1.add(speed);
jSplitPane1.setDividerLocation(250);
img.setMaximumSize(new java.awt.Dimension(32768, 100));
img.setMinimumSize(new java.awt.Dimension(0, 100));
table.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
{null, null, null, null},
{null, null, null, null},
{null, null, null, null},
{null, null, null, null}
},
new String [] {
"Title 1", "Title 2", "Title 3", "Title 4"
}
));
jScrollPane2.setViewportView(table);
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(img, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 792, Short.MAX_VALUE)
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 240, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(img, javax.swing.GroupLayout.PREFERRED_SIZE, 510, javax.swing.GroupLayout.PREFERRED_SIZE))
);
jSplitPane1.setRightComponent(jPanel1);
jToolBar2.setFloatable(false);
jToolBar2.setRollover(true);
add.setText("Add");
add.setFocusable(false);
add.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
add.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
add.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
addActionPerformed(evt);
}
});
jToolBar2.add(add);
edit.setText("Edit");
edit.setFocusable(false);
edit.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
edit.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
edit.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
editActionPerformed(evt);
}
});
jToolBar2.add(edit);
delete.setText("Delete");
delete.setFocusable(false);
delete.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
delete.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
delete.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
deleteActionPerformed(evt);
}
});
jToolBar2.add(delete);
jScrollPane1.setViewportView(list);
javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
jPanel2.setLayout(jPanel2Layout);
jPanel2Layout.setHorizontalGroup(
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jToolBar2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 249, Short.MAX_VALUE)
);
jPanel2Layout.setVerticalGroup(
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel2Layout.createSequentialGroup()
.addComponent(jToolBar2, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 725, Short.MAX_VALUE))
);
jSplitPane1.setLeftComponent(jPanel2);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jToolBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jSplitPane1, javax.swing.GroupLayout.Alignment.TRAILING)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jSplitPane1))
);
pack();
}// </editor-fold>//GEN-END:initComponents
private void addActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addActionPerformed
add();
}//GEN-LAST:event_addActionPerformed
private void deleteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteActionPerformed
delete();
}//GEN-LAST:event_deleteActionPerformed
private void runActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_runActionPerformed
run();
}//GEN-LAST:event_runActionPerformed
private void clearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearActionPerformed
clear();
}//GEN-LAST:event_clearActionPerformed
private void loadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_loadActionPerformed
load();
}//GEN-LAST:event_loadActionPerformed
private void saveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveActionPerformed
save();
}//GEN-LAST:event_saveActionPerformed
private void newProjectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newProjectActionPerformed
newProject();
}//GEN-LAST:event_newProjectActionPerformed
private void editActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editActionPerformed
edit();
}//GEN-LAST:event_editActionPerformed
private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
if (worker == null) {
System.exit(0);
}
}//GEN-LAST:event_formWindowClosing
private void logBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_logBtnActionPerformed
logFile();
}//GEN-LAST:event_logBtnActionPerformed
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new App().setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton add;
private javax.swing.JButton clear;
private javax.swing.JButton delete;
private javax.swing.JButton edit;
private javax.swing.JLabel img;
private javax.swing.JLabel jLabel1;
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel2;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JScrollPane jScrollPane2;
private javax.swing.JToolBar.Separator jSeparator1;
private javax.swing.JToolBar.Separator jSeparator2;
private javax.swing.JToolBar.Separator jSeparator3;
private javax.swing.JToolBar.Separator jSeparator4;
private javax.swing.JSplitPane jSplitPane1;
private javax.swing.JToolBar jToolBar1;
private javax.swing.JToolBar jToolBar2;
private javax.swing.JList<String> list;
private javax.swing.JButton load;
private javax.swing.JButton logBtn;
private javax.swing.JButton newProject;
private javax.swing.JToggleButton run;
private javax.swing.JButton save;
private javax.swing.JComboBox<String> speed;
private javax.swing.JTable table;
// End of variables declaration//GEN-END:variables
public static App app;
public static ArrayList<Tag> tags = new ArrayList<Tag>();
public static DefaultTableModel tableModel = new DefaultTableModel();
public static DefaultListModel listModel = new DefaultListModel();
public static JFImage logImage = new JFImage(1, 510);
public static Worker worker;
public static Task task;
public static int speedIdx;
public static int delay;
public static int tickCounter;
public static FileOutputStream logger;
public String projectFile;
public String logFile;
public static boolean active;
public void newProject() {
tags.clear();
listModel.clear();
list.removeAll();
clear();
projectFile = null;
}
public void clear() {
while (tableModel.getRowCount() > 0) {
tableModel.removeRow(0);
}
logImage.fill(0, 0, logImage.getWidth(), logImage.getHeight(), 0xffffffff);
img.repaint();
}
public static String dl_filters[][] = new String[][] { {"Data Logger Files (*.dl)", "dl"} };
public static String csv_filters[][] = new String[][] { {"CSV Files (*.csv)", "csv"} };
public void load() {
String filename = JF.getOpenFile(JF.getUserPath(), dl_filters);
if (filename == null) return;
newProject();
projectFile = filename;
XML xml = new XML();
xml.read(filename);
int cnt = xml.root.getChildCount();
for(int a=0;a<cnt;a++) {
XML.XMLTag xmltag = xml.root.getChildAt(a);
Tag tag = new Tag();
tag_load(tag, xmltag.content);
tags.add(tag);
listModel.addElement(tag.toString());
}
}
public void tag_load(Tag tag, String data) {
String f[] = data.split("[|]");
tag.host = f[0];
switch (f[1]) {
case "S7": tag.type = Controller.types.S7; break;
case "AB": tag.type = Controller.types.AB; break;
case "MB": tag.type = Controller.types.MB; break;
case "NI": tag.type = Controller.types.NI; break;
}
tag.tag = f[2];
switch (f[3]) {
case "bit": tag.size = Controller.sizes.bit; break;
case "int8": tag.size = Controller.sizes.int8; break;
case "int16": tag.size = Controller.sizes.int16; break;
case "int32": tag.size = Controller.sizes.int32; break;
case "float32": tag.size = Controller.sizes.float32; break;
case "float64": tag.size = Controller.sizes.float64; break;
}
if (tag.isFloat()) {
tag.fmin = Float.valueOf(f[4]);
tag.fmax = Float.valueOf(f[5]);
} else {
tag.min = Integer.valueOf(f[4]);
tag.max = Integer.valueOf(f[5]);
}
tag.color = Integer.valueOf(f[6], 16);
}
public String tag_save(Tag tag) {
return tag.host + "|" + tag.type + "|" + tag + "|" + tag.size + "|" + tag.getmin() + "|" + tag.getmax() + "|" + Integer.toUnsignedString(tag.color, 16);
}
public void save() {
String filename;
if (projectFile != null) {
filename = JF.getSaveFile(projectFile, dl_filters);
} else {
filename = JF.getSaveAsFile(JF.getUserPath(), dl_filters);
}
if (filename == null) return;
if (!filename.toLowerCase().endsWith(".dl")) {
filename += ".dl";
}
XML xml = new XML();
xml.setRoot("jfDataLogger", "", "");
int cnt = tags.size();
for(int a=0;a<cnt;a++) {
Tag tag = tags.get(a);
xml.addTag(xml.root, "tag", "", tag_save(tag));
}
xml.write(filename);
projectFile = filename;
}
public void logFile() {
if (logFile == null) {
logFile = JF.getSaveAsFile(JF.getUserPath(), csv_filters);
if (logFile == null) return;
if (!logFile.toLowerCase().endsWith(".csv")) {
logFile += ".csv";
}
logBtn.setText("LogFile*");
}
}
public void add() {
TagDialog dialog = new TagDialog(null, true);
dialog.setVisible(true);
if (dialog.accepted()) {
Tag tag = new Tag();
dialog.save(tag);
tags.add(tag);
listModel.addElement(tag.toString());
}
clear();
}
public void edit() {
int idx = list.getSelectedIndex();
if (idx == -1) return;
Tag tag = tags.get(idx);
TagDialog dialog = new TagDialog(null, true);
dialog.load(tag);
dialog.setVisible(true);
if (dialog.accepted()) {
dialog.save(tag);
}
listModel.setElementAt(tags.get(idx).toString(), idx);
clear();
}
public void delete() {
int idx = list.getSelectedIndex();
if (idx == -1) return;
tags.remove(idx);
listModel.remove(idx);
clear();
}
public void run() {
if (worker == null) {
if (tags.size() == 0) {
JF.showError("Error", "No tags defined");
return;
}
setState(true);
speedIdx = speed.getSelectedIndex();
delay = delays[speedIdx];
tickCounter = ticks[speedIdx];
clear();
if (logFile != null) {
try {
logger = new FileOutputStream(logFile);
} catch (Exception e) {
e.printStackTrace();
logger = null;
}
} else {
logger = null;
}
run.setText("Stop");
worker = new Worker();
worker.start();
} else {
worker.cancel();
if (logFile != null) {
logBtn.setText("LogFile");
logFile = null;
}
}
}
private void setState(boolean running) {
newProject.setEnabled(!running);
load.setEnabled(!running);
save.setEnabled(!running);
add.setEnabled(!running);
edit.setEnabled(!running);
delete.setEnabled(!running);
logBtn.setEnabled(!running);
speed.setEnabled(!running);
}
public void drawImage(Graphics g) {
if (logImage.getWidth() != img.getWidth()) {
int ow = logImage.getWidth();
int nw = img.getWidth();
int diff = nw - ow;
if (diff > 0) {
//expanding image
int px[] = logImage.getPixels();
logImage.setSize(img.getWidth(), 510);
logImage.fill(0, 0, logImage.getWidth(), logImage.getHeight(), 0xffffffff);
logImage.putPixels(px, diff, 0, ow, 510, 0);
} else {
//shrinking image
diff *= -1;
int px[] = logImage.getPixels(diff, 0, nw, 510);
logImage.setSize(img.getWidth(), 510);
logImage.fill(0, 0, logImage.getWidth(), logImage.getHeight(), 0xffffffff);
logImage.putPixels(px, 0, 0, nw, 510, 0);
}
}
g.drawImage(logImage.getImage(), 0, 0, null);
}
public static void gui(Runnable task) {
try {
java.awt.EventQueue.invokeAndWait(task);
} catch (Exception e) {
e.printStackTrace();
}
}
public static java.util.Timer timer;
public static boolean running;
public static class Worker extends Thread {
public void run() {
JFLog.log("connecting to controllers...");
running = false;
//create controllers and find fastest timer
tableModel.setColumnCount(0);
tableModel.addColumn("timestamp");
Controller.rate = 1000 / delay;
active = true;
System.out.println("rate=" + Controller.rate);
System.gc(); //ensure all prev connections are closed
int cnt = tags.size();
//start tag timers
for(int a=0;a<cnt;a++) {
Tag tag = tags.get(a);
tag.delay = delay;
Tag parent = null;
for(int b=0;b<a;b++) {
if (tags.get(b).host.equals(tag.host)) {
parent = tags.get(b);
break;
}
}
tag.start(parent);
tableModel.addColumn(tag.toString());
}
//start timer
timer = new java.util.Timer();
task = new Task();
timer.scheduleAtFixedRate(task, delay, delay);
JFLog.log("running...");
running = true;
}
public void cancel() {
if (!running) return;
active = false;
timer.cancel();
timer = null;
worker = null;
task = null;
if (logger != null) {
try { logger.close(); } catch (Exception e) {}
logger = null;
}
int cnt = tags.size();
for(int a=0;a<cnt;a++) {
Tag tag = tags.get(a);
tag.stop();
}
app.run.setText("Run");
app.setState(false);
running = false;
}
}
public static class Task extends TimerTask {
public String[] row;
public int idx;
public int delaycount = 0;
public String ln;
public long start = -1;
public void run() {
try {
int cnt = tags.size();
row = new String[cnt+1];
if (start == -1) {
start = System.nanoTime();
}
int timestamp = (int)((System.nanoTime() - start) / 1000000L);
String now = Long.toString(timestamp);
row[0] = now;
ln = now;
idx = 1;
for(int a=0;a<cnt;a++) {
Tag tag = tags.get(a);
String data = tag.getValue();
if (data == null) {
row[idx] = "error";
} else {
row[idx] = data;
}
ln += ",";
ln += row[idx];
idx++;
}
ln += "\r\n";
if (logger != null) {
logger.write(ln.getBytes());
}
gui(() -> {
tableModel.addRow(row);
if (tableModel.getRowCount() > 10) {
tableModel.removeRow(0);
}
});
updateImage();
delaycount += delay;
if (delaycount >= 100) {
gui( () -> {
app.img.repaint();
});
delaycount = 0;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public int scaleInt(Tag tag, int value) {
if (value < tag.min) return 0;
if (value > tag.max) return 100;
float delta = tag.max - tag.min;
return (int)((value - tag.min) / delta * 100.0);
}
public int scaleFloat(Tag tag, float value) {
if (value < tag.fmin) return 0;
if (value > tag.fmax) return 100;
float delta = tag.fmax - tag.fmin;
float fval = value;
float fmin = tag.fmin;
return (int)((fval - fmin) / delta * 100.0);
}
public int scaleDouble(Tag tag, double value) {
if (value < tag.fmin) return 0;
if (value > tag.fmax) return 100;
double delta = tag.fmax - tag.fmin;
double fval = value;
double fmin = tag.fmin;
return (int)((fval - fmin) / delta * 100.0);
}
public int sv, lsv;
public void getValues(Tag tag) {
String value = tag.getValue();
if (value == null) value = "0";
int iv;
float fv;
double dv;
if (tag.isFloat()) {
if (tag.getSize() == 8) {
dv = Double.valueOf(value);
sv = scaleDouble(tag, dv);
} else {
fv = Float.valueOf(value);
sv = scaleFloat(tag, fv);
}
} else {
iv = Integer.valueOf(value);
sv = scaleInt(tag, iv);
}
Integer lv = (Integer)tag.getData("last");
if (lv != null) {
lsv = lv;
} else {
lsv = sv;
}
tag.setData("last", sv);
}
public void updateImage() {
int x2 = logImage.getWidth() - 1;
int px[] = logImage.getPixels(1, 0, x2, 510);
logImage.putPixels(px, 0, 0, x2, 510, 0);
logImage.line(x2, 0, x2, 509, 0xffffff);
int cnt = tags.size();
for(int a=0;a<cnt;a++) {
Tag tag = tags.get(a);
getValues(tag);
int y = 5 + 500 - (sv * 5);
int ly = 5 + 500 - (lsv * 5);
logImage.line(x2-1, ly, x2, y, tag.color);
}
tickCounter--;
if (tickCounter == 0) {
tickCounter = ticks[speedIdx];
logImage.line(x2, 0, x2, 4, 0x000000);
logImage.line(x2, 505, x2, 509, 0x000000);
}
}
}
}