Device: Touch Screen - fdechaumont/micecraft GitHub Wiki
The touchscreen is a device that can display anything and handle touch events by animals. Images can be displayed in grid layouts, at specific XY coordinates or moving.
** PHOTO **
[!NOTE] This device is design for both Mice and Rats thanks to different operational modes.
- Documentation
- Examples
- Blueprint
- Manage Alarms
- Auto-Reconnect
Events Handling
When an animal (or user) touches the screen, the TouchScreen fires a DeviceEvent. The event.description string indicates the type of touch:
- Grid-based image
- exact string: "symbol touched" + data
- data: (image_name, image_id, grid_x, grid_y, touch_x, touch_y)
- XY-based image
- exact string: "symbol xy touched" + data
- data: (image_name, image_id, image_center_x, image_center_y, touch_x, touch_y)
- Missed touch
- exact string: "missed" + data
- data: (touch_x, touch_y)
- Device Error
- exact string: "traceback"
Where image_id is the id of the image in the device memory, grid_x and grid_y are the grid coordinates of the image, image_center_x and image_center_y are the coordinates of the center of the image touched, and touch_x and touch_y are the coordinates of the touch.
Image Configuration
- Grid-based image
- method: setImage(id, x, y)
- places a predefined image ID at a specific grid coordinate (x, y)
- XY based image
- method: setXYImage(name, id, centerX, centerY, rotation, scale)
- places an image on precise centered pixel coordinates (centerX, centerY) with rotation and scaling options
- Remove all images (black screen)
- method: clear()
- remove all displayed images
- Remove grid-based image
- method: removeImage(x, y)
- removes the image at the specified grid coordinate
- Remove XY-based image
- removeXYImage(name)
- removes a specific XY image by its unique name
[!TIP] The screen size is 1920 x 1080 pixels.
Blueprints:
[!NOTE] Will be released on publication
Example:
In this example, we create a touchscreen, and we listen to its events.
[!IMPORTANT] You need to check the COM port where the device is connected.
from micecraft.soft.device_event.DeviceEvent import DeviceEvent
from micecraft.devices.touchscreen.TouchScreen import TouchScreen
# 1. Define a listener for touch events
def my_listener(event: DeviceEvent) -> None:
if "symbol xy touched" in event.description:
img_name, img_id, center_x, center_y, touch_x, touch_y = event.data
name_info = f"Touched {img_name} image ID: {img_id}"
center_info = f"Image center: ({center_x}, {center_y})"
touch_info = f"Touch point: ({touch_x}, {touch_y})"
print(f"{name_info}, {center_info}, {touch_info}")
if __name__ == "__main__":
# 2. Initialize the TouchScreen and add the listener
ts = TouchScreen(comPort="COM39")
ts.addDeviceListener(my_listener)
# 3. Clear screen
ts.clear()
# 4. Display images on left and right
center_x = 1920 / 2
center_y = 750
half_size = 400
ts.setXYImage(
name="example_left_image",
id=0,
centerX=center_x - half_size,
centerY=center_y,
rotation=0,
scale=1,
)
ts.setXYImage(
name="example_right_image",
id=1,
centerX=center_x + half_size,
centerY=center_y,
rotation=0,
scale=1,
)
# 5. Shutdown the TouchScreen after testing
input("Press enter to stop the test")
ts.shutdown()
Features
Configuration & Modes
- setConfig(nbCols, nbRows, imageSize): Defines the grid layout and the size of the images displayed within the grid.
- setMouseMode() / setRatMode(): Configures animal mode of the touchscreen.
- setNormalMode(): Disables any special rotation of touch coordinates.
- setTransparency(transparency): Sets the transparency level (0.0 to 1.0) for displayed images.
- ping(): Sends a heartbeat to the device to check connectivity.
- showCalibration(bool): Toggles the visibility of calibration points and pointers on the hardware device. addDeviceListener(listener): Registers a callback function that receives DeviceEvent objects when the screen is touched or a system event occurs.
[!TIP] If the TouchScreen display red lines on the screen, they are calibrations lines. To remove them, use the showCalibration method with False as input.
Standalone example code
To run a an example of the TouchScreen, you need to setup it in a standalone QT app. Full code here:
import sys
import time
import random
import threading
import traceback
from PyQt6 import QtCore
from PyQt6.QtGui import QPainter, QPaintEvent
from PyQt6.QtWidgets import QWidget, QApplication, QPushButton, QVBoxLayout
from micecraft.soft.device_event.DeviceEvent import DeviceEvent
from micecraft.devices.touchscreen.TouchScreen import TouchScreen
class VisualExperiment(QWidget):
"""This class implements a simple visual experiment using PyQt6
and the TouchScreen device."""
refresher = QtCore.pyqtSignal()
def __init__(self, *args, **kwargs) -> None:
"""Initialize the visual experiment."""
super().__init__(*args, **kwargs)
self.shuttingDown = False
print("Starting visual experiment...")
def shutdown(self) -> None:
"""Shut down the visual experiment and release all resources."""
print("Exiting...")
self.shuttingDown = True
self.touchscreen.shutdown()
print("Done.")
def on_refresh_data(self) -> None:
"""Refresh the visual experiment data."""
self.update()
def monitorGUI(self) -> None:
"""Monitor the GUI and emit refresh signals at regular intervals."""
while self.shuttingDown == False:
self.refresher.emit()
time.sleep(0.1)
def listener(self, event: DeviceEvent) -> None:
"""Listener for DeviceEvent from all devices."""
if "symbol xy touched" in event.description:
if not self.touch_enabled:
print("Touch event received but touch is currently disabled.")
return
assert event.data is not None
img_name, img_id, center_x, center_y, touch_x, touch_y = event.data
name_info = f"Touched {img_name} image ID: {img_id}"
center_info = f"Image center: ({center_x}, {center_y})"
touch_info = f"Touch point: ({touch_x}, {touch_y})"
print(f"{name_info}, {center_info}, {touch_info}")
self.on_touch(True)
elif "missed" in event.description:
print("Touch event received but no image was touched.")
self.on_touch(False)
else:
print(f"Received event: {event.description}")
def start(self) -> None:
"""Initialize the user interface (UI) and start the visual
experiment."""
# App window
# ----------------
self.resize(400, 400)
self.setWindowTitle("MiceCraft - Lever display test")
# Layout for buttons
layout = QVBoxLayout(self)
self.btn_touch = QPushButton("Simulate Touch (Correct)", self)
self.btn_touch.clicked.connect(self.simulate_touch)
layout.addWidget(self.btn_touch)
self.btn_miss = QPushButton("Simulate Miss", self)
self.btn_miss.clicked.connect(self.simulate_miss)
layout.addWidget(self.btn_miss)
# Visual elements
# ----------------
self.touchscreen = TouchScreen("COM39") # Create a touchscreen
self.touchscreen.addDeviceListener(self.listener)
self.touch_enabled = True
def simulate_touch(self) -> None:
"""Simulate a successful touch event."""
event = DeviceEvent(
deviceType="touchscreen",
deviceObject=self.touchscreen,
description="symbol xy touched ",
data=("simulate", 0, 0, 0, 0, 0),
)
self.listener(event)
def simulate_miss(self) -> None:
"""Simulate a missed touch event."""
event = DeviceEvent(
deviceType="touchscreen",
deviceObject=self.touchscreen,
description="missed",
data=None,
)
self.listener(event)
def on_touch(self, result: bool) -> None:
"""Show the results on the touchscreen for 1 second then display a
random image."""
self.touch_enabled = False # Disable touch until next display update
if result:
result_name = "example_correct_image"
result_id = 0
else:
result_name = "example_wrong_image"
result_id = 1
self.touchscreen.clear()
center_x = 1920 / 2
center_y = 750
self.touchscreen.setXYImage(
name=result_name,
id=result_id,
centerX=center_x,
centerY=center_y,
rotation=0,
scale=1,
)
time.sleep(1) # Show result for 1 second
self.random_display()
self.touch_enabled = True # Re-enable touch for next round
def random_display(self) -> None:
"""Display an example image randomly on one side of the screen."""
self.touchscreen.clear()
side = random.choice(["left", "right"])
image_name = f"example_{side}_image"
image_id = 3
center_x = 1920 / 2
center_y = 750
half_size = 400
if side == "left":
img_center_x = center_x - half_size
else:
img_center_x = center_x + half_size
self.touchscreen.setXYImage(
name=image_name,
id=image_id,
centerX=img_center_x,
centerY=center_y,
rotation=0,
scale=1,
)
def paintEvent(self, event: QPaintEvent) -> None:
"""Override the paint event to draw custom visuals on the widget."""
super().paintEvent(event)
painter = QPainter()
painter.begin(self)
# here should be located your custom display code
painter.end()
def excepthook(type_, value, traceback_) -> None:
"""Custom exception hook to print the traceback and exit properly."""
traceback.print_exception(type_, value, traceback_)
QtCore.qFatal("")
def shutdown_handler() -> None:
"""This function is called when the application is about to quit.
It ensures that all programs and devices are properly shutdown."""
visualExperiment.shutdown()
if __name__ == "__main__":
sys.excepthook = excepthook
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(True)
app.aboutToQuit.connect(shutdown_handler)
visualExperiment = VisualExperiment()
visualExperiment.start()
visualExperiment.show()
sys.exit(app.exec())
This provides the following display:
** TODO: SCREENSHOT **
Note that until you connect a touchscreen, you will see a red dot blinking on the component complaining that there is no touchscreen connected.
** TODO: SCREENSHOT **