Black Lives Matter - Action and Equality. ... Adafruit is open and shipping.
0

PyPortal Touch inaccurate
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

PyPortal Touch inaccurate

by cr2n on Thu Mar 19, 2020 6:56 pm

I am looking for suggestions to help me isolate my issue.

I am having trouble with the accuracy of touch screen inputs using both the original and my edited version of the PyPortal Alarm clock example by Dave Astels. The original software worked but I could, for example, click outside the button areas of the main screen and the device would navigate to the Mugsy page. Also, changing the alarm time with swipe was not always accurate and would sometimes navigate back to main screen.

But since it worked generally, I proceeded with my edits.

The symptom I have with my software is that when I touch the screen along the bottom of the device, the device initially records the correct touch point but then "ends" on an incorrect point resulting in my software using the incorrect value.

Troubleshooting so far:
    checking the display cable was seated correctly
    I removed the plastic case
    I then installed the PyPortal NeoPixel Color Picker and this works perfectly.
I am using the adafruit-circuitpython-bundle-5.x-mpy-20200313 library, however, I first installed adafruit-circuitpython-bundle-5.x-mpy-20200311 when I received the PyPortal.

I setup the touch area buttons like this in the Time_State class:
Code: Select all | TOGGLE FULL SIZE
# each button has it's edges as well as the state to transition to when touched
        self.buttons = [dict(left=7, top=160, right=77, bottom=230, next_state='home'),
                        dict(left=87, top=160, right=154, bottom=230, next_state='alarm'),
                        dict(left=166, top=160, right=236, bottom=230, next_state='settings'),
                        dict(left=242, top=160, right=312, bottom=230, next_state='info')]

If I place a print(t) statement in the Time_State.touch class function below, the console writes the correct touch location until the last line, then Touch records the incorrect value which is used by the software and so the wrong background appears. This happens almost consistently but randomly the correct value will be the last value and then it behaves correctly. I just haven't figured out yet why.

Code: Select all | TOGGLE FULL SIZE
    def touch(self, t, touched):
        if t:
           # capture input
            print(t)   
        if t and not touched:             # only process the initial touch
            for button_index in range(len(self.buttons)):
                b = self.buttons[button_index]
                if touch_in_button(t, b):
                    change_to_state(b['next_state'])
                    break
        return bool(t)

And the output in console from startup to one touch/click on the bottom right of the screen:
Set background to 0
No SD card found: no SD card
Set background to alarm_panel.bmp
Getting time from IP address
struct_time(tm_year=2020, tm_mon=3, tm_mday=19, tm_hour=17, tm_min=24, tm_sec=4, tm_wday=4, tm_yday=79, tm_isdst=None)
Retrieving data...Reply is OK!
(43, 128, 19583)
(275, 204, 41951)
(272, 206, 47775)
(276, 205, 50303)
(274, 205, 51199)
(274, 205, 51519)
(274, 205, 51647)
(274, 204, 51711)
(274, 204, 51743)
(274, 204, 51839)
(274, 204, 51967)
(274, 204, 52127)
(275, 204, 52031)
(275, 204, 52063)
(275, 203, 51967)
(276, 204, 51903)
(276, 204, 51903)
(276, 204, 51743)
(277, 203, 51775)
(277, 203, 51711)
(277, 203, 51647)
(277, 203, 51583)
(277, 203, 51551)
(278, 203, 51519)
(277, 203, 51551)
(277, 203, 51359)
(277, 203, 51455)
(278, 204, 51327)
(278, 203, 51199)
(278, 203, 51263)
(278, 203, 51263)
(278, 203, 51263)
(278, 203, 50911)
(279, 203, 51199)
(278, 203, 51199)
(278, 203, 51071)
(278, 203, 51103)
(278, 203, 50975)
(278, 203, 50975)
(278, 203, 50687)
(278, 204, 50783)
(278, 206, 50719)
(278, 204, 50559)
(278, 203, 50399)
(278, 204, 50271)
(278, 205, 50015)
(278, 204, 49567)
(279, 205, 49311)
(279, 205, 49311)
(279, 205, 48895)
(279, 204, 48863)
(279, 206, 48191)
(280, 205, 47743)
(280, 205, 47135)
(281, 205, 45471)
(283, 206, 43423)
(283, 206, 42719)
(283, 207, 38975)
(279, 205, 35999)
(61, 208, 36735)
Set background to home_background.bmp

This is my main screen with 4 buttons - a single background image
mainscreen.jpg
mainscreen.jpg (80.28 KiB) Viewed 104 times

And if the "i" button is clicked, this is what should appear (essentially highlighted) - another background. However, most times the Home icon is highlighted and clicking any of the other buttons results in similar behaviour.
secondscreen.jpg
secondscreen.jpg (66.19 KiB) Viewed 104 times

This is my edited code adapted from the PyPortal Alarm Clock.
Thank you in advance for any suggestions how to isolate this further.
Code: Select all | TOGGLE FULL SIZE
"""
PyPortal based alarm clock.

Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!

Written by Dave Astels for Adafruit Industries
Copyright (c) 2019 Adafruit Industries
Licensed under the MIT license.

All text above must be included in any redistribution.
"""

#CR2N alarm interface.

import time
import json
from secrets import secrets
import board
from adafruit_pyportal import PyPortal
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label
from digitalio import DigitalInOut, Direction, Pull
import analogio
import displayio
import adafruit_logging as logging
import busio

# Set up where we'll be fetching data from
DATA_SOURCE = 'http://api.openweathermap.org/data/2.5/weather?id='+secrets['city_id']
DATA_SOURCE += '&appid='+secrets['openweather_token']
DATA_LOCATION = []

####################
# setup hardware

pyportal = PyPortal(url=DATA_SOURCE,
                    json_path=DATA_LOCATION,
                    status_neopixel=board.NEOPIXEL)

light = analogio.AnalogIn(board.LIGHT)

####################
# variables

# alarm support
alarm_file = 'alarm.wav'
alarm_enabled = False
alarm_armed = False
alarm_interval = 10.0
alarm_hour = 9
alarm_minute = 45

# weather support
icon_file = None
icon_sprite = None
celcius = secrets['celsius']

# display/data refresh timers
refresh_time = None
update_time = None
weather_refresh = None

# The most recently fetched time
current_time = None

# track whether we're in low light mode

low_light = False


####################
# Load the fonts

time_font = bitmap_font.load_font('/fonts/Anton-Regular-104.bdf')
time_font.load_glyphs(b'0123456789:') # pre-load glyphs for fast printing

alarm_font = bitmap_font.load_font('/fonts/Helvetica-Bold-36.bdf')
alarm_font.load_glyphs(b'0123456789:')

temperature_font = bitmap_font.load_font('/fonts/Arial-16.bdf')
temperature_font.load_glyphs(b'0123456789CF')

####################
# Set up logging

logger = logging.getLogger('CR2N_Alarm')
logger.setLevel(logging.INFO)            # change as desired

####################
# Functions

def create_text_areas(configs):
    """Given a list of area specifications, create and return text areas."""
    text_areas = []
    for cfg in configs:
        textarea = Label(cfg['font'], text=' '*cfg['size'])
        textarea.x = cfg['x']
        textarea.y = cfg['y']
        textarea.color = cfg['color']
        text_areas.append(textarea)
    return text_areas


def clear_splash():
    for _ in range(len(pyportal.splash) - 1):
        pyportal.splash.pop()


def touch_in_button(t, b):
    in_horizontal = b['left'] <= t[0] <= b['right']
    in_vertical = b['top'] <= t[1] <= b['bottom']
    return in_horizontal and in_vertical


touched = False

####################
# states

class State(object):
    """State abstract base class"""

    def __init__(self):
        pass

    @property
    def name(self):
        """Return the name of the state"""
        return ''


    def tick(self, now):
        """Handle a tick: one pass through the main loop"""
        pass


    #pylint:disable=unused-argument
    def touch(self, t, touched):
        """Handle a touch event.
        :param (x, y, z) - t: the touch location/strength"""
        return bool(t)


    def enter(self):
        """Just after the state is entered."""
        pass


    def exit(self):
        """Just before the state exits."""
        clear_splash()


class Time_State(State):
    """This state manages the primary time display screen/mode"""

    def __init__(self):
        super().__init__()
        self.previous_touch = None
        self.background = 'alarm_panel.bmp'
        self.refresh_time = None
        self.update_time = None
        self.weather_refresh = None
        text_area_configs = [dict(x=88, y=80, size=5, color=0xFFFFFF, font=time_font),
                             dict(x=10, y=80, size=6, color=0xFFFFFF, font=temperature_font),
                             dict(x=88, y=80, size=5, color=0xFFFFFF, font=time_font),]
        self.text_areas = create_text_areas(text_area_configs)
        self.weather_icon = displayio.Group()
        self.weather_icon.x = 10
        self.weather_icon.y = 10
        self.icon_file = None

        # each button has it's edges as well as the state to transition to when touched
        self.buttons = [dict(left=7, top=160, right=77, bottom=230, next_state='home'),
                        dict(left=87, top=160, right=154, bottom=230, next_state='alarm'),
                        dict(left=166, top=160, right=236, bottom=230, next_state='settings'),
                        dict(left=242, top=160, right=312, bottom=230, next_state='info')]


    @property
    def name(self):
        return 'time'

    '''
    def adjust_backlight_based_on_light(self, force=False):
        """Check light level. Adjust the backlight and background image if it's dark."""
        global low_light
        if light.value <= 1000 and (force or not low_light):
            pyportal.set_backlight(0.1)
            pyportal.set_background(self.background_night)
            low_light = True
        elif force or (light.value >= 2000 and low_light):
            pyportal.set_backlight(1.00)
            pyportal.set_background(self.background_day)
            low_light = False
    '''

    def tick(self, now):
        global alarm_armed, update_time, current_time

        # check light level and adjust background & backlight
        #self.adjust_backlight_based_on_light()

        # only query the online time once per hour (and on first run)
        if (not self.refresh_time) or ((now - self.refresh_time) > 3600):
            logger.debug('Fetching time')
            try:
                pyportal.get_local_time(location=secrets['timezone'])
                self.refresh_time = now
            except RuntimeError as e:
                self.refresh_time = now - 600   # delay 10 minutes before retrying
                logger.error('Some error occured, retrying! - %s', str(e))

        # only query the weather every 10 minutes (and on first run)
        if (not self.weather_refresh) or (now - self.weather_refresh) > 600:
            logger.debug('Fetching weather')
            try:
                value = pyportal.fetch()
                weather = json.loads(value)

                # set the icon/background
                weather_icon_name = weather['weather'][0]['icon']
                try:
                    self.weather_icon.pop()
                except IndexError:
                    pass
                filename = "/icons/"+weather_icon_name+".bmp"
                if filename:
                    if self.icon_file:
                        self.icon_file.close()
                    self.icon_file = open(filename, "rb")
                    icon = displayio.OnDiskBitmap(self.icon_file)
                    try:
                        icon_sprite = displayio.TileGrid(icon,
                                                         pixel_shader=displayio.ColorConverter(),
                                                         x=0, y=0)
                    except TypeError:
                        icon_sprite = displayio.TileGrid(icon,
                                                         pixel_shader=displayio.ColorConverter(),
                                                         position=(0, 0))


                    self.weather_icon.append(icon_sprite)

                temperature = weather['main']['temp'] - 273.15 # its...in kelvin
                if celcius:
                    temperature_text = '%3d C' % round(temperature)
                else:
                    temperature_text = '%3d F' % round(((temperature * 9 / 5) + 32))
                self.text_areas[1].text = temperature_text
                self.weather_refresh = now
                try:
                    board.DISPLAY.refresh(target_frames_per_second=60)
                except AttributeError:
                    board.DISPLAY.refresh_soon()
                    board.DISPLAY.wait_for_frame()


            except RuntimeError as e:
                self.weather_refresh = now - 540   # delay a minute before retrying
                logger.error("Some error occured, retrying! - %s", str(e))

        if (not update_time) or ((now - update_time) > 30):
            # Update the time
            update_time = now
            current_time = time.localtime()
            time_string = '%02d:%02d' % (current_time.tm_hour,current_time.tm_min)
            self.text_areas[0].text = time_string
            try:
                board.DISPLAY.refresh(target_frames_per_second=60)
            except AttributeError:
                board.DISPLAY.refresh_soon()
                board.DISPLAY.wait_for_frame()


            # Check if alarm should sound
        if current_time is not None:
            minutes_now = current_time.tm_hour * 60 + current_time.tm_min
            minutes_alarm = alarm_hour * 60 + alarm_minute
            if minutes_now == minutes_alarm:
                if alarm_armed:
                    change_to_state('alarm')
            else:
                alarm_armed = alarm_enabled


    def touch(self, t, touched):
        print(t)
        if t and not touched:             # only process the initial touch
            for button_index in range(len(self.buttons)):
                b = self.buttons[button_index]
                if touch_in_button(t, b):
                    change_to_state(b['next_state'])
                    break
        return bool(t)

    def enter(self):
    #    self.adjust_backlight_based_on_light(force=True)
        pyportal.set_background(self.background)
        for ta in self.text_areas:
            pyportal.splash.append(ta)
        pyportal.splash.append(self.weather_icon)
       
#        if alarm_enabled:
#            self.text_areas[1].text = '%2d:%02d' % (alarm_hour, alarm_minute)
#        else:
#            self.text_areas[1].text = '     '
        try:
            board.DISPLAY.refresh(target_frames_per_second=60)
        except AttributeError:
            board.DISPLAY.refresh_soon()
            board.DISPLAY.wait_for_frame()

    def exit(self):
        super().exit()


class Info_State(State):
    """This state shows who is home """

    def __init__(self):
        super().__init__()
        self.background = 'info_background.bmp'

    @property
    def name(self):
        return 'info'
    '''
    def tick(self, now):
        # Once the job is done, go back to the main screen
        change_to_state('time')
    '''
    def touch(self, t, touched):
        if t and not touched:             # only process the initial touch
            change_to_state('time')
        return bool(t)
   
       
    def enter(self):
        global low_light
        low_light = False
        pyportal.set_backlight(1.00)
        pyportal.set_background(self.background)       
       
        try:
            board.DISPLAY.refresh(target_frames_per_second=60)
        except AttributeError:
            board.DISPLAY.refresh_soon()
            board.DISPLAY.wait_for_frame()

class Home_State(State):
    """This state arms alarm in home mode' """

    def __init__(self):
        super().__init__()
        self.background = 'home_background.bmp'

    @property
    def name(self):
        return 'home'
    '''
    def tick(self, now):
        # Once the job is done, go back to the main screen
        self.text_areas[0].text = '1234'
        change_to_state('time')
    '''
    def touch(self, t, touched):
        if t and not touched:             # only process the initial touch
            change_to_state('time')
        return bool(t)
   
       
    def enter(self):
        global low_light
        low_light = False
        pyportal.set_backlight(1.00)
        pyportal.set_background(self.background)

        try:
            board.DISPLAY.refresh(target_frames_per_second=60)
        except AttributeError:
            board.DISPLAY.refresh_soon()
            board.DISPLAY.wait_for_frame()
       
    def exit(self):
    #    self.text_areas[0].text = '     '
        super().exit()


class Alarm_State(State):
    """This state arms the alarm in away mode."""

    def __init__(self):
        super().__init__()
        self.background = 'alarm_background.bmp'

    @property
    def name(self):
        return 'alarm'
    '''
    def tick(self, now):
        # Once the job is done, go back to the main screen
        change_to_state('time')
    '''
    def touch(self, t, touched):
        if t and not touched:             # only process the initial touch
            change_to_state('time')
        return bool(t)
   
    def enter(self):
        global low_light
        low_light = False
        pyportal.set_backlight(1.00)
        pyportal.set_background(self.background)
        try:
            board.DISPLAY.refresh(target_frames_per_second=60)
        except AttributeError:
            board.DISPLAY.refresh_soon()
            board.DISPLAY.wait_for_frame()   
           
    def exit(self):
        super().exit()



class Setting_State(State):
    """This state lets the user configure names of sensors"""

    def __init__(self):
        super().__init__()
        self.background = 'settings_background.bmp'

    @property
    def name(self):
        return 'settings'
    '''
    def tick(self, now):
        # Once the job is done, go back to the main screen
        change_to_state('time')
    '''
    def touch(self, t, touched):
        if t and not touched:             # only process the initial touch
            change_to_state('time')
        return bool(t)
       
    def enter(self):
        pyportal.set_background(self.background)
        try:
            board.DISPLAY.refresh(target_frames_per_second=60)
        except AttributeError:
            board.DISPLAY.refresh_soon()
            board.DISPLAY.wait_for_frame()

    def exit(self):
        super().exit()
       
       
####################
# State management

states = {'time': Time_State(),
          'info': Info_State(),
          'alarm': Alarm_State(),
          'settings': Setting_State(),
          'home': Home_State()
          }

current_state = None


def change_to_state(state_name):
    global current_state
    if current_state:
        logger.debug('Exiting %s', current_state.name)
        current_state.exit()
    current_state = states[state_name]
    logger.debug('Entering %s', current_state.name)
    current_state.enter()

####################
# And... go

clear_splash()
change_to_state("time")

while True:
    touched = current_state.touch(pyportal.touchscreen.touch_point, touched)
    current_state.tick(time.monotonic())

cr2n
 
Posts: 22
Joined: Mon Mar 31, 2014 9:41 pm

Re: PyPortal Touch inaccurate

by dastels on Fri Mar 20, 2020 9:32 am

I saw similar occasional touch point inaccuracies but didn't have time to dig in then. I've been looking at the touchscreen code (https://github.com/adafruit/Adafruit_CircuitPython_Touchscreen/blob/1753b423e39b5e9d2f3fba5d84b6cdb5a575d4b8/adafruit_touchscreen.py)and wonder if passing in a value for z_threshhold (which defaults to 10000) would help. I'll dig out that project and play around. Calibration might help as well. Another thing to try is increasing the number of samples.

From the comment on Touchscreen.__init__:

By default we oversample 4 times, change by adjusting 'samples' arg.
We can also detect the 'z' threshold, how much its prssed. We don't
register a touch unless its higher than 'z_threshold'
'calibration' is a tuple of two tuples, the default is
((0, 65535), (0, 65535)). The numbers are the min/max readings for the
X and Y coordinate planes, respectively. To figure these out, pass in
no calibration value and read the raw values out while touching the
panel.

dastels
 
Posts: 3102
Joined: Tue Oct 20, 2015 3:22 pm

Re: PyPortal Touch inaccurate

by cr2n on Fri Mar 20, 2020 3:17 pm

Thank you for the reply.
I looked at the PyPortal class here:
https://github.com/adafruit/Adafruit_Ci ... yportal.py

and I found the touchscreen in PyPortal is instantiated with default calibration values calibration=((5200, 59000), (5800, 57000)):
Code: Select all | TOGGLE FULL SIZE
if hasattr(board, "TOUCH_XL"):
            if self._debug:
                print("Init touchscreen")
            # pylint: disable=no-member
            self.touchscreen = adafruit_touchscreen.Touchscreen(
                board.TOUCH_XL,
                board.TOUCH_XR,
                board.TOUCH_YD,
                board.TOUCH_YU,
                calibration=((5200, 59000), (5800, 57000)),
                size=(board.DISPLAY.width, board.DISPLAY.height),
            )

I tried to "overload" the touchscreen class with a different samples values as suggested (not sure if I did this correctly) :
Code: Select all | TOGGLE FULL SIZE
from adafruit_pyportal import PyPortal
import adafruit_touchscreen
...
pyportal = PyPortal(url=DATA_SOURCE,
                    json_path=DATA_LOCATION,
                    status_neopixel=board.NEOPIXEL)
...
light = analogio.AnalogIn(board.LIGHT)
pyportal.touchscreen = adafruit_touchscreen.Touchscreen(
    board.TOUCH_XL,
    board.TOUCH_XR,
    board.TOUCH_YD,
    board.TOUCH_YU,
    calibration=((5200, 59000), (5800, 57000)),
    size=(board.DISPLAY.width, board.DISPLAY.height),
    samples=8)

I tried with samples=2, 6, 8 and 20 (not certain what are valid values) but no lasting improvement.
I also tried:
z-threshold=50 - device became unresponsive to touch
z-threshold=5 - device was responsive, still not accurate but then recorded touches when I did not touch the device, so some sort of noise in the device was being captured.
Even after cleaning the screen with a monitor wipe, the device detected random touches when I did not bump the desk or touch the screen.
I then resized the button area definitions smaller (approx 60x60px) in Time_State and tried moving the button areas above y=170 with no improvement (matching the max x/y definition of the neopixel example).
I braced the back of the display in case there was any "bending" of the display when touched (I am not pressing that hard) - no improvement.

I tried different touch methods such as fingernail, pen cap, and pencil eraser head in case the size of my finger is problematic - no obvious improvement

If I should be testing something different or differently, I appreciate any suggestions.

And thank you again for taking the time to look at this.

cr2n
 
Posts: 22
Joined: Mon Mar 31, 2014 9:41 pm

Re: PyPortal Touch inaccurate

by dastels on Fri Mar 20, 2020 3:41 pm

Likewise, I haven't found anything to improve it. My approach was:

Code: Select all | TOGGLE FULL SIZE
pyportal = PyPortal(
    url=DATA_SOURCE, json_path=DATA_LOCATION, status_neopixel=board.NEOPIXEL
)
pyportal.touchscreen.calibration = ((5792, 60032), (1504, 42688))
pyportal.touchscreen._xsamples = [0] * 10
pyportal.touchscreen._ysamples = [0] * 10
pyportal.touchscreen._zthresh = 15000


I found the calibration values using this in place of the main while True loop:

Code: Select all | TOGGLE FULL SIZE
x_min = 6500000
x_max = 0
y_min = 6500000
y_max = 0
for i in range(1000):
    pyportal.touchscreen.touch_point
    for x in pyportal.touchscreen._xsamples:
        if x > x_max:
            x_max = x
        elif x < x_min:
            x_min = x
    for y in pyportal.touchscreen._ysamples:
        if y > y_max:
            y_max = y
        elif y < y_min:
            y_min = y

print("x: ({0}, {1}), y: ({2}, {3})".format(x_min, x_max, y_min, y_max))


I had actually pulled this off the shelf since my Mugsy will be here shortly and I want to clock with it's coffee summoning abilities operational :)

I'll keep poking at it.

Dave

dastels
 
Posts: 3102
Joined: Tue Oct 20, 2015 3:22 pm

Re: PyPortal Touch inaccurate

by cr2n on Sat Mar 21, 2020 4:09 pm

Thank you again for your assistance and suggestions.

I think I found a workaround for myself, adapting your code further, that has proven reliable for my use. I do not need the swipe functionality, but hopefully my workaround below can give you an idea how to work around this issue for your project.

When troubleshooting this issue using the serial monitor in Mu, I consistently found that a single touch generated many "touches" in the while loop. Often, the first detected touch point was erroneous, and then the next 10+ detections recorded were accurate. Since the PyPortal Alarm Clock code uses the first touch point value it detects, I simply skip the first detected value.

Hopefully someone can benefit from this idea, even if it is an inelegant hack.

First, declare a variable (skipFirstTouch) and set it to 0 to track how many touches have been detected - we really only care about the first occurrence each time. This is in addition to the "touched" boolean variable already declared.
Code: Select all | TOGGLE FULL SIZE
touched = False
skipFirstTouch = 0


In each class, modify the touch method to only call touch_in_button function after skipFirstTouch has been incremented to 1 and a touch has been detected through the "touched" variable.
Code: Select all | TOGGLE FULL SIZE
 def touch(self, t, touched):
        global skipFirstTouch
        if skipFirstTouch == 1:
            if t and touched :             # only process the initial touch
                for button_index in range(len(self.buttons)):
                    b = self.buttons[button_index]
                    if touch_in_button(t, b):
                        change_to_state(b['next_state'])
                        break
        else:
            skipFirstTouch += 1
        return bool(t)


Then reset skipFirstTouch variable during state change in the function change_to_state:
Code: Select all | TOGGLE FULL SIZE
def change_to_state(state_name):
    global current_state
    if current_state:
        logger.debug('Exiting %s', current_state.name)
        current_state.exit()
    current_state = states[state_name]
    logger.debug('Entering %s', current_state.name)
    skipFirstTouch = 0
    current_state.enter()

cr2n
 
Posts: 22
Joined: Mon Mar 31, 2014 9:41 pm

Re: PyPortal Touch inaccurate

by cr2n on Sat Mar 21, 2020 4:22 pm

I almost forgot ... Thank you for sharing your project and code. It is helping me to finish a project I have been working on for several years. I was never satisfied with the user interface until I found the PyPortal and your project example helped me start well beyond the starting line. Cheers!

cr2n
 
Posts: 22
Joined: Mon Mar 31, 2014 9:41 pm

Re: PyPortal Touch inaccurate

by dastels on Sat Mar 21, 2020 5:33 pm

I'll give skipping the first touch a try.

And I'm glad the guide & code helped.

Consider bringing your project to the weekly Adafruit Show & Tell live-stream each Wed evening.

Dave

dastels
 
Posts: 3102
Joined: Tue Oct 20, 2015 3:22 pm

Re: PyPortal Touch inaccurate

by cr2n on Sat Mar 21, 2020 8:55 pm

If you find a more elegant solution, I would love to see it.
Thanks again!

cr2n
 
Posts: 22
Joined: Mon Mar 31, 2014 9:41 pm

Please be positive and constructive with your questions and comments.