nuttx-apps/graphics/twm4nx/apps/ccalibration.cxx
Xiang Xiao 857158451b Unify the void cast usage
1.Remove void cast for function because many place ignore the returned value witout cast
2.Replace void cast for variable with UNUSED macro

Change-Id: Ie644129a563244a6397036789c4c3ea83c4e9b09
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2020-01-02 23:21:01 +08:00

1405 lines
39 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// apps/graphics/twm4nx/src/ccalibration.cxx
// Perform Touchscreen Calibration
//
// Copyright (C) 2019 Gregory Nutt. All rights reserved.
// Author: Gregory Nutt <gnutt@nuttx.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the
// distribution.
// 3. Neither the name NuttX nor the names of its contributors may be
// used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Included Files
/////////////////////////////////////////////////////////////////////////////
#include <nuttx/config.h>
#include <cunistd>
#include <csched>
#include <cassert>
#include <cerrno>
#include <limits.h>
#include <semaphore.h>
#include <debug.h>
#include <nuttx/semaphore.h>
#include <nuttx/nx/nxbe.h>
#ifdef CONFIG_TWM4NX_TOUCHSCREEN_CONFIGDATA
# include "platform/configdata.h"
#endif
#include "graphics/twm4nx/twm4nx_config.hxx"
#include "graphics/twm4nx/ctwm4nx.hxx"
#include "graphics/twm4nx/cinput.hxx"
#include "graphics/twm4nx/cmainmenu.hxx"
#include "graphics/twm4nx/apps/calib_config.hxx"
#include "graphics/twm4nx/apps/ccalibration.hxx"
/////////////////////////////////////////////////////////////////////////////
// Pre-processor Definitions
/////////////////////////////////////////////////////////////////////////////
// Configuration
#ifndef CONFIG_NX
# error "NX is not enabled (CONFIG_NX)"
#endif
// Positional/size data for the calibration lines and circles
#define CALIBRATION_LEFTX CONFIG_TWM4NX_CALIBRATION_MARGIN
#define CALIBRATION_RIGHTX (windowSize.w - CONFIG_TWM4NX_CALIBRATION_MARGIN + 1)
#define CALIBRATION_TOPY CONFIG_TWM4NX_CALIBRATION_MARGIN
#define CALIBRATION_BOTTOMY (windowSize.h - CONFIG_TWM4NX_CALIBRATION_MARGIN + 1)
#define CALIBRATION_CIRCLE_RADIUS 16
#define CALIBRATION_LINE_THICKNESS 2
/////////////////////////////////////////////////////////////////////////////
// Private Data
/////////////////////////////////////////////////////////////////////////////
using namespace Twm4Nx;
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
static const char GTouchMsg[] = "Touch";
static const char GAgainMsg[] = "Again";
static const char GOkMsg[] = "OK";
#endif
/////////////////////////////////////////////////////////////////////////////
// CCalibration Method Implementations
/////////////////////////////////////////////////////////////////////////////
/**
* CCalibration Constructor
*
* @param twm4nx. The Twm4Nx session instance.
*/
CCalibration::CCalibration(FAR CTwm4Nx *twm4nx)
{
// Initialize state data
m_twm4nx = twm4nx;
m_nxWin = (FAR NXWidgets::CNxWindow *)0;
m_thread = (pthread_t)0;
m_calthread = CALTHREAD_NOTRUNNING;
m_calphase = CALPHASE_NOT_STARTED;
m_touched = false;
#ifdef CONFIG_TWM4NX_CALIBRATION_AVERAGE
m_nsamples = 0;
#endif
// Initialize touch sample.
m_sample.valid = false;
m_sample.pos.x = 0;
m_sample.pos.y = 0;
m_sample.buttons = 0;
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
// Nullify widgets that will be instantiated when the calibration thread
// is started
m_text = (NXWidgets::CLabel *)0;
m_font = (NXWidgets::CNxFont *)0;
#endif
// Set up the semaphores that are used to synchronize the calibration
// thread with Twm4Nx events
sem_init(&m_exclSem, 0, 1);
sem_init(&m_synchSem, 0, 0);
sem_setprotocol(&m_synchSem, SEM_PRIO_NONE);
}
/**
* CCalibration Destructor
*/
CCalibration::~CCalibration(void)
{
// Make sure that the application is not running (it should already
// have been stopped)
stop();
if (m_nxWin != (FAR NXWidgets::CNxWindow *)0)
{
delete m_nxWin;
}
}
/**
* CCalibration Initializer. Performs parts of the instance
* construction that may fail. This function creates the
* initial calibration display.
*/
bool CCalibration::initialize(void)
{
// Create the calibration window
// 1. Get the server instance. m_twm4nx inherits from NXWidgets::CNXServer
// so we all ready have the server instance.
// 2. Create the style, using the selected colors (REVISIT)
// 3. Create a Widget control instance for the window using the default
// style for now. CWindowEvent derives from CWidgetControl.
struct SAppEvents events;
events.eventObj = (FAR void *)this;
events.redrawEvent = EVENT_CALIB_REDRAW;
events.mouseEvent = EVENT_CALIB_XYINPUT;
events.kbdEvent = EVENT_CALIB_KBDINPUT;
events.closeEvent = EVENT_CALIB_CLOSE;
events.deleteEvent = EVENT_CALIB_DELETE;
FAR CWindowEvent *control =
new CWindowEvent(m_twm4nx, (FAR CWindow *)0, events);
// 4. Create the calibration window
uint8_t wflags = NXBE_WINDOW_HIDDEN;
m_nxWin = m_twm4nx->createRawWindow(control, wflags);
if (m_nxWin == (FAR NXWidgets::CNxWindow *)0)
{
twmerr("ERROR: Failed open raw window\n");
delete control;
return false;
}
// 5. Open and initialize the main window
bool success = m_nxWin->open();
if (!success)
{
delete m_nxWin;
m_nxWin = (FAR NXWidgets::CNxWindow *)0;
return false;
}
// 6. Set the window position to the display origin
struct nxgl_point_s pos =
{
.x = 0,
.y = 0
};
if (!m_nxWin->setPosition(&pos))
{
delete m_nxWin;
m_nxWin = (FAR NXWidgets::CNxWindow *)0;
return false;
}
// 7. Set the window size to fill the whole display
struct nxgl_size_s displaySize;
m_twm4nx->getDisplaySize(&displaySize);
if (!m_nxWin->setSize(&displaySize))
{
delete m_nxWin;
m_nxWin = (FAR NXWidgets::CNxWindow *)0;
return false;
}
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
// Create widgets that will be used in the calibration display
if (!createWidgets())
{
twmerr("ERROR: failed to create widgets\n");
return (FAR void *)0;
}
#endif
// Disable touchscreen processing so that we receive raw, un-calibrated
// touchscreen input
FAR CInput *cinput = m_twm4nx->getInput();
cinput->enableCalibration(false);
return true;
}
/**
* Start the application (perhaps in the minimized state).
*
* @return True if the application was successfully started.
*/
bool CCalibration::run(void)
{
// Verify that the thread is not already running
if (m_calthread == CALTHREAD_RUNNING)
{
twmwarn("WARNING: The calibration thread is already running\n");
return false;
}
// Configure the calibration thread
pthread_attr_t attr;
pthread_attr_init(&attr);
struct sched_param param;
param.sched_priority = CONFIG_TWM4NX_CALIBRATION_LISTENERPRIO;
pthread_attr_setschedparam(&attr, &param);
pthread_attr_setstacksize(&attr, CONFIG_TWM4NX_CALIBRATION_LISTENERSTACK);
// Set the initial state of the thread
m_calthread = CALTHREAD_STARTED;
// Start the thread that will perform the calibration process
int ret = pthread_create(&m_thread, &attr, calibration, (FAR void *)this);
if (ret != 0)
{
twmerr("ERROR: pthread_create failed: %d\n", ret);
return false;
}
twminfo("Calibration thread m_calthread=%d\n", (int)m_calthread);
return true;
}
/**
* Handle CCalibration events. This overrides a method from
* CTwm4NXEvent.
*
* @param eventmsg. The received NxWidget WINDOW event message.
* @return True if the message was properly handled. false is
* return on any failure.
*/
bool CCalibration::event(FAR struct SEventMsg *eventmsg)
{
bool success = true;
switch (eventmsg->eventID)
{
case EVENT_CALIB_XYINPUT: // New touchscreen input
{
// Make sure that the thread is still running
if (m_calthread == CALTHREAD_RUNNING)
{
// Get exclusive access to the data shared between threads
success = exclusiveAccess();
if (success)
{
FAR struct SXyInputEventMsg *xymsg =
(FAR struct SXyInputEventMsg *)eventmsg;
// Save the new sample data
m_sample.valid = true;
m_sample.pos = xymsg->pos;
m_sample.buttons = xymsg->buttons;
// And let the let the calibration thread know that new
// data is available
sem_post(&m_synchSem);
sem_post(&m_exclSem);
}
}
}
break;
case EVENT_CALIB_DELETE: // Today is a good day to die.
DEBUGASSERT(m_calthread == CALTHREAD_TERMINATED);
delete this; // This is suicide
break;
default:
success = false;
break;
}
return success;
}
/**
* Accept raw touchscreen input.
*
* @param sample Touchscreen input sample
*/
void CCalibration::touchscreenInput(struct STouchSample &sample)
{
// Is this a new touch event?
if ((sample.buttons & MOUSE_BUTTON_1) != 0)
{
// Yes.. save the touch position and wait for the un-touch report
m_touchPos.x = sample.pos.x;
m_touchPos.y = sample.pos.y;
twminfo("Touch buttons: %02x x: %d y: %d\n",
sample.buttons, sample.pos.x, sample.pos.y);
// Show calibration screen again, changing the color of the circle to
// make it clear that the touch has been noticed.
if (!m_touched)
{
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_TOUCHEDCOLOR;
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
m_text->setText(GOkMsg);
#endif
showCalibration();
m_touched = true;
}
}
// There is no touch. Did we see the pen down event?
else if (m_touched)
{
// Yes.. invoke the state machine.
twminfo("State: %d Screen x: %d y: %d Touch x: %d y: %d\n",
m_calphase, m_screenInfo.pos.x, m_screenInfo.pos.y,
m_touchPos.x, m_touchPos.y);
stateMachine();
// The screen is no longer touched
m_touched = false;
}
}
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
/**
* Create widgets need by the calibration thread.
*
* @return True if the widgets were successfully created.
*/
bool CCalibration::createWidgets(void)
{
// Select a font for the calculator
m_font = new NXWidgets::
CNxFont((nx_fontid_e)CONFIG_TWM4NX_CALIBRATION_FONTID,
CONFIG_TWM4NX_DEFAULT_FONTCOLOR, CONFIG_TWM4NX_TRANSPARENT_COLOR);
if (!m_font)
{
twmerr("ERROR failed to create font\n");
return false;
}
// Get the size of the window
struct nxgl_size_s windowSize;
if (!m_nxWin->getSize(&windowSize))
{
twmerr("ERROR: Failed to get window size\n");
delete m_font;
m_font = (NXWidgets::CNxFont *)0;
return false;
}
// How big is the biggest string we might display?
nxgl_coord_t maxStringWidth = m_font->getStringWidth(GTouchMsg);
nxgl_coord_t altStringWidth = m_font->getStringWidth(GAgainMsg);
if (altStringWidth > maxStringWidth)
{
maxStringWidth = altStringWidth;
}
// How big can the label be?
struct nxgl_size_s labelSize;
labelSize.w = maxStringWidth + 2*4;
labelSize.h = m_font->getHeight() + 2*4;
// Where should the label be?
struct nxgl_point_s labelPos;
labelPos.x = ((windowSize.w - labelSize.w) / 2);
labelPos.y = ((windowSize.h - labelSize.h) / 2);
// Get the widget control associated with the application window
NXWidgets::CWidgetControl *control = m_nxWin->getWidgetControl();
// Create a label to show the calibration message.
m_text = new NXWidgets::
CLabel(control, labelPos.x, labelPos.y, labelSize.w, labelSize.h, "");
if (!m_text)
{
twmerr("ERROR: Failed to create CLabel\n");
delete m_font;
m_font = (NXWidgets::CNxFont *)0;
return false;
}
// No border
m_text->setBorderless(true);
// Center text
m_text->setTextAlignmentHoriz(NXWidgets::CLabel::TEXT_ALIGNMENT_HORIZ_CENTER);
// Disable drawing and events until we are asked to redraw the window
m_text->disableDrawing();
m_text->setRaisesEvents(false);
// Select the font
m_text->setFont(m_font);
return true;
}
/**
* Destroy widgets created for the calibration thread.
*/
void CCalibration::destroyWidgets(void)
{
delete m_text;
m_text = (NXWidgets::CLabel *)0;
delete m_font;
m_font = (NXWidgets::CNxFont *)0;
}
#endif
/**
* The calibration thread. This is the entry point of a thread that provides the
* calibration displays, waits for input, and collects calibration data.
*
* @param arg. The CCalibration 'this' pointer cast to a void*.
* @return This function always returns NULL when the thread exits
*/
FAR void *CCalibration::calibration(FAR void *arg)
{
CCalibration *This = (CCalibration *)arg;
// The calibration thread is now running
This->m_calthread = CALTHREAD_RUNNING;
This->m_calphase = CALPHASE_NOT_STARTED;
twminfo("Started: m_calthread=%d\n", (int)This->m_calthread);
// Make the calibration display visible and show the initial calibration
// display
This->m_nxWin->show();
This->stateMachine();
// Loop until calibration completes or we have been requested to terminate
while (This->m_calthread != CALTHREAD_STOPREQUESTED &&
This->m_calphase != CALPHASE_COMPLETE)
{
// Wait for the next raw touchscreen input (or possibly a signal)
struct STouchSample sample;
while (!This->waitTouchSample(sample) &&
This->m_calthread == CALTHREAD_RUNNING);
// Then process the raw touchscreen input
if (This->m_calthread == CALTHREAD_RUNNING)
{
This->touchscreenInput(sample);
}
}
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
// Hide the message
This->m_text->setText("");
This->m_text->enableDrawing();
This->m_text->redraw();
This->m_text->disableDrawing();
#endif
// Perform the final steps of calibration
This->finishCalibration();
// Then destroy the window and free resources
This->destroy();
twminfo("Terminated: m_calthread=%d\n", (int)This->m_calthread);
return (FAR void *)0;
}
/**
* Get exclusive access data shared across threads.
*
* @return True is returned if the sample data was obtained without error.
*/
bool CCalibration::exclusiveAccess(void)
{
int ret;
do
{
ret = sem_wait(&m_exclSem);
if (ret < 0)
{
int errcode = errno;
DEBUGASSERT(errcode == EINTR || errcode == ECANCELED);
if (errcode != EINTR)
{
return false;
}
}
}
while (ret < 0);
return true;
}
/**
* Wait for touchscreen next sample data to become available.
*
* @param sample Snapshot of last touchscreen sample
* @return True is returned if the sample data was obtained without error.
*/
bool CCalibration::waitTouchSample(struct STouchSample &sample)
{
// Loop until we get the sample (or something really bad happens)
for (; ; )
{
// Is the sample valid?
if (!m_sample.valid)
{
// No, wait for new data to be posted
int ret = sem_wait(&m_synchSem);
if (ret < 0)
{
// EINTR is not an error, it just means that we were
// interrupted by a signal. Everything else is bad news.
int errcode = errno;
DEBUGASSERT(errcode == EINTR || errcode == ECANCELED);
if (errcode != EINTR)
{
return false;
}
}
}
// The samples is valid! Get exclusive access and check again
else if (exclusiveAccess())
{
if (m_sample.valid)
{
// We have good data. Return it to the caller
sample = m_sample;
m_sample.valid = false;
sem_post(&m_exclSem);
return true;
}
else
{
// No.. What happened?
sem_post(&m_exclSem);
}
}
}
return true;
}
/**
* Accumulate and average touch sample data
*
* @param average. When the averaged data is available, return it here
* @return True: Average data is available; False: Need to collect more samples
*/
#ifdef CONFIG_TWM4NX_CALIBRATION_AVERAGE
bool CCalibration::averageSamples(struct nxgl_point_s &average)
{
// Have we started collecting sample data? */
// Save the sample data
twminfo("Sample %d: Touch x: %d y: %d\n",
m_nsamples + 1, m_touchPos.x, m_touchPos.y);
m_sampleData[m_nsamples].x = m_touchPos.x;
m_sampleData[m_nsamples].y = m_touchPos.y;
m_nsamples++;
// Have all of the samples been collected?
if (m_nsamples < CONFIG_TWM4NX_CALIBRATION_NSAMPLES)
{
// Not yet... return false
return false;
}
// Yes.. we have all of the samples
#ifdef CONFIG_TWM4NX_CALIBRATION_NSAMPLES
// Discard the smallest X and Y values
int xValue = INT_MAX;
int xIndex = -1;
int yValue = INT_MAX;
int yIndex = -1;
for (int i = 0; i < CONFIG_TWM4NX_CALIBRATION_NSAMPLES; i++)
{
if ((int)m_sampleData[i].x < xValue)
{
xValue = (int)m_sampleData[i].x;
xIndex = i;
}
if ((int)m_sampleData[i].y < yValue)
{
yValue = (int)m_sampleData[i].y;
yIndex = i;
}
}
m_sampleData[xIndex].x = m_sampleData[CONFIG_TWM4NX_CALIBRATION_NSAMPLES-1].x;
m_sampleData[yIndex].y = m_sampleData[CONFIG_TWM4NX_CALIBRATION_NSAMPLES-1].y;
// Discard the largest X and Y values
xValue = -1;
xIndex = -1;
yValue = -1;
yIndex = -1;
for (int i = 0; i < CONFIG_TWM4NX_CALIBRATION_NSAMPLES-1; i++)
{
if ((int)m_sampleData[i].x > xValue)
{
xValue = (int)m_sampleData[i].x;
xIndex = i;
}
if ((int)m_sampleData[i].y > yValue)
{
yValue = (int)m_sampleData[i].y;
yIndex = i;
}
}
m_sampleData[xIndex].x = m_sampleData[CONFIG_TWM4NX_CALIBRATION_NSAMPLES-2].x;
m_sampleData[yIndex].y = m_sampleData[CONFIG_TWM4NX_CALIBRATION_NSAMPLES-2].y;
#endif
#if TWM4NX_CALIBRATION_NAVERAGE > 1
// Calculate the average of the remaining values
long xaccum = 0;
long yaccum = 0;
for (int i = 0; i < TWM4NX_CALIBRATION_NAVERAGE; i++)
{
xaccum += m_sampleData[i].x;
yaccum += m_sampleData[i].y;
}
average.x = xaccum / TWM4NX_CALIBRATION_NAVERAGE;
average.y = yaccum / TWM4NX_CALIBRATION_NAVERAGE;
#else
average.x = m_sampleData[0].x;
average.y = m_sampleData[0].y;
#endif
twminfo("Average: Touch x: %d y: %d\n", average.x, average.y);
m_nsamples = 0;
return true;
}
#endif
/**
* This is the calibration state machine. It is called initially and then
* as new touchscreen data is received.
*/
void CCalibration::stateMachine(void)
{
twminfo("Old m_calphase=%d\n", m_calphase);
#ifdef CONFIG_TWM4NX_CALIBRATION_AVERAGE
// Are we collecting samples?
struct nxgl_point_s average;
switch (m_calphase)
{
case CALPHASE_UPPER_LEFT:
case CALPHASE_UPPER_RIGHT:
case CALPHASE_LOWER_RIGHT:
case CALPHASE_LOWER_LEFT:
{
// Yes... Have all of the samples been collected?
if (!averageSamples(average))
{
// Not yet... Show the calibration screen again with the circle
// in the normal color and an instruction to touch the circle again.
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_CIRCLECOLOR;
m_text->setText(GAgainMsg);
showCalibration();
// And wait for the next touch
return;
}
}
break;
// No... we are not collecting data now
default:
break;
}
#endif
// Get the size of the fullscreen window
struct nxgl_size_s windowSize;
if (!m_nxWin->getSize(&windowSize))
{
return;
}
switch (m_calphase)
{
default:
case CALPHASE_NOT_STARTED:
{
// Clear the entire screen
// Get the widget control associated with the full screen window
NXWidgets::CWidgetControl *control = m_nxWin->getWidgetControl();
// Get the CCGraphicsPort instance for this window
NXWidgets::CGraphicsPort *port = control->getGraphicsPort();
// Fill the entire window with the background color
port->drawFilledRect(0, 0, windowSize.w, windowSize.h,
CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR);
// Then draw the first calibration screen
m_screenInfo.pos.x = CALIBRATION_LEFTX;
m_screenInfo.pos.y = CALIBRATION_TOPY;
m_screenInfo.lineColor = CONFIG_TWM4NX_CALIBRATION_LINECOLOR;
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_CIRCLECOLOR;
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
m_text->setText(GTouchMsg);
#endif
showCalibration();
// Then set up the current state
m_calphase = CALPHASE_UPPER_LEFT;
}
break;
case CALPHASE_UPPER_LEFT:
{
// A touch has been received while in the CALPHASE_UPPER_LEFT state.
// Save the touch data and set up the next calibration display
#ifdef CONFIG_TWM4NX_CALIBRATION_AVERAGE
m_calibData[CALIB_UPPER_LEFT_INDEX].x = average.x;
m_calibData[CALIB_UPPER_LEFT_INDEX].y = average.y;
#else
m_calibData[CALIB_UPPER_LEFT_INDEX].x = m_touchPos.x;
m_calibData[CALIB_UPPER_LEFT_INDEX].y = m_touchPos.y;
#endif
// Clear the previous screen by re-drawing it using the background
// color. That is much faster than clearing the whole display
m_screenInfo.lineColor = CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR;
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR;
showCalibration();
// Then draw the next calibration screen
m_screenInfo.pos.x = CALIBRATION_RIGHTX;
m_screenInfo.pos.y = CALIBRATION_TOPY;
m_screenInfo.lineColor = CONFIG_TWM4NX_CALIBRATION_LINECOLOR;
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_CIRCLECOLOR;
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
m_text->setText(GTouchMsg);
#endif
showCalibration();
// Then set up the current state
m_calphase = CALPHASE_UPPER_RIGHT;
}
break;
case CALPHASE_UPPER_RIGHT:
{
// A touch has been received while in the CALPHASE_UPPER_RIGHT state.
// Save the touch data and set up the next calibration display
#ifdef CONFIG_TWM4NX_CALIBRATION_AVERAGE
m_calibData[CALIB_UPPER_RIGHT_INDEX].x = average.x;
m_calibData[CALIB_UPPER_RIGHT_INDEX].y = average.y;
#else
m_calibData[CALIB_UPPER_RIGHT_INDEX].x = m_touchPos.x;
m_calibData[CALIB_UPPER_RIGHT_INDEX].y = m_touchPos.y;
#endif
// Clear the previous screen by re-drawing it using the backgro9und
// color. That is much faster than clearing the whole display
m_screenInfo.lineColor = CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR;
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR;
showCalibration();
// Then draw the next calibration screen
m_screenInfo.pos.x = CALIBRATION_RIGHTX;
m_screenInfo.pos.y = CALIBRATION_BOTTOMY;
m_screenInfo.lineColor = CONFIG_TWM4NX_CALIBRATION_LINECOLOR;
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_CIRCLECOLOR;
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
m_text->setText(GTouchMsg);
#endif
showCalibration();
// Then set up the current state
m_calphase = CALPHASE_LOWER_RIGHT;
}
break;
case CALPHASE_LOWER_RIGHT:
{
// A touch has been received while in the CALPHASE_LOWER_RIGHT state.
// Save the touch data and set up the next calibration display
#ifdef CONFIG_TWM4NX_CALIBRATION_AVERAGE
m_calibData[CALIB_LOWER_RIGHT_INDEX].x = average.x;
m_calibData[CALIB_LOWER_RIGHT_INDEX].y = average.y;
#else
m_calibData[CALIB_LOWER_RIGHT_INDEX].x = m_touchPos.x;
m_calibData[CALIB_LOWER_RIGHT_INDEX].y = m_touchPos.y;
#endif
// Clear the previous screen by re-drawing it using the backgro9und
// color. That is much faster than clearing the whole display
m_screenInfo.lineColor = CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR;
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR;
showCalibration();
// Then draw the next calibration screen
m_screenInfo.pos.x = CALIBRATION_LEFTX;
m_screenInfo.pos.y = CALIBRATION_BOTTOMY;
m_screenInfo.lineColor = CONFIG_TWM4NX_CALIBRATION_LINECOLOR;
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_CIRCLECOLOR;
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
m_text->setText(GTouchMsg);
#endif
showCalibration();
// Then set up the current state
m_calphase = CALPHASE_LOWER_LEFT;
}
break;
case CALPHASE_LOWER_LEFT:
{
// A touch has been received while in the CALPHASE_LOWER_LEFT state.
// Save the touch data and set up the next calibration display
#ifdef CONFIG_TWM4NX_CALIBRATION_AVERAGE
m_calibData[CALIB_LOWER_LEFT_INDEX].x = average.x;
m_calibData[CALIB_LOWER_LEFT_INDEX].y = average.y;
#else
m_calibData[CALIB_LOWER_LEFT_INDEX].x = m_touchPos.x;
m_calibData[CALIB_LOWER_LEFT_INDEX].y = m_touchPos.y;
#endif
// Clear the previous screen by re-drawing it using the background
// color. That is much faster than clearing the whole display
m_screenInfo.lineColor = CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR;
m_screenInfo.circleFillColor = CONFIG_TWM4NX_CALIBRATION_BACKGROUNDCOLOR;
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
m_text->setText(GTouchMsg);
#endif
showCalibration();
// Inform any waiter that calibration is complete
m_calphase = CALPHASE_COMPLETE;
}
break;
case CALPHASE_COMPLETE:
// Might happen... do nothing if it does
break;
}
twminfo("New m_calphase=%d Screen x: %d y: %d\n",
m_calphase, m_screenInfo.pos.x, m_screenInfo.pos.y);
}
/**
* Presents the next calibration screen
*
* @param screenInfo Describes the next calibration screen
*/
void CCalibration::showCalibration(void)
{
// Get the widget control associated with the full screen window
NXWidgets::CWidgetControl *control = m_nxWin->getWidgetControl();
// Get the CCGraphicsPort instance for this window
NXWidgets::CGraphicsPort *port = control->getGraphicsPort();
// Get the size of the full screen window
struct nxgl_size_s windowSize;
if (!m_nxWin->getSize(&windowSize))
{
return;
}
// Draw the circle at the center of the touch position
port->drawFilledCircle(&m_screenInfo.pos, CALIBRATION_CIRCLE_RADIUS,
m_screenInfo.circleFillColor);
// Draw horizontal line
port->drawFilledRect(0, m_screenInfo.pos.y, windowSize.w, CALIBRATION_LINE_THICKNESS,
m_screenInfo.lineColor);
// Draw vertical line
port->drawFilledRect(m_screenInfo.pos.x, 0, CALIBRATION_LINE_THICKNESS, windowSize.h,
m_screenInfo.lineColor);
// Show the touchscreen message
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
m_text->enableDrawing();
m_text->redraw();
m_text->disableDrawing();
#endif
}
/**
* Finish calibration steps and provide the calibration data to the
* touchscreen driver.
*/
void CCalibration::finishCalibration(void)
{
// Hide the window
m_nxWin->hide();
// Did we finish calibration successfully?
if (m_calphase == CALPHASE_COMPLETE)
{
// Yes... Get the final Calibration data
struct SCalibrationData caldata;
if (createCalibrationData(caldata))
{
#ifdef CONFIG_TWM4NX_TOUCHSCREEN_CONFIGDATA
// Save the new calibration data. The saved calibration
// data may be used to avoided recalibrating in the future.
int ret = platform_setconfig(CONFIGDATA_TSCALIBRATION, 0,
(FAR const uint8_t *)&caldata,
sizeof(struct SCalibrationData));
if (ret != 0)
{
twmerr("ERROR: Failed to save calibration data\n");
}
#endif
// And provide the calibration data to CInput, enabling
// touchscreen processing
FAR CInput *cinput = m_twm4nx->getInput();
cinput->setCalibrationData(caldata);
cinput->enableCalibration(true);
}
}
// And set the terminated stated
m_calthread = CALTHREAD_TERMINATED;
}
/**
* Stop the application.
*/
void CCalibration::stop(void)
{
twminfo("Stopping calibration: m_calthread=%d\n", (int)m_calthread);
// Was the calibration thread created?
if (m_thread != 0)
{
// Is the calibration thread running?
if (m_calthread == CALTHREAD_RUNNING)
{
// The main thread is stuck waiting for the next touchscreen input...
// We can signal that we would like the thread to stop, but we will be
// stuck here until the next touch
m_calthread = CALTHREAD_STOPREQUESTED;
// Try to wake up the calibration thread so that it will see our
// termination request
twminfo("Stopping calibration: m_calthread=%d\n", (int)m_calthread);
pthread_kill(m_thread, CONFIG_TWM4NX_CALIBRATION_SIGNO);
// Wait for the calibration thread to exit
FAR pthread_addr_t value;
pthread_join(m_thread, &value);
}
}
}
/**
* Destroy the application and free all of its resources. This method
* will initiate blocking of messages from the NX server. The server
* will flush the window message queue and reply with the blocked
* message. When the block message is received by CWindowEvent,
* it will send the destroy message to the calibration window task which
* will, finally, safely delete the application.
*/
void CCalibration::destroy(void)
{
#ifdef CONFIG_TWM4NX_CALIBRATION_MESSAGES
// Destroy widgets
destroyWidgets();
#endif
// Close the calibration window... but not yet. Send the blocked message.
// The actual termination will no occur until the NX server drains all of
// the message events. We will get the EVENT_WINDOW_DELETE event at that
// point
NXWidgets::CWidgetControl *control = m_nxWin->getWidgetControl();
nx_block(control->getWindowHandle(), (FAR void *)m_nxWin);
}
/**
* Given the raw touch data collected by the calibration thread, create the
* massaged calibration data needed by CTouchscreen.
*
* @param data. A reference to the location to save the calibration data
* @return True if the calibration data was successfully created.
*/
bool CCalibration::createCalibrationData(struct SCalibrationData &data)
{
// Get the size of the fullscreen window
struct nxgl_size_s windowSize;
if (!m_nxWin->getSize(&windowSize))
{
twmerr("ERROR: NXWidgets::INxWindow::getSize failed\n");
return false;
}
#ifdef CONFIG_TWM4NX_CALIBRATION_ANISOTROPIC
// X lines:
//
// x2 = slope*y1 + offset
//
// slope = (bottomY - topY) / (bottomX - topX)
// offset = (topY - topX * slope)
float topX = (float)m_calibData[CALIB_UPPER_LEFT_INDEX].x;
float bottomX = (float)m_calibData[CALIB_LOWER_LEFT_INDEX].x;
float topY = (float)m_calibData[CALIB_UPPER_LEFT_INDEX].y;
float bottomY = (float)m_calibData[CALIB_LOWER_LEFT_INDEX].y;
data.left.slope = (bottomX - topX) / (bottomY - topY);
data.left.offset = topX - topY * data.left.slope;
twminfo("Left slope: %6.2f offset: %6.2f\n", data.left.slope, data.left.offset);
topX = (float)m_calibData[CALIB_UPPER_RIGHT_INDEX].x;
bottomX = (float)m_calibData[CALIB_LOWER_RIGHT_INDEX].x;
topY = (float)m_calibData[CALIB_UPPER_RIGHT_INDEX].y;
bottomY = (float)m_calibData[CALIB_LOWER_RIGHT_INDEX].y;
data.right.slope = (bottomX - topX) / (bottomY - topY);
data.right.offset = topX - topY * data.right.slope;
twminfo("Right slope: %6.2f offset: %6.2f\n", data.right.slope, data.right.offset);
// Y lines:
//
// y2 = slope*x1 + offset
//
// slope = (rightX - topX) / (rightY - leftY)
// offset = (topX - leftY * slope)
float leftX = (float)m_calibData[CALIB_UPPER_LEFT_INDEX].x;
float rightX = (float)m_calibData[CALIB_UPPER_RIGHT_INDEX].x;
float leftY = (float)m_calibData[CALIB_UPPER_LEFT_INDEX].y;
float rightY = (float)m_calibData[CALIB_UPPER_RIGHT_INDEX].y;
data.top.slope = (rightY - leftY) / (rightX - leftX);
data.top.offset = leftY - leftX * data.top.slope;
twminfo("Top slope: %6.2f offset: %6.2f\n", data.top.slope, data.top.offset);
leftX = (float)m_calibData[CALIB_LOWER_LEFT_INDEX].x;
rightX = (float)m_calibData[CALIB_LOWER_RIGHT_INDEX].x;
leftY = (float)m_calibData[CALIB_LOWER_LEFT_INDEX].y;
rightY = (float)m_calibData[CALIB_LOWER_RIGHT_INDEX].y;
data.bottom.slope = (rightY - leftY) / (rightX - leftX);
data.bottom.offset = leftY - leftX * data.bottom.slope;
twminfo("Bottom slope: %6.2f offset: %6.2f\n", data.bottom.slope, data.bottom.offset);
// Save also the calibration screen positions
data.leftX = CALIBRATION_LEFTX;
data.rightX = CALIBRATION_RIGHTX;
data.topY = CALIBRATION_TOPY;
data.bottomY = CALIBRATION_BOTTOMY;
#else
// Calculate the calibration parameters
//
// (scaledX - LEFTX) / (rawX - leftX) = (RIGHTX - LEFTX) / (rightX - leftX)
// scaledX = (rawX - leftX) * (RIGHTX - LEFTX) / (rightX - leftX) + LEFTX
// = rawX * xSlope + (LEFTX - leftX * xSlope)
// = rawX * xSlope + xOffset
//
// where:
// xSlope = (RIGHTX - LEFTX) / (rightX - leftX)
// xOffset = (LEFTX - leftX * xSlope)
b16_t leftX = (m_calibData[CALIB_UPPER_LEFT_INDEX].x +
m_calibData[CALIB_LOWER_LEFT_INDEX].x) << 15;
b16_t rightX = (m_calibData[CALIB_UPPER_RIGHT_INDEX].x +
m_calibData[CALIB_LOWER_RIGHT_INDEX].x) << 15;
data.xSlope = b16divb16(itob16(CALIBRATION_RIGHTX - CALIBRATION_LEFTX), (rightX - leftX));
data.xOffset = itob16(CALIBRATION_LEFTX) - b16mulb16(leftX, data.xSlope);
twminfo("New xSlope: %08x xOffset: %08x\n", data.xSlope, data.xOffset);
// Similarly for Y
//
// (scaledY - TOPY) / (rawY - topY) = (BOTTOMY - TOPY) / (bottomY - topY)
// scaledY = (rawY - topY) * (BOTTOMY - TOPY) / (bottomY - topY) + TOPY
// = rawY * ySlope + (TOPY - topY * ySlope)
// = rawY * ySlope + yOffset
//
// where:
// ySlope = (BOTTOMY - TOPY) / (bottomY - topY)
// yOffset = (TOPY - topY * ySlope)
b16_t topY = (m_calibData[CALIB_UPPER_LEFT_INDEX].y +
m_calibData[CALIB_UPPER_RIGHT_INDEX].y) << 15;
b16_t bottomY = (m_calibData[CALIB_LOWER_LEFT_INDEX].y +
m_calibData[CALIB_LOWER_RIGHT_INDEX].y) << 15;
data.ySlope = b16divb16(itob16(CALIBRATION_BOTTOMY - CALIBRATION_TOPY), (bottomY - topY));
data.yOffset = itob16(CALIBRATION_TOPY) - b16mulb16(topY, data.ySlope);
twminfo("New ySlope: %08x yOffset: %08x\n", data.ySlope, data.yOffset);
#endif
return true;
}
/////////////////////////////////////////////////////////////////////////////
// CCalibrationFactory Method Implementations
/////////////////////////////////////////////////////////////////////////////
/**
* CCalibrationFactory Initializer. Performs parts of the instance
* construction that may fail. In this implementation, it will
* initialize the NSH library and register an menu item in the
* Main Menu.
*/
bool CCalibrationFactory::initialize(FAR CTwm4Nx *twm4nx)
{
// Save the session instance
m_twm4nx = twm4nx;
// Register an entry with the Main menu. When selected, this will
// create a new instance of the touchscreen calibration.
FAR CMainMenu *cmain = twm4nx->getMainMenu();
bool success = cmain->addApplication(this);
#ifdef CONFIG_TW4NX_STARTUP_CALIB
if (success)
{
// Show the calibration thread at start-up (and later if selected
// from the Main Menu)
startFunction();
}
#endif
return success;
}
/**
* Handle CCalibrationFactory events. This overrides a method from
* CTwm4NXEvent
*
* @param eventmsg. The received NxWidget WINDOW event message.
* @return True if the message was properly handled. false is
* return on any failure.
*/
bool CCalibrationFactory::event(FAR struct SEventMsg *eventmsg)
{
bool success = true;
switch (eventmsg->eventID)
{
case EVENT_CALIB_START: // Main menu selection
startFunction(); // Create a new CCalibration instance
break;
default:
success = false;
break;
}
return success;
}
/**
* Create and start a new instance of CCalibration.
*/
bool CCalibrationFactory::startFunction(void)
{
// Instantiate the Nxterm application, providing only the session session
// instance to the constructor
CCalibration *calib = new CCalibration(m_twm4nx);
if (!calib)
{
twmerr("ERROR: Failed to instantiate CCalibration\n");
return false;
}
// Initialize the Calibration application
if (!calib->initialize())
{
twmerr("ERROR: Failed to initialize CCalibration instance\n");
delete calib;
return false;
}
// Start the Calibration application instance
if (!calib->run())
{
twmerr("ERROR: Failed to start the Calibration application\n");
delete calib;
return false;
}
return true;
}