NodeMcu와 DFPlayer MP3 모듈을 이용해 말하는 알람시계를 구현해 보자.

주요 기능

1. millis() 함수를 이용한 시계 구현 

2. 크리스털 LCD 및 시리얼 모니터에 시간 출력

3. wifi station mode를 통해 NTP 서버와 시간 동기화 

4. 정시 알림

5. 정시 알림 묵음 설정

6. 현재 시간 알림

7. 알람 시간 설정 및 알람 

8. wifi soft AP를 통해 NodeMcu 및 DFPlayer 원격제어 - 디지털 핀 제어 및 MP3 파일 재생

9. 스누즈 설정

 

이전 글 아두이노 말하는알람시계 예제 - DFPlayer 코드를 이용하여 아두이노와 ESP01 모듈, DFPlayer 조합에서는 할 수 없었던 wifi를 통한 NTP 서버와 시간 동기화도 시키고, 더불어 wifi soft AP를 이용하여 원격제어도 해보자. 

 

하드웨어 구성

1. 크리스털 LCD 연결

크리스털 LCD VCC -> NodeMcu VU 핀(5V 출력)

크리스털 LCD GND -> NodeMcu GND 핀

크리스털 LCD SDA -> D2 (NodeMcu의 I2C 통신용 핀: D1-SCL, D2-SDA)

크리스털 LCD SCL -> D1

 

2. DFPlayer 연결 

DFPlayer VCC -> NodeMcu VU 핀(5V 출력)

DFPlayer RX -> NodeMcu D4 핀(Software Serial TX)

DFPlayer TX -> NodeMcu D3 핀(Software Serial RX)

DFPlayer DAC_R -> 이어폰 잭 핀

DFPlayer DAC_L -> 이어폰 잭 핀

DFPlayer SPK1 -> 스피커 + 또는 -

DFPlayer SPK2 -> 스피커 + 또는 -

DFPlayer SPK1과 SPK2 사이 GND(이어폰용 GND) -> 이어폰 잭 GND 핀(이어폰 잭을 사용하지 않는다면 연결 필요 없음)

DFPlayer IO1과 IO2 사이 GND(모듈 및 스피커용 GND) -> NodeMcu GND 핀

 

3. 버튼 연결 

NodeMcu 버튼 PULL-UP 기능 핀 번호: D5, D6, D7중 선택

 

DFPalyer 전원 연결: 매뉴얼 상에 DFPlayer는 3.3V에서도 작동한다고 되어 있다. 하지만 NocdMcu의 3.3V에 연결을 하게 되면 음성 출력 시 스피커에서 "드드드드드"라는 소리와 함께 마치 리셋되는 것처럼 동작을 하게 된다. 이 증상은 이전 글 아두이노, DFPlayer, ESP01에서 발생했던 증상과 같은 것이다. 스피커를 제거하고 음성 출력을 하게 되면 이 증상은 사라지고 이어폰 단자에서는 출력이 정상적으로 이루어진다. 이는 스피커를 통해 음성을 출력하기 위한 전력이 충분하지 못하기 때문에 발생하는 것 같다. NodeMcu의 5V 출력 전원 VU단자에 연결을 해주면 이 증상은 말끔히 사라지게 된다. 그렇다면 이전 아두이노, DFPlayer, ESP01에서 발생했던 불특정 하게 리셋되는 현상이 이해가 되게 된다. 아두이노는 5V를 출력하지만 아두이노에서 출력하는 전력의 양이 ESP01의 구동과 동시에 DFPlayer의 3W 증폭기를 구동하고 스피커에 출력하기에 충분하지 않았다고 추정해 볼 수 있다. 아마도 DFPlayer의 전원을 별도로 해주었다면 해결되었을 거라 생각한다.  

 

 

음성 파일의 저장 

이전 글 DFplayer - 아두이노 사운드 모듈  에서 설명했었던 B. 저장 방식 2 (저장 방식 1에 추가로 ADVERT 폴더 사용)을 사용하여 코드를 적용하겠다. 

폴더명: 01 ~ 99까지 각 폴더별 파일갯수 1 ~ 255 
파일명: 숫자 3자리 001 ~ 255 반드시 포함하는 파일명으로, 최대 파일 개수 255개까지 각 폴더에 저장된 파일 인식 
ADVERT 폴더: mp3 또는 wave파일의 이름은 4자리 이하 숫자로만 저장되어야만 한다. 1 ~ 3000 
예) 폴더 파일명 
01 / 001.mp3, 002.wav, 003 시크릿a.mp3,,,,,, 255 yayayat.mp3 
02 / 001.mp3, 002.wav, 003 시크릿a.mp3,,,,,, 255 yayayat.mp3 
ADVERT / 0001.mp3/wav, 0002.mp3/wav, 0003 시크릿a.mp3/wav, .......3000.mp3/wav.

 

DFPlayer의 SD 카드에 아래의 음성 예제 파일의 폴더 01, 02, ADVERT를 모두 선택 후 복사하여 붙여 넣기 해준다. 

NodeMcu_DF_test.alz
10.00MB
NodeMcu_DF_test.a00
10.00MB
NodeMcu_DF_test.a01
3.56MB

상기 파일은 이전 글 아두이노 말하는알람시계 예제 - DFPlayer 코드에서 사용하던 음성 출력 파일들의 묵음 구간을 늘려 놓은 것이며 그에 따라 아래 코드를 수정하여 음성이 종료되기 전에 볼륨이 변경되는 오류를 수정하였다. 

// 음성 출력 변수 수정 64 -> 80
uint8_t time_sharp_end = 80;

// speak_time() 함수 수정 64 -> 80
time_sharp_end = 80;

// speak_sharp() 함수 수정 46 -> 48
time_sharp_end = 48;

또한, 앞선 예제의 에러 코드들을 수정 적용하였으며, NodeMcu의 핀 번호에 맞게 수정하고 NodeMcu용 와이파이 라이브러리 ESP8266WiFi.h를 불러오고 NTP 서버 시간 동기화 코드도 적용해 주었다. 추가로 EEPROM 라이브러리 관련하여 앞선 글(디지털 도어락, 말하는 알람시계 예제)의 코드에서는 EEPROM 사이즈 매크로를 정의만 하고 setup() 함수에서 적용하는 코드 EEPROM.begin(EEPROM_SIZE); 가 누락되어 있었다. 아두이노에서 작동에는 문제가 없었으나 사이즈를 정해주지 않으면 최대 크기로 설정될 것이다. (아두이노 우노 EEPROM 라이브러리에서는 크기 설정 기능이 없다.) 또한 NodeMcu의 EEPROM 라이브러리에서는 EEPROM에 쓰기를 할 때에는 반드시 EEPROM.commit(); 함수를 실행시켜주어야만 EEPROM에 쓰기가 실행 되게 된다. 아두이노에서는 상기 함수 실행 없이 바로 EEPROM에 쓰기를 할 수가 있었다. 

고정된 스트링 코드들을 F매크로를 사용하여 플래시에서 읽어 사용하도록 수정하여 메모리 stack 오류 발생 가능성을 줄이도록 수정하였다.  예) Serial.println(F("alarm on"));

 

NodeMcu 와이파이 라이브러리 및 NTP 관련 코드 

// NodeMcu wifi & NTP 라이브러리
#include <ESP8266WiFi.h> // NodeMcu 와이파이 라이브러리
#include <WiFiUdp.h>
#include <TimeLib.h>

// NTP 설정
#define ssid "SSID" // your network SSID (name)
#define pass "password"  // your network password
unsigned int localPort = 2390;      // local port to listen for UDP packets
char timeServer[] = "kr.pool.ntp.org";  // NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE];
WiFiUDP Udp;

bool got_NTP = false;
uint8_t timeZone = 9;  // 한국 타임존 적용
bool wait_NTP = false; // 음성 출력시 대기 플래그

// setup() 함수
WiFi.mode(WIFI_STA); // 와이파이 설정 station 모드
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) { // 와이파이 연결
  delay(500);
  Serial.print(F("."));
}
Serial.println(F("You're connected to the network"));
Udp.begin(localPort); // NTP 서버 연결을 위한 UDP 시작

// loop() 함수
wait_NTP_time();

// 음성 출력 및 알람시 시간동기화 대기
if (alarm_time == true || speak == true) wait_NTP = true; 

void wait_NTP_time() {
  if (wait_NTP == true) {
    got_NTP = true; 
    wait_NTP = false;
  }
}

// 음성 출력 종료시와 알람 정지시 시간 동기화 시작
if (wait_NTP == true) got_NTP = false;

loop() 함수의 NTP 코드는 이전 예제  아두이노시계 예제, ESP01 WiFi 이용 시간 동기화 하기 와 동일하다.

 

시간 조정 코드 수정: 이전 글 아두이노시계 예제, ESP01 WiFi 이용 시간 동기화 하기  에서 millis() 함수의 시간 오차가 있음을 확인하고 수정해 주었었다. NodeMcu의 millis() 함수는 아두이노의 millis() 함수보다 더 정확하게 작동한다. 1000 밀리 초를 기준으로 4시간에 약 0.3초(300 밀리 초) 빨라지는 오차가 발생하여 하루에 약 1.8초 빨라지게 될 것이다. 이를 보정하기 위해 1분마다 1 밀리초씩 늦어지도록 아래와 같이 cal_time() 함수를 수정해 주었다.  

uint8_t change_millis = 0; 
uint16_t millis_sec = 1000;

// cal_time() 함수
change_millis++;
if (change_millis < 60) millis_sec = 1000;
else { millis_sec = 1001; change_millis = 0; }

알람 정지 및 음성 출력 버튼이 한 번만 실행되도록(debouncing) millis() 함수 이용 아래 코드를 추가해 주었다. 

unsigned long int button_millis = 0;
bool button_hold = false;

// loop() 함수 추가
botton_delay();

// loop() 버튼 코드 추가
button_hold = true;
button_millis = millis();

// 사용자 함수 추가
void botton_delay() {
  if (button_hold == true) {
    if (millis() - button_millis >= 500) button_hold = false;
  }
}

만약 음성 출력이 실패할 경우 재시도 시 작동할 수 있도록 조건 플래그 초기화 코드를 추가해 주었다. 

// 음성 출력 실패시 초기화
void speak_failed() {
  if (sequence == 1 && status_check == false) { 
    sequence = 0; 
    receive_status = false; 
    speak = false;
    speak_finished = true;
  }
  
// speak_option() 함수
// speak_sharp() 함수
speak_failed();

아래 코드를 NodeMcu에 업로드 후 시간 동기화가 잘되는지 확인해 보고 시리얼 모니터를 통해 각종 기능들을 테스트해보자. 볼륨 초기값이 10이므로 연결된 스피커에 따라 시리얼 모니터 "play" 명령을 입력하여 mp3 파일 재생 시 스피커에서 출력이 안 되는 것처럼 느낄 수 있다. "vv" 명령을 입력하여 볼륨을 키워보자 또한, 버튼용 점퍼선을 점프시켜 현재 시간 출력을 해보자. 현재 시간의 볼륨은 최댓값(30)으로 설정되어 있다.

1.NodeMcu_clock_ntp.ino
0.02MB

NodeMcu의 와이파이와 Software Serial 충돌 문제

작동 테스트를 하던 중 음성 출력이 가끔씩 안 되는 경우가 발생하였다. 버튼을 이용해 현재 시간을 출력할 경우에는 재시도를 해볼 수 있지만 정시 출력 같은 경우에는 출력이 안된다. 오류를 확인해 본 결과 DFPlayer가 재생 중인지 아닌지 질의를 하는 코드는 정상적으로 실행되었으나 DFPlayer로부터 어떠한 응답도 받지 못하게 되고 응답을 받지 못하면 음성 출력 코드는 더 이상 실행되지 않게 된다. 문제 해결을 위해 응답을 받지 못하면 50밀리 초마다 계속 질의를 하도록 코드를 추가하여 여러 번 테스트해본 결과 문제 발생 후 약 5 ~ 20초 뒤에 다시 응답을 받게 되는 것을 확인했고 그동안에는 Software Serial이 작동하지 않는다는 것을 알게 되었다. DFPlayer의 문제였다면 DFPlayer를 리셋하기 전까지는 응답을 안 주던지 아니면 재 전송 시 몇 번 이내에 응답을 했어야 한다. 그렇다고 계속해서 Software Serial이 작동하지 않는 것은 아니고, 일정 시간이 지나면 다시 작동을 한다. 인터넷에 Nodemuc의 와이파이와 Software Serial에 관해 검색을 해보니 Nodemuc에서 와이파이 작동을 위해 interrupt를 사용하는데 Software Serial을 동시에 사용할 경우 충돌이 발생한다는 것이다. 해결할 방법은 없고 다른 MCU를 사용하던지 Hardware Serial을 사용해 보라는 글을 확인할 수 있었다. 라이브러리의 코드를 확인해 보지는 않았지만 아마도 Software Serial 역시 와이파이가 사용하는 interrupt를 사용하는 것 같다. 하지만 테스트를 하고 코드 Debug를 하기 위해서는 시리얼 모니터를 사용해야 하므로 Software Serial 작동 정지 시 다시 질의하는 아래의 코드를 적용시키고 사용하도록 하자. 만약 Software Serial이 작동 정지되었더라도 약 5 ~ 20초 뒤에 다시 작동하여 음성을 출력하게 된다. 

unsigned long int status_time = 0;
bool status_not_received = false;

// loop() 함수
// 추가
status_repeat();

// 수정전
if (dfSerial.available()) {
  if (dfSerial.peek() == 0x7E) {  // 42 00 02 01
    uint8_t dump = dfSerial.readBytes(received_df, 10);
    if (speak_finished == false) {
      if (received_df[3] == 0x42) {
        status_check = true;
        if (received_df[5] == 2 && received_df[6] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
        else if (received_df[5] == 2 && received_df[6] == 0) played = false; // 인덱스 3 = 0 정지중
      }
    }
  }
  else {
    uint8_t dump = dfSerial.read(); // 코드 값 아닌경우 무시
  }
}

// 수정후
if (status_not_received == true) { // 질의시에만 Software Serial 확인
  if (dfSerial.available()) {
    if (dfSerial.peek() == 0x7E) {  // 42 00 02 01
      uint8_t dump = dfSerial.readBytes(received_df, 10);
      if (speak_finished == false) {
        if (received_df[3] == 0x42) {
          status_check = true;
          if (received_df[5] == 2 && received_df[6] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
          else if (received_df[5] == 2 && received_df[6] == 0) played = false; // 인덱스 3 = 0 정지중
          status_not_received = false;
        }
      }
    }
    else {
      uint8_t dump = dfSerial.read(); // 코드 값 아닌경우 무시
    }
  }
}

// 재전송 함수
void status_repeat() {
  if ( status_not_received == true) {
    if (millis() - status_time >= 50) {
      status_time = millis();
      send_command(0x42, 0, 0, 0);
      Serial.print(".");
    }
  }
}

// speak_time() 함수
// 수정전
if (sequence == 0 && status_check == false) {
  Serial.println("speak time");
  send_command(0x42, 0, 0, 0);
  sequence++;
}

// 수정후    
if (sequence == 0 && status_check == false) {
  Serial.println("speak time");
  status_not_received = true;
  send_command(0x42, 0, 0, 0);
  sequence++;
  status_time = millis();
}

2.NodeMcu_clock_ntp_resend.ino
0.02MB

더보기
더보기
// EEPROM
#include <EEPROM.h>
#define EEPROM_SIZE 11 // 11 byte

// 크리스털 LCD
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // D1 - SCL, D2 SDA, Set the LCD address to 0x27 for a 16 chars and 2 line display

// DFPlayer Software Serial
#include <SoftwareSerial.h> 
#define DF_rxPin 2 // DFplayer RX -> NodeMcu D4
#define DF_txPin 0 // DFplayer TX -> NodeMcu D3 
SoftwareSerial dfSerial(DF_txPin, DF_rxPin); // (RX, TX)

// wifi & NTP 라이브러리
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <TimeLib.h>

// NTP 설정
#define ssid "SK_WiFiGIGA40F7" // your network SSID (name)
#define pass "1712037218"  // your network password
unsigned int localPort = 2390;      // local port to listen for UDP packets
char timeServer[] = "kr.pool.ntp.org";  // NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE];
WiFiUDP Udp;

bool got_NTP = false;
uint8_t timeZone = 9;
bool wait_NTP = false;

uint8_t received_df[10] = {0, }; // DFplayer에서 받은 데이터 저장

// DFplayer command 변수
uint8_t df_command[] = {0x7E, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0xEF};

void send_command(uint8_t cmd, uint8_t ack, uint8_t msb, uint8_t lsb) {
  df_command[3] = cmd;
  df_command[4] = ack;
  df_command[5] = msb;
  df_command[6] = lsb;
  for(int i = 0; i < sizeof(df_command); i++){
    dfSerial.write(df_command[i]);
  }
}

int h = 12; // initial Time display is 12:59:45 PM
int m = 56;
uint8_t s = 45;
bool meridian = true;
uint8_t hm;
bool now_am = true; //AM
uint16_t yy = 2019;
uint8_t mm = 1;
uint8_t dd = 1;
uint8_t week_num = 1;
String week_day = "SUN";
bool adjust = false;

bool display_t = false;
unsigned long int start_time = 0;
uint8_t volume = 10;

bool receive_status = false; // 질의 코드 0x42수신 여부 플래그
uint8_t status_count = 0;   // 질의값 저장 인덱스용 카운터
uint8_t status_val[7];         // 질의값 저장 배열
bool played = false;         // 질의결과 플래그 (재생중인지 아닌지)
bool status_check = false; // 질의 수신 완료 플래그

unsigned long int count_time = 0;
bool delay_set = false;
uint8_t sequence = 0;

bool speak = false; // 현재 상태 질의 시작 및 음성출력 시작 플래그
bool speak_finished = true;
bool short_sentence = false;
bool sharp = false;

int am;
int time_sharp;
int hour_a;
int min_w;
int min_s;

uint8_t alarm_track_folder = 2;
uint8_t alarm_track = 2;

bool alarm_on = false;
int alarm_h;
int alarm_m;
unsigned long int alarm_delay = 0;
bool alarm_sentence = false;
uint8_t alarm_count = 0;
bool alarm_time = false;

unsigned long int alarm_sequence_delay = 0;
uint8_t alarm_sequence = 0;
bool alarm_send_f = false;
bool alarm_stop = false; 

bool no_speak_sharp = false;
uint8_t no_speak_s = 0;
uint8_t no_speak_f = 0;

uint8_t time_volume = 30; // 음성출력 및 알람 볼륨

uint8_t pin = D5;

unsigned long int status_time = 0;
bool status_not_received = false;

void setup() {
  Serial.begin(115200);
  dfSerial.begin(9600); // DFplayer Serial
  EEPROM.begin(EEPROM_SIZE);
  pinMode(pin, INPUT_PULLUP);  // pullup 저항 이용 - 버튼쪽에 저항 없음 OFF - HIHG, ON - LOW
  if(EEPROM.read(0) != 1) {
    EEPROM.write(0, 1); // eeprom 저장 플래그
    EEPROM.write(1, no_speak_sharp); // 묵음 플래그
    EEPROM.write(2, alarm_on); // 알람 플래그
    EEPROM.write(3, meridian); // 24 플래그
    EEPROM.write(4, no_speak_s); // 묵음 시작
    EEPROM.write(5, no_speak_f); // 묵음 종료
    EEPROM.write(6, alarm_h); // 알람 시
    EEPROM.write(7, alarm_m); // 알람 분
    EEPROM.write(8, alarm_track_folder); // 알람 폴더
    EEPROM.write(9, alarm_track); // 알람 트랙
    EEPROM.write(10, time_volume); // 알람 볼륨 time_volume
    EEPROM.commit();
  } else {
    no_speak_sharp = EEPROM.read(1);
    alarm_on = EEPROM.read(2);
    meridian = EEPROM.read(3);
    no_speak_s = EEPROM.read(4);
    no_speak_f = EEPROM.read(5);
    alarm_h = EEPROM.read(6);
    alarm_m = EEPROM.read(7);
    alarm_track_folder = EEPROM.read(8);
    alarm_track = EEPROM.read(9);
    time_volume = EEPROM.read(10);
  }
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  lcd.begin();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0,0);  // row, col의 좌표로 커서를 위치 col: 0 ~ 15 row: 0, 1
  lcd.print(F("Arduino clock"));
  lcd.setCursor(0,1);
  lcd.print(F("Connecting..."));
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(F("."));
  }
  Serial.println(F("You're connected to the network"));
  Udp.begin(localPort);
  send_command(0x06, 0, 0, volume); // 기동시 초기 볼륨값 15
}

unsigned long int button_millis = 0;
bool button_hold = false;

void loop() {
  status_repeat();
  botton_delay();
  NTP_send();
  getNtpTime();
  wait_NTP_time();
  cal_time();
  adjust_time();  // 시리얼 모니터 설정 함수
  speak_time();
  delayCount();
  alarm_warning();
  alarm_send();
  if (status_not_received == true) { 
    if (dfSerial.available()) {
      if (dfSerial.peek() == 0x7E) {  // 42 00 02 01
        uint8_t dump = dfSerial.readBytes(received_df, 10);
        if (speak_finished == false) {
          if (received_df[3] == 0x42) {
            status_check = true;
            if (received_df[5] == 2 && received_df[6] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
            else if (received_df[5] == 2 && received_df[6] == 0) played = false; // 인덱스 3 = 0 정지중
            status_not_received = false;
          }
        }
      }
      else {
        uint8_t dump = dfSerial.read(); // 코드 값 아닌경우 무시
      }
    }
  }
  if (button_hold == false) {  // button 한번만 읽도록 하는 조건
    if (digitalRead(pin) == LOW) { // LOW - ON
      button_hold = true;
      button_millis = millis();
      if (alarm_time == true) {    // 알람 재생중일 때 알람 정지
        alarm_stop = true;
        alarm_time = false;
        alarm_sentence = false;
        alarm_count = 0;
        alarm_send_f = true;
        if (wait_NTP == true) got_NTP = false;
      }
      else {  // 알람이 재생중이지 않을 때 현재 시간 출력
        speak_option();
      }
    }
  }
}

void botton_delay() {
  if (button_hold == true) {
    if (millis() - button_millis >= 500) button_hold = false;
  }
}

void adjust_time() {
  if (Serial.available() > 0) {
    String temp = Serial.readStringUntil('\n');
    if (temp == "dtime") display_time();
    else if (temp == "s") { s = 0; start_time = millis(); display_t = true; }
    else if (temp == "mm") { m++; display_t = true; }
    else if (temp == "m") { m--; display_t = true; }
    else if (temp == "hh") { h++; display_t = true; }
    else if (temp == "h") { h--; display_t = true; }
    else if (temp == "24") { meridian = !meridian; EEPROM.write(3, meridian); EEPROM.commit(); display_t = true; }
    else if (temp == "yy") { yy++; display_t = true; }
    else if (temp == "y") { yy--; display_t = true; }
    else if (temp == "mon") { mm++; adjust = true; display_t = true; }
    else if (temp == "mon-") { mm--; adjust = true; display_t = true; }
    else if (temp == "dd") { dd++; adjust = true; display_t = true; }
    else if (temp == "d") { dd--; adjust = true; display_t = true; }
    else if (temp == "ww") { week_num++; adjust = true; display_t = true; }
    else if (temp == "w") { week_num--; adjust = true; display_t = true; }
    else if (temp == "ntp") got_NTP = false;
    else if (temp == "nsp") { 
      if (no_speak_s == no_speak_f) Serial.println(F("Please set NSP time"));
      else {
        no_speak_sharp = !no_speak_sharp; 
        EEPROM.write(1, no_speak_sharp);
        EEPROM.commit();
        if (no_speak_sharp) Serial.println(F("NSP ON"));
        else Serial.println(F("NSP OFF"));
        Serial.print(F("no speak time: ")); Serial.print(no_speak_s); Serial.print("-"); Serial.println(no_speak_f); 
      }
    }
    else if (temp.startsWith("atrack")) {
      String set = temp.substring(6, temp.indexOf(","));
      alarm_track_folder = set.toInt();
      set = temp.substring(temp.indexOf(",") + 1, temp.length());
      alarm_track = set.toInt();
      EEPROM.write(8, alarm_track_folder); // 알람 폴더
      EEPROM.write(9, alarm_track); // 알람 트랙
      EEPROM.commit();
      Serial.print(F("Folder: ")); Serial.print(alarm_track_folder); Serial.print(" ");
      Serial.print(F("Track: ")); Serial.println(alarm_track); 
    }
    else if (temp.startsWith("avol")) {
      temp.remove(0, 4);
      time_volume = temp.toInt();
      EEPROM.write(10, time_volume); // 알람 볼륨
      EEPROM.commit();
      Serial.print(F("alarm volume: ")); Serial.println(time_volume); 
    }
    else if (temp.startsWith("tset")) {
      String set = temp.substring(4, 6);
      h = set.toInt();
      if (h > 23) h = 0;
      set = temp.substring(6, 8);
      m = set.toInt();
      if (m > 59) m = 0;
      set = temp.substring(8, 10);
      s = set.toInt();
      if (s > 59) s = 0;
      start_time = millis();
      adjust = true;
      display_t = true;
    }
    else if (temp.startsWith("dset")) {
      String set = temp.substring(4, 8);
      yy = set.toInt();
      set = temp.substring(8, 10);
      mm = set.toInt();
      if (mm > 12) mm = 1;
      set = temp.substring(10, 12);
      dd = set.toInt();
      if (dd > 31) dd = 0;
      adjust = true;
      display_t = true;
    }
    else if (temp == "alarm") { 
      alarm_on = !alarm_on;
      EEPROM.write(2, alarm_on); // 알람 플래그
      EEPROM.commit();
      if (alarm_on) Serial.println(F("alarm on"));
      else Serial.println(F("alarm off"));
    }
    else if (temp.startsWith("aset")) {
      String set = temp.substring(4, 6);
      alarm_h = set.toInt();
      if (alarm_h > 23) alarm_h = 0;
      set = temp.substring(6, 8);
      alarm_m = set.toInt();
      if (alarm_m > 59) alarm_m = 0;
      EEPROM.write(6, alarm_h); // 알람 시
      EEPROM.write(7, alarm_m); // 알람 분
      EEPROM.commit();
      Serial.print("alarm set "); Serial.print(alarm_h); Serial.print(":"); Serial.println(alarm_m); 
    }
    else if (temp.startsWith("setnsp")) {
      String set = temp.substring(6, temp.indexOf("~"));
      no_speak_s = set.toInt();
      if (no_speak_s > 23) no_speak_s = 0;
      set = temp.substring(temp.indexOf("~") + 1, temp.length());
      no_speak_f = set.toInt();
      if (no_speak_f > 23) no_speak_f = 0;
      EEPROM.write(4, no_speak_s); // 묵음 시작
      EEPROM.write(5, no_speak_f); // 묵음 종료
      EEPROM.commit();
      Serial.print(F("no speak set: ")); Serial.print(no_speak_s); Serial.print("-"); Serial.println(no_speak_f); 
    }
    else if (temp == "test") speak_option();
    else if (temp == "stop") { 
      alarm_stop = true;
      alarm_time = false;
      alarm_sentence = false;
      alarm_count = 0;
      alarm_send_f = true;
      if (wait_NTP == true) got_NTP = false;
    }
    else if (temp == "play") send_command(0x0F, 0, 2, 6);
    else if (temp == "pause") send_command(0x0E, 1, 0, 0);
    else if (temp == "previous") send_command(0x02, 1, 0, 0);
    else if (temp == "next") send_command(0x01, 1, 0, 0);
    else if (temp == "vv") { if(volume < 30) volume++; send_command(0x06, 0, 0, volume); }
    else if (temp == "v") { if(volume > 0) volume--; send_command(0x06, 0, 0, volume); }
    else if (temp == "reset") send_command(0x0C, 0, 0, 0);
    if (m >= 60) m = 0;
    else if (m < 0) m = 59;
    if (h >= 24) h = 0; 
    else if (h < 0) h = 23; 
    if (h < 12) now_am = true;
    else now_am = false; 
    hm = h; 
    if(meridian == true && hm == 12) { now_am = false; }
    else if (meridian == true && hm > 12) { hm = hm - 12; }
    cal_dd();
    if (display_t == true) { display_time_serial(); display_t = false; } // 시리얼 모니터 출력
  }
}

void speak_option() {
  speak_failed();
  if (m == 0 && s != 0) {
    sharp = true;
    if (h == 0) time_sharp = 2212;
    else if (h < 13 && h > 0) time_sharp = 2200 + h;
    else time_sharp = 2200 + (h - 12);
  }
  else {
    sharp = false;
    if (h == 0) hour_a = 1012;
    else if (h < 13 && h > 0) hour_a = 1000 + h;
    else hour_a = 1000 + (h - 12);
    if (h < 12) am = 1301;
    else am = 1302;
    int temp = m%10;
    if (temp == 0 || m < 10) {
      short_sentence = true;
      min_s = 1200 + m;
    }
    else {
      short_sentence = false;
      min_w = 1100 + (m - temp);
      min_s = 1200 + temp;
    }
  }
  speak = true;
  speak_finished = false;
}

uint8_t am_s = 4;
uint8_t vol_s = 6;
uint8_t hour_a_s = 21;
uint8_t long_min_w_s = 44;
uint8_t long_min_s_s = 65;
uint8_t long_min_s_end = 96;
uint8_t short_min_s_s = 44;
uint8_t short_min_s_end = 74;
uint8_t time_sharp_s = 4;
uint8_t time_sharp_end = 80;

void status_repeat() {
  if ( status_not_received == true) {
    if (millis() - status_time >= 50) {
      status_time = millis();
      send_command(0x42, 0, 0, 0);
      Serial.print(".");
    }
  }
}

void speak_time() {
  if (speak == true) {
    if (sequence == 0 && status_check == false) {
      Serial.println("speak time");
      status_not_received = true;
      send_command(0x42, 0, 0, 0);
      sequence++;
      status_time = millis();
    }
    else if (sequence == 1 && status_check == true) { // status_not_received = false; -> status_check = true;
      if (played == false) {
        send_command(0x0F, 0, 2, 255);
        sequence++; 
        delay_set = true;
      }
      else {
        delay_set = true;
        sequence = 3;
      }
    }
    else if (sequence >= am_s && sharp == false) {
      if (sequence == am_s) { advert(am); speak = false; }
      else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
      else if (sequence == hour_a_s) { advert(hour_a); speak = false; }
      if (short_sentence == false) {
        if (sequence == long_min_w_s) { advert(min_w); speak = false; }
        else if (sequence == long_min_s_s) { advert(min_s); speak = false; }
        else if (sequence == long_min_s_end) {
          send_command(0x06, 0, 0, volume);
          speak_finished = true;
          speak = false;
          status_check = false;
          sequence = 0;
          if (wait_NTP == true) got_NTP = false;
        }
      }
      else {
        if (sequence == short_min_s_s) { advert(min_s); speak = false; }
        else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
        else if (sequence == short_min_s_end) {
          send_command(0x06, 0, 0, volume);
          speak_finished = true;
          speak = false;
          status_check = false;
          sequence = 0;
          if (wait_NTP == true) got_NTP = false;
        }
      }
    }
    else if (sequence >= time_sharp_s && sharp == true) {
      if (sequence == time_sharp_s) { advert(time_sharp); speak = false; }
      else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
      else if (sequence == time_sharp_end) {
        send_command(0x06, 0, 0, volume);
        speak_finished = true;
        speak = false;
        status_check = false;
        sequence = 0;
        time_sharp_end = 80;
        if (wait_NTP == true) got_NTP = false;
      }
    }
  }
}

void advert(int ad_num) {
  uint8_t ad_lsb = ad_num;
  uint8_t ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
}

void delayCount() {
  if (delay_set == true) {
    if (millis() - count_time >= 50) { // 시간 간격: 밀리초
      count_time = millis();
      sequence++;
      if (sharp == false) { 
        if (sequence == am_s) speak = true;
        else if (sequence == vol_s) speak = true;
        else if (sequence == hour_a_s) speak = true;
        if (short_sentence == false) {
          if (sequence == long_min_w_s) speak = true;
          else if (sequence == long_min_s_s) speak = true;
          else if (sequence == long_min_s_end) { speak = true; delay_set = false; }
        }
        else {
          if (sequence == short_min_s_s) speak = true;
          else if (sequence == short_min_s_end) { speak = true; delay_set = false; }
        }
      }
      else {
        if (sequence == time_sharp_s) speak = true;
        else if (sequence == vol_s) speak = true;
        else if (sequence == time_sharp_end)  { speak = true; delay_set = false; }
      }
    }
  }
}

uint8_t change_millis = 0; 
uint16_t millis_sec = 1000;

void cal_time() {
  if (millis() - start_time >= millis_sec) { // 시간 간격: 밀리초
    start_time = millis(); // 상기 조건을 만족할때의 밀리초를 다시 start_time에 저장하여 조건 초기화
    change_millis++;
    if (change_millis < 60) millis_sec = 1000;
    else { millis_sec = 1001; change_millis = 0; }
    s++;
    if (s == 60) {
      s = 0;
      m++;
    }
    if(m == 60) { m = 0; h++; }
    if(h == 12) { now_am = false;}
    else if (h == 24) { h = 0; now_am = true; dd++; week_num++; cal_dd(); }
    hm = h;
    if(meridian == true && hm >= 12) { now_am = false; }
    else now_am = true;
    if (meridian == true && hm > 12) { hm = hm - 12; }
    display_time();          // 크리스털 LCD 출력함수
    display_time_serial();   // 시리얼 모니터 출력함수 - 주석처리시 display_t 옵션에 의해 출력
    if (alarm_on) {
      if (alarm_h == h && alarm_m == m && s == 0){
        alarm_time = true;
        alarm_send_f = true;
      }
    }
    if (alarm_time != true) {
      if (m == 0 && s == 0) {
        if (no_speak_sharp == true) {
          if (no_speak_s == no_speak_f) {
            no_speak_sharp = false;
            speak_sharp();
          }
          else {
            if (no_speak_s > no_speak_f) {
              if (h < no_speak_s && h > no_speak_f) {
                speak_sharp();
              }
            }
            else if (no_speak_s < no_speak_f) {
              if (h < no_speak_s && h >= 0 || h > no_speak_f && h <= 24) {
                speak_sharp();
              }
            }
          }
        }
        else {
          speak_sharp();
        }
      }
    }
    if (h == 1 && m == 1 && s == 10) {
      if (alarm_time == true || speak == true) wait_NTP = true;
      else {
        got_NTP = false; 
      }
    }
  }
}

void wait_NTP_time() {
  if (wait_NTP == true) {
    got_NTP = true; 
    wait_NTP = false;
  }
}

void speak_failed() {
  if (sequence == 1 && status_check == false) { 
    sequence = 0; 
    receive_status = false; 
    speak = false;
    speak_finished = true;
  }
}

void speak_sharp() {
  speak_failed();
  if (speak_finished == true) {
    sharp = true;
    if (h == 0) time_sharp = 2112;
    else if (h < 13 && h > 0) time_sharp = 2100 + h;
    else time_sharp = 2100 + (h - 12);
    time_sharp_end = 48;
    speak = true;   
    speak_finished = false;
  }
}

void alarm_send() {
  if (alarm_send_f == true) {
    if (millis() - alarm_sequence_delay >= 50) { // 시간 간격: 밀리초
      alarm_sequence_delay = millis();
      alarm_sequence++;
      if (alarm_stop == true) {
        if (alarm_sequence == 1) send_command(0x06, 0, 0, volume);
        else if (alarm_sequence == 2) send_command(0x16, 0, 0, 0); 
        else if (alarm_sequence == 3) {
          send_command(0x15, 0, 0, 0); 
          alarm_send_f = false;
          alarm_sequence = 0;
          alarm_stop = false;
        }
      }
      else {
        if (alarm_sequence == 1) send_command(0x16, 0, 0, 0);
        else if (alarm_sequence == 2) send_command(0x06, 0, 0, time_volume); // 볼륨 30
        else if (alarm_sequence == 3) send_command(0x0F, 0, alarm_track_folder, alarm_track); // 폴더2 002번 사운드 파일
        else if (alarm_sequence == 8) {
          send_command(0x19, 0, 0, 0); // 반복재생
          alarm_sentence = true;
          alarm_count = 0;
          alarm_send_f = false;
          alarm_sequence = 0;
        }
      }
    }
  }
}

void alarm_warning() {
  if (alarm_sentence == true) {
    if (millis() - alarm_delay >= 1000) { // 시간 간격: 밀리초
      alarm_delay = millis();
      alarm_count++;
      if (alarm_count == 4) {
        advert(1401); 
      }
      else if (alarm_count == 12) {
        advert(1402);
      }
      else if (alarm_count == 25) {
        advert(1403);
      }
      else if (alarm_count == 70) {
        advert(1404);
        alarm_sentence = false;
        alarm_count = 0;
      }
    }
  }
}

void cal_dd() {
  uint8_t m_dds;
  switch (mm) {
    case 1:   m_dds = 31; break;
    case 2:   m_dds = 28; break;
    case 3:   m_dds = 31; break;
    case 4:   m_dds = 30; break;
    case 5:   m_dds = 31; break;
    case 6:   m_dds = 30; break;
    case 7:   m_dds = 31; break;
    case 8:   m_dds = 31; break;
    case 9:   m_dds = 30; break;
    case 10:  m_dds = 31; break;
    case 11:  m_dds = 30; break;
    case 12:  m_dds = 31; break;
  }
  if (adjust == false) {
    if (dd > m_dds) { dd = 1; mm++; }
    if (mm > 12) { mm = 1; yy++; }
    if (week_num > 7) week_num = 1;
    week_day_converter();
  }
  else {
    if (dd > m_dds) dd = 1;
    else if (dd < 1) dd = m_dds;
    if (mm > 12) mm = 1;
    else if (mm < 1) mm = 12;
    if (week_num > 7) week_num = 1;
    else if (week_num < 1) week_num = 7; 
    week_day_converter();
    adjust = false;
  }
}

void week_day_converter() {
  switch (week_num) {
    case 1:   week_day = "SUN"; break;
    case 2:   week_day = "MON"; break;
    case 3:   week_day = "TUE"; break;
    case 4:   week_day = "WED"; break;
    case 5:   week_day = "THU"; break;
    case 6:   week_day = "FRI"; break;
    case 7:   week_day = "SAT"; break;
  }
}

void display_time() {
  lcd.setCursor(0,0); 
  lcd.print(yy); lcd.print("."); 
  if (mm < 10) { lcd.print("0"); lcd.print(mm); lcd.print("."); }
  else { lcd.print(mm); lcd.print("."); }
  if (dd < 10) { lcd.print("0"); lcd.print(dd); lcd.print(". "); }
  else { lcd.print(dd); lcd.print(". "); }
  lcd.print(week_day); lcd.print(" ");  
  lcd.setCursor(0,1);
  if (meridian == true) {
    if (now_am == true) lcd.print("AM ");
    else lcd.print("PM ");
    if (hm < 10) { lcd.print("0"); lcd.print(hm); lcd.print(":"); }
    else { lcd.print(hm); lcd.print(":"); }
  }
  else {
    if (h < 10) { lcd.print("0"); lcd.print(h); lcd.print(":"); }
    else { lcd.print(h); lcd.print(":"); }
  }
  if (m < 10) { lcd.print("0"); lcd.print(m); lcd.print(":"); }
  else { lcd.print(m); lcd.print(":"); }
  if (s < 10) { lcd.print("0"); lcd.print(s); lcd.print("     "); }
  else { lcd.print(s); lcd.print("     "); }  // 크리스탈 LCD에는 pritln 명령어 없음
}

void display_time_serial() {
  Serial.print(yy); Serial.print("."); 
  if (mm < 10) { Serial.print("0"); Serial.print(mm); Serial.print("."); }
  else { Serial.print(mm); Serial.print("."); }
  if (dd < 10) { Serial.print("0"); Serial.print(dd); Serial.print(". "); }
  else { Serial.print(dd); Serial.print(". "); }
  Serial.print(week_day); Serial.print(" ");  
  if (meridian == true) {
    if (now_am == true) Serial.print("AM ");
    else Serial.print("PM ");
    if (hm < 10) { Serial.print("0"); Serial.print(hm); Serial.print(":"); }
    else { Serial.print(hm); Serial.print(":"); }
  }
  else {
    if (h < 10) { Serial.print("0"); Serial.print(h); Serial.print(":"); }
    else { Serial.print(h); Serial.print(":"); }
  }
  if (m < 10) { Serial.print("0"); Serial.print(m); Serial.print(":"); }
  else { Serial.print(m); Serial.print(":"); }
  if (s < 10) { Serial.print("0"); Serial.println(s); }
  else Serial.println(s);
}

unsigned long startMs = 0;
uint8_t kill_ntp = 0;

void NTP_send() {
  if (got_NTP == false) { // NTP 수신값이 없으면 2초마다 요청
    if (millis() - startMs >=  2000) {
      startMs = millis();
      Udp.begin(localPort);
      kill_ntp++;
      sendNTPpacket(timeServer);
      if (kill_ntp == 10) { got_NTP = false; kill_ntp = 0; }
    }
  }
}

void getNtpTime() {
  if (got_NTP == false) {
    if (Udp.available()) {
      if (Udp.parsePacket()) {
        Udp.read(packetBuffer, NTP_PACKET_SIZE);
        unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
        unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
        unsigned long secsSince1900 = highWord << 16 | lowWord;
        const unsigned long seventyYears = 2208988800UL;
        unsigned long epoch = secsSince1900 - seventyYears + timeZone * 3600;
        yy = year(epoch);
        mm = month(epoch);
        dd = day(epoch); 
        h = hour(epoch); 
        m = minute(epoch); 
        s = second(epoch); 
        week_num = weekday(epoch);
        week_day_converter();
        got_NTP = true;
        wait_NTP = false;
      }
    }
  }
}

void sendNTPpacket(char *ntpSrv) {
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  Udp.beginPacket(ntpSrv, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

이 예제 코드는 이전 글들의 코드를 그대로 이용하여 수정한 것이다. 코드 내용이 이해가 안 된다면 아래의 앞선 글들을 참조하기 바란다. 

아두이노시계 예제, ESP01 WiFi 이용 시간 동기화 하기

아두이노 말하는알람시계 예제 - DFPlayer

말하는알람시계 - 블루투스 연결 및 시간 동기화, DFPlayer 제어 

 

 

NodeMcu는 WiFi.mode(WIFI_AP_STA); 설정 함수를 통해 와이파이 Station 모드와 Soft AP모드를 동시에 이용할 수 있다. Soft AP모드를 이용하여 시리얼 모니터에서 제어하는 것처럼 웹브라우저에서 NodeMcu를 제어해 보자.

 

NodeMcu 와이파이 Soft AP 코드 

// Soft AP 설정
#define AP_SSID  "Esp12"
#define AP_PASS  "1234test"
WiFiServer server(80);
WiFiClient client;

// Soft AP용 변수
String income_wifi = ""; // 와이파이 수신 스트링 저장 변수

// setup() 함수
WiFi.mode(WIFI_AP_STA); // AP모드와 Station 모드 동시 사용
WiFi.softAP(AP_SSID, AP_PASS);  // WiFi.softAP(ssid, password);
IPAddress myIP = WiFi.softAPIP();
Serial.print(F("AP IP address: "));
Serial.println(myIP);
server.begin();

// loop() 함수
wifi_delay();

unsigned long int one_millis = 0;

void wifi_delay() { // 30 밀리초 마다 client 연결 확인 및 데이터 수신
  if (millis() - one_millis > 30) {
    one_millis = millis();
    WiFiClient client;
    wifi_read();
  }
}

void wifi_read() { // wifi 데이터 수신
  client = server.available(); // 서버가 작동하고 있으면 client 생성
  if(client.available()) {     // 클라이언트에 데이터가 있으면
    income_wifi = client.readStringUntil('\r');
    String wifi_temp = income_wifi.substring(income_wifi.indexOf("GET /")+5, income_wifi.indexOf("HTTP/1.1")-1);
    if (wifi_temp == "dtime") display_time_serial(); //
    else if (wifi_temp == "s") { s = 0; start_time = millis(); display_t = true; }
    else if (wifi_temp == "mm") { m++; display_t = true; }
    else if (wifi_temp == "m") { m--; display_t = true; }
    else if (wifi_temp == "hh") { h++; display_t = true; }
    else if (wifi_temp == "h") { h--; display_t = true; }
    else if (wifi_temp == "24") { meridian = !meridian; EEPROM.write(3, meridian); EEPROM.commit(); display_t = true; }
    else if (wifi_temp == "yy") { yy++; display_t = true; }
    else if (wifi_temp == "y") { yy--; display_t = true; }
    else if (wifi_temp == "mon") { mm++; adjust = true; display_t = true; }
    else if (wifi_temp == "mon-") { mm--; adjust = true; display_t = true; }
    else if (wifi_temp == "dd") { dd++; adjust = true; display_t = true; }
    else if (wifi_temp == "d") { dd--; adjust = true; display_t = true; }
    else if (wifi_temp == "ww") { week_num++; adjust = true; display_t = true; }
    else if (wifi_temp == "w") { week_num--; adjust = true; display_t = true; }
    else if (wifi_temp == "ntp") got_NTP = false;
    else if (wifi_temp == "nsp") { 
      if (no_speak_s == no_speak_f) {
          client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
          client.println(F("Please set NSP time"));
        }
      else {
        no_speak_sharp = !no_speak_sharp; 
        EEPROM.write(1, no_speak_sharp); 
        EEPROM.commit();
        client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
        if (no_speak_sharp) client.print(F("NSP ON "));
        else client.print(F("NSP OFF "));
        client.print(F("no speak time: ")); client.print(no_speak_s); client.print("-"); client.println(no_speak_f); 
      }
    }
    else if (wifi_temp.startsWith("atrack")) {
      String set = wifi_temp.substring(6, wifi_temp.indexOf(","));
      alarm_track_folder = set.toInt();
      set = wifi_temp.substring(wifi_temp.indexOf(",") + 1, wifi_temp.length());
      alarm_track = set.toInt();
      EEPROM.write(8, alarm_track_folder); // 알람 폴더
      EEPROM.write(9, alarm_track); // 알람 트랙
      EEPROM.commit();
      client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
      client.print(F("Folder: ")); client.print(alarm_track_folder); client.print(" ");
      client.print(F("Track: ")); client.print(alarm_track); 
    }
    else if (wifi_temp.startsWith("avol")) {
      wifi_temp.remove(0, 4);
      time_volume = wifi_temp.toInt();
      EEPROM.write(10, time_volume); // 알람 볼륨
      EEPROM.commit();
      client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
      client.print(F("alarm volume: ")); client.print(time_volume); 
    }
    else if (wifi_temp.startsWith("tset")) {
      String set = wifi_temp.substring(4, 6);
      h = set.toInt();
      if (h > 23) h = 0;
      set = wifi_temp.substring(6, 8);
      m = set.toInt();
      if (m > 59) m = 0;
      set = wifi_temp.substring(8, 10);
      s = set.toInt();
      if (s > 59) s = 0;
      start_time = millis();
      adjust = true;
      display_t = true;
    }
    else if (wifi_temp.startsWith("dset")) {
      String set = wifi_temp.substring(4, 8);
      yy = set.toInt();
      set = wifi_temp.substring(8, 10);
      mm = set.toInt();
      if (mm > 12) mm = 1;
      set = wifi_temp.substring(10, 12);
      dd = set.toInt();
      if (dd > 31) dd = 0;
      adjust = true;
      display_t = true;
    }
    else if (wifi_temp == "alarm") { 
      alarm_on = !alarm_on;
      EEPROM.write(2, alarm_on); // 알람 플래그
      EEPROM.commit();
      client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
      if (alarm_on) client.print(F("alarm on"));
      else client.print(F("alarm off"));
    }
    else if (wifi_temp.startsWith("aset")) {
      String set = wifi_temp.substring(4, 6);
      alarm_h = set.toInt();
      if (alarm_h > 23) alarm_h = 0;
      set = wifi_temp.substring(6, 8);
      alarm_m = set.toInt();
      if (alarm_m > 59) alarm_m = 0;
      EEPROM.write(6, alarm_h); // 알람 시
      EEPROM.write(7, alarm_m); // 알람 분
      EEPROM.commit();
      client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
      client.print(F("alarm set ")); client.print(alarm_h); client.print(F(":")); client.print(alarm_m); 
    }
    else if (wifi_temp.startsWith("setnsp")) {
      String set = wifi_temp.substring(6, wifi_temp.indexOf("~"));
      no_speak_s = set.toInt();
      if (no_speak_s > 23) no_speak_s = 0;
      set = wifi_temp.substring(wifi_temp.indexOf("~") + 1, wifi_temp.length());
      no_speak_f = set.toInt();
      if (no_speak_f > 23) no_speak_f = 0;
      EEPROM.write(4, no_speak_s); // 묵음 시작
      EEPROM.write(5, no_speak_f); // 묵음 종료
      EEPROM.commit();
      client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
      client.print(F("no speak set: ")); client.print(no_speak_s); client.print("-"); client.print(no_speak_f); 
    }
    else if (wifi_temp == "test") speak_option();
    else if (wifi_temp == "stop") { 
      alarm_stop = true;
      alarm_time = false;
      alarm_sentence = false;
      alarm_count = 0;
      alarm_send_f = true;
      if (wait_NTP == true) got_NTP = false;
    }
    else if (wifi_temp == "play") send_command(0x0F, 0, 2, 6);
    else if (wifi_temp == "pause") send_command(0x0E, 1, 0, 0);
    else if (wifi_temp == "previous") send_command(0x02, 1, 0, 0);
    else if (wifi_temp == "next") send_command(0x01, 1, 0, 0);
    else if (wifi_temp == "vv") { if(volume < 30) volume++; send_command(0x06, 0, 0, volume); }
    else if (wifi_temp == "v") { if(volume > 0) volume--; send_command(0x06, 0, 0, volume); }
    else if (wifi_temp == "reset") send_command(0x0C, 0, 0, 0);
    if (m >= 60) m = 0;
    else if (m < 0) m = 59;
    if (h >= 24) h = 0; 
    else if (h < 0) h = 23; 
    if (h < 12) now_am = true;
    else now_am = false; 
    hm = h; 
    if(meridian == true && hm == 12) { now_am = false; }
    else if (meridian == true && hm > 12) { hm = hm - 12; }
    cal_dd();
    if (display_t == true) { display_time_serial(); display_t = false; } // display_time();
  }
  income_wifi = "";
}

 

아래 코드를 NodeMcu에 업로드 해주자.

3.NodeMcu_clock_ntp_softap.ino
0.03MB

업로드 후 스마트폰에서 와이파이 연결 창을 열고 Esp12가 검색되면 클릭하여 설정된 암호를 입력하고 NodeMcu Soft AP에 연결을 해준다. 

웹브라우저를 열고 주소창에 "192.168.4.1/play"를 입력하면 코드상 지정된 2번 폴더 006 파일이 재생되게 된다.   

"192.168.4.1/test"를 입력하면 현재 시간이 출력되는 걸 확인할 수 있다. 

"192.168.4.1/alarm"를 입력하면 웹브라우저 창에서 "alarm on" 메시지를 볼 수 있다. (글자 크기가 너무 작아 확대해야 함-글자 크기 설정이 안 되어 있어서 작게 표시된다. )

웹브라우저를 통해 NodeMcu가 컴퓨터에 연결이 안 되어 있더라도 시리얼 모니터에서 제어하는 것과 같이 각종 세팅을 할 수 있고 제어를 할 수 있다. 하지만 실제로 해보면 너무 불편하다는 걸 느낄 수 있다. 안드로이드 앱 DFcontroller를 이용하여 제어를 해보자. 

 

DFcontroller - DFPlayer mini 원격제어 안드로이드 앱 버전 2.2 다운로드

DFcontroller 매뉴얼

 

안드로이드 앱 DFcontroller를 이용하여 NodeMcu를 제어하기 위해서는 DFcontroller의 제어용 프로토콜을 알아야 한다. 

 

DFcontroller의 제어용 프로토콜

1. DFPlayer 제어

    DFPlayer의 매뉴얼상 프로토콜을 따른다.

 

2. 와이파이 : 앱 송신 -> 아두이노 수신

    디지털 버튼: %%F0 + 1byte number + %%F1

    Alarm: %%F9 + 21:20 + \n(새줄)

    아두이노 응답 요청: %%F5


3. 와이파이: 아두이노 송신 -> 앱 수신

    디지털 버튼: %%F0 + 스트링 값 + %%P1

 

안드로이드 앱 DFcontroller에 관한 더 자세한 사항은 아래 글을 참조하기 바란다. 

DFcontroller - DFPlayer mini 원격제어 안드로이드 앱 버전 1.8

arduino bluetooth controller PWM - 아두이노 원격제어 안드로이드 앱 버전 3.4

 

상기 프로토콜에 따라 아래와 같이 loop() 함수, wifi_read(); 함수에 코드를 추가해 주었다. 

// loop() 함수 수정 및 추가
// 수정전
if (status_not_received == true) { 
  if (dfSerial.available()) {
    if (dfSerial.peek() == 0x7E) {  // 42 00 02 01
      uint8_t dump = dfSerial.readBytes(received_df, 10);
      if (speak_finished == false) {
        if (received_df[3] == 0x42) {
          status_check = true;
          if (received_df[5] == 2 && received_df[6] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
          else if (received_df[5] == 2 && received_df[6] == 0) played = false; // 인덱스 3 = 0 정지중
          status_not_received = false;
        }
      }
    }
    else {
      uint8_t dump = dfSerial.read(); // 코드 값 아닌경우 무시
    }
  }
}
  
// 수정후
if (wait_receive == true || status_not_received == true) { // DFPlayer 프로토콜 및 DFPlayer 재생 여부 수신시 확인
  if (dfSerial.available()) {
    if (dfSerial.peek() == 0x7E) {  // 42 00 02 01
      uint8_t dump = dfSerial.readBytes(received_df, 10);
      if (speak_finished == false) { // 음성 출력 코드에 의한 수신일 경우 
        if (received_df[3] == 0x42) {
          status_check = true;
          if (received_df[5] == 2 && received_df[6] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
          else if (received_df[5] == 2 && received_df[6] == 0) played = false; // 인덱스 3 = 0 정지중
          status_not_received = false;
        }
      }
      else if (wait_receive == true) { // DFPlayer 프로토콜에 의한 수신일 경우
        df_count++;                    // 몇번째 수신값인지 카운트
        if (df_count == 2) wait_receive = false; // play 종료시 두번 전송하는 코드값 수신하면 종료 
        convert = true;                // 카운트에 상관없이 헥사값을 스트링으로 변환
      }
    }
    else {
      uint8_t dump = dfSerial.read(); // 코드 값 아닌경우 무시
    }
  }
}

// DFplayer로 부터 수신된 코드 중 음성 출력 관련이 아닌 경우 코드값 스트링으로 변환 
if (convert == true) {
  if (df_count == 1) { // 첫번째 수신인 경우 
    for(int i = 0; i < 10; i++) {
      if (received_df[i] < 16) df_value1 += String('0') + String(received_df[i], HEX);
      else df_value1 += String(received_df[i], HEX);
    }
    received_df[10] = {0,};
  }
  else if (df_count == 2) { // 두번째 수신인 경우
    for(int i = 0; i < 10; i++) {
      if (received_df[i] < 16) df_value2 += String('0') + String(received_df[i], HEX);
      else df_value2 += String(received_df[i], HEX);
    }
    received_df[10] = {0,};
    df_count = 0; // 두번째 수신이면 초기화
  }
  convert = false;
}

// wifi_read() 함수
// DFPlayer 프로토콜 수신
if (income_wifi.indexOf("7EFF06") != -1) {          // DFPlayer 시작 코드가 있으면
  String wifi_temp = "0x" + income_wifi.substring(income_wifi.indexOf("7EFF06")+6, income_wifi.indexOf("7EFF06")+8);
  wifi_temp.toCharArray(buf, 5);
  cmd_d = strtol(buf, NULL, 16);
  wifi_temp = "0x" + income_wifi.substring(income_wifi.indexOf("7EFF06")+8, income_wifi.indexOf("7EFF06")+10);
  wifi_temp.toCharArray(buf, 5);
  ack_d = strtol(buf, NULL, 16);
  wifi_temp = "0x" + income_wifi.substring(income_wifi.indexOf("7EFF06")+10, income_wifi.indexOf("7EFF06")+12);
  wifi_temp.toCharArray(buf, 5);
  msb_d = strtol(buf, NULL, 16);
  wifi_temp = "0x" + income_wifi.substring(income_wifi.indexOf("7EFF06")+12, income_wifi.indexOf("7EFF06")+14);
  wifi_temp.toCharArray(buf, 5);
  lsb_d = strtol(buf, NULL, 16);
  send_command(cmd_d, ack_d, msb_d, lsb_d);
  if (cmd_d == 0x06) { volume = lsb_d; Serial.println(volume); }
  wait_receive = true; // DFPlayer로 부터 수신 대기
}

// 디지털 핀 프로토콜 수신
else if (income_wifi.indexOf("%%F1") != -1) {          // 종료문자가 있으면
  if (income_wifi.indexOf("%%F0") != -1) {        // 디지털 핀 값 수신
    String wifi_temp = income_wifi.substring(income_wifi.indexOf("%%F0")+4, income_wifi.indexOf("%%F1"));
    pin_val = wifi_temp.toInt();
    pin_control();
  }
}

// 스케쥴에 따른 응답요청 프로토콜 수신
else if (income_wifi.indexOf("%%F5") != -1) { // 스케쥴 접속확인 응답, 텍스트 및 로컬값 전송
  client.println(F("HTTP/1.1 200 OK\r\n"));      // 스케쥴 웹요청시 응답 헤더 
  client_out_check = 0;
  if (send_button_status == false) { 
    button_status_count++;
    if (button_status_count == 8) {
      clock_status(); 
      send_button_status = true; 
      client_in = true;
      Serial.print("send button status");
    }
  }
  if (df_value1 != "") { 
    client.print(df_value1);  
    df_value1 = "";  
    df_count = 0; // 값 전송시 몇번째 수신인지 확인 변수 초기화
  }
  if (df_value2 != "") { 
    client.print(df_value2);  
    df_value2 = "";  
    df_count = 0;
  }
  if (send_pin != "") {     // 전송할 핀 상태 값이 있으면
    client.print(send_pin); 
    send_pin = "";
  }
  if (send_text != "") {    // 전송할 텍스트가 있으면
    client.print(send_text);
    send_text = "";
  }
}

// 알람 세팅값 프로토콜 수신
if (income_wifi.indexOf("%%F9") != -1) { // 알람 세팅값 수신
  String wifi_temp = income_wifi.substring(income_wifi.indexOf("%%F9")+4, income_wifi.indexOf("\n"));
  String set = wifi_temp.substring(0, wifi_temp.indexOf(":"));
  alarm_h = set.toInt();
  set = wifi_temp.substring(wifi_temp.indexOf(":") + 1, wifi_temp.length());
  alarm_m = set.toInt();
  EEPROM.write(6, alarm_h); // 알람 시
  EEPROM.write(7, alarm_m); // 알람 분
  EEPROM.commit();
  client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
  client.print(F("alarm set ")); client.print(alarm_h); client.print(F(":")); client.print(alarm_m);
}

// DFcontroller가 연결되면 알람ON, 묵음 사용, 24모드등의 버튼 상태값 전송
// 연결이 종료되면 재 연결시 재 전송하도록 전송 코드 초기화

// DFcontroller 접속 여부 확인용 변수 
uint8_t button_status_count = 0;    
bool send_button_status = false;
uint8_t client_out_check = 0;
bool client_in = false;

// wifi_delay() 함수 추가 코드
if (client_in == true) {  // DFcontroller 연결된 상태에서
  if(!client.available()) {  // client가 연결이 안되어 있으면 카운터 증가
    client_out_check++;
    if (client_out_check > 100) { // 100 이상 되면 전송 코드 초기화
       button_status_count = 0;
       client_in = false;
       send_button_status = false;
       Serial.println("Client disconnected");
    }
  }
}

// wifi_read() 함수 추가 코드
client_out_check = 0; // %%F5 프로토콜이 확인되면 초기화
if (send_button_status == false) { // 버튼 상태 전송이 완료되지 않았으면
  button_status_count++;           // DFcontroller의 DFPlayer 초기 데이터 수신 절차 카운트
  if (button_status_count == 8) {  // DFPlayer 초기 데이터 수신 종료후
    clock_status();                // 버튼 상태 전송
    send_button_status = true;     // 버튼 전송 완료 플래그
    client_in = true;              // DFcontroller 연결 상태 확인 시작
    Serial.print("sent button status");
  }
}

// 버튼 상태 전송 함수
void clock_status() {  // 와이파이 연결시 버튼 및 볼륨 상태값 전송
  String volume_s = "";
  if (volume < 16) volume_s = '0' + String(volume, HEX);
  else volume_s = String(volume, HEX);
  df_value1 = "7eff06430000" + volume_s + "0000ef"; // 현재 볼륨
  if (no_speak_sharp == false) send_pin_echo(10);   // 묵음 사용 상태
  else send_pin_echo(11);
  if (alarm_on == false) send_pin_echo(20);         // 알람 ON/OFF 상태
  else send_pin_echo(21);
  if (meridian == true) send_pin_echo(30);          // 24 모드 사용 상태
  else send_pin_echo(31);
}
// ----------------------------------------------------------------------------------- //

// 버튼값 수신시 실행 코드
void pin_control() {
  if (pin_val == 11 && no_speak_s == no_speak_f) {
    pin_val = 0;
    client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
    send_pin_echo(10);
    client.print(F("Set NSP ")); client.print(no_speak_s); client.print(F("-")); client.print(no_speak_f);
  }
  if (pin_val != 0) {
    switch (pin_val) {
      case 11: no_speak_sharp = true; 
               EEPROM.write(1, no_speak_sharp); // 묵음 플래그
               EEPROM.commit();
               client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
               client.print(F("NSP ON "));
               client.print(no_speak_s);
               client.print(F("-"));
               client.print(no_speak_f);
                  break;
      case 10: no_speak_sharp = false; 
               EEPROM.write(1, no_speak_sharp); // 묵음 플래그
               EEPROM.commit();
               client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
               client.print(F("NSP OFF "));
               client.print(no_speak_s);
               client.print(F("-"));
               client.print(no_speak_f);
                  break;
      case 21: alarm_on = true;
               EEPROM.write(2, alarm_on); // 알람 플래그
               EEPROM.commit();
               client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
               client.print(F("Alarm ON "));
               client.print(alarm_h);
               client.print(F(":"));
               client.print(alarm_m);
                  break;
      case 20: alarm_on = false;
               EEPROM.write(2, alarm_on); // 알람 플래그
               EEPROM.commit();
               client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
               client.print(F("Alarm OFF "));
               client.print(alarm_h);
               client.print(F(":"));
               client.print(alarm_m);
                  break;
      case 31: meridian = false;
               EEPROM.write(3, meridian); // 24 플래그
               EEPROM.commit();
                  break;
      case 30: meridian = true;
               EEPROM.write(3, meridian); // 24 플래그
               EEPROM.commit();
                  break;
      case 41: client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
               client.print(F("Folder: "));
               client.print(alarm_track_folder);
               client.print(" ");
               client.print(F("Track: "));
               client.print(alarm_track);
                  break;
      case 51: alarm_stop = true;
               alarm_time = false;
               alarm_sentence = false;
               alarm_count = 0;
               alarm_send_f = true;
               if (wait_NTP == true) got_NTP = false;
                  break;
      case 61: client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
               client.print(F("alarm volume: "));
               client.print(time_volume);
                  break;
      case 71: speak_option();
                  break;
    }
  pin_val = 0;
  }
}

// 버튼 상태 전송 함수
void send_pin_echo(uint8_t pin_val){  // 디지털 버튼 상태값 전송
  String s_temp ="";
  s_temp += "%%F0";
  s_temp += pin_val;
  s_temp += "%%P1";
  if (client.connected())  client.print(s_temp); // 사용자 웹요청 응답
  else  send_pin = s_temp;                       // 스케쥴 웹요청 응답
}

 

 

안드로이드 앱 DFcontroller 설정

(*** 앱에서 와이파이 연결은 안드로이드8 [갤럭시S7]이하 버전에서만 작동합니다 ***)

앱 실행 후 블루투스 아이콘을 클릭

아래 그림대로 체크박스 설정 -> 설정 후 블루투스 아이콘 클릭하여 아두이노 메인화면으로 이동 

디스플레이 아이콘 클릭하고 버튼 라벨 및 버튼 표시 설정

버튼 속성 아이콘을 클릭하고 "echo text color" 체크 해제 및 버튼 속성 설정

아두이노 아이콘 클릭하여 아두이노 메인화면 진입 후 와이파이 아이콘 클릭

와이파이 연결화면에서 NodeMcu의 Soft AP 연결 확인(연결이 안 되어 있으면 연결)

Soft AP의 IP주소 192.168.4.1 입력 후 Confirm 클릭

NodeMcu의 Soft AP에 접속이 되면 아래와 같이 표시된다. 

Soft AP에 접속하여 데이터 수신이 되면 안드로이드 앱은 DFPlayer로부터 트랙 정보를 수신하고 트랙 목록을 앱에 표시한다.

알람 설정 

 

 

아두이노 메인 화면 아래쪽에 화살표 아이콘을 볼 수 있다. 아이콘을 클릭하여 표시되는 아이콘 중알람시계 아이콘을 클릭하고 설정하면 된다. 

아두이노 제어 메인 화면의 텍스트 입력창에서 텍스트 명령을 하여 시리얼 모니터에서 처럼 각종 설정을 할 수 있다. 이때에는 전송옵션이 "NO"(옵션없음)인 상태에서 전송하자. 블루투스 연결일 경우에는 "NL"(새줄)로 해야했지만 와이파이를 통해 전송되는 데이터의 마지막에는 새줄이 자동으로 붙는다.

 

아래 코드를 NodeMcu에 업로드하고 시간 출력이 잘되는지 확인해보고 DFPlayer 내 사운드 파일도 제어해보자.

4.NodeMcu_clock_dfplayer_DFcontroller.ino
0.04MB

스누즈 기능을 추가해 보자. 

버튼을 통해 알람을 정지했을 경우 알람이 완전히 정지 하는대신 스누즈 기능이 작동시켜 설정시간후에 다시 알람이 울리도록 하고 안드로이드 앱 알람 정지 버튼이나 시리얼 모니터상의 "stop" 명령을 주었을 때에만 알람이 완전 정지하도록 해보자. 스누즈 ON/OFF 기능이 있으므로 EEPROM에 저장공간을 1byte 추가시켜주어야 한다.  

#define EEPROM_SIZE 13 // 13 byte

// setup() 함수 EEPROM 코드 추가
EEPROM.write(11, snooze_on);  // 스누즈 플래그
EEPROM.write(12, snooze_set); // 스누즈 시간
snooze_on = EEPROM.read(11);
snooze_set = EEPROM.read(12);

// loop() 함수, 버튼 작동 알람 정지시 코드에 추가
if (snooze_on) snooze();  // 스누즈 작동

// 스누즈용 변수
bool snooze_on = false;    // 스누즈 기능 ON/OFF 플래그
bool snooze_time = false; 
uint8_t snooze_h = 0;
uint8_t snooze_m = 0;
uint8_t snooze_set = 5;    // 스누즈 시간

// cal_time() 함수 추가
if (snooze_time) {
  if (snooze_h == h && snooze_m == m && s == 0){
    alarm_time = true;
    alarm_send_f = true;
  }
}

// 사용자 함수 추가
void snooze() {
  snooze_time = true;
  snooze_m = m; // 버튼 정지시 현재 시간
  snooze_h = h;
  snooze_m = snooze_m + snooze_set;
  if (snooze_m > 59) {
    snooze_h = snooze_h + 1;
    snooze_m = snooze_m - 60;
    if (snooze_h > 23) snooze_h = 0;
  }
}

// wifi_read() 함수 "stop" 코드에 추가
// adjust_time() 함수 "stop" 코드에 추가
// pin_control() 함수 case 51 코드에 추가
snooze_time = false;

// adjust_time() 함수 코드 추가
else if (temp == "snooze") { 
  snooze_on = !snooze_on;
  EEPROM.write(11, snooze_on); // 알람 플래그
  EEPROM.commit();
  if (snooze_on) Serial.println(F("Snooze ON"));
  else Serial.println(F("Snooze OFF"));
}
else if (temp.startsWith("zset")) { // 스누즈 시간 설정
  String set = temp.substring(4, 6);
  snooze_set = set.toInt();
  if (snooze_set > 59) snooze_set = 59; 
  EEPROM.write(12, snooze_set); // 알람 플래그
  EEPROM.commit();
  Serial.println(F("snooze_set Time: ")); Serial.println(snooze_set);
}

// wifi_read() 함수 코드 추가
else if (wifi_temp == "snooze") { 
  snooze_on = !snooze_on;
  EEPROM.write(11, snooze_on); // 알람 플래그
  EEPROM.commit();
  client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
  if (alarm_on) client.print(F("Snooze ON"));
  else client.print(F("Snooze OFF"));
}
else if (wifi_temp.startsWith("zset")) { // 스누즈 시간 설정
  String set = wifi_temp.substring(4, 6);
  snooze_set = set.toInt();
  if (snooze_set > 59) snooze_set = 59; 
  EEPROM.write(12, snooze_set); // 알람 플래그
  EEPROM.commit();
  client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
  client.print(F("snooze_set Time: ")); client.println(snooze_set);
}

// pin_control() 함수 코드 추가
case 81: snooze_on = true;
         EEPROM.write(11, snooze_on); // 스누즈 플래그
         EEPROM.commit();
         client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
         client.print(F("Snooze ON"));
            break;
case 80: snooze_on = false;
         EEPROM.write(11, snooze_on); // 알람 플래그
         EEPROM.commit();
         client.println(F("HTTP/1.1 200 OK\r\n")); // 사용자 웹요청시 응답 헤더
         client.print(F("Snooze OFF"));
            break;

DFcontroller 스누즈 버튼 설정

아래 코드를 NodeMcu에 업로드하고 스누즈 기능을 테스트해보자.

5.NodeMcu_clock_dfplayer_DFcontroller_snooze.ino
0.04MB

NodeMcu를 이용한 말하는 알람시계 코드가 완성되었다. 하지만 와이파이와 Software Serial 충돌 문제가 남아 있는 상태이다. 또한 코드가 완성되었으므로 시리얼 모니터를 사용할 필요는 없다. 따라서 DFPlayer를 Hardware Serial에 연결하여 작동시켜 보자.

 

DFPlayer RX -> NodeMcu TX 핀

DFPlayer TX -> NodeMcu RX 핀

 

아래 코드는 시리얼 모니터 출력 코드를 삭제하고 Software Serial 관련 코드는 Hardware Serial로 수정한 코드이다. 코드를 NodeMcu에 업로드할 때에는 NodeMcu TX/RX 핀에서 DFPlayer RX/TX 핀을 제거한 상태에서 업로드해야 한다. 

6.NodeMcu_clock_dfplayer_DFcontroller_snooze_hdserial.ino
0.04MB

관련 글

[arduino] - DFplayer - 아두이노 사운드 모듈

[arduino] - NodeMcu(ESP8266)에서 DFplayer를 제어하는 코드

[arduino] - ESP32(DevKit)로 DFplayer 제어하기

[arduino] - 안드로이드 앱 DFcontroller를 이용하여 DFplayer 제어

[arduino] - 아두이노시계 예제, ESP01 WiFi 이용 시간 동기화 하기

[arduino] - 아두이노 말하는알람시계 예제 - DFPlayer

[arduino] - 말하는알람시계 - 블루투스 연결 및 시간 동기화, DFPlayer 제어

[arduino] - NodeMcu - 말하는 알람시계, wifi이용 시간 동기화 및 DFPlayer 원격제어

[arduino] - ESP8266 / ESP32 - time.h 라이브러리 이용 시간 출력 및 NTP 서버 시간 동기화 하기

 

DFcontroller - DFPlayer mini 원격제어 안드로이드 앱 버전 2.2 다운로드

DFcontroller 매뉴얼

 

 

 

반응형

+ Recent posts