Программирование

MicroPython ESP8266: OSError -40 с Telegram API и urequests

Почему urequests на MicroPython ESP8266 выдает OSError -40 при запросах к Telegram API? Разбор причин (SNI, длина URL) и проверенные решения для Telegram бота.

Почему библиотека urequests в MicroPython на ESP8266 выдает ошибку OSError: -40 при GET-запросе к API Telegram, но корректно работает с API OpenWeatherMap? В чем заключается разница в обработке этих запросов и как исправить проблему с Telegram API?

Контекст и код:

python
import urequests

# Запрос к Telegram API вызывает OSError: -40
url = f"https://api.telegram.org/{token}/sendMessage?chat_id={chat_id}&text=txt1"

# Запрос к OpenWeatherMap API работает корректно
# url = f"https://api.openweathermap.org/data/2.5/weather?lat=42.98&lon=47.47&appid={token}"

response = urequests.get(url)
content = response.text
print(content)

Ошибка OSError: -40 при GET к Telegram API вызвана ограничениями SSL/ TLS-стека в MicroPython на ESP8266 (отсутствие SNI и особенности ussl/usocket) и дополнительными проблемами с очень длинным URL (токен + query). Запрос к OpenWeatherMap проходит, потому что тот endpoint не требует SNI/специфичной обработки TLS в той же конфигурации; простое и надёжное решение — переводить вызов Telegram на POST (urequests.post) или перейти на прошивку/платформу с поддержкой SNI. Ниже — разбор причин, варианты исправления и проверенные примеры кода.

Содержание

Почему urequests в MicroPython на ESP8266 выдаёт OSError: -40

urequests в MicroPython поверх простых модулей использует usocket и ussl. На ESP8266 реализация TLS/SSL урезана: часто отсутствует поддержка SNI (Server Name Indication) и некоторые опции SSL-контекста. Если сервер требует SNI, TLS‑handshake может быть разорван со стороны сервера прямо во время handshake — в MicroPython это проявляется как OSError: -40 (Connection reset by peer). Об этом подробно описано в статье с тестами и примерами для ESP8266/ESP32: https://techtutorialsx.com/2017/06/11/esp32-esp8266-micropython-http-get-requests/ и в обсуждениях багов MicroPython: https://github.com/micropython/micropython/issues/16561.

Ключевые моменты:

  • SNI — это TLS‑расширение, которое сообщает серверу имя хоста ещё на этапе ClientHello, и многие хосты (виртуальные хосты, CDN, балансировщики) используют его, чтобы вернуть корректный сертификат. Без SNI сервер может закрыть соединение.
  • OSError: -40 сигнализирует, что сервер закрыл соединение на уровне TCP/TLS во время handshake, поэтому HTTP‑ответа вы не увидите.
  • urequests/ussl на ESP8266 часто не поддерживают передачу имени хоста для SNI, в то время как другие сервисы (например, OpenWeatherMap) могут принимать соединение и без SNI.

В чём разница между Telegram API и OpenWeatherMap для MicroPython/SSL

Почему один API работает, а другой — нет?

  • Telegram API (api.telegram.org) в практических тестах требовал SNI/особой TLS‑обработки в части серверной конфигурации. Если в момент TLS‑handshake не передано имя хоста, сервер может отвергнуть соединение — результат: OSError: -40. Это замечено в практических статьях с примерами для ESP8266: https://techtutorialsx.com/2017/06/11/esp32-esp8266-micropython-http-get-requests/.
  • OpenWeatherMap в ряде случаев пропускает соединение без SNI (или его конфигурация позволяет успешно завершить handshake даже при ограниченной реализации ussl), поэтому тот же код GET на том же модуле работает.

Дополнительный фактор — длина URL: Telegram-метод sendMessage при GET формирует очень длинный URL (токен ≈45 символов + query string). На ESP8266 сочетание длинного запроса и ограничений стеков (буферов/реализаций TLS) в некоторых тестах приводило к сбросу соединения; перевод на POST убирает длинную query-строку и решает проблему с практической стороны (подробности: https://techtutorialsx.com/2017/06/18/esp32-esp8266-micropython-http-post-requests/).

Как исправить проблему с Telegram API на ESP8266 в MicroPython

Перечень вариантов по убыванию практичности:

  1. Перейти на POST (самое простое и надёжное)

  2. Обновить прошивку / использовать плату с лучшей поддержкой TLS (ESP32)

    • На ESP32 поддержка SNI/более полного SSL чаще доступна; если вы можете перейти на ESP32 или поставить сборку MicroPython с расширенным ussl, это решит проблему TLS без изменений в коде. См. заметки и примеры для ESP32: https://randomnerdtutorials.com/esp32-https-requests/.
  3. Попытаться задать SNI вручную (только для платформ/сборок, где это поддерживается)

    • В некоторых сборках есть ussl.wrap_socket(..., server_hostname=...) или SSLContext с set_server_hostname. На стандартном ESP8266 MicroPython эта опция часто недоступна. Если у вас есть такая возможность — используйте её, но будьте готовы к тому, что на большинстве ESP8266 это не сработает. Примерная идея показана в руководствах по TLS для ESP32: https://randomnerdtutorials.com/esp32-https-requests/.
  4. Использовать прокси / внешний сервер

    • Если изменить устройство нельзя, можно отправлять запросы к своему лёгкому HTTPS‑прокси (на VPS/Heroku), который уже имеет нормальный TLS‑стек, и тот проксирует запросы к api.telegram.org.
  5. Проверить URL и формат запроса

    • Убедитесь, что URL корректен: путь в Telegram должен быть вида https://api.telegram.org/bot<token>/sendMessage (обратите внимание на префикс bot). Неправильный путь даст HTTP‑ошибку, но не OSError — всё равно стоит проверить ещё на этапе отладки.
  6. Системные рекомендации

    • Всегда закрывайте ответы: resp.close() — это освобождает память на ограниченных контроллерах.
    • Снижайте нагрузку на heap: избегайте больших строк/несколько больших объектов одновременно.

Рабочие примеры кода: POST и TLS-обход (ESP8266 / ESP32)

  1. Рекомендуемый способ (POST) — обычно решает проблему на ESP8266:
python
import urequests

url = "https://api.telegram.org/bot{}/sendMessage".format(token)
payload = {'chat_id': chat_id, 'text': 'Hello from ESP8266'}
try:
    resp = urequests.post(url, data=payload)
    print(resp.status_code)
    print(resp.text)
    resp.close()
except OSError as e:
    print("OSError:", e)

Если хотите отправлять JSON:

python
import ujson, urequests

url = "https://api.telegram.org/bot{}/sendMessage".format(token)
data = ujson.dumps({'chat_id': chat_id, 'text': 'Hi'})
headers = {'Content-Type': 'application/json'}
resp = urequests.post(url, data=data, headers=headers)
print(resp.status_code)
print(resp.text)
resp.close()
  1. Попытка задать SNI вручную (может работать на ESP32/специфичных сборках; на большинстве ESP8266 — нет):
python
import usocket, ussl

addr = usocket.getaddrinfo("api.telegram.org", 443)[0][-1]
s = usocket.socket()
s.connect(addr)
try:
    # Многие сборки MicroPython на ESP8266 НЕ поддерживают server_hostname
    ssl_sock = ussl.wrap_socket(s, server_hostname="api.telegram.org")
    req = "GET /bot{}/sendMessage?chat_id={}&text=hi HTTP/1.1\r\nHost: api.telegram.org\r\nConnection: close\r\n\r\n".format(token, chat_id)
    ssl_sock.write(req.encode())
    data = ssl_sock.read(4096)
    print(data)
    ssl_sock.close()
except Exception as ex:
    print("SSL wrap failed:", ex)
    s.close()
  1. На ESP32 с поддержкой SSLContext (возможность варьируется в зависимости от сборки):
python
# Встроенная поддержка SSLContext в MicroPython на ESP32 может отличаться от CPython.
# Если доступно, можно создавать контекст и указывать имя сервера — это даёт SNI.
# Также urequests в некоторых реализациях принимает параметр ssl_context.

Эти варианты и ограничения обсуждаются в статьях и примерах: https://techtutorialsx.com/2017/06/18/esp32-esp8266-micropython-http-post-requests/ и https://randomnerdtutorials.com/esp32-https-requests/.

Чек‑лист отладки и быстрые проверки

  1. Убедитесь, что общий HTTPS работает:
    • Попробуйте urequests.get к OpenWeatherMap (как в вашем примере) — если работает, сеть и базовая TLS связность есть.
  2. Проверьте правильность URL Telegram:
    • Формат должен быть https://api.telegram.org/bot<token>/sendMessage.
  3. Попробуйте POST (см. пример выше). Если POST работает — проблема в сочетании GET+TLS/серверной конфигурации на стороне Telegram при вашей сборке.
  4. Обновите MicroPython до последней стабильной/nightly сборки для вашей платы — возможно улучшена поддержка ussl.
  5. Попробуйте на ESP32 (если есть) — там поддержка SNI/SSL полнофункциональнее.
  6. Если POST и обновление не помогают — используйте простой прокси или внешний сервер для отправки сообщений.
  7. Проверьте, что бот добавлен в чат/группу и что chat_id в правильном формате (для групп часто нужен ведущий -): обсуждение формата chat_id — https://community.make.com/t/telegram-bot-chatid-value-must-not-be-empty-and-400-bad-request-chat-not-found/68872.
  8. Всегда закрывайте resp.close() и минимизируйте копирование больших строк, чтобы не исчерпать RAM.

Источники

Заключение

Вкратце: OSError: -40 в MicroPython на ESP8266 при GET к Telegram вызван ограничениями TLS/SSL (отсутствие SNI и связанные с этим handshake‑сбросы) и усугубляется длинной GET‑строкой с токеном. OpenWeatherMap в вашей конфигурации не вызывает такие требования, поэтому работает. Самый простой и проверенный способ — перейти на urequests.post (отправлять chat_id и text в теле), либо обновить платформу/прошивку или использовать ESP32/сервер‑прокси для полноценной поддержки SNI.

Авторы
Проверено модерацией
Модерация