Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
car-controlserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wenzhongjian
car-controlserver
Commits
8359d134
Commit
8359d134
authored
Jul 21, 2026
by
957dd
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature/add_aboard_tts' into 'master'
无网络语音播报到45s一次加优化了wfii扫描 See merge request
!130
parents
7200adca
8d1ec7b8
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
445 additions
and
1153 deletions
+445
-1153
device_wifi_manager.c
app/device_change/device_wifi_manager.c
+352
-2
device_wifi_manager.h
app/device_change/device_wifi_manager.h
+7
-2
main.c
app/main/main.c
+3
-3
main.h
app/main/main.h
+1
-1
pthread_open.c
app/main/pthread_open.c
+5
-3
pthread_open.h
app/main/pthread_open.h
+2
-3
wifi_autoconfig.c
app/main/wifi_autoconfig.c
+71
-35
wifi_autoconfig.h
app/main/wifi_autoconfig.h
+3
-0
main
build/main
+0
-0
audioplay.c.bak-audio-preempt-20260717
drivers/sensors/audioplay.c.bak-audio-preempt-20260717
+0
-551
audioplay.h.bak-audio-preempt-20260717
drivers/sensors/audioplay.h.bak-audio-preempt-20260717
+0
-16
audiotts_play.c.bak-audio-preempt-20260717
drivers/sensors/audiotts_play.c.bak-audio-preempt-20260717
+0
-536
zlog.conf
zlog.conf
+1
-1
No files found.
app/device_change/device_wifi_manager.c
View file @
8359d134
...
@@ -2,15 +2,37 @@
...
@@ -2,15 +2,37 @@
#include "device_wifi_change.h"
#include "device_wifi_change.h"
#include "device_wifi_manager.h"
#include "device_wifi_manager.h"
#include "modules_common.h"
#include "modules_common.h"
#include <sys/types.h>
#include <sys/wait.h>
#define MAX_WIFI_ENTRIES 32
#define MAX_WIFI_ENTRIES 32
#define MAX_SAVED_WIFI_SLOTS 10
#define MAX_SAVED_WIFI_SLOTS 10
#define SSID_MAX_LENGTH 64
#define SSID_MAX_LENGTH 64
#define WIFI_PSK_MAX_LENGTH 63
#define WIFI_PSK_MAX_LENGTH 63
#define NM_CONN_DIR "/etc/NetworkManager/system-connections/"
#define NM_CONN_DIR "/etc/NetworkManager/system-connections/"
#define WIFI_DELETE_COMPAT_DELAY_MS 2000
#define WIFI_SAVE_DELETE_GUARD_SEC 5
#define WIFI_SAVE_GUARD_SLOTS 8
#define WIFI_DELETED_PASSWORD_CACHE_SEC 60
static
ThreadPool_t
*
s_wifi_pool
=
NULL
;
static
ThreadPool_t
*
s_wifi_pool
=
NULL
;
static
pthread_mutex_t
s_wifi_pool_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
pthread_mutex_t
s_wifi_pool_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
pthread_mutex_t
s_wifi_save_guard_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
typedef
struct
{
char
ssid
[
SSID_MAX_LENGTH
];
time_t
saved_at
;
}
wifi_save_guard_t
;
static
wifi_save_guard_t
s_wifi_save_guards
[
WIFI_SAVE_GUARD_SLOTS
];
typedef
struct
{
char
ssid
[
SSID_MAX_LENGTH
];
char
password
[
WIFI_PSK_MAX_LENGTH
+
1
];
time_t
cached_at
;
}
wifi_deleted_credential_t
;
static
wifi_deleted_credential_t
s_deleted_credentials
[
WIFI_SAVE_GUARD_SLOTS
];
typedef
struct
{
typedef
struct
{
char
ssid
[
SSID_MAX_LENGTH
];
char
ssid
[
SSID_MAX_LENGTH
];
...
@@ -19,6 +41,11 @@ typedef struct {
...
@@ -19,6 +41,11 @@ typedef struct {
typedef
struct
{
typedef
struct
{
char
ssid
[
SSID_MAX_LENGTH
];
char
ssid
[
SSID_MAX_LENGTH
];
int
signal
;
}
visible_wifi_entry_t
;
typedef
struct
{
char
ssid
[
SSID_MAX_LENGTH
];
char
password
[
WIFI_PSK_MAX_LENGTH
+
1
];
char
password
[
WIFI_PSK_MAX_LENGTH
+
1
];
}
wifi_save_task_t
;
}
wifi_save_task_t
;
...
@@ -29,6 +56,129 @@ typedef struct {
...
@@ -29,6 +56,129 @@ typedef struct {
static
void
wifi_save_worker
(
void
*
arg
);
static
void
wifi_save_worker
(
void
*
arg
);
static
void
wifi_delete_worker
(
void
*
arg
);
static
void
wifi_delete_worker
(
void
*
arg
);
static
int
submit_wifi_task
(
void
(
*
worker
)(
void
*
),
void
*
arg
,
const
char
*
what
);
static
int
submit_wifi_task
(
void
(
*
worker
)(
void
*
),
void
*
arg
,
const
char
*
what
);
static
int
read_wifi_psk_from_profile
(
const
char
*
ssid
,
char
*
psk_out
,
size_t
psk_sz
);
static
void
note_wifi_save_request
(
const
char
*
ssid
)
{
int
slot
=
-
1
;
int
oldest_slot
=
0
;
if
(
!
ssid
||
!
ssid
[
0
])
return
;
pthread_mutex_lock
(
&
s_wifi_save_guard_mutex
);
for
(
int
i
=
0
;
i
<
WIFI_SAVE_GUARD_SLOTS
;
i
++
)
{
if
(
s_wifi_save_guards
[
i
].
ssid
[
0
]
&&
strcmp
(
s_wifi_save_guards
[
i
].
ssid
,
ssid
)
==
0
)
{
slot
=
i
;
break
;
}
if
(
!
s_wifi_save_guards
[
i
].
ssid
[
0
]
&&
slot
<
0
)
slot
=
i
;
if
(
s_wifi_save_guards
[
i
].
saved_at
<
s_wifi_save_guards
[
oldest_slot
].
saved_at
)
oldest_slot
=
i
;
}
if
(
slot
<
0
)
slot
=
oldest_slot
;
strncpy
(
s_wifi_save_guards
[
slot
].
ssid
,
ssid
,
sizeof
(
s_wifi_save_guards
[
slot
].
ssid
)
-
1
);
s_wifi_save_guards
[
slot
].
ssid
[
sizeof
(
s_wifi_save_guards
[
slot
].
ssid
)
-
1
]
=
'\0'
;
s_wifi_save_guards
[
slot
].
saved_at
=
time
(
NULL
);
pthread_mutex_unlock
(
&
s_wifi_save_guard_mutex
);
}
static
int
wifi_delete_blocked_by_recent_save
(
const
char
*
ssid
)
{
time_t
now
=
time
(
NULL
);
int
blocked
=
0
;
pthread_mutex_lock
(
&
s_wifi_save_guard_mutex
);
for
(
int
i
=
0
;
ssid
&&
ssid
[
0
]
&&
i
<
WIFI_SAVE_GUARD_SLOTS
;
i
++
)
{
double
age
;
if
(
!
s_wifi_save_guards
[
i
].
ssid
[
0
]
||
strcmp
(
ssid
,
s_wifi_save_guards
[
i
].
ssid
)
!=
0
)
continue
;
age
=
difftime
(
now
,
s_wifi_save_guards
[
i
].
saved_at
);
if
(
age
>=
0
.
0
&&
age
<=
WIFI_SAVE_DELETE_GUARD_SEC
)
blocked
=
1
;
break
;
}
pthread_mutex_unlock
(
&
s_wifi_save_guard_mutex
);
return
blocked
;
}
static
void
cache_wifi_password_before_delete
(
const
char
*
ssid
)
{
char
password
[
WIFI_PSK_MAX_LENGTH
+
1
];
int
slot
=
-
1
;
int
oldest_slot
=
0
;
if
(
read_wifi_psk_from_profile
(
ssid
,
password
,
sizeof
(
password
))
!=
0
||
password
[
0
]
==
'\0'
)
return
;
pthread_mutex_lock
(
&
s_wifi_save_guard_mutex
);
for
(
int
i
=
0
;
i
<
WIFI_SAVE_GUARD_SLOTS
;
i
++
)
{
if
(
s_deleted_credentials
[
i
].
ssid
[
0
]
&&
strcmp
(
s_deleted_credentials
[
i
].
ssid
,
ssid
)
==
0
)
{
slot
=
i
;
break
;
}
if
(
!
s_deleted_credentials
[
i
].
ssid
[
0
]
&&
slot
<
0
)
slot
=
i
;
if
(
s_deleted_credentials
[
i
].
cached_at
<
s_deleted_credentials
[
oldest_slot
].
cached_at
)
oldest_slot
=
i
;
}
if
(
slot
<
0
)
slot
=
oldest_slot
;
strncpy
(
s_deleted_credentials
[
slot
].
ssid
,
ssid
,
sizeof
(
s_deleted_credentials
[
slot
].
ssid
)
-
1
);
s_deleted_credentials
[
slot
].
ssid
[
sizeof
(
s_deleted_credentials
[
slot
].
ssid
)
-
1
]
=
'\0'
;
strncpy
(
s_deleted_credentials
[
slot
].
password
,
password
,
sizeof
(
s_deleted_credentials
[
slot
].
password
)
-
1
);
s_deleted_credentials
[
slot
].
password
[
sizeof
(
s_deleted_credentials
[
slot
].
password
)
-
1
]
=
'\0'
;
s_deleted_credentials
[
slot
].
cached_at
=
time
(
NULL
);
pthread_mutex_unlock
(
&
s_wifi_save_guard_mutex
);
memset
(
password
,
0
,
sizeof
(
password
));
}
static
int
get_recent_deleted_password
(
const
char
*
ssid
,
char
*
password
,
size_t
size
)
{
time_t
now
=
time
(
NULL
);
int
found
=
0
;
if
(
!
ssid
||
!
password
||
size
==
0
)
return
-
1
;
password
[
0
]
=
'\0'
;
pthread_mutex_lock
(
&
s_wifi_save_guard_mutex
);
for
(
int
i
=
0
;
i
<
WIFI_SAVE_GUARD_SLOTS
;
i
++
)
{
double
age
;
if
(
!
s_deleted_credentials
[
i
].
ssid
[
0
]
||
strcmp
(
s_deleted_credentials
[
i
].
ssid
,
ssid
)
!=
0
)
continue
;
age
=
difftime
(
now
,
s_deleted_credentials
[
i
].
cached_at
);
if
(
age
>=
0
.
0
&&
age
<=
WIFI_DELETED_PASSWORD_CACHE_SEC
&&
s_deleted_credentials
[
i
].
password
[
0
])
{
strncpy
(
password
,
s_deleted_credentials
[
i
].
password
,
size
-
1
);
password
[
size
-
1
]
=
'\0'
;
found
=
1
;
}
break
;
}
pthread_mutex_unlock
(
&
s_wifi_save_guard_mutex
);
return
found
?
0
:
-
1
;
}
static
void
clear_recent_deleted_password
(
const
char
*
ssid
)
{
pthread_mutex_lock
(
&
s_wifi_save_guard_mutex
);
for
(
int
i
=
0
;
ssid
&&
i
<
WIFI_SAVE_GUARD_SLOTS
;
i
++
)
{
if
(
strcmp
(
s_deleted_credentials
[
i
].
ssid
,
ssid
)
!=
0
)
continue
;
memset
(
&
s_deleted_credentials
[
i
],
0
,
sizeof
(
s_deleted_credentials
[
i
]));
break
;
}
pthread_mutex_unlock
(
&
s_wifi_save_guard_mutex
);
}
static
int
is_default_wifi_ssid
(
const
char
*
ssid
)
{
static
int
is_default_wifi_ssid
(
const
char
*
ssid
)
{
return
ssid
!=
NULL
&&
strcmp
(
ssid
,
default_SSID
)
==
0
;
return
ssid
!=
NULL
&&
strcmp
(
ssid
,
default_SSID
)
==
0
;
...
@@ -311,6 +461,153 @@ static int collect_saved_wifi_entries(saved_wifi_entry_t *entries, int max_count
...
@@ -311,6 +461,153 @@ static int collect_saved_wifi_entries(saved_wifi_entry_t *entries, int max_count
return
count
;
return
count
;
}
}
static
void
nmcli_unescape_field
(
char
*
field
)
{
char
*
src
;
char
*
dst
;
if
(
!
field
)
return
;
src
=
dst
=
field
;
while
(
*
src
)
{
if
(
*
src
==
'\\'
&&
src
[
1
]
!=
'\0'
)
src
++
;
*
dst
++
=
*
src
++
;
}
*
dst
=
'\0'
;
}
static
int
visible_wifi_index
(
const
visible_wifi_entry_t
*
entries
,
int
count
,
const
char
*
ssid
)
{
for
(
int
i
=
0
;
i
<
count
;
i
++
)
{
if
(
strcmp
(
entries
[
i
].
ssid
,
ssid
)
==
0
)
return
i
;
}
return
-
1
;
}
static
int
visible_wifi_signal_desc
(
const
void
*
a
,
const
void
*
b
)
{
const
visible_wifi_entry_t
*
wa
=
a
;
const
visible_wifi_entry_t
*
wb
=
b
;
return
wb
->
signal
-
wa
->
signal
;
}
static
int
scan_visible_wifi
(
visible_wifi_entry_t
*
entries
,
int
max_count
)
{
FILE
*
fp
;
char
line
[
256
];
int
count
=
0
;
fp
=
popen
(
"nmcli -t --escape yes -f SSID,SIGNAL device wifi list --rescan yes 2>/dev/null"
,
"r"
);
if
(
!
fp
)
{
my_zlog_error
(
"无法执行周围 WiFi 扫描"
);
return
-
1
;
}
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
char
*
separator
;
char
*
signal_text
;
int
signal
;
int
idx
;
trim_line
(
line
);
separator
=
strrchr
(
line
,
':'
);
if
(
!
separator
)
continue
;
*
separator
=
'\0'
;
signal_text
=
separator
+
1
;
nmcli_unescape_field
(
line
);
if
(
!
line
[
0
])
continue
;
signal
=
atoi
(
signal_text
);
idx
=
visible_wifi_index
(
entries
,
count
,
line
);
if
(
idx
>=
0
)
{
if
(
signal
>
entries
[
idx
].
signal
)
entries
[
idx
].
signal
=
signal
;
continue
;
}
if
(
count
>=
max_count
)
continue
;
strncpy
(
entries
[
count
].
ssid
,
line
,
sizeof
(
entries
[
count
].
ssid
)
-
1
);
entries
[
count
].
ssid
[
sizeof
(
entries
[
count
].
ssid
)
-
1
]
=
'\0'
;
entries
[
count
].
signal
=
signal
;
count
++
;
}
pclose
(
fp
);
qsort
(
entries
,
count
,
sizeof
(
entries
[
0
]),
visible_wifi_signal_desc
);
return
count
;
}
static
int
activate_saved_wifi_profile
(
const
char
*
profile_name
)
{
pid_t
pid
;
int
status
;
pid
=
fork
();
if
(
pid
<
0
)
{
my_zlog_error
(
"启动 nmcli 失败: %s"
,
strerror
(
errno
));
return
-
1
;
}
if
(
pid
==
0
)
{
execlp
(
"nmcli"
,
"nmcli"
,
"connection"
,
"up"
,
"id"
,
profile_name
,
"ifname"
,
"wlan0"
,
(
char
*
)
NULL
);
_exit
(
127
);
}
if
(
waitpid
(
pid
,
&
status
,
0
)
<
0
)
return
-
1
;
if
(
WIFEXITED
(
status
)
&&
WEXITSTATUS
(
status
)
==
0
)
return
0
;
return
-
1
;
}
int
device_wifi_connect_best_saved_visible
(
void
)
{
saved_wifi_entry_t
saved
[
MAX_WIFI_ENTRIES
];
visible_wifi_entry_t
visible
[
MAX_WIFI_ENTRIES
];
int
saved_count
;
int
visible_count
;
saved_count
=
collect_saved_wifi_entries
(
saved
,
MAX_WIFI_ENTRIES
);
if
(
saved_count
<=
0
)
{
my_zlog_warn
(
"断网恢复: 没有已保存的 WiFi 配置"
);
return
0
;
}
visible_count
=
scan_visible_wifi
(
visible
,
MAX_WIFI_ENTRIES
);
if
(
visible_count
<
0
)
return
-
1
;
if
(
visible_count
==
0
)
{
my_zlog_warn
(
"断网恢复: 未扫描到周围 WiFi"
);
return
0
;
}
get_current_wifi
();
/* 先尝试其它已保存网络;没有其它选择时再重新激活当前网络。 */
for
(
int
pass
=
0
;
pass
<
2
;
pass
++
)
{
for
(
int
i
=
0
;
i
<
visible_count
;
i
++
)
{
int
is_current
;
if
(
entry_index_by_name
(
saved
,
saved_count
,
visible
[
i
].
ssid
)
<
0
)
continue
;
is_current
=
g_current_ssid
[
0
]
&&
strcmp
(
g_current_ssid
,
visible
[
i
].
ssid
)
==
0
;
if
((
pass
==
0
&&
is_current
)
||
(
pass
==
1
&&
!
is_current
))
continue
;
my_zlog_info
(
"断网恢复: 尝试已保存 WiFi=%s, signal=%d"
,
visible
[
i
].
ssid
,
visible
[
i
].
signal
);
if
(
activate_saved_wifi_profile
(
visible
[
i
].
ssid
)
==
0
)
{
my_zlog_info
(
"断网恢复: NetworkManager 已激活 WiFi=%s"
,
visible
[
i
].
ssid
);
return
1
;
}
my_zlog_warn
(
"断网恢复: 激活 WiFi 失败=%s"
,
visible
[
i
].
ssid
);
}
}
my_zlog_warn
(
"断网恢复: 周围网络与已保存配置无可用连接"
);
return
0
;
}
static
int
nmcli_wifi_connection_exists
(
const
char
*
ssid
)
{
static
int
nmcli_wifi_connection_exists
(
const
char
*
ssid
)
{
FILE
*
fp
;
FILE
*
fp
;
char
line
[
256
];
char
line
[
256
];
...
@@ -710,17 +1007,55 @@ int get_saved_wifi_list(char ssid_list[][SSID_MAX_LENGTH], int max_count) {
...
@@ -710,17 +1007,55 @@ int get_saved_wifi_list(char ssid_list[][SSID_MAX_LENGTH], int max_count) {
}
}
int
orange_pi_save_wifi
(
const
char
*
ssid
,
const
char
*
password
)
{
int
orange_pi_save_wifi
(
const
char
*
ssid
,
const
char
*
password
)
{
if
(
validate_wifi_credentials
(
ssid
,
password
)
!=
0
)
int
already_saved
;
int
used_cached_password
=
0
;
char
cached_password
[
WIFI_PSK_MAX_LENGTH
+
1
]
=
{
0
};
if
(
!
ssid
||
!
ssid
[
0
]
||
strlen
(
ssid
)
>=
SSID_MAX_LENGTH
||
strpbrk
(
ssid
,
"
\"
`$|;
\\
"
))
{
my_zlog_error
(
"SSID无效"
);
return
-
1
;
return
-
1
;
}
if
(
!
wifi_connection_exists
(
ssid
)
&&
enforce_saved_wifi_limit
(
ssid
)
!=
0
)
already_saved
=
wifi_connection_exists
(
ssid
);
/*
* 后台同名更新可能不重复下发密码。此时保留 NetworkManager 中的原密码,
* 不删除、不重建配置,并按幂等保存成功处理。
*/
if
(
!
password
||
password
[
0
]
==
'\0'
)
{
if
(
already_saved
)
{
clear_recent_deleted_password
(
ssid
);
my_zlog_info
(
"[SUCCESS] 同名WiFi密码为空,保留原密码: %s"
,
ssid
);
return
0
;
}
if
(
get_recent_deleted_password
(
ssid
,
cached_password
,
sizeof
(
cached_password
))
!=
0
)
{
my_zlog_error
(
"新WiFi密码为空,无法创建配置: %s"
,
ssid
);
return
-
1
;
}
password
=
cached_password
;
used_cached_password
=
1
;
my_zlog_info
(
"同名WiFi已被后台提前删除,使用短期缓存的原密码恢复: %s"
,
ssid
);
}
if
(
validate_wifi_credentials
(
ssid
,
password
)
!=
0
)
{
memset
(
cached_password
,
0
,
sizeof
(
cached_password
));
return
-
1
;
}
if
(
!
already_saved
&&
enforce_saved_wifi_limit
(
ssid
)
!=
0
)
return
-
1
;
return
-
1
;
if
(
save_wifi_via_nmcli
(
ssid
,
password
)
!=
0
)
{
if
(
save_wifi_via_nmcli
(
ssid
,
password
)
!=
0
)
{
memset
(
cached_password
,
0
,
sizeof
(
cached_password
));
my_zlog_error
(
"[ERROR] 保存配置失败: %s"
,
ssid
);
my_zlog_error
(
"[ERROR] 保存配置失败: %s"
,
ssid
);
return
-
1
;
return
-
1
;
}
}
clear_recent_deleted_password
(
ssid
);
if
(
used_cached_password
)
memset
(
cached_password
,
0
,
sizeof
(
cached_password
));
my_zlog_info
(
"[SUCCESS] 已保存WiFi配置: %s"
,
ssid
);
my_zlog_info
(
"[SUCCESS] 已保存WiFi配置: %s"
,
ssid
);
return
0
;
return
0
;
}
}
...
@@ -786,6 +1121,19 @@ static void wifi_delete_worker(void *arg) {
...
@@ -786,6 +1121,19 @@ static void wifi_delete_worker(void *arg) {
if
(
!
task
)
if
(
!
task
)
return
;
return
;
/*
* 后台会先发删除再发保存;设备连接多个 MQTT 服务时两类消息可能乱序。
* 短暂等待保存接收函数登记 SSID,再决定是否真的删除。
*/
usleep
(
WIFI_DELETE_COMPAT_DELAY_MS
*
1000
);
if
(
wifi_delete_blocked_by_recent_save
(
task
->
ssid
))
{
my_zlog_info
(
"兼容同名保存: 忽略迟到/重复的删除请求: %s"
,
task
->
ssid
);
free
(
task
);
return
;
}
/* 若后台坚持先删后存,暂存原密码,供随后同名空密码保存恢复。 */
cache_wifi_password_before_delete
(
task
->
ssid
);
rc
=
device_wifi_delete_saved_profile
(
task
->
ssid
);
rc
=
device_wifi_delete_saved_profile
(
task
->
ssid
);
if
(
rc
==
0
)
{
if
(
rc
==
0
)
{
my_zlog_info
(
"删除成功"
);
my_zlog_info
(
"删除成功"
);
...
@@ -828,6 +1176,8 @@ void device_wifi_rec_sava(cJSON *body) {
...
@@ -828,6 +1176,8 @@ void device_wifi_rec_sava(cJSON *body) {
}
}
strncpy
(
task
->
ssid
,
wifi_ssid
->
valuestring
,
sizeof
(
task
->
ssid
)
-
1
);
strncpy
(
task
->
ssid
,
wifi_ssid
->
valuestring
,
sizeof
(
task
->
ssid
)
-
1
);
strncpy
(
task
->
password
,
wifi_password
->
valuestring
,
sizeof
(
task
->
password
)
-
1
);
strncpy
(
task
->
password
,
wifi_password
->
valuestring
,
sizeof
(
task
->
password
)
-
1
);
/* 入队前立即登记,避免单线程 WiFi 队列中的删除任务先执行。 */
note_wifi_save_request
(
task
->
ssid
);
my_zlog_debug
(
"接收到保存WiFi,后台 nmcli 保存"
);
my_zlog_debug
(
"接收到保存WiFi,后台 nmcli 保存"
);
if
(
submit_wifi_task
(
wifi_save_worker
,
task
,
"save wifi"
)
!=
0
)
if
(
submit_wifi_task
(
wifi_save_worker
,
task
,
"save wifi"
)
!=
0
)
device_wifi_public_sendmqtt
(
2
);
device_wifi_public_sendmqtt
(
2
);
...
...
app/device_change/device_wifi_manager.h
View file @
8359d134
...
@@ -18,6 +18,12 @@ int orange_pi_save_wifi(const char *ssid, const char *password);
...
@@ -18,6 +18,12 @@ int orange_pi_save_wifi(const char *ssid, const char *password);
/* 保存并立即连接 WiFi(含满 10 个自动删最旧逻辑) */
/* 保存并立即连接 WiFi(含满 10 个自动删最旧逻辑) */
int
orange_pi_connect_wifi
(
const
char
*
ssid
,
const
char
*
password
);
int
orange_pi_connect_wifi
(
const
char
*
ssid
,
const
char
*
password
);
/*
* 扫描周围 WiFi,与已保存的 NetworkManager 配置匹配,并优先连接信号最强的配置。
* 返回 1=已激活一个已保存网络,0=没有可用匹配或全部连接失败,-1=扫描失败。
*/
int
device_wifi_connect_best_saved_visible
(
void
);
/* ===== App WiFi 管理 (UDP 18888) 接口 ===== */
/* ===== App WiFi 管理 (UDP 18888) 接口 ===== */
/* 查询已保存 WiFi 列表,返回 cJSON 数组(调用方负责 cJSON_Delete):
/* 查询已保存 WiFi 列表,返回 cJSON 数组(调用方负责 cJSON_Delete):
...
@@ -37,4 +43,4 @@ int app_wifi_switch(const char *ssid);
...
@@ -37,4 +43,4 @@ int app_wifi_switch(const char *ssid);
* 返回 0=成功, -1=未找到/失败, -2=受保护不可删 */
* 返回 0=成功, -1=未找到/失败, -2=受保护不可删 */
int
app_wifi_delete
(
const
char
*
ssid
);
int
app_wifi_delete
(
const
char
*
ssid
);
#endif
#endif
\ No newline at end of file
app/main/main.c
View file @
8359d134
...
@@ -82,7 +82,8 @@ static int init_worker_threads(void)
...
@@ -82,7 +82,8 @@ static int init_worker_threads(void)
return
thread_start_init
(
thread_exit_time
,
thread_mqtt_beat
,
return
thread_start_init
(
thread_exit_time
,
thread_mqtt_beat
,
thread_open_browser
,
thread_mqtt_reconnect
,
thread_open_browser
,
thread_mqtt_reconnect
,
thread_time_calculation
,
thread_play_mp3
,
thread_time_calculation
,
thread_play_mp3
,
thread_steering_control
);
thread_steering_control
,
thread_wifi_offline_recovery
);
}
}
static
const
AppInitStep
s_init_steps
[]
=
{
static
const
AppInitStep
s_init_steps
[]
=
{
...
@@ -168,4 +169,4 @@ int main()
...
@@ -168,4 +169,4 @@ int main()
sleep
(
3600
);
sleep
(
3600
);
return
0
;
return
0
;
}
}
\ No newline at end of file
app/main/main.h
View file @
8359d134
...
@@ -35,7 +35,7 @@ int device_shot_cooling_init();
...
@@ -35,7 +35,7 @@ int device_shot_cooling_init();
int
thread_start_init
(
ThreadFunc
thread_exit_time
,
ThreadFunc
thread_mqtt_beat
,
int
thread_start_init
(
ThreadFunc
thread_exit_time
,
ThreadFunc
thread_mqtt_beat
,
ThreadFunc
thread_open_browser
,
ThreadFunc
thread_mqtt_reconnect
,
ThreadFunc
thread_open_browser
,
ThreadFunc
thread_mqtt_reconnect
,
ThreadFunc
thread_time_calculation
,
ThreadFunc
thread_play_mp3
,
ThreadFunc
thread_time_calculation
,
ThreadFunc
thread_play_mp3
,
ThreadFunc
thread_steering_control
);
ThreadFunc
thread_steering_control
,
ThreadFunc
thread_wifi_recovery
);
/*像服务器请求*/
/*像服务器请求*/
int
request_date
();
int
request_date
();
...
...
app/main/pthread_open.c
View file @
8359d134
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
#include "webrtcpush_config.h"
#include "webrtcpush_config.h"
#include "webrtcpush_run.h"
#include "webrtcpush_run.h"
#define PTHREAD_MAX
7
#define PTHREAD_MAX
8
static
pthread_t
s_thread
[
PTHREAD_MAX
];
// 全局线程句柄数组(或传参)
static
pthread_t
s_thread
[
PTHREAD_MAX
];
// 全局线程句柄数组(或传参)
...
@@ -19,7 +19,8 @@ static void *s_args[PTHREAD_MAX] = {NULL};
...
@@ -19,7 +19,8 @@ static void *s_args[PTHREAD_MAX] = {NULL};
int
thread_start_init
(
ThreadFunc
thread_exit_time
,
ThreadFunc
thread_mqtt_beat
,
int
thread_start_init
(
ThreadFunc
thread_exit_time
,
ThreadFunc
thread_mqtt_beat
,
ThreadFunc
thread_open_browser
,
ThreadFunc
thread_mqtt_reconnect
,
ThreadFunc
thread_open_browser
,
ThreadFunc
thread_mqtt_reconnect
,
ThreadFunc
thread_time_calculation
,
ThreadFunc
thread_play_mp3
,
ThreadFunc
thread_steering_control
)
ThreadFunc
thread_time_calculation
,
ThreadFunc
thread_play_mp3
,
ThreadFunc
thread_steering_control
,
ThreadFunc
thread_wifi_recovery
)
{
{
ThreadFunc
thread_funcs
[]
=
{
ThreadFunc
thread_funcs
[]
=
{
...
@@ -29,7 +30,8 @@ int thread_start_init(ThreadFunc thread_exit_time, ThreadFunc thread_mqtt_beat,
...
@@ -29,7 +30,8 @@ int thread_start_init(ThreadFunc thread_exit_time, ThreadFunc thread_mqtt_beat,
thread_mqtt_reconnect
,
thread_mqtt_reconnect
,
thread_time_calculation
,
thread_time_calculation
,
thread_play_mp3
,
thread_play_mp3
,
thread_steering_control
};
thread_steering_control
,
thread_wifi_recovery
};
for
(
int
i
=
0
;
i
<
PTHREAD_MAX
;
i
++
)
for
(
int
i
=
0
;
i
<
PTHREAD_MAX
;
i
++
)
{
{
...
...
app/main/pthread_open.h
View file @
8359d134
...
@@ -7,7 +7,7 @@ typedef void *(*ThreadFunc)(void *);
...
@@ -7,7 +7,7 @@ typedef void *(*ThreadFunc)(void *);
int
thread_start_init
(
ThreadFunc
thread_exit_time
,
ThreadFunc
thread_mqtt_beat
,
int
thread_start_init
(
ThreadFunc
thread_exit_time
,
ThreadFunc
thread_mqtt_beat
,
ThreadFunc
thread_open_browser
,
ThreadFunc
thread_mqtt_reconnect
,
ThreadFunc
thread_open_browser
,
ThreadFunc
thread_mqtt_reconnect
,
ThreadFunc
thread_time_calculation
,
ThreadFunc
thread_play_mp3
,
ThreadFunc
thread_time_calculation
,
ThreadFunc
thread_play_mp3
,
ThreadFunc
thread_steering_control
);
ThreadFunc
thread_steering_control
,
ThreadFunc
thread_wifi_recovery
);
void
*
thread_exit_time
(
void
*
arg
)
;
//异常停止线程
void
*
thread_exit_time
(
void
*
arg
)
;
//异常停止线程
void
*
thread_mqtt_beat
(
void
*
arg
)
;
//心跳线程
void
*
thread_mqtt_beat
(
void
*
arg
)
;
//心跳线程
...
@@ -19,4 +19,4 @@ void *thread_steering_control(void *arg);//方向盘控制线程
...
@@ -19,4 +19,4 @@ void *thread_steering_control(void *arg);//方向盘控制线程
void
thread_end_close
()
;
void
thread_end_close
()
;
#endif
#endif
\ No newline at end of file
app/main/wifi_autoconfig.c
View file @
8359d134
#include "wifi_autoconfig.h"
#include "wifi_autoconfig.h"
#include "main.h"
#include "main.h"
#include "http_config_mqtt.h"
#include "http_config_mqtt.h"
#include "device_wifi_manager.h"
#include "audioplay.h"
#include "cJSON.h"
#include "cJSON.h"
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
...
@@ -8,6 +10,10 @@
...
@@ -8,6 +10,10 @@
#include <unistd.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/stat.h>
#define WIFI_OFFLINE_RECOVERY_INTERVAL_SEC 40
#define WIFI_RECOVERY_SETTLE_SEC 5
#define WIFI_RECOVERY_MAX_SWITCH_ATTEMPTS 3
static
const
char
*
s_audio_files
[]
=
{
static
const
char
*
s_audio_files
[]
=
{
"noCameraDetected.mp3"
,
"noCameraDetected.mp3"
,
"connectionFailed.mp3"
,
"connectionFailed.mp3"
,
...
@@ -155,23 +161,12 @@ static void build_lang_filepath(char *filepath, size_t size, const char *lang, c
...
@@ -155,23 +161,12 @@ static void build_lang_filepath(char *filepath, size_t size, const char *lang, c
}
}
static
int
try_play_filepath
(
const
char
*
filepath
)
{
static
int
try_play_filepath
(
const
char
*
filepath
)
{
char
cmd
[
512
];
if
(
access
(
filepath
,
F_OK
)
!=
0
)
{
if
(
access
(
filepath
,
F_OK
)
!=
0
)
{
return
0
;
return
0
;
}
}
if
(
is_command_available
(
"ffplay"
))
{
if
(
audioplay_file_with_browser_preempt
(
filepath
,
1
.
0
)
==
0
)
{
snprintf
(
cmd
,
sizeof
(
cmd
),
"ffplay -nodisp -autoexit -loglevel quiet
\"
%s
\"
"
,
filepath
);
my_zlog_debug
(
"离线音频播放成功: %s"
,
filepath
);
if
(
system
(
cmd
)
==
0
)
{
return
1
;
my_zlog_debug
(
"ffplay 播放成功: %s"
,
filepath
);
return
1
;
}
}
if
(
is_command_available
(
"paplay"
))
{
snprintf
(
cmd
,
sizeof
(
cmd
),
"paplay
\"
%s
\"
"
,
filepath
);
if
(
system
(
cmd
)
==
0
)
{
my_zlog_debug
(
"paplay 播放成功: %s"
,
filepath
);
return
1
;
}
}
}
my_zlog_warn
(
"音频播放失败: %s"
,
filepath
);
my_zlog_warn
(
"音频播放失败: %s"
,
filepath
);
return
0
;
return
0
;
...
@@ -467,6 +462,48 @@ static int has_internet_connection() {
...
@@ -467,6 +462,48 @@ static int has_internet_connection() {
return
has_business_api_connection
();
return
has_business_api_connection
();
}
}
/* 一次完整的断网恢复周期:播报与扫描主动连接必须在同一个周期执行。 */
static
int
run_offline_recovery_cycle
(
void
)
{
my_zlog_warn
(
"断网恢复: 开始 40 秒周期播报和已保存 WiFi 检测"
);
play_audio_file
(
"connectionFailed.mp3"
);
for
(
int
attempt
=
0
;
attempt
<
WIFI_RECOVERY_MAX_SWITCH_ATTEMPTS
;
attempt
++
)
{
int
connect_result
=
device_wifi_connect_best_saved_visible
();
if
(
connect_result
<=
0
)
break
;
sleep
(
WIFI_RECOVERY_SETTLE_SEC
);
if
(
has_internet_connection
())
{
my_zlog_info
(
"断网恢复: 已恢复互联网连接"
);
prepare_offline_audio_after_network
();
play_audio_file
(
"connectionSuccessful.mp3"
);
return
1
;
}
my_zlog_warn
(
"断网恢复: WiFi 已连接但互联网仍不可用,继续尝试其它已保存网络"
);
}
return
0
;
}
void
*
thread_wifi_offline_recovery
(
void
*
arg
)
{
(
void
)
arg
;
my_zlog_info
(
"断网恢复线程已启动,检测间隔=%d秒"
,
WIFI_OFFLINE_RECOVERY_INTERVAL_SEC
);
while
(
1
)
{
time_t
cycle_started
=
time
(
NULL
);
if
(
!
has_internet_connection
())
run_offline_recovery_cycle
();
time_t
cycle_finished
=
time
(
NULL
);
int
elapsed
=
(
int
)
difftime
(
cycle_finished
,
cycle_started
);
int
remaining
=
WIFI_OFFLINE_RECOVERY_INTERVAL_SEC
-
elapsed
;
if
(
remaining
>
0
)
sleep
((
unsigned
int
)
remaining
);
}
return
NULL
;
}
// 尝试连接 WiFi
// 尝试连接 WiFi
static
int
try_connect_wifi
(
const
char
*
ssid
,
const
char
*
password
)
{
static
int
try_connect_wifi
(
const
char
*
ssid
,
const
char
*
password
)
{
char
cmd
[
512
];
char
cmd
[
512
];
...
@@ -495,29 +532,23 @@ void check_and_autoconfig_wifi() {
...
@@ -495,29 +532,23 @@ void check_and_autoconfig_wifi() {
my_zlog_warn
(
"进入扫码循环,将每隔几秒提示一次..."
);
my_zlog_warn
(
"进入扫码循环,将每隔几秒提示一次..."
);
const
char
*
img_path
=
"/tmp/wifi_scan.jpg"
;
const
char
*
img_path
=
"/tmp/wifi_scan.jpg"
;
time_t
last_prompt_time
=
0
;
time_t
last_recovery_time
=
0
;
time_t
last_camera_warning_time
=
0
;
// 首次播放
// play_audio_file("toScanQRCode.mp3"); // 改为进入循环后由定时逻辑触发(且会先检查网络)
last_prompt_time
=
0
;
// 设为 0,确保进入循环第一次就会满足 difftime > 10.0 并触发播放逻辑
while
(
1
)
{
while
(
1
)
{
// 0. 优先检查网络是否已恢复 (比如插上了网线)
if
(
has_internet_connection
())
{
my_zlog_info
(
"检测到网络已恢复,退出扫码模式。"
);
prepare_offline_audio_after_network
();
play_audio_file
(
"connectionSuccessful.mp3"
);
break
;
}
// 1. 周期性播放提示音 (每 10 秒)
time_t
now
=
time
(
NULL
);
time_t
now
=
time
(
NULL
);
if
(
difftime
(
now
,
last_prompt_time
)
>=
10
.
0
)
{
if
(
last_recovery_time
==
0
||
// 再检查一次,如果有网就不播了,避免干扰
difftime
(
now
,
last_recovery_time
)
>=
WIFI_OFFLINE_RECOVERY_INTERVAL_SEC
)
{
if
(
!
has_internet_connection
())
{
/* 从本轮开始计时,保证两轮检测启动时间约间隔 40 秒。 */
play_audio_file
(
"toScanQRCode.mp3"
);
last_recovery_time
=
now
;
if
(
has_internet_connection
())
{
my_zlog_info
(
"检测到网络已恢复,退出扫码模式。"
);
prepare_offline_audio_after_network
();
play_audio_file
(
"connectionSuccessful.mp3"
);
break
;
}
}
last_prompt_time
=
now
;
if
(
run_offline_recovery_cycle
())
break
;
}
}
// 2. 调用 fswebcam 拍照
// 2. 调用 fswebcam 拍照
...
@@ -532,8 +563,13 @@ void check_and_autoconfig_wifi() {
...
@@ -532,8 +563,13 @@ void check_and_autoconfig_wifi() {
if
(
cap_ret
!=
0
)
{
if
(
cap_ret
!=
0
)
{
// 拍照失败,通常意味着没插摄像头或设备忙
// 拍照失败,通常意味着没插摄像头或设备忙
my_zlog_error
(
"摄像头拍照失败 (fswebcam 返回 %d),请检查设备连接。"
,
cap_ret
);
my_zlog_error
(
"摄像头拍照失败 (fswebcam 返回 %d),请检查设备连接。"
,
cap_ret
);
play_audio_file
(
"noCameraDetected.mp3"
);
now
=
time
(
NULL
);
sleep
(
10
);
// 失败后多睡一会儿
if
(
last_camera_warning_time
==
0
||
difftime
(
now
,
last_camera_warning_time
)
>=
WIFI_OFFLINE_RECOVERY_INTERVAL_SEC
)
{
play_audio_file
(
"noCameraDetected.mp3"
);
last_camera_warning_time
=
now
;
}
sleep
(
5
);
continue
;
continue
;
}
}
...
...
app/main/wifi_autoconfig.h
View file @
8359d134
...
@@ -21,6 +21,9 @@
...
@@ -21,6 +21,9 @@
// 启动断网自动配网流程
// 启动断网自动配网流程
void
check_and_autoconfig_wifi
();
void
check_and_autoconfig_wifi
();
// 运行期断网守护:每 40 秒播报、扫描并尝试连接已保存 WiFi
void
*
thread_wifi_offline_recovery
(
void
*
arg
);
// 启动时加载离线语言配置(默认中文)
// 启动时加载离线语言配置(默认中文)
void
audio_init_offline_language
(
void
);
void
audio_init_offline_language
(
void
);
...
...
build/main
View file @
8359d134
No preview for this file type
drivers/sensors/audioplay.c.bak-audio-preempt-20260717
deleted
100644 → 0
View file @
7200adca
#include "common.h"
#include "audioplay.h"
#include "audio_sink.h" /* USB声卡排队锁 */
#include "device_identity.h"
#include "mqtt_init.h"
#include "http_config_mqtt.h"
#include "audiotts_play.h"
#include "wifi_autoconfig.h"
#include <stdio.h>
#include <pthread.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#define AUDIO_USB_ALSA_DEVICE "hw:2,0"
#define AUDIO_LOCAL_ALSA_DEVICE "plughw:2,0"
#define AUDIO_LOCAL_PLAY_TIMEOUT_SEC 8
static int s_audio_status=7;
static char s_urlbuf[512];
static char s_keybuf[256];
static double s_audio_volume=0.8;
#define AUDIO_DRIVING_LEVEL_MAX 30
static int s_local_play_pending = 0;
static char s_local_filepath[512];
static pthread_mutex_t s_local_play_mutex = PTHREAD_MUTEX_INITIALIZER;
static int local_is_cn_lang(const char *lang) {
return lang != NULL && (strcmp(lang, AUDIO_LANG_ZH) == 0 || strcmp(lang, "cn") == 0);
}
static int local_is_en_lang(const char *lang) {
return lang != NULL &&
(strcmp(lang, AUDIO_LANG_EN) == 0 || strcmp(lang, "en") == 0 || strcmp(lang, "EN") == 0);
}
static void local_resolve_lang(const char *language_override, char *out, size_t size) {
if (language_override == NULL || language_override[0] == '\0' ||
strcasecmp(language_override, "default") == 0) {
snprintf(out, size, "%s", audio_get_language());
return;
}
if (local_is_cn_lang(language_override)) {
snprintf(out, size, "%s", AUDIO_LANG_ZH);
return;
}
if (local_is_en_lang(language_override)) {
snprintf(out, size, "%s", AUDIO_LANG_EN);
return;
}
snprintf(out, size, "%s", audio_get_language());
my_zlog_warn("2017 未知 language: %s,使用当前语言: %s", language_override, out);
}
static int local_resolve_filepath(const char *filename, const char *language_override,
char *out, size_t size) {
char lang[8];
int use_default_lang = (language_override == NULL || language_override[0] == '\0' ||
strcasecmp(language_override, "default") == 0);
local_resolve_lang(language_override, lang, sizeof(lang));
if (local_is_cn_lang(lang)) {
snprintf(out, size, "%s/%s", AUDIO_LOCAL_BASE, filename);
return access(out, F_OK) == 0;
}
snprintf(out, size, "%s/%s/%s", AUDIO_LOCAL_BASE, lang, filename);
if (access(out, F_OK) == 0) {
return 1;
}
if (use_default_lang) {
snprintf(out, size, "%s/%s", AUDIO_LOCAL_BASE, filename);
return access(out, F_OK) == 0;
}
return 0;
}
static void local_queue_play(const char *filename, const char *language) {
char resolved[512];
if (!local_resolve_filepath(filename, language, resolved, sizeof(resolved))) {
my_zlog_warn("2017 本地音频不存在: %s", filename);
return;
}
pthread_mutex_lock(&s_local_play_mutex);
snprintf(s_local_filepath, sizeof(s_local_filepath), "%s", resolved);
s_local_play_pending = 1;
pthread_mutex_unlock(&s_local_play_mutex);
my_zlog_info("2017 已排队本地音频: %s", resolved);
}
void audioplay_local_mqtt_receive(cJSON *body) {
if (!cJSON_IsObject(body)) {
my_zlog_warn("2017 body 无效");
return;
}
cJSON *status = cJSON_GetObjectItemCaseSensitive(body, "status");
cJSON *level_item = cJSON_GetObjectItemCaseSensitive(body, "level");
cJSON *language = cJSON_GetObjectItemCaseSensitive(body, "language");
if (!cJSON_IsString(status) || status->valuestring == NULL) {
my_zlog_warn("2017 缺少 status");
return;
}
const char *lang = "default";
if (cJSON_IsString(language) && language->valuestring != NULL) {
lang = language->valuestring;
}
if (strcmp(status->valuestring, "speed") == 0) {
local_queue_play("Speedupforoneminute.mp3", lang);
return;
}
if (strcmp(status->valuestring, "drivinglevel") == 0) {
int level = 0;
if (cJSON_IsString(level_item) && level_item->valuestring != NULL) {
level = atoi(level_item->valuestring);
} else if (cJSON_IsNumber(level_item)) {
level = level_item->valueint;
} else {
my_zlog_warn("2017 drivinglevel 缺少 level");
return;
}
if (level < 1 || level > AUDIO_DRIVING_LEVEL_MAX) {
my_zlog_warn("2017 drivinglevel level 无效: %d (1-%d)",
level, AUDIO_DRIVING_LEVEL_MAX);
return;
}
char filename[48];
snprintf(filename, sizeof(filename), "DrivingLevel%d.mp3", level);
local_queue_play(filename, lang);
return;
}
my_zlog_warn("2017 未知 status: %s", status->valuestring);
}
static double audioplay_volume_clamp(double v) {
if (v < 0.0) {
return 0.0;
}
if (v > 2.0) {
return 2.0;
}
return v;
}
int audio_wheat_init();
static int audio_system_exit_code(int status) {
if (status == -1) {
return -1;
}
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) {
return 128 + WTERMSIG(status);
}
return -1;
}
static void shell_single_quote(char *out, size_t size, const char *in) {
size_t pos = 0;
if (!out || size == 0) {
return;
}
out[pos++] = '\'';
if (in) {
for (const char *p = in; *p && pos + 5 < size; p++) {
if (*p == '\'') {
const char *esc = "'\\''";
for (const char *e = esc; *e && pos + 1 < size; e++) {
out[pos++] = *e;
}
} else {
out[pos++] = *p;
}
}
}
if (pos + 1 < size) {
out[pos++] = '\'';
}
out[pos] = '\0';
}
static int play_local_audio_file(const char *filepath) {
char quoted_path[1024];
char command[2048];
int ret;
int exit_code;
shell_single_quote(quoted_path, sizeof(quoted_path), filepath);
/* 排队等待USB声卡: 如果audio_sink正在播放手机音频, 等它释放 */
audio_sink_lock_alsa();
snprintf(command, sizeof(command),
/* sync=true 防止 EOS 提前关 ALSA 丢尾部(只播前半段); channels=2 上混规避 USB 声卡单声道 ring_buffer CRITICAL */
"timeout %ds gst-launch-1.0 -q filesrc location=%s ! mpegaudioparse ! mpg123audiodec ! audioconvert ! audioresample ! audio/x-raw,channels=2 ! alsasink device=%s sync=true >/dev/null 2>&1",
AUDIO_LOCAL_PLAY_TIMEOUT_SEC, quoted_path, AUDIO_LOCAL_ALSA_DEVICE);
ret = system(command);
exit_code = audio_system_exit_code(ret);
if (exit_code != 0) {
my_zlog_warn("本地音频 GStreamer 播放失败 exit=%d,尝试 ffplay: %s",
exit_code, filepath);
snprintf(command, sizeof(command),
"timeout %ds ffplay -nodisp -autoexit -loglevel warning %s",
AUDIO_LOCAL_PLAY_TIMEOUT_SEC, quoted_path);
ret = system(command);
exit_code = audio_system_exit_code(ret);
}
audio_sink_unlock_alsa();
return exit_code;
}
//接收音频播放
void audioplay_mqtt_receive(cJSON *json) {
// 解析"audioLink"字段(修正了原始JSON中的拼写错误)
cJSON *audio_link = cJSON_GetObjectItemCaseSensitive(json, "audioLink");
if (cJSON_IsString(audio_link) && (audio_link->valuestring != NULL)) {
my_zlog_debug("音频链接: %s", audio_link->valuestring);
char *url=audio_link->valuestring;
snprintf(s_urlbuf, sizeof(s_urlbuf), "%s", url);
} else {
my_zlog_warn("错误:无法解析音频链接字段");
}
// 解析"key"字段
cJSON *key_char = cJSON_GetObjectItemCaseSensitive(json, "key");
if (cJSON_IsString(key_char) && (key_char->valuestring != NULL)) {
my_zlog_debug("音频链接KEY: %s", key_char->valuestring);
char *key=key_char->valuestring;
snprintf(s_keybuf, sizeof(s_keybuf), "%s", key);
} else {
my_zlog_warn("错误:无法解析音频链接");
s_audio_status=5;
}
// 解析"status"字段
cJSON *s_audio_status_val = cJSON_GetObjectItemCaseSensitive(json, "status");
if (cJSON_IsNumber(s_audio_status_val)) {
my_zlog_debug("标志: %d", s_audio_status_val->valueint);
s_audio_status=s_audio_status_val->valueint;
} else {
my_zlog_warn("错误:无法解析标志字段");
}
// 解析"volume"字段
cJSON *volume = cJSON_GetObjectItemCaseSensitive(json, "volume");
if (cJSON_IsNumber(volume)) {
my_zlog_debug("声量大小: %.3f", volume->valuedouble);
s_audio_volume = audioplay_volume_clamp(volume->valuedouble);
} else {
my_zlog_warn("错误:无法解析声量字段");
}
}
//发送音频播放是否完毕
void audioplay_send_mqtt() {
cJSON *root = cJSON_CreateObject();
cJSON *body = cJSON_CreateObject();
cJSON *head = cJSON_CreateObject();
// 添加各个字段到 JSON 对象
cJSON_AddStringToObject(body, "type", "audio");
cJSON_AddStringToObject(body, "audioLink", s_urlbuf);
cJSON_AddStringToObject(body, "key", s_keybuf);
cJSON_AddNumberToObject(body, "status", s_audio_status);
cJSON_AddNumberToObject(body, "volume", s_audio_volume);
cJSON_AddNumberToObject(head, "message_type",3001);
cJSON_AddItemToObject(root, "body", body);
cJSON_AddItemToObject(root, "head",head);
// 将 JSON 对象转换为字符串
char* json_string = cJSON_PrintUnformatted(root);
my_zlog_debug("%s",json_string);
mqtt_publish_to_all(mqtt_topic_pure_number(), json_string, 0);
free(json_string);
cJSON_Delete(root);
}
//音频播放
void audioplay_cycle(){
char command[2048];
int ret;
while(1){
if(s_audio_status==0){
char *urlmoddle=s_urlbuf;
s_audio_volume = audioplay_volume_clamp(s_audio_volume);
snprintf(command, sizeof(command),
"sudo ffplay -nodisp -autoexit -loglevel quiet -af \"volume=%.3f\" \"%s\"",
s_audio_volume,urlmoddle);
my_zlog_debug("播放地址: %s", s_urlbuf);
my_zlog_debug("执行播放命令: %s", command);
ret = system(command);
if (ret != 0) {
my_zlog_error("播放失败");
s_audio_status=2;
}
if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
my_zlog_debug("播放已成功完成 : %s ", s_urlbuf);
s_audio_status=1;
} else {
my_zlog_warn("播放失败或中断: %s ", s_urlbuf);
s_audio_status=2;
}
audioplay_send_mqtt();
}
char local_filepath[sizeof(s_local_filepath)];
int local_play_pending = 0;
pthread_mutex_lock(&s_local_play_mutex);
if (s_local_play_pending) {
s_local_play_pending = 0;
snprintf(local_filepath, sizeof(local_filepath), "%s", s_local_filepath);
local_play_pending = 1;
}
pthread_mutex_unlock(&s_local_play_mutex);
if (local_play_pending) {
int exit_code;
my_zlog_info("播放本地音频: %s", local_filepath);
exit_code = play_local_audio_file(local_filepath);
if (exit_code == 0) {
my_zlog_debug("本地音频播放完成: %s", local_filepath);
} else {
my_zlog_warn("本地音频播放失败 exit=%d: %s", exit_code, local_filepath);
}
}
video_tts_play();
delay_ms(100);
}
}
static void trim_line(char *s)
{
if (!s)
return;
s[strcspn(s, "\r\n")] = '\0';
}
static int pulse_pactl_exit_code(int status)
{
if (status == -1)
return -1;
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
static int pulse_output_means_already_loaded(const char *output)
{
if (!output || !output[0])
return 0;
return strstr(output, "already loaded") != NULL ||
strstr(output, "Already loaded") != NULL ||
strstr(output, "Module exists") != NULL;
}
static int pulse_module_exists(const char *module_name, const char *device)
{
FILE *fp = popen("pactl list modules short 2>/dev/null", "r");
char line[512];
char needle[96];
if (!fp)
return 0;
snprintf(needle, sizeof(needle), "device=%s", device);
while (fgets(line, sizeof(line), fp) != NULL) {
if (strstr(line, module_name) != NULL && strstr(line, needle) != NULL) {
pclose(fp);
return 1;
}
}
pclose(fp);
return 0;
}
static int pulse_load_alsa_module(const char *module_name, const char *label)
{
char cmd[256];
char output[512];
char line[256];
if (pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) {
my_zlog_info("%s 已注册,跳过 (%s device=%s)",
label, module_name, AUDIO_USB_ALSA_DEVICE);
return 0;
}
snprintf(cmd, sizeof(cmd), "pactl load-module %s device=%s 2>&1",
module_name, AUDIO_USB_ALSA_DEVICE);
FILE *fp = popen(cmd, "r");
if (!fp) {
my_zlog_warn("%s 注册失败: 无法执行 pactl (%s)", label, strerror(errno));
return -1;
}
output[0] = '\0';
while (fgets(line, sizeof(line), fp) != NULL) {
trim_line(line);
if (!line[0])
continue;
if (output[0]) {
strncat(output, "; ", sizeof(output) - strlen(output) - 1);
}
strncat(output, line, sizeof(output) - strlen(output) - 1);
}
int status = pclose(fp);
int exit_code = pulse_pactl_exit_code(status);
if (exit_code == 0) {
my_zlog_info("%s 注册成功 (%s device=%s)%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE,
output[0] ? ", pactl: " : "", output[0] ? output : "");
return 0;
}
if (pulse_output_means_already_loaded(output) ||
pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) {
my_zlog_info("%s 已注册,跳过 (%s device=%s)%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE,
output[0] ? ": " : "", output[0] ? output : "");
return 0;
}
my_zlog_warn("%s 注册失败 (%s device=%s): exit=%d%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE, exit_code,
output[0] ? ", pactl: " : ", pactl 无输出",
output[0] ? output : "");
return -1;
}
int audio_wheat_init(){
return pulse_load_alsa_module("module-alsa-sink", "喇叭");
}
int audio_speaker_init() {
return pulse_load_alsa_module("module-alsa-source", "麦");
}
int audio_init(){
delay_s(1);
audio_wheat_init();
delay_s(1);
audio_speaker_init();
return 0;
}
#define CONFIG_FILE "/usr/share/pulseaudio/alsa-mixer/profile-sets/default.conf"
static const char *s_yundea_block =
"# 为 Yundea 1076 USB 二合一设备添加配置\n"
"[Mapping yundea-input]\n"
"device-strings = hw:%f,0\n"
"direction = input\n"
"priority = 100\n"
"channel-map = mono\n"
"\n"
"[Mapping yundea-output]\n"
"device-strings = hw:%f,0\n"
"direction = output\n"
"priority = 100\n"
"channel-map = left,right\n"
"\n"
"[Profile yundea-duplex]\n"
"input-mappings = yundea-input\n"
"output-mappings = yundea-output\n"
"priority = 200\n";
/*
*功能:加入usb声卡配置
*
*/
int audio_config_init() {
delay_s(12);
FILE *fp = fopen(CONFIG_FILE, "r");
if (!fp) {
perror("无法打开配置文件进行读取");
return 1;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buffer = malloc(size + 1);
if (!buffer) {
my_zlog_error("内存分配失败");
fclose(fp);
return 1;
}
size_t result =fread(buffer, 1, size, fp);
if (result != size) {
my_zlog_warn("Error: Failed to read audio data");
}
buffer[size] = '\0';
fclose(fp);
// 检查是否包含完整配置块
if (strstr(buffer, s_yundea_block) != NULL) {
my_zlog_debug("配置文件中已包含指定配置,跳过添加。");
free(buffer);
return 0;
}
free(buffer);
my_zlog_info("未找到指定配置,正在追加到文件末尾...");
// 打开文件追加配置
fp = fopen(CONFIG_FILE, "a");
if (!fp) {
my_zlog_error("无法打开配置文件进行写入");
return 1;
}
fprintf(fp, "\n%s", s_yundea_block);
fclose(fp);
my_zlog_info("配置已成功追加。");
return 0;
}
drivers/sensors/audioplay.h.bak-audio-preempt-20260717
deleted
100644 → 0
View file @
7200adca
#ifndef AUDIOPLAY_H__
#define AUDIOPLAY_H__
#include <cjson/cJSON.h>
void audioplay_mqtt_receive(cJSON *body); //接收音频mqtt播放函数
void audioplay_local_mqtt_receive(cJSON *body); // 2017 有人驾驶本地音频
void audioplay_cycle();//音频播放线程中函数
int audio_wheat_init();
int audio_speaker_init();
int audio_init();
int audio_config_init();//加入配置
#endif
\ No newline at end of file
drivers/sensors/audiotts_play.c.bak-audio-preempt-20260717
deleted
100644 → 0
View file @
7200adca
#include "audiotts_play.h"
#include "audioplay.h"
#include "device_identity.h"
#include "mqtt_init.h"
#include "http_config_mqtt.h"
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#define TTS_ADUINO_PTAH PROJECT_ROOT_DIR "/audio.mp3"
// 全局变量:存储 HTTP 响应数据
static char *s_http_response = NULL;
static size_t s_http_response_len = 0;
static bool s_audio_tts_index=0;
static double s_volume=1.0;
// HTTP 响应回调函数:接收服务器返回的 JSON 数据
static size_t http_write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
size_t total = size * nmemb;
// 扩容并拷贝响应数据
char *temp = realloc(s_http_response, s_http_response_len + total + 1);
if (!temp) {
my_zlog_error("Realloc response buffer failed");
return 0;
}
s_http_response = temp;
memcpy(s_http_response + s_http_response_len, ptr, total);
s_http_response_len += total;
s_http_response[s_http_response_len] = '\0'; // 末尾加结束符
return total;
}
// 生成 UUID(去除连字符,保证 reqid 唯一性)
static void generate_uuid(char *buf, size_t len) {
if (len < 33) return; // UUID 去除连字符后 32 位
uuid_t uuid;
uuid_generate_random(uuid);
uuid_unparse(uuid, buf);
// 移除连字符
char *p = strchr(buf, '-');
while (p) {
memmove(p, p + 1, strlen(p));
p = strchr(buf, '-');
}
}
// Base64 解码(将响应中的音频数据解码为二进制)
static unsigned char *base64_decode(const char *input, size_t *output_len) {
BIO *bio, *b64;
size_t len = strlen(input);
unsigned char *buffer = (unsigned char *)malloc(len * 3 / 4 + 1024); // 预留足够空间
if (!buffer) return NULL;
// 初始化 OpenSSL Base64 解码
OpenSSL_add_all_algorithms(); // 初始化 OpenSSL 算法(避免解码失败)
b64 = BIO_new(BIO_f_base64());
bio = BIO_new_mem_buf(input, len);
bio = BIO_push(b64, bio);
// 禁用换行符处理(关键:避免 Base64 字符串含换行导致解码失败)
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
*output_len = BIO_read(bio, buffer, len * 3 / 4 + 1024);
// 清理资源
BIO_free_all(bio);
// 调整缓冲区大小为实际解码长度
buffer = realloc(buffer, *output_len);
return buffer;
}
/**
* 保存音频数据到文件(目录已存在)
* @param data 音频数据指针
* @param len 音频数据长度
* @param filepath 完整文件路径
* @return 0成功,1失败
*/
static int save_audio_file(const void *data, size_t len, const char *filepath) {
if (!data || len == 0 || !filepath) {
my_zlog_error("Invalid parameters for save_audio_file\n");
return 1;
}
FILE *fp = fopen(filepath, "wb");
if (!fp) {
my_zlog_error( "Open file %s failed: %s\n", filepath, strerror(errno));
return 1;
}
size_t written = fwrite(data, 1, len, fp);
fclose(fp);
if (written != len) {
my_zlog_error("Write file %s incomplete: %zu/%zu bytes", filepath, written, len);
remove(filepath); // 删除不完整的文件
return 1;
}
my_zlog_debug("Success! Audio saved to: %s (%zu bytes)", filepath, written);
return 0;
}
/*
*功能:获得随机字符串,不使用uuid
*/
static char* generate_random_string() {
// 设置随机种子(建议在main函数中调用一次即可)
static int seeded = 0;
if (!seeded) {
srand((unsigned int)time(NULL));
seeded = 1;
}
// 动态分配内存(10个字符 + 终止符)
char* str = (char*)malloc(11 * sizeof(char));
if (!str) return NULL;
// 生成数字字符(ASCII 48-57)
for (int i = 0; i < 10; i++) {
str[i] = '0' + (rand() % 10);
}
str[10] = '\0'; // 添加终止符
return str;
}
char *get_audio_text_tts(char *date ,bool flag){
static char text[256] = {0};
if(flag==1){
return text;
}else {
size_t date_len = strlen(date);
if (date) {
strcpy(text, date);
text[date_len] = '\0'; // 确保以null结尾
} else {
text[0] = '\0'; // 如果date为NULL,清空文本
}
}
}
/**
* 保存音频数据到文件(目录已存在)
* @param text 传入需要转化的文字指针
* @return 0成功,1失败
*/
int audio_tts(const char *text) {
// 1. 配置参数(替换为你的真实信息,已验证有效)
const char *appid = "5745089507"; // 你的火山引擎 AppID
const char *access_token = "0BeoBb_DOjrxfM4aTsJfUFVVS0k5V9mt"; // 你的访问令牌
const char *voice_type = "zh_female_linjianvhai_moon_bigtts"; // 非流式音色
const char *cluster = "volcano_tts"; // 固定值(必选)
char *uid = generate_random_string(); // 任意非空字符串
const char *encoding = "mp3"; // 输出格式(mp3/wav/pcm/ogg_opus)
const char *endpoint = "https://openspeech.bytedance.com/api/v1/tts"; // HTTP 接口地址
// 2. 生成唯一 reqid(每次调用必须不同)
char reqid[33] = {0};
generate_uuid(reqid, sizeof(reqid));
my_zlog_debug("Generated ReqID: %s", reqid);
// 3. 构建请求 JSON(严格遵循接口参数格式)
cJSON *root = cJSON_CreateObject();
if (!root) {
my_zlog_error("Create JSON root failed");
free(uid);
return 1;
}
// app 节点(必选)
cJSON *app = cJSON_CreateObject();
cJSON_AddStringToObject(app, "appid", appid);
cJSON_AddStringToObject(app, "token", access_token);
cJSON_AddStringToObject(app, "cluster", cluster);
cJSON_AddItemToObject(root, "app", app);
// user 节点(必选)
cJSON *user = cJSON_CreateObject();
cJSON_AddStringToObject(user, "uid", uid);
cJSON_AddItemToObject(root, "user", user);
// audio 节点(必选)
cJSON *audio = cJSON_CreateObject();
cJSON_AddStringToObject(audio, "voice_type", voice_type);
cJSON_AddStringToObject(audio, "encoding", encoding);
cJSON_AddNumberToObject(audio, "speed_ratio", 1.0); // 语速(0.1~2.0)
cJSON_AddNumberToObject(audio, "rate", 16000); // 采样率(默认24000)
cJSON_AddNumberToObject(audio, "loudness_ratio", 1.0); // 音量(0.5~2.0)
cJSON_AddBoolToObject(audio, "enable_emotion", true); // 开启情感
cJSON_AddStringToObject(audio, "emotion", "gentle"); // 情感类型(gentle=温柔)
cJSON_AddNumberToObject(audio, "emotion_scale", 4.0); // 情感强度(1~5,默认4)
cJSON_AddItemToObject(root, "audio", audio);
// request 节点(必选)
cJSON *request = cJSON_CreateObject();
cJSON_AddStringToObject(request, "reqid", reqid);
cJSON_AddStringToObject(request, "text", text);
cJSON_AddStringToObject(request, "operation", "query"); // HTTP 非流式固定为 query
cJSON_AddStringToObject(request, "extra_param", "{\"disable_markdown_filter\":true}"); // 附加参数
cJSON_AddItemToObject(root, "request", request);
// 生成 JSON 字符串(无格式,减少传输体积)
char *payload = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
if (!payload) {
my_zlog_error("Generate JSON payload failed");
free(uid);
return 1;
}
my_zlog_debug("Request Payload: %s", payload);
// 4. 初始化 curl 并配置 HTTP 请求
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL *curl = curl_easy_init();
if (!curl) {
my_zlog_error("Initialize curl failed");
free(payload);
free(uid);
return 1;
}
// 设置请求头(关键:Authorization 分号分隔,Content-Type 为 JSON)
struct curl_slist *headers = NULL;
char auth_header[256] = {0};
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer;%s", access_token);
headers = curl_slist_append(headers, auth_header);
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Accept: application/json");
if (s_http_response == NULL) {
s_http_response_len = 0;
}else {
return 1;
}
// curl 核心配置
curl_easy_setopt(curl, CURLOPT_URL, endpoint);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L); // POST 方法
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); // 请求体
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(payload));
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_write_callback); // 接收响应回调
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 开发环境禁用 SSL 证书校验(生产环境建议启用)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L); // 超时时间 15 秒(避免网络慢导致超时)
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); // 连接超时 10 秒
// 5. 执行 HTTP 请求
my_zlog_debug("Sending request to %s...", endpoint);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
my_zlog_error("curl_easy_perform failed: %s", curl_easy_strerror(res));
free(uid);
free(payload);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
// 6. 解析 HTTP 响应(JSON 格式)
my_zlog_debug("Response Length: %zu bytes", s_http_response_len);
if (s_http_response_len == 0) {
my_zlog_error("Empty response from server");
free(uid);
free(payload);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
// 解析 JSON 响应
cJSON *response_json = cJSON_Parse(s_http_response);
if (!response_json) {
my_zlog_error("Parse response JSON failed: %s", cJSON_GetErrorPtr());
free(uid);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
// // 打印完整响应(便于调试,可根据需要注释)
// char *response_str = cJSON_Print(response_json);
// printf("Full Response JSON (abbreviated):\n%s\n", response_str);
// free(response_str);
// 提取响应状态(火山引擎该接口成功码:3000 + message=Success)
cJSON *code = cJSON_GetObjectItem(response_json, "code");
cJSON *message = cJSON_GetObjectItem(response_json, "message");
if (!code || code->valueint != 3000 || !message || strcmp(message->valuestring, "Success") != 0) {
my_zlog_error("Request failed! Code: %d, Message: %s",
code ? code->valueint : -1,
message ? message->valuestring : "No message");
cJSON_Delete(response_json);
free(uid);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
my_zlog_debug("Request succeeded! Code: %d, Message: %s", code->valueint, message->valuestring);
// 关键:直接提取 data 字段的 Base64 音频字符串(接口实际返回格式)
cJSON *audio_base64 = cJSON_GetObjectItem(response_json, "data");
if (!audio_base64 || !audio_base64->valuestring || strlen(audio_base64->valuestring) == 0) {
my_zlog_error("Response missing audio data (data field is empty)");
cJSON_Delete(response_json);
free(uid);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
my_zlog_debug("Audio Base64 Length: %zu bytes", strlen(audio_base64->valuestring));
// 7. Base64 解码音频数据
size_t audio_len = 0;
unsigned char *audio_data = base64_decode(audio_base64->valuestring, &audio_len);
if (!audio_data || audio_len == 0) {
my_zlog_error("Base64 decode audio failed");
cJSON_Delete(response_json);
free(uid);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
my_zlog_debug("Decoded Audio Length: %zu bytes", audio_len);
if(save_audio_file(audio_data, audio_len, TTS_ADUINO_PTAH) != 0) {//保存文件
my_zlog_error("save_audio_file fail");
}
// 9. 清理资源(避免内存泄漏)
cJSON_Delete(response_json);
free(uid);
free(audio_data);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
// 初始化响应缓冲
s_http_response = NULL;
s_http_response_len = 0;
return 0;
}
/*
*功能: 设置播放标识
*/
void set_audio_tts_index(bool index){
s_audio_tts_index=index;
}
/*
*功能: 发送mqtt
*/
int mqtt_audio_tts_send(int status){
// 创建根对象
cJSON* root = cJSON_CreateObject();
if (root == NULL) {
return 1;
}
// 创建body对象
cJSON* body = cJSON_CreateObject();
if (body == NULL) {
cJSON_Delete(root);
return 1;
}
cJSON *head = cJSON_CreateObject();
if (head == NULL) {
cJSON_Delete(root);
return 1;
}
// 将body添加到root
cJSON_AddItemToObject(root, "body", body);
// 添加type和status字段到body
cJSON_AddStringToObject(body, "type", "tts_audio");
cJSON_AddNumberToObject(body, "status", status);
cJSON_AddNumberToObject(head, "message_type",3020);
cJSON_AddItemToObject(root, "body", body);
cJSON_AddItemToObject(root, "head",head);
// 生成JSON字符串
char* json_str = cJSON_PrintUnformatted(root);
my_zlog_debug("%s",json_str);
mqtt_publish_to_all(mqtt_topic_pure_number(), json_str, 0);
free(json_str);
cJSON_Delete(root);
return 0;
}
int mqtt_audio_tts(cJSON *body){
// 解析type字段
cJSON* type = cJSON_GetObjectItem(body, "type");
if(type == NULL || strcmp(type->valuestring, "tts_audio") != 0){
my_zlog_error("type is tts_audio error");
return 1;
}
// 解析date字段
cJSON* date = cJSON_GetObjectItem(body, "date");
if(date!=NULL){
get_audio_text_tts(date->valuestring,0);
my_zlog_debug("text:%s",date->valuestring);
}else {
return 1;
}
// 解析status字段
cJSON* status = cJSON_GetObjectItem(body, "status");
if(mqtt_audio_tts_send(status->valueint)==0){
set_audio_tts_index(status->valueint);
}else{
set_audio_tts_index(0);
my_zlog_error("statis error");
return 1;
}
cJSON* volume = cJSON_GetObjectItem(body, "volume");
s_volume = volume->valuedouble;
return 0;
}
/*
*功能,tts播放
*/
void video_tts_play() {
if(s_audio_tts_index == 1) {
if(audio_tts(get_audio_text_tts(NULL, 1))!=0){
my_zlog_error("audio_tts fail");
s_audio_tts_index = 0;
return;
}
pid_t pid = fork();
if (pid == 0) {
// 子进程:创建新的会话组,脱离父进程组
setsid(); // 关键!创建新的会话组
char command[256];
snprintf(command, sizeof(command),
"ffmpeg -i " TTS_ADUINO_PTAH " -af \"volume=%.1f\" -f alsa default 2>/dev/null",
s_volume);
// 重定向标准输入输出,避免占用终端
if (freopen("/dev/null", "r", stdin) == NULL) {
my_zlog_error("重定向stdin失败: %s", strerror(errno));
// 可以选择退出或继续
}
if (freopen("/dev/null", "w", stdout) == NULL) {
my_zlog_error("重定向stdout失败: %s", strerror(errno));
}
if (freopen("/dev/null", "w", stderr) == NULL) {
my_zlog_error("重定向stderr失败: %s", strerror(errno));
}
int res = system(command);
_exit(res); // 使用_exit避免清理父进程资源
}
else if (pid > 0) {
// 父进程:非阻塞等待,避免僵尸进程
int status;
waitpid(pid, &status, WNOHANG); // 不阻塞的等待
my_zlog_debug("启动独立播放进程 PID: %d", pid);
s_audio_tts_index = 0;
}
else {
my_zlog_error("fork()失败");
s_audio_tts_index = 0;
}
}
}
// void video_tts_play(){
// if(s_audio_tts_index == 1) {
// audio_tts(get_audio_text_tts(NULL,1));
// char command[256];
// snprintf(command, sizeof(command),
// "ffmpeg -i " TTS_ADUINO_PTAH " -af \"volume=%.1f\" -f alsa default",
// s_volume);
// int res = system(command);
// if(res == 0)
// my_zlog_info("play success, volume: %.1f", s_volume);
// else
// my_zlog_error("system play fail, volume: %.1f", s_volume);
// s_audio_tts_index == 0;
// }
// }
\ No newline at end of file
zlog.conf
View file @
8359d134
...
@@ -9,4 +9,4 @@ file perms = 600
...
@@ -9,4 +9,4 @@ file perms = 600
millisecond
=
"%d(%Y-%m-%d %H:%M:%S).%ms [%V] %m%n"
millisecond
=
"%d(%Y-%m-%d %H:%M:%S).%ms [%V] %m%n"
[
rules
]
[
rules
]
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-
14
.log"
;
millisecond
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-
20
.log"
;
millisecond
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment