diff --git a/web/src/App.svelte b/web/src/App.svelte index 47681de..3778fe9 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -21,6 +21,7 @@ import Guia from './telas/Guia.svelte'; import Criar from './telas/Criar.svelte'; import DiaADia from './telas/DiaADia.svelte'; + import Ligacoes from './telas/Ligacoes.svelte'; import Ajuda from './lib/componentes/Ajuda.svelte'; import { TOPICO_DA_TELA } from './lib/guia'; @@ -35,6 +36,7 @@ guia: Guia, criar: Criar, 'dia-a-dia': DiaADia, + ligacoes: Ligacoes, }; const ICONES: Record> = { 'ao-vivo': Video, diff --git a/web/src/lib/guia.ts b/web/src/lib/guia.ts index a718214..62b6dd4 100644 --- a/web/src/lib/guia.ts +++ b/web/src/lib/guia.ts @@ -74,6 +74,20 @@ export const TOPICOS: Record = { ], dica: 'Para ensinar rápido: quando ele fizer algo que você gostou, toque na tela do celular (vale como carinho) ou fale com ele logo em seguida.', }, + ligacoes: { + titulo: 'Ligações da placa', + emoji: '🔌', + onde: 'Ajustes › Ligações da placa (também em Ao vivo › Acontecimentos)', + resumo: + 'Mapa do conector de 26 pinos do Orange Pi Zero 3 com o que está ligado em cada pino: o sensor de presença (PIR), a placa dos servos (PCA9685) e os pinos reservados para a tela SPI. O pino do PIR vem da própria placa, então o desenho acompanha a configuração.', + itens: [ + { emoji: '🚶', nome: 'PIR', texto: 'VCC no 5 V (pino 2 ou 4), OUT no pino 11 (PC6) e GND num terra (pino 9, por exemplo). Nunca ligue o VCC no pino 11: os GPIOs são de 3,3 V. A ordem dos terminais muda entre módulos; leia o que está escrito no sensor.' }, + { emoji: '🦾', nome: 'PCA9685', texto: 'VCC no 3,3 V (pino 1), SDA no 3, SCL no 5 e GND no 6. Os servos usam a fonte externa no borne V+, com terra comum com a placa.' }, + { emoji: '🖥️', nome: 'Tela SPI (tft35)', texto: 'O overlay tft35 reserva os pinos 18, 19, 21, 22, 23 e 24. Não ligue sensores neles.' }, + { emoji: '🟢', nome: 'Estado ao vivo', texto: 'Cada dispositivo mostra se a placa está lendo: o PIR diz se o sinal está alto agora, e a PCA9685 se responde no I²C.' }, + ], + dica: 'Sinal do PIR alto o tempo todo, por muito tempo, costuma ser fio trocado (VCC e OUT invertidos), sensor apontado para uma fonte de calor ou o ajuste de tempo do HC-SR501 no máximo.', + }, cuidados: { titulo: 'Cuidar do Carinha', emoji: '❤️', @@ -354,12 +368,12 @@ export const TOPICOS: Record = { /** Ordem do tutorial (página Guia). */ export const ORDEM_GUIA = [ 'inicio', 'vida', 'aprendizado', 'cuidados', 'humores', 'ritmo', 'sono', 'celular', 'camera', 'gestos', 'atividades', 'criar', 'ideias', 'aparencia', - 'personalidade', 'acontecimentos', 'eventos', 'presenca', 'tela', 'sensores', 'sensibilidade', + 'personalidade', 'acontecimentos', 'ligacoes', 'eventos', 'presenca', 'tela', 'sensores', 'sensibilidade', 'reacoes', 'conversa', 'movimento', 'atalhos', ]; /** Tópico de cada tela, para o ⓘ do topo. */ export const TOPICO_DA_TELA: Record = { 'ao-vivo': 'inicio', eventos: 'eventos', conversa: 'conversa', reacoes: 'reacoes', - movimento: 'movimento', ajustes: 'tela', rosto: 'aparencia', guia: 'inicio', criar: 'criar', 'dia-a-dia': 'aprendizado', + movimento: 'movimento', ajustes: 'tela', rosto: 'aparencia', guia: 'inicio', criar: 'criar', 'dia-a-dia': 'aprendizado', ligacoes: 'ligacoes', }; diff --git a/web/src/lib/ligacoes.ts b/web/src/lib/ligacoes.ts new file mode 100644 index 0000000..5746503 --- /dev/null +++ b/web/src/lib/ligacoes.ts @@ -0,0 +1,133 @@ +/** + * Ligações da placa: o conector de 26 pinos do Orange Pi Zero 3 e o que está ligado em cada pino. + * O pino do PIR vem ao vivo da placa (CARINHA_PIR, padrão gpiochip1:70 = PC6 = pino 11). + */ + +export type TipoPino = '5v' | '3v3' | 'gnd' | 'gpio' | 'i2c' | 'spi' | 'uart'; + +export interface Pino { + num: number; + nome: string; + tipo: TipoPino; + /** Linha no gpiochip1 (libgpiod), quando é um GPIO. */ + linha?: number; + funcao?: string; +} + +// Conector de 26 pinos (2 fileiras): ímpares de um lado, pares do outro. +export const PINOS: Pino[] = [ + { num: 1, nome: '3,3 V', tipo: '3v3' }, + { num: 2, nome: '5 V', tipo: '5v' }, + { num: 3, nome: 'PH5', tipo: 'i2c', linha: 229, funcao: 'I²C3 SDA' }, + { num: 4, nome: '5 V', tipo: '5v' }, + { num: 5, nome: 'PH4', tipo: 'i2c', linha: 228, funcao: 'I²C3 SCL' }, + { num: 6, nome: 'GND', tipo: 'gnd' }, + { num: 7, nome: 'PC9', tipo: 'gpio', linha: 73 }, + { num: 8, nome: 'PH2', tipo: 'uart', linha: 226, funcao: 'UART5 TX' }, + { num: 9, nome: 'GND', tipo: 'gnd' }, + { num: 10, nome: 'PH3', tipo: 'uart', linha: 227, funcao: 'UART5 RX' }, + { num: 11, nome: 'PC6', tipo: 'gpio', linha: 70 }, + { num: 12, nome: 'PC11', tipo: 'gpio', linha: 75 }, + { num: 13, nome: 'PC5', tipo: 'gpio', linha: 69 }, + { num: 14, nome: 'GND', tipo: 'gnd' }, + { num: 15, nome: 'PC8', tipo: 'gpio', linha: 72 }, + { num: 16, nome: 'PC15', tipo: 'gpio', linha: 79 }, + { num: 17, nome: '3,3 V', tipo: '3v3' }, + { num: 18, nome: 'PC14', tipo: 'gpio', linha: 78 }, + { num: 19, nome: 'PH7', tipo: 'spi', linha: 231, funcao: 'SPI1 MOSI' }, + { num: 20, nome: 'GND', tipo: 'gnd' }, + { num: 21, nome: 'PH8', tipo: 'spi', linha: 232, funcao: 'SPI1 MISO' }, + { num: 22, nome: 'PC7', tipo: 'gpio', linha: 71 }, + { num: 23, nome: 'PH6', tipo: 'spi', linha: 230, funcao: 'SPI1 CLK' }, + { num: 24, nome: 'PH9', tipo: 'spi', linha: 233, funcao: 'SPI1 CS0' }, + { num: 25, nome: 'GND', tipo: 'gnd' }, + { num: 26, nome: 'PC10', tipo: 'gpio', linha: 74 }, +]; + +export const NOME_TIPO: Record = { + '5v': '5 V', '3v3': '3,3 V', gnd: 'Terra (GND)', gpio: 'GPIO (3,3 V)', i2c: 'I²C', spi: 'SPI', uart: 'Serial', +}; + +export interface Fio { + pino: number; + /** Nome do terminal no dispositivo (VCC, OUT, GND, SDA...). */ + terminal: string; + cor: string; +} + +export interface Dispositivo { + id: string; + nome: string; + emoji: string; + cor: string; + descricao: string; + fios: Fio[]; + avisos: string[]; + /** 'em uso' = o servidor usa; 'reservado' = a placa reserva os pinos (overlay), mas o Carinha não usa. */ + situacao: 'em uso' | 'reservado'; +} + +export function pinoDaLinha(linha: number) { + return PINOS.find((p) => p.linha === linha)?.num ?? null; +} + +/** Dispositivos registrados. O pino do PIR é o que a placa informa (CARINHA_PIR). */ +export function dispositivos(pinoPir: number | null): Dispositivo[] { + const saida = pinoPir ?? 11; + return [ + { + id: 'pir', + nome: 'Sensor de presença (PIR)', + emoji: '🚶', + cor: '#34c759', + situacao: 'em uso', + descricao: 'Módulo PIR (tipo HC-SR501 ou AM312). Avisa quando alguém se mexe na frente: acorda o rosto e liga a câmera no modo presença.', + fios: [ + { pino: 2, terminal: 'VCC', cor: '#ff3b30' }, + { pino: saida, terminal: 'OUT', cor: '#ffcc00' }, + { pino: 9, terminal: 'GND', cor: '#8e8e93' }, + ], + avisos: [ + 'A ordem dos 3 terminais muda de um módulo para outro: leia o que está escrito na plaquinha do sensor (no HC-SR501, tire a cúpula branca para ver VCC, OUT e GND).', + `Nunca ligue o VCC (5 V) no pino ${saida}: os GPIOs da placa são de 3,3 V e queimam com 5 V.`, + 'A saída do HC-SR501 e do AM312 é de 3,3 V, segura para o GPIO. Se o seu sensor soltar 5 V na saída, use um divisor de tensão.', + 'O VCC pode ir em qualquer 5 V (pino 2 ou 4) e o GND em qualquer terra (6, 9, 14, 20 ou 25).', + ], + }, + { + id: 'pca9685', + nome: 'Placa dos servos (PCA9685)', + emoji: '🦾', + cor: '#0a84ff', + situacao: 'em uso', + descricao: 'Controla os servos do braço por I²C (barramento /dev/i2c-2, endereço 0x40).', + fios: [ + { pino: 1, terminal: 'VCC', cor: '#ff9500' }, + { pino: 3, terminal: 'SDA', cor: '#0a84ff' }, + { pino: 5, terminal: 'SCL', cor: '#5ac8fa' }, + { pino: 6, terminal: 'GND', cor: '#8e8e93' }, + ], + avisos: [ + 'O VCC da PCA9685 vai no 3,3 V (pino 1): é só a lógica.', + 'Os servos NÃO são alimentados pela placa: o borne V+ da PCA9685 recebe a fonte externa (6 V), com o GND da fonte ligado ao GND da placa (terra comum).', + ], + }, + { + id: 'tela', + nome: 'Tela SPI 3,5" (overlay tft35)', + emoji: '🖥️', + cor: '#af52de', + situacao: 'reservado', + descricao: 'O overlay tft35 está ativo no boot e reserva estes pinos para uma tela SPI. O Carinha não usa essa tela: não ligue outros sensores neles.', + fios: [ + { pino: 18, terminal: 'DC', cor: '#af52de' }, + { pino: 19, terminal: 'MOSI', cor: '#af52de' }, + { pino: 21, terminal: 'MISO', cor: '#af52de' }, + { pino: 22, terminal: 'RESET', cor: '#af52de' }, + { pino: 23, terminal: 'CLK', cor: '#af52de' }, + { pino: 24, terminal: 'CS', cor: '#af52de' }, + ], + avisos: [], + }, + ]; +} diff --git a/web/src/lib/paleta.svelte.ts b/web/src/lib/paleta.svelte.ts index 4dea4f5..dc1b531 100644 --- a/web/src/lib/paleta.svelte.ts +++ b/web/src/lib/paleta.svelte.ts @@ -37,6 +37,7 @@ export function itensDaPaleta(): ItemPaleta[] { registrarNaPaleta(() => [ ...ROTAS.map((r) => ({ id: 'ir-' + r.id, grupo: 'Ir para', titulo: r.titulo, fazer: () => nav.ir(r.id) })), { id: 'ir-rosto', grupo: 'Ir para', titulo: 'Rosto (estilo, formato e cor)', fazer: () => nav.ir('rosto') }, + { id: 'ir-ligacoes', grupo: 'Ir para', titulo: 'Ligações da placa (pinos do PIR, servos e tela)', fazer: () => nav.ir('ligacoes') }, { id: 'ir-dia-a-dia', grupo: 'Ir para', titulo: 'Dia a dia (rotina e decisões do Carinha)', fazer: () => nav.ir('dia-a-dia') }, { id: 'ir-criar', grupo: 'Ir para', titulo: 'Criar com IA (expressões, atividades, figurinhas)', fazer: () => nav.ir('criar') }, ...Object.entries(ATIVIDADES_INFO).map(([id, a]) => ({ diff --git a/web/src/lib/rotas.svelte.ts b/web/src/lib/rotas.svelte.ts index e4cd332..2c9a673 100644 --- a/web/src/lib/rotas.svelte.ts +++ b/web/src/lib/rotas.svelte.ts @@ -1,6 +1,6 @@ /** Rotas por hash (#/conversa): o servidor só precisa entregar index.html em "/". */ -export type Rota = 'ao-vivo' | 'eventos' | 'conversa' | 'reacoes' | 'movimento' | 'ajustes' | 'rosto' | 'guia' | 'criar' | 'dia-a-dia'; +export type Rota = 'ao-vivo' | 'eventos' | 'conversa' | 'reacoes' | 'movimento' | 'ajustes' | 'rosto' | 'guia' | 'criar' | 'dia-a-dia' | 'ligacoes'; export const ROTAS: { id: Rota; titulo: string; atalho: string }[] = [ { id: 'ao-vivo', titulo: 'Ao vivo', atalho: 'g v' }, @@ -17,6 +17,7 @@ export const SECUNDARIAS: { id: Rota; titulo: string; volta: Rota }[] = [ { id: 'guia', titulo: 'Guia', volta: 'ajustes' }, { id: 'criar', titulo: 'Criar com IA', volta: 'reacoes' }, { id: 'dia-a-dia', titulo: 'Dia a dia', volta: 'ao-vivo' }, + { id: 'ligacoes', titulo: 'Ligações da placa', volta: 'ajustes' }, ]; export const tituloDa = (id: Rota) => [...ROTAS, ...SECUNDARIAS].find((r) => r.id === id)?.titulo ?? ''; diff --git a/web/src/telas/Ajustes.svelte b/web/src/telas/Ajustes.svelte index e360829..1e88d14 100644 --- a/web/src/telas/Ajustes.svelte +++ b/web/src/telas/Ajustes.svelte @@ -1,5 +1,5 @@ + +
+ +
+ +
+

+ {#each Object.entries(NOME_TIPO) as [tipo, nome] (tipo)} + {nome} + {/each} +

+
+ +
+ {#each lista as d (d.id)} + {@const st = estadoDe(d)} + + +

{d.descricao}

+ + + + {#each d.fios as f (f.pino + f.terminal)} + {@const p = PINOS[f.pino - 1]} + + + + + + {/each} + +
No dispositivoPino da placaO que é
{f.terminal}{f.pino}{p.funcao ?? NOME_TIPO[p.tipo]}{p.tipo === 'gpio' ? ` (${p.nome})` : ''}
+ {#if d.avisos.length} +
    + {#each d.avisos as a (a)}
  • {a}
  • {/each} +
+ {/if} + {#if d.id === 'pir' && pir} +

Configuração da placa: CARINHA_PIR={pir.linha} → pino {pinoPir ?? '?'}

+ {/if} +
+ {/each} +
+
+ +