FabGL
ESP32 Display Controller and Graphics Library
mouse.cpp
1 /*
2  Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3  Copyright (c) 2019-2020 Fabrizio Di Vittorio.
4  All rights reserved.
5 
6  This file is part of FabGL Library.
7 
8  FabGL is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 3 of the License, or
11  (at your option) any later version.
12 
13  FabGL is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with FabGL. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 
23 #include "freertos/FreeRTOS.h"
24 
25 #include "mouse.h"
27 #include "displaycontroller.h"
28 
29 
30 
31 
32 
33 
34 namespace fabgl {
35 
36 
37 bool Mouse::s_quickCheckHardware = false;
38 
39 
40 Mouse::Mouse()
41  : m_mouseAvailable(false),
42  m_mouseType(LegacyMouse),
43  m_prevDeltaTime(0),
44  m_movementAcceleration(180),
45  m_wheelAcceleration(60000),
46  m_absoluteUpdateTimer(nullptr),
47  m_absoluteQueue(nullptr),
48  m_updateDisplayController(nullptr),
49  m_uiApp(nullptr)
50 {
51 }
52 
53 
54 Mouse::~Mouse()
55 {
57 }
58 
59 
60 void Mouse::begin(int PS2Port)
61 {
62  if (s_quickCheckHardware)
63  PS2Device::quickCheckHardware();
64  PS2Device::begin(PS2Port);
65  reset();
66 }
67 
68 
69 void Mouse::begin(gpio_num_t clkGPIO, gpio_num_t dataGPIO)
70 {
72  PS2->begin(clkGPIO, dataGPIO);
73  PS2->setMouse(this);
74  begin(0);
75 }
76 
77 
79 {
80  if (s_quickCheckHardware) {
81  m_mouseAvailable = send_cmdReset();
82  } else {
83  // tries up to three times for mouse reset
84  for (int i = 0; i < 3; ++i) {
85  m_mouseAvailable = send_cmdReset();
86  if (m_mouseAvailable)
87  break;
88  vTaskDelay(500 / portTICK_PERIOD_MS);
89  }
90  }
91 
92  // negotiate compatibility and default parameters
93  if (m_mouseAvailable) {
94  // try Intellimouse (three buttons + scroll wheel, 4 bytes packet)
95  if (send_cmdSetSampleRate(200) && send_cmdSetSampleRate(100) && send_cmdSetSampleRate(80) && identify() == PS2DeviceType::MouseWithScrollWheel) {
96  // Intellimouse ok!
97  m_mouseType = Intellimouse;
98  }
99 
100  setSampleRate(60);
101  }
102 
103  return m_mouseAvailable;
104 }
105 
106 
107 int Mouse::getPacketSize()
108 {
109  return (m_mouseType == Intellimouse ? 4 : 3);
110 }
111 
112 
114 {
115  return dataAvailable() / getPacketSize();
116 }
117 
118 
119 bool Mouse::getNextDelta(MouseDelta * delta, int timeOutMS, bool requestResendOnTimeOut)
120 {
121  // receive packet
122  int packetSize = getPacketSize();
123  int rcv[packetSize];
124  for (int i = 0; i < packetSize; ++i) {
125  while (true) {
126  rcv[i] = getData(timeOutMS);
127  if (parityError()) {
128  return false;
129  }
130  if (rcv[i] == -1 && requestResendOnTimeOut) {
131  requestToResendLastByte();
132  continue;
133  }
134  break;
135  }
136  if (rcv[i] < 0)
137  return false; // timeout
138  }
139 
140  // the unique way we have to check a packet is disaligned: the bit 4 of first byte must be always 1
141  if ((rcv[0] & 8) == 0) {
142  PS2Controller::instance()->warmInit();
143  return false;
144  }
145 
146  m_prevStatus = m_status;
147 
148  // decode packet
149  m_status.buttons.left = (rcv[0] & 0x01 ? 1 : 0);
150  m_status.buttons.middle = (rcv[0] & 0x04 ? 1 : 0);
151  m_status.buttons.right = (rcv[0] & 0x02 ? 1 : 0);
152  if (delta) {
153  delta->deltaX = (int16_t)(rcv[0] & 0x10 ? 0xFF00 | rcv[1] : rcv[1]);
154  delta->deltaY = (int16_t)(rcv[0] & 0x20 ? 0xFF00 | rcv[2] : rcv[2]);
155  delta->deltaZ = (int8_t)(packetSize > 3 ? rcv[3] : 0);
156  delta->overflowX = (rcv[0] & 0x40 ? 1 : 0);
157  delta->overflowY = (rcv[0] & 0x80 ? 1 : 0);
158  delta->buttons = m_status.buttons;
159  }
160 
161  return true;
162 }
163 
164 
165 void Mouse::setupAbsolutePositioner(int width, int height, bool createAbsolutePositionsQueue, BitmappedDisplayController * updateDisplayController, uiApp * app)
166 {
167  m_area = Size(width, height);
168  m_status.X = width >> 1;
169  m_status.Y = height >> 1;
170  m_status.wheelDelta = 0;
171  m_status.buttons.left = 0;
172  m_status.buttons.middle = 0;
173  m_status.buttons.right = 0;
174  m_prevStatus = m_status;
175 
176  m_updateDisplayController = updateDisplayController;
177 
178  m_uiApp = app;
179 
180  if (createAbsolutePositionsQueue && m_absoluteQueue == nullptr) {
181  m_absoluteQueue = xQueueCreate(FABGLIB_MOUSE_EVENTS_QUEUE_SIZE, sizeof(MouseStatus));
182  }
183 
184  if (m_updateDisplayController) {
185  // setup initial position
186  m_updateDisplayController->setMouseCursorPos(m_status.X, m_status.Y);
187  }
188 
189  if ((m_updateDisplayController || createAbsolutePositionsQueue || m_uiApp) && m_absoluteUpdateTimer == nullptr) {
190  // create and start the timer
191  m_absoluteUpdateTimer = xTimerCreate("", pdMS_TO_TICKS(10), pdTRUE, this, absoluteUpdateTimerFunc);
192  xTimerStart(m_absoluteUpdateTimer, portMAX_DELAY);
193  }
194 }
195 
196 
198 {
199  m_updateDisplayController = nullptr;
200  m_uiApp = nullptr;
201  if (m_absoluteQueue) {
202  vQueueDelete(m_absoluteQueue);
203  m_absoluteQueue = nullptr;
204  }
205  if (m_absoluteUpdateTimer) {
206  xTimerDelete(m_absoluteUpdateTimer, portMAX_DELAY);
207  m_absoluteUpdateTimer = nullptr;
208  }
209 }
210 
211 
213 {
214  const int maxDeltaTimeUS = 500000; // after 0.5s doesn't consider acceleration
215 
216  int dx = delta->deltaX;
217  int dy = delta->deltaY;
218  int dz = delta->deltaZ;
219 
220  int64_t now = esp_timer_get_time();
221  int deltaTime = now - m_prevDeltaTime; // time in microseconds
222 
223  if (deltaTime < maxDeltaTimeUS) {
224 
225  // calcualte movement acceleration
226  if (dx != 0 || dy != 0) {
227  int deltaDist = isqrt(dx * dx + dy * dy); // distance in mouse points
228  float vel = (float)deltaDist / deltaTime; // velocity in mousepoints/microsecond
229  float newVel = vel + m_movementAcceleration * vel * vel; // new velocity
230  int newDeltaDist = newVel * deltaTime; // new distance
231  dx = dx * newDeltaDist / deltaDist;
232  dy = dy * newDeltaDist / deltaDist;
233  }
234 
235  // calculate wheel acceleration
236  if (dz != 0) {
237  int deltaDist = abs(dz); // distance in wheel points
238  float vel = (float)deltaDist / deltaTime; // velocity in mousepoints/microsecond
239  float newVel = vel + m_wheelAcceleration * vel * vel; // new velocity
240  int newDeltaDist = newVel * deltaTime; // new distance
241  dz = dz * newDeltaDist / deltaDist;
242  }
243 
244  }
245 
246  m_status.X = tclamp((int)m_status.X + dx, 0, m_area.width - 1);
247  m_status.Y = tclamp((int)m_status.Y - dy, 0, m_area.height - 1);
248  m_status.wheelDelta = dz;
249  m_prevDeltaTime = now;
250 }
251 
252 
253 void Mouse::absoluteUpdateTimerFunc(TimerHandle_t xTimer)
254 {
255  Mouse * mouse = (Mouse*) pvTimerGetTimerID(xTimer);
256  MouseDelta delta;
257  if (mouse->deltaAvailable() && mouse->getNextDelta(&delta, 0, false)) {
258  mouse->updateAbsolutePosition(&delta);
259 
260  // VGA Controller
261  if (mouse->m_updateDisplayController)
262  mouse->m_updateDisplayController->setMouseCursorPos(mouse->m_status.X, mouse->m_status.Y);
263 
264  // queue (if you need availableStatus() or getNextStatus())
265  if (mouse->m_absoluteQueue) {
266  xQueueSend(mouse->m_absoluteQueue, &mouse->m_status, 0);
267  }
268 
269  if (mouse->m_uiApp) {
270  // generate uiApp events
271  if (mouse->m_prevStatus.X != mouse->m_status.X || mouse->m_prevStatus.Y != mouse->m_status.Y) {
272  // X and Y movement: UIEVT_MOUSEMOVE
273  uiEvent evt = uiEvent(nullptr, UIEVT_MOUSEMOVE);
274  evt.params.mouse.status = mouse->m_status;
275  evt.params.mouse.changedButton = 0;
276  mouse->m_uiApp->postEvent(&evt);
277  }
278  if (mouse->m_status.wheelDelta != 0) {
279  // wheel movement: UIEVT_MOUSEWHEEL
280  uiEvent evt = uiEvent(nullptr, UIEVT_MOUSEWHEEL);
281  evt.params.mouse.status = mouse->m_status;
282  evt.params.mouse.changedButton = 0;
283  mouse->m_uiApp->postEvent(&evt);
284  }
285  if (mouse->m_prevStatus.buttons.left != mouse->m_status.buttons.left) {
286  // left button: UIEVT_MOUSEBUTTONDOWN, UIEVT_MOUSEBUTTONUP
287  uiEvent evt = uiEvent(nullptr, mouse->m_status.buttons.left ? UIEVT_MOUSEBUTTONDOWN : UIEVT_MOUSEBUTTONUP);
288  evt.params.mouse.status = mouse->m_status;
289  evt.params.mouse.changedButton = 1;
290  mouse->m_uiApp->postEvent(&evt);
291  }
292  if (mouse->m_prevStatus.buttons.middle != mouse->m_status.buttons.middle) {
293  // middle button: UIEVT_MOUSEBUTTONDOWN, UIEVT_MOUSEBUTTONUP
294  uiEvent evt = uiEvent(nullptr, mouse->m_status.buttons.middle ? UIEVT_MOUSEBUTTONDOWN : UIEVT_MOUSEBUTTONUP);
295  evt.params.mouse.status = mouse->m_status;
296  evt.params.mouse.changedButton = 2;
297  mouse->m_uiApp->postEvent(&evt);
298  }
299  if (mouse->m_prevStatus.buttons.right != mouse->m_status.buttons.right) {
300  // right button: UIEVT_MOUSEBUTTONDOWN, UIEVT_MOUSEBUTTONUP
301  uiEvent evt = uiEvent(nullptr, mouse->m_status.buttons.right ? UIEVT_MOUSEBUTTONDOWN : UIEVT_MOUSEBUTTONUP);
302  evt.params.mouse.status = mouse->m_status;
303  evt.params.mouse.changedButton = 3;
304  mouse->m_uiApp->postEvent(&evt);
305  }
306  }
307 
308  }
309 }
310 
311 
313 {
314  return m_absoluteQueue ? uxQueueMessagesWaiting(m_absoluteQueue) : 0;
315 }
316 
317 
319 {
321  if (m_absoluteQueue)
322  xQueueReceive(m_absoluteQueue, &status, msToTicks(timeOutMS));
323  return status;
324 }
325 
326 
328 {
329  while (getData(0) != -1)
330  ;
331  if (m_absoluteQueue)
332  xQueueReset(m_absoluteQueue);
333 }
334 
335 
336 
337 } // end of namespace
MouseButtons buttons
Definition: fabutils.h:246
uint8_t overflowX
Definition: mouse.h:56
This file contains fabgl::PS2Controller definition.
Represents the whole application base class.
Definition: fabui.h:2561
uint8_t overflowY
Definition: mouse.h:57
bool setSampleRate(int value)
Sets the maximum rate of mouse movements reporting.
Definition: mouse.h:190
This file contains fabgl::BitmappedDisplayController definition.
Represents the base abstract class for bitmapped display controllers.
int16_t deltaY
Definition: mouse.h:53
Describes mouse movement and buttons status.
Definition: mouse.h:51
This file contains fabgl::Mouse definition.
int8_t deltaZ
Definition: mouse.h:54
Describes mouse absolute position, scroll wheel delta and buttons status.
Definition: fabutils.h:242
PS2DeviceType identify()
Identifies the device attached to the PS2 port.
Definition: ps2device.h:75
MouseStatus getNextStatus(int timeOutMS=-1)
Gets the next status from the status queue.
Definition: mouse.cpp:318
void emptyQueue()
Empties the mouse status and events queue.
Definition: mouse.cpp:327
static PS2Controller * instance()
Returns the singleton instance of PS2Controller class.
Definition: canvas.cpp:31
int16_t width
Definition: fabutils.h:177
MouseButtons buttons
Definition: mouse.h:55
void terminateAbsolutePositioner()
Terminates absolute position handler.
Definition: mouse.cpp:197
int16_t height
Definition: fabutils.h:178
bool getNextDelta(MouseDelta *delta, int timeOutMS=-1, bool requestResendOnTimeOut=false)
Gets a mouse movement from the queue.
Definition: mouse.cpp:119
Represents a bidimensional size.
Definition: fabutils.h:176
The PS2 device controller class.
Definition: ps2controller.h:74
void setMouseCursorPos(int X, int Y)
Sets mouse cursor position.
bool postEvent(uiEvent const *event)
Places an event in the event queue and returns without waiting for the receiver to process the event...
Definition: fabui.cpp:519
MouseStatus & status()
Gets or sets current mouse status.
Definition: mouse.h:271
int availableStatus()
Gets the number of available mouse status.
Definition: mouse.cpp:312
void begin(gpio_num_t clkGPIO, gpio_num_t dataGPIO)
Initializes Mouse specifying CLOCK and DATA GPIOs.
Definition: mouse.cpp:69
int16_t deltaX
Definition: mouse.h:52
void updateAbsolutePosition(MouseDelta *delta)
Updates absolute position from the specified mouse delta event.
Definition: mouse.cpp:212
uint8_t height
int deltaAvailable()
Determines the number of mouse movements available in the queue.
Definition: mouse.cpp:113
The PS2 Mouse controller class.
Definition: mouse.h:99
void setupAbsolutePositioner(int width, int height, bool createAbsolutePositionsQueue, BitmappedDisplayController *updateDisplayController=nullptr, uiApp *app=nullptr)
Initializes absolute position handler.
Definition: mouse.cpp:165
bool reset()
Sends a Reset command to the mouse.
Definition: mouse.cpp:78
uint8_t width
void begin(gpio_num_t port0_clkGPIO, gpio_num_t port0_datGPIO, gpio_num_t port1_clkGPIO=GPIO_UNUSED, gpio_num_t port1_datGPIO=GPIO_UNUSED)
Initializes PS2 device controller.