robot-xbox-controller 0.1.0__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.
robot/xbox_controller.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import os
|
|
3
|
+
import pygame
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from robot.xbox_state import XboxState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class XboxController:
|
|
10
|
+
EVENT_LOOP_DELAY = 0.05
|
|
11
|
+
WAITING_LOOP_DELAY = 1.0
|
|
12
|
+
CLOCK_TICKS = 20
|
|
13
|
+
|
|
14
|
+
BUTTON_A = 0
|
|
15
|
+
BUTTON_B = 1
|
|
16
|
+
BUTTON_X = 2
|
|
17
|
+
BUTTON_Y = 3
|
|
18
|
+
BUTTON_VIEW = 4 # left middle button
|
|
19
|
+
BUTTON_XBOX = 5 # center middle button
|
|
20
|
+
BUTTON_SHARE = 6 # right middle button
|
|
21
|
+
BUTTON_LEFT_JOYSTICK = 7
|
|
22
|
+
BUTTON_RIGHT_JOYSTICK = 8
|
|
23
|
+
BUTTON_LEFT_BUMPER = 9
|
|
24
|
+
BUTTON_RIGHT_BUMPER = 10
|
|
25
|
+
BUTTON_FORWARD = 11
|
|
26
|
+
BUTTON_BACKWARD = 12
|
|
27
|
+
BUTTON_LEFT = 13
|
|
28
|
+
BUTTON_RIGHT = 14
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.connected = False
|
|
32
|
+
self.controller = None
|
|
33
|
+
self.controller_name = None
|
|
34
|
+
self.device_index = None
|
|
35
|
+
self.state = XboxState()
|
|
36
|
+
self.clock = pygame.time.Clock()
|
|
37
|
+
|
|
38
|
+
os.environ["SDL_VIDEODRIVER"] = "dummy"
|
|
39
|
+
|
|
40
|
+
pygame.init()
|
|
41
|
+
pygame.joystick.init()
|
|
42
|
+
|
|
43
|
+
def _connect_event(self, event):
|
|
44
|
+
self.device_index = event.device_index
|
|
45
|
+
self.controller = pygame.joystick.Joystick(self.device_index)
|
|
46
|
+
self.controller_name = self.controller.get_name()
|
|
47
|
+
self.connected = True
|
|
48
|
+
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def connect(self, wait_for_controller_message="Waiting for Xbox Controller"):
|
|
52
|
+
if wait_for_controller_message:
|
|
53
|
+
while True:
|
|
54
|
+
for event in pygame.event.get():
|
|
55
|
+
if event.type == pygame.JOYDEVICEADDED:
|
|
56
|
+
return self._connect_event(event)
|
|
57
|
+
|
|
58
|
+
print(wait_for_controller_message)
|
|
59
|
+
|
|
60
|
+
time.sleep(XboxController.WAITING_LOOP_DELAY)
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def quit_event(cls, event):
|
|
64
|
+
if event.type == pygame.QUIT:
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
if event.type == pygame.JOYBUTTONDOWN:
|
|
68
|
+
if event.button == 15:
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def run(self, run_method, debugging=False):
|
|
74
|
+
running = True
|
|
75
|
+
|
|
76
|
+
while running:
|
|
77
|
+
for event in pygame.event.get():
|
|
78
|
+
if self.quit_event(event):
|
|
79
|
+
return False
|
|
80
|
+
else:
|
|
81
|
+
self.state.event(event, debugging)
|
|
82
|
+
|
|
83
|
+
running = run_method(self)
|
|
84
|
+
|
|
85
|
+
def tick(self, frame_rate):
|
|
86
|
+
self.clock.tick(frame_rate)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def wheel_speeds(cls, x_axis, y_axis, max_speed=1.0):
|
|
90
|
+
"""
|
|
91
|
+
Converts controller axes to differential wheel speeds.
|
|
92
|
+
y_axis: forward/backward (-1 to 1)
|
|
93
|
+
x_axis: left/right (-1 to 1)
|
|
94
|
+
"""
|
|
95
|
+
# Simple mapping:
|
|
96
|
+
# Forward: Y is negative, so invert it
|
|
97
|
+
# Turn: X is positive to the right
|
|
98
|
+
|
|
99
|
+
speed = -y_axis
|
|
100
|
+
turn = x_axis
|
|
101
|
+
|
|
102
|
+
# Calculate speeds
|
|
103
|
+
left_speed = speed + turn
|
|
104
|
+
right_speed = speed - turn
|
|
105
|
+
|
|
106
|
+
# Normalize/Clamp values to -1.0 to 1.0
|
|
107
|
+
left_speed = max(-1.0, min(1.0, left_speed)) * max_speed
|
|
108
|
+
right_speed = max(-1.0, min(1.0, right_speed)) * max_speed
|
|
109
|
+
|
|
110
|
+
return round(left_speed, 2), round(right_speed, 2)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def wheel_speed_and_angle(cls, x_axis, y_axis, max_speed=1.0):
|
|
114
|
+
if abs(x_axis) < 0.25:
|
|
115
|
+
x_axis = 0.0
|
|
116
|
+
if abs(y_axis) < 0.25:
|
|
117
|
+
y_axis = 0.0
|
|
118
|
+
|
|
119
|
+
speed = math.sqrt(x_axis**2 + y_axis**2)
|
|
120
|
+
|
|
121
|
+
if speed > 1.0:
|
|
122
|
+
speed = 1.0
|
|
123
|
+
|
|
124
|
+
angle_rad = math.atan2(-y_axis, x_axis)
|
|
125
|
+
angle_deg = math.degrees(angle_rad)
|
|
126
|
+
|
|
127
|
+
# if angle_deg < 0:
|
|
128
|
+
# angle_deg += 360
|
|
129
|
+
|
|
130
|
+
# print("Axis:", x_axis, y_axis, " speed:", speed, " angle", angle_deg)
|
|
131
|
+
return round(speed, 2), round(angle_deg, 2)
|
robot/xbox_state.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class XboxState:
|
|
6
|
+
JOYSTICK_DRIFT = 0.18
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.axis_value = [0.0, 0.0, 0.0, 0.0, -1.0, -1.0]
|
|
10
|
+
self.button_down_at = [0] * 16
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def show_value_string(self, value):
|
|
14
|
+
return "{:6.2f}".format(value)
|
|
15
|
+
|
|
16
|
+
def state_string(self, extra_string=None):
|
|
17
|
+
s = ""
|
|
18
|
+
s = s + self.show_value_string(self.left_x())
|
|
19
|
+
s = s + self.show_value_string(self.left_y())
|
|
20
|
+
s = s + self.show_value_string(self.right_x())
|
|
21
|
+
s = s + self.show_value_string(self.right_y())
|
|
22
|
+
s = s + self.show_value_string(self.left_trigger())
|
|
23
|
+
s = s + self.show_value_string(self.right_trigger())
|
|
24
|
+
if extra_string is not None:
|
|
25
|
+
s = s + " " + extra_string
|
|
26
|
+
|
|
27
|
+
for button in range(16):
|
|
28
|
+
down_seconds = self.button_down_seconds(button)
|
|
29
|
+
|
|
30
|
+
if down_seconds > 0:
|
|
31
|
+
s += f" button{button:3d} down: {down_seconds:4.2f} seconds"
|
|
32
|
+
|
|
33
|
+
return s
|
|
34
|
+
|
|
35
|
+
def print_state_string(self, extra_string=None):
|
|
36
|
+
print(self.state_string(extra_string))
|
|
37
|
+
|
|
38
|
+
def event(self, event, debugging=False):
|
|
39
|
+
if event.type == pygame.JOYBUTTONUP:
|
|
40
|
+
self.button_down_at[event.button] = 0
|
|
41
|
+
elif event.type == pygame.JOYBUTTONDOWN:
|
|
42
|
+
self.button_down_at[event.button] = time.perf_counter()
|
|
43
|
+
elif event.type == pygame.JOYAXISMOTION:
|
|
44
|
+
value = event.value
|
|
45
|
+
|
|
46
|
+
if abs(value) < XboxState.JOYSTICK_DRIFT:
|
|
47
|
+
value = 0.0
|
|
48
|
+
|
|
49
|
+
self.axis_value[event.axis] = round(value, 2)
|
|
50
|
+
else:
|
|
51
|
+
print("Unprocessed event:", event)
|
|
52
|
+
|
|
53
|
+
if debugging:
|
|
54
|
+
print(self.state_string())
|
|
55
|
+
|
|
56
|
+
def left_x(self):
|
|
57
|
+
return self.axis_value[0]
|
|
58
|
+
|
|
59
|
+
def left_y(self):
|
|
60
|
+
return self.axis_value[1]
|
|
61
|
+
|
|
62
|
+
def left_axis(self):
|
|
63
|
+
return self.left_x(), self.left_y()
|
|
64
|
+
|
|
65
|
+
def right_x(self):
|
|
66
|
+
return self.axis_value[2]
|
|
67
|
+
|
|
68
|
+
def right_y(self):
|
|
69
|
+
return self.axis_value[3]
|
|
70
|
+
|
|
71
|
+
def right_axis(self):
|
|
72
|
+
return self.right_x(), self.right_y()
|
|
73
|
+
|
|
74
|
+
def adjusted_trigger_value(self, value):
|
|
75
|
+
return round((value + 1.0) / 2.0, 2)
|
|
76
|
+
|
|
77
|
+
def left_trigger(self):
|
|
78
|
+
return self.adjusted_trigger_value(self.axis_value[4])
|
|
79
|
+
|
|
80
|
+
def right_trigger(self):
|
|
81
|
+
return self.adjusted_trigger_value(self.axis_value[5])
|
|
82
|
+
|
|
83
|
+
def left_trigger_raw(self):
|
|
84
|
+
return self.axis_value[4]
|
|
85
|
+
|
|
86
|
+
def right_trigger_raw(self):
|
|
87
|
+
return self.axis_value[5]
|
|
88
|
+
|
|
89
|
+
def button_down_seconds(self, button):
|
|
90
|
+
if self.button_down_at[button] <= 0:
|
|
91
|
+
return 0
|
|
92
|
+
|
|
93
|
+
return time.perf_counter() - self.button_down_at[button]
|
|
94
|
+
|
|
95
|
+
def button_down_milliseconds(self, button):
|
|
96
|
+
return int(self.button_down_seconds(button) * 1000.0)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robot-xbox-controller
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library for xbox controllers.
|
|
5
|
+
Project-URL: Documentation, https://github.com/fmorton/robot-python-xbox-controller#readme
|
|
6
|
+
Project-URL: Issues, https://github.com/fmorton/robot-python-xbox-controller/issues
|
|
7
|
+
Project-URL: Source, https://github.com/fmorton/robot-python-xbox-controller
|
|
8
|
+
Author-email: Frank Morton <fmorton@mac.com>
|
|
9
|
+
License-Expression: LGPL-3.0-only
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Requires-Dist: pygame
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: black; extra == 'dev'
|
|
24
|
+
Requires-Dist: flake8; extra == 'dev'
|
|
25
|
+
Requires-Dist: pylint; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-mock; extra == 'dev'
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: flake8-pyproject; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest-mock; extra == 'test'
|
|
32
|
+
Requires-Dist: pytest>=8.3; extra == 'test'
|
|
33
|
+
Description-Content-Type: text/x-rst
|
|
34
|
+
|
|
35
|
+
========
|
|
36
|
+
Overview
|
|
37
|
+
========
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
.. start-badges
|
|
41
|
+
|
|
42
|
+
.. list-table::
|
|
43
|
+
:stub-columns: 1
|
|
44
|
+
|
|
45
|
+
* - docs
|
|
46
|
+
- |docs|
|
|
47
|
+
* - package
|
|
48
|
+
- | |version| |wheel| |supported-versions|
|
|
49
|
+
.. |docs| image:: https://readthedocs.org/projects/robot-python-xbox-controller/badge/?style=flat
|
|
50
|
+
:target: https://robot-xbox-controller.readthedocs.io/
|
|
51
|
+
:alt: Documentation Status
|
|
52
|
+
|
|
53
|
+
.. |github-actions| image:: https://github.com/fmorton/robot-python-xbox-controller/actions/workflows/github-actions.yml/badge.svg
|
|
54
|
+
:alt: GitHub Actions Build Status
|
|
55
|
+
:target: https://github.com/fmorton/robot-python-xbox-controller/actions
|
|
56
|
+
|
|
57
|
+
.. |codecov| image:: https://codecov.io/gh/fmorton/robot-xbox-controller/branch/main/graphs/badge.svg?branch=main
|
|
58
|
+
:alt: Coverage Status
|
|
59
|
+
:target: https://codecov.io/github/fmorton/robot-xbox-controller
|
|
60
|
+
|
|
61
|
+
.. |version| image:: https://img.shields.io/pypi/v/robot-xbox-controller.svg
|
|
62
|
+
:alt: PyPI Package latest release
|
|
63
|
+
:target: https://pypi.org/project/robot-xbox-controller
|
|
64
|
+
|
|
65
|
+
.. |wheel| image:: https://img.shields.io/pypi/wheel/robot-xbox-controller.svg
|
|
66
|
+
:alt: PyPI Wheel
|
|
67
|
+
:target: https://pypi.org/project/robot-xbox-controller
|
|
68
|
+
|
|
69
|
+
.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/robot-xbox-controller.svg
|
|
70
|
+
:alt: Supported versions
|
|
71
|
+
:target: https://pypi.org/project/robot-xbox-controller
|
|
72
|
+
|
|
73
|
+
.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/robot-xbox-controller.svg
|
|
74
|
+
:alt: Supported implementations
|
|
75
|
+
:target: https://pypi.org/project/robot-xbox-controller
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
.. end-badges
|
|
79
|
+
|
|
80
|
+
Library for xbox controllers.
|
|
81
|
+
|
|
82
|
+
* Free software: MIT License
|
|
83
|
+
|
|
84
|
+
Installation
|
|
85
|
+
============
|
|
86
|
+
|
|
87
|
+
::
|
|
88
|
+
|
|
89
|
+
pip install robot-xbox-controller
|
|
90
|
+
|
|
91
|
+
You can also install the in-development version with::
|
|
92
|
+
|
|
93
|
+
pip install https://github.com/fmorton/robot-xbox-controller/archive/main.zip
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
Xbox Controller Example
|
|
98
|
+
=======================
|
|
99
|
+
|
|
100
|
+
.. code-block:: python
|
|
101
|
+
|
|
102
|
+
import pygame
|
|
103
|
+
|
|
104
|
+
from robot.xbox_controller import XboxController
|
|
105
|
+
|
|
106
|
+
controller = XboxController().connect()
|
|
107
|
+
|
|
108
|
+
print("Controller connected")
|
|
109
|
+
|
|
110
|
+
running = True
|
|
111
|
+
|
|
112
|
+
while running:
|
|
113
|
+
for event in pygame.event.get():
|
|
114
|
+
if event.type == pygame.QUIT:
|
|
115
|
+
running = False
|
|
116
|
+
elif event.type == pygame.JOYAXISMOTION:
|
|
117
|
+
print(f"Controller axis {event.axis} value {event.value}")
|
|
118
|
+
elif event.type == pygame.JOYBUTTONDOWN:
|
|
119
|
+
print(f"Button {event.button} pressed on controller {event.joy}")
|
|
120
|
+
|
|
121
|
+
if event.button == XboxController.BUTTON_XBOX:
|
|
122
|
+
running = False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
Xbox Controller Example Using run() and the Controller State
|
|
126
|
+
============================================================
|
|
127
|
+
|
|
128
|
+
The run() loop maintains the state of the controllers and all buttons for processing by your supplied method, "robot" in the example below.
|
|
129
|
+
|
|
130
|
+
.. code-block:: python
|
|
131
|
+
|
|
132
|
+
from robot.xbox_controller import XboxController
|
|
133
|
+
from time import sleep
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def robot(controller):
|
|
137
|
+
print("runner", controller.state.state_string())
|
|
138
|
+
|
|
139
|
+
if controller.state.button_down_milliseconds(XboxController.BUTTON_XBOX):
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
sleep(XboxController.EVENT_LOOP_DELAY)
|
|
143
|
+
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
controller = XboxController().connect()
|
|
148
|
+
|
|
149
|
+
controller.run(robot)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
The controller.state has the following methods to retrieve the selections.
|
|
154
|
+
|
|
155
|
+
.. code-block:: python
|
|
156
|
+
|
|
157
|
+
controller.state.left_x() # returns x value of left controller (-1 means max forward/1 max backward)
|
|
158
|
+
controller.state.left_y() # returns y value of left controller (-1 means max left/1 max right)
|
|
159
|
+
controller.state.left_axis() # returns left_x, left_y
|
|
160
|
+
|
|
161
|
+
controller.state.right_x() # returns x value of right controller (-1 means max forward/1 max backward)
|
|
162
|
+
controller.state.right_y() # returns y value of right controller (-1 means max left/1 max right)
|
|
163
|
+
controller.state.right_axis() # returns right_x, right_y
|
|
164
|
+
|
|
165
|
+
controller.state.left_trigger() # returns left trigger value (0 to 1 if fully pressed)
|
|
166
|
+
controller.state.right_trigger() # returns right trigger value (0 to 1 if fully pressed)
|
|
167
|
+
|
|
168
|
+
controller.state.left_trigger_raw() # returns left trigger unadjusted value (-1 to 1 if fully pressed)
|
|
169
|
+
controller.state.right_trigger_raw() # returns right trigger unadjusted value (-1 to 1 if fully pressed)
|
|
170
|
+
|
|
171
|
+
controller.state.button_down_seconds(button) # how many seconds "button" has been pressed
|
|
172
|
+
controller.state.button_down_milliseconds(button) # how many milliseconds "button" has been pressed
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
The following button constant values are available in XboxController:
|
|
177
|
+
|
|
178
|
+
.. code-block:: python
|
|
179
|
+
|
|
180
|
+
BUTTON_A = 0
|
|
181
|
+
BUTTON_B = 1
|
|
182
|
+
BUTTON_X = 2
|
|
183
|
+
BUTTON_Y = 3
|
|
184
|
+
BUTTON_VIEW = 4 # left middle button
|
|
185
|
+
BUTTON_XBOX = 5 # center middle button
|
|
186
|
+
BUTTON_SHARE = 6 # right middle button
|
|
187
|
+
BUTTON_LEFT_JOYSTICK = 7
|
|
188
|
+
BUTTON_RIGHT_JOYSTICK = 8
|
|
189
|
+
BUTTON_LEFT_BUMPER = 9
|
|
190
|
+
BUTTON_RIGHT_BUMPER = 10
|
|
191
|
+
BUTTON_FORWARD = 11
|
|
192
|
+
BUTTON_BACKWARD = 12
|
|
193
|
+
BUTTON_LEFT = 13
|
|
194
|
+
BUTTON_RIGHT = 14
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
Testing
|
|
199
|
+
=======
|
|
200
|
+
|
|
201
|
+
To run all the tests run::
|
|
202
|
+
|
|
203
|
+
pytest -s
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
robot/xbox_controller.py,sha256=nyQVqwWOKkyhaUNx5bYcUsxHaWBt5jxAqlNOTj-aPe8,3599
|
|
2
|
+
robot/xbox_state.py,sha256=y93Akl2pAMnyO3DtejTKeKW9PyY-Yw1GVgE2pDC2UQE,2764
|
|
3
|
+
robot_xbox_controller-0.1.0.dist-info/METADATA,sha256=BOXdPtrDJPdyccicdzH1cbRpcU3s-J0eYs5n6qSeiMM,6453
|
|
4
|
+
robot_xbox_controller-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
5
|
+
robot_xbox_controller-0.1.0.dist-info/licenses/LICENSE,sha256=jg4vPk0w0vVVIE12vUtvLN-8x8yrxoS5z0_WwirTHGo,1092
|
|
6
|
+
robot_xbox_controller-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Frank Morton (fmorton@base2inc.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|