GemeOpen 智能音箱 GSSM0B - 播放控制(Java示例)

GemeOpen智能音箱 GSSM0B 播放控制(Java示例)

基于 Eclipse Paho MQTT 客户端控制「远程音频音乐智能音箱-WiFi版(型号 GSSM0B)」的示例程序。

功能

  • 音频播放控制:播放 / 暂停 / 停止 / 上一曲 / 下一曲 / 播放 SD 卡指定文件 / 设置播放模式
  • 音量调节:设置音量(0-100)、音量 +1、音量 -1
  • 播放指定网络音频 :player-http-play 直接播放 http/https 直链;player-http-play-save 边播边存到 SD 卡
  • 附加:TTS 文字播报、查询播放器状态、下载音频到 SD 卡

产品基本描述

一款支持远程控制的迷你音箱,可存储歌曲500首高品质MP3,最多支持4个音箱组网,定义前置左右声道、环绕左右声道,

Wifi网络连接,MQTT通信协议,支持远程指令下载音频文件以及播放指定的音频。

产品主要特点

WiFi网络,支持本地云端音频播放

TTS语音转文字,48种拟人音色

20段EQ调节,到点语音插播

5V2A供电

开发者文档

https://www.smart-bird.cn/doc/product/GSSM/device/GSSM0B/command

产品电气参数


参数名 参数值
品牌 GemeOpen
上市日期 2026年
产品类型 远程音频音乐播放器-WiFi版
产品型号 GSSM0B
外观尺寸 87mm87mm63mm
输入电压 直流5V
工作电流 200mA
通讯方式 2.4GHz WIFI
通讯模块 乐鑫ESP32-S3
供电接口 Type-C
阻抗功率 4Ω/12W
振动膜规格 2寸方形52mm<
腔体设计 自研腔体结构设计,减少失真并提升音质
外壳材质 ABS 塑料

使用前提

  1. 准备一个 MQTT 服务器(如本机 mosquitto,或局域网 EMQX 等),电脑和音箱都能访问。
  2. 将音箱的自定义 MQTT 指向该服务器(可通过官方 App 配置,或向音箱当前服务器发送 setting-mqtt 指令):
    • server: 你的 MQTT 服务器 IP
    • port: 1883
    • publish / subcribe: 记下设备配置的两个 topic(本示例默认 /topic/qos0 为设备上报、/topic/qos1 为设备订阅)
    • 配置后给设备断电重启或发送重启指令生效
  3. 修改 GSSM0BDemo.java 顶部的 MQTT_BROKER、MQTT_USERNAME、MQTT_PASSWORD、TOPIC_COMMAND、TOPIC_REPORT。

编译运行

bash 复制代码
mvn package
java -jar target/gssm0b-demo-1.0.0.jar

指令协议要点(与设备文档对应)

功能 action 关键字段
播放/暂停/停止/上一曲/下一曲 player-play / player-pause / player-stop / player-prev / player-next ---
播放SD卡指定文件 player-sd-play name
设置音量 player-set-vol value (0-100)
音量+/- player-add-vol / player-sub-vol ---
播放网络音频 player-http-play url
播放并保存 player-http-play-save url, name
TTS tts-play text, voice

设备回复统一带 success、message、commandName、messageId 字段;程序按 messageId 关联请求与回复,超时 8 秒报错。

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>gssm0b-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Eclipse Paho MQTT v3 客户端 -->
        <dependency>
            <groupId>org.eclipse.paho</groupId>
            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
            <version>1.2.5</version>
        </dependency>
        <!-- JSON 解析(解析设备返回的消息) -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20240303</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 打成可执行 fat jar: mvn package 后 java -jar 运行 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals><goal>shade</goal></goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.example.GSSM0BDemo</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

GSSM0BDemo.java

java 复制代码
package com.example;

import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.json.JSONObject;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.*;

/**
 * GSSM0B 远程音频音乐智能音箱(WiFi版)Java 控制示例
 *
 * 设备通过 MQTT 通信,控制方式为:
 *   服务端(本程序) -> 发布指令 JSON 到【设备订阅的 topic】
 *   设备            -> 上报执行结果到【设备发布的 topic】(即服务端需要订阅的 topic)
 *
 * 使用前先通过官方 App 或 setting-mqtt 指令把设备配置为连接你自己的 MQTT 服务器,
 * 并记录下设备的 publish / subcribe 两个 topic(本例默认 /topic/qos0 和 /topic/qos1)。
 */
public class GSSM0BDemo {

    // ====================== 按实际情况修改这里 ======================
    /** MQTT 服务器地址(设备与电脑都要能连上) */
    static final String MQTT_BROKER   = "tcp://192.168.0.66:1883";
    /** MQTT 用户名/密码(无则留空字符串) */
    static final String MQTT_USERNAME = "admin";
    static final String MQTT_PASSWORD = "123456";
    /** 设备订阅的 topic(服务端向这里发指令) */
    static final String TOPIC_COMMAND = "/topic/qos1";
    /** 设备发布的 topic(服务端订阅这里接收回复/上报) */
    static final String TOPIC_REPORT  = "/topic/qos0";
    // ==============================================================

    private MqttClient client;

    /** 等待设备回复用:messageId -> Future */
    private final Map<String, CompletableFuture<JSONObject>> pending = new ConcurrentHashMap<>();
    /** 回复超时时间 */
    private static final long REPLY_TIMEOUT_MS = 8000;

    public static void main(String[] args) throws Exception {
        new GSSM0BDemo().run();
    }

    // ==================================================================
    // 连接与消息收发
    // ==================================================================

    public void connect() throws MqttException {
        String clientId = "gssm0b-demo-" + UUID.randomUUID().toString().substring(0, 8);
        client = new MqttClient(MQTT_BROKER, clientId, new MemoryPersistence());

        MqttConnectOptions opts = new MqttConnectOptions();
        opts.setCleanSession(true);
        opts.setAutomaticReconnect(true);
        opts.setConnectionTimeout(10);
        if (!MQTT_USERNAME.isEmpty()) {
            opts.setUserName(MQTT_USERNAME);
            opts.setPassword(MQTT_PASSWORD.toCharArray());
        }

        client.setCallback(new MqttCallbackExtended() {
            @Override public void connectComplete(boolean reconnect, String serverURI) {
                try {
                    client.subscribe(TOPIC_REPORT, 0);
                    System.out.println((reconnect ? "重新连接" : "已连接") + " MQTT 服务器,并订阅设备上报 topic: " + TOPIC_REPORT);
                } catch (MqttException e) {
                    System.err.println("订阅失败: " + e.getMessage());
                }
            }
            @Override public void connectionLost(Throwable cause) {
                System.out.println("MQTT 连接断开,将自动重连: " + cause.getMessage());
            }
            @Override public void messageArrived(String topic, MqttMessage message) {
                handleDeviceMessage(new JSONObject(new String(message.getPayload(), java.nio.charset.StandardCharsets.UTF_8)));
            }
            @Override public void deliveryComplete(IMqttDeliveryToken token) { }
        });

        System.out.println("正在连接 " + MQTT_BROKER + " ...");
        client.connect(opts);
    }

    /** 处理设备上报:完成对应 messageId 的等待,其余当作主动上报打印 */
    private void handleDeviceMessage(JSONObject json) {
        String messageId = json.optString("messageId", "");
        CompletableFuture<JSONObject> future = pending.remove(messageId);
        if (future != null) {
            future.complete(json);
        } else {
            System.out.println("[设备主动上报] " + json);
        }
    }

    /**
     * 发送一条指令并等待设备回复。
     * @param fields 指令字段(action、type、messageId 会自动补充/覆盖)
     * @return 设备回复 JSON;超时或失败抛出异常
     */
    public JSONObject sendCommand(Map<String, Object> fields) throws Exception {
        String messageId = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 18);
        JSONObject json = new JSONObject(fields);
        json.put("messageId", messageId);

        CompletableFuture<JSONObject> future = new CompletableFuture<>();
        pending.put(messageId, future);

        String payload = json.toString();
        System.out.println("\n>>> 发送指令: " + payload);
        client.publish(TOPIC_COMMAND, new MqttMessage(payload.getBytes(java.nio.charset.StandardCharsets.UTF_8)));

        try {
            JSONObject reply = future.get(REPLY_TIMEOUT_MS, TimeUnit.MILLISECONDS);
            boolean success = reply.optBoolean("success", false);
            System.out.println("<<< 设备回复: success=" + success
                    + ", message=" + reply.optString("message", "")
                    + ", commandName=" + reply.optString("commandName", ""));
            if (!success) {
                throw new IllegalStateException("设备执行失败: " + reply.optString("message", ""));
            }
            return reply;
        } catch (TimeoutException e) {
            throw new TimeoutException("等待设备回复超时(" + REPLY_TIMEOUT_MS + "ms),请检查设备是否在线、topic 配置是否正确");
        }
    }

    /** 快捷构造指令参数 */
    private static Map<String, Object> cmd(String action) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("action", action);
        m.put("type", "event");
        return m;
    }

    public void close() {
        if (client != null && client.isConnected()) {
            try { client.disconnect(); } catch (MqttException ignored) { }
        }
    }

    // ==================================================================
    // 一、音频播放控制(SD 卡本地播放)
    // ==================================================================

    /** 开始播放(恢复播放) */
    public void play() throws Exception {
        sendCommand(cmd("player-play"));
    }

    /** 暂停播放 */
    public void pause() throws Exception {
        sendCommand(cmd("player-pause"));
    }

    /** 停止播放 */
    public void stop() throws Exception {
        sendCommand(cmd("player-stop"));
    }

    /** 下一曲 */
    public void next() throws Exception {
        sendCommand(cmd("player-next"));
    }

    /** 上一曲 */
    public void prev() throws Exception {
        sendCommand(cmd("player-prev"));
    }

    /** 播放 SD 卡内指定文件,例如 "001.mp3" */
    public void playSdFile(String name) throws Exception {
        Map<String, Object> m = cmd("player-sd-play");
        m.put("name", name);
        sendCommand(m);
    }

    /** 设置播放模式:OnePlay 单曲 / OneLoopPlay 单曲循环 / ListPlay 列表 / ListLoopPlay 列表循环 */
    public void setPlayMode(String mode) throws Exception {
        Map<String, Object> m = cmd("player-set-mode");
        m.put("player-mode", mode);
        sendCommand(m);
    }

    /** 查询播放器状态(音量、SD 卡空间、文件数、当前模式等) */
    public JSONObject getPlayerInfo() throws Exception {
        return sendCommand(cmd("player-info"));
    }

    // ==================================================================
    // 二、音量调节
    // ==================================================================

    /** 设置音量(0-100) */
    public void setVolume(int value) throws Exception {
        if (value < 0 || value > 100) throw new IllegalArgumentException("音量范围 0-100");
        Map<String, Object> m = cmd("player-set-vol");
        m.put("value", value);
        sendCommand(m);
    }

    /** 音量 +1 */
    public void volumeUp() throws Exception {
        sendCommand(cmd("player-add-vol"));
    }

    /** 音量 -1 */
    public void volumeDown() throws Exception {
        sendCommand(cmd("player-sub-vol"));
    }

    // ==================================================================
    // 三、播放指定的网络音频
    // ==================================================================

    /** 播放在线音频(http/https 直链),例如 https://megface.x1.smart-bird.cn/sunday.mp3 */
    public void playHttpAudio(String url) throws Exception {
        Map<String, Object> m = cmd("player-http-play");
        m.put("url", url);
        sendCommand(m);
    }

    /** 播放在线音频并下载保存到 SD 卡 */
    public void playHttpAudioAndSave(String url, String sdName) throws Exception {
        Map<String, Object> m = cmd("player-http-play-save");
        m.put("url", url);
        m.put("name", sdName);
        sendCommand(m);
    }

    /** 下载在线音频到 SD 卡(不播放) */
    public void downloadToSd(String url, String sdPath) throws Exception {
        Map<String, Object> m = cmd("download");
        m.put("url", url);
        m.put("filepath", sdPath);
        sendCommand(m);
    }

    /** TTS 播报文字(消耗设备 TTS 额度) */
    public void tts(String text, String voice) throws Exception {
        Map<String, Object> m = cmd("tts-play");
        m.put("text", text);
        m.put("voice", voice);
        sendCommand(m);
    }

    // ==================================================================
    // 交互式演示菜单
    // ==================================================================

    private void run() throws Exception {
        connect();
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("\n========= GSSM0B 音箱控制 =========");
            System.out.println(" 1. 播放          2. 暂停          3. 停止");
            System.out.println(" 4. 下一曲        5. 上一曲        6. 播放SD卡指定文件");
            System.out.println(" 7. 设置音量      8. 音量+         9. 音量-");
            System.out.println("10. 播放网络音频  11. 播放并保存    12. TTS播报");
            System.out.println("13. 播放器状态    14. 设置播放模式  0. 退出");
            System.out.print("请选择: ");
            String line = sc.nextLine().trim();
            try {
                switch (line) {
                    case "1": play(); break;
                    case "2": pause(); break;
                    case "3": stop(); break;
                    case "4": next(); break;
                    case "5": prev(); break;
                    case "6": System.out.print("文件名(如 001.mp3): "); playSdFile(sc.nextLine().trim()); break;
                    case "7": System.out.print("音量(0-100): "); setVolume(Integer.parseInt(sc.nextLine().trim())); break;
                    case "8": volumeUp(); break;
                    case "9": volumeDown(); break;
                    case "10":
                        System.out.print("音频URL: ");
                        playHttpAudio(sc.nextLine().trim());
                        break;
                    case "11":
                        System.out.print("音频URL: ");
                        String url = sc.nextLine().trim();
                        System.out.print("保存到SD卡的名称: ");
                        playHttpAudioAndSave(url, sc.nextLine().trim());
                        break;
                    case "12":
                        System.out.print("播报文字: ");
                        String text = sc.nextLine().trim();
                        tts(text, "Cherry");
                        break;
                    case "13": getPlayerInfo(); break;
                    case "14":
                        System.out.print("模式(OnePlay/OneLoopPlay/ListPlay/ListLoopPlay): ");
                        setPlayMode(sc.nextLine().trim());
                        break;
                    case "0": close(); return;
                    default: System.out.println("无效选择");
                }
            } catch (Exception e) {
                System.out.println("操作失败: " + e.getMessage());
            }
        }
    }
}
相关推荐
今天的砖头有点烫手啊1 小时前
Java IO/NIO/AIO 演进:从 BIO 到 Netty 的底层逻辑
java·jvm·nio
m0_380743872 小时前
PHP 8.3 接口返回数据为空怎么排查
开发语言·php
谢亮_vipxieliang2 小时前
Java 21 新特性实战:Record、Sealed、模式匹配
java·开发语言
霸道流氓气质2 小时前
LLM 应用限流与熔断机制完全指南:从多层防护架构到Java生产级弹性实战
java·开发语言·架构
大侠归来2 小时前
C语言内存管理:从栈到堆的完整指南
c语言·开发语言·python
m0_380743872 小时前
PHP7.0字符串在Docker怎么用
开发语言·php
不会就选b2 小时前
算法日常・每日刷题--<动态规划>1
java·数据结构·算法
晚安code2 小时前
JVM 内存结构入门:程序计数器、虚拟机栈、本地方法栈与堆的溢出诊断
java·jvm
自强的小白2 小时前
Spring事务失效的场景
java·spring