티스토리 뷰

728x90
반응형
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <Adafruit_MLX90614.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <WiFiNINA.h>
#include <ArduinoMqttClient.h>
#include <RTCZero.h>
#include <Arduino.h>
#include "wiring_private.h" 
#include <TinyGPSPlus.h>
#include <BH1750.h>
#include "DHT.h"

#define DHTPIN 2
#define DHTTYPE DHT22
#define BH1750_ADDRESS 0x23  // BH1750 조도센서의 I2C 주소

//ㄷ
#define PULSE_FILTER_SIZE 5
#define MLX90614_ADDRESS 0x5A
int pulseReadings[PULSE_FILTER_SIZE];
int pulseIndex = 0;
int totalPulse = 0;
//
//온습도 조도
BH1750 lightMeter(BH1750_ADDRESS);
DHT dht(DHTPIN, DHTTYPE);

char ssid[] = "TX_Lab";    // your network SSID (name)
char pass[] = "txtxxoxo123";    // your network password (use for WPA, or use as key for WEP)

// Define the pins for the GPS module connection
static const int RXPin =5, TXPin = 6;
static const uint32_t GPSBaud = 9600;

int count = 0;
const long interval = 1000 / 30;  // 1초에 30개의 값을 보내기 위한 간격 (약 33.33ms)
unsigned long previousMillis = 0;

#define LED_BUILTIN 13

WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);

const char broker[] = "test.mosquitto.org";
int port = 1883;
const char topic[] = "arduino/data";

// time
RTCZero rtc;

Adafruit_SSD1306 display(128, 64);
Adafruit_MLX90614 mlx;

String fileName = "LSM_01.csv";
File myFile;

int temp;

const int FILTER_SIZE = 5;
float tempReadings[FILTER_SIZE];
int readIndex = 0;
float totalTemp = 0;

float filteredSignal = 0;
float alpha = 0.1;
int threshold = 700;
bool handPresent = false;
int PulseSensorPurplePin = A0;

char *dtostrf2(double val, signed char width, unsigned char prec, char *sout) {
 char fmt[20];
 sprintf(fmt, "%%%d.%df", width, prec);
 sprintf(sout, fmt, val);
 return sout;
}

// Create a TinyGPSPlus object
TinyGPSPlus gps;

// Setup for the new hardware serial port over SERCOM
Uart myGPS(&sercom0, RXPin, TXPin, SERCOM_RX_PAD_1, UART_TX_PAD_0);

void SERCOM0_Handler()
{
    myGPS.IrqHandler();
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  // time
  rtc.begin();

  // Initialize Serial communication at 9600 baud
  Serial.begin(9600);
  
  // Initialize Serial1 communication at 9600 baud for the GPS module
  myGPS.begin(GPSBaud);
  // Assign pins to SERCOM functionality
  pinPeripheral(RXPin, PIO_SERCOM_ALT);
  pinPeripheral(TXPin, PIO_SERCOM_ALT);

  Serial.println(F("DeviceExample.ino"));
  Serial.println(F("A simple demonstration of TinyGPSPlus with an attached GPS module"));
  Serial.print(F("Testing TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
  Serial.println(F("by Mikal Hart"));
  Serial.println();

  // OLED 초기화
  while (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED initialization failed!");
    delay(1000);
  }
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);

  // 시작 메시지 표시
  display.clearDisplay();
  display.setCursor(28, 44);
  display.println("Starting...");
  display.display();
  delay(1000);

  // attempt to connect to WiFi network
  display.clearDisplay();
  display.setCursor(28, 44);
  display.println("WiFi connecting...");
  display.display();

  while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
    delay(1000);
    Serial.println("WiFi try...");
    // 와이파이 연결 중 표시를 위해 LED를 깜박입니다.
    digitalWrite(LED_BUILTIN , HIGH);
    delay(100);
    digitalWrite(LED_BUILTIN , LOW);
  }
  // 와이파이에 연결되었음을 표시하기 위해 LED를 3번 깜박입니다.
  for (int i = 0; i < 3; i++) {
    digitalWrite(LED_BUILTIN , HIGH);
    delay(50);
    digitalWrite(LED_BUILTIN , LOW);
    delay(50);
  }
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // WiFi 연결 완료 메시지 표시
  display.clearDisplay();
  display.setCursor(28, 34);
  display.println("WiFi connected");
  display.setCursor(28, 54);
  display.println(WiFi.localIP());
  display.display();
  delay(2000);

  // temp sensor 초기화
  while (!mlx.begin(MLX90614_ADDRESS)) {
    Serial.println("MLX90614 initialization failed!");
    delay(1000);
  }
  // 온습도 센서 초기화
  dht.begin();
  delay(2000);  // DHT 센서 초기화를 위한 대기 시간
  if (isnan(dht.readHumidity()) || isnan(dht.readTemperature())) {
    Serial.println("DHT22 initialization failed!");
    while (1);
  }
  // 시작 메시지 표시
  display.clearDisplay();
  display.setCursor(28, 44);
  display.println("temp...");
  display.display();
  delay(1000);

  while (!SD.begin(10)) {
    Serial.println("SD card initialization failed!");
    delay(1000);
  }
  Serial.println("SD card initialization done.");
  // 시작 메시지 표시
  display.clearDisplay();
  display.setCursor(28, 44);
  display.println("SD...");
  display.display();
  delay(1000);
  // 파일명 생성
  while (SD.exists(fileName)) {
    fileName = "LSM_" + String(fileName.substring(4, 6).toInt() + 1) + ".csv";
    if (fileName.substring(4, 6).toInt() > 99) {
      unsigned long currentTime = millis();
      fileName = "LSM_" + String(currentTime) + ".csv";
      break;
    }
  }
  // 시작 메시지 표시
  display.clearDisplay();
  display.setCursor(28, 44);
  display.println("file...");
  display.display();
  delay(1000);
  myFile = SD.open(fileName, FILE_WRITE);
  if (myFile) {
    Serial.print("Writing to ");
    Serial.print(fileName);
    Serial.println("...");
    myFile.println("Time, Ambient Temperature, Object Temperature, Pulse, Illuminance, Temperature, Humidity, Heat Index");
    myFile.close();
    Serial.println("done.");
  } else {
    Serial.print("error opening ");
    Serial.println(fileName);
  }
  // 시작 메시지 표시
  display.clearDisplay();
  display.setCursor(28, 44);
  display.println("open...");
  display.display();
  delay(1000);
  for (int i = 0; i < FILTER_SIZE; i++) {
    tempReadings[i] = 0;
  }
  // 시작 메시지 표시
  display.clearDisplay();
  display.setCursor(28, 44);
  display.println("reading...");
  display.display();
  delay(1000);
  while (!mqttClient.connect(broker, port)) {
    Serial.print("MQTT connection failed! Error code = ");
    Serial.println(mqttClient.connectError());
    delay(1000);
  }
  // 시작 메시지 표시
  display.clearDisplay();
  display.setCursor(28, 44);
  display.println("mqtt...");
  display.display();
  delay(1000);
}

void loop() {
  mqttClient.poll();

  // Display information every time a new sentence is correctly encoded.
  while (myGPS.available() > 0)
    if (gps.encode(myGPS.read()))
      displayInfo();

  // If no GPS data is detected after 5 seconds, display a warning.
  if (millis() > 5000 && gps.charsProcessed() < 10)
  {
    Serial.println(F("No GPS detected: check wiring."));
    while(true);
  }

  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;

    float ambientTemp = mlx.readAmbientTempC();
    float objectTemp = 0;
    
    // 객체 온도 측정 및 이동평균 필터링
    float tempReading = mlx.readObjectTempC();
    if (tempReading >= 20 && tempReading <= 42) {
      totalTemp -= tempReadings[readIndex];
      tempReadings[readIndex] = tempReading;
      totalTemp += tempReadings[readIndex];
      readIndex = (readIndex + 1) % FILTER_SIZE;
      objectTemp = totalTemp / FILTER_SIZE;
    }

    // 맥박값 이동평균 필터링
    int signal = analogRead(PulseSensorPurplePin);
    totalPulse -= pulseReadings[pulseIndex];
    pulseReadings[pulseIndex] = signal;
    totalPulse += pulseReadings[pulseIndex];
    pulseIndex = (pulseIndex + 1) % PULSE_FILTER_SIZE;
    filteredSignal = totalPulse / PULSE_FILTER_SIZE;

    // Measure illuminance 온습도
    uint16_t lux = lightMeter.readLightLevel();
    
    // Measure temperature and humidity
    float h = dht.readHumidity();
    float t = dht.readTemperature();
    
    // Calculate heat index in Celsius
    float hic = dht.computeHeatIndex(t, h, false);

    // OLED 디스플레이 업데이트
    display.clearDisplay(); 
    // 온도 값을 표시할 네모 그리기
    display.drawRect(28, 18, 68, 24, WHITE);

    display.setFont(&FreeMonoBold9pt7b);
    display.setCursor(33, 38);
    display.println(String(objectTemp, 1));
    display.setCursor(78, 38);
    display.println("C");

    // 맥박 값 또는 "No hand" 텍스트를 표시할 네모 그리기
    display.drawRect(28, 44, 68, 24, WHITE);

    if (handPresent) {
      display.setCursor(33, 62);
      display.println(String(filteredSignal, 0));
    } else {
      display.setCursor(33, 62);
      display.println("X");
    }

    display.display();

    // SD 카드에 데이터 쓰기
    myFile = SD.open(fileName, FILE_WRITE);
    if (myFile) {
      myFile.print(rtc.getEpoch());  // 현재 시간을 초 단위로 저장
      myFile.print(", ");
      myFile.print(ambientTemp);
      myFile.print(", ");
      myFile.print(objectTemp);
      myFile.print(", ");
      myFile.print(filteredSignal);
      myFile.print(", ");
      myFile.print(lux);
      myFile.print(", ");
      myFile.print(t);
      myFile.print(", ");
      myFile.print(h);
      myFile.print(", ");
      myFile.println(hic);
      myFile.print(", ");
      myFile.print(gps.location.lat(), 6);
      myFile.print(", ");
      myFile.print(gps.location.lng(), 6);
      myFile.print(", ");
      myFile.print(gps.date.year());
      myFile.print("-");
      myFile.print(gps.date.month());
      myFile.print("-");
      myFile.print(gps.date.day());
      myFile.print(" ");
      myFile.print(gps.time.hour());
      myFile.print(":");
      myFile.print(gps.time.minute());
      myFile.print(":");
      myFile.print(gps.time.second());
      myFile.println();
      myFile.close();
    } else {
      Serial.println("Error opening " + fileName);
    }

    // MQTT를 통해 데이터 전송
    if (mqttClient.connected()) {
      mqttClient.beginMessage(topic);
      mqttClient.print("{ \"ambientTemp\": ");
      mqttClient.print(ambientTemp);
      mqttClient.print(", \"objectTemp\": ");
      mqttClient.print(objectTemp);
      mqttClient.print(", \"pulse\": ");
      mqttClient.print(filteredSignal);
      mqttClient.print(", \"illuminance\": ");
      mqttClient.print(lux);
      mqttClient.print(", \"temperature\": ");
      mqttClient.print(t);
      mqttClient.print(", \"humidity\": ");
      mqttClient.print(h);
      mqttClient.print(", \"heatIndex\": ");
      mqttClient.print(hic);

      // Include GPS values if valid
      if (gps.location.isValid()) {
        mqttClient.print(", \"latitude\": ");
        mqttClient.print(gps.location.lat(), 6);
        mqttClient.print(", \"longitude\": ");
        mqttClient.print(gps.location.lng(), 6);
      }

      if (gps.date.isValid() && gps.time.isValid()) {
        mqttClient.print(", \"date\": \"");
        mqttClient.print(gps.date.year());
        mqttClient.print("-");
        mqttClient.print(gps.date.month());
        mqttClient.print("-");
        mqttClient.print(gps.date.day());
        mqttClient.print("T");
        mqttClient.print(gps.time.hour());
        mqttClient.print(":");
        mqttClient.print(gps.time.minute());
        mqttClient.print(":");
        mqttClient.print(gps.time.second());
        mqttClient.print(".");
        mqttClient.print(gps.time.centisecond());
        mqttClient.print("Z\"");
      }

      mqttClient.println(" }");
      mqttClient.endMessage();
    }
  }

 delay(30);
}

void displayInfo()
{
  Serial.println(F("google map link below:")); 
  if (gps.location.isValid())
  {
    Serial.print("http://www.google.com/maps/place/");
    Serial.print(gps.location.lat(), 6);
    Serial.print(F(","));
    Serial.println(gps.location.lng(), 6);
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  // Output date and time information
  Serial.print(F("Date/Time:"));
  if (gps.date.isValid())
  {
    Serial.print(gps.date.month());
    Serial.print(F("/"));
    Serial.print(gps.date.day());
    Serial.print(F("/"));
    Serial.print(gps.date.year());
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.print(F(" "));
  if (gps.time.isValid())
  {
    if (gps.time.hour() < 10) Serial.print(F("0"));
    Serial.print(gps.time.hour());
    Serial.print(F(":"));
    if (gps.time.minute() < 10) Serial.print(F("0"));
    Serial.print(gps.time.minute());
    Serial.print(F(":"));
    if (gps.time.second() < 10) Serial.print(F("0"));
    Serial.print(gps.time.second());
    Serial.print(F("."));
    if (gps.time.centisecond() < 10) Serial.print(F("0"));
    Serial.print(gps.time.centisecond());
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.println();
}

이동평균 필터(Moving Average Filter)는 일정 구간의 데이터를 평균내어 출력하는 저역통과 필터(Low Pass Filter)의 일종입니다. 주로 노이즈를 제거하거나 신호를 평탄화하는 용도로 사용됩니다.

이동평균 필터는 다음과 같은 수식으로 표현할 수 있습니다:

y[i] = (x[i] + x[i-1] + ... + x[i-N+1]) / N

여기서,

  • y[i]: 필터링된 출력값
  • x[i]: 현재 입력값
  • N: 이동평균 구간의 길이

예를 들어, N=5인 이동평균 필터는 현재 값과 이전 4개의 값을 더하여 5로 나눈 평균값을 출력합니다. 이렇게 하면 short-term fluctuation이 제거되고 신호가 평탄화됩니다.

이동평균 필터를 코드로 구현하면 다음과 같습니다:

const int FILTER_SIZE = 5;
float readings[FILTER_SIZE];
int readIndex = 0;
float total = 0;

float filteredValue = 0;

void setup() {
  for (int i = 0; i < FILTER_SIZE; i++) {
    readings[i] = 0;
  }
}

void loop() {
  float reading = analogRead(sensorPin);

  total -= readings[readIndex];
  readings[readIndex] = reading;
  total += readings[readIndex];
  readIndex = (readIndex + 1) % FILTER_SIZE;

  filteredValue = total / FILTER_SIZE;
}

위 코드에서:

  1. FILTER_SIZE는 이동평균 구간의 길이입니다.
  2. readings 배열은 최근 FILTER_SIZE개의 센서 값을 저장합니다.
  3. readIndex는 readings 배열에 새로운 값을 저장할 인덱스를 가리킵니다.
  4. total은 readings 배열에 저장된 값들의 합입니다.
  5. loop() 함수에서는 새로운 센서 값을 읽어 readings 배열에 저장하고, total과 readIndex를 갱신합니다.
  6. 필터링된 값은 total을 FILTER_SIZE로 나누어 계산합니다.

이동평균 필터는 구현이 간단하고 메모리 사용량이 적어 임베디드 시스템에서 자주 사용됩니다. 다만 이동평균 구간의 길이(N)에 따라 출력 값의 지연(delay)이 발생할 수 있다는 점을 고려해야 합니다.


TD

>mqttclient1

>mqttclient1_callbacks

# me - this DAT

# MQTT 브로커 정보

subscribed_topic = "arduino/data"

# Called when connection established
# dat - the OP which is cooking
def onConnect(dat):
	op("mqttclient1").subscribe(subscribed_topic)
	return

# Called when connection failed
# dat - the OP which is cooking
# msg - reason for failure
def onConnectFailure(dat, msg):
    print(f"Connection failed: {msg}")
    return

# Called when current connection lost
# dat - the OP which is cooking
# msg - reason for failure
def onConnectionLost(dat, msg):
    print(f"Connection lost: {msg}")
    return

# Called when server receives subscription request
# dat - the OP which is cooking
def onSubscribe(dat):
    print(f"Subscribed to topic: {subscribed_topic}")
    return

# Called when subscription request fails.
# dat - the OP which is cooking
# msg - reason for failure
def onSubscribeFailure(dat, msg):
    print(f"Subscription failed: {msg}")
    return

# Called when server receives unsubscription request
# dat - the OP which is cooking
def onUnsubscribe(dat):
    print(f"Unsubscribed from topic: {subscribed_topic}")
    return

# Called when unsubscription request fails.
# dat - the OP which is cooking
# msg - reason for failure
def onUnsubscribeFailure(dat, msg):
    print(f"Unsubscription failed: {msg}")
    return

# Called when server receives publish request
# dat - the OP which is cooking
def onPublish(dat):
    print("Publish request received")
    return

# Called when new content received from server
# dat - the OP which is cooking
# topic - topic name of the incoming message
# payload - payload of the incoming message
# qos - qos flag for of the incoming message
# retained - retained flag of the incoming message
# dup - dup flag of the incoming message
def onMessage(dat, topic, payload, qos, retained, dup):
    print(f"Received message - Topic: {topic}, Payload: {payload}, QoS: {qos}, Retained: {retained}, Dup: {dup}")

    if topic == subscribed_topic:
        try:
            # Assuming payload is already a byte string as it comes from MQTT
            data = payload.decode('utf-8')
            print(f"Decoded payload: {data}")

            # JSON parsing
            import json
            parsed_data = json.loads(data)

            # Extracting values
            ambientTemp = parsed_data.get("ambientTemp", 0)  # Using .get for safer extraction
            objectTemp = parsed_data.get("objectTemp", 0)
            pulse = parsed_data.get("pulse", 0)
            latitude = parsed_data.get("latitude",0)
            longitude = parsed_data.get("longitude",0)
            illuminance = parsed_data.get("illuminance",0)
            temperature = parsed_data.get("temperature",0)
            humidity = parsed_data.get("humidity",0)
            date = parsed_data.get("date",0)

            print(f"Received data - Pulse: {pulse}, Ambient Temperature: {ambientTemp}, Object Temperature: {objectTemp},Latitude:{latitude}, Longitude: {longitude}")

            # TouchDesigner Integration: Append to table1 operator and keep rows count to 100
            tableOp = op('table2')  # Access the operator

            # Append the received data as a new row
            tableOp.appendRow([pulse, ambientTemp, objectTemp, latitude, longitude,illuminance,temperature,humidity,date])

            # Keep the number of rows to 100
            while tableOp.numRows > 300:
                tableOp.deleteRow(0)  # Delete the oldest row

        except Exception as e:
            print(f"Error parsing payload: {e}")

 

728x90
반응형
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함
반응형