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 塑料 |
使用前提
- 准备一个 MQTT 服务器(如本机
mosquitto,或局域网 EMQX 等),电脑和音箱都能访问。 - 将音箱的自定义 MQTT 指向该服务器(可通过官方 App 配置,或向音箱当前服务器发送
setting-mqtt指令):- server: 你的 MQTT 服务器 IP
- port: 1883
- publish / subcribe: 记下设备配置的两个 topic(本示例默认
/topic/qos0为设备上报、/topic/qos1为设备订阅) - 配置后给设备断电重启或发送重启指令生效
- 修改
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());
}
}
}
}