Tasmota won't connect to ESP32 WiFi server, but iPhone will connect

Background: I'm creating a LoRa transmitter and receiver to monitor the power of my washer and dryer so I can send alerts when they've finished running.

The setup (on the basement end)

  • A Heltec WiFi LoRa ESP32(V3) functions as a WiFi Access Point (AP). The distance is too far to connect to an AP in my apartment, so I am using LoRa to cover the distance.
  • A Tasmota NOUS A1T smart plug connects to the AP and sends messages via MQTT

The problem: The Tasmota plug will not connect to the ESP32 AP.

The ESP32 AP is working and I am able to connect to it with my iPhone. I confirmed the iPhone to ESP32 connection is working by creating an MQTT message on the iPhone with MQTT Analyzer app and sending. The ESP32 receives it and processes the message properly.

I also have it display the number of connections on the ESP32 (via serial and the built-in display) and it says 1 connection for the iPhone.

In the console for the Tasmota plug, it gives the following error after trying to connect:

Connect failed as AP cannot be reached

The Tasmota plug does successfully connect to my home WiFi AP (which I've set as WiFi 2 in the Tasmota config).

Troubleshooting:

  • Triple checked AP name and password. Tried different AP names and passwords, including no password.
  • Manually set the Wifi channel, max connections, disabled WPA3, and the local IP/gateway/subnet.
  • Updated the Tasmota plug to the latest firmware (Tasmota 14.4.1)

I'm not sure how to debug the WiFi side. The SoftAccessPoint documentation doesn't provide much around debugging.

Sorry, I'm a bit new to all of this so and help is very much appreciated.

Here's my sketch (I removed the passwords):

/* ---------- LORA TRANSMITTER (LAUNDRY SIDE) ---------- */

// LoRa
#include "LoRaWan_APP.h"
#include "Arduino.h"

// MQTT
#include <PicoMQTT.h>
#include <ArduinoJson.h>

// Screen
#include <Wire.h>
#include "HT_SSD1306Wire.h"

// Display: Define
SSD1306Wire factory_display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);  // addr , freq , i2c group , resolution , rst

// Display: Button pin definition
#define BUTTON_PIN 0
#define DEBOUNCE_TIME 50

// Display: Button variables
int buttonLastSteadyState = LOW;
int buttonLastFlickerableState = LOW;
int buttonCurrentState;
unsigned long buttonLastDebounceTime = 0;

// Display: State
bool displayOn = true;

// LoRa: Definitions
#define RF_FREQUENCY 915000000
#define TX_OUTPUT_POWER 5
#define LORA_BANDWIDTH 0
#define LORA_SPREADING_FACTOR 7
#define LORA_CODINGRATE 1
#define LORA_PREAMBLE_LENGTH 8
#define LORA_SYMBOL_TIMEOUT 0
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define RX_TIMEOUT_VALUE 1000
#define BUFFER_SIZE 30

// LoRa: Variables
char txpacket[BUFFER_SIZE];
char rxpacket[BUFFER_SIZE];
double txNumber;
bool lora_idle = true;
static RadioEvents_t RadioEvents;
void OnTxDone(void);
void OnTxTimeout(void);

//WiFi
const char *ssid = "LaundrySensors";
const char *password = "XXXXXX";
WiFiServer server(80);

// Define SensorData structure
struct SensorData {
  String device;
  int power;
  String time;
};

// Display functions
void VextON(void) {
  pinMode(Vext, OUTPUT);
  digitalWrite(Vext, LOW);
}

void VextOFF(void)  //Vext default OFF
{
  pinMode(Vext, OUTPUT);
  digitalWrite(Vext, HIGH);
}

void OnTxDone(void) {
  lora_idle = true;

  // Display
  if (displayOn) {
    factory_display.setTextAlignment(TEXT_ALIGN_LEFT);
    factory_display.drawString(0, 50, "LoRa sent:");
    factory_display.setTextAlignment(TEXT_ALIGN_RIGHT);
    factory_display.drawString(128, 50, "Yes");

    factory_display.display();
  }
}

void OnTxTimeout(void) {
  Radio.Sleep();
  lora_idle = true;
}

// MQTT
class MQTT : public PicoMQTT::Server {
protected:
  PicoMQTT::ConnectReturnCode auth(const char *client_id, const char *username, const char *password) override {
    // Only accept client IDs which are 3 chars or longer
    if (String(client_id).length() < 3) {  // Client_id is never NULL
      return PicoMQTT::CRC_IDENTIFIER_REJECTED;
    }

    // Only accept connections if username and password are provided
    if (!username || !password) {           // Username and password can not be NULL
      return PicoMQTT::CRC_NOT_AUTHORIZED;  // No username or password supplied
    }

    // Accept two user/password combinations
    if ((String(username) == "laundry") && (String(password) == "XXXXXX")) {
      return PicoMQTT::CRC_ACCEPTED;
    }

    // Reject all other credentials
    return PicoMQTT::CRC_BAD_USERNAME_OR_PASSWORD;
  }
} mqtt;

SensorData parseMqttMessage(const char *topic, Stream &stream, String device) {

  SensorData data;  // Create an instance of SensorData
  StaticJsonDocument<256> sensor;

  DeserializationError error = deserializeJson(sensor, stream);
  if (error) {
    Serial.print(F("deserializeJson() failed (in parseMessage func): "));
    Serial.println(error.f_str());
    return data;  // Returns an empty SensorData object
  }

  Serial.printf("Received message in topic '%s':\n", topic);

  data.device = device;
  data.power = sensor["ENERGY"]["Power"];
  data.time = sensor["Time"].as<String>();

  Serial.printf("Device: %s\n", data.device);
  Serial.printf("Power: %d\n", data.power);
  Serial.printf("Time: %s\n", data.time.c_str());

  // Display sensor data
  if (displayOn) {

    setDisplayHeader();

    factory_display.setTextAlignment(TEXT_ALIGN_LEFT);
    factory_display.drawString(0, 20, "> Packet received. Sending...");

    factory_display.setTextAlignment(TEXT_ALIGN_LEFT);
    factory_display.drawString(0, 30, "Device: " + data.device);
    factory_display.setTextAlignment(TEXT_ALIGN_RIGHT);
    factory_display.drawString(128, 30, "Power: " + String(data.power));

    factory_display.setTextAlignment(TEXT_ALIGN_LEFT);
    factory_display.drawString(0, 40, "Time:");
    factory_display.setTextAlignment(TEXT_ALIGN_RIGHT);
    factory_display.drawString(128, 40, data.time);

    factory_display.display();
  }

  return data;
}

void sendLoRaPacket(int power, String time, String device) {
  StaticJsonDocument<128> jsonPacket;
  jsonPacket["power"] = power;
  jsonPacket["time"] = time;
  jsonPacket["device"] = device;

  char txpacket[128];  // Adjust size based on the expected JSON length
  size_t len = serializeJson(jsonPacket, txpacket);

  // LoRa send (three times to increase likelyhood of reception)
  // for (int i = 0; i < 3; i++) {  // Loop 3 times

  Serial.printf("Sending packet: \"%s\", Length: %d\r\n\n", txpacket, strlen(txpacket));
  Serial.println();

  Radio.Send((uint8_t *)txpacket, strlen(txpacket));
  Radio.IrqProcess();
  delay(500);

  // }

  lora_idle = false;  // Set LoRa to idle after sending
}

void setDisplayHeader() {
  factory_display.clear();
  factory_display.display();

  factory_display.setTextAlignment(TEXT_ALIGN_CENTER);
  factory_display.drawString(64, 0, "-------------------- LORA TRANSMITTER --------------------");

  // WiFi server status
  String wifiStatus;
  if (WiFi.softAPIP()) {
    wifiStatus = "WiFi: Yes";
  } else {
    wifiStatus = "WiFi: No";
  }

  // Number of clients connected to WiFi
  size_t numClients = WiFi.softAPgetStationNum();

  // Set display
  factory_display.setTextAlignment(TEXT_ALIGN_LEFT);
  factory_display.drawString(0, 10, wifiStatus);
  factory_display.setTextAlignment(TEXT_ALIGN_RIGHT);
  factory_display.drawString(128, 10, "Clients: " + String(numClients));
}

void setDefaultDisplay() {

  setDisplayHeader();

  factory_display.setTextAlignment(TEXT_ALIGN_LEFT);
  factory_display.drawString(0, 20, ">Waiting on next packet...");
  factory_display.display();
}

void setup() {
  // Setup serialvfvc
  Serial.begin(115200);

  // LoRa: Setup
  Mcu.begin(HELTEC_BOARD, SLOW_CLK_TPYE);

  // Initialize the button pin (for display)
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  txNumber = 0;

  RadioEvents.TxDone = OnTxDone;
  RadioEvents.TxTimeout = OnTxTimeout;

  Radio.Init(&RadioEvents);
  Radio.SetChannel(RF_FREQUENCY);
  Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                    LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                    LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                    true, 0, 0, LORA_IQ_INVERSION_ON, 3000);

  // Display: Setup
  VextON();
  delay(100);
  factory_display.init();

  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);

  // WiFI
  Serial.print("Setting AP (Access Point)...");
  WiFi.softAP(ssid, password, 1, false, 4, false);

  IPAddress local_IP(192, 168, 4, 1);  // ESP32's IP
  IPAddress gateway(192, 168, 4, 1);   // Gateway
  IPAddress subnet(255, 255, 255, 0);  // Subnet mask
  WiFi.softAPConfig(local_IP, gateway, subnet);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);
  server.begin();

  // Subscribe to MQTT topics
  mqtt.subscribe("tele/laundry/washer/SENSOR", [](const char *topic, Stream &stream) {
    SensorData data = parseMqttMessage(topic, stream, "washer");  // Parse Mqtt
    delay(1000);
    sendLoRaPacket(data.power, data.time, data.device);  // Send LoRa Packet
  });

  mqtt.subscribe("tele/laundry/dryer/SENSOR", [](const char *topic, Stream &stream) {
    SensorData data = parseMqttMessage(topic, stream, "dryer");  // Parse Mqtt
    delay(1000);
    sendLoRaPacket(data.power, data.time, data.device);  // Send LoRa Packet
  });

  mqtt.begin();

  delay(250);

  // Set the display
  setDefaultDisplay();
}

void loop() {
  mqtt.loop();

  // Button pressing and debounce
  buttonCurrentState = digitalRead(BUTTON_PIN);
  if (buttonCurrentState != buttonLastFlickerableState) {
    buttonLastDebounceTime = millis();
    buttonLastFlickerableState = buttonCurrentState;
  }

  if ((millis() - buttonLastDebounceTime) > DEBOUNCE_TIME) {  // Button
    if (buttonLastSteadyState == HIGH && buttonCurrentState == LOW) {
      Serial.println("Button pressed");
      Serial.println();

      if (displayOn) {
        displayOn = false;
        factory_display.clear();
        factory_display.display();
      } else {
        displayOn = true;
        setDefaultDisplay();
      }

    } else if (buttonLastSteadyState == LOW && buttonCurrentState == HIGH) {
      Serial.println("Button released");
      Serial.println();
    }

    buttonLastSteadyState = buttonCurrentState;
  }
}

Do you have everything set to 2.4GHZ? Some things don't work at 5GHZ.

I checked the specs for the plug and ESP32 board and it looks like both only support 2.4GHz. I don't have it explicitly set in the sketch though.

Edit: I just tried a Shelly WiFi Plug and that connected to the ESP32 AP fine. Hum...

I have just recently brushed up against this Shelly, Zigbee, Tasmota tech, and I am fairly sure that the ESP32 supports Zigbee (only?)

Don't quote me, I am just new to this but I see where an esp32 can be flashed with Tasmota. Since you didn't do that, I suspect the esp32 will act as you saw. Either stay with Zigbee (I would) or flash tasmota onto the esp32 but I don't know if you can then continue to use it as an esp32 so again I would stay with zigbee. AFAIK zigbee works with Alexa as well, probably also HA.

Thanks for the replies. Unfortunately, I believe if I were to use Zigbee for the local communication, I would have to get all new hardware. I also don't think Zigbee alone would work due to the distance and several block-work walls.

Either way though, it seems to be a Tasmota issue as both my iPhone and the Shelly plug do connect to the ESP32 AP just fine. I've asked on the Tasmota forum.

No new hardware, zigbee is built into esp32. As far as range, they are all radios and building penetration is a function of frequency. Do you think there is much difference in frequencies?

Zigbee is typically on 868/915 where tasmota is wifi 2.4

Zigbee in the context of home automation is all 2.4GHz.