Once you've finished setting up your QT Py ESP32-S2 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.

To do this, click on the Download Project Bundle button in the window below. It will download as a zipped folder.

# SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import ssl
import board
import touchio
import pwmio
from analogio import AnalogIn
import adafruit_requests
import socketpool
import wifi
from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError
from simpleio import map_range
from adafruit_motor import servo

#  select which display is running the code
servo_one = True
#  servo_two = True

try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise
#  connect to adafruitio
aio_username = secrets["aio_username"]
aio_key = secrets["aio_key"]

print("Connecting to %s" % secrets["ssid"])
wifi.radio.connect(secrets["ssid"], secrets["password"])
print("Connected to %s!" % secrets["ssid"])

pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())
# Initialize an Adafruit IO HTTP API object
io = IO_HTTP(aio_username, aio_key, requests)

#  pylint: disable=undefined-variable
#  disabling undefined-variable for ease of comment/uncomment
#  servo_one or servo_two at top for user

#  setup for display 1
if servo_one:
    #  servo calibration values
    CALIB_MIN = 15708
    CALIB_MAX = 43968
    #  create feeds
    try:
        # get feed
        out_feed = io.get_feed("touch-1")
        in_feed = io.get_feed("touch-2")
    except AdafruitIO_RequestError:
        # if no feed exists, create one
        out_feed = io.create_new_feed("touch-1")
        in_feed = io.create_new_feed("touch-2")
#  setup for display 2
if servo_two:
    CALIB_MIN = 15668
    CALIB_MAX = 43550
    try:
        # get feed
        out_feed = io.get_feed("touch-2")
        in_feed = io.get_feed("touch-1")
    except AdafruitIO_RequestError:
        # if no feed exists, create one
        out_feed = io.create_new_feed("touch-2")
        in_feed = io.create_new_feed("touch-1")

received_data = io.receive_data(in_feed["key"])

# Pin setup
SERVO_PIN = board.A1
FEEDBACK_PIN = board.A2
touch = touchio.TouchIn(board.TX)

#  angles for servo
ANGLE_MIN = 0
ANGLE_MAX = 180

# servo setup
pwm = pwmio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50)
servo = servo.Servo(pwm)
servo.angle = None

# setup feedback
feedback = AnalogIn(FEEDBACK_PIN)

#  position finder function for servo
def get_position():
    return map_range(feedback.value, CALIB_MIN, CALIB_MAX, ANGLE_MIN, ANGLE_MAX)

#  touch debounce
touch_state = False
#  new_msg value
new_msg = None
#  last_msg value
last_msg = None
#  time.monotonic() holder for pinging IO
clock = 5

while True:
    #  check IO for new data every 5 seconds
    if (time.monotonic() - clock) > 5:
        #  get data
        received_data = io.receive_data(in_feed["key"])
        #  reset clock
        clock = time.monotonic()
    #  if touched...
    if touch.value and touch_state is False:
        touch_state = True
    #  when touch is released...
    if not touch.value and touch_state is True:
        #  get position of servo
        pos = get_position()
        #  send position to IO
        io.send_data(out_feed["key"], float(pos))
        #  delay to settle
        time.sleep(1)
        #  reset touch state
        touch_state = False
    #  if a new value is detected
    if float(received_data["value"]) != last_msg:
        #  assign value to new_msg
        new_msg = float(received_data["value"])
        #  set servo angle
        servo.angle = new_msg
        #  quick delay to settle
        time.sleep(1)
        #  release servo
        servo.angle = None
        #  log msg
        last_msg = new_msg

Upload the Code and Libraries to the QT Py ESP32-S2

After downloading the Project Bundle, plug your QT Py ESP32-S2 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the QT Py ESP32-S2's CIRCUITPY drive. 

  • lib folder
  • code.py

Your QT Py ESP32-S2 CIRCUITPY drive should look like this after copying the lib folder and the code.py file.

CIRCUITPY

secrets.py

You will need to create and add a secrets.py file to your CIRCUITPY drive. Your secrets.py file will need to include the following information:

secrets = {
    'ssid' : 'YOUR-SSID-HERE',
    'password' : 'YOUR-SSID-PASSWORD-HERE',
    'aio_username' : 'YOUR-AIO-USERNAME-HERE',
    'aio_key' : 'YOUR-AIO-KEY-HERE',
    }

Your secrets.py file will have your Adafruit IO username and key. Reference this guide page for steps on how to grab this information from your Adafruit IO account.

Analog Servo Calibration

Analog servo motors make this project possible, but they work best after they've been calibrated. This CircuitPython calibration code from the Analog Feedback Servos Learn Guide will tell you your analog servo's minimum and maximum values.

Run this code with both of your analog servos and record the results for the code.py file for this project as the CALIB_MIN and CALIB_MAX values on lines 46 and 47 for the first display and lines 59 and 60 for the second display.

#  setup for display 1
if servo_one:
    #  servo calibration values
    CALIB_MIN = 15708
    CALIB_MAX = 43968
...
#  setup for display 2
if servo_two:
    CALIB_MIN = 15668
    CALIB_MAX = 43550

How the CircuitPython Code Works

After importing the libraries, you'll leave either servo_one = True or servo_two = True uncommented, depending on which of the two displays the code is running for. 

#  select which display is running the code
servo_one = True
#  servo_two = True

Next, depending on which variable is True, the analog servo's CALIB_MIN and CALIB_MAX calibration values are set. Then, the out_feed and in_feed are setup. If the feeds do not exist in your Adafruit IO account yet, then they will be created with io.create_new_feed().

#  setup for display 1
if servo_one:
    #  servo calibration values
    CALIB_MIN = 15708
    CALIB_MAX = 43968
    #  create feeds
    try:
        # get feed
        out_feed = io.get_feed("touch-1")
        in_feed = io.get_feed("touch-2")
    except AdafruitIO_RequestError:
        # if no feed exists, create one
        out_feed = io.create_new_feed("touch-1")
        in_feed = io.create_new_feed("touch-2")
#  setup for display 2
if servo_two:
    CALIB_MIN = 15668
    CALIB_MAX = 43550
    try:
        # get feed
        out_feed = io.get_feed("touch-2")
        in_feed = io.get_feed("touch-1")
    except AdafruitIO_RequestError:
        # if no feed exists, create one
        out_feed = io.create_new_feed("touch-2")
        in_feed = io.create_new_feed("touch-1")

In the loop, Adafruit IO is polled every 5 seconds to check the current value in the in_feed. This is the feed that the opposing servo is sending data to.

#  check IO for new data every 5 seconds
    if (time.monotonic() - clock) > 5:
        #  get data
        received_data = io.receive_data(in_feed["key"])
        #  reset clock
        clock = time.monotonic()

If the incoming data has changed from the previous value, then the servo's position updates to the new angle.

#  if a new value is detected
    if float(received_data["value"]) != last_msg:
        #  assign value to new_msg
        new_msg = float(received_data["value"])
        #  set servo angle
        servo.angle = new_msg
        #  quick delay to settle
        time.sleep(1)
        #  release servo
        servo.angle = None
        #  log msg
        last_msg = new_msg

When the telegraph's touch input is released, the position of the servo is sent to the out_feed and the touch_state for debouncing is reset. By having two feeds, each telegraph build has one feed it is publishing to and one feed it is listening to.

#  if touched...
    if touch.value and touch_state is False:
        touch_state = True
    #  when touch is released...
    if not touch.value and touch_state is True:
        #  get position of servo
        pos = get_position()
        #  send position to IO
        io.send_data(out_feed["key"], float(pos))
        #  delay to settle
        time.sleep(1)
        #  reset touch state
        touch_state = False

This guide was first published on Aug 10, 2022. It was last updated on Dec 08, 2023.

This page (Code the Two Way Telegraph) was last updated on Dec 08, 2023.

Text editor powered by tinymce.