import processing.serial.*; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.ArrayList; import processing.awt.*; import java.awt.*; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import controlP5.*; ControlP5 cp5; String selectedFolder = ""; Serial xbee; // input serial port from the Xbee Radio int[] packet = new int[78]; // with 5 samples, the Xbee packet is 24bytes long int byteCounter; // keeps track of where you are in the packet int fontSize = 24; // size of the text on the screen int dc = 0; // データ数 int Addr = 0; int addrRead = 0; int RSSI = 0; byte thisSample1; float minTemp = 10; float maxTemp = 40; float tempkizami = 0; String address; // sender's address float Signal = 0; // average of the sensor data float Signalv = 0; int firstRectPos = 25; // horizontal pos of the first graph bar int AddrNum = 3; // アドレスの数 ArrayList[] signals = new ArrayList[AddrNum]; // 各アドレスごとの温度データを保存するリスト ArrayList[] timeSeriesData = new ArrayList[AddrNum]; // 各アドレスごとの時系列データを保存するリスト ArrayList[] timeLabels = new ArrayList[AddrNum]; // 各アドレスごとの時間ラベルを保存するリスト int interval = 600000*6*24; // 600000が10分(ミリ秒単位) int lastSaveTime = 0; // 最後にデータを保存した時間 float[] temperatures = new float[AddrNum]; float[] rssiValues = new float[AddrNum]; float[] BVoltValues = new float[AddrNum]; PFont font; SerialSetting serialSetting; boolean settingsDone = false; void setup() { size(1300, 760, P3D); // ウィンドウサイズを大きくしてNumSensor個のメータを表示 serialSetting = new SerialSetting(this, 30, 20, "COM7", 9600); serialSetting.showSettingsWindow(); // 初期化 for (int i = 0; i < AddrNum; i++) { signals[i] = new ArrayList(); timeSeriesData[i] = new ArrayList(); timeLabels[i] = new ArrayList(); } lastSaveTime = millis(); } class SerialSetting { final int COMBO_PORT_WIDTH = 100; // ポート選択コンボボックスの幅 final int COMBO_PORT_HEIGHT = 20; // ポート選択コンボボックスの高さ final int COMBO_RATE_WIDTH = 80; // ボーレート選択コンボボックスの幅 final int COMBO_RATE_HEIGHT = 20; // ボーレート選択コンボボックスの高さ final Integer[] list_rate = {300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200, 230400, 250000, 500000, 1000000, 2000000}; // ボーレートのリスト String[] list_port = null; // 利用可能なシリアルポートのリスト JComboBox combo_port; // シリアルポート選択のためのコンボボックス JComboBox combo_rate; // ボーレート選択のためのコンボボックス String current_port = null; // 現在選択されているポート int current_rate = 0; // 現在選択されているボーレート Serial serial = null; // シリアルオブジェクト PApplet parent = null; // 親のPAppletオブジェクト JFrame settingsFrame; JLabel folderLabel; // 選択されたフォルダのパスを表示するラベル // コンストラクタ SerialSetting(PApplet app, int x, int y, String default_port, int default_rate) { parent = app; x = (x/2)*2; y = (y/2)*2; list_port = Serial.list(); // 利用可能なシリアルポートのリストを取得 // シリアルポート選択のためのコンボボックスを設定 combo_port = new JComboBox<>(list_port); combo_port.setSelectedItem(default_port); // ボーレート選択のためのコンボボックスを設定 combo_rate = new JComboBox<>(list_rate); combo_rate.setSelectedItem(default_rate); } // 設定ウィンドウを表示 void showSettingsWindow() { SwingUtilities.invokeLater(() -> { settingsFrame = new JFrame("Serial Settings"); settingsFrame.setSize(380, 250); settingsFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); settingsFrame.setLayout(null); settingsFrame.setAlwaysOnTop(true); // メインウィンドウの手前で開く // モニタの中心にウィンドウを表示 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int x = (screenSize.width - settingsFrame.getWidth()) / 7; int y = (screenSize.height - settingsFrame.getHeight()) / 2; settingsFrame.setLocation(x, y); JLabel portLabel = new JLabel("COM Port:"); portLabel.setBounds(30, 20, 100, 25); settingsFrame.add(portLabel); combo_port.setBounds(100, 20, COMBO_PORT_WIDTH, COMBO_PORT_HEIGHT); settingsFrame.add(combo_port); JLabel rateLabel = new JLabel("Baud Rate:"); rateLabel.setBounds(30, 60, 100, 25); settingsFrame.add(rateLabel); combo_rate.setBounds(100, 60, COMBO_RATE_WIDTH, COMBO_RATE_HEIGHT); settingsFrame.add(combo_rate); JButton folderButton = new JButton("Select Folder"); folderButton.setBounds(100, 100, COMBO_PORT_WIDTH + 50, COMBO_PORT_HEIGHT); folderButton.addActionListener(e -> { selectFolder("Select a folder to save your file:", "folderSelected"); }); settingsFrame.add(folderButton); folderLabel = new JLabel("Selected folder: "); // 初期テキスト folderLabel.setBounds(30, 125, 240, 25); // 表示位置とサイズ 30 settingsFrame.add(folderLabel); JButton exitButton = new JButton("Start meas."); exitButton.setBounds(100, 150, COMBO_PORT_WIDTH, COMBO_PORT_HEIGHT); exitButton.addActionListener(e -> { // 設定を反映 current_port = (String) combo_port.getSelectedItem(); current_rate = (int) combo_rate.getSelectedItem(); settingsDone = true; xbee = new Serial(parent, current_port, current_rate); settingsFrame.dispose(); }); settingsFrame.add(exitButton); settingsFrame.setVisible(true); }); } } void folderSelected(File selection) { if (selection == null) { println("No folder was selected."); } else { selectedFolder = selection.getAbsolutePath(); println("Selected folder: " + selectedFolder); SwingUtilities.invokeLater(() -> { serialSetting.folderLabel.setText("Selected folder: " + selectedFolder); }); } } void draw() { background(35, 59, 108); // set the background: fill(255, 255, 0); // 各メータの位置とサイズ float meterSize = 170; float[] meterX = new float[10]; for (int i = 0; i < AddrNum; i++) { meterX[i] = (1 + i) * width / (AddrNum + 1); } float meterY = height / 4.1; //温度メータの背景 for (int i = 0; i < AddrNum; i++) { fill(245, 220, 189); //肌色 noStroke(); rect(meterX[i] - 140, meterY - 140, 320, 320, 20); drawMeter(meterX[i], meterY, meterSize, temperatures[i], rssiValues[i], BVoltValues[i], i + 1); } // 時系列データのグラフ描画 drawTimeSeriesGraph(); // 指定時間経過したらデータを保存 if (millis() - lastSaveTime > interval) { saveData(); lastSaveTime = millis(); } } void drawBar(int rectHeight) { if (rectHeight > 0 ) { stroke(255, 127, 0); fill(123, 255, 0); } } void serialEvent(Serial xbee) { int thisByte = xbee.read(); if (thisByte == 0x7E) { // start byte if (packet[21] > 0) { parseAddr(packet); } if (packet[36] > 0) { parseData(packet); } if (packet[49] > 0) { parseRSSI(packet); } if (packet[72] > 0) { parseBVolt(packet); } // reset the byte counter: byteCounter = 0; } // put the current byte into the packet at the current position: packet[byteCounter] = thisByte; // increment the byte counter: byteCounter++; } void parseAddr(int[] thisPacket) { int addrRead = 21;// String Addrs = str(char(thisPacket[addrRead])); Addr = int(Addrs); println("Addr:" + Addr); } void parseData(int[] thisPacket) { int adcStart = 36; String T = str(char(thisPacket[adcStart]))+str(char(thisPacket[adcStart + 1]))+"."+str(char(thisPacket[adcStart + 3]))+str(char(thisPacket[adcStart + 4])); float thisSample = float(T); Signal = thisSample; if ( minTemp > Signal) { Signal = minTemp; } if ( maxTemp < Signal) { Signal = maxTemp; } println("Signal:" + Signal); // アドレスに応じてデータを保存 if (Addr >= 1 && Addr <= AddrNum) { signals[Addr - 1].add(Signal); temperatures[Addr - 1] = Signal; timeSeriesData[Addr - 1].add(Signal); // 時系列データに追加 timeLabels[Addr - 1].add(new SimpleDateFormat("HH:mm:ss").format(new Date())); // 時間ラベルを追加 } } void parseRSSI(int[] thisPacket) { int RSSI_StartByte = 49; String Ra = str(char(thisPacket[RSSI_StartByte])); println("Ra:" + Ra); String Rb = str(char(thisPacket[RSSI_StartByte + 1])); String Rd = str(char(thisPacket[RSSI_StartByte + 2])); float RSSI = -float(Ra)*100 - float(Rb)*10 - float(Rd); println("RSSI:" + RSSI); rssiValues[Addr - 1] = RSSI; println("RSSI:" + RSSI); } void parseBVolt(int[] thisPacket) { int BVolt_StartByte = 72; String Ba = str(char(thisPacket[BVolt_StartByte])); String Bb = str(char(thisPacket[BVolt_StartByte + 2])); String Bc = str(char(thisPacket[BVolt_StartByte + 3])); float BVolt = float(Ba) + float(Bb)/10 + float(Bc)/100 ; println("BVolt:" + BVolt); BVoltValues[Addr - 1] = BVolt; println("BVolt:" + BVolt); } void saveData() { String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String fileName = selectedFolder + "/TempData_" + timestamp + ".csv"; PrintWriter output = createWriter(fileName); // ヘッダー行の作成 String header = "Time"; for (int i = 0; i < AddrNum; i++) { header += ",Addr " + (i + 1); } output.println(header); // データ行の作成 int maxRows = 0; for (int i = 0; i < AddrNum; i++) { maxRows = max(maxRows, signals[i].size()); } for (int row = 0; row < maxRows; row++) { String line = ""; for (int col = 0; col < AddrNum; col++) { if (col > 0) line += ","; if (row < timeLabels[col].size()) { line += timeLabels[col].get(row) + ","; } else { line += ","; } if (row < signals[col].size()) { line += signals[col].get(row); } } output.println(line); } output.flush(); output.close(); println("Data saved to " + fileName); // データのリセット for (int i = 0; i < AddrNum; i++) { signals[i].clear(); timeSeriesData[i].clear(); timeLabels[i].clear(); } } void drawMeter(float meterX, float meterY, float meterSize, float temperature, float rssi, float bvolt, int sensorID) { // 温度を表示する角度の計算 (左に90度回転) float startAngle = radians(-225); float endAngle = radians(45); float angle = map(temperature, minTemp, maxTemp, startAngle, endAngle); // メータの弧のバックグラウンド stroke(200); strokeWeight(10); noFill(); arc(meterX, meterY, meterSize, meterSize, startAngle, endAngle); // メータの目盛り for (float i = minTemp; i <= maxTemp; i+=5) { float tickAngle = map(i, minTemp, maxTemp, startAngle, endAngle); float x1 = meterX + cos(tickAngle) * meterSize / 2; float y1 = meterY + sin(tickAngle) * meterSize / 2; float x2 = meterX + cos(tickAngle) * meterSize / 1.9; float y2 = meterY + sin(tickAngle) * meterSize / 1.9; line(x1, y1, x2, y2); // メモリ値の表示 fill(70); textSize(19); textAlign(CENTER, CENTER); float labelX = meterX + cos(tickAngle) * meterSize / 1.7; float labelY = meterY + sin(tickAngle) * meterSize / 1.7; text(int(i), labelX, labelY); } // 温度を表示する部分 stroke(255, 0, 0); // 赤色 strokeWeight(10); fill(245, 220, 189); arc(meterX, meterY, meterSize, meterSize, startAngle, angle); // 指示針の描画(黒色の三角矢印) fill(255, 215, 0); stroke(0); strokeWeight(1); float needleLength = meterSize / 2.0; float arrowSize = 10; float x1 = meterX + cos(angle) * needleLength; float y1 = meterY + sin(angle) * needleLength; float x2 = meterX + cos(angle + radians(90)) * arrowSize; float y2 = meterY + sin(angle + radians(90)) * arrowSize; float x3 = meterX + cos(angle - radians(90)) * arrowSize; float y3 = meterY + sin(angle - radians(90)) * arrowSize; float x4 = meterX + cos(angle) * (needleLength - arrowSize); float y4 = meterY + sin(angle) * (needleLength - arrowSize); beginShape(); vertex(x1, y1); vertex(x2, y2); vertex(x3, y3); vertex(x4, y4); endShape(CLOSE); fill(0, 0, 255); // 針の軸を青に塗りつぶす ellipse(meterX, meterY, 20, 20); // 現在の温度をテキストで表示 fill(0); textSize(24); textAlign(CENTER, CENTER); text("Sensor " + sensorID + ": " + nf(temperature, 1, 2) + "[°C]", meterX, meterY + meterSize / 2 + 50); // 現在のバッテリー電圧をテキストで表示 fill(0); textSize(22); textAlign(CENTER, CENTER); text("Battery voltage " + ": " + nf(bvolt, 1, 2) + "[V]", meterX, meterY + meterSize / 2 + 80); // RSSIのカラーグラデーションバーを表示 float rssiBarWidth = 30; float rssiBarHeight = meterSize; float rssiBarX = meterX + meterSize / 1.5; float rssiBarY = meterY + meterSize / 2 - rssiBarHeight; float rssiLength = map(rssi, -255, 0, 0, rssiBarHeight); // RSSIのバックグラウンドを白で表示 fill(255); noStroke(); rect(rssiBarX, rssiBarY, rssiBarWidth, rssiBarHeight); // RSSIのカラーグラデーションバー for (int i = 0; i < rssiLength; i++) { float inter = map(i, 0, rssiBarHeight, 1.0, 0.0); int c = lerpColor(color(255, 0, 0), color(255, 255, 0), inter); // 赤から黄色のグラデーション stroke(c); line(rssiBarX, rssiBarY + rssiBarHeight - i, rssiBarX + rssiBarWidth, rssiBarY + rssiBarHeight - i); } // 信号が最大のときにバーの周囲を黒線で囲む if (rssi >= -50) { stroke(0); noFill(); rect(rssiBarX, rssiBarY, rssiBarWidth, rssiBarHeight); } // RSSIのラベルを表示 fill(0); textSize(20); textAlign(CENTER, CENTER); text("RSSI", rssiBarX + rssiBarWidth / 2, rssiBarY - 20); // 現在のRSSIを数値で表示 fill(0); textSize(20); textAlign(CENTER, CENTER); text(nf(rssi, 1, 2) + " dBm", rssiBarX + rssiBarWidth / 2, rssiBarY + rssiBarHeight + 20); } void drawTimeSeriesGraph() { float graphX = 180; float graphY = height * 1/ 2; float graphWidth = width - 300; float graphHeight = 250; // グラフのバックグラウンド fill(245, 220, 189); // 肌色 noStroke(); rect(graphX, graphY, graphWidth, graphHeight); // グラフの枠を描画 stroke(255); noFill(); rect(graphX, graphY, graphWidth, graphHeight); // 縦軸の目盛りとラベルを表示 fill(255); textSize(26); textAlign(CENTER); tempkizami = ( maxTemp - minTemp)/ 5; for (int i = 0; i <= tempkizami; i++) { float y = map(i, 0, tempkizami, graphY + graphHeight, graphY); stroke(0, 0, 0); strokeWeight(1); line(graphX, y, graphX + graphWidth, y); // 目盛線を描画 stroke(255, 255, 255); line(graphX - tempkizami, y, graphX, y); text(nf(minTemp + i * 5, 1, 0), graphX - 25, y + 8); // 10°Cから40°Cまでの目盛り } // グラフのタイトルを表示 textSize(36); textAlign(CENTER); text("Temperature sensor network monitor", graphX + graphWidth / 2, 40); // グラフのラベルを表示 textSize(36); textAlign(CENTER); text("Time", graphX + graphWidth / 2, graphY + graphHeight + 70); // センサーの色のラベルを表示 textSize(20); textAlign(CENTER); fill(255, 0, 0); text("Sensor 1", graphX + graphWidth / 1.3, graphY + graphHeight + 70); fill(0, 255, 0); text("Sensor 2", graphX + graphWidth / 1.18, graphY + graphHeight + 70); fill(255, 0, 255); text("Sensor 3", graphX + graphWidth / 1.08, graphY + graphHeight + 70); // 縦軸のラベルを縦書きで表示 textSize(36); textAlign(CENTER); fill(255); pushMatrix(); translate(graphX - 60, graphY + graphHeight / 2); rotate(-PI / 2); text("Temperature [°C]", 0, 0); popMatrix(); // 時間スケールを表示 for (int i = 0; i < 1; i++) { textSize(26); textAlign(CENTER); int numLabels = min(timeLabels[i].size(), 5); // 時間ラベルを5個以下に制限 for (int j = 0; j < numLabels; j++) { int index = int(map(j, 0, numLabels - 1, 0, timeLabels[i].size() - 1)); float x = map(index, 0, timeLabels[i].size() - 1, graphX, graphX + graphWidth); float y = graphY + graphHeight + 40; // 前の時間ラベルをバックグラウンドカラーで塗りつぶす noStroke(); // 新たな時間ラベルを表示 fill(255); // 白色で描画 text(timeLabels[i].get(index), x, y); // 短い目盛線を描画 stroke(255); // 白色の目盛線 strokeWeight(1); // 細い線 line(x, graphY + graphHeight, x, graphY + graphHeight + 10); // 短い目盛線 } } // 各アドレスごとの温度データをプロット color[] colors = {color(255, 0, 0), color(0, 255, 0), color(255, 0, 255)}; for (int i = 0; i < AddrNum; i++) { stroke(colors[i]); strokeWeight(3); noFill(); beginShape(); for (int j = 0; j < timeSeriesData[i].size(); j++) { float x = map(j, 0, timeSeriesData[i].size() - 1, graphX, graphX + graphWidth); float y = map(timeSeriesData[i].get(j), 10, 40, graphY + graphHeight, graphY); vertex(x, y); } endShape(); } }