Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
GCLOUD_PROJECT="your-gcloud-project-id"
GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"

# -- Wakeup Word --
# STACKCHAN_NO_USE_CLIENT_WAKEUP_WORD=1

# STACKCHAN_USE_OPEN_WAKE_WORD=1

# -- Speech Recognition --
# Google Cloud STT
STACKCHAN_USE_GOOGLE_CLOUD_STT=1
Expand Down
35 changes: 35 additions & 0 deletions firmware/include/metadata.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include <cstdint>

#include "protocols.hpp"

struct FirmwareMetadataState
{
stackchan_websocket_v1_DeviceType device_type = stackchan_websocket_v1_DeviceType_DEVICE_TYPE_UNSPECIFIED;
uint32_t display_width = 0;
uint32_t display_height = 0;
bool has_device_wake_word = false;
bool has_led = false;
stackchan_websocket_v1_ServoType servo_type = stackchan_websocket_v1_ServoType_SERVO_TYPE_UNSPECIFIED;
bool supports_audio_duplex = false;
char firmware_version[64] = "";
};

struct ServerMetadataState
{
bool available = false;
bool has_server_wake_word = false;
char server_version[64] = "";
};

extern FirmwareMetadataState g_firmware_metadata;
extern ServerMetadataState g_server_metadata;

void initializeFirmwareMetadata();
void resetServerMetadata();
bool shouldUseDeviceWakeWord();
void setFirmwareMetadataMessage(
stackchan_websocket_v1_WebSocketMessage &message,
uint32_t seq);
void applyServerMetadata(const stackchan_websocket_v1_ServerMetadata &metadata);
10 changes: 10 additions & 0 deletions firmware/lib/generated_protobuf/websocket-message.pb.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ PB_BIND(stackchan_websocket_v1_ServoCommand, stackchan_websocket_v1_ServoCommand
PB_BIND(stackchan_websocket_v1_ServoDoneEvent, stackchan_websocket_v1_ServoDoneEvent, AUTO)


PB_BIND(stackchan_websocket_v1_FirmwareMetadata, stackchan_websocket_v1_FirmwareMetadata, AUTO)


PB_BIND(stackchan_websocket_v1_ServerMetadata, stackchan_websocket_v1_ServerMetadata, AUTO)









Expand Down
98 changes: 94 additions & 4 deletions firmware/lib/generated_protobuf/websocket-message.pb.h

Large diffs are not rendered by default.

46 changes: 44 additions & 2 deletions firmware/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <limits>
#include <vector>
#include "config.h"
#include "../include/metadata.hpp"
#include "../include/protocols.hpp"
#include "../include/state_machine.hpp"
#include "../include/speaking.hpp"
Expand Down Expand Up @@ -107,6 +108,16 @@ void notifyWakeWordDetected()
}
}

void notifyFirmwareMetadata()
{
auto &message = g_tx_message;
setFirmwareMetadataMessage(message, g_uplink_seq++);
if (!sendUplinkMessage(message))
{
log_w("Failed to send FirmwareMetadata");
}
}

void notifyCurrentState(StateMachine::State state)
{
auto &message = g_tx_message;
Expand Down Expand Up @@ -242,6 +253,7 @@ void handleWsEvent(WStype_t type, uint8_t *payload, size_t length)
case WStype_DISCONNECTED:
// M5.Display.println("WS: disconnected");
log_i("WS disconnected");
resetServerMetadata();
stateMachine.setState(StateMachine::Disconnected);
break;
case WStype_CONNECTED:
Expand All @@ -252,6 +264,7 @@ void handleWsEvent(WStype_t type, uint8_t *payload, size_t length)
stateMachine.setState(StateMachine::Idle);
}
markCommunicationActive();
notifyFirmwareMetadata();
notifyCurrentState(stateMachine.getState());
break;
case WStype_TEXT:
Expand Down Expand Up @@ -322,6 +335,28 @@ void handleWsEvent(WStype_t type, uint8_t *payload, size_t length)
log_w("ServoCmd protobuf body mismatch type=%u body=%u", (unsigned)rx.message_type, (unsigned)rx.which_body);
}
break;
case stackchan_websocket_v1_MessageKind_MESSAGE_KIND_SERVER_METADATA:
if (rx.message_type == stackchan_websocket_v1_MessageType_MESSAGE_TYPE_DATA &&
rx.which_body == stackchan_websocket_v1_WebSocketMessage_server_metadata_tag)
{
applyServerMetadata(rx.body.server_metadata);
if (stateMachine.getState() == StateMachine::Idle)
{
if (shouldUseDeviceWakeWord())
{
wakeUpWord.begin();
}
else
{
wakeUpWord.end();
}
}
}
else
{
log_w("ServerMetadata protobuf body mismatch type=%u body=%u", (unsigned)rx.message_type, (unsigned)rx.which_body);
}
break;
default:
// M5.Display.printf("WS bin kind=%u len=%d\n", (unsigned)rx.kind, (int)length);
break;
Expand Down Expand Up @@ -365,6 +400,7 @@ void setup()
notifyWakeWordDetected();
});
display.init();
initializeFirmwareMetadata();

connectWiFi();

Expand All @@ -380,7 +416,10 @@ void setup()
// State entry/exit hooks
stateMachine.addStateEntryEvent(StateMachine::Idle, [](StateMachine::State, StateMachine::State) {
notifyCurrentState(StateMachine::Idle);
wakeUpWord.begin();
if (shouldUseDeviceWakeWord())
{
wakeUpWord.begin();
}
});
stateMachine.addStateExitEvent(StateMachine::Idle, [](StateMachine::State, StateMachine::State) {
wakeUpWord.end();
Expand Down Expand Up @@ -418,7 +457,10 @@ void loop()
switch (current)
{
case StateMachine::Idle:
wakeUpWord.loop();
if (shouldUseDeviceWakeWord())
{
wakeUpWord.loop();
}
break;
case StateMachine::Listening:
listening.loop();
Expand Down
110 changes: 110 additions & 0 deletions firmware/src/metadata.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#include "../include/metadata.hpp"

#include <M5Unified.h>

#include <cstdio>

#include "config.h"

FirmwareMetadataState g_firmware_metadata;
ServerMetadataState g_server_metadata;

namespace
{
stackchan_websocket_v1_DeviceType detectDeviceType()
{
#if defined(ARDUINO_ATOM_ECHOS3R)
return stackchan_websocket_v1_DeviceType_DEVICE_TYPE_M5ATOM_ECHOS3R;
#elif defined(ARDUINO_M5STACK_ATOMS3R)
return stackchan_websocket_v1_DeviceType_DEVICE_TYPE_M5ATOM_S3R;
#elif defined(ARDUINO_M5STACK_CORES3)
return stackchan_websocket_v1_DeviceType_DEVICE_TYPE_M5STACK_CORES3;
#else
return stackchan_websocket_v1_DeviceType_DEVICE_TYPE_UNSPECIFIED;
#endif
}

bool detectHasLed()
{
#if defined(ARDUINO_ATOM_ECHOS3R) || defined(ARDUINO_M5STACK_ATOMS3R)
return true;
#else
return false;
#endif
}

stackchan_websocket_v1_ServoType detectServoType()
{
#if defined(USE_SERVO_SG90)
return stackchan_websocket_v1_ServoType_SERVO_TYPE_SG90;
#elif defined(USE_SERVO_SCS0009)
return stackchan_websocket_v1_ServoType_SERVO_TYPE_SCS0009;
#else
return stackchan_websocket_v1_ServoType_SERVO_TYPE_NONE;
#endif
}
} // namespace

void initializeFirmwareMetadata()
{
g_firmware_metadata.device_type = detectDeviceType();
g_firmware_metadata.display_width = static_cast<uint32_t>(M5.Display.width());
g_firmware_metadata.display_height = static_cast<uint32_t>(M5.Display.height());
g_firmware_metadata.has_device_wake_word = true;
g_firmware_metadata.has_led = detectHasLed();
g_firmware_metadata.servo_type = detectServoType();
g_firmware_metadata.supports_audio_duplex = false;
snprintf(
g_firmware_metadata.firmware_version,
sizeof(g_firmware_metadata.firmware_version),
"%s",
STACKCHAN_FIRMWARE_VERSION);
}

void resetServerMetadata()
{
g_server_metadata = ServerMetadataState{};
}

bool shouldUseDeviceWakeWord()
{
return g_server_metadata.available && !g_server_metadata.has_server_wake_word;
}

void setFirmwareMetadataMessage(
stackchan_websocket_v1_WebSocketMessage &message,
uint32_t seq)
{
message = stackchan_websocket_v1_WebSocketMessage_init_zero;
message.kind = stackchan_websocket_v1_MessageKind_MESSAGE_KIND_FIRMWARE_METADATA;
message.message_type = stackchan_websocket_v1_MessageType_MESSAGE_TYPE_DATA;
message.seq = seq;
message.which_body = stackchan_websocket_v1_WebSocketMessage_firmware_metadata_tag;
message.body.firmware_metadata.device_type = g_firmware_metadata.device_type;
message.body.firmware_metadata.display_width = g_firmware_metadata.display_width;
message.body.firmware_metadata.display_height = g_firmware_metadata.display_height;
message.body.firmware_metadata.has_device_wake_word = g_firmware_metadata.has_device_wake_word;
message.body.firmware_metadata.has_led = g_firmware_metadata.has_led;
message.body.firmware_metadata.servo_type = g_firmware_metadata.servo_type;
message.body.firmware_metadata.supports_audio_duplex = g_firmware_metadata.supports_audio_duplex;
snprintf(
message.body.firmware_metadata.firmware_version,
sizeof(message.body.firmware_metadata.firmware_version),
"%s",
g_firmware_metadata.firmware_version);
}

void applyServerMetadata(const stackchan_websocket_v1_ServerMetadata &metadata)
{
g_server_metadata.available = true;
g_server_metadata.has_server_wake_word = metadata.has_server_wake_word;
snprintf(
g_server_metadata.server_version,
sizeof(g_server_metadata.server_version),
"%s",
metadata.server_version);
log_i(
"Server metadata wakeword=%u version=%s",
static_cast<unsigned>(g_server_metadata.has_server_wake_word),
g_server_metadata.server_version);
}
1 change: 1 addition & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ monitor_filters = esp32_exception_decoder, time
upload_speed = 1500000

build_flags =
-DSTACKCHAN_FIRMWARE_VERSION=\"0.1.0\"
lib_deps =
adafruit/Adafruit NeoPixel@^1.15.2
Links2004/WebSockets@^2.7.2
Expand Down
2 changes: 2 additions & 0 deletions protobuf/websocket-message.options
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
stackchan.websocket.v1.AudioChunk.pcm_bytes max_size:4096
stackchan.websocket.v1.ServoCommandSequence.commands max_count:255
stackchan.websocket.v1.FirmwareMetadata.firmware_version max_length:63
stackchan.websocket.v1.ServerMetadata.server_version max_length:63
34 changes: 34 additions & 0 deletions protobuf/websocket-message.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ message WebSocketMessage {
SpeakDoneEvent speak_done_evt = 33;
ServoCommandSequence servo_cmd = 34;
ServoDoneEvent servo_done_evt = 35;
FirmwareMetadata firmware_metadata = 36;
ServerMetadata server_metadata = 37;
}
}

Expand All @@ -42,6 +44,8 @@ enum MessageKind {
MESSAGE_KIND_SPEAK_DONE_EVT = 6;
MESSAGE_KIND_SERVO_CMD = 7;
MESSAGE_KIND_SERVO_DONE_EVT = 8;
MESSAGE_KIND_FIRMWARE_METADATA = 9;
MESSAGE_KIND_SERVER_METADATA = 10;
}

enum MessageType {
Expand All @@ -64,6 +68,20 @@ enum ServoOperation {
SERVO_OPERATION_MOVE_Y = 2;
}

enum DeviceType {
DEVICE_TYPE_UNSPECIFIED = 0;
DEVICE_TYPE_M5STACK_CORES3 = 1;
DEVICE_TYPE_M5ATOM_S3R = 2;
DEVICE_TYPE_M5ATOM_ECHOS3R = 3;
}

enum ServoType {
SERVO_TYPE_UNSPECIFIED = 0;
SERVO_TYPE_NONE = 1;
SERVO_TYPE_SG90 = 2;
SERVO_TYPE_SCS0009 = 3;
}

message AudioPcmStart {}

message AudioPcmEnd {}
Expand Down Expand Up @@ -108,3 +126,19 @@ message ServoCommand {
message ServoDoneEvent {
bool done = 1;
}

message FirmwareMetadata {
DeviceType device_type = 1;
uint32 display_width = 2;
uint32 display_height = 3;
bool has_device_wake_word = 4;
bool has_led = 5;
ServoType servo_type = 6;
bool supports_audio_duplex = 7;
string firmware_version = 8;
}

message ServerMetadata {
bool has_server_wake_word = 1;
string server_version = 2;
}
3 changes: 3 additions & 0 deletions stackchan_server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__version__ = "0.1.0"

__all__ = ["__version__"]
Loading
Loading