Skip to content

dysv17f.tc

DY-SV17F MP3 Player Driver (Serial TX)

Source on GitHub

// DY-SV17F MP3 Player Driver (Serial TX)
// UART 9600 baud, TX only
// Packet: [0xAA] [CMD] [data_len] [data...] [checksum]
// Checksum = sum of all bytes (incl. 0xAA) & 0xFF
// Play by filename: cmd 0x08, data = [device][path_transformed]
// Path: /prefix, dots→stars, uppercase. E.g. "Sound.mp3" → "/SOUND*MP3"
// Console: MP3 Play Sound.mp3, MP3 Stop, MP3 Pause, MP3 Next, MP3 Prev, MP3 Vol 20

#define DY_DEVICE  2    // 0=USB, 1=SD, 2=FLASH (built-in)

int dy_txpin = 10;      // TX pin — selectable via web UI
int dy_ok = 0;
int dy_vol = 15;
char dy_file[32];
char dy_pkt[34];    // global packet buffer for dy_send

// Send raw packet: [0xAA][cmd][len][data from dy_pkt][checksum]
// Builds complete packet in dy_buf and sends as single buffer write
char dy_buf[38];    // complete packet buffer (header + data + checksum)

void dy_send(int cmd, int len) {
    dy_buf[0] = 0xAA;
    dy_buf[1] = cmd;
    dy_buf[2] = len;
    int cs = 0xAA + cmd + len;
    int i = 0;
    while (i < len) {
        dy_buf[3 + i] = dy_pkt[i];
        cs = cs + dy_pkt[i];
        i++;
    }
    dy_buf[3 + len] = cs & 0xFF;
    serialWriteBytes(dy_buf, len + 4);
}

// Simple commands (no data)
void dy_play()  { dy_send(0x02, 0); }
void dy_stop()  { dy_send(0x04, 0); }
void dy_pause() { dy_send(0x03, 0); }
void dy_next()  { dy_send(0x06, 0); }
void dy_prev()  { dy_send(0x05, 0); }

void dy_volume(int v) {
    if (v < 0) v = 0;
    if (v > 30) v = 30;
    dy_vol = v;
    dy_pkt[0] = v;
    dy_send(0x13, 1);
}

// Play file by name on specified device
// Reads filename from dy_file global
// Transforms: dots→stars, uppercase
// Sends cmd 0x08: [device][transformed_path]
void dy_play_file() {
    dy_pkt[0] = DY_DEVICE;
    int i = 0;
    int o = 1;
    // Auto-prepend '/' if missing (DY-SV17F needs absolute path)
    if (dy_file[0] != '/') {
        dy_pkt[o] = '/';
        o++;
    }
    while (dy_file[i] && o < 33) {
        int c = dy_file[i];
        if (c == '.') {
            dy_pkt[o] = '*';
        } else if (c >= 'a' && c <= 'z') {
            dy_pkt[o] = c - 32;  // uppercase
        } else {
            dy_pkt[o] = c;
        }
        o++;
        i++;
    }
    dy_send(0x08, o);
}

void Command(char cmd[]) {
    char buf[64];
    char arg[32];

    // Tasmota uppercases the topic, so subcommands arrive as PLAY, STOP, etc.
    // Data after the space (filenames, numbers) keeps original case.
    if (strFind(cmd, "PLAY") == 0) {
        // "PLAY" or "PLAY Sound.mp3"
        if (strlen(cmd) > 5) {
            strSub(dy_file, cmd, 5, 0);
            dy_play_file();
            sprintf(buf, "Playing %s", dy_file);
            responseCmnd(buf);
        } else {
            dy_play();
            responseCmnd("Playing");
        }
    } else if (strFind(cmd, "STOP") == 0) {
        dy_stop();
        responseCmnd("Stopped");
    } else if (strFind(cmd, "PAUSE") == 0) {
        dy_pause();
        responseCmnd("Paused");
    } else if (strFind(cmd, "NEXT") == 0) {
        dy_next();
        responseCmnd("Next track");
    } else if (strFind(cmd, "PREV") == 0) {
        dy_prev();
        responseCmnd("Previous track");
    } else if (strFind(cmd, "VOL") == 0) {
        if (strlen(cmd) > 4) {
            strSub(arg, cmd, 4, 0);
            int val = atoi(arg);
            dy_volume(val);
        }
        sprintf(buf, "Volume: %d", dy_vol);
        responseCmnd(buf);
    } else {
        responseCmnd("Play [file]|Stop|Pause|Next|Prev|Vol <n>");
    }
}

void WebUI() {
    webPulldown(dy_txpin, "TX Pin", "@getfreepins");
}

void WebCall() {
    char buf[64];
    if (dy_ok) {
        sprintf(buf, "{s}MP3 Volume{m}%d{e}", dy_vol);
        webSend(buf);
        if (dy_file[0]) {
            sprintf(buf, "{s}MP3 File{m}%s{e}", dy_file);
            webSend(buf);
        }
    } else {
        webSend("{s}MP3{m}not ready{e}");
    }
}

void OnExit() {
    dy_stop();
    delay(50);
    serialClose();
}

int main() {
    dy_file[0] = 0;
    // Wait for system to fully initialize (serial/GPIO not ready during early boot)
    delay(10000);
    // Restore persisted pin from web UI before opening serial
    webPulldown(dy_txpin, "TX Pin", "@getfreepins");
    int ret = serialBegin(-1, dy_txpin, 9600, 3, 64);  // 3 = 8N1
    if (ret == 1) {
        dy_ok = 1;
        addCommand("MP3");
        delay(100);
        dy_volume(dy_vol);
        char buf[48];
        sprintf(buf, "DY-SV17F ready on GPIO %d", dy_txpin);
        addLog(buf);
    } else {
        addLog("DY-SV17F: serial init failed");
    }
    return 0;
}