text: # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
  # SPDX-License-Identifier: MIT
  
  import time
  import json
  import board
  import busio
  from digitalio import DigitalInOut
  import neopixel
  from adafruit_esp32spi import adafruit_esp32spi
  from adafruit_esp32spi import adafruit_esp32spi_wifimanager
  import adafruit_esp32spi.adafruit_esp32spi_socket as socket
  import adafruit_minimqtt.adafruit_minimqtt as MQTT
  from adafruit_aws_iot import MQTT_CLIENT
  
  ### WiFi ###
  
  # Get wifi details and more from a secrets.py file
  try:
      from secrets import secrets
  except ImportError:
      print("WiFi secrets are kept in secrets.py, please add them there!")
      raise
  
  # Get device certificate
  try:
      with open("aws_cert.pem.crt", "rb") as f:
          DEVICE_CERT = f.read()
  except ImportError:
      print("Certificate (aws_cert.pem.crt) not found on CIRCUITPY filesystem.")
      raise
  
  # Get device private key
  try:
      with open("private.pem.key", "rb") as f:
          DEVICE_KEY = f.read()
  except ImportError:
      print("Certificate (private.pem.key) not found on CIRCUITPY filesystem.")
      raise
  
  # If you are using a board with pre-defined ESP32 Pins:
  esp32_cs = DigitalInOut(board.ESP_CS)
  esp32_ready = DigitalInOut(board.ESP_BUSY)
  esp32_reset = DigitalInOut(board.ESP_RESET)
  
  # If you have an externally connected ESP32:
  # esp32_cs = DigitalInOut(board.D9)
  # esp32_ready = DigitalInOut(board.D10)
  # esp32_reset = DigitalInOut(board.D5)
  
  spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
  esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
  
  # Verify nina-fw version >= 1.4.0
  assert (
      int(bytes(esp.firmware_version).decode("utf-8")[2]) >= 4
  ), "Please update nina-fw to >=1.4.0."
  
  # Use below for Most Boards
  status_light = neopixel.NeoPixel(
      board.NEOPIXEL, 1, brightness=0.2
  )  # Uncomment for Most Boards
  # Uncomment below for ItsyBitsy M4
  # status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
  # Uncomment below for an externally defined RGB LED
  # import adafruit_rgbled
  # from adafruit_esp32spi import PWMOut
  # RED_LED = PWMOut.PWMOut(esp, 26)
  # GREEN_LED = PWMOut.PWMOut(esp, 27)
  # BLUE_LED = PWMOut.PWMOut(esp, 25)
  # status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED)
  wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
  
  ### Code ###
  
  # Define callback methods which are called when events occur
  # pylint: disable=unused-argument, redefined-outer-name
  def connect(client, userdata, flags, rc):
      # This function will be called when the client is connected
      # successfully to the broker.
      print("Connected to MQTT Broker!")
      print("Flags: {0}\n RC: {1}".format(flags, rc))
  
      # Subscribe client to all shadow updates
      print("Subscribing to shadow updates...")
      aws_iot.shadow_subscribe()
  
  
  def disconnect(client, userdata, rc):
      # This method is called when the client disconnects
      # from the broker.
      print("Disconnected from MQTT Broker!")
  
  
  def subscribe(client, userdata, topic, granted_qos):
      # This method is called when the client subscribes to a new topic.
      print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos))
  
      # Update device shadow with example JSON payload
      payload = {"state": {"reported": {"moisture": 50, "temp": 30}}}
      aws_iot.shadow_update(json.dumps(payload))
  
      # We can also retrieve the shadow from AWS IoT,
      # aws_iot.shadow_get()
  
      # or delete the shadow
      # aws_iot.shadow_delete()
  
  
  def unsubscribe(client, userdata, topic, pid):
      # This method is called when the client unsubscribes from a topic.
      print("Unsubscribed from {0} with PID {1}".format(topic, pid))
  
  
  def publish(client, userdata, topic, pid):
      # This method is called when the client publishes data to a topic.
      print("Published to {0} with PID {1}".format(topic, pid))
  
  
  def message(client, topic, msg):
      # This method is called when the client receives data from a topic.
      print("Message from {}: {}".format(topic, msg))
  
  
  # Set AWS Device Certificate
  esp.set_certificate(DEVICE_CERT)
  
  # Set AWS RSA Private Key
  esp.set_private_key(DEVICE_KEY)
  
  # Connect to WiFi
  print("Connecting to WiFi...")
  wifi.connect()
  print("Connected!")
  
  # Initialize MQTT interface with the esp interface
  MQTT.set_socket(socket, esp)
  
  # Set up a new MiniMQTT Client
  client = MQTT.MQTT(broker=secrets["broker"], client_id=secrets["client_id"])
  
  # Initialize AWS IoT MQTT API Client
  aws_iot = MQTT_CLIENT(client)
  
  # Connect callback handlers to AWS IoT MQTT Client
  aws_iot.on_connect = connect
  aws_iot.on_disconnect = disconnect
  aws_iot.on_subscribe = subscribe
  aws_iot.on_unsubscribe = unsubscribe
  aws_iot.on_publish = publish
  aws_iot.on_message = message
  
  print("Attempting to connect to %s" % client.broker)
  aws_iot.connect()
  
  # Pump the message loop forever, all events
  # are handled in their callback handlers
  # while True:
  #   aws_iot.loop()
  
  # Start a blocking message loop...
  # NOTE: NO code below this loop will execute
  # NOTE: Network reconnection is handled within this loop
  while True:
      try:
          aws_iot.loop()
      except (ValueError, RuntimeError) as e:
          print("Failed to get data, retrying\n", e)
          wifi.reset()
          aws_iot.reconnect()
          continue
      time.sleep(1)
  