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.hreal 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).
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
- A · Função pura
- B · Sketch com Serial
- C · Firmware real
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.
O aluno escreve um sketch (setup()/loop()) que imprime via Serial. O mock manda o Serial para o stdout e um driver chama setup().
// aluno (trecho do lab de bits)
void setup() {
uint8_t reg = 0; bitSet(reg,0); bitSet(reg,7);
Serial.print("reg=0x"); Serial.println(reg, HEX);
}
void loop() {}
// driver: emula o core do Arduino
int main() { setup(); return 0; } // para o loop: for(int i=0;i<5;i++) loop();
Saída capturada: reg=0x81. Funciona para a parte de saída serial.
WiFi, digitalRead de um botão real, millis() para temporização, MQTT… não dá para executar no Jobe (não há hardware nem a arquitetura do ESP32).
- Compila? →
pio run(cross-compila para o ESP32). É o checkcompilado autograder. - Comporta-se certo? → Wokwi (simulação) ou ESP32 físico.
A receita (nível A/B)
- 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/delaysimulados e umSerialque escreve nostdout. - Combinar em um único fonte (o Jobe compila só o
sourcecode): mock + código do aluno (removendo o#include <Arduino.h>dele) + harness. - Enviar ao Jobe (
POST .../restapi/runs,language_id: "cpp",input= stdin do caso). - Avaliar: passou se
outcome == 15e ostdoutbate com o esperado. Seoutcome == 11, mostre ocmpinfo(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).
- Testar local (g++)
- Testar via Jobe
python jobe_test.py --local \
--mock arduino-mock/Arduino.h \
--student sol.cpp \
--harness harness/harness_funcao.cpp \
--tests tests.json
python jobe_test.py --jobe http://localhost:4000 \
--mock arduino-mock/Arduino.h \
--student sol.cpp \
--harness harness/harness_funcao.cpp \
--tests tests.json
# --apikey SUA-CHAVE (se o Jobe exigir)
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):
| Helper | Para 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):
#include <Arduino.h>
String montaPayload(int n, float temp) {
return String("{\"n\":") + n + ",\"temp\":" + temp + "}";
}
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:
#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);
}
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.
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.
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:
digitalReaddevolve 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
intde 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.