Pular para o conteúdo principal

Testar código Arduino no Jobe (sem Moodle)

O Jobe executa código no compilador do host (g++), não no ESP32. Então há uma fronteira clara:

  • ✅ Lógica pura (bits, operadores, estruturas de controle, tipos, funções): roda no Jobe, desde que você troque o Arduino.h real por um mock.
  • ❌ Firmware real (WiFi, GPIO físico, timing, periféricos): não roda no Jobe — isso é pio run (compila para ESP32) + Wokwi (executa).
A ideia em uma frase

Compile o código do aluno contra um Arduino.h falso que implementa as funções em C puro (as macros de bit, um Serial que escreve no stdout), rode no Jobe e compare o stdout com o esperado.


Três níveis do que se pode testar​

O aluno escreve uma função (ex.: ehPar, menorDivisor, somaDigitos). O harness chama a função e imprime o resultado.

// aluno
bool ehPar(uint8_t n) { return (n & 1) == 0; }
// harness (lê n do stdin, imprime 0/1)
int main() { int n; scanf("%d", &n); printf("%d\n", ehPar((uint8_t)n) ? 1 : 0); return 0; }

Testável e auto-corrigível por stdout — é o caso mais comum.


A receita (nível A/B)​

  1. Mock Arduino.h — um header que implementa em C puro: macros de bit (bit, bitRead, bitSet, bitClear, bitWrite, highByte, lowByte), constantes (HIGH, LOW, INPUT_PULLUP, LED_BUILTIN, HEX…), GPIO como no-op, millis/delay simulados e um Serial que escreve no stdout.
  2. Combinar em um único fonte (o Jobe compila só o sourcecode): mock + código do aluno (removendo o #include <Arduino.h> dele) + harness.
  3. Enviar ao Jobe (POST .../restapi/runs, language_id: "cpp", input = stdin do caso).
  4. Avaliar: passou se outcome == 15 e o stdout bate com o esperado. Se outcome == 11, mostre o cmpinfo (erro de compilação).

Ferramentas prontas​

Este guia acompanha uma pasta jobe-arduino/:

jobe-arduino/
├── arduino-mock/
│ ├── Arduino.h # bit macros, String, Serial->stdout, pinos, millis...
│ ├── WiFi.h # WiFiClient + objeto WiFi (status, localIP, RSSI, MAC)
│ └── PubSubClient.h # mock MQTT: registra publish/subscribe, injeta mensagens
├── harness/
│ ├── harness_funcao.cpp # nivel A: chama a funcao lendo o stdin
│ ├── driver_sketch.cpp # nivel B: chama setup() uma vez
│ ├── harness_payload.cpp # MQTT: imprime o payload montado pelo aluno
│ └── harness_callback.cpp # MQTT: injeta um comando e imprime o estado do LED
├── exemplos/ # solucoes + tests.json de exemplo (payload, callback)
├── jobe_test.py # driver: monta o fonte, roda no Jobe (ou g++), corrige
├── sol.cpp / tests.json # exemplo de bits

O jobe_test.py aceita vários --mock (combinados num fonte só) e tem o modo --local (usa g++, sem precisar de Jobe no ar).

python jobe_test.py --local \
--mock arduino-mock/Arduino.h \
--student sol.cpp \
--harness harness/harness_funcao.cpp \
--tests tests.json

Saída típica:

[1] OK
[2] OK
[3] OK

3/3 casos passaram.

Aluno com erro mostra esperado vs obtido; aluno que não compila mostra a mensagem do compilador. O código de saída é 0 só quando todos os casos passam — pronto para CI.


Mock do PubSubClient (lógica de MQTT)​

Publicar/assinar de verdade precisa de rede e broker. Para testar a lógica — o que o aluno publica e como reage a uma mensagem — o mock do PubSubClient não abre conexão: ele registra os publish/subscribe e deixa você injetar uma mensagem recebida para disparar o callback.

Helpers de teste do mock (não existem no PubSubClient real):

HelperPara quê
mock_inject(topico, payload)dispara o callback do aluno como se tivesse chegado uma mensagem
mock_lastPayload(topico)último payload publicado naquele tópico
mock_publishedTo(topico)true se algo foi publicado no tópico
mock_subscribedTo(topico)true se o aluno assinou o tópico
mock_pin(pino)estado atual de um pino (para conferir o LED do callback)

Exemplo A — o payload da telemetria​

O aluno escreve uma função que monta o JSON (lógica pura de String, nem precisa do MQTT):

payload_sol.cpp
#include <Arduino.h>
String montaPayload(int n, float temp) {
return String("{\"n\":") + n + ",\"temp\":" + temp + "}";
}
harness_payload.cpp
int main() {
int n; double temp;
if (scanf("%d %lf", &n, &temp) != 2) return 1;
Serial.println(montaPayload(n, (float)temp)); // imprime o payload
return 0;
}
python jobe_test.py --local --mock arduino-mock/Arduino.h \
--student payload_sol.cpp --harness harness_payload.cpp --tests payload_tests.json

Casos: 1 26.0 → {"n":1,"temp":26.00}; 7 30.5 → {"n":7,"temp":30.50}.

Exemplo B — o callback reage a on/off​

O aluno escreve o callback; o harness injeta um comando e imprime o estado do LED:

callback_sol.cpp
#include <WiFi.h>
#include <PubSubClient.h>
const int LED = 2;
void aoReceber(char* topico, byte* payload, unsigned int tam) {
String msg;
for (unsigned int i = 0; i < tam; i++) msg += (char)payload[i];
if (msg == "on") digitalWrite(LED, HIGH);
if (msg == "off") digitalWrite(LED, LOW);
}
harness_callback.cpp
WiFiClient _c; PubSubClient _mqtt(_c);
int main() {
char cmd[64];
if (scanf("%63s", cmd) != 1) return 1;
_mqtt.setCallback(aoReceber);
_mqtt.mock_inject("elt85b/grupo-a/comando", cmd); // simula mensagem recebida
printf("%d\n", mock_pin(2)); // estado do LED
return 0;
}
python jobe_test.py --local \
--mock arduino-mock/Arduino.h --mock arduino-mock/WiFi.h --mock arduino-mock/PubSubClient.h \
--student callback_sol.cpp --harness harness_callback.cpp --tests callback_tests.json

Casos: on → 1; off → 0.

Projete para testar

Para o Jobe conseguir avaliar, peça funções testáveis: montaPayload(n, temp) (retorna a String) e um callback aoReceber(...) separado — em vez de tudo embutido no loop(). Isso melhora o código e viabiliza a correção automática.

O que o mock cobre

publish, subscribe, connect (inclusive com Last Will), state, loop e o disparo do callback. Ele não fala com broker nem valida a rede — para o end-to-end real (WiFi + broker), use o ESP32 físico ou o Wokwi com a stack Docker.


Integração com o autograder do curso​

Isto encaixa como um novo tipo de check no grade.py: em vez de só contem/compila, um check jobe que executa o código do aluno e confere a saída.

# esboço de check "jobe" (roda no estado do commit da tarefa)
{"tipo": "jobe", "arquivo": "src/main.cpp",
"harness": "harness/harness_funcao.cpp",
"casos": [{"input": "4\n", "expected": "1\n"},
{"input": "7\n", "expected": "0\n"}],
"desc": "ehPar passa nos casos"}

O check monta o fonte (mock + arquivo do aluno + harness) e chama o Jobe; passa se todos os casos derem outcome 15 e a saída certa. Assim você avalia comportamento, não só presença de texto — sem instalar compiladores no runner de CI (o Jobe faz isso).


Limitações honestas do mock​

  • O mock não simula hardware: digitalRead devolve um valor fixo, millis() é um contador falso, delay() não espera. Bom para lógica e saída serial; inútil para timing real, interrupções, WiFi, I2C/SPI.
  • Cubra no mock apenas o que os exercícios usam. Se um lab usa uma função nova do Arduino, adicione-a ao Arduino.h.
  • Diferenças de tamanho de tipo: o host geralmente é 64 bits; se um exercício depende de int de 32 bits (como no ESP32), fixe os tipos no teste (int32_t, uint8_t) para o resultado bater.

Para tudo que precisa de hardware de verdade, o caminho continua sendo pio run + Wokwi. Veja também a API REST do Jobe.


Guia — testar código Arduino no Jobe.