Spaces:
Sleeping
Sleeping
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.") |