@stattus4/native-engine
v0.1.1
Published
Stattus4 engine com arquitetura Clean/Hexagonal e facade única para integração com SuperApp.
Readme
Corrigindo workflow
@stattus4/native-engine
Biblioteca nativa da Stattus4 para integração com o SuperAPP via uma única fachada (Engine), com suporte a gateway (TCP/BLE), pipeline de áudio, observabilidade e integração opcional com bridges nativas (TurboModule).
Este documento é a documentação funcional da biblioteca. Ele descreve a API pública e o comportamento atual do pacote @stattus4/native-engine.
Visão geral
A native-engine centraliza:
- ciclo de vida da engine (
start,stop,destroy) - conexão de gateway (
connect,disconnect,sendGatewayCommand) - captura/reprodução de áudio e exportação de gravação
- eventos de engine/gateway/áudio/protocolo
- diagnósticos e métricas
- fallback em memória quando bridges nativas não estão disponíveis
Suporte de plataforma (estado atual)
Engine(núcleo TypeScript): multiplataforma- factories TurboModule (
createTurboNativeGatewayBridge,createTurboNativeAudioBridge): Android (retornamundefinedfora de Android) - bridges customizadas (
config.native): suportadas via interfaces públicas (NativeGatewayBridge,NativeAudioBridge)
Observação: o pacote contém código nativo iOS/Android, mas o auto-discovery de bridge TurboModule exportado hoje está habilitado apenas para Android.
Requisitos (recomendado)
- Node.js
>= 20(validado no appexample) - React Native (exemplo usa
0.83.0) - Android
minSdkVersion 24(conformeandroid/build.gradle)
Instalação no repositório
Este pacote está dentro do repositório stattus4-SuperAPP-lib em @stattus4/native-engine/.
cd @stattus4/native-engine
npm installPara rodar o app de exemplo:
npm run example:android
# ou
npm run example:iosPublicação pública no npm
O pacote está preparado para publicação pública no npm sob o escopo @stattus4.
Pré-requisitos:
- conta npm com permissão para publicar no escopo
@stattus4 - autenticação no registry
https://registry.npmjs.org/
Publicação:
cd @stattus4/native-engine
npm login
npm publishO package.json define publishConfig.access = "public" e publishConfig.registry = "https://registry.npmjs.org/", então o primeiro publish do pacote escopado já sai como público no npm.
Publicação via GitHub Actions
O repositório agora possui o workflow .github/workflows/publish-native-engine.yml, que:
- instala as dependências com
npm ci - valida com
npm run typecheckenpm run test:unit - publica
@stattus4/native-engineno npm com o secretNPM_TOKEN
Antes de usar o workflow, configure em Settings > Secrets and variables > Actions um secret NPM_TOKEN com um token do npm que tenha permissão para publicar no escopo @stattus4.
Você pode disparar esse publish de duas formas:
- manualmente em
Actions > Publish @stattus4/native-engine to npm - automaticamente ao subir uma tag no formato
native-engine-v*
Fluxo sugerido para release:
cd @stattus4/native-engine
npm version patch
git add package.json package-lock.json
git commit -m "chore: release 0.1.1"
git tag native-engine-v0.1.1
git push origin main --follow-tagsInstalação em outro projeto:
npm install @stattus4/native-engineDepois disso, basta declarar a dependência normalmente no package.json do app:
{
"dependencies": {
"@stattus4/native-engine": "0.1.0"
}
}Início rápido
import {
createEngine,
createTurboNativeAudioBridge,
createTurboNativeGatewayBridge,
type Engine,
} from '@stattus4/native-engine';
const nativeGateway = createTurboNativeGatewayBridge();
const nativeAudio = createTurboNativeAudioBridge();
const engine: Engine = createEngine({
appId: 'superapp',
gateway: {
mode: 'auto',
port: 3000,
reconnectOnFailure: true,
},
audio: {
sampleRate: 16_000,
channels: 1,
bitsPerSample: 16,
chunkSizeBytes: 320,
},
security: {
enableEncryption: false,
enableSigning: false,
},
protocol: {
daqExpectedBytes: 160_000,
streamIdleTimeoutMs: 4_000,
getPermission: () => true,
getProtocolVersion: () => 3,
getAvailableFirmware: () => false,
},
retry: {
maxAttempts: 3,
baseDelayMs: 300,
maxDelayMs: 2_000,
jitter: 0.2,
},
timeout: {
connectMs: 5_000,
commandMs: 4_000,
captureStartMs: 2_000,
},
observability: {
level: 'info',
enableMetrics: true,
enableDiagnostics: true,
},
native: {
...(nativeGateway ? { gateway: nativeGateway } : {}),
...(nativeAudio ? { audio: nativeAudio } : {}),
},
// Em Android produção, você pode habilitar modo estrito:
// capabilities: ANDROID_SUPERAPP_PRODUCTION_CAPABILITIES,
});
const unsubscribe = engine.onEvent((event) => {
if (event.type === 'engine_error') {
console.warn(event.error.code, event.error.message);
}
});
(async () => {
const started = await engine.start();
if (!started.ok) return;
const connected = await engine.connect();
if (!connected.ok) return;
const encoder = new TextEncoder();
await engine.sendGatewayCommand({
payload: encoder.encode('AT+INFO\r\n'),
});
const snapshot = engine.snapshot();
console.log(snapshot.gatewayState, snapshot.audioState);
const diagnostics = await engine.diagnostics();
if (diagnostics.ok) {
console.log(diagnostics.value);
}
await engine.disconnect();
await engine.stop();
unsubscribe();
engine.destroy();
})();Conceitos principais
1. Engine (fachada única)
A interface Engine concentra o uso público da biblioteca:
- lifecycle:
start,stop,destroy - gateway:
connect,disconnect,sendGatewayCommand - áudio:
startCapture,stopCapture,playAudioFile, exportação de gravações - observabilidade:
onEvent,snapshot,diagnostics
2. Resultado padronizado (Result<T, EngineError>)
A API assíncrona retorna Result em vez de lançar exceções na maioria das operações:
- sucesso:
{ ok: true, value } - erro:
{ ok: false, error }
Exceções ainda podem acontecer em createEngine(...) quando há configuração inválida ou exigência de capability não atendida (ex.: strictNative sem bridge nativa disponível).
3. Fallback automático
Se as bridges nativas não estiverem disponíveis e você não exigir modo estrito, a engine usa adapters em memória para gateway/áudio. Isso é útil para:
- desenvolvimento local
- testes unitários
- execução em ambientes sem TurboModule
API pública (resumo)
Criação e exports principais
createEngine(config: EngineConfig): EnginecreateTurboNativeGatewayBridge(): NativeGatewayBridge | undefinedcreateTurboNativeAudioBridge(): NativeAudioBridge | undefinedANDROID_SUPERAPP_PRODUCTION_CAPABILITIESEngineError,EngineErrorCode,engineError(...)ok(...),err(...)
Métodos da Engine
start()- Inicializa a engine e publica
engine_started.
- Inicializa a engine e publica
stop()- Para captura de áudio, desconecta gateway e publica
engine_stopped.
- Para captura de áudio, desconecta gateway e publica
connect()- Conecta o gateway configurado (
tcp/ble). Requerstart()antes.
- Conecta o gateway configurado (
disconnect()- Desconecta o gateway atual.
sendGatewayCommand({ payload })- Envia bytes para o gateway.
startCapture({ mode? })- Inicia pipeline de áudio manualmente (
playouplay_and_record).
- Inicia pipeline de áudio manualmente (
stopCapture()- Para a captura/pipeline de áudio.
playAudioFile(wavAudio)- Reproduz um arquivo WAV via bridge de áudio nativa.
saveLastRecordingWav({ sink? })- Exporta a última gravação para WAV (e opcionalmente grava em
sink.write(...)).
- Exporta a última gravação para WAV (e opcionalmente grava em
getLastRecordingPcm()- Retorna o PCM bruto da última gravação.
clearLastRecording()- Limpa buffers de gravação exportável.
snapshot()- Snapshot síncrono do estado de execução/gateway/áudio.
diagnostics()- Diagnóstico completo (capacidades, métricas, protocolo, gateway, áudio).
onEvent(listener)- Assina eventos da engine.
destroy()- Libera listeners/recursos internos. Chame no teardown da tela/processo.
Configuração (EngineConfig)
Campos obrigatórios
interface EngineConfig {
appId: string;
gateway: GatewayConfig;
audio: AudioConfig;
security: SecurityConfig;
retry: RetryPolicy;
timeout: TimeoutPolicy;
observability: ObservabilityConfig;
productScope?: EngineProductScopeConfig;
protocol?: ProtocolConfig;
capabilities?: EngineCapabilitiesConfig;
native?: EngineNativeBridges;
}Observação: createEngine() aplica defaults internos para productScope, protocol, retry e timeout, mas os tipos públicos exigem retry e timeout explicitamente.
gateway (GatewayConfig)
{
mode: 'tcp' | 'ble' | 'auto';
host?: string;
port?: number;
deviceName?: string;
scanTimeoutMs?: number;
reconnectDelayMs?: number;
updateRateMs?: number;
reconnectOnFailure: boolean;
initialState?: GatewayState;
}Regras de resolução quando mode: 'auto':
- se
hostouportestiver definido: usatcp - senão, se
deviceNameestiver definido: tentablequando houver bridge nativa de gateway; caso contrário, cai paratcp - caso contrário: usa
tcp
audio (AudioConfig)
{
sampleRate: number;
channels: number;
bitsPerSample: number;
chunkSizeBytes: number;
}Configura o pipeline de áudio (captura/reprodução) e também influencia defaults de gravação/DAQ.
security (SecurityConfig)
{
enableEncryption: boolean;
enableSigning: boolean;
}Esses flags fazem parte do contrato público da engine e da configuração de produto. No estado atual da API pública, eles são principalmente declarativos (a fachada Engine não expõe operações de criptografia/assinatura diretamente).
protocol (ProtocolConfig)
A engine processa comandos/eventos de protocolo sobre payloads de gateway (especialmente no fluxo TCP/AT). Campos relevantes:
daqExpectedBytestrackExpectedBytesstreamIdleTimeoutMsrequestsEnabled/getPermissionprotocolVersion/getProtocolVersionfirmwareAvailable/getAvailableFirmware
Comandos AT reconhecidos no runtime atual (via parser interno):
AT+STARTAT+DAQAT+DAQPOSAT+STREAMAT+INFOAT+UPDATEAT+DEVICEAT+ALARMAT+HBEATAT+UTRACK
observability (ObservabilityConfig)
{
level: 'debug' | 'info' | 'warn' | 'error' | 'silent';
enableMetrics: boolean;
enableDiagnostics: boolean;
}enableMetrics: habilita coleta de métricas e eventosmetric_observedenableDiagnostics: habilitaengine.diagnostics()
productScope (EngineProductScopeConfig)
Metadata de produto exposta em diagnostics():
fota:'deferred' | 'partial' | 'ready'gps:'deferred' | 'partial' | 'ready'security:'deferred' | 'partial' | 'ready'
Default:
{
fota: 'deferred',
gps: 'deferred',
security: 'deferred',
}capabilities (EngineCapabilitiesConfig)
Controla fallback/uso de bridges nativas:
forceFallbackallowNativeGatewayallowNativeAudioallowSecureStorerequireNativeGatewayrequireNativeAudiostrictNative(atalho para exigir gateway + áudio nativos)
Preset exportado para Android produção:
ANDROID_SUPERAPP_PRODUCTION_CAPABILITIES
// => { forceFallback: false, allowNativeGateway: true, allowNativeAudio: true, strictNative: true }Bridges nativas (config.native)
Você pode injetar bridges customizadas para adaptar a engine ao host/plataforma:
native.gateway?: NativeGatewayBridgenative.audio?: NativeAudioBridge
Interface NativeGatewayBridge (resumo)
Métodos principais:
configure?(config)connect()disconnect()send(payload)onData(listener)onDisconnect(listener)
Métodos opcionais para richer TCP lifecycle:
closeClient?()onClientConnected?(listener)onClientDisconnected?(listener)
Interface NativeAudioBridge (resumo)
startCapture(config)stopCapture()playFrame?(frame, config)playWav?(payload)getPlaybackHeadPosition?()
Factories TurboModule (Android)
createTurboNativeGatewayBridge()createTurboNativeAudioBridge()
As factories tentam localizar o TurboModule NativeEngine e retornam undefined quando não disponível (ou fora de Android).
Gateway: modos tcp e ble
TCP
- A engine atua com um servidor TCP (via bridge nativa) e recebe dados do cliente (sensor/dispositivo).
- Eventos de cliente (
gateway_client_connected,gateway_client_disconnected) e estado de servidor (gateway_transport_state_changed) são emitidos quando suportados pela bridge. - Erro de bind de porta (
EADDRINUSE) é normalizado paraEngineErrorCode.GATEWAY_PORT_IN_USE.
BLE
- O modo BLE usa
gateway.deviceNamee os parâmetros de scan/reconnect/update. - O comportamento exato depende da
NativeGatewayBridgefornecida. - Na bridge TurboModule Android atual, comandos enviados pela engine são mapeados para solicitações de bateria/GPS no runtime BLE.
Áudio e gravação
Captura manual
await engine.startCapture({ mode: 'play_and_record' });
await engine.stopCapture();Modos:
playplay_and_record(default da captura)
Pipeline automático por protocolo
A engine também controla o áudio automaticamente em ciclos de protocolo:
- estado de protocolo
daq-> inicia pipeline emplay_and_record - estado de protocolo
stream-> inicia pipeline emplay - estado
idle/error-> desliga pipeline
Exportação da última gravação
saveLastRecordingWav()retorna WAV (Uint8Array)getLastRecordingPcm()retorna PCM bruto (Uint8Array)clearLastRecording()limpa buffers exportáveis
Se não houver gravação disponível, a engine retorna AUDIO_RECORDING_UNAVAILABLE.
Você também pode persistir a exportação via sink:
await engine.saveLastRecordingWav({
sink: {
write(payload) {
// salvar em arquivo / upload / persistência
},
},
});Eventos (engine.onEvent(...))
A engine publica eventos tipados (EngineEvent). Principais grupos:
Lifecycle da engine
engine_startedengine_stoppedengine_error
Gateway
gateway_state_changedgateway_transport_state_changedgateway_client_connectedgateway_client_disconnectedgateway_data_receivedgateway_data_sent
Áudio
audio_state_changedaudio_frame_captured
Observabilidade
metric_observed
Protocolo
fluidcore_protocol_event
Observação: o nome do evento/tipo público (fluidcore_protocol_event, FluidCoreProtocolEvent) é mantido por compatibilidade da API atual. Ele representa os eventos de protocolo processados pela engine.
Campos úteis em fluidcore_protocol_event:
messagestatecommand?payload?device?clientId?sessionId?progress?
Snapshot e diagnósticos
snapshot()
Leitura síncrona e leve do estado atual:
runninggatewayStateaudioStateconnectedAtgateway(client count, active client, timestamps de atividade, estado do servidor)
diagnostics()
Retorna um snapshot estendido (EngineDiagnostics) com:
capabilities(fallback, nativeGateway, nativeAudio, secureStore)productScopegateway(bytes, reconnectAttempts, lastError, cliente ativo)protocol(estado, último comando, último payload de device, metadados do device)audio(frames, dropped, bytes)metrics
Se observability.enableDiagnostics === false, retorna erro com EngineErrorCode.CAPABILITY_UNAVAILABLE.
Códigos de erro públicos (EngineErrorCode)
Lista atual:
INVALID_ARGUMENTNOT_READYALREADY_RUNNINGTIMEOUTPERMISSION_DENIEDCAPABILITY_UNAVAILABLEGATEWAY_PORT_IN_USEGATEWAY_CONNECT_FAILEDGATEWAY_DISCONNECTEDGATEWAY_SEND_FAILEDAUDIO_START_FAILEDAUDIO_STOP_FAILEDAUDIO_PLAYBACK_FAILEDAUDIO_RECORDING_UNAVAILABLEAUDIO_EXPORT_FAILEDSTORAGE_FAILEDSECURITY_FAILEDINTERNAL_ERROR
Boas práticas de uso
- Chame
start()antes deconnect(). - Sempre faça teardown com
disconnect()/stop()edestroy(). - Use
strictNativeem produção Android quando a bridge nativa for obrigatória. - Em desenvolvimento/testes, permita fallback para acelerar validação.
- Assine
engine_errorviaonEventpara telemetria e troubleshooting.
Troubleshooting
connect() falha com GATEWAY_PORT_IN_USE
A porta TCP já está em uso. Troque gateway.port ou encerre o processo que está ocupando a porta.
createEngine(...) lança erro de capability nativa
Exemplo comum:
Native gateway bridge is required but unavailable
Isso ocorre quando strictNative / requireNativeGateway / requireNativeAudio está ativo e a bridge correspondente não foi encontrada/injetada.
connect() retorna NOT_READY
engine.start() não foi chamado antes de engine.connect().
diagnostics() indisponível
Verifique observability.enableDiagnostics. Quando false, a engine retorna CAPABILITY_UNAVAILABLE.
Desenvolvimento da biblioteca
Dentro de @stattus4/native-engine/:
npm install
npm run typecheck
npm run lint
npm run test:unitTestes Android (dependem de ambiente Android configurado):
npm run test:android:native
npm run test:android:e2eExemplo de integração
O app de exemplo em example/ demonstra:
- criação da engine
- uso das factories TurboModule
- fluxo de ações (
start,connect, envio de comando, captura, diagnósticos) - renderização de
snapshot()e stream de eventos
Arquivo principal: example/src/App.tsx.
Licença
MIT. Veja LICENSE.
