// =========================== // Wireless Rocket Launch System v2 // Implements: MOSFET output, interrupts, state machine, non-blocking scheduler, // ESP-NOW encryption, CRC16, sequence numbering, OTA updates, watchdog, heartbeat, // RSSI monitoring, Adafruit SSD1306 OLED UI. // =========================== // =========================== // ===== RECEIVER CODE ====== // =========================== #include #include #include #include #include #include #include // Globals volatile bool powerOnRx = false; uint16_t lastSeq = 0; unsigned long lastRecv = 0; bool isArmed = false; // OLED Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); // CRC uint16_t crc16_ccitt_rx(const uint8_t *data, size_t len) { uint16_t crc = 0xFFFF; while (len--) { crc ^= (uint16_t)*data++ << 8; for (uint8_t i = 0; i < 8; i++) { if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; else crc <<= 1; } } return crc; } // Interrupt void IRAM_ATTR handlePowerBtnRx() { powerOnRx = (digitalRead(RX_PIN_PWR_BTN) == LOW); } // Receive callback void onDataRecvRx(const uint8_t *mac, const uint8_t *incomingData, int len) { struct CommandMessage { bool arm; bool launch; uint16_t seq; uint16_t crc; } msg; memcpy(&msg, incomingData, sizeof(msg)); uint16_t calc = crc16_ccitt_rx((uint8_t*)&msg, sizeof(msg) - sizeof(msg.crc)); if (calc != msg.crc || msg.seq <= lastSeq) return; // drop bad/replay lastSeq = msg.seq; lastRecv = millis(); isArmed = msg.arm; if (isArmed && msg.launch) { // fire mosfets digitalWrite(RX_PIN_MOSFET1, HIGH); digitalWrite(RX_PIN_MOSFET2, HIGH); delay(1000); digitalWrite(RX_PIN_MOSFET1, LOW); digitalWrite(RX_PIN_MOSFET2, LOW); isArmed = false; } } void setupRx() { Serial.begin(115200); esp_task_wdt_init(5, true); esp_task_wdt_add(NULL); // Pins pinMode(RX_PIN_MOSFET1, OUTPUT); pinMode(RX_PIN_MOSFET2, OUTPUT); pinMode(RX_PIN_CONTINUITY, INPUT); pinMode(RX_PIN_PWR_BTN, INPUT_PULLUP); pinMode(RX_LED_PWR, OUTPUT); pinMode(RX_LED_CONN, OUTPUT); attachInterrupt(RX_PIN_PWR_BTN, handlePowerBtnRx, CHANGE); // OLED Wire.begin(RX_OLED_SDA, RX_OLED_SCL); if (oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { oled.clearDisplay(); oled.setTextSize(1); oled.setTextColor(SSD1306_WHITE); oled.setCursor(0,0); oled.println("Receiver v2 ready"); oled.display(); } // Wait for power if (!powerOnRx) { Serial.println("RX Power off. Halt."); while (!powerOnRx) esp_task_wdt_reset(); } digitalWrite(RX_LED_PWR, HIGH); // Wi-Fi & ESP-NOW WiFi.mode(WIFI_STA); esp_now_init(); esp_now_register_recv_cb(onDataRecvRx); esp_now_peer_info_t peer = {}; memcpy(peer.peer_addr, peerMac, 6); peer.channel = 0; peer.encrypt = true; memcpy(peer.lmk, espnow_key, 16); esp_now_add_peer(&peer); // OTA ArduinoOTA.begin(); } void loopRx() { esp_task_wdt_reset(); ArduinoOTA.handle(); // Connection LED digitalWrite(RX_LED_CONN, millis() - lastRecv < 1000); // Continuity analog int cont = analogRead(RX_PIN_CONTINUITY); if (!cont || !isArmed) { isArmed = false; } // Timeout static unsigned long armTime = 0; if (isArmed && armTime == 0) armTime = lastRecv; if (isArmed && millis() - armTime > 30000) isArmed = false; if (!isArmed) armTime = 0; // OLED status oled.clearDisplay(); oled.setCursor(0,0); oled.printf("Conn:%d Armed:%d", digitalRead(RX_LED_CONN), isArmed); oled.setCursor(0,10); oled.printf("Cont:%d", cont); oled.display(); delay(50); } void main() { setupRx(); while (true) loopRx(); }