triki-library 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- triki/__init__.py +21 -0
- triki/controller.py +197 -0
- triki/exceptions.py +14 -0
- triki/scanner.py +27 -0
- triki/types.py +7 -0
- triki_library-0.1.dist-info/METADATA +126 -0
- triki_library-0.1.dist-info/RECORD +9 -0
- triki_library-0.1.dist-info/WHEEL +4 -0
- triki_library-0.1.dist-info/licenses/LICENSE +9 -0
triki/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .controller import TRIKIController
|
|
2
|
+
from .scanner import TRIKIScanner
|
|
3
|
+
from .types import TRIKIDevice
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
TRIKIError,
|
|
6
|
+
TRIKIConnectionError,
|
|
7
|
+
TRIKIDeviceNotFoundError,
|
|
8
|
+
TRIKINotConnectedError,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1"
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"TRIKIController",
|
|
15
|
+
"TRIKIScanner",
|
|
16
|
+
"TRIKIDevice",
|
|
17
|
+
"TRIKIError",
|
|
18
|
+
"TRIKIConnectionError",
|
|
19
|
+
"TRIKIDeviceNotFoundError",
|
|
20
|
+
"TRIKINotConnectedError",
|
|
21
|
+
]
|
triki/controller.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import bleak.exc
|
|
2
|
+
from bleak import BleakClient
|
|
3
|
+
|
|
4
|
+
from .exceptions import TRIKIDeviceNotFoundError, TRIKINotConnectedError, TRIKIConnectionError
|
|
5
|
+
|
|
6
|
+
UART_RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
|
|
7
|
+
UART_TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
|
|
8
|
+
BATTERY_LEVEL_UUID = "00002a19-0000-1000-8000-00805f9b34fb"
|
|
9
|
+
LED_CHARACTERISTIC_UUID = "6e400004-b5a3-f393-e0a9-e50e24dcca9e"
|
|
10
|
+
START_STREAM_COMMAND = bytes.fromhex("20 10 00 d0 07 34 00 03")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TRIKIController:
|
|
14
|
+
"""
|
|
15
|
+
Base class for the TRIKI controller
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
address:
|
|
19
|
+
Device Bluetooth address
|
|
20
|
+
"""
|
|
21
|
+
__device_client: BleakClient = None
|
|
22
|
+
__bt_address: str = "AA:BB:CC:DD:EE:FF"
|
|
23
|
+
__battery_level: int = 0
|
|
24
|
+
__sensor_spin_x: int = 0
|
|
25
|
+
__sensor_spin_y: int = 0
|
|
26
|
+
__sensor_turn_z: int = 0
|
|
27
|
+
__sensor_tilt_x: int = 0
|
|
28
|
+
__sensor_tilt_y: int = 0
|
|
29
|
+
__sensor_flip_z: int = 0
|
|
30
|
+
__button_pressed: bool = False
|
|
31
|
+
|
|
32
|
+
def __init__(self, address: str) -> None:
|
|
33
|
+
self.__bt_address = address
|
|
34
|
+
|
|
35
|
+
def __little_endian(self, a: int, b: int) -> int:
|
|
36
|
+
"""Parse data by using a little endian algorithm"""
|
|
37
|
+
x = a + (b * 256)
|
|
38
|
+
if x > 32767:
|
|
39
|
+
x -= 65536
|
|
40
|
+
return x
|
|
41
|
+
|
|
42
|
+
def __parse_received_data(self, sender, data: bytearray) -> None:
|
|
43
|
+
"""Parse data when received"""
|
|
44
|
+
if len(data) >= 16 and data[0] == 0x22:
|
|
45
|
+
data_sliced = data.hex(" ").split(" ")
|
|
46
|
+
data_decimal = []
|
|
47
|
+
for i in range(len(data_sliced)):
|
|
48
|
+
data_decimal.append(int(data_sliced[i], 16))
|
|
49
|
+
|
|
50
|
+
self.__sensor_spin_x = self.__little_endian(data_decimal[2], data_decimal[3])
|
|
51
|
+
self.__sensor_spin_y = self.__little_endian(data_decimal[4], data_decimal[5])
|
|
52
|
+
self.__sensor_turn_z = self.__little_endian(data_decimal[6], data_decimal[7])
|
|
53
|
+
self.__sensor_tilt_x = self.__little_endian(data_decimal[8], data_decimal[9])
|
|
54
|
+
self.__sensor_tilt_y = self.__little_endian(data_decimal[10], data_decimal[11])
|
|
55
|
+
self.__sensor_flip_z = self.__little_endian(data_decimal[12], data_decimal[13])
|
|
56
|
+
|
|
57
|
+
self.__button_pressed = data_decimal[15] == 1
|
|
58
|
+
|
|
59
|
+
async def connect(self, raise_exception_on_fail: bool = False) -> None:
|
|
60
|
+
"""Connect to the TRIKI controller"""
|
|
61
|
+
self.__device_client = BleakClient(self.__bt_address, timeout=10)
|
|
62
|
+
try:
|
|
63
|
+
await self.__device_client.connect()
|
|
64
|
+
except bleak.exc.BleakDeviceNotFoundError as error:
|
|
65
|
+
if raise_exception_on_fail:
|
|
66
|
+
raise TRIKIDeviceNotFoundError(
|
|
67
|
+
f"TRIKI device with address {self.__bt_address} was not found"
|
|
68
|
+
) from error
|
|
69
|
+
except bleak.exc.BleakError as error:
|
|
70
|
+
if raise_exception_on_fail:
|
|
71
|
+
raise TRIKIConnectionError(
|
|
72
|
+
f"Connection with TRIKI device (at {self.__bt_address}) failed"
|
|
73
|
+
) from error
|
|
74
|
+
|
|
75
|
+
if self.__device_client.is_connected:
|
|
76
|
+
try:
|
|
77
|
+
battery_data = await self.__device_client.read_gatt_char(BATTERY_LEVEL_UUID)
|
|
78
|
+
self.__battery_level = int(battery_data[0])
|
|
79
|
+
|
|
80
|
+
await self.__device_client.start_notify(UART_TX_UUID, self.__parse_received_data)
|
|
81
|
+
|
|
82
|
+
await self.__device_client.write_gatt_char(
|
|
83
|
+
UART_RX_UUID, START_STREAM_COMMAND, response=True
|
|
84
|
+
)
|
|
85
|
+
except bleak.exc.BleakError as error:
|
|
86
|
+
raise TRIKIConnectionError(
|
|
87
|
+
f"Connected to the TRIKI device (at {self.__bt_address}), but initialization failed"
|
|
88
|
+
) from error
|
|
89
|
+
else:
|
|
90
|
+
raise TRIKIConnectionError(
|
|
91
|
+
f"Connection with TRIKI device (at {self.__bt_address}) failed"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async def disconnect(self) -> None:
|
|
95
|
+
"""Disconnect from the TRIKI device"""
|
|
96
|
+
if self.__device_client is not None and self.__device_client.is_connected:
|
|
97
|
+
await self.toggle_led(False)
|
|
98
|
+
await self.__device_client.disconnect()
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def is_connected(self) -> bool:
|
|
102
|
+
"""Check if the TRIKI controller is connected"""
|
|
103
|
+
return self.__device_client is not None and self.__device_client.is_connected
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def battery_level(self) -> int:
|
|
107
|
+
"""Get the TRIKI controller battery level"""
|
|
108
|
+
if not self.is_connected:
|
|
109
|
+
raise TRIKINotConnectedError(
|
|
110
|
+
"Cannot read battery_level, because the TRIKI controller is not connected"
|
|
111
|
+
)
|
|
112
|
+
return self.__battery_level
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def spin_x(self) -> int:
|
|
116
|
+
"""Get the TRIKI controller X Spin value"""
|
|
117
|
+
if not self.is_connected:
|
|
118
|
+
raise TRIKINotConnectedError(
|
|
119
|
+
"Cannot read spin_x, because the TRIKI controller is not connected"
|
|
120
|
+
)
|
|
121
|
+
return self.__sensor_spin_x
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def spin_y(self) -> int:
|
|
125
|
+
"""Get the TRIKI controller Y Spin value"""
|
|
126
|
+
if not self.is_connected:
|
|
127
|
+
raise TRIKINotConnectedError(
|
|
128
|
+
"Cannot read spin_y, because the TRIKI controller is not connected"
|
|
129
|
+
)
|
|
130
|
+
return self.__sensor_spin_y
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def turn_z(self) -> int:
|
|
134
|
+
"""Get the TRIKI controller Z Turn value"""
|
|
135
|
+
if not self.is_connected:
|
|
136
|
+
raise TRIKINotConnectedError(
|
|
137
|
+
"Cannot read turn_z, because the TRIKI controller is not connected"
|
|
138
|
+
)
|
|
139
|
+
return self.__sensor_turn_z
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def tilt_x(self) -> int:
|
|
143
|
+
"""Get the TRIKI controller X Tilt value"""
|
|
144
|
+
if not self.is_connected:
|
|
145
|
+
raise TRIKINotConnectedError(
|
|
146
|
+
"Cannot read tilt_x, because the TRIKI controller is not connected"
|
|
147
|
+
)
|
|
148
|
+
return self.__sensor_tilt_x
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def tilt_y(self) -> int:
|
|
152
|
+
"""Get the TRIKI controller Y Tilt value"""
|
|
153
|
+
if not self.is_connected:
|
|
154
|
+
raise TRIKINotConnectedError(
|
|
155
|
+
"Cannot read tilt_y, because the TRIKI controller is not connected"
|
|
156
|
+
)
|
|
157
|
+
return self.__sensor_tilt_y
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def flip_z(self) -> int:
|
|
161
|
+
"""Get the TRIKI controller Z Flip value"""
|
|
162
|
+
if not self.is_connected:
|
|
163
|
+
raise TRIKINotConnectedError(
|
|
164
|
+
"Cannot read flip_z, because the TRIKI controller is not connected"
|
|
165
|
+
)
|
|
166
|
+
return self.__sensor_flip_z
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def button_pressed(self) -> bool:
|
|
170
|
+
"""Check if the TRIKI controller button is pressed"""
|
|
171
|
+
if not self.is_connected:
|
|
172
|
+
raise TRIKINotConnectedError(
|
|
173
|
+
"Cannot read button_pressed, because the TRIKI controller is not connected"
|
|
174
|
+
)
|
|
175
|
+
return self.__button_pressed
|
|
176
|
+
|
|
177
|
+
async def toggle_led(self, is_on: bool) -> None:
|
|
178
|
+
"""
|
|
179
|
+
Toggle the TRIKI controller built-in LED light
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
is_on:
|
|
183
|
+
Set the LED status
|
|
184
|
+
"""
|
|
185
|
+
if not self.is_connected:
|
|
186
|
+
raise TRIKINotConnectedError(
|
|
187
|
+
"Cannot toggle LED, because the TRIKI controller is not connected"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if is_on:
|
|
191
|
+
bytes_to_send = bytes([0x01])
|
|
192
|
+
else:
|
|
193
|
+
bytes_to_send = bytes([0x00])
|
|
194
|
+
|
|
195
|
+
await self.__device_client.write_gatt_char(
|
|
196
|
+
LED_CHARACTERISTIC_UUID, bytes_to_send, response=True
|
|
197
|
+
)
|
triki/exceptions.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class TRIKIError(Exception):
|
|
2
|
+
"""Base exception for triki errors."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TRIKIConnectionError(TRIKIError):
|
|
6
|
+
"""Raised when connection to a TRIKI device fails."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TRIKIDeviceNotFoundError(TRIKIError):
|
|
10
|
+
"""Raised when a TRIKI device cannot be found."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TRIKINotConnectedError(TRIKIError):
|
|
14
|
+
"""Raised when called operation requires a connected TRIKI device."""
|
triki/scanner.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from bleak import BleakScanner, BLEDevice
|
|
2
|
+
from .types import TRIKIDevice
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TRIKIScanner:
|
|
6
|
+
"""Base class for the TRIKI controller scanner"""
|
|
7
|
+
__scanner = None
|
|
8
|
+
__bleak_devices: list[BLEDevice] = []
|
|
9
|
+
__scanned_devices: list[TRIKIDevice] = []
|
|
10
|
+
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
self.__scanner = BleakScanner()
|
|
13
|
+
|
|
14
|
+
async def scan(self) -> None:
|
|
15
|
+
"""Scan for TRIKI controllers"""
|
|
16
|
+
self.__bleak_devices = await self.__scanner.discover()
|
|
17
|
+
self.__scanned_devices.clear()
|
|
18
|
+
|
|
19
|
+
for device in self.__bleak_devices:
|
|
20
|
+
if device.name is not None and "Triki" in device.name:
|
|
21
|
+
triki_device = TRIKIDevice("Triki " + device.address[-6:], device.address)
|
|
22
|
+
self.__scanned_devices.append(triki_device)
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def scanned_devices(self) -> list[TRIKIDevice]:
|
|
26
|
+
"""Scanned TRIKI controllers"""
|
|
27
|
+
return self.__scanned_devices
|
triki/types.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: triki-library
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: A Python library for integrating the TRIKI motion controller (made by Caps Apps) into your projects.
|
|
5
|
+
Project-URL: Homepage, https://github.com/woofter-wolf/triki-library-python
|
|
6
|
+
Project-URL: Repository, https://github.com/woofter-wolf/triki-library-python
|
|
7
|
+
Project-URL: Issues, https://github.com/woofter-wolf/triki-library-python
|
|
8
|
+
Author: Woofter_Wolf
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ble,bluetooth,controller,triki
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: System :: Hardware
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: bleak>=3.0.2
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# TRIKI Library
|
|
25
|
+
A Python library for the integrating TRIKI motion controller (made by Caps Apps) into your projects.
|
|
26
|
+
|
|
27
|
+
> This library is still in development. Some things may change sligthly.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## What is TRIKI?
|
|
32
|
+
TRIKI is a Bluetooth Low Energy (BLE) motion controller that has the shape and size of a bottle cap. It is produced by Caps Apps (also known as HOPX) and sold by 'Żabka', a convenience store franchise.
|
|
33
|
+
|
|
34
|
+
It was designed to be used in mini-games which are on the store's loyalty app.
|
|
35
|
+
|
|
36
|
+
## Why I made this library
|
|
37
|
+
I wanted to make this library to give everyone a way to integrate this niche controller into their own games, experiments and other Python projects.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
- Scan for TRIKI controllers
|
|
41
|
+
- Connect to TRIKI controller
|
|
42
|
+
- Get battery level
|
|
43
|
+
- Get IMU data from the controller
|
|
44
|
+
- Get state of the built-in button
|
|
45
|
+
- Control built-in LED
|
|
46
|
+
|
|
47
|
+
## Platforms
|
|
48
|
+
| Platform | Does it work? |
|
|
49
|
+
|---------------|---------------------|
|
|
50
|
+
| Windows 10/11 | ✅ Tested |
|
|
51
|
+
| macOS | ❔ Untested |
|
|
52
|
+
| Linux | ⚠️ Partially tested |
|
|
53
|
+
> Linux support needs further testing. Some Arch-based distributions may have issues with receiving data.
|
|
54
|
+
---
|
|
55
|
+
## Installation
|
|
56
|
+
```bash
|
|
57
|
+
pip install triki-library
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
```python
|
|
62
|
+
# Import the library
|
|
63
|
+
import asyncio
|
|
64
|
+
from triki import TRIKIScanner, TRIKIController
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# Scan for TRIKI controllers
|
|
69
|
+
scanner = TRIKIScanner()
|
|
70
|
+
await scanner.scan()
|
|
71
|
+
devices = scanner.scanned_devices # Returned as list[TRIKIDevice]
|
|
72
|
+
|
|
73
|
+
devices[0].name # Get device name
|
|
74
|
+
devices[0].address # Get device BT address
|
|
75
|
+
```
|
|
76
|
+
```python
|
|
77
|
+
# Connect to the controller
|
|
78
|
+
controller = TRIKIController("AA:BB:CC:DD:EE:FF")
|
|
79
|
+
await controller.connect()
|
|
80
|
+
```
|
|
81
|
+
```python
|
|
82
|
+
controller.is_connected # Check if device is connected
|
|
83
|
+
controller.battery_level # Get battery level
|
|
84
|
+
```
|
|
85
|
+
```python
|
|
86
|
+
controller.spin_x # Get X Spin
|
|
87
|
+
controller.spin_y # Get Y Spin
|
|
88
|
+
controller.turn_z # Get Z Turn
|
|
89
|
+
controller.tilt_x # Get X Tilt
|
|
90
|
+
controller.tilt_y # Get Y Tilt
|
|
91
|
+
controller.flip_z # Get Z Flip
|
|
92
|
+
```
|
|
93
|
+
```python
|
|
94
|
+
controller.button_pressed # Check if built-in button is pressed
|
|
95
|
+
```
|
|
96
|
+
```python
|
|
97
|
+
#turn on LED
|
|
98
|
+
await controller.toggle_led(True)
|
|
99
|
+
|
|
100
|
+
#turn off LED
|
|
101
|
+
await controller.toggle_led(False)
|
|
102
|
+
```
|
|
103
|
+
```python
|
|
104
|
+
# Loop
|
|
105
|
+
try:
|
|
106
|
+
while controller.is_connected:
|
|
107
|
+
# Do stuff
|
|
108
|
+
finally:
|
|
109
|
+
await controller.disconnect()
|
|
110
|
+
```
|
|
111
|
+
```python
|
|
112
|
+
# IMPORTANT: disconnect from the controller when you're done with it!
|
|
113
|
+
await controller.disconnect()
|
|
114
|
+
```
|
|
115
|
+
>Examples can be found on the 'examples' directory.
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Credits
|
|
119
|
+
Library made by [Woofter_Wolf](https://woofterwolf.space)
|
|
120
|
+
|
|
121
|
+
Special thanks to [Wojciech "Koksny" Górny](https://koksny.com) for doing the hard work of documenting this device (look here for communication documentation): [Link](https://github.com/koksny/TRIKI-Control)
|
|
122
|
+
|
|
123
|
+
## Copyright
|
|
124
|
+
"TRIKI" and "Żabka" are trademarks of Żabka Polska sp. z o.o. "HOPX" is a trademark of Caps Apps sp. z o.o.
|
|
125
|
+
|
|
126
|
+
This library is an independent, open-source project. It is **NOT** affiliated, endorsed or sponsored by these companies. This library ships with no software, images or any assets belonging to these companies.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
triki/__init__.py,sha256=AHjiD0BA2QFSnSfpgUbRpiB2bBc0I6Th0adQHKLAk4Q,460
|
|
2
|
+
triki/controller.py,sha256=7XH_JM6WffCzyuQuwLoCffbQwIIU2iNFpRwCAutEo1k,7515
|
|
3
|
+
triki/exceptions.py,sha256=wwUwVGIXCudFYMTk1ItNpIiXgGg9WTjlaeCvr7w0ShQ,404
|
|
4
|
+
triki/scanner.py,sha256=UZovNbsSws_bdu6-0Dxfsih_NtbJe2GrufwqYhxtwLw,944
|
|
5
|
+
triki/types.py,sha256=3dcQ_ULegVH5B173x8iqKIg7AlB5n_fxKefoAR83hzU,117
|
|
6
|
+
triki_library-0.1.dist-info/METADATA,sha256=E_h4T_if4z7WKrVGWk3DoJp7iIBpDo3G6xhFpwoTU1c,4106
|
|
7
|
+
triki_library-0.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
8
|
+
triki_library-0.1.dist-info/licenses/LICENSE,sha256=p3f-kD18WHRPEDAoq1ZetdFSIO4XszzcuJ9A6cQRQCo,1083
|
|
9
|
+
triki_library-0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Woofter_Wolf
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|