File size: 12,620 Bytes
5616e12
 
 
 
 
 
fda8251
5616e12
 
 
fda8251
5616e12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fda8251
5616e12
 
 
 
 
 
 
fda8251
5616e12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fda8251
5616e12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fda8251
5616e12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
import base64
import io
import os
import re
from typing import Tuple

import streamlit as st
from PIL import Image
from openai import OpenAI
from dotenv import load_dotenv

# Tentativa de importar OpenCV/Numpy (usado apenas no modo Local)
try:
    import cv2
    import numpy as np
    OPENCV_OK = True
except Exception:
    OPENCV_OK = False

# Carregar variáveis do .env
load_dotenv()

# ---------------------------
# Config & Helpers
# ---------------------------
st.set_page_config(
    page_title="Interpretação de Fluxogramas • BitDogLab",
    page_icon="🤖",
)

def get_openai_client() -> OpenAI:
    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key:
        st.error("❌ Chave da OpenAI não encontrada no .env")
        st.stop()
    return OpenAI(api_key=api_key)

def limpar_codigo(texto: str) -> str:
    """
    Remove marcadores de markdown e outros adornos, mantendo a indentação.
    """
    linhas = texto.splitlines()
    resultado = []
    dentro_do_bloco = False

    for linha in linhas:
        if linha.strip().startswith("```"):
            dentro_do_bloco = not dentro_do_bloco
            continue
        if not dentro_do_bloco:
            linha_limpa = linha.replace("**", "").replace("__", "").replace(">>>", "")
            resultado.append(linha_limpa)
    return "\n".join(resultado)

def interpretar_fluxograma(img: Image.Image, model: str = "gpt-4o", max_tokens: int = 1500, temperature: float = 0.2) -> Tuple[str, str]:
    """
    Envia a imagem do fluxograma para o modelo multimodal e retorna
    (pseudocódigo, código MicroPython).
    """
    client = get_openai_client()

    # Converter imagem para base64
    buffered = io.BytesIO()
    img.save(buffered, format="PNG")
    image_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8")

    # Montar prompt
    prompt = [
    {
        "role": "system",
        "content": """
Você é um assistente especializado que interpreta fluxogramas infantis e gera pseudocódigo seguido por código equivalente em MicroPython para a placa BitDogLab V7.
A BitDogLab (Projeto Escola 4.0/Unicamp) é baseada na Raspberry Pi Pico H/W e possui:
- LED RGB (cátodo comum): R=GPIO13, G=GPIO11, B=GPIO12
- Botões com pull-up: A=GPIO5, B=GPIO6 (pressionado = nível LOW)
- Buzzer passivo: GPIO21
- Matriz WS2812B: GPIO7
- Joystick: VRx=GPIO27, VRy=GPIO26, botão=GPIO22 (pull-up)
- Display SH1107: SDA=GPIO2, SCL=GPIO3 (I2C1 ou SoftI2C)
- Microfone analógico: GPIO28

REGRAS GERAIS
- Saída sempre em texto simples, sem blocos de markdown, sem cercas ``` e sem negrito.
- A resposta deve ter exatamente duas seções, nessa ordem:
  1) Pseudocódigo:
  2) Código MicroPython:
- O pseudocódigo deve refletir os blocos do fluxograma em linguagem simples (Início, Configurar matriz, Cor atual, Desenhar <forma>, Esperar, Limpar, Fim, etc.).
- O código deve ser completo e executável na BitDogLab, apenas com bibliotecas padrão do MicroPython (machine, time, neopixel, etc.).
- Para desenhos na matriz WS2812, use os bitmaps definidos abaixo (não invente novos formatos se já houver mapeamento).

VOCABULÁRIO DE BLOCOS (exemplos)
- Início / Fim
- Configurar matriz (largura, altura, pino, brilho)
- Cor atual (R,G,B) → define color = (R,G,B)
- Desenhar carinha feliz (smile)
- Desenhar girafa (giraffe)
- Desenhar coração (heart)
- Desenhar pacman (pacman)
- Desenhar happy (happy)
- Desenhar pato (duck)
- Limpar matriz
- Esperar (ms)

MINI-DSL INTERNA (orientação; não imprimir)
[
  {"op":"setup_matrix", "w":<int>, "h":<int>, "pin":<int>, "brightness":<0..1>},
  {"op":"set_color", "rgb":[R,G,B]},
  {"op":"draw_shape", "name":"smile|giraffe|heart|pacman|happy|duck"},
  {"op":"wait", "ms":<int>},
  {"op":"clear"}
]

REGRAS PARA A MATRIZ WS2812
- Pino padrão: GPIO7.
- Tamanho padrão: 5x5 (a menos que o fluxograma especifique 8x8).
- Mapeamento serpentina por linha: linhas pares da esquerda→direita; linhas ímpares da direita→esquerda.
- Inclua no código as funções: xy_to_i(x,y), clear(), set_pixel(x,y,color), draw_bitmap(bitmap,color).
- Controle de brilho multiplicando os canais por um fator 0..1.
- Sempre chamar np.write() após atualizar pixels.

BITMAPS 5x5 (1 = aceso, 0 = apagado)
- smile:
01110
10101
10001
10001
01110

- giraffe:
00100
01110
01010
11100
01000

- heart:
00100
01110
11111
11111
01010

- pacman:
01110
00011
00111
00011
01110

- happy:
01110
10001  
10101
10001
01110

- duck:
01111
01110
11100
01110
00100

BITMAPS 8x8 (usar se a matriz for 8x8)
- smile:
00111100
01000010
10011001
10100101
10000001
10100101
01000010
00111100

- giraffe:
00001000
00011000
00011000
00111000
01111100
00101000
00111100
00011000

- heart:
00011000
00111100
01111110
11111111
11111111
01111110
00111100
00011000

- pacman:
00000000
00111100
01111110
11110000
11100000
11110000
01111110
00111100

- happy:
00111100
01000010
10011001
10100101
10000001
10100101
01000010
00111100

- duck:
00011000
00111100
01111110
11111110
11110000
01111110
00111100
00011000

FORMATO DA RESPOSTA (OBRIGATÓRIO)
Pseudocódigo:
[descrever a sequência interpretada, linha a linha, com eventuais padrões assumidos, ex.: cores]

Código MicroPython:
[entregar programa completo e pronto para rodar; incluir setup WS2812, bitmaps, utilitários e a sequência do fluxograma]

RESTRIÇÕES
- Não usar markdown, não usar cercas de código, não usar negrito.
- Se um parâmetro faltar no fluxograma (ex.: cor), usar um padrão pedagógico seguro e sinalizar no pseudocódigo com “(padrão assumido)”.
- Evitar laços infinitos sem necessidade.
"""
    },
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "Interprete o fluxograma abaixo, gere o pseudocódigo e depois o código equivalente em MicroPython:"},
            {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}}
        ]
    }
]



    response = client.chat.completions.create(
        model=model,
        messages=prompt,
        max_tokens=max_tokens,
        temperature=temperature
    )

    content = response.choices[0].message.content.strip()

    # Regex para extrair as seções
    match = re.search(
        r"Pseudocódigo:\s*(.*?)\s*(?:Código\s+MicroPython:|\*\*Código\s+MicroPython\*\*)\s*(.*)",
        content,
        re.DOTALL
    )

    if match:
        pseudocodigo = match.group(1).strip()
        micropython = limpar_codigo(match.group(2).strip())
    else:
        pseudocodigo = "❌ Não foi possível separar as seções corretamente.\n\n" + content
        micropython = ""

    return pseudocodigo, micropython

# ---------------------------
# Captura local via OpenCV (apenas quando executando na Raspberry)
# ---------------------------
def capturar_foto_webcam(device_index: int = 2, width: int = 1280, height: int = 720) -> Image.Image:
    """
    Captura 1 frame da webcam USB (lado servidor/Raspberry) e retorna PIL.Image.
    """
    if not OPENCV_OK:
        raise RuntimeError("OpenCV não está disponível. Para modo local, instale: pip install opencv-python==4.8.1.78 numpy")

    cap = cv2.VideoCapture(device_index)
    if not cap.isOpened():
        raise RuntimeError(f"Não foi possível abrir a câmera (índice {device_index}).")

    # Tenta setar resolução
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, float(width))
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, float(height))

    # Lê alguns frames para estabilizar exposição/balanço
    for _ in range(3):
        _ = cap.read()

    ok, frame = cap.read()
    cap.release()
    if not ok or frame is None:
        raise RuntimeError("Falha ao capturar imagem da webcam.")

    # OpenCV é BGR → converte para RGB
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    img = Image.fromarray(frame_rgb)
    return img

# ---------------------------
# UI
# ---------------------------
st.title("🤖 Interpretação de Fluxogramas • BitDogLab")
st.caption("Envie um fluxograma para gerar pseudocódigo e código MicroPython ajustado à placa BitDogLab.")

# Estados
if "pseudocodigo" not in st.session_state:
    st.session_state.pseudocodigo = ""
if "micropython" not in st.session_state:
    st.session_state.micropython = ""
if "captured_image" not in st.session_state:
    st.session_state.captured_image = None

uploaded = st.file_uploader("📷 Envie uma imagem de fluxograma (PNG/JPG)", type=["png", "jpg", "jpeg", "jfif"])

col_preview, col_actions = st.columns([2, 1])

with col_actions:
    st.markdown("#### Fonte da câmera")
    modo = st.radio(
        "Escolha como capturar a imagem",
        ["Navegador (Streamlit Cloud)", "Raspberry (OpenCV)"],
        index=0,
        help="Use 'Navegador' no Streamlit Cloud (usa a câmera do dispositivo via navegador). Use 'Raspberry' apenas quando rodar localmente na RPi com webcam USB."
    )

    img_capturada = None

    if modo == "Navegador (Streamlit Cloud)":
        cam = st.camera_input("📸 Capturar foto (navegador)", help="Funciona no Streamlit Cloud", key="cam_nav")
        if cam is not None:
            try:
                img_capturada = Image.open(cam).convert("RGB")
                st.session_state.captured_image = img_capturada
                st.success("✅ Foto capturada (navegador)!")
            except Exception as e:
                st.error(f"Erro ao ler imagem do navegador: {e}")
    else:
        cam_idx = st.number_input("Índice da câmera", min_value=0, value=0, step=1, help="Geralmente 0 = /dev/video0")
        col_w, col_h = st.columns(2)
        with col_w:
            w = st.selectbox("Largura", [640, 800, 1024, 1280, 1920], index=3)
        with col_h:
            h = st.selectbox("Altura", [480, 600, 720, 1080], index=2)
        if st.button("📸 Tirar foto (webcam USB via OpenCV)", use_container_width=True):
            try:
                img_capturada = capturar_foto_webcam(int(cam_idx), int(w), int(h))
                st.session_state.captured_image = img_capturada
                st.success("✅ Foto capturada (OpenCV)!")
            except Exception as e:
                st.error(f"Erro ao capturar da webcam: {e}")

    st.divider()
    executar = st.button("🚀 Interpretar fluxograma", type="primary", use_container_width=True)

with col_preview:
    # Prioridade: upload > imagem capturada (navegador/rasp)
    if uploaded is not None:
        image = Image.open(uploaded).convert("RGB")
        st.image(image, caption="Pré-visualização (upload)", use_container_width=True)
    elif st.session_state.captured_image is not None:
        image = st.session_state.captured_image
        st.image(image, caption=f"Pré-visualização ({'navegador' if modo.startswith('Navegador') else 'webcam USB'})", use_container_width=True)
    else:
        image = None
        st.info("Carregue uma imagem, capture pelo navegador ou pela webcam USB.")

# Execução
if executar:
    if uploaded is not None:
        img_to_process = Image.open(uploaded).convert("RGB")
    else:
        img_to_process = st.session_state.captured_image

    if img_to_process is None:
        st.warning("Envie ou capture uma imagem antes de interpretar.")
        st.stop()

    with st.spinner("Chamando o modelo e gerando resultados..."):
        try:
            pseudo, micro = interpretar_fluxograma(img_to_process, model="gpt-4o", max_tokens=1500, temperature=0)
            st.session_state.pseudocodigo = pseudo
            st.session_state.micropython = micro
        except Exception as e:
            st.error(f"Erro ao interpretar o fluxograma: {e}")
            st.stop()

st.subheader("🧩 Pseudocódigo")
st.text_area("Saída (pode editar se quiser)", value=st.session_state.pseudocodigo, height=220, key="pseudo_area")

st.subheader("🐍 Código MicroPython (BitDogLab)")
st.code(st.session_state.micropython or "# O código aparecerá aqui após a interpretação.", language="python")

# Botões de download
col_d1, col_d2 = st.columns(2)
with col_d1:
    st.download_button(
        label="📄 Baixar pseudocódigo (.txt)",
        data=(st.session_state.pseudocodigo or "").encode("utf-8"),
        file_name="pseudocodigo.txt",
        mime="text/plain",
        use_container_width=True,
    )
with col_d2:
    st.download_button(
        label="💾 Baixar código MicroPython (.py)",
        data=(st.session_state.micropython or "").encode("utf-8"),
        file_name="codigo_bitdoglab.py",
        mime="text/x-python",
        use_container_width=True,
    )

st.caption("Dica: no Cloud use 'Navegador'. Localmente na RPi use 'Raspberry (OpenCV)'. GPIOs padrão para BitDogLab V6/V7, ajuste conforme seu hardware.")