Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorflavio <fdanesse@gmail.com>2012-06-17 04:29:21 (GMT)
committer flavio <fdanesse@gmail.com>2012-06-17 04:29:21 (GMT)
commitc28946e4af89ef5d1f95dca7876f93dafa859ea8 (patch)
treea53ae24a1ccaf66fdb7139de85f46ac27a09e454
InterfaceHEADmaster
-rw-r--r--.gitignore4
-rw-r--r--Globales.py32
-rw-r--r--Iconos/bluetooth.pngbin0 -> 5632 bytes
-rw-r--r--Iconos/luz.pngbin0 -> 43029 bytes
-rw-r--r--Iconos/motor.pngbin0 -> 38486 bytes
-rw-r--r--Iconos/presion.pngbin0 -> 46407 bytes
-rw-r--r--Iconos/sonido.pngbin0 -> 55607 bytes
-rw-r--r--Iconos/ultrasonido.pngbin0 -> 37731 bytes
-rw-r--r--Iconos/usb.pngbin0 -> 1910 bytes
-rw-r--r--LegoJAM.py38
-rw-r--r--Widgets.py221
-rw-r--r--nxt_plugin/__init__.py0
-rw-r--r--nxt_plugin/ejemplo.py9
-rw-r--r--nxt_plugin/ejemplo2.py14
-rw-r--r--nxt_plugin/icons/nxt-motorsoff.svg108
-rw-r--r--nxt_plugin/icons/nxt-motorson.svg142
-rw-r--r--nxt_plugin/icons/nxt-sensorsoff.svg115
-rw-r--r--nxt_plugin/icons/nxt-sensorson.svg115
-rw-r--r--nxt_plugin/nxt/LICENSE674
-rw-r--r--nxt_plugin/nxt/PKG-INFO189
-rw-r--r--nxt_plugin/nxt/README179
-rw-r--r--nxt_plugin/nxt/__init__.py17
-rw-r--r--nxt_plugin/nxt/bluesock.py79
-rw-r--r--nxt_plugin/nxt/brick.py235
-rw-r--r--nxt_plugin/nxt/direct.py218
-rw-r--r--nxt_plugin/nxt/error.py87
-rw-r--r--nxt_plugin/nxt/fantomsock.py118
-rw-r--r--nxt_plugin/nxt/ipsock.py61
-rw-r--r--nxt_plugin/nxt/lightblueglue.py53
-rw-r--r--nxt_plugin/nxt/locator.py177
-rw-r--r--nxt_plugin/nxt/motcont.py146
-rw-r--r--nxt_plugin/nxt/motor.py431
-rw-r--r--nxt_plugin/nxt/sensor/__init__.py50
-rw-r--r--nxt_plugin/nxt/sensor/analog.py41
-rw-r--r--nxt_plugin/nxt/sensor/common.py67
-rw-r--r--nxt_plugin/nxt/sensor/digital.py229
-rw-r--r--nxt_plugin/nxt/sensor/generic.py158
-rw-r--r--nxt_plugin/nxt/sensor/hitechnic.py611
-rw-r--r--nxt_plugin/nxt/sensor/mindsensors.py815
-rw-r--r--nxt_plugin/nxt/system.py299
-rw-r--r--nxt_plugin/nxt/telegram.py120
-rw-r--r--nxt_plugin/nxt/usbsock.py87
-rw-r--r--nxt_plugin/nxt/utils.py33
-rwxr-xr-xnxt_plugin/nxt_plugin.py580
-rw-r--r--nxt_plugin/prueba1.py15
-rw-r--r--nxt_plugin/usb/ACKNOWLEDGEMENTS37
-rw-r--r--nxt_plugin/usb/LICENSE27
-rw-r--r--nxt_plugin/usb/README78
-rw-r--r--nxt_plugin/usb/ReleaseNotes65
-rw-r--r--nxt_plugin/usb/TODO29
-rw-r--r--nxt_plugin/usb/__init__.py92
-rw-r--r--nxt_plugin/usb/_debug.py77
-rw-r--r--nxt_plugin/usb/_interop.py135
-rw-r--r--nxt_plugin/usb/backend/__init__.py368
-rw-r--r--nxt_plugin/usb/backend/libusb01.py582
-rw-r--r--nxt_plugin/usb/backend/libusb10.py661
-rw-r--r--nxt_plugin/usb/backend/openusb.py707
-rw-r--r--nxt_plugin/usb/control.py252
-rw-r--r--nxt_plugin/usb/core.py859
-rw-r--r--nxt_plugin/usb/legacy.py334
-rw-r--r--nxt_plugin/usb/util.py260
61 files changed, 11130 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..db73526
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bak
+*.??~
diff --git a/Globales.py b/Globales.py
new file mode 100644
index 0000000..aa99b55
--- /dev/null
+++ b/Globales.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# CeibalJAM! - Uruguay
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import os
+import sys
+
+sys.path.append("nxt_plugin")
+
+BASE = os.path.dirname(__file__)
+ICONOS = os.path.join(BASE, "Iconos")
+
+BLANCO = gtk.gdk.Color(65535, 65535, 65535,1)
+NEGRO = gtk.gdk.Color(0, 0, 0, 1)
+AMARILLO = gtk.gdk.Color(65000,65000,40275,1)
+NARANJA = gtk.gdk.Color(65000,26000,0,1) \ No newline at end of file
diff --git a/Iconos/bluetooth.png b/Iconos/bluetooth.png
new file mode 100644
index 0000000..2667c48
--- /dev/null
+++ b/Iconos/bluetooth.png
Binary files differ
diff --git a/Iconos/luz.png b/Iconos/luz.png
new file mode 100644
index 0000000..5ce987f
--- /dev/null
+++ b/Iconos/luz.png
Binary files differ
diff --git a/Iconos/motor.png b/Iconos/motor.png
new file mode 100644
index 0000000..9ded348
--- /dev/null
+++ b/Iconos/motor.png
Binary files differ
diff --git a/Iconos/presion.png b/Iconos/presion.png
new file mode 100644
index 0000000..69d623a
--- /dev/null
+++ b/Iconos/presion.png
Binary files differ
diff --git a/Iconos/sonido.png b/Iconos/sonido.png
new file mode 100644
index 0000000..f19482f
--- /dev/null
+++ b/Iconos/sonido.png
Binary files differ
diff --git a/Iconos/ultrasonido.png b/Iconos/ultrasonido.png
new file mode 100644
index 0000000..b6237fe
--- /dev/null
+++ b/Iconos/ultrasonido.png
Binary files differ
diff --git a/Iconos/usb.png b/Iconos/usb.png
new file mode 100644
index 0000000..2433889
--- /dev/null
+++ b/Iconos/usb.png
Binary files differ
diff --git a/LegoJAM.py b/LegoJAM.py
new file mode 100644
index 0000000..c59b554
--- /dev/null
+++ b/LegoJAM.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# CeibalJAM! - Uruguay
+
+import os
+import gtk
+import sys
+import gobject
+
+from Widgets import *
+import Globales as G
+
+class LegoJAM(gtk.Window):
+ def __init__(self):
+ super(LegoJAM, self).__init__()
+ self.set_title("LegoJAM")
+ #self.set_icon_from_file(os.path.join(G.ICONOS, "legojam.png"))
+ self.set_size_request(800, 600)
+ self.set_border_width(5)
+ self.set_position(gtk.WIN_POS_CENTER)
+ self.set_resizable(True)
+ self.canvas = CanvasRobot()
+ self.add(self.canvas)
+ self.show_all()
+
+ self.connect("delete_event", self.delete_event)
+
+ def delete_event(self, widget, event):
+ self.salir()
+ return False
+
+ def salir(self, widget=None):
+ sys.exit(0)
+
+if __name__ == "__main__":
+ LegoJAM()
+ gtk.main()
diff --git a/Widgets.py b/Widgets.py
new file mode 100644
index 0000000..9eb90f0
--- /dev/null
+++ b/Widgets.py
@@ -0,0 +1,221 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# CeibalJAM! - Uruguay
+
+import gtk
+import os
+import cairo
+import gobject
+import Globales as G
+
+class CanvasRobot(gtk.VBox):
+ def __init__(self):
+ gtk.VBox.__init__(self)
+ self.motores_sensores = Motores_Sensores()
+ self.bateria = Bateria()
+
+ self.pack_start(self.motores_sensores, True, True, 0)
+ self.pack_start(self.bateria, False, False, 0)
+
+ self.show_all()
+
+class Motores_Sensores(gtk.HBox):
+ def __init__(self):
+ gtk.HBox.__init__(self)
+ self.motores = Motores()
+ self.sensores = Sensores()
+ self.pack_start(self.motores, True, True, 0)
+ self.pack_start(self.sensores, True, True, 0)
+ self.show_all()
+
+class Motores(gtk.VBox):
+ def __init__(self):
+ gtk.VBox.__init__(self)
+ self.widgetMotorA = BotonMotor()
+ self.widgetMotorA.set_tooltip("Motor A")
+ self.widgetMotorB = BotonMotor()
+ self.widgetMotorB.set_tooltip("Motor B")
+ self.widgetMotorC = BotonMotor()
+ self.widgetMotorC.set_tooltip("Motor C")
+ motores = [self.widgetMotorA, self.widgetMotorB,
+ self.widgetMotorC]
+ for motor in motores:
+ motor.set_imagen(os.path.join(G.ICONOS, 'motor.png'))
+ self.pack_start(motor, True, True, 0)
+ self.show_all()
+
+class Sensores(gtk.VBox):
+ def __init__(self):
+ gtk.VBox.__init__(self)
+
+ self.widget_presion = ControlSensor()
+ self.widget_presion.set_imagen(os.path.join(G.ICONOS, 'presion.png'))
+ self.widget_presion.set_tooltip("Sensor de Presión")
+ self.widget_sonido = ControlSensor()
+ self.widget_sonido.set_imagen(os.path.join(G.ICONOS, 'sonido.png'))
+ self.widget_sonido.set_tooltip("Sensor de Sonido")
+ self.widget_ultrasonido = ControlSensor()
+ self.widget_ultrasonido.set_imagen(os.path.join(G.ICONOS, 'ultrasonido.png'))
+ self.widget_ultrasonido.set_tooltip("Sensor de Ultrasonido")
+ self.widget_luz = ControlSensor()
+ self.widget_luz.set_imagen(os.path.join(G.ICONOS, 'luz.png'))
+ self.widget_luz.set_tooltip("Sensor de Luz")
+
+ hbox1 = gtk.HBox()
+ hbox1.pack_start(self.widget_presion, True, True, 0)
+ hbox1.pack_start(self.widget_sonido, True, True, 0)
+ hbox2 = gtk.HBox()
+ hbox2.pack_start(self.widget_ultrasonido, True, True, 0)
+ hbox2.pack_start(self.widget_luz, True, True, 0)
+
+ self.pack_start(hbox1, True, True, 0)
+ self.pack_start(hbox2, True, True, 0)
+ self.show_all()
+
+class ControlSensor(gtk.VBox):
+ def __init__(self):
+ gtk.VBox.__init__(self)
+ self.botonsensor = BotonSensor()
+ self.barradeprogreso = Barra_de_Progreso()
+ self.pack_start(self.botonsensor, True, True, 0)
+ self.pack_start(self.barradeprogreso, False, False, 0)
+ self.show_all()
+
+ def set_tooltip(self, texto):
+ self.botonsensor.set_tooltip(texto)
+
+ def set_imagen(self, archivo):
+ self.botonsensor.set_imagen(archivo)
+
+class BotonSensor(gtk.EventBox):
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+ self.set_visible_window(True)
+ self.modify_bg(gtk.STATE_NORMAL, G.BLANCO)
+ self.set_border_width(1)
+ self.imagen = None
+ self.show_all()
+ self.connect('expose-event', self.repintar)
+
+ def set_tooltip(self, texto):
+ tooltips = gtk.Tooltips()
+ tooltips.set_tip(self, texto, tip_private=None)
+
+ def set_imagen(self, archivo):
+ self.imagen = cairo.ImageSurface.create_from_png(archivo)
+
+ def set_tamanio(self, w, h):
+ self.set_size_request(w,h)
+
+ def repintar(self, widget, senial):
+ imgpat = cairo.SurfacePattern(self.imagen)
+ scaler = cairo.Matrix()
+
+ x,y,w,h = self.get_allocation()
+ width = float(self.imagen.get_width())
+ height = float(self.imagen.get_height())
+ ws = float(width/float(w))
+ hs = float(height/float(h))
+
+ scaler.scale(ws, hs)
+ imgpat.set_matrix(scaler)
+ imgpat.set_filter(cairo.FILTER_BEST)
+
+ cr = self.window.cairo_create()
+ cr.set_source(imgpat)
+ cr.paint()
+
+ return True
+
+class BotonMotor(BotonSensor):
+ __gsignals__ = {"clicked":(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, ))}
+ def __init__(self):
+ BotonSensor.__init__(self)
+ self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK |
+ gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK)
+ self.on = False
+ self.normal = G.BLANCO
+ self.clicked = G.NARANJA
+ self.enter = G.AMARILLO
+ self.connect("button_press_event", self.button_press)
+ self.connect("button_release_event", self.button_release)
+ self.connect("enter-notify-event", self.enter_notify_event)
+ self.connect("leave-notify-event", self.leave_notify_event)
+
+ def button_release(self, widget, event):
+ self.modify_bg(gtk.STATE_NORMAL, self.enter)
+ def leave_notify_event(self, widget, event):
+ self.modify_bg(gtk.STATE_NORMAL, self.normal)
+ def enter_notify_event(self, widget, event):
+ self.modify_bg(gtk.STATE_NORMAL, self.enter)
+ def button_press(self, widget, event):
+ if event.button == 1:
+ if not self.on:
+ self.modify_bg(gtk.STATE_NORMAL, self.clicked)
+ self.on = True
+ self.normal = G.NARANJA
+ self.enter = G.NARANJA
+ else:
+ self.modify_bg(gtk.STATE_NORMAL, G.AMARILLO)
+ self.on = False
+ self.normal = G.BLANCO
+ self.enter = G.AMARILLO
+ self.emit("clicked", event)
+
+class Bateria(gtk.HBox):
+ def __init__(self):
+ gtk.HBox.__init__(self)
+ etiqueta = gtk.Label("Batería: ")
+ self.barradeprogreso = Barra_de_Progreso()
+ self.pack_start(etiqueta, False, False, 0)
+ self.pack_start(self.barradeprogreso, True, True, 0)
+
+ self.show_all()
+
+class Barra_de_Progreso(gtk.EventBox):
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+ self.scale = ProgressBar(gtk.Adjustment(0.0, 0.0, 101.0, 0.1, 1.0, 1.0))
+ self.valor = 0
+ self.add(self.scale)
+ self.show_all()
+
+ def set_progress(self, valor = 0):
+ self.scale.set_value(valor)
+
+class ProgressBar(gtk.HScale):
+ def __init__(self, ajuste):
+ gtk.HScale.__init__(self, ajuste)
+ self.ajuste = ajuste
+ self.set_digits(0)
+ self.set_draw_value(False)
+ self.x, self.y, self.w, self.h = (0,0,200,40)
+ self.borde, self.ancho = (15, 10)
+ self.connect("expose_event", self.expose)
+ self.connect("size-allocate", self.size_allocate)
+
+ def expose( self, widget, event ):
+ x, y, w, h = (self.x, self.y, self.w, self.h)
+ ancho, borde = (self.ancho, self.borde)
+ gc = gtk.gdk.Drawable.new_gc(self.window)
+ gc.set_rgb_fg_color(G.BLANCO)
+ self.window.draw_rectangle( gc, True, x, y, w, h )
+ gc.set_rgb_fg_color(G.AMARILLO)
+ ww = w- borde*2
+ xx = x+ w/2 - ww/2
+ hh = ancho
+ yy = y+ h/2 - ancho/2
+ self.window.draw_rectangle( gc, True, xx, yy, ww, hh )
+ anchoimagen, altoimagen = (25,25)
+ ximagen = int((xx- anchoimagen/2) + self.get_value() * (ww / (self.ajuste.upper - self.ajuste.lower)))
+ yimagen = yy + hh/2 - altoimagen/2
+ gc.set_rgb_fg_color(G.NARANJA)
+ self.window.draw_rectangle( gc, True, xx, yy, ximagen, hh)
+ gc.set_rgb_fg_color(G.BLANCO)
+ self.window.draw_rectangle( gc, False, xx, yy, ww, hh )
+ return True
+
+ def size_allocate( self, widget, allocation ):
+ self.x, self.y, self.w, self.h= allocation
+ return False \ No newline at end of file
diff --git a/nxt_plugin/__init__.py b/nxt_plugin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/nxt_plugin/__init__.py
diff --git a/nxt_plugin/ejemplo.py b/nxt_plugin/ejemplo.py
new file mode 100644
index 0000000..7a8d828
--- /dev/null
+++ b/nxt_plugin/ejemplo.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+
+import nxt.locator
+from nxt.sensor import *
+
+b = nxt.locator.find_one_brick()
+
+print 'Touch:', Touch(b, PORT_1).get_sample()
+print 'Sound:', Sound(b, PORT_2).get_sample()
diff --git a/nxt_plugin/ejemplo2.py b/nxt_plugin/ejemplo2.py
new file mode 100644
index 0000000..613a11a
--- /dev/null
+++ b/nxt_plugin/ejemplo2.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+import nxt.locator
+from nxt.motor import *
+
+b = nxt.locator.find_one_brick()
+m_left = Motor(b, PORT_B)
+
+def spin_around(b):
+ m_left.turn(100, 360000)
+ m_right = Motor(b, PORT_C)
+ m_right.turn(-100, 360)
+
+spin_around(b)
diff --git a/nxt_plugin/icons/nxt-motorsoff.svg b/nxt_plugin/icons/nxt-motorsoff.svg
new file mode 100644
index 0000000..25f02ae
--- /dev/null
+++ b/nxt_plugin/icons/nxt-motorsoff.svg
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="nxt-sensorsoff.svg">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1366"
+ inkscape:window-height="744"
+ id="namedview13"
+ showgrid="false"
+ inkscape:zoom="6.0682618"
+ inkscape:cx="51.183474"
+ inkscape:cy="6.8233034"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <metadata
+ id="metadata3669">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439"
+ y="7.9770989"
+ x="8.396946"
+ height="7.3473282"
+ width="39.045803" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439-1"
+ transform="matrix(-0.00502678,0.99998737,-0.99998737,-0.00502678,0,0)"
+ y="-15.568534"
+ x="8.1275778"
+ height="7.3473282"
+ width="39.045803" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439-1-5"
+ transform="matrix(-0.01239668,0.99992316,-0.99999792,-0.00203822,0,0)"
+ y="-47.746151"
+ x="8.08708"
+ height="7.3472505"
+ width="15.832836" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439-6"
+ y="39.780533"
+ x="8.396945"
+ height="7.3473282"
+ width="14.484735" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4556"
+ y="22.88168"
+ x="23.091602"
+ height="9.0267172"
+ width="9.2366409" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+ x="24.236315"
+ y="54.995998"
+ id="text3014"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3016"
+ x="24.236315"
+ y="54.995998">A</tspan></text>
+</svg>
diff --git a/nxt_plugin/icons/nxt-motorson.svg b/nxt_plugin/icons/nxt-motorson.svg
new file mode 100644
index 0000000..579041f
--- /dev/null
+++ b/nxt_plugin/icons/nxt-motorson.svg
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="nxt-motorson.svg">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1366"
+ inkscape:window-height="744"
+ id="namedview13"
+ showgrid="false"
+ inkscape:zoom="3.0341309"
+ inkscape:cx="69.326779"
+ inkscape:cy="-9.6437821"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <metadata
+ id="metadata3669">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439"
+ y="7.9770989"
+ x="8.396946"
+ height="7.3473282"
+ width="39.045803" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439-1"
+ transform="matrix(-0.00502678,0.99998737,-0.99998737,-0.00502678,0,0)"
+ y="-15.568534"
+ x="8.1275778"
+ height="7.3473282"
+ width="39.045803" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:0.64025837;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439-1-5"
+ transform="matrix(-0.01226238,0.99992481,-0.99999788,-0.00206055,0,0)"
+ y="-47.74506"
+ x="8.0861673"
+ height="7.3472509"
+ width="16.006245" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439-6"
+ y="39.780533"
+ x="8.396945"
+ height="7.3473282"
+ width="14.484735" />
+ <rect
+ style="fill:#ff7d00;fill-opacity:1;fill-rule:evenodd;stroke:#ff5e00;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4556"
+ y="22.88168"
+ x="23.091602"
+ height="9.0267172"
+ width="9.2366409" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#282828;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+ x="171.75847"
+ y="32.39407"
+ id="text2998"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3000"
+ x="171.75847"
+ y="32.39407" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="36.913372"
+ y="86.310448"
+ id="text3006"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3008"
+ x="36.913372"
+ y="86.310448"
+ style="font-weight:bold;-inkscape-font-specification:Sans Bold" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="-80.748001"
+ y="55.329582"
+ id="text3010"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3012"
+ x="-80.748001"
+ y="55.329582" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#282828;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+ x="24.545702"
+ y="54.999996"
+ id="text3014"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3016"
+ x="24.545702"
+ y="54.999996">A</tspan></text>
+</svg>
diff --git a/nxt_plugin/icons/nxt-sensorsoff.svg b/nxt_plugin/icons/nxt-sensorsoff.svg
new file mode 100644
index 0000000..052e544
--- /dev/null
+++ b/nxt_plugin/icons/nxt-sensorsoff.svg
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="nxt-motorsoff.svg">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1366"
+ inkscape:window-height="744"
+ id="namedview13"
+ showgrid="false"
+ inkscape:zoom="6.0682618"
+ inkscape:cx="-9.0113913"
+ inkscape:cy="13.088437"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <metadata
+ id="metadata3669">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439"
+ y="7.9770989"
+ x="8.396946"
+ height="7.3473282"
+ width="39.045803" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439-1"
+ transform="matrix(-0.00502678,0.99998737,-0.99998737,-0.00502678,0,0)"
+ y="-15.568534"
+ x="8.1275778"
+ height="7.3473282"
+ width="39.045803" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439-1-5"
+ transform="matrix(-0.01235996,0.99992361,-0.99999791,-0.00204428,0,0)"
+ y="-47.745853"
+ x="8.086833"
+ height="7.3472509"
+ width="15.879877" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439-6"
+ y="39.780533"
+ x="8.396945"
+ height="7.3473282"
+ width="14.484735" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4439-6-4"
+ y="39.780537"
+ x="32.85305"
+ height="7.3473282"
+ width="5.8618531" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect4556"
+ y="22.88168"
+ x="23.091602"
+ height="9.0267172"
+ width="9.2366409" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+ x="29.892578"
+ y="55.014397"
+ id="text3014"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3016"
+ x="29.892578"
+ y="55.014397">1</tspan></text>
+</svg>
diff --git a/nxt_plugin/icons/nxt-sensorson.svg b/nxt_plugin/icons/nxt-sensorson.svg
new file mode 100644
index 0000000..4978b47
--- /dev/null
+++ b/nxt_plugin/icons/nxt-sensorson.svg
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="nxton.svg">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1366"
+ inkscape:window-height="744"
+ id="namedview13"
+ showgrid="false"
+ inkscape:zoom="4.2909091"
+ inkscape:cx="3.1618992"
+ inkscape:cy="13.74113"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <metadata
+ id="metadata3669">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439"
+ y="7.9770989"
+ x="8.396946"
+ height="7.3473282"
+ width="39.045803" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439-1"
+ transform="matrix(-0.00502678,0.99998737,-0.99998737,-0.00502678,0,0)"
+ y="-15.568534"
+ x="8.1275778"
+ height="7.3473282"
+ width="39.045803" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:0.61931962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439-1-5"
+ transform="matrix(-0.01310556,0.99991412,-0.99999814,-0.00192796,0,0)"
+ y="-47.751934"
+ x="8.0915976"
+ height="7.347249"
+ width="14.976443" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439-6"
+ y="39.780533"
+ x="8.396945"
+ height="7.3473282"
+ width="14.484735" />
+ <rect
+ style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:0.66856074;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4439-6-4"
+ y="39.780537"
+ x="32.85305"
+ height="7.3473282"
+ width="6.4742923" />
+ <rect
+ style="fill:#ff7d00;fill-opacity:1;fill-rule:evenodd;stroke:#ff5e00;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4556"
+ y="22.88168"
+ x="23.091602"
+ height="9.0267172"
+ width="9.2366409" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#282828;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+ x="30.183891"
+ y="55.130924"
+ id="text3014"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3016"
+ x="30.183891"
+ y="55.130924">1</tspan></text>
+</svg>
diff --git a/nxt_plugin/nxt/LICENSE b/nxt_plugin/nxt/LICENSE
new file mode 100644
index 0000000..818433e
--- /dev/null
+++ b/nxt_plugin/nxt/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/nxt_plugin/nxt/PKG-INFO b/nxt_plugin/nxt/PKG-INFO
new file mode 100644
index 0000000..bccb0a9
--- /dev/null
+++ b/nxt_plugin/nxt/PKG-INFO
@@ -0,0 +1,189 @@
+Metadata-Version: 1.0
+Name: nxt-python
+Version: 2.2.1
+Summary: LEGO Mindstorms NXT Control Package
+Home-page: http://code.google.com/p/nxt-python/
+Author: Marcus Wanner
+Author-email: marcus@wanners.net
+License: Gnu GPL v3
+Description: NXT-Python is a package for controlling a LEGO NXT robot using the
+ Python programming language. It can communicate using either USB or
+ Bluetooth. It is available under the Gnu GPL v3 license. It is based on
+ NXT_Python, where releases halted in May 2007.
+
+ Requirements:
+
+ * Python 2.6 or greater, but not 3.x (http://www.python.org)
+ And at least one comm library:
+ * Bluetooth communications:
+ Linux/Windows: PyBluez (http://code.google.com/p/pybluez/)
+ (package python-bluez in deb-based linuxes)
+ Mac: LightBlue (http://lightblue.sourceforge.net/)
+ * USB communications:
+ PyUSB (http://sourceforge.net/projects/pyusb/)
+ * Fantom communications (tested on Mac OSX):
+ Pyfantom (http://pyfantom.ni.fr.eu.org/)
+
+ Installation (see http://code.google.com/p/nxt-python/wiki/Installation):
+
+ * Untar/unzip source package.
+ * In package directory, run "python setup.py install" (as root), or if
+ under windows, double-click install.bat.
+ * To use USB on Linux as non-superuser, at a root terminal type:
+ groupadd lego
+ usermod -a -G lego [username]
+ echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0694", GROUP="lego", MODE="0660"' > /etc/udev/rules.d/70-lego.rules
+
+ Getting Started:
+
+ Take a look at the examples directory. Feel free to copy that code
+ into your scripts and don't be afraid to experiment! If you are having
+ trouble with something, you may find the solution in the docstrings (for
+ example, help('nxt.sensor.Ultrasonic')) or even in the source code
+ (especially for digital sensors).
+
+ Notes/FAQ:
+ (I have tried to put the most important stuff first, but it would be a good
+ idea to read the whole section. In any case, read it all the way through
+ before asking for help. Thanks!)
+
+ -=-=-About v2-=-=-
+ This version is part of the 2.x series of releases. Programs
+ designed for NXT_Python or for the 1.x series of nxt-python will not
+ work with this version. If you are trying to get an old program to work,
+ it most likely needs a 1.x series release, which can be downloaded from
+ the nxt-python downloads page at googlecode. New projects should use a
+ 2.x series release (hint: this is one!) due to the new features and API
+ improvements. Converting old projects is somewhat difficult and not
+ officially supported, though as always you're welcome to ask for help.
+ -=-=-Problems and Their Solutions-=-=-
+ Support for a number of sensors has not been tested at all, due to
+ lack of hardware. I have started a project to test this code, but the
+ going is slow and I still can't test everything. If you have a problem
+ with a digital sensor, see the troubleshooting guide below and don't
+ forget to report your trouble!
+ The Synchronized Motor support has not been extensively tested for
+ accuracy. It seems to mostly work well but the accuracy of the braking
+ function and the closeness of the two motors to each other have not been
+ given a formal scientific assessment.
+ NXT-Python has not been tested and may not work with custom nxt
+ firmware versions (if you don't know what that means, you don't need to
+ worry about it). However, if the firmware supports the standard LEGO
+ USB/BT communications protocol, everything should more or less work.
+ NXT-Python has been tested with bricks using LEGO firmware version up to
+ 1.29 and is compatible with protocol version 1.124 (used by most if not
+ all of the official firmwares). It has also been reported working with
+ LeJOS.
+ -=-=-If you co'd the SVN...-=-=-
+ The arduino directory of an svn checkout (not included with release
+ packages) contains a digital sensor unit test system called nxtduemu. It
+ is intended for developers and advanced users who are looking to
+ experiment with sensors and the digital sensor framework and classes.
+ See Arduino/README for more info and usage instructions.
+
+ Specific Stability Status:
+ nxt.brick, nxt.telegram, nxt.direct, and nxt.system:
+ Have been redone somewhat as of v2.2.0 but appear to work well.
+ USB Communication System (nxt.usbsock)
+ On Linux: Very stable and extensively tested.
+ On Windows: Somewhat tested; seems to work pretty well.
+ On Mac: Some users having problems.
+ BlueTooth Communication System (nxt.bluesock, nxt.lightblueglue)
+ On Linux: Stable; well tested with both pybluez and lightblue.
+ On Windows: Stable; working last I checked.
+ On Mac: Some users having problems.
+ Internet Communications System (nxt.ipsock)
+ Seems to work for the most part. Occasionally has hiccups.
+ Fantom Communications System (nxt.fantomsock)
+ On Linux: N/A (Fantom driver not supported)
+ On Windows: Not tested.
+ On Mac: Tested, USB interface working, Bluetooth not working.
+ nxt.locator:
+ Tested working with revamped logic and new code in v2.2.0.
+ nxt.motor:
+ Stable except for Synchronized Motor support, which is experimental at
+ this stage and has not been extensively tested.
+ nxt.sensor:
+ Code not specific to a particular sensor is well-tested and working
+ great. More than half of the sensor classes were last reported working;
+ the rest have not to my knowlege been tested and were written blindly
+ from the manuacturers' specifications.
+ nxt.error:
+ If there's a problem with this one, I'm gonna cry.
+
+ Contact:
+
+ NXT-Python's Head Developer:
+ Marcus Wanner (marcus@wanners.net)
+ The support and development mailing list:
+ http://groups.google.com/group/nxt-python
+ Report bugs and suggest new features at:
+ http://code.google.com/p/nxt-python/issues/list
+
+ Thanks to:
+
+ Doug Lau for writing NXT_Python, our starting point.
+ rhn for creating what would become v2, making lots of smaller changes, and
+ reviewing tons of code.
+ mindsensors.com (esp. Ryan Kneip) for helping out with the code for a lot of
+ their sensors, expanding the sensors covered by the type checking
+ database, and providing hardware for testing.
+ HiTechnic for providing identification information for their sensors. I note
+ that they have now included this information in their website. ;)
+ Linus Atorf, Samuel Leeman-Munk, melducky, Simon Levy, Steve Castellotti,
+ Paulo Vieira, zonedabone, migpics, TC Wan, jerradgenson, henryacev,
+ Paul Hollensen, and anyone else I forgot for various fixes and
+ additions.
+ All our users for their interest and support!
+
+
+
+ Troubleshooting Digital Sensors (don't read unless you have problems):
+ If you are getting errors, strange behavor, or incorrect values from a digital
+ sensor, chances are that there is a bug in our code. Follow these instructions
+ to try and find out what's wrong:
+ 1. Test the sensor with a different access library to make sure it's working
+ right.
+ 2. Check your code again. There are some weird "features" in the interfaces
+ of some of the sensors; make sure you are doing things right.
+ 3. Locate the sensor class's source code in nxt-python. It should be
+ somewhere in nxt/sensor/<manufacturer>.py, under the heading "class SensorName(
+ BaseDigitalSensor):". Read any comments for instructions on certain things.
+
+ If you get to here and are still having a problem, you can either go ahead and
+ report it now or continue to try and find and fix the problem and then report
+ it (or not report it at all, but that wouldn't be very nice...).
+ Python experience required beyond this point.
+
+ 4. Get the sensor's specifications from the manufacturer's website. Make
+ sure it includes a table of I2C registers and instructions for using them.
+ 5. Pick one of the following depending on what the problem is:
+ ####Errors:
+ Cause: We screwed up.
+ Solution: Check the line mentioned in the error for incorrect syntax or
+ other problem. A bit of python experience and maybe some googling is needed
+ here.
+ ####Strange Behavior (in sensors with modes/commands):
+ Cause: nxt-python's command enumerations are incorrect.
+ Solution: Verify them using the sensor's specs, and correct any problems.
+ See "Incorrect Values" for more.
+ ####Incorrect Values:
+ Cause: nxt-python is processing the value wrong.
+ Solution: Check what goes on in the sampling method against what the spec
+ says should be done. If there is an inconsistency, try to fix it.
+ Cause: nxt-python has an incorrect register number or type in I2C_ADDRESS.
+ Solution: Verify the address (the number) and the string (the struct format
+ string). To verify the address, use the spec. To verify the struct format, you
+ will need to read this: <http://docs.python.org/library/struct.html#format-
+ strings> or have experience with struct.
+ Read the spec for the sensor to determine how the given value should be read,
+ then start at the sample method and read through it, checking for problems as
+ you go. If it seems right, go back to the I2C_ADDRESS chunk (near the top of the
+ class) and make sure that the correct struct format string is being used. The
+ most common problem here is values that are off by plus or minus 128 or 32768
+ because of an incorrect signed/unsigned setting. This can be fixed by switching
+ the case (as in upper or lower) of the letter in the string. Other problems
+ could include the wrong size (B, H, or L) being used, or, in the two latter
+ ones, the wrong byte order (< or >). As always, common sense required.
+
+Platform: UNKNOWN
diff --git a/nxt_plugin/nxt/README b/nxt_plugin/nxt/README
new file mode 100644
index 0000000..fb8b72b
--- /dev/null
+++ b/nxt_plugin/nxt/README
@@ -0,0 +1,179 @@
+NXT-Python is a package for controlling a LEGO NXT robot using the
+Python programming language. It can communicate using either USB or
+Bluetooth. It is available under the Gnu GPL v3 license. It is based on
+NXT_Python, where releases halted in May 2007.
+
+Requirements:
+
+ * Python 2.6 or greater, but not 3.x (http://www.python.org)
+ And at least one comm library:
+ * Bluetooth communications:
+ Linux/Windows: PyBluez (http://code.google.com/p/pybluez/)
+ (package python-bluez in deb-based linuxes)
+ Mac: LightBlue (http://lightblue.sourceforge.net/)
+ * USB communications:
+ PyUSB (http://sourceforge.net/projects/pyusb/)
+ * Fantom communications (tested on Mac OSX):
+ Pyfantom (http://pyfantom.ni.fr.eu.org/)
+
+Installation (see http://code.google.com/p/nxt-python/wiki/Installation):
+
+ * Untar/unzip source package.
+ * In package directory, run "python setup.py install" (as root), or if
+ under windows, double-click install.bat.
+ * To use USB on Linux as non-superuser, at a root terminal type:
+ groupadd lego
+ usermod -a -G lego [username]
+ echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0694", GROUP="lego", MODE="0660"' > /etc/udev/rules.d/70-lego.rules
+
+Getting Started:
+
+ Take a look at the examples directory. Feel free to copy that code
+into your scripts and don't be afraid to experiment! If you are having
+trouble with something, you may find the solution in the docstrings (for
+example, help('nxt.sensor.Ultrasonic')) or even in the source code
+(especially for digital sensors).
+
+Notes/FAQ:
+ (I have tried to put the most important stuff first, but it would be a good
+ idea to read the whole section. In any case, read it all the way through
+ before asking for help. Thanks!)
+
+ -=-=-About v2-=-=-
+ This version is part of the 2.x series of releases. Programs
+designed for NXT_Python or for the 1.x series of nxt-python will not
+work with this version. If you are trying to get an old program to work,
+it most likely needs a 1.x series release, which can be downloaded from
+the nxt-python downloads page at googlecode. New projects should use a
+2.x series release (hint: this is one!) due to the new features and API
+improvements. Converting old projects is somewhat difficult and not
+officially supported, though as always you're welcome to ask for help.
+ -=-=-Problems and Their Solutions-=-=-
+ Support for a number of sensors has not been tested at all, due to
+lack of hardware. I have started a project to test this code, but the
+going is slow and I still can't test everything. If you have a problem
+with a digital sensor, see the troubleshooting guide below and don't
+forget to report your trouble!
+ The Synchronized Motor support has not been extensively tested for
+accuracy. It seems to mostly work well but the accuracy of the braking
+function and the closeness of the two motors to each other have not been
+given a formal scientific assessment.
+ NXT-Python has not been tested and may not work with custom nxt
+firmware versions (if you don't know what that means, you don't need to
+worry about it). However, if the firmware supports the standard LEGO
+USB/BT communications protocol, everything should more or less work.
+NXT-Python has been tested with bricks using LEGO firmware version up to
+1.29 and is compatible with protocol version 1.124 (used by most if not
+all of the official firmwares). It has also been reported working with
+LeJOS.
+ -=-=-If you co'd the SVN...-=-=-
+ The arduino directory of an svn checkout (not included with release
+packages) contains a digital sensor unit test system called nxtduemu. It
+is intended for developers and advanced users who are looking to
+experiment with sensors and the digital sensor framework and classes.
+See Arduino/README for more info and usage instructions.
+
+Specific Stability Status:
+ nxt.brick, nxt.telegram, nxt.direct, and nxt.system:
+ Have been redone somewhat as of v2.2.0 but appear to work well.
+ USB Communication System (nxt.usbsock)
+ On Linux: Very stable and extensively tested.
+ On Windows: Somewhat tested; seems to work pretty well.
+ On Mac: Some users having problems.
+ BlueTooth Communication System (nxt.bluesock, nxt.lightblueglue)
+ On Linux: Stable; well tested with both pybluez and lightblue.
+ On Windows: Stable; working last I checked.
+ On Mac: Some users having problems.
+ Internet Communications System (nxt.ipsock)
+ Seems to work for the most part. Occasionally has hiccups.
+ Fantom Communications System (nxt.fantomsock)
+ On Linux: N/A (Fantom driver not supported)
+ On Windows: Not tested.
+ On Mac: Tested, USB interface working, Bluetooth not working.
+ nxt.locator:
+ Tested working with revamped logic and new code in v2.2.0.
+ nxt.motor:
+ Stable except for Synchronized Motor support, which is experimental at
+ this stage and has not been extensively tested.
+ nxt.sensor:
+ Code not specific to a particular sensor is well-tested and working
+ great. More than half of the sensor classes were last reported working;
+ the rest have not to my knowlege been tested and were written blindly
+ from the manuacturers' specifications.
+ nxt.error:
+ If there's a problem with this one, I'm gonna cry.
+
+Contact:
+
+ NXT-Python's Head Developer:
+ Marcus Wanner (marcus@wanners.net)
+ The support and development mailing list:
+ http://groups.google.com/group/nxt-python
+ Report bugs and suggest new features at:
+ http://code.google.com/p/nxt-python/issues/list
+
+Thanks to:
+
+ Doug Lau for writing NXT_Python, our starting point.
+ rhn for creating what would become v2, making lots of smaller changes, and
+ reviewing tons of code.
+ mindsensors.com (esp. Ryan Kneip) for helping out with the code for a lot of
+ their sensors, expanding the sensors covered by the type checking
+ database, and providing hardware for testing.
+ HiTechnic for providing identification information for their sensors. I note
+ that they have now included this information in their website. ;)
+ Linus Atorf, Samuel Leeman-Munk, melducky, Simon Levy, Steve Castellotti,
+ Paulo Vieira, zonedabone, migpics, TC Wan, jerradgenson, henryacev,
+ Paul Hollensen, and anyone else I forgot for various fixes and
+ additions.
+ All our users for their interest and support!
+
+
+
+Troubleshooting Digital Sensors (don't read unless you have problems):
+If you are getting errors, strange behavor, or incorrect values from a digital
+sensor, chances are that there is a bug in our code. Follow these instructions
+to try and find out what's wrong:
+ 1. Test the sensor with a different access library to make sure it's working
+right.
+ 2. Check your code again. There are some weird "features" in the interfaces
+of some of the sensors; make sure you are doing things right.
+ 3. Locate the sensor class's source code in nxt-python. It should be
+somewhere in nxt/sensor/<manufacturer>.py, under the heading "class SensorName(
+BaseDigitalSensor):". Read any comments for instructions on certain things.
+
+If you get to here and are still having a problem, you can either go ahead and
+report it now or continue to try and find and fix the problem and then report
+it (or not report it at all, but that wouldn't be very nice...).
+Python experience required beyond this point.
+
+ 4. Get the sensor's specifications from the manufacturer's website. Make
+sure it includes a table of I2C registers and instructions for using them.
+ 5. Pick one of the following depending on what the problem is:
+####Errors:
+ Cause: We screwed up.
+ Solution: Check the line mentioned in the error for incorrect syntax or
+other problem. A bit of python experience and maybe some googling is needed
+here.
+####Strange Behavior (in sensors with modes/commands):
+ Cause: nxt-python's command enumerations are incorrect.
+ Solution: Verify them using the sensor's specs, and correct any problems.
+ See "Incorrect Values" for more.
+####Incorrect Values:
+ Cause: nxt-python is processing the value wrong.
+ Solution: Check what goes on in the sampling method against what the spec
+says should be done. If there is an inconsistency, try to fix it.
+ Cause: nxt-python has an incorrect register number or type in I2C_ADDRESS.
+ Solution: Verify the address (the number) and the string (the struct format
+string). To verify the address, use the spec. To verify the struct format, you
+will need to read this: <http://docs.python.org/library/struct.html#format-
+strings> or have experience with struct.
+Read the spec for the sensor to determine how the given value should be read,
+then start at the sample method and read through it, checking for problems as
+you go. If it seems right, go back to the I2C_ADDRESS chunk (near the top of the
+class) and make sure that the correct struct format string is being used. The
+most common problem here is values that are off by plus or minus 128 or 32768
+because of an incorrect signed/unsigned setting. This can be fixed by switching
+the case (as in upper or lower) of the letter in the string. Other problems
+could include the wrong size (B, H, or L) being used, or, in the two latter
+ones, the wrong byte order (< or >). As always, common sense required.
diff --git a/nxt_plugin/nxt/__init__.py b/nxt_plugin/nxt/__init__.py
new file mode 100644
index 0000000..21d8eb8
--- /dev/null
+++ b/nxt_plugin/nxt/__init__.py
@@ -0,0 +1,17 @@
+# nxt.__init__ module -- LEGO Mindstorms NXT python package
+# Copyright (C) 2006 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+from nxt.locator import find_one_brick, Method
+from nxt.motor import *
+from nxt.sensor import *
diff --git a/nxt_plugin/nxt/bluesock.py b/nxt_plugin/nxt/bluesock.py
new file mode 100644
index 0000000..6af38cb
--- /dev/null
+++ b/nxt_plugin/nxt/bluesock.py
@@ -0,0 +1,79 @@
+# nxt.bluesock module -- Bluetooth socket communication with LEGO Minstorms NXT
+# Copyright (C) 2006, 2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+try:
+ import bluetooth
+except ImportError:
+ import lightblueglue as bluetooth
+from nxt.brick import Brick
+
+class BlueSock(object):
+
+ bsize = 118 # Bluetooth socket block size
+ PORT = 1 # Standard NXT rfcomm port
+
+ type = 'bluetooth'
+
+ def __init__(self, host):
+ self.host = host
+ self.sock = None
+ self.debug = False
+
+ def __str__(self):
+ return 'Bluetooth (%s)' % self.host
+
+ def connect(self):
+ if self.debug:
+ print 'Connecting via Bluetooth...'
+ sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
+ sock.connect((self.host, BlueSock.PORT))
+ self.sock = sock
+ if self.debug:
+ print 'Connected.'
+ return Brick(self)
+
+ def close(self):
+ if self.debug:
+ print 'Closing Bluetooth connection...'
+ self.sock.close()
+ if self.debug:
+ print 'Bluetooth connection closed.'
+
+ def send(self, data):
+ if self.debug:
+ print 'Send:',
+ print ':'.join('%02x' % ord(c) for c in data)
+ l0 = len(data) & 0xFF
+ l1 = (len(data) >> 8) & 0xFF
+ d = chr(l0) + chr(l1) + data
+ self.sock.send(d)
+
+ def recv(self):
+ data = self.sock.recv(2)
+ l0 = ord(data[0])
+ l1 = ord(data[1])
+ plen = l0 + (l1 << 8)
+ data = self.sock.recv(plen)
+ if self.debug:
+ print 'Recv:',
+ print ':'.join('%02x' % ord(c) for c in data)
+ return data
+
+def _check_brick(arg, value):
+ return arg is None or arg == value
+
+def find_bricks(host=None, name=None):
+ for h, n in bluetooth.discover_devices(lookup_names=True):
+ if _check_brick(host, h) and _check_brick(name, n):
+ yield BlueSock(h)
diff --git a/nxt_plugin/nxt/brick.py b/nxt_plugin/nxt/brick.py
new file mode 100644
index 0000000..6e90ac6
--- /dev/null
+++ b/nxt_plugin/nxt/brick.py
@@ -0,0 +1,235 @@
+# nxt.brick module -- Classes to represent LEGO Mindstorms NXT bricks
+# Copyright (C) 2006 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, rhn
+# Copyright (C) 2010 rhn, Marcus Wanner, zonedabone
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+from time import sleep
+from threading import Lock
+from nxt.error import FileNotFound, ModuleNotFound
+from nxt.telegram import OPCODES, Telegram
+from nxt.sensor import get_sensor
+from nxt.motcont import MotCont
+
+def _make_poller(opcode, poll_func, parse_func):
+ def poll(self, *args, **kwargs):
+ ogram = poll_func(opcode, *args, **kwargs)
+ with self.lock:
+ self.sock.send(str(ogram))
+ if ogram.reply:
+ igram = Telegram(opcode=opcode, pkt=self.sock.recv())
+ if ogram.reply:
+ return parse_func(igram)
+ else:
+ return None
+ return poll
+
+class _Meta(type):
+ 'Metaclass which adds one method for each telegram opcode'
+
+ def __init__(cls, name, bases, dict):
+ super(_Meta, cls).__init__(name, bases, dict)
+ for opcode in OPCODES:
+ poll_func, parse_func = OPCODES[opcode][0:2]
+ m = _make_poller(opcode, poll_func, parse_func)
+ try:
+ m.__doc__ = OPCODES[opcode][2]
+ except:
+ pass
+ setattr(cls, poll_func.__name__, m)
+
+
+class FileFinder(object):
+ 'A generator to find files on a NXT brick.'
+
+ def __init__(self, brick, pattern):
+ self.brick = brick
+ self.pattern = pattern
+ self.handle = None
+
+ def _close(self):
+ if self.handle is not None:
+ self.brick.close(self.handle)
+ self.handle = None
+
+ def __del__(self):
+ self._close()
+
+ def __iter__(self):
+ results = []
+ self.handle, fname, size = self.brick.find_first(self.pattern)
+ results.append((fname, size))
+ while True:
+ try:
+ handle, fname, size = self.brick.find_next(self.handle)
+ results.append((fname, size))
+ except FileNotFound:
+ self._close()
+ break
+ for result in results:
+ yield result
+
+
+def File(brick, name, mode='r', size=None):
+ """Opens a file for reading/writing. Mode is 'r' or 'w'. If mode is 'w',
+ size must be provided.
+ """
+ if mode == 'w':
+ if size is not None:
+ return FileWriter(brick, name, size)
+ else:
+ return ValueError('Size not specified')
+ elif mode == 'r':
+ return FileReader(brick, name)
+ else:
+ return ValueError('Mode ' + str(mode) + ' not supported')
+
+
+class FileReader(object):
+ """Context manager to read a file on a NXT brick. Do use the iterator or
+ the read() method, but not both at the same time!
+ The iterator returns strings of an arbitrary (short) length.
+ """
+
+ def __init__(self, brick, fname):
+ self.brick = brick
+ self.handle, self.size = brick.open_read(fname)
+
+ def read(self, bytes=None):
+ if bytes is not None:
+ remaining = bytes
+ else:
+ remaining = self.size
+ bsize = self.brick.sock.bsize
+ data = []
+ while remaining > 0:
+ handle, bsize, buffer_ = self.brick.read(self.handle,
+ min(bsize, remaining))
+ remaining -= len(buffer_)
+ data.append(buffer_)
+ return ''.join(data)
+
+ def close(self):
+ if self.handle is not None:
+ self.brick.close(self.handle)
+ self.handle = None
+
+ def __del__(self):
+ self.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, etp, value, tb):
+ self.close()
+
+ def __iter__(self):
+ rem = self.size
+ bsize = self.brick.sock.bsize
+ while rem > 0:
+ handle, bsize, data = self.brick.read(self.handle,
+ min(bsize, rem))
+ yield data
+ rem -= len(data)
+
+
+class FileWriter(object):
+ "Object to write to a file on a NXT brick"
+
+ def __init__(self, brick, fname, size):
+ self.brick = brick
+ self.handle = self.brick.open_write(fname, size)
+ self._position = 0
+ self.size = size
+
+ def __del__(self):
+ self.close()
+
+ def close(self):
+ if self.handle is not None:
+ self.brick.close(self.handle)
+ self.handle = None
+
+ def tell(self):
+ return self._position
+
+ def write(self, data):
+ remaining = len(data)
+ if remaining > self.size - self._position:
+ raise ValueError('Data will not fit into remaining space')
+ bsize = self.brick.sock.bsize
+ data_position = 0
+
+ while remaining > 0:
+ batch_size = min(bsize, remaining)
+ next_data_position = data_position + batch_size
+ buffer_ = data[data_position:next_data_position]
+
+ handle, size = self.brick.write(self.handle, buffer_)
+
+ self._position += batch_size
+ data_position = next_data_position
+ remaining -= batch_size
+
+
+class ModuleFinder(object):
+ 'Iterator to lookup modules on a NXT brick'
+
+ def __init__(self, brick, pattern):
+ self.brick = brick
+ self.pattern = pattern
+ self.handle = None
+
+ def _close(self):
+ if self.handle:
+ self.brick.close(self.handle)
+ self.handle = None
+
+ def __del__(self):
+ self._close()
+
+ def __iter__(self):
+ self.handle, mname, mid, msize, miomap_size = \
+ self.brick.request_first_module(self.pattern)
+ yield (mname, mid, msize, miomap_size)
+ while True:
+ try:
+ handle, mname, mid, msize, miomap_size = \
+ self.brick.request_next_module(
+ self.handle)
+ yield (mname, mid, msize, miomap_size)
+ except ModuleNotFound:
+ self._close()
+ break
+
+
+class Brick(object): #TODO: this begs to have explicit methods
+ 'Main object for NXT Control'
+
+ __metaclass__ = _Meta
+
+ def __init__(self, sock):
+ self.sock = sock
+ self.lock = Lock()
+ self.mc = MotCont(self)
+
+ def play_tone_and_wait(self, frequency, duration):
+ self.play_tone(frequency, duration)
+ sleep(duration / 1000.0)
+
+ def __del__(self):
+ self.sock.close()
+
+ find_files = FileFinder
+ find_modules = ModuleFinder
+ open_file = File
+ get_sensor = get_sensor
diff --git a/nxt_plugin/nxt/direct.py b/nxt_plugin/nxt/direct.py
new file mode 100644
index 0000000..202bcf5
--- /dev/null
+++ b/nxt_plugin/nxt/direct.py
@@ -0,0 +1,218 @@
+# nxt.direct module -- LEGO Mindstorms NXT direct telegrams
+# Copyright (C) 2006, 2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+'Use for direct communication with the NXT ***EXTREMELY ADVANCED USERS ONLY***'
+
+def _create(opcode, need_reply = True):
+ 'Create a simple direct telegram'
+ from telegram import Telegram
+ return Telegram(True, opcode, need_reply)
+
+def start_program(opcode, fname):
+ tgram = _create(opcode)
+ tgram.add_filename(fname)
+ return tgram
+
+def _parse_simple(tgram):
+ tgram.check_status()
+
+def stop_program(opcode):
+ return _create(opcode)
+
+def play_sound_file(opcode, loop, fname):
+ tgram = _create(opcode, False)
+ tgram.add_u8(loop)
+ tgram.add_filename(fname)
+ return tgram
+
+def play_tone(opcode, frequency, duration):
+ 'Play a tone at frequency (Hz) for duration (ms)'
+ tgram = _create(opcode, False)
+ tgram.add_u16(frequency)
+ tgram.add_u16(duration)
+ return tgram
+
+def set_output_state(opcode, port, power, mode, regulation, turn_ratio,
+ run_state, tacho_limit):
+ tgram = _create(opcode, False)
+ tgram.add_u8(port)
+ tgram.add_s8(power)
+ tgram.add_u8(mode)
+ tgram.add_u8(regulation)
+ tgram.add_s8(turn_ratio)
+ tgram.add_u8(run_state)
+ tgram.add_u32(tacho_limit)
+ return tgram
+
+def set_input_mode(opcode, port, sensor_type, sensor_mode):
+ tgram = _create(opcode, False)
+ tgram.add_u8(port)
+ tgram.add_u8(sensor_type)
+ tgram.add_u8(sensor_mode)
+ return tgram
+
+def get_output_state(opcode, port):
+ tgram = _create(opcode)
+ tgram.add_u8(port)
+ return tgram
+
+def _parse_get_output_state(tgram):
+ tgram.check_status()
+ port = tgram.parse_u8()
+ power = tgram.parse_s8()
+ mode = tgram.parse_u8()
+ regulation = tgram.parse_u8()
+ turn_ratio = tgram.parse_s8()
+ run_state = tgram.parse_u8()
+ tacho_limit = tgram.parse_u32()
+ tacho_count = tgram.parse_s32()
+ block_tacho_count = tgram.parse_s32()
+ rotation_count = tgram.parse_s32()
+ return (port, power, mode, regulation, turn_ratio, run_state,
+ tacho_limit, tacho_count, block_tacho_count, rotation_count)
+
+def get_input_values(opcode, port):
+ tgram = _create(opcode)
+ tgram.add_u8(port)
+ return tgram
+
+def _parse_get_input_values(tgram):
+ tgram.check_status()
+ port = tgram.parse_u8()
+ valid = tgram.parse_u8()
+ calibrated = tgram.parse_u8()
+ sensor_type = tgram.parse_u8()
+ sensor_mode = tgram.parse_u8()
+ raw_ad_value = tgram.parse_u16()
+ normalized_ad_value = tgram.parse_u16()
+ scaled_value = tgram.parse_s16()
+ calibrated_value = tgram.parse_s16()
+ return (port, valid, calibrated, sensor_type, sensor_mode, raw_ad_value,
+ normalized_ad_value, scaled_value, calibrated_value)
+
+def reset_input_scaled_value(opcode, port):
+ tgram = _create(opcode)
+ tgram.add_u8(port)
+ return tgram
+
+def message_write(opcode, inbox, message):
+ tgram = _create(opcode)
+ tgram.add_u8(inbox)
+ tgram.add_u8(len(message) + 1)
+ tgram.add_string(len(message), message)
+ tgram.add_u8(0)
+ return tgram
+
+def reset_motor_position(opcode, port, relative):
+ tgram = _create(opcode)
+ tgram.add_u8(port)
+ tgram.add_u8(relative)
+ return tgram
+
+def get_battery_level(opcode):
+ return _create(opcode)
+
+def _parse_get_battery_level(tgram):
+ tgram.check_status()
+ millivolts = tgram.parse_u16()
+ return millivolts
+
+def stop_sound_playback(opcode):
+ return _create(opcode)
+
+def keep_alive(opcode):
+ return _create(opcode)
+
+def _parse_keep_alive(tgram):
+ tgram.check_status()
+ sleep_time = tgram.parse_u32()
+ return sleep_time
+
+def ls_get_status(opcode, port):
+ 'Get status of low-speed sensor (ultrasonic)'
+ tgram = _create(opcode)
+ tgram.add_u8(port)
+ return tgram
+
+def _parse_ls_get_status(tgram):
+ tgram.check_status()
+ n_bytes = tgram.parse_u8()
+ return n_bytes
+
+def ls_write(opcode, port, tx_data, rx_bytes):
+ 'Write a low-speed command to a sensor (ultrasonic)'
+ tgram = _create(opcode)
+ tgram.add_u8(port)
+ tgram.add_u8(len(tx_data))
+ tgram.add_u8(rx_bytes)
+ tgram.add_string(len(tx_data), tx_data)
+ return tgram
+
+def ls_read(opcode, port):
+ 'Read a low-speed sensor value (ultrasonic)'
+ tgram = _create(opcode)
+ tgram.add_u8(port)
+ return tgram
+
+def _parse_ls_read(tgram):
+ tgram.check_status()
+ n_bytes = tgram.parse_u8()
+ contents = tgram.parse_string()
+ return contents[:n_bytes]
+
+def get_current_program_name(opcode):
+ return _create(opcode)
+
+def _parse_get_current_program_name(tgram):
+ tgram.check_status()
+ fname = tgram.parse_string()
+ return fname
+
+def message_read(opcode, remote_inbox, local_inbox, remove):
+ tgram = _create(opcode)
+ tgram.add_u8(remote_inbox)
+ tgram.add_u8(local_inbox)
+ tgram.add_u8(remove)
+ return tgram
+
+def _parse_message_read(tgram):
+ tgram.check_status()
+ local_inbox = tgram.parse_u8()
+ n_bytes = tgram.parse_u8()
+ message = tgram.parse_string()
+ return (local_inbox, message[:n_bytes])
+
+#TODO Add docstrings to all methods
+
+OPCODES = {
+ 0x00: (start_program, _parse_simple,'Starts program execution on the NXT brick'),
+ 0x01: (stop_program, _parse_simple, 'Stops program execution on the NXT brick'),
+ 0x02: (play_sound_file, _parse_simple, 'Plays a sound file on the NXT brick.'),
+ 0x03: (play_tone, _parse_simple, 'Plays a tone on the NXT brick'),
+ 0x04: (set_output_state, _parse_simple, 'Sets the state of motor ports on the NXT brick'),
+ 0x05: (set_input_mode, _parse_simple, 'Sets the state of sensor ports on the NXT brick'),
+ 0x06: (get_output_state, _parse_get_output_state, 'Gets the state of motor ports on the NXT brick'),
+ 0x07: (get_input_values, _parse_get_input_values, 'Gets the state of sensor ports on the NXT brick'),
+ 0x08: (reset_input_scaled_value, _parse_simple), #TODO What does this method do?
+ 0x09: (message_write, _parse_simple, 'Sends a message to the mailboxes on the NXT brick'),
+ 0x0A: (reset_motor_position, _parse_simple, 'Resets tachometers on the NXT brick'),
+ 0x0B: (get_battery_level, _parse_get_battery_level, 'Gets the voltage of the NXT battery'),
+ 0x0C: (stop_sound_playback, _parse_simple, 'Stops the currently running sound file'),
+ 0x0D: (keep_alive, _parse_keep_alive, 'Resets the standby timer on the NXT brick'),
+ 0x0E: (ls_get_status, _parse_ls_get_status), #TODO What does this method do?
+ 0x0F: (ls_write, _parse_simple), #TODO What does this method do?
+ 0x10: (ls_read, _parse_ls_read), #TODO What does this method do?
+ 0x11: (get_current_program_name, _parse_get_current_program_name, 'Gets the name of the currently running program'),
+ 0x13: (message_read, _parse_message_read, 'Reads the next message sent from the NXT brick'),
+}
diff --git a/nxt_plugin/nxt/error.py b/nxt_plugin/nxt/error.py
new file mode 100644
index 0000000..3d14497
--- /dev/null
+++ b/nxt_plugin/nxt/error.py
@@ -0,0 +1,87 @@
+# nxt.error module -- LEGO Mindstorms NXT error handling
+# Copyright (C) 2006, 2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+'Declarations for the errors'
+
+class ProtocolError(Exception):
+ pass
+
+class SysProtError(ProtocolError):
+ pass
+
+class FileExistsError(SysProtError):
+ pass
+
+class FileNotFound(SysProtError):
+ pass
+
+class ModuleNotFound(SysProtError):
+ pass
+
+class DirProtError(ProtocolError):
+ pass
+
+class I2CError(DirProtError):
+ pass
+
+class I2CPendingError(I2CError):
+ pass
+
+CODES = {
+ 0x00: None,
+ 0x20: I2CPendingError('Pending communication transaction in progress'),
+ 0x40: DirProtError('Specified mailbox queue is empty'),
+ 0x81: SysProtError('No more handles'),
+ 0x82: SysProtError('No space'),
+ 0x83: SysProtError('No more files'),
+ 0x84: SysProtError('End of file expected'),
+ 0x85: SysProtError('End of file'),
+ 0x86: SysProtError('Not a linear file'),
+ 0x87: FileNotFound('File not found'),
+ 0x88: SysProtError('Handle already closed'),
+ 0x89: SysProtError('No linear space'),
+ 0x8A: SysProtError('Undefined error'),
+ 0x8B: SysProtError('File is busy'),
+ 0x8C: SysProtError('No write buffers'),
+ 0x8D: SysProtError('Append not possible'),
+ 0x8E: SysProtError('File is full'),
+ 0x8F: FileExistsError('File exists'),
+ 0x90: ModuleNotFound('Module not found'),
+ 0x91: SysProtError('Out of bounds'),
+ 0x92: SysProtError('Illegal file name'),
+ 0x93: SysProtError('Illegal handle'),
+ 0xBD: DirProtError('Request failed (i.e. specified file not found)'),
+ 0xBE: DirProtError('Unknown command opcode'),
+ 0xBF: DirProtError('Insane packet'),
+ 0xC0: DirProtError('Data contains out-of-range values'),
+ 0xDD: DirProtError('Communication bus error'),
+ 0xDE: DirProtError('No free memory in communication buffer'),
+ 0xDF: DirProtError('Specified channel/connection is not valid'),
+ 0xE0: I2CError('Specified channel/connection not configured or busy'),
+ 0xEC: DirProtError('No active program'),
+ 0xED: DirProtError('Illegal size specified'),
+ 0xEE: DirProtError('Illegal mailbox queue ID specified'),
+ 0xEF: DirProtError('Attempted to access invalid field of a structure'),
+ 0xF0: DirProtError('Bad input or output specified'),
+ 0xFB: DirProtError('Insufficient memory available'),
+ 0xFF: DirProtError('Bad arguments'),
+}
+
+def check_status(status):
+ if status:
+ ex = CODES.get(status)
+ if ex:
+ raise ex
+ else:
+ raise ProtocolError, status
diff --git a/nxt_plugin/nxt/fantomsock.py b/nxt_plugin/nxt/fantomsock.py
new file mode 100644
index 0000000..f528e58
--- /dev/null
+++ b/nxt_plugin/nxt/fantomsock.py
@@ -0,0 +1,118 @@
+# fantomsock.py module -- NXT_Python socket wrapper for pyfantom (Mac)
+#
+# Copyright (C) 2011 Tat-Chee Wan, Marcus Wanner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import pyfantom
+from nxt.brick import Brick
+
+USB_BUFSIZE = 64
+BT_BUFSIZE = 1024 #"arbitrary"
+FANTOMSOCK_DEBUG = False
+
+
+class BluetoothSocket:
+ bsize = BT_BUFSIZE - 4 # Bluetooth socket block size
+
+ '''recv() method is currently reported broken'''
+ def __init__(self, _sock=None):
+ # We instantiate a NXT object only when we connect if none supplied
+ self._sock = _sock
+ self.debug = FANTOMSOCK_DEBUG
+
+ def __str__(self):
+ return 'FantomSock BT (%s)' % self.device_name()
+
+ def device_name(self):
+ devinfo = self._sock.get_device_info()
+ return devinfo.name
+
+ def connect(self, addrport=None):
+ if self._sock is None:
+ if self.debug:
+ print "No NXT object assigned"
+ assert addrport is not None
+ # Port is ignored
+ addr, port = addrport
+ paired_addr = pair_bluetooth(addr)
+ if self.debug:
+ print "BT Paired Addr: ", paired_addr
+ self._sock = pyfantom.NXT(paired_addr)
+ else:
+ if self.debug:
+ print "Using existing NXT object"
+ return Brick(self)
+
+ def send(self, data):
+ return self._sock.write(data)
+
+ def recv(self, numbytes=BT_BUFSIZE):
+ '''currently reported broken'''
+ return self._sock.read(numbytes)
+
+ def close(self):
+ if self._sock is not None:
+ self._sock.close()
+ self._sock = None
+
+
+class USBSocket:
+ bsize = USB_BUFSIZE - 4 # USB socket block size
+
+ def __init__(self, device=None):
+ self._sock = device
+ self.debug = FANTOMSOCK_DEBUG
+
+ def __str__(self):
+ return 'FantomSock USB (%s)' % self.device_name()
+
+ def device_name(self):
+ devinfo = self._sock.get_device_info()
+ return devinfo.name
+
+ def connect(self, addrport=None):
+ if self._sock is None:
+ if self.debug:
+ print "No NXT object assigned"
+ assert addrport is not None
+ # Port is ignored
+ addr, port = addrport
+ self._sock = pyfantom.NXT(addr)
+ else:
+ if self.debug:
+ print "Using existing NXT object"
+ return Brick(self)
+
+ def send(self, data):
+ return self._sock.write(data)
+
+ def recv(self, numbytes=USB_BUFSIZE):
+ return self._sock.read(numbytes)
+
+ def close(self):
+ if self._sock is not None:
+ self._sock.close()
+ self._sock = None
+
+
+def find_bricks(host=None, name=None, useBT=False):
+ for d in pyfantom.NXTIterator(useBT):
+ nxt = d.get_nxt()
+ if host or name:
+ info = nxt.get_device_info()
+ if ((host and not info.bluetooth_address == host) or
+ (name and not info.name == name)): #name or host doesn't match
+ continue
+ if useBT:
+ yield BluetoothSocket(nxt)
+ else:
+ yield USBSocket(nxt)
diff --git a/nxt_plugin/nxt/ipsock.py b/nxt_plugin/nxt/ipsock.py
new file mode 100644
index 0000000..66c1a51
--- /dev/null
+++ b/nxt_plugin/nxt/ipsock.py
@@ -0,0 +1,61 @@
+# nxt.ipsock module -- Server socket communication with LEGO Minstorms NXT
+# Copyright (C) 2011 zonedaobne, Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import socket
+from nxt.brick import Brick
+
+class IpSock(object):
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.sock = None
+ self.debug = False
+
+ def __str__(self):
+ return 'Server (%s)' % self.host
+
+ def connect(self):
+ if self.debug:
+ print 'Connecting via Server...'
+ sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect((self.host, self.port))
+ #TODO: sasl authentication here?
+ self.sock = sock
+ if self.debug:
+ print 'Connected.'
+ self.send('\x98')
+ self.type = 'ip'+self.recv()
+ return Brick(self)
+
+ def close(self):
+ if self.debug:
+ print 'Closing Server connection...'
+ self.sock.send('\x99')
+ self.sock.close()
+ if self.debug:
+ print 'Server connection closed.'
+
+ def send(self, data):
+ if self.debug:
+ print 'Send:',
+ print ':'.join('%02x' % ord(c) for c in data)
+ self.sock.send(data)
+
+ def recv(self):
+ data = self.sock.recv(1024)
+ if self.debug:
+ print 'Recv:',
+ print ':'.join('%02x' % ord(c) for c in data)
+ return data
+
+#TODO: add a find_bricks method and a corresponding broadcast system to nxt_server?
diff --git a/nxt_plugin/nxt/lightblueglue.py b/nxt_plugin/nxt/lightblueglue.py
new file mode 100644
index 0000000..f2ab92f
--- /dev/null
+++ b/nxt_plugin/nxt/lightblueglue.py
@@ -0,0 +1,53 @@
+# bluetooth.py module -- Glue code from NXT_Python to Lightblue, allowing
+# NXT_Python to run on Mac without modification. Supports subset of
+# PyBluez/bluetooth.py used by NXT_Python.
+#
+# Copyright (C) 2007 Simon D. Levy
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import lightblue
+
+RFCOMM=11
+
+def discover_devices(lookup_names=False): # parameter is ignored
+ pairs = []
+ d = lightblue.finddevices()
+ for p in d:
+ h = p[0]
+ n = p[1]
+ pairs.append((h, n))
+ return pairs
+
+class BluetoothSocket:
+
+ def __init__(self, proto = RFCOMM, _sock=None):
+ if _sock is None:
+ _sock = lightblue.socket(proto)
+ self._sock = _sock
+ self._proto = proto
+
+ def connect(self, addrport):
+ addr, port = addrport
+ self._sock.connect( (addr, port ))
+
+ def send(self, data):
+ return self._sock.send( data )
+
+ def recv(self, numbytes):
+ return self._sock.recv( numbytes )
+
+ def close(self):
+ return self._sock.close()
+
+class BluetoothError(IOError):
+ pass
+
diff --git a/nxt_plugin/nxt/locator.py b/nxt_plugin/nxt/locator.py
new file mode 100644
index 0000000..46b5ec8
--- /dev/null
+++ b/nxt_plugin/nxt/locator.py
@@ -0,0 +1,177 @@
+# nxt.locator module -- Locate LEGO Minstorms NXT bricks via USB or Bluetooth
+# Copyright (C) 2006, 2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import traceback, ConfigParser, os
+
+class BrickNotFoundError(Exception):
+ pass
+
+class NoBackendError(Exception):
+ pass
+
+class Method():
+ """Used to indicate which comm backends should be tried by find_bricks/
+find_one_brick. Any or all can be selected."""
+ def __init__(self, usb=True, bluetooth=True, fantomusb=False, fantombt=False):
+ #new method options MUST default to False!
+ self.usb = usb
+ self.bluetooth = bluetooth
+ self.fantom = fantomusb or fantombt
+ self.fantomusb = fantomusb
+ self.fantombt = fantombt
+
+def find_bricks(host=None, name=None, silent=False, method=Method()):
+ """Used by find_one_brick to look for bricks ***ADVANCED USERS ONLY***"""
+ methods_available = 0
+
+ if method.usb:
+ try:
+ import usbsock
+ methods_available += 1
+ socks = usbsock.find_bricks(host, name)
+ for s in socks:
+ yield s
+ except ImportError:
+ import sys
+ if not silent: print >>sys.stderr, "USB module unavailable, not searching there"
+
+ if method.bluetooth:
+ try:
+ import bluesock
+ methods_available += 1
+ try:
+ socks = bluesock.find_bricks(host, name)
+ for s in socks:
+ yield s
+ except (bluesock.bluetooth.BluetoothError, IOError): #for cases such as no adapter, bluetooth throws IOError, not BluetoothError
+ pass
+ except ImportError:
+ import sys
+ if not silent: print >>sys.stderr, "Bluetooth module unavailable, not searching there"
+
+ if method.fantom:
+ try:
+ import fantomsock
+ methods_available += 1
+ if method.fantomusb:
+ usbsocks = fantomsock.find_bricks(host, name, False)
+ for s in usbsocks:
+ yield s
+ if method.fantombt:
+ btsocks = fantomsock.find_bricks(host, name, True)
+ for s in btsocks:
+ yield s
+ except ImportError:
+ import sys
+ if not silent: print >>sys.stderr, "Fantom module unavailable, not searching there"
+
+ if methods_available == 0:
+ raise NoBackendError("No selected backends are available! Did you install the comm modules?")
+
+
+def find_one_brick(host=None, name=None, silent=False, strict=None, debug=False, method=None, confpath=None):
+ """Use to find one brick. The host and name args limit the search to
+a given MAC or brick name. Set silent to True to stop nxt-python from
+printing anything during the search. This function by default
+automatically checks to see if the brick found has the correct host/name
+(if either are provided) and will not return a brick which doesn't
+match. This can be disabled (so the function returns any brick which can
+be connected to and provides a valid reply to get_device_info()) by
+passing strict=False. This will, however, still tell the comm backends
+to only look for devices which match the args provided. The confpath arg
+specifies the location of the configuration file which brick location
+information will be read from if no brick location directives (host,
+name, strict, or method) are provided."""
+ if debug and silent:
+ silent=False
+ print "silent and debug can't both be set; giving debug priority"
+
+ conf = read_config(confpath, debug)
+ if not (host or name or strict or method):
+ host = conf.get('Brick', 'host')
+ name = conf.get('Brick', 'name')
+ strict = bool(int(conf.get('Brick', 'strict')))
+ method = eval('Method(%s)' % conf.get('Brick', 'method'))
+ if not strict: strict = True
+ if not method: method = Method()
+ if debug:
+ print "Host: %s Name: %s Strict: %s" % (host, name, str(strict))
+ print "USB: %s BT: %s Fantom: %s FUSB: %s FBT: %s" % (method.usb, method.bluetooth, method.fantom, method.fantombt, method.fantomusb)
+
+ for s in find_bricks(host, name, silent, method):
+ try:
+ if host and 'host' in dir(s) and s.host != host:
+ if debug:
+ print "Warning: the brick found does not match the host provided (s.host)."
+ if strict: continue
+ b = s.connect()
+ info = b.get_device_info()
+ if host and info[1] != host:
+ if debug:
+ print "Warning: the brick found does not match the host provided (get_device_info)."
+ if strict:
+ s.close()
+ continue
+ if name and info[0].strip('\0') != name:
+ if debug:
+ print "Warning; the brick found does not match the name provided."
+ if strict:
+ s.close()
+ continue
+ return b
+ except:
+ if debug:
+ traceback.print_exc()
+ print "Failed to connect to possible brick"
+ #raise BrickNotFoundError
+
+
+def server_brick(host, port = 2727):
+ import ipsock
+ sock = ipsock.IpSock(host, port)
+ return sock.connect()
+
+
+def read_config(confpath=None, debug=False):
+ conf = ConfigParser.RawConfigParser({'host': None, 'name': None, 'strict': True, 'method': ''})
+ if not confpath: confpath = os.path.expanduser('~/.nxt-python')
+ if conf.read([confpath]) == [] and debug:
+ print "Warning: Config file (should be at %s) was not read. Use nxt.locator.make_config() to create a config file." % confpath
+ if conf.has_section('Brick') == False:
+ conf.add_section('Brick')
+ return conf
+
+def make_config(confpath=None):
+ conf = ConfigParser.RawConfigParser()
+ if not confpath: confpath = os.path.expanduser('~/.nxt-python')
+ print "Welcome to the nxt-python config file generator!"
+ print "This function creates an example file which find_one_brick uses to find a brick."
+ try:
+ if os.path.exists(confpath): raw_input("File already exists at %s. Press Enter to overwrite or Ctrl+C to abort." % confpath)
+ except KeyboardInterrupt:
+ print "Not writing file."
+ return
+ conf.add_section('Brick')
+ conf.set('Brick', 'name', 'MyNXT')
+ conf.set('Brick', 'host', '54:32:59:92:F9:39')
+ conf.set('Brick', 'strict', 0)
+ conf.set('Brick', 'method', 'usb=True, bluetooth=False, fantomusb=True')
+ conf.write(open(confpath, 'w'))
+ print "The file has been written at %s" % confpath
+ print "The file contains less-than-sane default values to get you started."
+ print "You must now edit the file with a text editor and change the values to match what you would pass to find_one_brick"
+ print "The fields for name, host, and strict correspond to the similar args accepted by find_one_brick"
+ print "The method field contains the string which would be passed to Method()"
+ print "Any field whose corresponding option does not need to be passed to find_one_brick should be commented out (using a # at the start of the line) or simply removed."
+ print "If you have questions, check the wiki and then ask on the mailing list."
diff --git a/nxt_plugin/nxt/motcont.py b/nxt_plugin/nxt/motcont.py
new file mode 100644
index 0000000..f71ea6b
--- /dev/null
+++ b/nxt_plugin/nxt/motcont.py
@@ -0,0 +1,146 @@
+# nxt.motcont module -- Interface to Linus Atorf's MotorControl NXC
+# Copyright (C) 2011 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import nxt
+import nxt.error
+import time
+from threading import Lock
+
+class MotorConError(nxt.error.ProtocolError):
+ pass
+
+def _power(power):
+ pw = abs(power)
+ psign = int(power >= 0) * 2 - 1
+ if psign == -1:
+ pw += 100
+ pw = str(pw)
+ pw = '0'*(3-len(pw))+pw #pad front with 0s to make 3 chars
+ return pw
+
+def _tacho(tacholimit):
+ tacho = str(tacholimit)
+ tacho = '0'*(6-len(tacho))+tacho #pad front with 0s to make 6 chars
+ return tacho
+
+def interval(delay, lastrun):
+ now = time.time()
+ if lastrun+delay > now:
+ diff = now - lastrun
+ time.sleep(0.010 - diff)
+
+class MotCont():
+ '''
+This class provides an interface to Linus Atorf's MotorControl NXC
+program. It is a wrapper which follows the documentation at
+http://www.mindstorms.rwth-aachen.de/trac/wiki/MotorControl
+and provides command strings and timing intervals as dictated there. To
+use this module, you will need to put MotorControl22.rxe on your NXT
+brick. This file and its corresponding source can be found at
+http://www.mindstorms.rwth-aachen.de/trac/browser/trunk/tools/MotorControl
+You can use nxt_push or any other nxt file manager to put the file on
+the NXT. Before using any of the functions here, use MotCont.start() to
+start the program. You can also start it manually my using the menu on
+the brick. When your script exits, it would be a good idea to do
+b.stop_program().
+'''
+ def __init__(self, brick):
+ self.brick = brick
+ self.is_ready_lock = Lock()
+ self.last_is_ready = time.time()-1
+ self.last_cmd = {}
+
+ def cmd(self, port, power, tacholimit, speedreg=1, smoothstart=0, brake=0):
+ '''
+Sends a "CONTROLLED_MOTORCMD" to MotorControl. port is
+nxt.motor.PORT_[A-C], power is -100-100, tacholimit is 0-999999,
+speedreg is whether to try to maintain speeds under load, and brake is
+whether to enable active braking after the motor is in the specified
+place (DIFFERENT from the nxt.motor.turn() function's brake arg).'''
+ interval(0.010, self.last_is_ready)
+ if port in self.last_cmd:
+ interval(0.015, self.last_cmd[port])
+ mode = str(
+ 0x01*int(brake)+
+ 0x02*int(speedreg)+
+ 0x04*int(smoothstart)
+ )
+ command = '1'+str(port)+_power(power)+_tacho(tacholimit)+mode
+ self.brick.message_write(1, command)
+ self.last_cmd[port] = time.time()
+
+ def move_to(self, port, power, tachocount, speedreg=1, smoothstart=0, brake=0):
+ '''
+Same as cmd(), except that the tachocount is subtracted from the motor's
+current position and that value is used to turn the motor. Power is
+-100-100, but the sign is rewritten as needed.'''
+ tacho = nxt.Motor(self.brick, port).get_tacho().block_tacho_count
+ tacho = tachocount-tacho
+ tsign = int(tacho >= 0) * 2 - 1
+ tacho = abs(tacho)
+ power = abs(power)*tsign
+ self.cmd(port, power, tacho, speedreg, smoothstart, brake)
+
+ def reset_tacho(self, port):
+ '''
+Sends a "RESET_ERROR_CORRECTION" to MotorControl, which causes it to
+reset the current tacho count for that motor.'''
+ interval(0.010, self.last_is_ready)
+ self.brick.message_write(1, '2'+str(port))
+ self.last_cmd[port] = time.time()
+
+ def is_ready(self, port):
+ '''
+Sends an "ISMOTORREADY" to MotorControl and returns the reply.'''
+ interval(0.010, self.last_is_ready)
+ with self.is_ready_lock:
+ self.brick.message_write(1, '3'+str(port))
+ time.sleep(0.015) #10ms pause from the docs seems to not be adequate
+ reply = self.brick.message_read(0, 1, 1)[1]
+ if reply[0] != str(port):
+ raise MotorConError, 'Wrong port returned from ISMOTORREADY'
+ self.last_is_ready = time.time()
+ return bool(int(reply[1]))
+
+ def set_output_state(self, port, power, tacholimit, speedreg=1):
+ '''
+Sends a "CLASSIC_MOTORCMD" to MotorControl. Brick is a brick object,
+port is nxt.motor.PORT_[A-C], power is -100-100, tacholimit is 0-999999,
+speedreg is whether to try to maintain speeds under load, and brake is
+whether to enable active braking after the motor is in the specified
+place (DIFFERENT from the nxt.motor.turn() function's brake arg).'''
+ interval(0.010, self.last_is_ready)
+ if port in self.last_cmd:
+ interval(0.015, self.last_cmd[port])
+ command = '4'+str(port)+_power(power)+_tacho(tacholimit)+str(speedreg)
+ self.brick.message_write(1, command)
+ self.last_cmd[port] = time.time()
+
+ def start(self, version=22):
+ '''
+Starts the MotorControl program on the brick. It needs to already be
+present on the brick's flash and named MotorControlXX.rxc, where XX is
+the version number passed as the version arg (default is whatever is
+bundled with this version of nxt-python).'''
+ try:
+ self.brick.stop_program()
+ except nxt.error.DirProtError:
+ pass
+ self.brick.start_program('MotorControl%d.rxe' % version)
+ time.sleep(0.1)
+
+ def stop(self):
+ '''
+Used to stop the MotorControl program. All this actually does is stop
+the currently running rxe.'''
+ self.brick.stop_program()
diff --git a/nxt_plugin/nxt/motor.py b/nxt_plugin/nxt/motor.py
new file mode 100644
index 0000000..a2f0040
--- /dev/null
+++ b/nxt_plugin/nxt/motor.py
@@ -0,0 +1,431 @@
+# nxt.motor module -- Class to control LEGO Mindstorms NXT motors
+# Copyright (C) 2006 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, rhn
+# Copyright (C) 2010 rhn
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+"""Use for motor control"""
+
+import time
+
+PORT_A = 0x00
+PORT_B = 0x01
+PORT_C = 0x02
+PORT_ALL = 0xFF
+
+MODE_IDLE = 0x00
+MODE_MOTOR_ON = 0x01
+MODE_BRAKE = 0x02
+MODE_REGULATED = 0x04
+
+REGULATION_IDLE = 0x00
+REGULATION_MOTOR_SPEED = 0x01
+REGULATION_MOTOR_SYNC = 0x02
+
+RUN_STATE_IDLE = 0x00
+RUN_STATE_RAMP_UP = 0x10
+RUN_STATE_RUNNING = 0x20
+RUN_STATE_RAMP_DOWN = 0x40
+
+LIMIT_RUN_FOREVER = 0
+
+class BlockedException(Exception):
+ pass
+
+class OutputState(object):
+ """An object holding the internal state of a motor, not including rotation
+ counters.
+ """
+ def __init__(self, values):
+ (self.power, self.mode, self.regulation,
+ self.turn_ratio, self.run_state, self.tacho_limit) = values
+
+ def to_list(self):
+ """Returns a list of properties that can be used with set_output_state.
+ """
+ return [self.power, self.mode, self.regulation,
+ self.turn_ratio, self.run_state, self.tacho_limit]
+
+ def __str__(self):
+ modes = []
+ if self.mode & MODE_MOTOR_ON:
+ modes.append('on')
+ if self.mode & MODE_BRAKE:
+ modes.append('brake')
+ if self.mode & MODE_REGULATED:
+ modes.append('regulated')
+ if not modes:
+ modes.append('idle')
+ mode = '&'.join(modes)
+ regulation = 'regulation: ' + \
+ ['idle', 'speed', 'sync'][self.regulation]
+ run_state = 'run state: ' + {0: 'idle', 0x10: 'ramp_up',
+ 0x20: 'running', 0x40: 'ramp_down'}[self.run_state]
+ return ', '.join([mode, regulation, str(self.turn_ratio), run_state] + [str(self.tacho_limit)])
+
+
+class TachoInfo:
+ """An object containing the information about the rotation of a motor"""
+ def __init__(self, values):
+ self.tacho_count, self.block_tacho_count, self.rotation_count = values
+
+ def get_target(self, tacho_limit, direction):
+ """Returns a TachoInfo object which corresponds to tacho state after
+ moving for tacho_limit ticks. Direction can be 1 (add) or -1 (subtract)
+ """
+ # TODO: adjust other fields
+ if abs(direction) != 1:
+ raise ValueError('Invalid direction')
+ new_tacho = self.tacho_count + direction * tacho_limit
+ return TachoInfo([new_tacho, None, None])
+
+ def is_greater(self, target, direction):
+ return direction * (self.tacho_count - target.tacho_count) > 0
+
+ def is_near(self, target, threshold):
+ difference = abs(target.tacho_count - self.tacho_count)
+ return difference < threshold
+
+ def __str__(self):
+ return str((self.tacho_count, self.block_tacho_count,
+ self.rotation_count))
+
+
+class SynchronizedTacho(object):
+ def __init__(self, leader_tacho, follower_tacho):
+ self.leader_tacho = leader_tacho
+ self.follower_tacho = follower_tacho
+
+ def get_target(self, tacho_limit, direction):
+ """This method will leave follower's target as None"""
+ leader_tacho = self.leader_tacho.get_target(tacho_limit, direction)
+ return SynchronizedTacho(leader_tacho, None)
+
+ def is_greater(self, other, direction):
+ return self.leader_tacho.is_greater(other.leader_tacho, direction)
+
+ def is_near(self, other, threshold):
+ return self.leader_tacho.is_near(other.leader_tacho, threshold)
+
+ def __str__(self):
+ if self.follower_tacho is not None:
+ t2 = str(self.follower_tacho.tacho_count)
+ else:
+ t2 = 'None'
+ t1 = str(self.leader_tacho.tacho_count)
+ return 'tacho: ' + t1 + ' ' + t2
+
+
+def get_tacho_and_state(values):
+ """A convenience function. values is the list of values from
+ get_output_state. Returns both OutputState and TachoInfo.
+ """
+ return OutputState(values[1:7]), TachoInfo(values[7:])
+
+
+class BaseMotor(object):
+ """Base class for motors"""
+ debug = 0
+ def _debug_out(self, message):
+ if self.debug:
+ print message
+
+ def turn(self, power, tacho_units, brake=True, timeout=1, emulate=True):
+ """Use this to turn a motor. The motor will not stop until it turns the
+ desired distance. Accuracy is much better over a USB connection than
+ with bluetooth...
+ power is a value between -127 and 128 (an absolute value greater than
+ 64 is recommended)
+ tacho_units is the number of degrees to turn the motor. values smaller
+ than 50 are not recommended and may have strange results.
+ brake is whether or not to hold the motor after the function exits
+ (either by reaching the distance or throwing an exception).
+ timeout is the number of seconds after which a BlockedException is
+ raised if the motor doesn't turn
+ emulate is a boolean value. If set to False, the motor is aware of the
+ tacho limit. If True, a run() function equivalent is used.
+ Warning: motors remember their positions and not using emulate
+ may lead to strange behavior, especially with synced motors
+ """
+
+ tacho_limit = tacho_units
+
+ if tacho_limit < 0:
+ raise ValueError, "tacho_units must be greater than 0!"
+ #TODO Calibrate the new values for ip socket latency.
+ if self.method == 'bluetooth':
+ threshold = 70
+ elif self.method == 'usb':
+ threshold = 5
+ elif self.method == 'ipbluetooth':
+ threshold = 80
+ elif self.method == 'ipusb':
+ threshold = 15
+ else:
+ threshold = 30 #compromise
+
+ tacho = self.get_tacho()
+ state = self._get_new_state()
+
+ # Update modifiers even if they aren't used, might have been changed
+ state.power = power
+ if not emulate:
+ state.tacho_limit = tacho_limit
+
+ self._debug_out('Updating motor information...')
+ self._set_state(state)
+
+ direction = 1 if power > 0 else -1
+ self._debug_out('tachocount: ' + str(tacho))
+ current_time = time.time()
+ tacho_target = tacho.get_target(tacho_limit, direction)
+
+ blocked = False
+ try:
+ while True:
+ time.sleep(self._eta(tacho, tacho_target, power) / 2)
+
+ if not blocked: # if still blocked, don't reset the counter
+ last_tacho = tacho
+ last_time = current_time
+
+ tacho = self.get_tacho()
+ current_time = time.time()
+ blocked = self._is_blocked(tacho, last_tacho, direction)
+ if blocked:
+ self._debug_out(('not advancing', last_tacho, tacho))
+ # the motor can be up to 80+ degrees in either direction from target when using bluetooth
+ if current_time - last_time > timeout:
+ if tacho.is_near(tacho_target, threshold):
+ break
+ else:
+ raise BlockedException("Blocked!")
+ else:
+ self._debug_out(('advancing', last_tacho, tacho))
+ if tacho.is_near(tacho_target, threshold) or tacho.is_greater(tacho_target, direction):
+ break
+ finally:
+ if brake:
+ self.brake()
+ else:
+ self.idle()
+
+
+class Motor(BaseMotor):
+ def __init__(self, brick, port):
+ self.brick = brick
+ self.port = port
+ self._read_state()
+ self.sync = 0
+ self.turn_ratio = 0
+ try:
+ self.method = brick.sock.type
+ except:
+ print "Warning: Socket did not report a type!"
+ print "Please report this problem to the developers!"
+ print "For now, turn() accuracy will not be optimal."
+ print "Continuing happily..."
+ self.method = None
+
+ def _set_state(self, state):
+ self._debug_out('Setting brick output state...')
+ list_state = [self.port] + state.to_list()
+ self.brick.set_output_state(*list_state)
+ self._debug_out(state)
+ self._state = state
+ self._debug_out('State set.')
+
+ def _read_state(self):
+ self._debug_out('Getting brick output state...')
+ values = self.brick.get_output_state(self.port)
+ self._debug_out('State got.')
+ self._state, tacho = get_tacho_and_state(values)
+ return self._state, tacho
+
+ #def get_tacho_and_state here would allow tacho manipulation
+
+ def _get_state(self):
+ """Returns a copy of the current motor state for manipulation."""
+ return OutputState(self._state.to_list())
+
+ def _get_new_state(self):
+ state = self._get_state()
+ if self.sync:
+ state.mode = MODE_MOTOR_ON | MODE_REGULATED
+ state.regulation = REGULATION_MOTOR_SYNC
+ state.turn_ratio = self.turn_ratio
+ else:
+ state.mode = MODE_MOTOR_ON | MODE_REGULATED
+ state.regulation = REGULATION_MOTOR_SPEED
+ state.run_state = RUN_STATE_RUNNING
+ state.tacho_limit = LIMIT_RUN_FOREVER
+ return state
+
+ def get_tacho(self):
+ return self._read_state()[1]
+
+ def reset_position(self, relative):
+ """Resets the counters. Relative can be True or False"""
+ self.brick.reset_motor_position(self.port, relative)
+
+ def run(self, power=100, regulated=False):
+ '''Tells the motor to run continuously. If regulated is True, then the
+ synchronization starts working.
+ '''
+ state = self._get_new_state()
+ state.power = power
+ if not regulated:
+ state.mode = MODE_MOTOR_ON
+ self._set_state(state)
+
+ def brake(self):
+ """Holds the motor in place"""
+ state = self._get_new_state()
+ state.power = 0
+ state.mode = MODE_MOTOR_ON | MODE_BRAKE | MODE_REGULATED
+ self._set_state(state)
+
+ def idle(self):
+ '''Tells the motor to stop whatever it's doing. It also desyncs it'''
+ state = self._get_new_state()
+ state.power = 0
+ state.mode = MODE_IDLE
+ state.regulation = REGULATION_IDLE
+ state.run_state = RUN_STATE_IDLE
+ self._set_state(state)
+
+ def weak_turn(self, power, tacho_units):
+ """Tries to turn a motor for the specified distance. This function
+ returns immediately, and it's not guaranteed that the motor turns that
+ distance. This is an interface to use tacho_limit without
+ REGULATION_MODE_SPEED
+ """
+ tacho_limit = tacho_units
+ tacho = self.get_tacho()
+ state = self._get_new_state()
+
+ # Update modifiers even if they aren't used, might have been changed
+ state.mode = MODE_MOTOR_ON
+ state.regulation = REGULATION_IDLE
+ state.power = power
+ state.tacho_limit = tacho_limit
+
+ self._debug_out('Updating motor information...')
+ self._set_state(state)
+
+ def _eta(self, current, target, power):
+ """Returns time in seconds. Do not trust it too much"""
+ tacho = abs(current.tacho_count - target.tacho_count)
+ return (float(tacho) / abs(power)) / 5
+
+ def _is_blocked(self, tacho, last_tacho, direction):
+ """Returns if any of the engines is blocked"""
+ return direction * (last_tacho.tacho_count - tacho.tacho_count) >= 0
+
+
+class SynchronizedMotors(BaseMotor):
+ """The object used to make two motors run in sync. Many objects may be
+ present at the same time but they can't be used at the same time.
+ Warning! Movement methods reset tacho counter.
+ THIS CODE IS EXPERIMENTAL!!!
+ """
+ def __init__(self, leader, follower, turn_ratio):
+ """Turn ratio can be >= 0 only! If you want to have it reversed,
+ change motor order.
+ """
+ if follower.brick != leader.brick:
+ raise ValueError('motors belong to different bricks')
+ self.leader = leader
+ self.follower = follower
+ self.method = self.leader.method #being from the same brick, they both have the same com method.
+
+ if turn_ratio < 0:
+ raise ValueError('Turn ratio <0. Change motor order instead!')
+
+ if self.leader.port == self.follower.port:
+ raise ValueError("The same motor passed twice")
+ elif self.leader.port > self.follower.port:
+ self.turn_ratio = turn_ratio
+ else:
+ self._debug_out('reversed')
+ self.turn_ratio = -turn_ratio
+
+ def _get_new_state(self):
+ return self.leader._get_new_state()
+
+ def _set_state(self, state):
+ self.leader._set_state(state)
+ self.follower._set_state(state)
+
+ def get_tacho(self):
+ leadertacho = self.leader.get_tacho()
+ followertacho = self.follower.get_tacho()
+ return SynchronizedTacho(leadertacho, followertacho)
+
+ def reset_position(self, relative):
+ """Resets the counters. Relative can be True or False"""
+ self.leader.reset_position(relative)
+ self.follower.reset_position(relative)
+
+ def _enable(self): # This works as expected. I'm not sure why.
+ #self._disable()
+ self.reset_position(True)
+ self.leader.sync = True
+ self.follower.sync = True
+ self.leader.turn_ratio = self.turn_ratio
+ self.follower.turn_ratio = self.turn_ratio
+
+ def _disable(self): # This works as expected. (tacho is reset ok)
+ self.leader.sync = False
+ self.follower.sync = False
+ #self.reset_position(True)
+ self.leader.idle()
+ self.follower.idle()
+ #self.reset_position(True)
+
+ def run(self, power=100):
+ """Warning! After calling this method, make sure to call idle. The
+ motors are reported to behave wildly otherwise.
+ """
+ self._enable()
+ self.leader.run(power, True)
+ self.follower.run(power, True)
+
+ def brake(self):
+ self._disable() # reset the counters
+ self._enable()
+ self.leader.brake() # brake both motors at the same time
+ self.follower.brake()
+ self._disable() # now brake as usual
+ self.leader.brake()
+ self.follower.brake()
+
+ def idle(self):
+ self._disable()
+
+ def turn(self, power, tacho_units, brake=True, timeout=1):
+ self._enable()
+ # non-emulation is a nightmare, tacho is being counted differently
+ try:
+ if power < 0:
+ self.leader, self.follower = self.follower, self.leader
+ BaseMotor.turn(self, power, tacho_units, brake, timeout, emulate=True)
+ finally:
+ if power < 0:
+ self.leader, self.follower = self.follower, self.leader
+
+ def _eta(self, tacho, target, power):
+ return self.leader._eta(tacho.leader_tacho, target.leader_tacho, power)
+
+ def _is_blocked(self, tacho, last_tacho, direction):
+ # no need to check both, they're synced
+ return self.leader._is_blocked(tacho.leader_tacho, last_tacho.leader_tacho, direction)
diff --git a/nxt_plugin/nxt/sensor/__init__.py b/nxt_plugin/nxt/sensor/__init__.py
new file mode 100644
index 0000000..8f2a337
--- /dev/null
+++ b/nxt_plugin/nxt/sensor/__init__.py
@@ -0,0 +1,50 @@
+# nxt.sensor module -- Classes to read LEGO Mindstorms NXT sensors
+# Copyright (C) 2006,2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, Paulo Vieira, rhn
+# Copyright (C) 2010 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+from .common import *
+from .analog import BaseAnalogSensor
+from .digital import BaseDigitalSensor, find_class
+from .generic import Touch, Light, Sound, Ultrasonic, Color20
+import mindsensors
+MSSumoEyes = mindsensors.SumoEyes
+MSCompassv2 = mindsensors.Compassv2
+MSDIST = mindsensors.DIST
+MSRTC = mindsensors.RTC
+MSACCL = mindsensors.ACCL
+MSServo = mindsensors.Servo
+MSMTRMUX = mindsensors.MTRMUX
+MSLineLeader = mindsensors.LineLeader
+MSMMX = mindsensors.MMX
+MSPS2 = mindsensors.PS2
+MSHID = mindsensors.HID
+import hitechnic
+HTCompass = hitechnic.Compass
+HTAccelerometer = hitechnic.Accelerometer
+HTGyro = hitechnic.Gyro
+HTColorv2 = hitechnic.Colorv2
+HTEOPD = hitechnic.EOPD
+HTIRReceiver = hitechnic.IRReceiver
+HTIRSeekerv2 = hitechnic.IRSeekerv2
+HTPrototype = hitechnic.Prototype
+
+
+def get_sensor(brick, port):
+ """Tries to detect the sensor type and return the correct sensor
+object. Does not work for sensors with no identification information (such as
+all analog sensors or the MindSensors RTC.
+ """
+ base_sensor = BaseDigitalSensor(brick, port, False)
+ info = base_sensor.get_sensor_info()
+ return find_class(info)(brick, port, check_compatible=False)
diff --git a/nxt_plugin/nxt/sensor/analog.py b/nxt_plugin/nxt/sensor/analog.py
new file mode 100644
index 0000000..e8d9b7b
--- /dev/null
+++ b/nxt_plugin/nxt/sensor/analog.py
@@ -0,0 +1,41 @@
+# nxt.sensor.analog module -- submodule for use with analog sensors
+# Copyright (C) 2006,2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, Paulo Vieira, rhn
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+from common import *
+
+
+class RawReading: # can be converted to the old version
+ """A pseudo-structure holding the raw sensor values as returned by the NXT
+ brick.
+ """
+ def __init__(self, values):
+ (self.port, self.valid, self.calibrated, self.sensor_type, self.mode,
+ self.raw_ad_value, self.normalized_ad_value, self.scaled_value,
+ self.calibrated_value) = values
+
+ def __repr__(self):
+ return str((self.port, self.valid, self.calibrated, self.sensor_type, self.mode,
+ self.raw_ad_value, self.normalized_ad_value, self.scaled_value,
+ self.calibrated_value))
+
+
+class BaseAnalogSensor(Sensor):
+ """Object for analog sensors."""
+ def get_input_values(self):
+ """Returns the raw sensor values as returned by the NXT brick."""
+ return RawReading(self.brick.get_input_values(self.port))
+
+ def reset_input_scaled_value(self):
+ self.brick.reset_input_scaled_value()
+
diff --git a/nxt_plugin/nxt/sensor/common.py b/nxt_plugin/nxt/sensor/common.py
new file mode 100644
index 0000000..5afd6c8
--- /dev/null
+++ b/nxt_plugin/nxt/sensor/common.py
@@ -0,0 +1,67 @@
+# nxt.sensor.common module -- submodule with stuff useful in all sensors
+# Copyright (C) 2006,2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, Paulo Vieira, rhn
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+PORT_1 = 0x00
+PORT_2 = 0x01
+PORT_3 = 0x02
+PORT_4 = 0x03
+
+class Type(object):
+ 'Namespace for enumeration of the type of sensor'
+ # NOTE: just a namespace (enumeration)
+ NO_SENSOR = 0x00
+ SWITCH = 0x01 # Touch sensor
+ TEMPERATURE = 0x02
+ REFLECTION = 0x03
+ ANGLE = 0x04
+ LIGHT_ACTIVE = 0x05 # Light sensor (illuminated)
+ LIGHT_INACTIVE = 0x06 # Light sensor (ambient)
+ SOUND_DB = 0x07 # Sound sensor (unadjusted)
+ SOUND_DBA = 0x08 # Sound sensor (adjusted)
+ CUSTOM = 0x09
+ LOW_SPEED = 0x0A
+ LOW_SPEED_9V = 0x0B # Low-speed I2C (Ultrasonic sensor)
+ HIGH_SPEED = 0x0C #Possibly other mode for I2C; may be used by future sensors.
+ COLORFULL = 0x0D #NXT 2.0 color sensor in full color mode (color sensor mode)
+ COLORRED = 0x0E #NXT 2.0 color sensor with red light on (light sensor mode)
+ COLORGREEN = 0x0F #NXT 2.0 color sensor with green light on (light sensor mode)
+ COLORBLUE = 0x10 #NXT 2.0 color sensor in with blue light on (light sensor mode)
+ COLORNONE = 0x11 #NXT 2.0 color sensor in with light off (light sensor mode)
+ COLOREXIT = 0x12 #NXT 2.0 color sensor internal state (not sure what this is for yet)
+
+
+class Mode(object):
+ 'Namespace for enumeration of the mode of sensor'
+ # NOTE: just a namespace (enumeration)
+ RAW = 0x00
+ BOOLEAN = 0x20
+ TRANSITION_CNT = 0x40
+ PERIOD_COUNTER = 0x60
+ PCT_FULL_SCALE = 0x80
+ CELSIUS = 0xA0
+ FAHRENHEIT = 0xC0
+ ANGLE_STEPS = 0xE0
+ MASK = 0xE0
+ MASK_SLOPE = 0x1F # Why isn't this slope thing documented?
+
+
+class Sensor(object):
+ 'Main sensor object'
+
+ def __init__(self, brick, port):
+ self.brick = brick
+ self.port = port
+
+ def set_input_mode(self, type_, mode):
+ self.brick.set_input_mode(self.port, type_, mode)
diff --git a/nxt_plugin/nxt/sensor/digital.py b/nxt_plugin/nxt/sensor/digital.py
new file mode 100644
index 0000000..a2dafba
--- /dev/null
+++ b/nxt_plugin/nxt/sensor/digital.py
@@ -0,0 +1,229 @@
+# nxt.sensor module -- Classes to read LEGO Mindstorms NXT sensors
+# Copyright (C) 2006,2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, Paulo Vieira, rhn
+# Copyright (C) 2010,2011 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+from nxt.error import I2CError, I2CPendingError, DirProtError
+
+from common import *
+from time import sleep, time
+import struct
+
+
+class SensorInfo:
+ def __init__(self, version, product_id, sensor_type):
+ self.version = version
+ self.product_id = product_id
+ self.sensor_type = sensor_type
+
+ def clarifybinary(self, instr, label):
+ outstr = ''
+ outstr += (label + ': `' + instr + '`\n')
+ for char in instr:
+ outstr += (hex(ord(char))+', ')
+ outstr += ('\n')
+ return outstr
+
+ def __str__(self):
+ outstr = ''
+ outstr += (self.clarifybinary(str(self.version), 'Version'))
+ outstr += (self.clarifybinary(str(self.product_id), 'Product ID'))
+ outstr += (self.clarifybinary(str(self.sensor_type), 'Type'))
+ return outstr
+
+class BaseDigitalSensor(Sensor):
+ """Object for digital sensors. I2C_ADDRESS is the dictionary storing name
+ to i2c address mappings. It should be updated in every subclass. When
+ subclassing this class, make sure to call add_compatible_sensor to add
+ compatible sensor data.
+ """
+ I2C_DEV = 0x02
+ I2C_ADDRESS = {'version': (0x00, '8s'),
+ 'product_id': (0x08, '8s'),
+ 'sensor_type': (0x10, '8s'),
+# 'factory_zero': (0x11, 1), # is this really correct?
+ 'factory_scale_factor': (0x12, 'B'),
+ 'factory_scale_divisor': (0x13, 'B'),
+ }
+
+ def __init__(self, brick, port, check_compatible=True):
+ """Creates a BaseDigitalSensor. If check_compatible is True, queries
+ the sensor for its name, and if a wrong sensor class was used, prints
+ a warning.
+ """
+ super(BaseDigitalSensor, self).__init__(brick, port)
+ self.set_input_mode(Type.LOW_SPEED_9V, Mode.RAW)
+ self.last_poll = time()
+ self.poll_delay = 0.01
+ sleep(0.1) # Give I2C time to initialize
+ #Don't do type checking if this class has no compatible sensors listed.
+ try: self.compatible_sensors
+ except AttributeError: check_compatible = False
+ if check_compatible:
+ sensor = self.get_sensor_info()
+ if not sensor in self.compatible_sensors:
+ print ('WARNING: Wrong sensor class chosen for sensor ' +
+ str(sensor.product_id) + ' on port ' + str(port) + '. ' + """
+You may be using the wrong type of sensor or may have connected the cable
+incorrectly. If you are sure you're using the correct sensor class for the
+sensor, this message is likely in error and you should disregard it and file a
+bug report, including the output of get_sensor_info(). This message can be
+suppressed by passing "check_compatible=False" when creating the sensor object.""")
+
+ def _ls_get_status(self, n_bytes):
+ for n in range(10):
+ try:
+ b = self.brick.ls_get_status(self.port)
+ if b >= n_bytes:
+ return b
+ except I2CPendingError:
+ pass
+ raise I2CError, 'ls_get_status timeout'
+
+ def _i2c_command(self, address, value, format):
+ """Writes an i2c value to the given address. value must be a string. value is
+ a tuple of values corresponding to the given format.
+ """
+ value = struct.pack(format, *value)
+ msg = chr(self.I2C_DEV) + chr(address) + value
+ now = time()
+ if self.last_poll+self.poll_delay > now:
+ diff = now - self.last_poll
+ sleep(self.poll_delay - diff)
+ self.last_poll = time()
+ self.brick.ls_write(self.port, msg, 0)
+
+ def _i2c_query(self, address, format):
+ """Reads an i2c value from given address, and returns a value unpacked
+ according to the given format. Format is the same as in the struct
+ module. See http://docs.python.org/library/struct.html#format-strings
+ """
+ n_bytes = struct.calcsize(format)
+ msg = chr(self.I2C_DEV) + chr(address)
+ now = time()
+ if self.last_poll+self.poll_delay > now:
+ diff = now - self.last_poll
+ sleep(self.poll_delay - diff)
+ self.last_poll = time()
+ self.brick.ls_write(self.port, msg, n_bytes)
+ try:
+ self._ls_get_status(n_bytes)
+ finally:
+ #we should clear the buffer no matter what happens
+ data = self.brick.ls_read(self.port)
+ if len(data) < n_bytes:
+ raise I2CError, 'Read failure: Not enough bytes'
+ data = struct.unpack(format, data[-n_bytes:])
+ return data
+
+ def read_value(self, name):
+ """Reads a value from the sensor. Name must be a string found in
+ self.I2C_ADDRESS dictionary. Entries in self.I2C_ADDRESS are in the
+ name: (address, format) form, with format as in the struct module.
+ Be careful on unpacking single variables - struct module puts them in
+ tuples containing only one element.
+ """
+ address, fmt = self.I2C_ADDRESS[name]
+ for n in range(3):
+ try:
+ return self._i2c_query(address, fmt)
+ except DirProtError:
+ pass
+ raise I2CError, "read_value timeout"
+
+ def write_value(self, name, value):
+ """Writes value to the sensor. Name must be a string found in
+ self.I2C_ADDRESS dictionary. Entries in self.I2C_ADDRESS are in the
+ name: (address, format) form, with format as in the struct module.
+ value is a tuple of values corresponding to the format from
+ self.I2C_ADDRESS dictionary.
+ """
+ address, fmt = self.I2C_ADDRESS[name]
+ self._i2c_command(address, value, fmt)
+
+ def get_sensor_info(self):
+ version = self.read_value('version')[0].split('\0')[0]
+ product_id = self.read_value('product_id')[0].split('\0')[0]
+ sensor_type = self.read_value('sensor_type')[0].split('\0')[0]
+ return SensorInfo(version, product_id, sensor_type)
+
+ @classmethod
+ def add_compatible_sensor(cls, version, product_id, sensor_type):
+ """Adds an entry in the compatibility table for the sensor. If version
+ is None, then it's the default class for this model. If product_id is
+ None, then this is the default class for this vendor.
+ """
+ try:
+ cls.compatible_sensors
+ except AttributeError:
+ cls.compatible_sensors = []
+ finally:
+ cls.compatible_sensors.append(SCompatibility(version, product_id,
+ sensor_type))
+ add_mapping(cls, version, product_id, sensor_type)
+
+
+class SCompatibility(SensorInfo):
+ """An object that helps manage the sensor mappings"""
+ def __eq__(self, other):
+ if self.product_id is None:
+ return self.product_id == other.product_id
+ elif self.version is None:
+ return (self.product_id == other.product_id and
+ self.sensor_type == other.sensor_type)
+ else:
+ return (self.version == other.version and
+ self.product_id == other.product_id and
+ self.sensor_type == other.sensor_type)
+
+sensor_mappings = {}
+
+
+def add_mapping(cls, version, product_id, sensor_type):
+ "None means any other value"
+ if product_id not in sensor_mappings:
+ sensor_mappings[product_id] = {}
+ models = sensor_mappings[product_id]
+
+ if sensor_type is None:
+ if sensor_type in models:
+ raise ValueError('Already registered!')
+ models[sensor_type] = cls
+ return
+
+ if sensor_type not in models:
+ models[sensor_type] = {}
+ versions = models[sensor_type]
+
+ if version in versions:
+ raise ValueError('Already registered!')
+ else:
+ versions[version] = cls
+
+
+class SearchError(Exception):
+ pass
+
+
+def find_class(info):
+ """Returns an appropriate class for the given SensorInfo"""
+ dic = sensor_mappings
+ for val, msg in zip((info.product_id, info.sensor_type, info.version),
+ ('Vendor', 'Model', 'Version')):
+ if val in dic:
+ dic = dic[val]
+ elif None in dic:
+ dic = dic[None]
+ else:
+ raise SearchError(msg + ' not found')
+ return dic[info.sensor_type][None]
diff --git a/nxt_plugin/nxt/sensor/generic.py b/nxt_plugin/nxt/sensor/generic.py
new file mode 100644
index 0000000..16c3895
--- /dev/null
+++ b/nxt_plugin/nxt/sensor/generic.py
@@ -0,0 +1,158 @@
+# nxt.sensor.generic module -- Classes to read LEGO Mindstorms NXT sensors
+# Copyright (C) 2006,2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, Paulo Vieira, rhn
+# Copyright (C) 2010 melducky, Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+from .common import *
+from .digital import BaseDigitalSensor
+from .analog import BaseAnalogSensor
+
+
+class Touch(BaseAnalogSensor):
+ """The LEGO touch sensor"""
+
+ def __init__(self, brick, port):
+ super(Touch, self).__init__(brick, port)
+ self.set_input_mode(Type.SWITCH, Mode.BOOLEAN)
+
+ def is_pressed(self):
+ return bool(self.get_input_values().scaled_value)
+
+ get_sample = is_pressed
+
+
+class Light(BaseAnalogSensor):
+ """Object for light sensors. It automatically turns off light when it's not
+ used.
+ """
+ def __init__(self, brick, port, illuminated=True):
+ super(Light, self).__init__(brick, port)
+
+ def set_illuminated(self, active):
+ if active:
+ type_ = Type.LIGHT_ACTIVE
+ else:
+ type_ = Type.LIGHT_INACTIVE
+ self.set_input_mode(type_, Mode.RAW)
+
+ def get_lightness(self):
+ return self.get_input_values().scaled_value
+
+ get_sample = get_lightness
+
+
+class Sound(BaseAnalogSensor):
+ 'Object for sound sensors'
+
+ def __init__(self, brick, port, adjusted=True):
+ super(Sound, self).__init__(brick, port)
+ self.set_adjusted(adjusted)
+
+ def set_adjusted(self, active):
+ if active:
+ type_ = Type.SOUND_DBA
+ else:
+ type_ = Type.SOUND_DB
+ self.set_input_mode(type_, Mode.RAW)
+
+ def get_loudness(self):
+ return self.get_input_values().scaled_value
+
+ get_sample = get_loudness
+
+
+class Ultrasonic(BaseDigitalSensor):
+ """Object for ultrasonic sensors"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'measurement_units': (0x14, '7s'),
+ 'continuous_measurement_interval': (0x40, 'B'),
+ 'command': (0x41, 'B'),
+ 'measurement_byte_0': (0x42, 'B'),
+ 'measurements': (0x42, '8B'),
+ 'actual_scale_factor': (0x51, 'B'),
+ 'actual_scale_divisor': (0x52, 'B'),
+ })
+
+ class Commands:
+ 'These are for passing to command()'
+ OFF = 0x00
+ SINGLE_SHOT = 0x01
+ CONTINUOUS_MEASUREMENT = 0x02
+ EVENT_CAPTURE = 0x03 #Optimize results when other Ultrasonic sensors running
+ REQUEST_WARM_RESET = 0x04
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(Ultrasonic, self).__init__(brick, port, check_compatible)
+ self.set_input_mode(Type.LOW_SPEED_9V, Mode.RAW)
+
+ def get_distance(self):
+ 'Function to get data from the ultrasonic sensor'
+ return self.read_value('measurement_byte_0')[0]
+
+ get_sample = get_distance
+
+ def get_measurement_units(self):
+ return self.read_value('measurement_units')[0].split('\0')[0]
+
+ def get_all_measurements(self):
+ "Returns all the past readings in measurement_byte_0 through 7"
+ return self.read_value('measurements')
+
+ def get_measurement_no(self, number):
+ "Returns measurement_byte_number"
+ if not 0 <= number < 8:
+ raise ValueError('Measurements are numbered 0 to 7, not ' + str(number))
+ base_address, format = self.I2C_ADDRESS['measurement_byte_0']
+ return self._i2c_query(base_address + number, format)[0]
+
+ def command(self, command):
+ self.write_value('command', (command, ))
+
+ def get_interval(self):
+ 'Get the sample interval for continuous measurement mode -- Unknown units'
+ return self.read_value('continuous_measurement_interval')
+
+ def set_interval(self, interval):
+ """Set the sample interval for continuous measurement mode.
+Unknown units; default is 1"""
+ self.write_value('continuous_measurement_interval', interval)
+
+Ultrasonic.add_compatible_sensor(None, 'LEGO', 'Sonar') #Tested with version 'V1.0'
+
+
+class Color20(BaseAnalogSensor):
+ def __init__(self, brick, port):
+ super(Color20, self).__init__(brick, port)
+ self.set_light_color(Type.COLORFULL)
+
+ def set_light_color(self, color):
+ """color should be one of the COLOR* Type namespace values, e.g. Type.COLORBLUE"""
+ self.set_input_mode(color, Mode.RAW)
+
+ def get_light_color(self):
+ """Returns one of the COLOR* Type namespace values, e.g. Type.COLORRED"""
+ return self.get_input_values().sensor_type
+
+ def get_reflected_light(self, color):
+ self.set_light_color(color)
+ return self.get_input_values().scaled_value
+
+ def get_color(self):
+ self.get_reflected_light(Type.COLORFULL)
+ return self.get_input_values().scaled_value
+
+ def get_light(self):
+ self.get_reflected_light(Type.LIGHT_INACTIVE)
+ return self.get_input_values().scaled_value
+
+ get_sample = get_color
diff --git a/nxt_plugin/nxt/sensor/hitechnic.py b/nxt_plugin/nxt/sensor/hitechnic.py
new file mode 100644
index 0000000..cf1bf9c
--- /dev/null
+++ b/nxt_plugin/nxt/sensor/hitechnic.py
@@ -0,0 +1,611 @@
+# nxt.sensor.hitechnic module -- Classes to read HiTechnic sensors
+# Copyright (C) 2006,2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, Paulo Vieira, rhn
+# Copyright (C) 2010 rhn, Marcus Wanner, melducky, Samuel Leeman-Munk
+# Copyright (C) 2011 jerradgenson, Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+from .common import *
+from .digital import BaseDigitalSensor
+from .analog import BaseAnalogSensor
+
+
+class Compass(BaseDigitalSensor):
+ """Hitechnic compass sensor."""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'mode': (0x41, 'B'),
+ 'heading': (0x42, 'B'),
+ 'adder' : (0x43, 'B'),
+ })
+
+ class Modes:
+ MEASUREMENT = 0x00
+ CALIBRATION = 0x43
+ CALIBRATION_FAILED = 0x02
+
+ def get_heading(self):
+ """Returns heading from North in degrees."""
+
+ two_degree_heading = self.read_value('heading')[0]
+ adder = self.read_value('adder')[0]
+ heading = two_degree_heading * 2 + adder
+
+ return heading
+
+ get_sample = get_heading
+
+ def get_relative_heading(self,target=0):
+ rheading = self.get_sample()-target
+ if rheading > 180:
+ rheading -= 360
+ elif rheading < -180:
+ rheading += 360
+ return rheading
+
+ def is_in_range(self,minval,maxval):
+ """This deserves a little explanation:
+if max > min, it's straightforward, but
+if min > max, it switches the values of max and min
+and returns true if heading is NOT between the new max and min
+ """
+ if minval > maxval:
+ (maxval,minval) = (minval,maxval)
+ inverted = True
+ else:
+ inverted = False
+ heading = self.get_sample()
+ in_range = (heading > minval) and (heading < maxval)
+ #an xor handles the reversal
+ #a faster, more compact way of saying
+ #if !reversed return in_range
+ #if reversed return !in_range
+ return bool(inverted) ^ bool(in_range)
+
+ def get_mode(self):
+ return self.read_value('mode')[0]
+
+ def set_mode(self, mode):
+ if mode != self.Modes.MEASUREMENT and \
+ mode != self.Modes.CALIBRATION:
+ raise ValueError('Invalid mode specified: ' + str(mode))
+ self.write_value('mode', (mode, ))
+
+Compass.add_compatible_sensor(None, 'HiTechnc', 'Compass ') #Tested with version '\xfdV1.23 '
+Compass.add_compatible_sensor(None, 'HITECHNC', 'Compass ') #Tested with version '\xfdV2.1 '
+
+
+class Accelerometer(BaseDigitalSensor):
+ 'Object for Accelerometer sensors. Thanks to Paulo Vieira.'
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'x_axis_high': (0x42, 'b'),
+ 'y_axis_high': (0x43, 'b'),
+ 'z_axis_high': (0x44, 'b'),
+ 'xyz_short': (0x42, '3b'),
+ 'all_data': (0x42, '3b3B')
+ })
+
+ class Acceleration:
+ def __init__(self, x, y, z):
+ self.x, self.y, self.z = x, y, z
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(Accelerometer, self).__init__(brick, port, check_compatible)
+
+ def get_acceleration(self):
+ """Returns the acceleration along x, y, z axes. Units are unknown to me.
+ """
+ xh, yh, zh, xl, yl, zl = self.read_value('all_data')
+ x = xh << 2 + xl
+ y = yh << 2 + yl
+ z = zh << 2 + yl
+ return self.Acceleration(x, y, z)
+
+ get_sample = get_acceleration
+
+Accelerometer.add_compatible_sensor(None, 'HiTechnc', 'Accel. ')
+Accelerometer.add_compatible_sensor(None, 'HITECHNC', 'Accel. ') #Tested with version '\xfdV1.1 '
+
+
+class IRReceiver(BaseDigitalSensor):
+ """Object for HiTechnic IRReceiver sensors for use with LEGO Power Functions IR
+Remotes. Coded to HiTechnic's specs for the sensor but not tested. Please report
+whether this worked for you or not!
+ """
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({
+ 'm1A': (0x42, 'b'),
+ 'm1B': (0x43, 'b'),
+ 'm2A': (0x44, 'b'),
+ 'm2B': (0x45, 'b'),
+ 'm3A': (0x46, 'b'),
+ 'm3B': (0x47, 'b'),
+ 'm4A': (0x48, 'b'),
+ 'm4B': (0x49, 'b'),
+ 'all_data': (0x42, '8b')
+ })
+
+ class SpeedReading:
+ def __init__(self, m1A, m1B, m2A, m2B, m3A, m3B, m4A, m4B):
+ self.m1A, self.m1B, self.m2A, self.m2B, self.m3A, self.m3B, self.m4A, self.m4B = m1A, m1B, m2A, m2B, m3A, m3B, m4A, m4B
+ self.channel_1 = (m1A, m1B)
+ self.channel_2 = (m2A, m2B)
+ self.channel_3 = (m3A, m3B)
+ self.channel_4 = (m4A, m4B)
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(IRReceiver, self).__init__(brick, port, check_compatible)
+
+ def get_speeds(self):
+ """Returns the motor speeds for motors A and B on channels 1-4.
+Values are -128, -100, -86, -72, -58, -44, -30, -16, 0, 16, 30, 44, 58, 72, 86
+and 100. -128 specifies motor brake mode. Note that no motors are actually
+being controlled here!
+ """
+ m1A, m1B, m2A, m2B, m3A, m3B, m4A, m4B = self.read_value('all_data')
+ return self.SpeedReading(m1A, m1B, m2A, m2B, m3A, m3B, m4A, m4B)
+
+ get_sample = get_speeds
+
+IRReceiver.add_compatible_sensor(None, 'HiTechnc', 'IRRecv ')
+IRReceiver.add_compatible_sensor(None, 'HITECHNC', 'IRRecv ')
+
+
+class IRSeekerv2(BaseDigitalSensor):
+ """Object for HiTechnic IRSeeker sensors. Coded to HiTechnic's specs for the sensor
+but not tested. Please report whether this worked for you or not!
+ """
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({
+ 'dspmode': (0x41, 'B'),
+ 'DC_direction': (0x42, 'B'),
+ 'DC_sensor_1': (0x43, 'B'),
+ 'DC_sensor_2': (0x44, 'B'),
+ 'DC_sensor_3': (0x45, 'B'),
+ 'DC_sensor_4': (0x46, 'B'),
+ 'DC_sensor_5': (0x47, 'B'),
+ 'DC_sensor_mean': (0x48, 'B'),
+ 'all_DC': (0x42, '7B'),
+ 'AC_direction': (0x49, 'B'),
+ 'AC_sensor_1': (0x4A, 'B'),
+ 'AC_sensor_2': (0x4B, 'B'),
+ 'AC_sensor_3': (0x4C, 'B'),
+ 'AC_sensor_4': (0x4D, 'B'),
+ 'AC_sensor_5': (0x4E, 'B'),
+ 'all_AC': (0x49, '6B')
+ })
+ I2C_DEV = 0x10 #different from standard 0x02
+
+ class DSPModes:
+ #Modes for modulated (AC) data.
+ AC_DSP_1200Hz = 0x00
+ AC_DSP_600Hz = 0x01
+
+ class _data:
+ def get_dir_brightness(self, direction):
+ "Gets the brightness of a given direction (1-9)."
+ if direction%2 == 1: #if it's an odd number
+ exec("val = self.sensor_%d" % ((direction-1)/2+1))
+ else:
+ exec("val = (self.sensor_%d+self.sensor_%d)/2" % (direction/2, (direction/2)+1))
+ return val
+
+ class DCData(_data):
+ def __init__(self, direction, sensor_1, sensor_2, sensor_3, sensor_4, sensor_5, sensor_mean):
+ self.direction, self.sensor_1, self.sensor_2, self.sensor_3, self.sensor_4, self.sensor_5, self.sensor_mean = direction, sensor_1, sensor_2, sensor_3, sensor_4, sensor_5, sensor_mean
+
+ class ACData(_data):
+ def __init__(self, direction, sensor_1, sensor_2, sensor_3, sensor_4, sensor_5):
+ self.direction, self.sensor_1, self.sensor_2, self.sensor_3, self.sensor_4, self.sensor_5 = direction, sensor_1, sensor_2, sensor_3, sensor_4, sensor_5
+
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(IRSeekerv2, self).__init__(brick, port, check_compatible)
+
+ def get_dc_values(self):
+ """Returns the unmodulated (DC) values.
+ """
+ direction, sensor_1, sensor_2, sensor_3, sensor_4, sensor_5, sensor_mean = self.read_value('all_DC')
+ return self.DCData(direction, sensor_1, sensor_2, sensor_3, sensor_4, sensor_5, sensor_mean)
+
+ def get_ac_values(self):
+ """Returns the modulated (AC) values. 600Hz and 1200Hz modes can be selected
+between by using the set_dsp_mode() function.
+ """
+ direction, sensor_1, sensor_2, sensor_3, sensor_4, sensor_5 = self.read_value('all_AC')
+ return self.ACData(direction, sensor_1, sensor_2, sensor_3, sensor_4, sensor_5)
+
+ def get_dsp_mode(self):
+ return self.read_value('dspmode')[0]
+
+ def set_dsp_mode(self, mode):
+ self.write_value('dspmode', (mode, ))
+
+ get_sample = get_ac_values
+
+IRSeekerv2.add_compatible_sensor(None, 'HiTechnc', 'NewIRDir')
+IRSeekerv2.add_compatible_sensor(None, 'HITECHNC', 'NewIRDir')
+
+
+class EOPD(BaseAnalogSensor):
+ """Object for HiTechnic Electro-Optical Proximity Detection sensors.
+ """
+
+ # To be divided by processed value.
+ _SCALE_CONSTANT = 250
+
+ # Maximum distance the sensor can detect.
+ _MAX_DISTANCE = 1023
+
+ def __init__(self, brick, port):
+ super(EOPD, self).__init__(brick, port)
+ from math import sqrt
+ self.sqrt = sqrt
+
+ def set_range_long(self):
+ ''' Choose this mode to increase the sensitivity
+ of the EOPD sensor by approximately 4x. May
+ cause sensor overload.
+ '''
+
+ self.set_input_mode(Type.LIGHT_ACTIVE, Mode.RAW)
+
+ def set_range_short(self):
+ ''' Choose this mode to prevent the EOPD sensor from
+ being overloaded by white objects.
+ '''
+
+ self.set_input_mode(Type.LIGHT_INACTIVE, Mode.RAW)
+
+ def get_raw_value(self):
+ '''Unscaled value read from sensor.'''
+
+ return self._MAX_DISTANCE - self.get_input_values().raw_ad_value
+
+ def get_processed_value(self):
+ '''Derived from the square root of the raw value.'''
+
+ return self.sqrt(self.get_raw_value())
+
+ def get_scaled_value(self):
+ ''' Returns a value that will scale linearly as distance
+ from target changes. This is the method that should
+ generally be called to get EOPD sensor data.
+ '''
+
+ try:
+ result = self._SCALE_CONSTANT / self.get_processed_value()
+ return result
+
+ except ZeroDivisionError:
+ return self._SCALE_CONSTANT
+
+ get_sample = get_scaled_value
+
+
+class Colorv2(BaseDigitalSensor):
+ """Object for HiTechnic Color v2 Sensors. Coded to HiTechnic's specs for the sensor
+but not tested. Please report whether this worked for you or not!"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({
+ 'mode': (0x41, 'B'),
+ 'number': (0x42, 'B'),
+ 'red': (0x43, 'B'),
+ 'green': (0x44, 'B'),
+ 'blue': (0x45, 'B'),
+ 'white': (0x46, 'B'),
+ 'index': (0x47, 'B'),
+ 'normred': (0x48, 'B'),
+ 'normgreen': (0x49, 'B'),
+ 'normblue': (0x4A, 'B'),
+ 'all_data': (0x42, '9B'),
+ 'rawred': (0x42, '<H'),
+ 'rawgreen': (0x44, '<H'),
+ 'rawblue': (0x46, '<H'),
+ 'rawwhite': (0x48, '<H'),
+ 'all_raw_data': (0x42, '<4H')
+ })
+
+ class Modes:
+ ACTIVE = 0x00 #get measurements using get_active_color
+ PASSIVE = 0x01 #get measurements using get_passive_color
+ RAW = 0x03 #get measurements using get_passive_color
+ BLACK_CALIBRATION = 0x42 #hold away from objects, results saved in EEPROM
+ WHITE_CALIBRATION = 0x43 #hold in front of white surface, results saved in EEPROM
+ LED_POWER_LOW = 0x4C #saved in EEPROM, must calibrate after using
+ LED_POWER_HIGH = 0x48 #saved in EEPROM, must calibrate after using
+ RANGE_NEAR = 0x4E #saved in EEPROM, only affects active mode
+ RANGE_FAR = 0x46 #saved in EEPROM, only affects active mode, more susceptable to noise
+ FREQ_50 = 0x35 #saved in EEPROM, use when local wall power is 50Hz
+ FREQ_60 = 0x36 #saved in EEPROM, use when local wall power is 60Hz
+
+ class ActiveData:
+ def __init__(self, number, red, green, blue, white, index, normred, normgreen, normblue):
+ self.number, self.red, self.green, self.blue, self.white, self.index, self.normred, self.normgreen, self.normblue = number, red, green, blue, white, index, normred, normgreen, normblue
+
+ class PassiveData:
+ #also holds raw mode data
+ def __init__(self, red, green, blue, white):
+ self.red, self.green, self.blue, self.white = red, green, blue, white
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(Colorv2, self).__init__(brick, port, check_compatible)
+
+ def get_active_color(self):
+ """Returns color values when in active mode.
+ """
+ number, red, green, blue, white, index, normred, normgreen, normblue = self.read_value('all_data')
+ return self.ActiveData(number, red, green, blue, white, index, normred, normgreen, normblue)
+
+ get_sample = get_active_color
+
+ def get_passive_color(self):
+ """Returns color values when in passive or raw mode.
+ """
+ red, green, blue, white = self.read_value('all_raw_data')
+ return self.PassiveData(red, green, blue, white)
+
+ def get_mode(self):
+ return self.read_value('mode')[0]
+
+ def set_mode(self, mode):
+ self.write_value('mode', (mode, ))
+
+Colorv2.add_compatible_sensor(None, 'HiTechnc', 'ColorPD')
+Colorv2.add_compatible_sensor(None, 'HITECHNC', 'ColorPD')
+Colorv2.add_compatible_sensor(None, 'HiTechnc', 'ColorPD ')
+Colorv2.add_compatible_sensor(None, 'HITECHNC', 'ColorPD ')
+
+
+class Gyro(BaseAnalogSensor):
+ 'Object for gyro sensors'
+#This class is for the hitechnic gryo sensor. When the gryo is not
+#moving there will be a constant offset that will change with
+#temperature and other ambient factors. The calibrate() function
+#takes the currect value and uses it to offset subsequesnt ones.
+
+ def __init__(self, brick, port):
+ super(Gyro, self).__init__(brick, port)
+ self.set_input_mode(Type.ANGLE, Mode.RAW)
+ self.offset = 0
+
+ def get_rotation_speed(self):
+ return self.get_input_values().scaled_value - self.offset
+
+ def set_zero(self, value):
+ self.offset = value
+
+ def calibrate(self):
+ self.set_zero(self.get_rotation_speed())
+
+ get_sample = get_rotation_speed
+
+
+class Prototype(BaseDigitalSensor):
+ """Object for HiTechnic sensor prototype boards. Coded to HiTechnic's specs but not
+tested. Please report whether this worked for you or not!
+ """
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({
+ 'A0': (0x42, '<H'),
+ 'A0': (0x44, '<H'),
+ 'A0': (0x46, '<H'),
+ 'A0': (0x48, '<H'),
+ 'A0': (0x4A, '<H'),
+ 'all_analog': (0x42, '<5H'),
+ 'digital_in': (0x4C, 'B'),
+ 'digital_out': (0x4D, 'B'),
+ 'digital_cont': (0x4E, 'B'),
+ 'sample_time': (0x4F, 'B'),
+ })
+
+ class Digital_Data():
+ """Container for 6 bits of digital data. Takes an integer or a list of six bools
+and can be converted into a list of bools or an integer."""
+ def __init__(self, pins):
+ if isinstance(pins, int):
+ self.dataint = pins
+ self.datalst = self.tolist(pins)
+ else:
+ self.dataint = self.toint(pins)
+ self.datalst = pins
+ self.d0, self.d1, self.d2, self.d3, self.d4, self.d5 = self.datalst
+
+ def tolist(self, val):
+ lst = []
+ for i in range(6):
+ lst.append(bool(val & 2**i))
+ return lst
+
+ def toint(self, lst):
+ val = 0
+ for i in range(6):
+ val += int(bool(lst[i])) * (2**i)
+ return val
+
+ def __int__(self):
+ return self.dataint
+
+ def __iter__(self):
+ return iter(self.datalst)
+
+ def __getitem__(self, i):
+ return self.datalst[i]
+
+ class Analog_Data():
+ def __init__(self, a0, a1, a2, a3, a4):
+ self.a0, self.a1, self.a2, self.a3, self.a4 = a0, a1, a2, a3, a4
+
+ def get_analog(self):
+ return Analog_Data(self.read_value('all_analog'))
+
+ def get_digital(self):
+ return Digital_Data(self.read_value('digital_in')[0])
+
+ def set_digital(self, pins):
+ """Can take a Digital_Data() object"""
+ self.write_value('digital_out', (int(pins), ))
+
+ def set_digital_modes(self, modes):
+ """Sets input/output mode of digital pins. Can take a Digital_Data() object."""
+ self.write_value('digital_cont', (int(modes), ))
+
+Prototype.add_compatible_sensor(None, 'HiTechnc', 'Proto ')
+
+
+class ServoCon(BaseDigitalSensor):
+ """Object for HiTechnic FIRST Servo Controllers. Coded to HiTechnic's specs for
+the sensor but not tested. Please report whether this worked for you or not!"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({
+ 'status': (0x40, 'B'),
+ 'steptime': (0x41, 'B'),
+ 's1pos': (0x42, 'B'),
+ 's2pos': (0x43, 'B'),
+ 's3pos': (0x44, 'B'),
+ 'p4pos': (0x45, 'B'),
+ 'p5pos': (0x46, 'B'),
+ 'p6pos': (0x47, 'B'),
+ 'pwm': (0x46, 'B'),
+ })
+
+ class Status:
+ RUNNING = 0x00 #all motors stopped
+ STOPPED = 0x01 #motor(s) moving
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(ServoCon, self).__init__(brick, port, check_compatible)
+
+ def get_status(self):
+ """Returns the status of the motors. 0 for all stopped, 1 for
+some running.
+ """
+ return self.read_value('status')[0]
+
+ def set_step_time(self, time):
+ """Sets the step time (0-15).
+ """
+ self.write_value('steptime', (time, ))
+
+ def set_pos(self, num, pos):
+ """Sets the position of a server. num is the servo number (1-6),
+pos is the position (0-255).
+ """
+ self.write_value('s%dpos' % num, (pos, ))
+
+ def get_pwm(self):
+ """Gets the "PWM enable" value. The function of this value is
+nontrivial and can be found in the documentation for the sensor.
+ """
+ return self.read_value('pwm')[0]
+
+ def set_pwm(self, pwm):
+ """Sets the "PWM enable" value. The function of this value is
+nontrivial and can be found in the documentation for the sensor.
+ """
+ self.write_value('pwm', (pwm, ))
+
+ServoCon.add_compatible_sensor(None, 'HiTechnc', 'ServoCon')
+
+
+class MotorCon(BaseDigitalSensor):
+ """Object for HiTechnic FIRST Motor Controllers. Coded to HiTechnic's specs for
+the sensor but not tested. Please report whether this worked for you or not!"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({
+ 'm1enctarget': (0x40, '>l'),
+ 'm1mode': (0x44, 'B'),
+ 'm1power': (0x45, 'b'),
+ 'm2power': (0x46, 'b'),
+ 'm2mode': (0x47, 'B'),
+ 'm2enctarget': (0x48, '>l'),
+ 'm1enccurrent': (0x4c, '>l'),
+ 'm2enccurrent': (0x50, '>l'),
+ 'batteryvoltage': (0x54, '2B'),
+ 'm1gearratio': (0x56, 'b'),
+ 'm1pid': (0x57, '3B'),
+ 'm2gearratio': (0x5a, 'b'),
+ 'm2pid': (0x5b, '3B'),
+ })
+
+ class PID_Data():
+ def __init__(self, p, i, d):
+ self.p, self.i, self.d = p, i, d
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(MotorCon, self).__init__(brick, port, check_compatible)
+
+ def set_enc_target(self, mot, val):
+ """Set the encoder target (-2147483648-2147483647) for a motor
+ """
+ self.write_value('m%denctarget'%mot, (val, ))
+
+ def get_enc_target(self, mot):
+ """Get the encoder target for a motor
+ """
+ return self.read_value('m%denctarget'%mot)[0]
+
+ def get_enc_current(self, mot):
+ """Get the current encoder value for a motor
+ """
+ return self.read_value('m%denccurrent'%mot)[0]
+
+ def set_mode(self, mot, mode):
+ """Set the mode for a motor. This value is a bit mask and you can
+find details about it in the sensor's documentation.
+ """
+ self.write_value('m%dmode'%mot, (mode, ))
+
+ def get_mode(self, mot):
+ """Get the mode for a motor. This value is a bit mask and you can
+find details about it in the sensor's documentation.
+ """
+ return self.read_value('m%dmode'%mot)[0]
+
+ def set_power(self, mot, power):
+ """Set the power (-100-100) for a motor
+ """
+ self.write_value('m%dpower'%mot, (power, ))
+
+ def get_power(self, mot):
+ """Get the power for a motor
+ """
+ return self.read_value('m%dpower'%mot)[0]
+
+ def set_gear_ratio(self, mot, ratio):
+ """Set the gear ratio for a motor
+ """
+ self.write_value('m%dgearratio'%mot, (ratio, ))
+
+ def get_gear_ratio(self, mot):
+ """Get the gear ratio for a motor
+ """
+ return self.read_value('m%dgearratio'%mot)[0]
+
+ def set_pid(self, mot, piddata):
+ """Set the PID coefficients for a motor. Takes data in
+MotorCon.PID_Data(p, i, d) format.
+ """
+ self.write_value('m%dpid'%mot, (piddata.p, piddata.i, piddata.d))
+
+ def get_pid(self, mot):
+ """Get the PID coefficients for a motor. Returns a PID_Data() object.
+ """
+ p, i, d = self.read_value('m%dpid'%mot)
+ return self.PID_Data(p, i, d)
+
+ def get_battery_voltage(self):
+ """Gets the battery voltage (in millivolts/20)
+ """
+ high, low = self.read_value('bateryvoltage')[0]
+ return high << 2 + low
+
+MotorCon.add_compatible_sensor(None, 'HiTechnc', 'MotorCon')
diff --git a/nxt_plugin/nxt/sensor/mindsensors.py b/nxt_plugin/nxt/sensor/mindsensors.py
new file mode 100644
index 0000000..de6c7ee
--- /dev/null
+++ b/nxt_plugin/nxt/sensor/mindsensors.py
@@ -0,0 +1,815 @@
+# nxt.sensor.mindsensors module -- Classes implementing Mindsensors sensors
+# Copyright (C) 2006,2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner, Paulo Vieira, rhn
+# Copyright (C) 2010 Marcus Wanner, MindSensors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+
+from .common import *
+from .digital import BaseDigitalSensor, SensorInfo
+from .analog import BaseAnalogSensor
+
+
+class SumoEyes(BaseAnalogSensor):
+ """The class to control Mindsensors Sumo sensor. Warning: long range not
+ working for my sensor.
+ """
+ #range: 5-10cm
+ class Reading:
+ """Contains the reading of SumoEyes sensor. left and right can be True or
+ False. If True, then there is something there, if False, then it's empty
+ there.
+ """
+ def __init__(self, raw_reading):
+ self.raw = raw_reading
+ val = raw_reading.normalized_ad_value # FIXME: make it rely on raw_ad_value
+ right = 600 < val < 700
+ both = 700 <= val < 900
+ left = 300 < val < 400
+ self.left = left or both
+ self.right = right or both
+
+ def __str__(self):
+ return '(left: ' + str(self.left) + ', right: ' + str(self.right) + ')'
+
+ def __init__(self, brick, port, long_range=False):
+ super(SumoEyes, self).__init__(brick, port)
+ self.set_long_range(long_range)
+
+ def set_long_range(self, val):
+ """Sets if the sensor should operate in long range mode (12 inches) or
+ the short range mode (6 in). val should be True or False.
+ """
+ if val:
+ type_ = Type.LIGHT_INACTIVE
+ else:
+ type_ = Type.LIGHT_ACTIVE
+ self.set_input_mode(type_, Mode.RAW)
+
+ def get_sample(self):
+ """Returns the processed meaningful values of the sensor"""
+ return self.Reading(self.get_input_values())
+
+
+class Compassv2(BaseDigitalSensor):
+ """Class for the now-discontinued CMPS-Nx sensor. Also works with v1.1 sensors.
+Note that when using a v1.x sensor, some of the commands are not supported!
+To determine your sensor's version, use get_sensor_info().version"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'command': (0x41, '<B'),
+ 'heading': (0x42, '<H'),
+ 'x_offset': (0x44, '<h'), #unsure about signedness for this one
+ 'y_offset': (0x46, '<h'), #and this one
+ 'x_range': (0x48, '<H'),
+ 'y_range': (0x4A, '<H'),
+ 'x_raw': (0x4C, '<H'), #and this one
+ 'y_raw': (0x4E, '<H'), #and this one
+ })
+
+ class Commands:
+ AUTO_TRIG_ON = 'A'
+ AUTO_TRIG_OFF = 'S'
+ MAP_HEADING_BYTE = 'B' # map heading to 0-255 range
+ MAP_HEADING_INTEGER = 'I' # map heading to 0-36000 (or 3600) range
+ SAMPLING_50_HZ = 'E' # set sampling frequency to 50 Hz
+ SAMPLING_60_HZ = 'U' # set sampling frequency to 60 Hz
+ SET_ADPA_MODE_ON = 'N' # set ADPA mode on
+ SET_ADPA_MODE_OFF = 'O' # set ADPA mode off
+ BEGIN_CALIBRATION = 'C' # begin calibration
+ DONE_CALIBRATION = 'D' # done with calibration
+ LOAD_USER_CALIBRATION = 'L' # load user calibration value
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(Compassv2, self).__init__(brick, port, check_compatible)
+ self.command(self.Commands.MAP_HEADING_INTEGER)
+
+ def command(self, command):
+ value = ord(command)
+ self.write_value('command', (value, ))
+
+ def get_heading(self):
+ return self.read_value('heading')[0]
+
+ get_sample = get_heading
+
+Compassv2.add_compatible_sensor(None, 'mndsnsrs', 'CMPS')
+
+
+class DIST(BaseDigitalSensor):
+ """Class for the Distance Infrared Sensor"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'command': (0x41, '<B'),
+ 'distance': (0x42, '<H'),
+ 'voltage': (0x44, '<H'),
+ 'type': (0x50, '<B'),
+ 'no_of_data_points': (0x51, '<B'),
+ 'min_distance': (0x52, '<H'),
+ 'max_distance': (0x54, '<H'),
+ })
+
+ class Commands:
+ TYPE_GP2D12 = '1' #GP2D12 sensor Module
+ TYPE_GP2D120 = '2' #Short range sensor Module
+ TYPE_GP2Y0A21YK = '3' #Medium range sensor Module
+ TYPE_GP2Y0A02YK = '4' #Long range sensor Module
+ TYPE_CUSTOM = '5' #Custom sensor Module
+ POWER_ON = 'E' #Sensor module power on
+ POWER_OFF = 'D' #Sensor module power offset
+ ADPA_ON = 'N' #ADPA mode on
+ ADPA_OFF = 'O' #ADPA mode off (default)
+
+ def command(self, command):
+ value = ord(command)
+ self.write_value('command', (value, ))
+
+ def get_distance(self):
+ return self.read_value('distance')[0]
+
+ get_sample = get_distance
+
+ def get_type(self):
+ return self.read_value('type')[0]
+
+ def get_voltage(self):
+ return self.read_value('voltage')[0]
+
+ def get_min_distance(self):
+ return self.read_value('min_distance')[0]
+
+ def get_max_distance(self):
+ return self.read_value('max_distance')[0]
+
+DIST.add_compatible_sensor(None, 'mndsnsrs', 'DIST')
+
+
+class RTC(BaseDigitalSensor):
+ """Class for the RealTime Clock sensor"""
+ #TODO: Create a function to set the clock
+ #Has no indentification
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'seconds': (0x00, '<B'),
+ 'minutes': (0x01, '<B'),
+ 'hours': (0x02, '<B'),
+ 'day': (0x03, '<B'),
+ 'date': (0x04, '<B'),
+ 'month': (0x05, '<B'),
+ 'year': (0x06, '<B'),
+ })
+ I2C_DEV = 0xD0
+
+ def __init__(self, brick, port, check_compatible=False): #check_compatible must remain false due to no identification!
+ super(RTC, self).__init__(brick, port, check_compatible)
+
+ def get_seconds(self):
+ gs = self.read_value('seconds')[0]
+ gs2 = gs & 0xf # bitmasks
+ gs3 = gs & 0x70
+ gs3 = gs3 >> 4
+ return str(gs3) + str(gs2)
+
+ def get_minutes(self):
+ gm = self.read_value('minutes')[0]
+ gm2 = gm & 0xf
+ gm3 = gm & 0x70
+ gm3 = gm3 >> 4
+ return str(gm3) + str(gm2)
+
+ def get_hours(self):
+ gh = self.read_value('hours')[0]
+ gh2 = gh & 0xf
+ gh3 = gh & 0x30
+ gh3 = gh3 >> 4
+ return str(gh3) + str(gh2)
+
+ def get_day(self):
+ gwd = self.read_value('day')[0]
+ gwd = gwd & 0x07
+ return gwd
+
+ def get_month(self):
+ gmo = self.read_value('month')[0]
+ gmo2 = gmo & 0xf
+ gmo3 = gmo & 0x10
+ gmo3 = gmo3 >> 4
+ return str(gmo3) + str(gmo2)
+
+ def get_year(self):
+ """Last two digits (10 for 2010)"""
+ gy = self.read_value('year')[0]
+ gy2 = gy & 0xf
+ gy3 = gy & 0xF0
+ gy3 = gy3 >> 4
+ return str(gy3) + str(gy2)
+
+ def get_date(self):
+ gd = self.read_value('date')[0]
+ gd2 = gd & 0xf
+ gd3 = gd & 0x60
+ gd3 = gd3 >> 4
+ return str(gd3) + str(gd2)
+
+ def hour_mode(self, mode):
+ """Writes mode bit and re-enters hours, which is required"""
+ if mode == 12 or 24:
+ hm = self.read_value('hours')[0]
+ hm2 = hm & 0x40
+ hm2 = hm2 >> 6
+ if mode == 12 and hm2 == 0: #12_HOUR = 1
+ hm3 = hm + 64
+ self.write_value('hours', (hm3, ))
+ elif mode == 24 and hm2 == 1: #24_HOUR = 0
+ hm3 = hm - 64
+ self.write_value('hours', (hm3, ))
+ else:
+ print 'That mode is already selected!'
+ else:
+ raise ValueError('Must be 12 or 24!')
+
+ def get_mer(self):
+ mer = self.read_value('hours')[0]
+ mer2 = mer & 0x40
+ mer2 = mer2 >> 6
+ if mer2 == 1:
+ mer3 = mer & 0x20
+ mer3 = mer3 >> 0x10
+ return mer3
+ else:
+ print 'Cannot get mer! In 24-hour mode!'
+
+ def get_sample(self):
+ """Returns a struct_time() tuple which can be processed by the time module."""
+ import time
+ return time.struct_time((
+ int(self.get_year())+2000,
+ int(self.get_month()),
+ int(self.get_date()),
+ int(self.get_hours()),
+ int(self.get_minutes()),
+ int(self.get_seconds()),
+ int(self.get_day()),
+ 0, #Should be the Julian Day, but computing that is hard.
+ 0 #No daylight savings time to worry about here.
+ ))
+
+
+class ACCL(BaseDigitalSensor):
+ """Class for Accelerometer sensor"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'sensitivity': (0x19, 'B'),
+ 'command': (0x41, 'B'),
+ 'x_tilt': (0x42, 'b'),
+ 'y_tilt': (0x43, 'b'),
+ 'z_tilt': (0x44, 'b'),
+ 'all_tilt': (0x42, '3b'),
+
+ 'x_accel': (0x45, '<h'),
+ 'y_accel': (0x47, '<h'),
+ 'z_accel': (0x49, '<h'),
+ 'all_accel': (0x45, '<3h'),
+
+ 'x_offset': (0x4B, '<h'),
+ 'x_range': (0x4D, '<h'),
+
+ 'y_offset': (0x4F, '<h'),
+ 'y_range': (0x51, '<h'),
+
+ 'z_offset': (0x53, '<h'),
+ 'z_range': (0x55, '<h'),
+ })
+
+ class Commands:
+ SENS_15G = '1' #that's 1.5...Alt. 2.5G (sensors older than V3.20)
+ SENS_2G = '2' #Alt .3.3G
+ SENS_4G = '3' #Alt. 6.7G
+ SENS_6G = '4' #Alt. 10G
+ X_CALIBRATION = 'X' #Acquire X point calibration
+ X_CAL_AND_END = 'x' #X point calibration and end calibration
+ Y_CALIBRATION = 'Y' #Acquire Y point calibration
+ Y_CAL_AND_END = 'y' #Y point calibration and end calibration
+ Z_CALIBRATION = 'Z' #Acquire Z point calibration
+ Z_CAL_AND_END = 'z' #Z point calibration and end calibration
+ CAL_RESET = 'R' #Reset to factory set calibration
+ ADPA_ON = 'N' #Set ADPA mode On
+ ADPA_OFF = 'O' #Set ADPA mode Off (default)
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(ACCL, self).__init__(brick, port, check_compatible)
+
+ def command(self, command):
+ value = ord(command)
+ self.write_value('command', (value, ))
+
+ def get_sensitivity(self):
+ return chr(self.read_value('sensitivity')[0])
+
+ def get_tilt(self, axis):
+ xyz = str(axis) + '_tilt'
+ return self.read_value(xyz)[0]
+
+ def get_all_tilt(self):
+ return self.read_value('all_tilt')
+
+ def get_accel(self, axis):
+ xyz = str(axis) + '_accel'
+ return self.read_value(xyz)[0]
+
+ def get_all_accel(self):
+ return self.read_value('all_accel')
+
+ get_sample = get_all_accel
+
+ def get_offset(self, axis):
+ xyz = str(axis) + '_offset'
+ return self.read_value(xyz)[0]
+
+ def get_range(self, axis):
+ xyz = str(axis) + '_range'
+ return self.read_value(xyz)[0]
+
+ def set_offset(self, axis, value):
+ xyz = str(axis) + '_offset'
+ self.write_value(xyz, (value, ))
+
+ def set_range(self, axis, value):
+ xyz = str(axis) + '_range'
+ self.write_value(xyz, (value, ))
+
+ACCL.add_compatible_sensor(None, 'mndsnsrs', 'ACCL-NX') #Tested with version 'V3.20'
+
+
+class MTRMUX(BaseDigitalSensor):
+ """Class for RCX Motor Multiplexer sensor"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'command' : (0x41, '<B'),
+ 'direction_m1': (0x42, '<B'),
+ 'speed_m1': (0x43, '<B'),
+ 'direction_m2': (0x44, '<B'),
+ 'speed_m2': (0x45, '<B'),
+ 'direction_m3': (0x46, '<B'),
+ 'speed_m3': (0x47, '<B'),
+ 'direction_m4': (0x48, '<B'),
+ 'speed_m4': (0x49, '<B'),
+ })
+ I2C_DEV = 0xB4
+
+ class Commands:
+ FLOAT = 0x00
+ FORWARD = 0x01
+ REVERSE = 0x02
+ BRAKE = 0x03
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(MTRMUX, self).__init__(brick, port, check_compatible)
+
+ def command(self, command):
+ self.write_value('command', (command, ))
+
+ def set_direction(self, number, value):
+ addressname = 'direction_m' + str(number)
+ self.write_value(addressname, (value, ))
+
+ def set_speed(self, number, value):
+ addressname = 'speed_m' + str(number)
+ self.write_value(addressname, (value, ))
+
+ def get_direction(self, number):
+ addressname = 'direction_m' + str(number)
+ self.read_value(addressname)
+
+ def get_speed(self, number):
+ addressname = 'speed_m' + str(number)
+ self.read_value(addressname)
+
+MTRMUX.add_compatible_sensor(None, 'mndsnsrs', 'MTRMUX') #Tested with version 'V2.11'
+
+
+class LineLeader(BaseDigitalSensor):
+ """Class for Line Sensor Array"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'command': (0x41, '<B'),
+ 'steering': (0x42, '<b'),
+ 'average': (0x43, '<B'),
+ 'result': (0x44, '<B'),
+ 'set_point': (0x45, '<B'),
+
+ 'kp': (0x46, '<B'),
+ 'ki': (0x47, '<B'),
+ 'kd': (0x48, '<B'),
+ 'kp_divisor':(0x61, '<B'),
+ 'ki_divisor':(0x62, '<B'),
+ 'kd_divisor':(0x63, '<B'),
+ #One byte for each sensor, so byte# = sensor#
+ 'calibrated_reading_byte1': (0x49, '<B'),
+ 'calibrated_reading_byte2': (0x4A, '<B'),
+ 'calibrated_reading_byte3': (0x4B, '<B'),
+ 'calibrated_reading_byte4': (0x4C, '<B'),
+ 'calibrated_reading_byte5': (0x4D, '<B'),
+ 'calibrated_reading_byte6': (0x4E, '<B'),
+ 'calibrated_reading_byte7': (0x4F, '<B'),
+ 'calibrated_reading_byte8': (0x50, '<B'),
+ 'all_calibrated_readings': (0x49, '<8B'),
+
+ 'w_read_limit':(0x51, '<H'),
+ 'b_read_limit':(0x59, '<B'),
+ 'w_cal_data1':(0x64, '<B'),
+ 'b_cal_data':(0x6C, '<B'),
+
+ 'uncal_sensor1_voltage_byte1':(0x74, '<B'),
+ 'uncal_sensor2_voltage_byte1':(0x76, '<B'),
+ 'uncal_sensor3_voltage_byte1':(0x78, '<B'),
+ 'uncal_sensor4_voltage_byte1':(0x7A, '<B'),
+ 'uncal_sensor5_voltage_byte1':(0x7C, '<B'),
+ 'uncal_sensor6_voltage_byte1':(0x7E, '<B'),
+ 'uncal_sensor7_voltage_byte1':(0x80, '<B'),
+ 'uncal_sensor8_voltage_byte1':(0x82, '<B'),
+ 'all_uncal_readings': (0x74, '<8B'),
+ })
+
+ class Commands:
+ CALIBRATE_WHITE = 'W'
+ CALIBRATE_BLACK = 'B'
+ SENSOR_SLEEP = 'D'
+ US_CONFIG = 'A'
+ EU_CONFIG = 'E'
+ UNI_CONFIG = 'U'
+ SENSOR_WAKE = 'P'
+ COLOR_INVERT = 'I'
+ COLOR_INVERT_REVERSE = 'R'
+ SNAPSHOT = 'S'
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(LineLeader, self).__init__(brick, port, check_compatible)
+
+ def command(self, command):
+ value = ord(command)
+ self.write_value('command', (value, ))
+
+ def get_steering(self):
+ 'Value to add to the left and subtract from the right motor\'s power.'
+ return self.read_value('steering')[0]
+
+ def get_average(self):
+ 'Weighted average; greater as line is closer to right edge. 0 for no line.'
+ return self.read_value('average')[0]
+
+ def get_result(self):
+ 'Bitmap, one bit for each sensor'
+ return self.read_value('result')[0]
+
+ def set_set_point(self, value):
+ 'Average value for steering to gravitate to. 10 (left) to 80 (right).'
+ self.write_value('set_point', (value, ))
+
+ def set_pid(self, pid, value):
+ addressname = 'k' + str(pid)
+ self.write_value(addressname, (value, ))
+
+ def set_pid_divisor(self, pid, value):
+ addressname = 'k' + str(pid) + '_divisor'
+ self.write_value(addressname, (value, ))
+
+ def get_reading(self, number):
+ addressname = 'calibrated_reading_byte' + str(number)
+ return self.read_value(addressname)[0]
+
+ def get_reading_all(self):
+ return self.read_value('all_calibrated_readings')
+
+ get_sample = get_reading_all
+
+ def get_uncal_reading(self, number):
+ addressname = 'uncal_sensor' + str(number) + '_voltage_byte1'
+ return self.read_value(addressname)[0]
+
+ def get_uncal_all(self):
+ return self.read_value('all_uncal_readings')
+
+LineLeader.add_compatible_sensor(None, 'mndsnsrs', 'LineLdr') #Tested with version 'V1.16'
+
+
+class Servo(BaseDigitalSensor):
+ """Class for Servo sensors"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'command' : (0x41, '<B'),
+
+ 'servo_1_pos': (0x42, '<H'),
+ 'servo_2_pos': (0x44, '<H'),
+ 'servo_3_pos': (0x46, '<H'),
+ 'servo_4_pos': (0x48, '<H'),
+ 'servo_5_pos': (0x4A, '<H'),
+ 'servo_6_pos': (0x4C, '<H'),
+ 'servo_7_pos': (0x4E, '<H'),
+ 'servo_8_pos': (0x50, '<H'),
+
+ 'servo_1_speed': (0x52, '<B'),
+ 'servo_2_speed': (0x53, '<B'),
+ 'servo_3_speed': (0x54, '<B'),
+ 'servo_4_speed': (0x55, '<B'),
+ 'servo_5_speed': (0x56, '<B'),
+ 'servo_6_speed': (0x57, '<B'),
+ 'servo_7_speed': (0x58, '<B'),
+ 'servo_8_speed': (0x59, '<B'),
+
+ 'servo_1_quick': (0x5A, '<B'),
+ 'servo_2_quick': (0x5B, '<B'),
+ 'servo_3_quick': (0x5C, '<B'),
+ 'servo_4_quick': (0x5D, '<B'),
+ 'servo_5_quick': (0x5E, '<B'),
+ 'servo_6_quick': (0x5F, '<B'),
+ 'servo_7_quick': (0x60, '<B'),
+ 'servo_8_quick': (0x61, '<B'),
+ })
+ I2C_DEV = 0xB0
+
+ COMMANDVALUES = {'R': (0x52), #Resume macro execution
+ 'S': (0x53), #reset initial position and speed
+ 'I1': (0x4931), #store initial position motor 1
+ 'I2': (0x4932), #store initial position motor 2
+ 'I3': (0x4933), #etc...
+ 'I4': (0x4934),
+ 'I5': (0x4935),
+ 'I6': (0x4936),
+ 'I7': (0x4937),
+ 'I8': (0x4938),
+ 'H': (0x48), #Halt macro
+ 'Gx': (0x4778), #not going to work yet x = variable
+ 'EM': (0x454d), #Edit Macro
+ 'P': (0x50), #Pause Macro
+ }
+
+ class Commands:
+ RESUME_MACRO = 'R'
+ RESET_POS_SPEED = 'S'
+ STORE_MOTOR_POS_1 = 'I1'
+ STORE_MOTOR_POS_2 = 'I2'
+ STORE_MOTOR_POS_3 = 'I3'
+ STORE_MOTOR_POS_4 = 'I4'
+ STORE_MOTOR_POS_5 = 'I5'
+ STORE_MOTOR_POS_6 = 'I6'
+ STORE_MOTOR_POS_7 = 'I7'
+ STORE_MOTOR_POS_8 = 'I8'
+ HALT_MACRO = 'H'
+ X_TO_VAR = 'Gx' #not going to work yet
+ EDIT_MACRO = 'EM'
+ PAUSE_MACRO = 'P'
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(Servo, self).__init__(brick, port, check_compatible)
+
+ def command(self, command):
+ value = self.COMMANDVALUES[command]
+ self.write_value('command', (value, ))
+
+ def get_bat_level(self):
+ return self.read_value('command')[0]
+
+ def set_position(self, number, value):
+ addressname = 'servo_' + str(number) + '_pos'
+ self.write_value(addressname, (value, ))
+
+ def get_position(self, number):
+ return self.read_value('servo_' + str(number) + '_pos')[0]
+
+ def set_speed(self, number, value):
+ addressname = 'servo_' + str(number) + '_speed'
+ self.write_value(addressname, (value, ))
+
+ def get_speed(self, number):
+ return self.read_value('servo_' + str(number) + '_speed')[0]
+
+ def set_quick(self, number, value):
+ addressname = 'servo_' + str(number) + '_quick'
+ self.write_value(addressname, (value, ))
+
+Servo.add_compatible_sensor(None, 'mndsnsrs', 'NXTServo') #Tested with version 'V1.20'
+
+
+class MMX(BaseDigitalSensor):
+ """Class for MMX sensors"""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'command' : (0x41, '<B'),
+ #Motor Writes
+ 'encoder_1_target': (0x42, '<l'),
+ 'speed_1': (0x46, '<B'),
+ 'seconds_to_run_1': (0x47, '<B'),
+ 'command_b_1': (0x48, '<B'),
+ 'command_a_1': (0x49, '<B'),
+
+ 'encoder_2_target': (0x4A, '<l'),
+ 'speed_2': (0x4E, '<B'),
+ 'seconds_to_run_2': (0x4F, '<B'),
+ 'command_b_2': (0x50, '<B'),
+ 'command_a_2': (0x51, '<B'),
+ #Motor reads
+ 'encoder_1_pos': (0x62, '<H'),
+ 'encoder_2_pos': (0x66, '<H'),
+ 'status_m1': (0x72, '<B'),
+ 'status_m2': (0x73, '<B'),
+ 'tasks_running_m1': (0x76, '<H'),
+ 'tasks_running_m2': (0x77, '<H'),
+ #PID Control
+ 'p_encoder': (0x7A, '<H'),
+ 'i_encoder': (0x7C, '<H'),
+ 'd_encoder': (0x7E, '<H'),
+ 'p_speed': (0x80, '<H'),
+ 'i_speed': (0x82, '<H'),
+ 'd_speed': (0x84, '<H'),
+ 'pass_count': (0x86, '<B'),
+ 'tolerance': (0x87, '<B'),
+ })
+ I2C_DEV = 0x06
+
+ class Commands:
+ RESET_PARAMS_ENCODERS = 'R'
+ ISSUE_SYNCED_COMMANDS = 'S'
+ MOTOR_1_FLOAT_STOP = 'a'
+ MOTOR_2_FLOAT_STOP = 'b'
+ BOTH_FLOAT_STOP = 'c'
+ MOTOR_1_BRAKE_STOP = 'A'
+ MOTOR_2_BRAKE_STOP = 'B'
+ BOTH_BRAKE_STOP = 'C'
+ MOTOR_1_ENC_RESET = 'r'
+ MOTOR_2_ENC_RESET = 's'
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(MMX, self).__init__(brick, port, check_compatible)
+
+ def command(self, command):
+ value = ord(command)
+ self.write_value('command', (value, ))
+
+ def get_bat_level(self):
+ return self.read_value('command')[0]
+
+ def set_encoder_target(self, motor_number, value):
+ addressname = 'encoder_' + str(motor_number) + '_target'
+ self.write_value(addressname, (value, ))
+
+ def set_speed(self, motor_number, value):
+ addressname = 'speed_' + str(motor_number)
+ self.write_value(addressname, (value, ))
+
+ def set_time_run(self, motor_number, seconds):
+ addressname = 'seconds_to_run_' + str(motor_number)
+ self.write_value(addressname, (seconds, ))
+
+ def command_b(self, motor_number, value):
+ addressname = 'command_b_' + str(motor_number)
+ self.write_value(addressname, (value, ))
+
+ def command_a(self, motor_number, bit_num, bit_val):
+ addressname = 'command_a_' + str(motor_number)
+ s = self.read_value(addressname)[0]
+ #I feel like there must be an easier way to write one bit...
+ val = bit_val << bit_num
+ if bit_val == 1:
+ value = val | s
+ self.write_value(addressname, (value, ))
+ return value #testing purposes
+ elif bit_val == 0:
+ val = 1
+ val = val << bit_num
+ val = val ^ 0xFF
+ value = val & s
+ self.write_value(addressname, (value, ))
+ return value
+
+ def get_encoder_pos(self, motor_number):
+ addressname = 'encoder_' +str(motor_number) +'_pos'
+ return self.read_value(addressname)[0]
+
+ def get_motor_status(self, motor_number, bit_num):
+ addressname = 'status_m' + str(motor_number)
+ s = self.read_value(addressname)[0]
+ x = 1
+ x = x << bit_num
+ value = x & s
+ value = value >> bit_num
+ return value
+
+ def get_tasks(self, motor_number):
+ addressname = 'tasks_running_m' + str(motor_number)
+ return self.read_value(addressname)[0]
+
+ def set_pid(self, pid, target, value):
+ addressname = str(pid) + '_' + str(target)
+ self.write_value(addressname, (value, ))
+
+ def set_pass_count(self, value):
+ self.write_value('pass_count', (value, ))
+
+ def set_tolerance(self, value):
+ self.write_value('tolerance', (value, ))
+
+MMX.add_compatible_sensor(None, 'mndsnsrs', 'NxTMMX') #Tested with version 'V1.01'
+
+
+class HID(BaseDigitalSensor):
+ """Class for Human Interface Device sensors.
+These are connected to a computer and look like a keyboard to it."""
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'command' : (0x41, '<B'),
+ 'modifier' : (0x42, '<B'),
+ 'keyboard_data' : (0x43, '<B'),
+ })
+ I2C_DEV = 0x04
+
+ class Commands:
+ TRANSMIT = 'T'
+ ASCII_MODE = 'A'
+ DIRECT_MODE = 'D'
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(HID, self).__init__(brick, port, check_compatible)
+
+ def command(self, command):
+ value = ord(command)
+ self.write_value('command', (value, ))
+
+ def set_modifier(self, mod):
+ self.write_value('modifier', (mod, ))
+
+ def write_data(self, data):
+ data = ord(data)
+ self.write_value('keyboard_data', (data, ))
+
+HID.add_compatible_sensor(None, 'mndsnsrs', 'NXTHID') #Tested with version 'V1.02'
+
+
+class PS2(BaseDigitalSensor):
+ I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
+ I2C_ADDRESS.update({'command' : (0x41, '<B'),
+ 'button_set_1': (0x42, '<B'),
+ 'button_set_2': (0x43, '<B'),
+ 'x_left_joystick': (0x44, '<b'),
+ 'y_left_joystick': (0x45, '<b'),
+ 'x_right_joystick': (0x46, '<b'),
+ 'y_right_joystick': (0x47, '<b'),
+ })
+
+ class ControllerState:
+ class Buttons:
+ left, down, right, up, square, cross, circle, triangle, r1, r2, r3, l1, l2, l3 = [0 for i in range(14)] #14 zeros
+ def __init__(self, buttons_1, buttons_2, left_x, left_y, right_x, right_y):
+ self.leftstick = (left_x, left_y)
+ self.rightstick = (right_x, right_y)
+ buttons_1 = ~buttons_1
+ buttons_2 = ~buttons_2
+ self.buttons = self.Buttons()
+ self.buttons.left = bool(buttons_1 & 0x80)
+ self.buttons.down = bool(buttons_1 & 0x40)
+ self.buttons.right = bool(buttons_1 & 0x20)
+ self.buttons.up = bool(buttons_1 & 0x10)
+ self.buttons.square = bool(buttons_2 & 0x80)
+ self.buttons.cross = bool(buttons_2 & 0x40)
+ self.buttons.circle = bool(buttons_2 & 0x20)
+ self.buttons.triangle = bool(buttons_2 & 0x10)
+ self.buttons.r1 = bool(buttons_2 & 0x08)
+ self.buttons.r2 = bool(buttons_2 & 0x02)
+ self.buttons.r3 = bool(buttons_1 & 0x04)
+ self.buttons.l1 = bool(buttons_2 & 0x04)
+ self.buttons.l2 = bool(buttons_2 & 0x01)
+ self.buttons.l3 = bool(buttons_1 & 0x02)
+
+ class Commands:
+ POWER_ON = 'E'
+ POWER_OFF = 'D'
+ DIGITAL_MODE = 'A'
+ ANALOG_MODE = 's'
+ ADPA_ON = 'N'
+ ADPA_OFF = 'O'
+
+ def __init__(self, brick, port, check_compatible=True):
+ super(PS2, self).__init__(brick, port, check_compatible)
+
+ def command(self, command):
+ value = ord(command)
+ self.write_value('command', (value, ))
+
+ def get_joystick(self, xy, lr):
+ addressname = str(xy) + '_' + str(lr) + '_joystick'
+ return self.read_value(addressname)[0]
+
+ def get_buttons(self, setnum):
+ addressname = 'button_set_' + str(setnum)
+ return self.read_value(addressname)[0]
+
+ def get_sample(self):
+ return self.ControllerState(
+ get_buttons(0),
+ get_buttons(1),
+ get_joystick('x', 'l'),
+ get_joystick('y', 'l'),
+ get_joystick('x', 'r'),
+ get_joystick('y', 'r'))
+
+PS2.add_compatible_sensor(None, 'mndsnsrs', 'PSPNX') #Tested with version 'V2.00'
diff --git a/nxt_plugin/nxt/system.py b/nxt_plugin/nxt/system.py
new file mode 100644
index 0000000..8196534
--- /dev/null
+++ b/nxt_plugin/nxt/system.py
@@ -0,0 +1,299 @@
+# nxt.system module -- LEGO Mindstorms NXT system telegrams
+# Copyright (C) 2006 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+'Use for communications regarding the NXT filesystem and such ***ADVANCED USERS ONLY***'
+
+def _create(opcode):
+ 'Create a simple system telegram'
+ from telegram import Telegram
+ return Telegram(False, opcode)
+
+def _create_with_file(opcode, fname):
+ tgram = _create(opcode)
+ tgram.add_filename(fname)
+ return tgram
+
+def _create_with_handle(opcode, handle):
+ tgram = _create(opcode)
+ tgram.add_u8(handle)
+ return tgram
+
+def open_read(opcode, fname):
+ return _create_with_file(opcode, fname)
+
+def _parse_open_read(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ n_bytes = tgram.parse_u32()
+ return (handle, n_bytes)
+
+def open_write(opcode, fname, n_bytes):
+ tgram = _create_with_file(opcode, fname)
+ tgram.add_u32(n_bytes)
+ return tgram
+
+def _parse_open_write(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ return handle
+
+def read(opcode, handle, n_bytes):
+ tgram = _create_with_handle(opcode, handle)
+ tgram.add_u16(n_bytes)
+ return tgram
+
+def _parse_read(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ n_bytes = tgram.parse_u16()
+ data = tgram.parse_string()
+ return (handle, n_bytes, data)
+
+def write(opcode, handle, data):
+ tgram = _create_with_handle(opcode, handle)
+ tgram.add_string(len(data), data)
+ return tgram
+
+def _parse_write(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ n_bytes = tgram.parse_u16()
+ return (handle, n_bytes)
+
+def close(opcode, handle):
+ return _create_with_handle(opcode, handle)
+
+def _parse_close(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ return handle
+
+def delete(opcode, fname):
+ return _create_with_file(opcode, fname)
+
+def _parse_delete(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ fname = tgram.parse_string()
+ return (handle, fname)
+
+def find_first(opcode, fname):
+ return _create_with_file(opcode, fname)
+
+def _parse_find(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ fname = tgram.parse_string(20)
+ n_bytes = tgram.parse_u32()
+ return (handle, fname, n_bytes)
+
+def find_next(opcode, handle):
+ return _create_with_handle(opcode, handle)
+
+def get_firmware_version(opcode):
+ return _create(opcode)
+
+def _parse_get_firmware_version(tgram):
+ tgram.check_status()
+ prot_minor = tgram.parse_u8()
+ prot_major = tgram.parse_u8()
+ prot_version = (prot_major, prot_minor)
+ fw_minor = tgram.parse_u8()
+ fw_major = tgram.parse_u8()
+ fw_version = (fw_major, fw_minor)
+ return (prot_version, fw_version)
+
+def open_write_linear(opcode, fname, n_bytes):
+ tgram = _create_with_file(opcode, fname)
+ tgram.add_u32(n_bytes)
+ return tgram
+
+def open_read_linear(opcode, fname):
+ return _create_with_file(opcode, fname)
+
+def _parse_open_read_linear(tgram):
+ tgram.check_status()
+ n_bytes = tgram.parse_u32()
+ return n_bytes
+
+def open_write_data(opcode, fname, n_bytes):
+ tgram = _create_with_file(opcode, fname)
+ tgram.add_u32(n_bytes)
+ return tgram
+
+def open_append_data(opcode, fname):
+ return _create_with_file(opcode, fname)
+
+def _parse_open_append_data(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ n_bytes = tgram.parse_u32()
+ return (handle, n_bytes)
+
+def request_first_module(opcode, mname):
+ return _create_with_file(opcode, mname)
+
+def _parse_request_module(tgram):
+ tgram.check_status()
+ handle = tgram.parse_u8()
+ mname = tgram.parse_string(20)
+ mod_id = tgram.parse_u32()
+ mod_size = tgram.parse_u32()
+ mod_iomap_size = tgram.parse_u16()
+ return (handle, mname, mod_id, mod_size, mod_iomap_size)
+
+def request_next_module(opcode, handle):
+ return _create_with_handle(opcode, handle)
+
+def close_module_handle(opcode, handle):
+ return _create_with_handle(opcode, handle)
+
+def read_io_map(opcode, mod_id, offset, n_bytes):
+ tgram = _create(opcode)
+ tgram.add_u32(mod_id)
+ tgram.add_u16(offset)
+ tgram.add_u16(n_bytes)
+ return tgram
+
+def _parse_read_io_map(tgram):
+ tgram.check_status()
+ mod_id = tgram.parse_u32()
+ n_bytes = tgram.parse_u16()
+ contents = tgram.parse_string()
+ return (mod_id, n_bytes, contents)
+
+def write_io_map(opcode, mod_id, offset, content):
+ tgram = _create(opcode)
+ tgram.add_u32(mod_id)
+ tgram.add_u16(offset)
+ tgram.add_u16(len(content))
+ tgram.add_string(len(content), content)
+ return tgram
+
+def _parse_write_io_map(tgram):
+ tgram.check_status()
+ mod_id = tgram.parse_u32()
+ n_bytes = tgram.parse_u16()
+ return (mod_id, n_bytes)
+
+def boot(opcode):
+ # Note: this command is USB only (no Bluetooth)
+ tgram = _create(opcode)
+ tgram.add_string(19, "Let's dance: SAMBA\0")
+ return tgram
+
+def _parse_boot(tgram):
+ tgram.check_status()
+ resp = tgram.parse_string()
+ # Resp should be 'Yes\0'
+ return resp
+
+def set_brick_name(opcode, bname):
+ tgram = _create(opcode)
+ if len(bname) > 15:
+ print "Warning! Brick name %s will be truncated to %s!" % (bname, bname[0:15])
+ bname = bname[0:15]
+ elif len(bname) < 15:
+ bname += '\x00' * (15-len(bname)) #fill the extra chars with nulls
+ tgram.add_string(len(bname), bname)
+ return tgram
+
+def _parse_set_brick_name(tgram):
+ tgram.check_status()
+
+def get_device_info(opcode):
+ return _create(opcode)
+
+def _parse_get_device_info(tgram):
+ tgram.check_status()
+ name = tgram.parse_string(15)
+ a0 = tgram.parse_u8()
+ a1 = tgram.parse_u8()
+ a2 = tgram.parse_u8()
+ a3 = tgram.parse_u8()
+ a4 = tgram.parse_u8()
+ a5 = tgram.parse_u8()
+ a6 = tgram.parse_u8()
+ # FIXME: what is a6 for?
+ address = '%02X:%02X:%02X:%02X:%02X:%02X' % (a0, a1, a2, a3, a4, a5)
+ signal_strength = tgram.parse_u32()
+ user_flash = tgram.parse_u32()
+ return (name, address, signal_strength, user_flash)
+
+def delete_user_flash(opcode):
+ return _create(opcode)
+
+def _parse_delete_user_flash(tgram):
+ tgram.check_status()
+
+def poll_command_length(opcode, buf_num):
+ tgram = _create(opcode)
+ tgram.add_u8(buf_num)
+ return tgram
+
+def _parse_poll_command_length(tgram):
+ buf_num = tgram.parse_u8()
+ tgram.check_status()
+ n_bytes = tgram.parse_u8()
+ return (buf_num, n_bytes)
+
+def poll_command(opcode, buf_num, n_bytes):
+ tgram = _create(opcode)
+ tgram.add_u8(buf_num)
+ tgram.add_u8(n_bytes)
+ return tgram
+
+def _parse_poll_command(tgram):
+ buf_num = tgram.parse_u8()
+ tgram.check_status()
+ n_bytes = tgram.parse_u8()
+ command = tgram.parse_string()
+ return (buf_num, n_bytes, command)
+
+def bluetooth_factory_reset(opcode):
+ # Note: this command is USB only (no Bluetooth)
+ return _create(opcode)
+
+def _parse_bluetooth_factory_reset(tgram):
+ tgram.check_status()
+
+#TODO Add docstrings to all methods
+
+OPCODES = {
+ 0x80: (open_read, _parse_open_read),
+ 0x81: (open_write, _parse_open_write),
+ 0x82: (read, _parse_read),
+ 0x83: (write, _parse_write),
+ 0x84: (close, _parse_close),
+ 0x85: (delete, _parse_delete),
+ 0x86: (find_first, _parse_find),
+ 0x87: (find_next, _parse_find),
+ 0x88: (get_firmware_version, _parse_get_firmware_version),
+ 0x89: (open_write_linear, _parse_open_write),
+ 0x8A: (open_read_linear, _parse_open_read_linear),
+ 0x8B: (open_write_data, _parse_open_write),
+ 0x8C: (open_append_data, _parse_open_append_data),
+ 0x90: (request_first_module, _parse_request_module),
+ 0x91: (request_next_module, _parse_request_module),
+ 0x92: (close_module_handle, _parse_close),
+ 0x94: (read_io_map, _parse_read_io_map),
+ 0x95: (write_io_map, _parse_write_io_map),
+ 0x97: (boot, _parse_boot),
+ 0x98: (set_brick_name, _parse_set_brick_name),
+ 0x9B: (get_device_info, _parse_get_device_info),
+ 0xA0: (delete_user_flash, _parse_delete_user_flash),
+ 0xA1: (poll_command_length, _parse_poll_command_length),
+ 0xA2: (poll_command, _parse_poll_command),
+ 0xA4: (bluetooth_factory_reset, _parse_bluetooth_factory_reset),
+}
diff --git a/nxt_plugin/nxt/telegram.py b/nxt_plugin/nxt/telegram.py
new file mode 100644
index 0000000..cdc83cd
--- /dev/null
+++ b/nxt_plugin/nxt/telegram.py
@@ -0,0 +1,120 @@
+# nxt.telegram module -- LEGO Mindstorms NXT telegram formatting and parsing
+# Copyright (C) 2006 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+'Used by nxt.system for sending telegrams to the NXT'
+
+from cStringIO import StringIO
+from struct import pack, unpack
+import nxt.error
+
+class InvalidReplyError(Exception):
+ pass
+
+class InvalidOpcodeError(Exception):
+ pass
+
+class Telegram(object):
+
+ TYPE = 0 # type byte offset
+ CODE = 1 # code byte offset
+ DATA = 2 # data byte offset
+
+ TYPE_NOT_DIRECT = 0x01 # system vs. direct type
+ TYPE_REPLY = 0x02 # reply type (from NXT brick)
+ TYPE_REPLY_NOT_REQUIRED = 0x80 # reply not required flag
+
+ def __init__(self, direct=False, opcode=0, reply_req=True, pkt=None):
+ self.reply = True
+ if pkt:
+ self.pkt = StringIO(pkt)
+ self.typ = self.parse_u8()
+ self.opcode = self.parse_u8()
+ if not self.is_reply():
+ raise InvalidReplyError
+ if self.opcode != opcode:
+ raise InvalidOpcodeError, self.opcode
+ else:
+ self.pkt = StringIO()
+ typ = 0
+ if not direct:
+ typ |= Telegram.TYPE_NOT_DIRECT
+ if not reply_req:
+ typ |= Telegram.TYPE_REPLY_NOT_REQUIRED
+ self.reply = False
+ self.add_u8(typ)
+ self.add_u8(opcode)
+
+ def __str__(self):
+ return self.pkt.getvalue()
+
+ def is_reply(self):
+ return self.typ == Telegram.TYPE_REPLY
+
+ def add_string(self, n_bytes, v):
+ self.pkt.write(pack('%ds' % n_bytes, v))
+
+ def add_filename(self, fname):
+ self.pkt.write(pack('20s', fname))
+
+ def add_s8(self, v):
+ self.pkt.write(pack('<b', v))
+
+ def add_u8(self, v):
+ self.pkt.write(pack('<B', v))
+
+ def add_s16(self, v):
+ self.pkt.write(pack('<h', v))
+
+ def add_u16(self, v):
+ self.pkt.write(pack('<H', v))
+
+ def add_s32(self, v):
+ self.pkt.write(pack('<i', v))
+
+ def add_u32(self, v):
+ self.pkt.write(pack('<I', v))
+
+ def parse_string(self, n_bytes=0):
+ if n_bytes:
+ return unpack('%ss' % n_bytes,
+ self.pkt.read(n_bytes))[0]
+ else:
+ return self.pkt.read()
+
+ def parse_s8(self):
+ return unpack('<b', self.pkt.read(1))[0]
+
+ def parse_u8(self):
+ return unpack('<B', self.pkt.read(1))[0]
+
+ def parse_s16(self):
+ return unpack('<h', self.pkt.read(2))[0]
+
+ def parse_u16(self):
+ return unpack('<H', self.pkt.read(2))[0]
+
+ def parse_s32(self):
+ return unpack('<i', self.pkt.read(4))[0]
+
+ def parse_u32(self):
+ return unpack('<I', self.pkt.read(4))[0]
+
+ def check_status(self):
+ nxt.error.check_status(self.parse_u8())
+
+import nxt.direct
+import nxt.system
+
+OPCODES = dict(nxt.system.OPCODES)
+OPCODES.update(nxt.direct.OPCODES)
diff --git a/nxt_plugin/nxt/usbsock.py b/nxt_plugin/nxt/usbsock.py
new file mode 100644
index 0000000..de338b1
--- /dev/null
+++ b/nxt_plugin/nxt/usbsock.py
@@ -0,0 +1,87 @@
+# nxt.usbsock module -- USB socket communication with LEGO Minstorms NXT
+# Copyright (C) 2006, 2007 Douglas P Lau
+# Copyright (C) 2009 Marcus Wanner
+# Copyright (C) 2011 Paul Hollensen, Marcus Wanner
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import usb, os
+from nxt.brick import Brick
+
+ID_VENDOR_LEGO = 0x0694
+ID_PRODUCT_NXT = 0x0002
+
+class USBSock(object):
+ 'Object for USB connection to NXT'
+
+ bsize = 60 # USB socket block size
+
+ type = 'usb'
+
+ def __init__(self, device):
+ self.device = device
+ self.handle = None
+ self.debug = False
+
+ def __str__(self):
+ return 'USB (%s)' % (self.device.filename)
+
+ def connect(self):
+ 'Use to connect to NXT.'
+ if self.debug:
+ print 'Connecting via USB...'
+ config = self.device.configurations[0]
+ iface = config.interfaces[0][0]
+ self.blk_out, self.blk_in = iface.endpoints
+ self.handle = self.device.open()
+ self.handle.setConfiguration(1)
+ self.handle.claimInterface(0)
+ if os.name != 'nt': # http://code.google.com/p/nxt-python/issues/detail?id=33
+ self.handle.reset()
+ if self.debug:
+ print 'Connected.'
+ return Brick(self)
+
+ def close(self):
+ 'Use to close the connection.'
+ if self.debug:
+ print 'Closing USB connection...'
+ self.device = None
+ self.handle = None
+ self.blk_out = None
+ self.blk_in = None
+ if self.debug:
+ print 'USB connection closed.'
+
+ def send(self, data):
+ 'Use to send raw data over USB connection ***ADVANCED USERS ONLY***'
+ if self.debug:
+ print 'Send:',
+ print ':'.join('%02x' % ord(c) for c in data)
+ self.handle.bulkWrite(self.blk_out.address, data)
+
+ def recv(self):
+ 'Use to recieve raw data over USB connection ***ADVANCED USERS ONLY***'
+ data = self.handle.bulkRead(self.blk_in.address, 64)
+ if self.debug:
+ print 'Recv:',
+ print ':'.join('%02x' % (c & 0xFF) for c in data)
+ # NOTE: bulkRead returns a tuple of ints ... make it sane
+ return ''.join(chr(d & 0xFF) for d in data)
+
+def find_bricks(host=None, name=None):
+ 'Use to look for NXTs connected by USB only. ***ADVANCED USERS ONLY***'
+ # FIXME: probably should check host (MAC)
+ # if anyone knows how to do this, please file a bug report
+ for bus in usb.busses():
+ for device in bus.devices:
+ if device.idVendor == ID_VENDOR_LEGO and device.idProduct == ID_PRODUCT_NXT:
+ yield USBSock(device)
diff --git a/nxt_plugin/nxt/utils.py b/nxt_plugin/nxt/utils.py
new file mode 100644
index 0000000..98ba6df
--- /dev/null
+++ b/nxt_plugin/nxt/utils.py
@@ -0,0 +1,33 @@
+# nxt.utils module -- Generic utilities to support other modules
+# Copyright (C) 2010 Vladimir Moskva
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+from collections import defaultdict
+
+def parse_command_line_arguments(arguments):
+ keyword_parameters = defaultdict(lambda: None)
+ parameters = []
+ current_key = None
+
+ for argument in arguments[1:]:
+ if argument in ('-h', '--host'):
+ current_key = 'host'
+ else:
+ if current_key is not None:
+ if argument.startswith('-'):
+ raise Exception('Invalid arguments')
+ keyword_parameters[current_key] = argument
+ current_key = None
+ else:
+ parameters.append(argument)
+ return parameters, keyword_parameters
+
diff --git a/nxt_plugin/nxt_plugin.py b/nxt_plugin/nxt_plugin.py
new file mode 100755
index 0000000..21b3529
--- /dev/null
+++ b/nxt_plugin/nxt_plugin.py
@@ -0,0 +1,580 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Emiliano Pastorino <epastorino@plan.ceibal.edu.uy>
+# Copyright (C) 2011, 2012 Butiá Team butia@fing.edu.uy
+# Butia is a free open plataform for robotics projects
+# www.fing.edu.uy/inco/proyectos/butia
+# Universidad de la República del Uruguay
+#
+# 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:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# 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.
+
+import os
+import sys
+import time
+
+from gettext import gettext as _
+
+from plugins.plugin import Plugin
+
+from TurtleArt.tapalette import make_palette
+from TurtleArt.tapalette import palette_name_to_index
+from TurtleArt.tapalette import special_block_colors
+from TurtleArt.tapalette import palette_blocks
+from TurtleArt.talogo import primitive_dictionary
+from TurtleArt.taconstants import BLACK, WHITE, CONSTANTS, BOX_COLORS
+from TurtleArt.tautils import debug_output
+
+
+sys.path.insert(0, os.path.abspath('./plugins/nxt_plugin'))
+import usb
+import nxt
+from nxt.locator import find_one_brick
+from nxt.motor import PORT_A, PORT_B, PORT_C, Motor, SynchronizedMotors
+from nxt.sensor import PORT_1, PORT_2, PORT_3, PORT_4, Touch, Color20, \
+ Ultrasonic, Type, Sound
+
+NXT_SENSORS = {_('touch'): 0, _('ultrasonic'): 1, _('color'): 2, _('light'): 3, _('sound'): 4}
+NXT_MOTOR_PORTS = {_('PORT A'): PORT_A, _('PORT B'): PORT_B, _('PORT C'): PORT_C}
+NXT_SENSOR_PORTS = {_('PORT 1'): PORT_1, _('PORT 2'): PORT_2, _('PORT 3'): PORT_3, _('PORT 4'): PORT_4}
+
+colors = [None, BLACK, CONSTANTS['blue'], CONSTANTS['green'], CONSTANTS['yellow'], CONSTANTS['red'], WHITE]
+
+COLOR_NOTPRESENT = ["#A0A0A0","#808080"]
+COLOR_PRESENT = ["#00FF00","#008000"]
+
+
+ERROR_BRICK = _('Please check the connection with the brick.')
+ERROR_PORT = _('Please check the port.')
+ERROR_POWER = _('The value of power must be between -127 to 127.')
+ERROR = _('An error has occurred: check all connections and try to reconnect.')
+
+MINIMO_INTERVALO = 0.2
+
+class Nxt_plugin(Plugin):
+
+ def __init__(self, parent):
+ self.tw = parent
+ self.nxtbrick = None
+
+ """
+ Adding a rule to /etc/udev/rules.d call: /etc/udev/rules.d/99-lego.rules
+ with:
+
+ BUS=="usb", ATTRS{idVendor}=="0694", ATTRS{idProduct}=="0002", MODE="0666"
+ """
+
+ self.nxtbrick = nxt.locator.find_one_brick()
+
+ if self.nxtbrick:
+ debug_output(_('NXT found'))
+ else:
+ debug_output(_('NXT not found'))
+
+ self.anterior = time.time()
+ self.res = -1
+ self.motor_pos = 0
+
+ def setup(self):
+
+ # Palette of Motors
+ palette_motors = make_palette('nxt-motors', COLOR_NOTPRESENT, _('Palette of LEGO NXT blocks of motors'))
+
+ if self.nxtbrick:
+ COLOR = COLOR_PRESENT
+ else:
+ COLOR = COLOR_NOTPRESENT
+
+ primitive_dictionary['nxtrefresh'] = self._prim_nxtrefresh
+ palette_motors.add_block('nxtrefresh',
+ style='basic-style',
+ label=_('refresh NXT'),
+ prim_name='nxtrefresh',
+ help_string=_('Search for a connected NXT brick.'))
+ self.tw.lc.def_prim('nxtrefresh', 0, lambda self :
+ primitive_dictionary['nxtrefresh']())
+ BOX_COLORS['nxtrefresh'] = COLOR_PRESENT[:]
+
+ primitive_dictionary['nxtplaytone'] = self._prim_nxtplaytone
+ palette_motors.add_block('nxtplaytone',
+ style='basic-style-2arg',
+ label=[_('play tone'), _('freq'), _('time')],
+ default=[433, 500],
+ help_string=_('Play a tone at freq for time.'),
+ prim_name='nxtplaytone')
+ self.tw.lc.def_prim('nxtplaytone', 2, lambda self, x, y:
+ primitive_dictionary['nxtplaytone'](x, y))
+ BOX_COLORS['nxtplaytone'] = COLOR[:]
+
+ primitive_dictionary['nxtturnmotor'] = self._prim_nxtturnmotor
+ palette_motors.add_block('nxtturnmotor',
+ style='basic-style-3arg',
+ label=[_('turn motor\nrotations'), _('port'), _('power')],
+ default=['None', 1, 100],
+ help_string=_('turn a motor'),
+ prim_name='nxtturnmotor')
+ self.tw.lc.def_prim('nxtturnmotor', 3, lambda self, x, y, z:
+ primitive_dictionary['nxtturnmotor'](x, y, z))
+ BOX_COLORS['nxtturnmotor'] = COLOR[:]
+
+ primitive_dictionary['nxtsyncmotors'] = self._prim_nxtsyncmotors
+ palette_motors.add_block('nxtsyncmotors',
+ style='basic-style-3arg',
+ label=[_('sync motors\nsteering'), _('power'), _('rotations')],
+ default=[100, 0, 1],
+ help_string=_('synchronize two motors'),
+ prim_name='nxtsyncmotors')
+ self.tw.lc.def_prim('nxtsyncmotors', 3, lambda self, x, y, z:
+ primitive_dictionary['nxtsyncmotors'](x, y, z))
+ BOX_COLORS['nxtsyncmotors'] = COLOR[:]
+
+ primitive_dictionary['nxtporta'] = self._prim_nxtporta
+ palette_motors.add_block('nxtporta',
+ style='box-style',
+ label=_('PORT A'),
+ help_string=_('PORT A of the brick'),
+ prim_name='nxtporta')
+ self.tw.lc.def_prim('nxtporta', 0, lambda self:
+ primitive_dictionary['nxtporta']())
+ BOX_COLORS['nxtporta'] = COLOR[:]
+
+ primitive_dictionary['nxtportb'] = self._prim_nxtportb
+ palette_motors.add_block('nxtportb',
+ style='box-style',
+ label=_('PORT B'),
+ help_string=_('PORT B of the brick'),
+ prim_name='nxtportb')
+ self.tw.lc.def_prim('nxtportb', 0, lambda self:
+ primitive_dictionary['nxtportb']())
+ BOX_COLORS['nxtportb'] = COLOR[:]
+
+ primitive_dictionary['nxtportc'] = self._prim_nxtportc
+ palette_motors.add_block('nxtportc',
+ style='box-style',
+ label=_('PORT C'),
+ help_string=_('PORT C of the brick'),
+ prim_name='nxtportc')
+ self.tw.lc.def_prim('nxtportc', 0, lambda self:
+ primitive_dictionary['nxtportc']())
+ BOX_COLORS['nxtportc'] = COLOR[:]
+
+ primitive_dictionary['nxtstartmotor'] = self._prim_nxtstartmotor
+ palette_motors.add_block('nxtstartmotor',
+ style='basic-style-2arg',
+ label=[_('start motor'), _('port'), _('power')],
+ default=['None', 100],
+ help_string=_('Run a motor forever.'),
+ prim_name='nxtstartmotor')
+ self.tw.lc.def_prim('nxtstartmotor', 2, lambda self, x, y:
+ primitive_dictionary['nxtstartmotor'](x, y))
+ BOX_COLORS['nxtstartmotor'] = COLOR[:]
+
+ primitive_dictionary['nxtbrake'] = self._prim_nxtbrake
+ palette_motors.add_block('nxtbrake',
+ style='basic-style-1arg',
+ label=_('brake motor'),
+ default=['None'],
+ help_string=_('Stop a specified motor.'),
+ prim_name='nxtbrake')
+ self.tw.lc.def_prim('nxtbrake', 1, lambda self, x:
+ primitive_dictionary['nxtbrake'](x))
+ BOX_COLORS['nxtbrake'] = COLOR[:]
+
+ primitive_dictionary['nxtmotorreset'] = self._prim_nxtmotorreset
+ palette_motors.add_block('nxtmotorreset',
+ style='basic-style-1arg',
+ label=_('reset motor'),
+ default=['None'],
+ help_string=_('Reset the motor counter.'),
+ prim_name='nxtmotorreset')
+ self.tw.lc.def_prim('nxtmotorreset', 1, lambda self, x:
+ primitive_dictionary['nxtmotorreset'](x))
+ BOX_COLORS['nxtmotorreset'] = COLOR[:]
+
+ primitive_dictionary['nxtmotorposition'] = self._prim_nxtmotorposition
+ palette_motors.add_block('nxtmotorposition',
+ style='number-style-1arg',
+ label=_('motor position'),
+ default=['None'],
+ help_string=_('Get the motor position.'),
+ prim_name='nxtmotorposition')
+ self.tw.lc.def_prim('nxtmotorposition', 1, lambda self, x:
+ primitive_dictionary['nxtmotorposition'](x))
+ BOX_COLORS['nxtmotorposition'] = COLOR[:]
+
+ # Palette of Sensors
+ palette_sensors = make_palette('nxt-sensors', COLOR_NOTPRESENT,
+ _('Palette of LEGO NXT blocks of sensors'))
+
+ primitive_dictionary['nxtport1'] = self._prim_nxtport1
+ palette_sensors.add_block('nxtport1',
+ style='box-style',
+ label=_('PORT 1'),
+ help_string=_('PORT 1 of the brick'),
+ prim_name='nxtport1')
+ self.tw.lc.def_prim('nxtport1', 0, lambda self:
+ primitive_dictionary['nxtport1']())
+ BOX_COLORS['nxtport1'] = COLOR[:]
+
+ primitive_dictionary['nxtreadsensor'] = self._prim_nxtreadsensor
+ palette_sensors.add_block('nxtreadsensor',
+ style='number-style-block',
+ label=[_('read'), _('sensor'), _('port')],
+ help_string=_('Read sensor output.'),
+ prim_name='nxtreadsensor')
+ self.tw.lc.def_prim('nxtreadsensor', 2, lambda self, x, y:
+ primitive_dictionary['nxtreadsensor'](x, y))
+ BOX_COLORS['nxtreadsensor'] = COLOR[:]
+
+ primitive_dictionary['nxtport2'] = self._prim_nxtport2
+ palette_sensors.add_block('nxtport2',
+ style='box-style',
+ label=_('PORT 2'),
+ help_string=_('PORT 2 of the brick'),
+ prim_name='nxtport2')
+ self.tw.lc.def_prim('nxtport2', 0, lambda self:
+ primitive_dictionary['nxtport2']())
+ BOX_COLORS['nxtport2'] = COLOR[:]
+
+ primitive_dictionary['nxtcolor'] = self._prim_nxtcolor
+ palette_sensors.add_block('nxtcolor',
+ style='box-style',
+ label=_('color'),
+ help_string=_('color sensor'),
+ prim_name='nxtcolor')
+ self.tw.lc.def_prim('nxtcolor', 0, lambda self:
+ primitive_dictionary['nxtcolor']())
+ BOX_COLORS['nxtcolor'] = COLOR[:]
+
+ primitive_dictionary['nxtlight'] = self._prim_nxtlight
+ palette_sensors.add_block('nxtlight',
+ style='box-style',
+ label=_('light'),
+ help_string=_('light sensor'),
+ prim_name='nxtlight')
+ self.tw.lc.def_prim('nxtlight', 0, lambda self:
+ primitive_dictionary['nxtlight']())
+ BOX_COLORS['nxtlight'] = COLOR[:]
+
+ primitive_dictionary['nxtport3'] = self._prim_nxtport3
+ palette_sensors.add_block('nxtport3',
+ style='box-style',
+ label=_('PORT 3'),
+ help_string=_('PORT 3 of the brick'),
+ prim_name='nxtport3')
+ self.tw.lc.def_prim('nxtport3', 0, lambda self:
+ primitive_dictionary['nxtport3']())
+ BOX_COLORS['nxtport3'] = COLOR[:]
+
+ primitive_dictionary['nxttouch'] = self._prim_nxttouch
+ palette_sensors.add_block('nxttouch',
+ style='box-style',
+ label=_('touch'),
+ help_string=_('touch sensor'),
+ prim_name='nxttouch')
+ self.tw.lc.def_prim('nxttouch', 0, lambda self:
+ primitive_dictionary['nxttouch']())
+ BOX_COLORS['nxttouch'] = COLOR[:]
+
+ primitive_dictionary['nxtultrasonic'] = self._prim_nxtultrasonic
+ palette_sensors.add_block('nxtultrasonic',
+ style='box-style',
+ label=_('ultrasonic'),
+ help_string=_('distance sensor'),
+ prim_name='nxtultrasonic')
+ self.tw.lc.def_prim('nxtultrasonic', 0, lambda self:
+ primitive_dictionary['nxtultrasonic']())
+ BOX_COLORS['nxtultrasonic'] = COLOR[:]
+
+ primitive_dictionary['nxtport4'] = self._prim_nxtport4
+ palette_sensors.add_block('nxtport4',
+ style='box-style',
+ label=_('PORT 4'),
+ help_string=_('PORT 4 of the brick'),
+ prim_name='nxtport4')
+ self.tw.lc.def_prim('nxtport4', 0, lambda self:
+ primitive_dictionary['nxtport4']())
+ BOX_COLORS['nxtport4'] = COLOR[:]
+
+ primitive_dictionary['nxtsound'] = self._prim_nxtsound
+ palette_sensors.add_block('nxtsound',
+ style='box-style',
+ label=_('sound'),
+ help_string=_('sound sensor'),
+ prim_name='nxtsound')
+ self.tw.lc.def_prim('nxtsound', 0, lambda self:
+ primitive_dictionary['nxtsound']())
+ BOX_COLORS['nxtsound'] = COLOR[:]
+
+ primitive_dictionary['nxtsetcolor'] = self._prim_nxtsetcolor
+ palette_sensors.add_block('nxtsetcolor',
+ style='basic-style-2arg',
+ label=[_('set light'), _('color'), _('port')],
+ help_string=_('Set color sensor light.'),
+ prim_name='nxtsetcolor')
+ self.tw.lc.def_prim('nxtsetcolor', 2, lambda self, x, y:
+ primitive_dictionary['nxtsetcolor'](x, y))
+ BOX_COLORS['nxtsetcolor'] = COLOR[:]
+
+ def start(self):
+ # This gets called by the start button
+ pass
+
+ def stop(self):
+ # This gets called by the stop button
+ if self.nxtbrick:
+ try:
+ Motor(self.nxtbrick, PORT_A).idle()
+ Motor(self.nxtbrick, PORT_B).idle()
+ Motor(self.nxtbrick, PORT_C).idle()
+ except:
+ pass
+
+
+ def goto_background(self):
+ # This gets called when your process is sent to the background
+ pass
+
+ def return_to_foreground(self):
+ # This gets called when your process returns from the background
+ pass
+
+ def quit(self):
+ # This gets called by the quit button
+ if self.nxtbrick:
+ try:
+ Motor(self.nxtbrick, PORT_A).idle()
+ Motor(self.nxtbrick, PORT_B).idle()
+ Motor(self.nxtbrick, PORT_C).idle()
+ except:
+ pass
+
+ def _prim_nxtturnmotor(self, port, turns, power):
+ if self.nxtbrick:
+ if (port in NXT_MOTOR_PORTS):
+ port = NXT_MOTOR_PORTS[port]
+ if not((power < -127) or (power > 127)):
+ try:
+ m = Motor(self.nxtbrick, port)
+ m.turn(power, int(turns*360), brake=True)
+ m.brake()
+ except:
+ return ERROR
+ else:
+ return ERROR_POWER
+ else:
+ return ERROR_PORT
+ else:
+ return ERROR_BRICK
+
+ def _prim_nxtsyncmotors(self, power, steering, turns):
+ if self.nxtbrick:
+ if not((power < -127) or (power > 127)):
+ try:
+ motorB = Motor(self.nxtbrick, PORT_B)
+ motorC = Motor(self.nxtbrick, PORT_C)
+ syncmotors = SynchronizedMotors(motorB, motorC, steering)
+ syncmotors.turn(power, int(turns*360))
+ except:
+ return ERROR
+ else:
+ return ERROR_POWER
+ else:
+ return ERROR_BRICK
+
+ def _prim_nxtplaytone(self, freq, time):
+ if self.nxtbrick:
+ try:
+ self.nxtbrick.play_tone(freq, time)
+ except:
+ return ERROR
+ else:
+ return ERROR_BRICK
+
+ def _prim_nxttouch(self):
+ return _('touch')
+
+ def _prim_nxtultrasonic(self):
+ return _('ultrasonic')
+
+ def _prim_nxtcolor(self):
+ return _('color')
+
+ def _prim_nxtlight(self):
+ return _('light')
+
+ def _prim_nxtsound(self):
+ return _('sound')
+
+ def _prim_nxtport1(self):
+ return _('PORT 1')
+
+ def _prim_nxtport2(self):
+ return _('PORT 2')
+
+ def _prim_nxtport3(self):
+ return _('PORT 3')
+
+ def _prim_nxtport4(self):
+ return _('PORT 4')
+
+ def _prim_nxtporta(self):
+ return _('PORT A')
+
+ def _prim_nxtportb(self):
+ return _('PORT B')
+
+ def _prim_nxtportc(self):
+ return _('PORT C')
+
+ def _prim_nxtreadsensor(self, sensor, port):
+ """ Read sensor at specified port"""
+ if (port in NXT_SENSOR_PORTS):
+ actual = time.time()
+ if ((actual - self.anterior) > MINIMO_INTERVALO) and (self.nxtbrick):
+ self.anterior = actual
+ port = NXT_SENSOR_PORTS[port]
+ try:
+ if sensor == _('color'):
+ self.res = colors[Color20(self.nxtbrick, port).get_sample()]
+ elif sensor == _('light'):
+ self.res = int(Color20(self.nxtbrick, port).get_light())
+ elif sensor == _('ultrasonic'):
+ self.res = Ultrasonic(self.nxtbrick, port).get_sample()
+ elif sensor == _('touch'):
+ self.res = Touch(self.nxtbrick, port).get_sample()
+ elif sensor == _('sound'):
+ self.res = Sound(self.nxtbrick, port).get_sample()
+ except:
+ pass
+ return self.res
+ else:
+ return ERROR_PORT
+
+ def _prim_nxtstartmotor(self, port, power):
+ if self.nxtbrick:
+ if (port in NXT_MOTOR_PORTS):
+ port = NXT_MOTOR_PORTS[port]
+ if not((power < -127) or (power > 127)):
+ try:
+ m = Motor(self.nxtbrick, port)
+ m.weak_turn(power, 0)
+ except:
+ return ERROR
+ else:
+ return ERROR_POWER
+ else:
+ return ERROR_PORT
+ else:
+ return ERROR_BRICK
+
+ def _prim_nxtbrake(self, port):
+ if self.nxtbrick:
+ if (port in NXT_MOTOR_PORTS):
+ port = NXT_MOTOR_PORTS[port]
+ try:
+ m = Motor(self.nxtbrick, port)
+ m.brake()
+ except:
+ return ERROR
+ else:
+ return ERROR_PORT
+ else:
+ return ERROR_BRICK
+
+ def _prim_nxtsetcolor(self, color, port):
+ if self.nxtbrick:
+ if (port in NXT_SENSOR_PORTS):
+ port = NXT_SENSOR_PORTS[port]
+ if color == WHITE:
+ color = Type.COLORFULL
+ elif color == CONSTANTS['red']:
+ color = Type.COLORRED
+ elif color == CONSTANTS['green']:
+ color = Type.COLORGREEN
+ elif color == CONSTANTS['blue']:
+ color = Type.COLORBLUE
+ else:
+ color = Type.COLORNONE
+ try:
+ Color20(self.nxtbrick, port).set_light_color(color)
+ except:
+ return ERROR
+ else:
+ return ERROR_PORT
+ else:
+ return ERROR_BRICK
+
+ def _prim_nxtmotorreset(self, port):
+ if self.nxtbrick:
+ if (port in NXT_MOTOR_PORTS):
+ port = NXT_MOTOR_PORTS[port]
+ try:
+ m = Motor(self.nxtbrick, port)
+ t = m.get_tacho()
+ self.motor_pos = t.tacho_count
+ m.idle()
+ except:
+ return ERROR
+ else:
+ return ERROR_PORT
+ else:
+ return ERROR_BRICK
+
+ def _prim_nxtmotorposition(self, port):
+ if self.nxtbrick:
+ if (port in NXT_MOTOR_PORTS):
+ port = NXT_MOTOR_PORTS[port]
+ try:
+ m = Motor(self.nxtbrick, port)
+ t = m.get_tacho()
+ d = t.tacho_count - self.motor_pos
+ return d
+ except:
+ return ERROR
+ else:
+ return ERROR_PORT
+ else:
+ return ERROR_BRICK
+
+ def _prim_nxtrefresh(self):
+ try:
+ self.nxtbrick.get_device_info()
+ except:
+ self.nxtbrick = nxt.locator.find_one_brick()
+
+ self.change_color_blocks()
+
+ self.tw.show_toolbar_palette(palette_name_to_index('nxt-motors'), regenerate=True, show=False)
+ self.tw.show_toolbar_palette(palette_name_to_index('nxt-sensors'), regenerate=True, show=False)
+
+ def change_color_blocks(self):
+ motors_blocks = palette_blocks[palette_name_to_index('nxt-motors')]
+ sensors_blocks = palette_blocks[palette_name_to_index('nxt-sensors')]
+ nxt_palette_blocks = motors_blocks + sensors_blocks
+
+ for block in self.tw.block_list.list:
+ if block.type in ['proto', 'block']:
+ if block.name in nxt_palette_blocks:
+ if (self.nxtbrick) or (block.name == 'nxtrefresh'):
+ BOX_COLORS[block.name] = COLOR_PRESENT[:]
+ else:
+ BOX_COLORS[block.name] = COLOR_NOTPRESENT[:]
+ block.refresh()
+
diff --git a/nxt_plugin/prueba1.py b/nxt_plugin/prueba1.py
new file mode 100644
index 0000000..9e8ea34
--- /dev/null
+++ b/nxt_plugin/prueba1.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# importamos las funciones de la API
+import nxt.locator
+from nxt.motor import *
+
+# esta función trata de conectarse al brick
+b = nxt.locator.find_one_brick()
+
+# movemos el motor conectado en el puerto B del brick b
+motor_izquierdo = Motor(b, PORT_B)
+
+# con potencia 100 y que de una vuelta completa (360º)
+motor_izquierdo.turn(100, 360)
diff --git a/nxt_plugin/usb/ACKNOWLEDGEMENTS b/nxt_plugin/usb/ACKNOWLEDGEMENTS
new file mode 100644
index 0000000..2a04054
--- /dev/null
+++ b/nxt_plugin/usb/ACKNOWLEDGEMENTS
@@ -0,0 +1,37 @@
+This is a list of people who has contributed to PyUSB 1.0 development.
+If I forgot you, please email me.
+
+PyUSB 1.0.0 (alpha 3)
+---------------------
+
+- Robert von Burg: for the bug reports about Python versions compatibility and
+ kernel driver functions.
+- James Rowe: for patches to the tutorial file.
+- Braiden Kindt: for the patch fixing bug when less than o number of requested
+ bytes are read.
+
+PyUSB 1.0.0 (alpha 2)
+---------------------
+
+- Chris Clark: for the bug report in the log subsystem.
+- Emmanuel Blot: for the patch which improves performance when debug is disabled.
+- Peter Bigot: for the patch that fixes get_active_configuration seg fault,
+ the patch to add error code to USBError and the patch to fix
+ parameter order in the USBError.
+- Travis Robinson and Xiaofan Chen: for let me use their benchmark firmware.
+- Poul-Henning Kamp: for the suggestion of ``bus`` and ``address`` attributes.
+
+PyUSB 1.0.0 (alpha 1)
+---------------------
+
+- Xiaofan Chen: for support in mailing list.
+- Poul-Henning Kam: for the documentation patches.
+
+PyUSB 1.0.0 (alpha 0)
+---------------------
+
+- Thomas Reitmayr: thanks for your patches to get PyUSB running with libusb 1.0.
+- Carl Ritson: thanks for your patch to get minimal working of legacy layer.
+- Romain Aviolat: thanks for pointing out a mistake in the tutorial and to report a bug in ctrl_transfer.
+- Xiaofan Chen: thanks for your effort testing PyUSB with libusb 1.0 Windows backend and on FreeBSD.
+
diff --git a/nxt_plugin/usb/LICENSE b/nxt_plugin/usb/LICENSE
new file mode 100644
index 0000000..1c88b5a
--- /dev/null
+++ b/nxt_plugin/usb/LICENSE
@@ -0,0 +1,27 @@
+Copyright (C) 2009-2011 Wander Lairson Costa. All Rights Reserved.
+
+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. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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.
+
diff --git a/nxt_plugin/usb/README b/nxt_plugin/usb/README
new file mode 100644
index 0000000..d8bbb48
--- /dev/null
+++ b/nxt_plugin/usb/README
@@ -0,0 +1,78 @@
+=======================================
+PyUSB 1.0 - Easy USB access from Python
+=======================================
+
+Introduction
+============
+
+The PyUSB module provides for Python easy access to the host
+machine's Universal Serial Bus (USB) system.
+
+Until 0.4 version, PyUSB used to be a thin wrapper over libusb.
+With 1.0 version, things changed considerably. Now PyUSB is an
+API rich, backend neutral Python USB module easy to use.
+
+As with most Python modules, PyUSB's documentation is based on Python
+doc strings and can therefore be manipulated by tools such as pydoc.
+
+You can also find a tutorial at: http://pyusb.sourceforge.net/docs/1.0/tutorial.html.
+
+PyUSB is being developed and tested in Linux and Windows, but it should work
+fine in any platform running Python >= 2.4, ctypes and at least one of the
+builtin backends.
+
+PyUSB supports libusb 0.1, libusb 1.0 and OpenUSB, but the user does not need
+to worry about that, unless in some corner cases.
+
+If you have any question about PyUSB, you can use the PyUSB mailing list
+hosted in the SourceForge. In the PyUSB website (http://pyusb.sourceforge.net)
+you can find instructions on how to subscribe to the mailing list.
+
+Installing PyUSB on GNU/Linux Systems
+=====================================
+
+These instructions are for Debian-based systems. Instructions for
+other flavors of GNU/Linux should be similar.
+
+You will first need to install the following packages:
+
+1) python (PyUSB is useless without it), version >= 2.4
+2) At least one of the supported libraries (libusb 1.0, libusb 0.1 or OpenUSB)
+3) If your Python version is < 2.5, you have to install ctypes as a separate package,
+ because these versions of Python does not ship it.
+
+For example, the command::
+
+ $ sudo apt-get install python libusb
+
+should install all these packages on most Debian-based systems with
+access to the proper package repositories.
+
+Once the above packages are installed, you can install PyUSB
+with the command::
+
+ $ sudo python setup.py install
+
+Run it as root from within the same directory as this README file.
+
+Installing PyUSB on Windows
+===========================
+
+Now that PyUSB is 100% written in Python, you install it on Windows
+in the same way you do on Linux::
+
+ python setup.py install
+
+Remember that you need libusb (1.0 or 0.1) or OpenUSB running on your
+system. For Windows users, libusb 1.0 is still experimental, so it is
+recommended libusb-win32 package. Check the libusb website for updates
+(http://www.libusb.org).
+
+Reporting bugs/Submitting patches
+=================================
+
+Some people have been sending patches and reporting bugs directly
+at my email. Please, do it through Trac, I had a hardtime tracking
+their names to put them in the acknowledgments file. ;-)
+
+PS: this README file was based on the great Josh Lifton's one... ^_^
diff --git a/nxt_plugin/usb/ReleaseNotes b/nxt_plugin/usb/ReleaseNotes
new file mode 100644
index 0000000..40e7945
--- /dev/null
+++ b/nxt_plugin/usb/ReleaseNotes
@@ -0,0 +1,65 @@
+==========
+PyUSB News
+==========
+
+What's new in PyUSB 1.0.0 (alpha 2)?
+====================================
+
+- Test firmware now lives in its own respository (https://github.com/walac/bmfw).
+- ``USBError`` now has the property ``backend_error_code`` that tells the
+ backend specific error.
+- ``errno`` value in ``USBError`` is translated according to the backend error.
+- Now ``Device`` class has the ``bus`` and ``address`` attributes to
+ differentiate identical devices.
+- Optimization when log is disabled (by Emmanuel Blot).
+- Several other minor fixes and improvaments (check ChangeLog file).
+
+Features not implemented
+------------------------
+
+- OpenUSB support.
+- Isochronous transfer.
+
+What's new in PyUSB 1.0.0 (alpha 1)?
+====================================
+
+This release implements more PyUSB 1.0 features towards beta stage. The new
+features implemented include:
+
+- Standard control requests through usb.control module.
+- Request current configuration from device when you do not call
+ set_configuration.
+- get_string function in the usb.util module to get string descriptors.
+- Full 0.4 API emulation.
+- Device is not reset anymore in test cases to avoid problems in systems
+ where it does not work.
+
+Features not implemented
+------------------------
+
+- OpenUSB support.
+- Isochronous transfer.
+
+What's new in PyUSB 1.0.0 (alpha 0)?
+====================================
+
+This is the first PyUSB 1.0 series public release. This is an alpha release, which
+means that most of the features described in the README file and on the website are
+not yet stable or even implemented.
+
+Features not implemented
+------------------------
+
+- Full support for legacy 0.4 legacy code (although partial support is provided).
+- OpenUSB backend.
+- libusb 1.0 windows backend stability (although it is reasonable usable).
+- Support for several standard control requests (including GET_STRING).
+- Python < 2.6 and Python 3 not yet fully tested.
+
+Known issues
+------------
+
+- ``reset`` method fails under FreeUSB (libusb 1.0 backend).
+- ``reset`` method hangs under Windows (libusb 1.0 backend).
+- Sometimes occurs `read` timeout on Windows (libusb 1.0 backend).
+- Test cases fail to run under cygwin.
diff --git a/nxt_plugin/usb/TODO b/nxt_plugin/usb/TODO
new file mode 100644
index 0000000..784de26
--- /dev/null
+++ b/nxt_plugin/usb/TODO
@@ -0,0 +1,29 @@
+1.0.0-a2:
+ * Validate it on platforms other than Windows and Linux.
+ * Finish implementation and test OpenUSB backend.
+ * Samples.
+ * Improve documentation.
+ * Implement isochronous transfers.
+ * Test, test and test.
+
+1.0.0-a1:
+ * Validate it on platforms other than Windows and Linux.
+ * Finish implementation and test OpenUSB backend.
+ * Samples.
+ * Improve documentation.
+ * Implement isochronous transfers.
+ * Test, test and test.
+
+1.0.0-a0:
+ * Implement standard control requests API.
+ * Finish implementation of legacy compatibility layer.
+ * Determine automatically current configuration when user hasn't set it.
+ * Validate it on platforms other than Windows and Linux.
+ * Finish implementation and test of OpenUSB backend.
+ * Validate it on Python 2.3 and Python 3.x.
+ * Samples.
+ * Improve documentation.
+ * Implement isochronous transfers.
+ * Upgrade PIC test firmware to use 2.6a version Microchip USB stack.
+ * Test, test and test.
+
diff --git a/nxt_plugin/usb/__init__.py b/nxt_plugin/usb/__init__.py
new file mode 100644
index 0000000..8909cf2
--- /dev/null
+++ b/nxt_plugin/usb/__init__.py
@@ -0,0 +1,92 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+r"""PyUSB - Easy USB access in Python
+
+This package exports the following modules and subpackages:
+
+ core - the main USB implementation
+ legacy - the compatibility layer with 0.x version
+ backend - the support for backend implementations.
+
+Since version 1.0, main PyUSB implementation lives in the 'usb.core'
+module. New applications are encouraged to use it.
+"""
+
+import logging
+import os
+
+__author__ = 'Wander Lairson Costa'
+
+__all__ = ['legacy', 'core', 'backend', 'util']
+
+
+def _setup_log():
+ from usb import _debug
+ logger = logging.getLogger('usb')
+ debug_level = os.getenv('PYUSB_DEBUG_LEVEL')
+
+ if debug_level is not None:
+ _debug.enable_tracing(True)
+ filename = os.getenv('PYUSB_LOG_FILENAME')
+
+ LEVELS = {'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ 'critical': logging.CRITICAL}
+
+ level = LEVELS.get(debug_level, logging.CRITICAL + 10)
+ logger.setLevel(level = level)
+
+ try:
+ handler = logging.FileHandler(filename)
+ except:
+ handler = logging.StreamHandler()
+
+ fmt = logging.Formatter('%(asctime)s %(levelname)s:%(name)s:%(message)s')
+ handler.setFormatter(fmt)
+ logger.addHandler(handler)
+ else:
+ class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+
+ # We set the log level to avoid delegation to the
+ # parent log handler (if there is one).
+ # Thanks to Chris Clark to pointing this out.
+ logger.setLevel(logging.CRITICAL + 10)
+
+ logger.addHandler(NullHandler())
+
+
+_setup_log()
+
+# We import all 'legacy' module symbols to provide compatility
+# with applications that use 0.x versions.
+from usb.legacy import *
diff --git a/nxt_plugin/usb/_debug.py b/nxt_plugin/usb/_debug.py
new file mode 100644
index 0000000..13b0ced
--- /dev/null
+++ b/nxt_plugin/usb/_debug.py
@@ -0,0 +1,77 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+__author__ = 'Wander Lairson Costa'
+
+__all__ = ['methodtrace', 'functiontrace']
+
+import logging
+import usb._interop as _interop
+
+_enable_tracing = False
+
+def enable_tracing(enable):
+ global _enable_tracing
+ _enable_tracing = enable
+
+def _trace_function_call(logger, fname, *args, **named_args):
+ logger.debug(
+ # TODO: check if 'f' is a method or a free function
+ fname + '(' + \
+ ', '.join((str(val) for val in args)) + \
+ ', '.join((name + '=' + str(val) for name, val in named_args.items())) + ')'
+ )
+
+# decorator for methods calls tracing
+def methodtrace(logger):
+ def decorator_logging(f):
+ if not _enable_tracing:
+ return f
+ def do_trace(*args, **named_args):
+ # this if is just a optimization to avoid unecessary string formatting
+ if logging.DEBUG >= logger.getEffectiveLevel():
+ fn = type(args[0]).__name__ + '.' + f.__name__
+ _trace_function_call(logger, fn, *args[1:], **named_args)
+ return f(*args, **named_args)
+ _interop._update_wrapper(do_trace, f)
+ return do_trace
+ return decorator_logging
+
+# decorator for methods calls tracing
+def functiontrace(logger):
+ def decorator_logging(f):
+ if not _enable_tracing:
+ return f
+ def do_trace(*args, **named_args):
+ # this if is just a optimization to avoid unecessary string formatting
+ if logging.DEBUG >= logger.getEffectiveLevel():
+ _trace_function_call(logger, f.__name__, *args, **named_args)
+ return f(*args, **named_args)
+ _interop._update_wrapper(do_trace, f)
+ return do_trace
+ return decorator_logging
diff --git a/nxt_plugin/usb/_interop.py b/nxt_plugin/usb/_interop.py
new file mode 100644
index 0000000..6069d5e
--- /dev/null
+++ b/nxt_plugin/usb/_interop.py
@@ -0,0 +1,135 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+# All the hacks necessary to assure compatibility across all
+# supported versions come here.
+# Please, note that there is one version check for each
+# hack we need to do, this makes maintenance easier... ^^
+
+import sys
+import array
+
+__all__ = ['_reduce', '_set', '_next', '_groupby', '_sorted', '_update_wrapper']
+
+# we support Python >= 2.3
+assert sys.hexversion >= 0x020300f0
+
+# On Python 3, reduce became a functools module function
+try:
+ import functools
+ _reduce = functools.reduce
+except (ImportError, AttributeError):
+ _reduce = reduce
+
+# we only have the builtin set type since 2.5 version
+try:
+ _set = set
+except NameError:
+ import sets
+ _set = sets.Set
+
+# On Python >= 2.6, we have the builtin next() function
+# On Python 2.5 and before, we have to call the iterator method next()
+def _next(iter):
+ try:
+ return next(iter)
+ except NameError:
+ return iter.next()
+
+# groupby is available only since 2.4 version
+try:
+ import itertools
+ _groupby = itertools.groupby
+except (ImportError, AttributeError):
+ # stolen from Python docs
+ class _groupby(object):
+ # [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
+ # [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
+ def __init__(self, iterable, key=None):
+ if key is None:
+ key = lambda x: x
+ self.keyfunc = key
+ self.it = iter(iterable)
+ self.tgtkey = self.currkey = self.currvalue = object()
+ def __iter__(self):
+ return self
+ def next(self):
+ while self.currkey == self.tgtkey:
+ self.currvalue = _next(self.it) # Exit on StopIteration
+ self.currkey = self.keyfunc(self.currvalue)
+ self.tgtkey = self.currkey
+ return (self.currkey, self._grouper(self.tgtkey))
+ def _grouper(self, tgtkey):
+ while self.currkey == tgtkey:
+ yield self.currvalue
+ self.currvalue = _next(self.it) # Exit on StopIteration
+ self.currkey = self.keyfunc(self.currvalue)
+
+# builtin sorted function is only availale since 2.4 version
+try:
+ _sorted = sorted
+except NameError:
+ def _sorted(l, key=None, reverse=False):
+ # sort function on Python 2.3 does not
+ # support 'key' parameter
+ class KeyToCmp(object):
+ def __init__(self, K):
+ self.key = K
+ def __call__(self, x, y):
+ kx = self.key(x)
+ ky = self.key(y)
+ if kx < ky:
+ return reverse and 1 or -1
+ elif kx > ky:
+ return reverse and -1 or 1
+ else:
+ return 0
+ tmp = list(l)
+ tmp.sort(KeyToCmp(key))
+ return tmp
+
+try:
+ import functools
+ _update_wrapper = functools.update_wrapper
+except (ImportError, AttributeError):
+ def _update_wrapper(wrapper, wrapped):
+ wrapper.__name__ = wrapped.__name__
+ wrapper.__module__ = wrapped.__module__
+ wrapper.__doc__ = wrapped.__doc__
+ wrapper.__dict__ = wrapped.__dict__
+
+def as_array(data=None):
+ if data is None:
+ return array.array('B')
+ try:
+ return array.array('B', data)
+ except TypeError:
+ # When you pass a unicode string, you got a TypeError
+ # if first parameter is not 'u'
+ return array.array('u', data)
+
diff --git a/nxt_plugin/usb/backend/__init__.py b/nxt_plugin/usb/backend/__init__.py
new file mode 100644
index 0000000..67ee00f
--- /dev/null
+++ b/nxt_plugin/usb/backend/__init__.py
@@ -0,0 +1,368 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+r"""usb.backend - Backend interface.
+
+This module exports:
+
+IBackend - backend interface.
+
+Backends are Python objects which implement the IBackend interface.
+The easiest way to do so is inherinting from IBackend.
+
+PyUSB already provides backends for libusb versions 0.1 and 1.0,
+and OpenUSB library. Backends modules included with PyUSB are required to
+export the get_backend() function, which returns an instance of a backend
+object. You can provide your own customized backend if you
+want to. Bellow you find a skeleton of a backend implementation module:
+
+import usb.backend
+
+class MyBackend(usb.backend.IBackend):
+ pass
+
+def get_backend():
+ return MyBackend()
+
+You can use your customized backend by passing it as the backend parameter of the
+usb.core.find() function. For example:
+
+import custom_backend
+import usb.core
+
+myidVendor = 0xfffe
+myidProduct = 0x0001
+
+mybackend = custom_backend.get_backend()
+
+dev = usb.core.find(backend = mybackend, idProduct=myidProduct,
+ idVendor=myidVendor)
+
+For custom backends, you are not required to supply the get_backend() function,
+since the application code will instantiate the backend.
+
+If you do not provide a backend to the find() function, it will use one of the
+defaults backend according to its internal rules. For details, consult the
+find() function documentation.
+"""
+
+__author__ = 'Wander Lairson Costa'
+
+__all__ = ['IBackend', 'libusb01', 'libusb10', 'openusb']
+
+def _not_implemented(func):
+ raise NotImplementedError(func.__name__)
+
+class IBackend(object):
+ r"""Backend interface.
+
+ IBackend is the basic interface for backend implementations. By default,
+ the methods of the interface raise a NotImplementedError exception. A
+ backend implementation should replace the methods to provide the funcionality
+ necessary.
+
+ As Python is a dynamic typed language, you are not obligated to inherit from
+ IBackend: everything that bahaves like an IBackend is an IBackend. But you
+ are strongly recommended to do so, inheriting from IBackend provides consistent
+ default behavior.
+ """
+ def enumerate_devices(self):
+ r"""This function is required to return an iterable object which
+ yields an implementation defined device identification for each
+ USB device found in the system.
+
+ The device identification object is used as argument to other methods
+ of the interface.
+ """
+ _not_implemented(self.enumerate_devices)
+
+ def get_device_descriptor(self, dev):
+ r"""Return the device descriptor of the given device.
+
+ The object returned is required to have all the Device Descriptor
+ fields accessible as member variables. They must be convertible (but
+ not required to be equal) to the int type.
+
+ dev is an object yielded by the iterator returned by the enumerate_devices()
+ method.
+ """
+ _not_implemented(self.get_device_descriptor)
+
+ def get_configuration_descriptor(self, dev, config):
+ r"""Return a configuration descriptor of the given device.
+
+ The object returned is required to have all the Configuration Descriptor
+ fields acessible as member variables. They must be convertible (but
+ not required to be equal) to the int type.
+
+ The dev parameter is the already described device identification object.
+ config is the logical index of the configuration (not the bConfigurationValue
+ field). By "logical index" we mean the relative order of the configurations
+ returned by the peripheral as a result of GET_DESCRIPTOR request.
+ """
+ _not_implemented(self.get_configuration_descriptor)
+
+ def get_interface_descriptor(self, dev, intf, alt, config):
+ r"""Return an interface descriptor of the given device.
+
+ The object returned is required to have all the Interface Descriptor
+ fields accessible as member variables. They must be convertible (but
+ not required to be equal) to the int type.
+
+ The dev parameter is the already described device identification object.
+ The intf parameter is the interface logical index (not the bInterfaceNumber field)
+ and alt is the alternate setting logical index (not the bAlternateSetting value).
+ Not every interface has more than one alternate setting. In this case, the alt
+ parameter should be zero. config is the configuration logical index (not the
+ bConfigurationValue field).
+ """
+ _not_implemented(self.get_interface_descriptor)
+
+ def get_endpoint_descriptor(self, dev, ep, intf, alt, config):
+ r"""Return an endpoint descriptor of the given device.
+
+ The object returned is required to have all the Endpoint Descriptor
+ fields acessible as member variables. They must be convertible (but
+ not required to be equal) to the int type.
+
+ The ep parameter is the endpoint logical index (not the bEndpointAddress
+ field) of the endpoint descriptor desired. intf, alt and config are the same
+ values already described in the get_interface_descriptor() method.
+ """
+ _not_implemented(self.get_endpoint_descriptor)
+
+ def open_device(self, dev):
+ r"""Open the device for data exchange.
+
+ This method opens the device identified by the dev parameter for communication.
+ This method must be called before calling any communication related method, such
+ as transfer methods.
+
+ It returns a handle identifying the communication instance. This handle must be
+ passed to the communication methods.
+ """
+ _not_implemented(self.open_device)
+
+ def close_device(self, dev_handle):
+ r"""Close the device handle.
+
+ This method closes the device communication channel and releases any
+ system resources related to it.
+ """
+ _not_implemented(self.close_device)
+
+ def set_configuration(self, dev_handle, config_value):
+ r"""Set the active device configuration.
+
+ This method should be called to set the active configuration
+ of the device. The dev_handle parameter is the value returned
+ by the open_device() method and the config_value parameter is the
+ bConfigurationValue field of the related configuration descriptor.
+ """
+ _not_implemented(self.set_configuration)
+
+ def get_configuration(self, dev_handle):
+ r"""Get the current active device configuration.
+
+ This method returns the bConfigurationValue of the currently
+ active configuration. Depending on the backend and the OS,
+ either a cached value may be returned or a control request may
+ be issued. The dev_handle parameter is the value returned by
+ the open_device method.
+ """
+ _not_implemented(self.get_configuration)
+
+ def set_interface_altsetting(self, dev_handle, intf, altsetting):
+ r"""Set the interface alternate setting.
+
+ This method should only be called when the interface has more than
+ one alternate setting. The dev_handle is the value returned by the
+ open_device() method. intf and altsetting are respectivelly the
+ bInterfaceNumber and bAlternateSetting fields of the related interface.
+ """
+ _not_implemented(self.set_interface_altsetting)
+
+ def claim_interface(self, dev_handle, intf):
+ r"""Claim the given interface.
+
+ Interface claiming is not related to USB spec itself, but it is
+ generally an necessary call of the USB libraries. It requests exclusive
+ access to the interface on the system. This method must be called
+ before using one of the transfer methods.
+
+ dev_handle is the value returned by the open_device() method and
+ intf is the bInterfaceNumber field of the desired interface.
+ """
+ _not_implemented(self.claim_interface)
+
+ def release_interface(self, dev_handle, intf):
+ r"""Release the claimed interface.
+
+ dev_handle and intf are the same parameters of the claim_interface
+ method.
+ """
+ _not_implemented(self.release_interface)
+
+ def bulk_write(self, dev_handle, ep, intf, data, timeout):
+ r"""Perform a bulk write.
+
+ dev_handle is the value returned by the open_device() method.
+ The ep parameter is the bEndpointAddress field whose endpoint
+ the data will be sent to. intf is the bInterfaceNumber field
+ of the interface containing the endpoint. The data parameter
+ is the data to be sent. It must be an instance of the array.array
+ class. The timeout parameter specifies a time limit to the operation
+ in miliseconds.
+
+ The method returns the number of bytes written.
+ """
+ _not_implemented(self.bulk_write)
+
+ def bulk_read(self, dev_handle, ep, intf, size, timeout):
+ r"""Perform a bulk read.
+
+ dev_handle is the value returned by the open_device() method.
+ The ep parameter is the bEndpointAddress field whose endpoint
+ the data will be received from. intf is the bInterfaceNumber field
+ of the interface containing the endpoint. The size parameter
+ is the number of bytes to be read. The timeout parameter specifies
+ a time limit to the operation in miliseconds.
+
+ The method returns an array.array object containing the data read.
+ """
+ _not_implemented(self.bulk_read)
+
+ def intr_write(self, dev_handle, ep, intf, data, timeout):
+ r"""Perform an interrupt write.
+
+ dev_handle is the value returned by the open_device() method.
+ The ep parameter is the bEndpointAddress field whose endpoint
+ the data will be sent to. intf is the bInterfaceNumber field
+ of the interface containing the endpoint. The data parameter
+ is the data to be sent. It must be an instance of the array.array
+ class. The timeout parameter specifies a time limit to the operation
+ in miliseconds.
+
+ The method returns the number of bytes written.
+ """
+ _not_implemented(self.intr_write)
+
+ def intr_read(self, dev_handle, ep, intf, size, timeout):
+ r"""Perform an interrut read.
+
+ dev_handle is the value returned by the open_device() method.
+ The ep parameter is the bEndpointAddress field whose endpoint
+ the data will be received from. intf is the bInterfaceNumber field
+ of the interface containing the endpoint. The size parameter
+ is the number of bytes to be read. The timeout parameter specifies
+ a time limit to the operation in miliseconds.
+
+ The method returns an array.array object containing the data read.
+ """
+ _not_implemented(self.intr_read)
+
+ def iso_write(self, dev_handle, ep, intf, data, timeout):
+ r"""Perform an isochronous write.
+
+ dev_handle is the value returned by the open_device() method.
+ The ep parameter is the bEndpointAddress field whose endpoint
+ the data will be sent to. intf is the bInterfaceNumber field
+ of the interface containing the endpoint. The data parameter
+ is the data to be sent.It must be an instance of the array.array
+ class. The timeout parameter specifies a time limit to the operation
+ in miliseconds.
+
+ The method returns the number of bytes written.
+ """
+ _not_implemented(self.iso_write)
+
+ def iso_read(self, dev_handle, ep, intf, size, timeout):
+ r"""Perform an isochronous read.
+
+ dev_handle is the value returned by the open_device() method.
+ The ep parameter is the bEndpointAddress field whose endpoint
+ the data will be received from. intf is the bInterfaceNumber field
+ of the interface containing the endpoint. The size parameter
+ is the number of bytes to be read. The timeout parameter specifies
+ a time limit to the operation in miliseconds.
+
+ The method returns an array.array object containing the data read.
+ """
+ _not_implemented(self.iso_read)
+
+ def ctrl_transfer(self,
+ dev_handle,
+ bmRequestType,
+ bRequest,
+ wValue,
+ wIndex,
+ data_or_wLength,
+ timeout):
+ r"""Perform a control transfer on the endpoint 0.
+
+ The direction of the transfer is inferred from the bmRequestType
+ field of the setup packet.
+
+ dev_handle is the value returned by the open_device() method.
+ bmRequestType, bRequest, wValue and wIndex are the same fields
+ of the setup packet. data_or_wLength is either the payload to be sent
+ to the device, if any, as an array.array object (None there is no
+ payload) for OUT requests in the data stage or the wLength field
+ specifying the number of bytes to read for IN requests in the data
+ stage. The timeout parameter specifies a time limit to the operation
+ in miliseconds.
+
+ Return the number of bytes written (for OUT transfers) or the data
+ read (for IN transfers), as an array.array object.
+ """
+ _not_implemented(self.ctrl_transfer)
+
+ def reset_device(self, dev_handle):
+ r"""Reset the device."""
+ _not_implemented(self.reset_device)
+
+ def is_kernel_driver_active(self, dev_handle, intf):
+ r"""Determine if a kernel driver is active on an interface.
+
+ If a kernel driver is active, you cannot claim the interface,
+ and the backend will be unable to perform I/O.
+ """
+ _not_implemented(self.is_kernel_driver_active)
+
+ def detach_kernel_driver(self, dev_handle, intf):
+ r"""Detach a kernel driver from an interface.
+
+ If successful, you will then be able to claim the interface
+ and perform I/O.
+ """
+ _not_implemented(self.detach_kernel_driver)
+
+ def attach_kernel_driver(self, dev_handle, intf):
+ r"""Re-attach an interface's kernel driver, which was previously
+ detached using detach_kernel_driver()."""
+ _not_implemented(self.attach_kernel_driver)
diff --git a/nxt_plugin/usb/backend/libusb01.py b/nxt_plugin/usb/backend/libusb01.py
new file mode 100644
index 0000000..cf344c0
--- /dev/null
+++ b/nxt_plugin/usb/backend/libusb01.py
@@ -0,0 +1,582 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+from ctypes import *
+import ctypes.util
+import os
+import usb.backend
+import usb.util
+import sys
+from usb.core import USBError
+from usb._debug import methodtrace
+import usb._interop as _interop
+import logging
+
+__author__ = 'Wander Lairson Costa'
+
+__all__ = ['get_backend']
+
+_logger = logging.getLogger('usb.backend.libusb01')
+
+# usb.h
+
+_PC_PATH_MAX = 4
+
+if sys.platform != 'win32' and sys.platform != 'cygwin':
+ _PATH_MAX = os.pathconf('.', _PC_PATH_MAX)
+else:
+ _PATH_MAX = 511
+
+# libusb-win32 makes all structures packed, while
+# default libusb only does for some structures
+# _PackPolicy defines the structure packing according
+# to the platform.
+class _PackPolicy(object):
+ pass
+
+if sys.platform == 'win32' or sys.platform == 'cygwin':
+ _PackPolicy._pack_ = 1
+
+# Data structures
+
+class _usb_descriptor_header(Structure):
+ _pack_ = 1
+ _fields_ = [('blength', c_uint8),
+ ('bDescriptorType', c_uint8)]
+
+class _usb_string_descriptor(Structure):
+ _pack_ = 1
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('wData', c_uint16)]
+
+class _usb_endpoint_descriptor(Structure, _PackPolicy):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bEndpointAddress', c_uint8),
+ ('bmAttributes', c_uint8),
+ ('wMaxPacketSize', c_uint16),
+ ('bInterval', c_uint8),
+ ('bRefresh', c_uint8),
+ ('bSynchAddress', c_uint8),
+ ('extra', POINTER(c_uint8)),
+ ('extralen', c_int)]
+
+class _usb_interface_descriptor(Structure, _PackPolicy):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bInterfaceNumber', c_uint8),
+ ('bAlternateSetting', c_uint8),
+ ('bNumEndpoints', c_uint8),
+ ('bInterfaceClass', c_uint8),
+ ('bInterfaceSubClass', c_uint8),
+ ('bInterfaceProtocol', c_uint8),
+ ('iInterface', c_uint8),
+ ('endpoint', POINTER(_usb_endpoint_descriptor)),
+ ('extra', POINTER(c_uint8)),
+ ('extralen', c_int)]
+
+class _usb_interface(Structure, _PackPolicy):
+ _fields_ = [('altsetting', POINTER(_usb_interface_descriptor)),
+ ('num_altsetting', c_int)]
+
+class _usb_config_descriptor(Structure, _PackPolicy):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('wTotalLength', c_uint16),
+ ('bNumInterfaces', c_uint8),
+ ('bConfigurationValue', c_uint8),
+ ('iConfiguration', c_uint8),
+ ('bmAttributes', c_uint8),
+ ('bMaxPower', c_uint8),
+ ('interface', POINTER(_usb_interface)),
+ ('extra', POINTER(c_uint8)),
+ ('extralen', c_int)]
+
+class _usb_device_descriptor(Structure, _PackPolicy):
+ _pack_ = 1
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bcdUSB', c_uint16),
+ ('bDeviceClass', c_uint8),
+ ('bDeviceSubClass', c_uint8),
+ ('bDeviceProtocol', c_uint8),
+ ('bMaxPacketSize0', c_uint8),
+ ('idVendor', c_uint16),
+ ('idProduct', c_uint16),
+ ('bcdDevice', c_uint16),
+ ('iManufacturer', c_uint8),
+ ('iProduct', c_uint8),
+ ('iSerialNumber', c_uint8),
+ ('bNumConfigurations', c_uint8)]
+
+class _usb_device(Structure, _PackPolicy):
+ pass
+
+class _usb_bus(Structure, _PackPolicy):
+ pass
+
+_usb_device._fields_ = [('next', POINTER(_usb_device)),
+ ('prev', POINTER(_usb_device)),
+ ('filename', c_int8 * (_PATH_MAX + 1)),
+ ('bus', POINTER(_usb_bus)),
+ ('descriptor', _usb_device_descriptor),
+ ('config', POINTER(_usb_config_descriptor)),
+ ('dev', c_void_p),
+ ('devnum', c_uint8),
+ ('num_children', c_ubyte),
+ ('children', POINTER(POINTER(_usb_device)))]
+
+_usb_bus._fields_ = [('next', POINTER(_usb_bus)),
+ ('prev', POINTER(_usb_bus)),
+ ('dirname', c_char * (_PATH_MAX + 1)),
+ ('devices', POINTER(_usb_device)),
+ ('location', c_uint32),
+ ('root_dev', POINTER(_usb_device))]
+
+_usb_dev_handle = c_void_p
+
+class _DeviceDescriptor:
+ def __init__(self, dev):
+ desc = dev.descriptor
+ self.bLength = desc.bLength
+ self.bDescriptorType = desc.bDescriptorType
+ self.bcdUSB = desc.bcdUSB
+ self.bDeviceClass = desc.bDeviceClass
+ self.bDeviceSubClass = desc.bDeviceSubClass
+ self.bDeviceProtocol = desc.bDeviceProtocol
+ self.bMaxPacketSize0 = desc.bMaxPacketSize0
+ self.idVendor = desc.idVendor
+ self.idProduct = desc.idProduct
+ self.bcdDevice = desc.bcdDevice
+ self.iManufacturer = desc.iManufacturer
+ self.iProduct = desc.iProduct
+ self.iSerialNumber = desc.iSerialNumber
+ self.bNumConfigurations = desc.bNumConfigurations
+ self.address = dev.devnum
+ self.bus = dev.bus[0].location
+
+_lib = None
+
+def _load_library():
+ if sys.platform != 'cygwin':
+ candidates = ('usb-0.1', 'usb', 'libusb0')
+ for candidate in candidates:
+ libname = ctypes.util.find_library(candidate)
+ if libname is not None: break
+ else:
+ # corner cases
+ # cygwin predefines library names with 'cyg' instead of 'lib'
+ try:
+ return CDLL('cygusb0.dll')
+ except:
+ _logger.error('Libusb 0 could not be loaded in cygwin', exc_info=True)
+
+ raise OSError('USB library could not be found')
+ return CDLL(libname)
+
+def _setup_prototypes(lib):
+ # usb_dev_handle *usb_open(struct usb_device *dev);
+ lib.usb_open.argtypes = [POINTER(_usb_device)]
+ lib.usb_open.restype = _usb_dev_handle
+
+ # int usb_close(usb_dev_handle *dev);
+ lib.usb_close.argtypes = [_usb_dev_handle]
+
+ # int usb_get_string(usb_dev_handle *dev,
+ # int index,
+ # int langid,
+ # char *buf,
+ # size_t buflen);
+ lib.usb_get_string.argtypes = [
+ _usb_dev_handle,
+ c_int,
+ c_int,
+ c_char_p,
+ c_size_t
+ ]
+
+ # int usb_get_string_simple(usb_dev_handle *dev,
+ # int index,
+ # char *buf,
+ # size_t buflen);
+ lib.usb_get_string_simple.argtypes = [
+ _usb_dev_handle,
+ c_int,
+ c_char_p,
+ c_size_t
+ ]
+
+ # int usb_get_descriptor_by_endpoint(usb_dev_handle *udev,
+ # int ep,
+ # unsigned char type,
+ # unsigned char index,
+ # void *buf,
+ # int size);
+ lib.usb_get_descriptor_by_endpoint.argtypes = [
+ _usb_dev_handle,
+ c_int,
+ c_ubyte,
+ c_ubyte,
+ c_void_p,
+ c_int
+ ]
+
+ # int usb_get_descriptor(usb_dev_handle *udev,
+ # unsigned char type,
+ # unsigned char index,
+ # void *buf,
+ # int size);
+ lib.usb_get_descriptor.argtypes = [
+ _usb_dev_handle,
+ c_ubyte,
+ c_ubyte,
+ c_void_p,
+ c_int
+ ]
+
+ # int usb_bulk_write(usb_dev_handle *dev,
+ # int ep,
+ # const char *bytes,
+ # int size,
+ # int timeout);
+ lib.usb_bulk_write.argtypes = [
+ _usb_dev_handle,
+ c_int,
+ c_char_p,
+ c_int,
+ c_int
+ ]
+
+ # int usb_bulk_read(usb_dev_handle *dev,
+ # int ep,
+ # char *bytes,
+ # int size,
+ # int timeout);
+ lib.usb_bulk_read.argtypes = [
+ _usb_dev_handle,
+ c_int,
+ c_char_p,
+ c_int,
+ c_int
+ ]
+
+ # int usb_interrupt_write(usb_dev_handle *dev,
+ # int ep,
+ # const char *bytes,
+ # int size,
+ # int timeout);
+ lib.usb_interrupt_write.argtypes = [
+ _usb_dev_handle,
+ c_int,
+ c_char_p,
+ c_int,
+ c_int
+ ]
+
+ # int usb_interrupt_read(usb_dev_handle *dev,
+ # int ep,
+ # char *bytes,
+ # int size,
+ # int timeout);
+ lib.usb_interrupt_read.argtypes = [
+ _usb_dev_handle,
+ c_int,
+ c_char_p,
+ c_int,
+ c_int
+ ]
+
+ # int usb_control_msg(usb_dev_handle *dev,
+ # int requesttype,
+ # int request,
+ # int value,
+ # int index,
+ # char *bytes,
+ # int size,
+ # int timeout);
+ lib.usb_control_msg.argtypes = [
+ _usb_dev_handle,
+ c_int,
+ c_int,
+ c_int,
+ c_int,
+ c_char_p,
+ c_int,
+ c_int
+ ]
+
+ # int usb_set_configuration(usb_dev_handle *dev, int configuration);
+ lib.usb_set_configuration.argtypes = [_usb_dev_handle, c_int]
+
+ # int usb_claim_interface(usb_dev_handle *dev, int interface);
+ lib.usb_claim_interface.argtypes = [_usb_dev_handle, c_int]
+
+ # int usb_release_interface(usb_dev_handle *dev, int interface);
+ lib.usb_release_interface.argtypes = [_usb_dev_handle, c_int]
+
+ # int usb_set_altinterface(usb_dev_handle *dev, int alternate);
+ lib.usb_set_altinterface.argtypes = [_usb_dev_handle, c_int]
+
+ # int usb_resetep(usb_dev_handle *dev, unsigned int ep);
+ lib.usb_resetep.argtypes = [_usb_dev_handle, c_int]
+
+ # int usb_clear_halt(usb_dev_handle *dev, unsigned int ep);
+ lib.usb_clear_halt.argtypes = [_usb_dev_handle, c_int]
+
+ # int usb_reset(usb_dev_handle *dev);
+ lib.usb_reset.argtypes = [_usb_dev_handle]
+
+ # char *usb_strerror(void);
+ lib.usb_strerror.argtypes = []
+ lib.usb_strerror.restype = c_char_p
+
+ # void usb_set_debug(int level);
+ lib.usb_set_debug.argtypes = [c_int]
+
+ # struct usb_device *usb_device(usb_dev_handle *dev);
+ lib.usb_device.argtypes = [_usb_dev_handle]
+ lib.usb_device.restype = POINTER(_usb_device)
+
+ # struct usb_bus *usb_get_busses(void);
+ lib.usb_get_busses.restype = POINTER(_usb_bus)
+
+def _check(retval):
+ if retval is None:
+ errmsg = _lib.usb_strerror()
+ else:
+ ret = int(retval)
+ if ret < 0:
+ errmsg = _lib.usb_strerror()
+ # No error means that we need to get the error
+ # message from the return code
+ # Thanks to Nicholas Wheeler to point out the problem...
+ # Also see issue #2860940
+ if errmsg.lower() == 'no error':
+ errmsg = os.strerror(-ret)
+ else:
+ return ret
+ raise USBError(errmsg, ret)
+
+# implementation of libusb 0.1.x backend
+class _LibUSB(usb.backend.IBackend):
+ @methodtrace(_logger)
+ def enumerate_devices(self):
+ _check(_lib.usb_find_busses())
+ _check(_lib.usb_find_devices())
+ bus = _lib.usb_get_busses()
+ while bool(bus):
+ dev = bus[0].devices
+ while bool(dev):
+ yield dev[0]
+ dev = dev[0].next
+ bus = bus[0].next
+
+ @methodtrace(_logger)
+ def get_device_descriptor(self, dev):
+ return _DeviceDescriptor(dev)
+
+ @methodtrace(_logger)
+ def get_configuration_descriptor(self, dev, config):
+ if config >= dev.descriptor.bNumConfigurations:
+ raise IndexError('Invalid configuration index ' + str(config))
+ return dev.config[config]
+
+ @methodtrace(_logger)
+ def get_interface_descriptor(self, dev, intf, alt, config):
+ cfgdesc = self.get_configuration_descriptor(dev, config)
+ if intf >= cfgdesc.bNumInterfaces:
+ raise IndexError('Invalid interface index ' + str(interface))
+ interface = cfgdesc.interface[intf]
+ if alt >= interface.num_altsetting:
+ raise IndexError('Invalid alternate setting index ' + str(alt))
+ return interface.altsetting[alt]
+
+ @methodtrace(_logger)
+ def get_endpoint_descriptor(self, dev, ep, intf, alt, config):
+ interface = self.get_interface_descriptor(dev, intf, alt, config)
+ if ep >= interface.bNumEndpoints:
+ raise IndexError('Invalid endpoint index ' + str(ep))
+ return interface.endpoint[ep]
+
+ @methodtrace(_logger)
+ def open_device(self, dev):
+ return _check(_lib.usb_open(dev))
+
+ @methodtrace(_logger)
+ def close_device(self, dev_handle):
+ _check(_lib.usb_close(dev_handle))
+
+ @methodtrace(_logger)
+ def set_configuration(self, dev_handle, config_value):
+ _check(_lib.usb_set_configuration(dev_handle, config_value))
+
+ @methodtrace(_logger)
+ def set_interface_altsetting(self, dev_handle, intf, altsetting):
+ _check(_lib.usb_set_altinterface(dev_handle, altsetting))
+
+ @methodtrace(_logger)
+ def get_configuration(self, dev_handle):
+ bmRequestType = usb.util.build_request_type(
+ usb.util.CTRL_IN,
+ usb.util.CTRL_TYPE_STANDARD,
+ usb.util.CTRL_RECIPIENT_DEVICE
+ )
+ return self.ctrl_transfer(dev_handle,
+ bmRequestType,
+ 0x08,
+ 0,
+ 0,
+ 1,
+ 100
+ )[0]
+
+
+ @methodtrace(_logger)
+ def claim_interface(self, dev_handle, intf):
+ _check(_lib.usb_claim_interface(dev_handle, intf))
+
+ @methodtrace(_logger)
+ def release_interface(self, dev_handle, intf):
+ _check(_lib.usb_release_interface(dev_handle, intf))
+
+ @methodtrace(_logger)
+ def bulk_write(self, dev_handle, ep, intf, data, timeout):
+ return self.__write(_lib.usb_bulk_write,
+ dev_handle,
+ ep,
+ intf,
+ data, timeout)
+
+ @methodtrace(_logger)
+ def bulk_read(self, dev_handle, ep, intf, size, timeout):
+ return self.__read(_lib.usb_bulk_read,
+ dev_handle,
+ ep,
+ intf,
+ size,
+ timeout)
+
+ @methodtrace(_logger)
+ def intr_write(self, dev_handle, ep, intf, data, timeout):
+ return self.__write(_lib.usb_interrupt_write,
+ dev_handle,
+ ep,
+ intf,
+ data,
+ timeout)
+
+ @methodtrace(_logger)
+ def intr_read(self, dev_handle, ep, intf, size, timeout):
+ return self.__read(_lib.usb_interrupt_read,
+ dev_handle,
+ ep,
+ intf,
+ size,
+ timeout)
+
+ @methodtrace(_logger)
+ def ctrl_transfer(self,
+ dev_handle,
+ bmRequestType,
+ bRequest,
+ wValue,
+ wIndex,
+ data_or_wLength,
+ timeout):
+ if usb.util.ctrl_direction(bmRequestType) == usb.util.CTRL_OUT:
+ address, length = data_or_wLength.buffer_info()
+ length *= data_or_wLength.itemsize
+ return _check(_lib.usb_control_msg(
+ dev_handle,
+ bmRequestType,
+ bRequest,
+ wValue,
+ wIndex,
+ cast(address, c_char_p),
+ length,
+ timeout
+ ))
+ else:
+ data = _interop.as_array((0,) * data_or_wLength)
+ read = int(_check(_lib.usb_control_msg(
+ dev_handle,
+ bmRequestType,
+ bRequest,
+ wValue,
+ wIndex,
+ cast(data.buffer_info()[0],
+ c_char_p),
+ data_or_wLength,
+ timeout
+ )))
+ return data[:read]
+
+ @methodtrace(_logger)
+ def reset_device(self, dev_handle):
+ _check(_lib.usb_reset(dev_handle))
+
+ @methodtrace(_logger)
+ def detach_kernel_driver(self, dev_handle, intf):
+ _check(_lib.usb_detach_kernel_driver_np(dev_handle, intf))
+
+ def __write(self, fn, dev_handle, ep, intf, data, timeout):
+ address, length = data.buffer_info()
+ length *= data.itemsize
+ return int(_check(fn(
+ dev_handle,
+ ep,
+ cast(address, c_char_p),
+ length,
+ timeout
+ )))
+
+ def __read(self, fn, dev_handle, ep, intf, size, timeout):
+ data = _interop.as_array((0,) * size)
+ address, length = data.buffer_info()
+ length *= data.itemsize
+ ret = int(_check(fn(
+ dev_handle,
+ ep,
+ cast(address, c_char_p),
+ length,
+ timeout
+ )))
+ return data[:ret]
+
+def get_backend():
+ global _lib
+ try:
+ if _lib is None:
+ _lib = _load_library()
+ _setup_prototypes(_lib)
+ _lib.usb_init()
+ return _LibUSB()
+ except Exception:
+ _logger.error('Error loading libusb 0.1 backend', exc_info=True)
+ return None
diff --git a/nxt_plugin/usb/backend/libusb10.py b/nxt_plugin/usb/backend/libusb10.py
new file mode 100644
index 0000000..e7291c5
--- /dev/null
+++ b/nxt_plugin/usb/backend/libusb10.py
@@ -0,0 +1,661 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+from ctypes import *
+import ctypes.util
+import usb.util
+import sys
+import logging
+from usb._debug import methodtrace
+import usb._interop as _interop
+import errno
+
+__author__ = 'Wander Lairson Costa'
+
+__all__ = [
+ 'get_backend',
+ 'LIBUSB_SUCESS',
+ 'LIBUSB_ERROR_IO',
+ 'LIBUSB_ERROR_INVALID_PARAM',
+ 'LIBUSB_ERROR_ACCESS',
+ 'LIBUSB_ERROR_NO_DEVICE',
+ 'LIBUSB_ERROR_NOT_FOUND',
+ 'LIBUSB_ERROR_BUSY',
+ 'LIBUSB_ERROR_TIMEOUT',
+ 'LIBUSB_ERROR_OVERFLOW',
+ 'LIBUSB_ERROR_PIPE',
+ 'LIBUSB_ERROR_INTERRUPTED',
+ 'LIBUSB_ERROR_NO_MEM',
+ 'LIBUSB_ERROR_NOT_SUPPORTED',
+ 'LIBUSB_ERROR_OTHER'
+ ]
+
+_logger = logging.getLogger('usb.backend.libusb10')
+
+# libusb.h
+
+# return codes
+
+LIBUSB_SUCCESS = 0
+LIBUSB_ERROR_IO = -1
+LIBUSB_ERROR_INVALID_PARAM = -2
+LIBUSB_ERROR_ACCESS = -3
+LIBUSB_ERROR_NO_DEVICE = -4
+LIBUSB_ERROR_NOT_FOUND = -5
+LIBUSB_ERROR_BUSY = -6
+LIBUSB_ERROR_TIMEOUT = -7
+LIBUSB_ERROR_OVERFLOW = -8
+LIBUSB_ERROR_PIPE = -9
+LIBUSB_ERROR_INTERRUPTED = -10
+LIBUSB_ERROR_NO_MEM = -11
+LIBUSB_ERROR_NOT_SUPPORTED = -12
+LIBUSB_ERROR_OTHER = -99
+
+# map return codes to strings
+_str_error = {
+ LIBUSB_SUCCESS:'Success (no error)',
+ LIBUSB_ERROR_IO:'Input/output error',
+ LIBUSB_ERROR_INVALID_PARAM:'Invalid parameter',
+ LIBUSB_ERROR_ACCESS:'Access denied (insufficient permissions)',
+ LIBUSB_ERROR_NO_DEVICE:'No such device (it may have been disconnected)',
+ LIBUSB_ERROR_NOT_FOUND:'Entity not found',
+ LIBUSB_ERROR_BUSY:'Resource busy',
+ LIBUSB_ERROR_TIMEOUT:'Operation timed out',
+ LIBUSB_ERROR_OVERFLOW:'Overflow',
+ LIBUSB_ERROR_PIPE:'Pipe error',
+ LIBUSB_ERROR_INTERRUPTED:'System call interrupted (perhaps due to signal)',
+ LIBUSB_ERROR_NO_MEM:'Insufficient memory',
+ LIBUSB_ERROR_NOT_SUPPORTED:'Operation not supported or unimplemented on this platform',
+ LIBUSB_ERROR_OTHER:'Unknown error'
+}
+
+# map return code to errno values
+_libusb_errno = {
+ LIBUSB_SUCCESS:None,
+ LIBUSB_ERROR_IO:errno.__dict__.get('EIO', None),
+ LIBUSB_ERROR_INVALID_PARAM:errno.__dict__.get('EINVAL', None),
+ LIBUSB_ERROR_ACCESS:errno.__dict__.get('EACCES', None),
+ LIBUSB_ERROR_NO_DEVICE:errno.__dict__.get('ENODEV', None),
+ LIBUSB_ERROR_NOT_FOUND:errno.__dict__.get('ENOENT', None),
+ LIBUSB_ERROR_BUSY:errno.__dict__.get('EBUSY', None),
+ LIBUSB_ERROR_TIMEOUT:errno.__dict__.get('ETIMEDOUT', None),
+ LIBUSB_ERROR_OVERFLOW:errno.__dict__.get('EOVERFLOW', None),
+ LIBUSB_ERROR_PIPE:errno.__dict__.get('EPIPE', None),
+ LIBUSB_ERROR_INTERRUPTED:errno.__dict__.get('EINTR', None),
+ LIBUSB_ERROR_NO_MEM:errno.__dict__.get('ENOMEM', None),
+ LIBUSB_ERROR_NOT_SUPPORTED:errno.__dict__.get('ENOSYS', None),
+ LIBUSB_ERROR_OTHER:None
+}
+
+# Data structures
+
+class _libusb_endpoint_descriptor(Structure):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bEndpointAddress', c_uint8),
+ ('bmAttributes', c_uint8),
+ ('wMaxPacketSize', c_uint16),
+ ('bInterval', c_uint8),
+ ('bRefresh', c_uint8),
+ ('bSynchAddress', c_uint8),
+ ('extra', POINTER(c_ubyte)),
+ ('extra_length', c_int)]
+
+class _libusb_interface_descriptor(Structure):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bInterfaceNumber', c_uint8),
+ ('bAlternateSetting', c_uint8),
+ ('bNumEndpoints', c_uint8),
+ ('bInterfaceClass', c_uint8),
+ ('bInterfaceSubClass', c_uint8),
+ ('bInterfaceProtocol', c_uint8),
+ ('iInterface', c_uint8),
+ ('endpoint', POINTER(_libusb_endpoint_descriptor)),
+ ('extra', POINTER(c_ubyte)),
+ ('extra_length', c_int)]
+
+class _libusb_interface(Structure):
+ _fields_ = [('altsetting', POINTER(_libusb_interface_descriptor)),
+ ('num_altsetting', c_int)]
+
+class _libusb_config_descriptor(Structure):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('wTotalLength', c_uint16),
+ ('bNumInterfaces', c_uint8),
+ ('bConfigurationValue', c_uint8),
+ ('iConfiguration', c_uint8),
+ ('bmAttributes', c_uint8),
+ ('bMaxPower', c_uint8),
+ ('interface', POINTER(_libusb_interface)),
+ ('extra', POINTER(c_ubyte)),
+ ('extra_length', c_int)]
+
+class _libusb_device_descriptor(Structure):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bcdUSB', c_uint16),
+ ('bDeviceClass', c_uint8),
+ ('bDeviceSubClass', c_uint8),
+ ('bDeviceProtocol', c_uint8),
+ ('bMaxPacketSize0', c_uint8),
+ ('idVendor', c_uint16),
+ ('idProduct', c_uint16),
+ ('bcdDevice', c_uint16),
+ ('iManufacturer', c_uint8),
+ ('iProduct', c_uint8),
+ ('iSerialNumber', c_uint8),
+ ('bNumConfigurations', c_uint8)]
+
+_lib = None
+_init = None
+
+_libusb_device_handle = c_void_p
+
+def _load_library():
+ if sys.platform != 'cygwin':
+ candidates = ('usb-1.0', 'libusb-1.0', 'usb')
+ for candidate in candidates:
+ libname = ctypes.util.find_library(candidate)
+ if libname is not None: break
+ else:
+ # corner cases
+ # cygwin predefines library names with 'cyg' instead of 'lib'
+ try:
+ return CDLL('cygusb-1.0.dll')
+ except Exception:
+ _logger.error('Libusb 1.0 could not be loaded in cygwin', exc_info=True)
+
+ raise OSError('USB library could not be found')
+ # Windows backend uses stdcall calling convention
+ if sys.platform == 'win32':
+ l = WinDLL(libname)
+ else:
+ l = CDLL(libname)
+ # On FreeBSD 8/9, libusb 1.0 and libusb 0.1 are in the same shared
+ # object libusb.so, so if we found libusb library name, we must assure
+ # it is 1.0 version. We just try to get some symbol from 1.0 version
+ if not hasattr(l, 'libusb_init'):
+ raise OSError('USB library could not be found')
+ return l
+
+def _setup_prototypes(lib):
+ # void libusb_set_debug (libusb_context *ctx, int level)
+ lib.libusb_set_debug.argtypes = [c_void_p, c_int]
+
+ # int libusb_init (libusb_context **context)
+ lib.libusb_init.argtypes = [POINTER(c_void_p)]
+
+ # void libusb_exit (struct libusb_context *ctx)
+ lib.libusb_exit.argtypes = [c_void_p]
+
+ # ssize_t libusb_get_device_list (libusb_context *ctx,
+ # libusb_device ***list)
+ lib.libusb_get_device_list.argtypes = [
+ c_void_p,
+ POINTER(POINTER(c_void_p))
+ ]
+
+ # void libusb_free_device_list (libusb_device **list,
+ # int unref_devices)
+ lib.libusb_free_device_list.argtypes = [
+ POINTER(c_void_p),
+ c_int
+ ]
+
+ # libusb_device *libusb_ref_device (libusb_device *dev)
+ lib.libusb_ref_device.argtypes = [c_void_p]
+ lib.libusb_ref_device.restype = c_void_p
+
+ # void libusb_unref_device(libusb_device *dev)
+ lib.libusb_unref_device.argtypes = [c_void_p]
+
+ # int libusb_open(libusb_device *dev, libusb_device_handle **handle)
+ lib.libusb_open.argtypes = [c_void_p, POINTER(_libusb_device_handle)]
+
+ # void libusb_close(libusb_device_handle *dev_handle)
+ lib.libusb_close.argtypes = [_libusb_device_handle]
+
+ # int libusb_set_configuration(libusb_device_handle *dev,
+ # int configuration)
+ lib.libusb_set_configuration.argtypes = [_libusb_device_handle, c_int]
+
+ # int libusb_get_configuration(libusb_device_handle *dev,
+ # int *config)
+ lib.libusb_get_configuration.argtypes = [_libusb_device_handle, POINTER(c_int)]
+
+ # int libusb_claim_interface(libusb_device_handle *dev,
+ # int interface_number)
+ lib.libusb_claim_interface.argtypes = [_libusb_device_handle, c_int]
+
+ # int libusb_release_interface(libusb_device_handle *dev,
+ # int interface_number)
+ lib.libusb_release_interface.argtypes = [_libusb_device_handle, c_int]
+
+ # int libusb_set_interface_alt_setting(libusb_device_handle *dev,
+ # int interface_number,
+ # int alternate_setting)
+ lib.libusb_set_interface_alt_setting.argtypes = [
+ _libusb_device_handle,
+ c_int,
+ c_int
+ ]
+
+ # int libusb_reset_device (libusb_device_handle *dev)
+ lib.libusb_reset_device.argtypes = [_libusb_device_handle]
+
+ # int libusb_kernel_driver_active(libusb_device_handle *dev,
+ # int interface)
+ lib.libusb_kernel_driver_active.argtypes = [
+ _libusb_device_handle,
+ c_int
+ ]
+
+ # int libusb_detach_kernel_driver(libusb_device_handle *dev,
+ # int interface)
+ lib.libusb_detach_kernel_driver.argtypes = [
+ _libusb_device_handle,
+ c_int
+ ]
+
+ # int libusb_attach_kernel_driver(libusb_device_handle *dev,
+ # int interface)
+ lib.libusb_attach_kernel_driver.argtypes = [
+ _libusb_device_handle,
+ c_int
+ ]
+
+ # int libusb_get_device_descriptor(
+ # libusb_device *dev,
+ # struct libusb_device_descriptor *desc
+ # )
+ lib.libusb_get_device_descriptor.argtypes = [
+ c_void_p,
+ POINTER(_libusb_device_descriptor)
+ ]
+
+ # int libusb_get_config_descriptor(
+ # libusb_device *dev,
+ # uint8_t config_index,
+ # struct libusb_config_descriptor **config
+ # )
+ lib.libusb_get_config_descriptor.argtypes = [
+ c_void_p,
+ c_uint8,
+ POINTER(POINTER(_libusb_config_descriptor))
+ ]
+
+ # void libusb_free_config_descriptor(
+ # struct libusb_config_descriptor *config
+ # )
+ lib.libusb_free_config_descriptor.argtypes = [
+ POINTER(_libusb_config_descriptor)
+ ]
+
+ # int libusb_get_string_descriptor_ascii(libusb_device_handle *dev,
+ # uint8_t desc_index,
+ # unsigned char *data,
+ # int length)
+ lib.libusb_get_string_descriptor_ascii.argtypes = [
+ _libusb_device_handle,
+ c_uint8,
+ POINTER(c_ubyte),
+ c_int
+ ]
+
+ # int libusb_control_transfer(libusb_device_handle *dev_handle,
+ # uint8_t bmRequestType,
+ # uint8_t bRequest,
+ # uint16_t wValue,
+ # uint16_t wIndex,
+ # unsigned char *data,
+ # uint16_t wLength,
+ # unsigned int timeout)
+ lib.libusb_control_transfer.argtypes = [
+ _libusb_device_handle,
+ c_uint8,
+ c_uint8,
+ c_uint16,
+ c_uint16,
+ POINTER(c_ubyte),
+ c_uint16,
+ c_uint
+ ]
+
+ #int libusb_bulk_transfer(
+ # struct libusb_device_handle *dev_handle,
+ # unsigned char endpoint,
+ # unsigned char *data,
+ # int length,
+ # int *transferred,
+ # unsigned int timeout
+ # )
+ lib.libusb_bulk_transfer.argtypes = [
+ _libusb_device_handle,
+ c_ubyte,
+ POINTER(c_ubyte),
+ c_int,
+ POINTER(c_int),
+ c_uint
+ ]
+
+ # int libusb_interrupt_transfer(
+ # libusb_device_handle *dev_handle,
+ # unsigned char endpoint,
+ # unsigned char *data,
+ # int length,
+ # int *actual_length,
+ # unsigned int timeout
+ # );
+ lib.libusb_interrupt_transfer.argtypes = [
+ _libusb_device_handle,
+ c_ubyte,
+ POINTER(c_ubyte),
+ c_int,
+ POINTER(c_int),
+ c_uint
+ ]
+
+ # uint8_t libusb_get_bus_number(libusb_device *dev)
+ lib.libusb_get_bus_number.argtypes = [c_void_p]
+ lib.libusb_get_bus_number.restype = c_uint8
+
+ # uint8_t libusb_get_device_address(libusb_device *dev)
+ lib.libusb_get_device_address.argtypes = [c_void_p]
+ lib.libusb_get_device_address.restype = c_uint8
+
+
+
+# check a libusb function call
+def _check(retval):
+ if isinstance(retval, int):
+ retval = c_int(retval)
+ if isinstance(retval, c_int):
+ if retval.value < 0:
+ from usb.core import USBError
+ ret = retval.value
+ raise USBError(_str_error[ret], ret, _libusb_errno[ret])
+ return retval
+
+# wrap a device
+class _Device(object):
+ def __init__(self, devid):
+ self.devid = _lib.libusb_ref_device(devid)
+ def __del__(self):
+ _lib.libusb_unref_device(self.devid)
+
+# wrap a descriptor and keep a reference to another object
+# Thanks to Thomas Reitmayr.
+class _WrapDescriptor(object):
+ def __init__(self, desc, obj = None):
+ self.obj = obj
+ self.desc = desc
+ def __getattr__(self, name):
+ return getattr(self.desc, name)
+
+# wrap a configuration descriptor
+class _ConfigDescriptor(object):
+ def __init__(self, desc):
+ self.desc = desc
+ def __del__(self):
+ _lib.libusb_free_config_descriptor(self.desc)
+ def __getattr__(self, name):
+ return getattr(self.desc.contents, name)
+
+# initialize and finalize the library
+class _Initializer(object):
+ def __init__(self):
+ _check(_lib.libusb_init(None))
+ def __del__(self):
+ _lib.libusb_exit(None)
+
+
+# iterator for libusb devices
+class _DevIterator(object):
+ def __init__(self):
+ self.dev_list = POINTER(c_void_p)()
+ self.num_devs = _check(_lib.libusb_get_device_list(
+ None,
+ byref(self.dev_list))
+ ).value
+ def __iter__(self):
+ for i in range(self.num_devs):
+ yield _Device(self.dev_list[i])
+ def __del__(self):
+ _lib.libusb_free_device_list(self.dev_list, 1)
+
+# implementation of libusb 1.0 backend
+class _LibUSB(usb.backend.IBackend):
+ @methodtrace(_logger)
+ def enumerate_devices(self):
+ return _DevIterator()
+
+ @methodtrace(_logger)
+ def get_device_descriptor(self, dev):
+ dev_desc = _libusb_device_descriptor()
+ _check(_lib.libusb_get_device_descriptor(dev.devid, byref(dev_desc)))
+ dev_desc.bus = _lib.libusb_get_bus_number(dev.devid)
+ dev_desc.address = _lib.libusb_get_device_address(dev.devid)
+ return dev_desc
+
+ @methodtrace(_logger)
+ def get_configuration_descriptor(self, dev, config):
+ cfg = POINTER(_libusb_config_descriptor)()
+ _check(_lib.libusb_get_config_descriptor(dev.devid,
+ config, byref(cfg)))
+ return _ConfigDescriptor(cfg)
+
+ @methodtrace(_logger)
+ def get_interface_descriptor(self, dev, intf, alt, config):
+ cfg = self.get_configuration_descriptor(dev, config)
+ if intf >= cfg.bNumInterfaces:
+ raise IndexError('Invalid interface index ' + str(intf))
+ i = cfg.interface[intf]
+ if alt >= i.num_altsetting:
+ raise IndexError('Invalid alternate setting index ' + str(alt))
+ return _WrapDescriptor(i.altsetting[alt], cfg)
+
+ @methodtrace(_logger)
+ def get_endpoint_descriptor(self, dev, ep, intf, alt, config):
+ i = self.get_interface_descriptor(dev, intf, alt, config)
+ if ep > i.bNumEndpoints:
+ raise IndexError('Invalid endpoint index ' + str(ep))
+ return _WrapDescriptor(i.endpoint[ep], i)
+
+ @methodtrace(_logger)
+ def open_device(self, dev):
+ handle = _libusb_device_handle()
+ _check(_lib.libusb_open(dev.devid, byref(handle)))
+ return handle
+
+ @methodtrace(_logger)
+ def close_device(self, dev_handle):
+ _lib.libusb_close(dev_handle)
+
+ @methodtrace(_logger)
+ def set_configuration(self, dev_handle, config_value):
+ _check(_lib.libusb_set_configuration(dev_handle, config_value))
+
+ @methodtrace(_logger)
+ def get_configuration(self, dev_handle):
+ config = c_int()
+ _check(_lib.libusb_get_configuration(dev_handle, byref(config)))
+ return config.value
+
+ @methodtrace(_logger)
+ def set_interface_altsetting(self, dev_handle, intf, altsetting):
+ _check(_lib.libusb_set_interface_alt_setting(dev_handle,
+ intf,
+ altsetting))
+
+ @methodtrace(_logger)
+ def claim_interface(self, dev_handle, intf):
+ _check(_lib.libusb_claim_interface(dev_handle, intf))
+
+ @methodtrace(_logger)
+ def release_interface(self, dev_handle, intf):
+ _check(_lib.libusb_release_interface(dev_handle, intf))
+
+ @methodtrace(_logger)
+ def bulk_write(self, dev_handle, ep, intf, data, timeout):
+ return self.__write(_lib.libusb_bulk_transfer,
+ dev_handle,
+ ep,
+ intf,
+ data,
+ timeout)
+
+ @methodtrace(_logger)
+ def bulk_read(self, dev_handle, ep, intf, size, timeout):
+ return self.__read(_lib.libusb_bulk_transfer,
+ dev_handle,
+ ep,
+ intf,
+ size,
+ timeout)
+
+ @methodtrace(_logger)
+ def intr_write(self, dev_handle, ep, intf, data, timeout):
+ return self.__write(_lib.libusb_interrupt_transfer,
+ dev_handle,
+ ep,
+ intf,
+ data,
+ timeout)
+
+ @methodtrace(_logger)
+ def intr_read(self, dev_handle, ep, intf, size, timeout):
+ return self.__read(_lib.libusb_interrupt_transfer,
+ dev_handle,
+ ep,
+ intf,
+ size,
+ timeout)
+
+# TODO: implement isochronous
+# @methodtrace(_logger)
+# def iso_write(self, dev_handle, ep, intf, data, timeout):
+# pass
+
+
+# @methodtrace(_logger)
+# def iso_read(self, dev_handle, ep, intf, size, timeout):
+# pass
+
+ @methodtrace(_logger)
+ def ctrl_transfer(self,
+ dev_handle,
+ bmRequestType,
+ bRequest,
+ wValue,
+ wIndex,
+ data_or_wLength,
+ timeout):
+ if usb.util.ctrl_direction(bmRequestType) == usb.util.CTRL_OUT:
+ buff = data_or_wLength
+ else:
+ buff = _interop.as_array((0,) * data_or_wLength)
+
+ addr, length = buff.buffer_info()
+ length *= buff.itemsize
+
+ ret = _check(_lib.libusb_control_transfer(dev_handle,
+ bmRequestType,
+ bRequest,
+ wValue,
+ wIndex,
+ cast(addr,
+ POINTER(c_ubyte)),
+ length,
+ timeout))
+
+ if usb.util.ctrl_direction(bmRequestType) == usb.util.CTRL_OUT:
+ return ret.value
+ else:
+ return buff[:ret.value]
+
+ @methodtrace(_logger)
+ def reset_device(self, dev_handle):
+ _check(_lib.libusb_reset_device(dev_handle))
+
+ @methodtrace(_logger)
+ def is_kernel_driver_active(self, dev_handle, intf):
+ return bool(_check(_lib.libusb_kernel_driver_active(dev_handle, intf)))
+
+ @methodtrace(_logger)
+ def detach_kernel_driver(self, dev_handle, intf):
+ _check(_lib.libusb_detach_kernel_driver(dev_handle, intf))
+
+ @methodtrace(_logger)
+ def attach_kernel_driver(self, dev_handle, intf):
+ _check(_lib.libusb_attach_kernel_driver(dev_handle, intf))
+
+ def __write(self, fn, dev_handle, ep, intf, data, timeout):
+ address, length = data.buffer_info()
+ length *= data.itemsize
+ transferred = c_int()
+ retval = fn(dev_handle,
+ ep,
+ cast(address, POINTER(c_ubyte)),
+ length,
+ byref(transferred),
+ timeout)
+ # do not assume LIBUSB_ERROR_TIMEOUT means no I/O.
+ if not (transferred.value and retval == LIBUSB_ERROR_TIMEOUT):
+ _check(retval)
+
+ return transferred.value
+
+ def __read(self, fn, dev_handle, ep, intf, size, timeout):
+ data = _interop.as_array((0,) * size)
+ address, length = data.buffer_info()
+ length *= data.itemsize
+ transferred = c_int()
+ retval = fn(dev_handle,
+ ep,
+ cast(address, POINTER(c_ubyte)),
+ length,
+ byref(transferred),
+ timeout)
+ # do not assume LIBUSB_ERROR_TIMEOUT means no I/O.
+ if not (transferred.value and retval == LIBUSB_ERROR_TIMEOUT):
+ _check(retval)
+ return data[:transferred.value]
+
+def get_backend():
+ global _lib, _init
+ try:
+ if _lib is None:
+ _lib = _load_library()
+ _setup_prototypes(_lib)
+ _init = _Initializer()
+ return _LibUSB()
+ except Exception:
+ _logger.error('Error loading libusb 1.0 backend', exc_info=True)
+ return None
diff --git a/nxt_plugin/usb/backend/openusb.py b/nxt_plugin/usb/backend/openusb.py
new file mode 100644
index 0000000..23e4d45
--- /dev/null
+++ b/nxt_plugin/usb/backend/openusb.py
@@ -0,0 +1,707 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+from ctypes import *
+import ctypes.util
+import usb.util
+from usb._debug import methodtrace
+import logging
+import errno
+
+__author__ = 'Wander Lairson Costa'
+
+__all__ = [
+ 'get_backend'
+ 'OPENUSB_SUCCESS'
+ 'OPENUSB_PLATFORM_FAILURE'
+ 'OPENUSB_NO_RESOURCES'
+ 'OPENUSB_NO_BANDWIDTH'
+ 'OPENUSB_NOT_SUPPORTED'
+ 'OPENUSB_HC_HARDWARE_ERROR'
+ 'OPENUSB_INVALID_PERM'
+ 'OPENUSB_BUSY'
+ 'OPENUSB_BADARG'
+ 'OPENUSB_NOACCESS'
+ 'OPENUSB_PARSE_ERROR'
+ 'OPENUSB_UNKNOWN_DEVICE'
+ 'OPENUSB_INVALID_HANDLE'
+ 'OPENUSB_SYS_FUNC_FAILURE'
+ 'OPENUSB_NULL_LIST'
+ 'OPENUSB_CB_CONTINUE'
+ 'OPENUSB_CB_TERMINATE'
+ 'OPENUSB_IO_STALL'
+ 'OPENUSB_IO_CRC_ERROR'
+ 'OPENUSB_IO_DEVICE_HUNG'
+ 'OPENUSB_IO_REQ_TOO_BIG'
+ 'OPENUSB_IO_BIT_STUFFING'
+ 'OPENUSB_IO_UNEXPECTED_PID'
+ 'OPENUSB_IO_DATA_OVERRUN'
+ 'OPENUSB_IO_DATA_UNDERRUN'
+ 'OPENUSB_IO_BUFFER_OVERRUN'
+ 'OPENUSB_IO_BUFFER_UNDERRUN'
+ 'OPENUSB_IO_PID_CHECK_FAILURE'
+ 'OPENUSB_IO_DATA_TOGGLE_MISMATCH'
+ 'OPENUSB_IO_TIMEOUT'
+ 'OPENUSB_IO_CANCELED'
+ ]
+
+_logger = logging.getLogger('usb.backend.openusb')
+
+OPENUSB_SUCCESS = 0
+OPENUSB_PLATFORM_FAILURE = -1
+OPENUSB_NO_RESOURCES = -2
+OPENUSB_NO_BANDWIDTH = -3
+OPENUSB_NOT_SUPPORTED = -4
+OPENUSB_HC_HARDWARE_ERROR = -5
+OPENUSB_INVALID_PERM = -6
+OPENUSB_BUSY = -7
+OPENUSB_BADARG = -8
+OPENUSB_NOACCESS = -9
+OPENUSB_PARSE_ERROR = -10
+OPENUSB_UNKNOWN_DEVICE = -11
+OPENUSB_INVALID_HANDLE = -12
+OPENUSB_SYS_FUNC_FAILURE = -13
+OPENUSB_NULL_LIST = -14
+OPENUSB_CB_CONTINUE = -20
+OPENUSB_CB_TERMINATE = -21
+OPENUSB_IO_STALL = -50
+OPENUSB_IO_CRC_ERROR = -51
+OPENUSB_IO_DEVICE_HUNG = -52
+OPENUSB_IO_REQ_TOO_BIG = -53
+OPENUSB_IO_BIT_STUFFING = -54
+OPENUSB_IO_UNEXPECTED_PID = -55
+OPENUSB_IO_DATA_OVERRUN = -56
+OPENUSB_IO_DATA_UNDERRUN = -57
+OPENUSB_IO_BUFFER_OVERRUN = -58
+OPENUSB_IO_BUFFER_UNDERRUN = -59
+OPENUSB_IO_PID_CHECK_FAILURE = -60
+OPENUSB_IO_DATA_TOGGLE_MISMATCH = -61
+OPENUSB_IO_TIMEOUT = -62
+OPENUSB_IO_CANCELED = -63
+
+_openusb_errno = {
+ OPENUSB_SUCCESS:None,
+ OPENUSB_PLATFORM_FAILURE:None,
+ OPENUSB_NO_RESOURCES:errno.__dict__.get('ENOMEM', None),
+ OPENUSB_NO_BANDWIDTH:None,
+ OPENUSB_NOT_SUPPORTED:errno.__dict__.get('ENOSYS', None),
+ OPENUSB_HC_HARDWARE_ERROR:errno.__dict__.get('EIO', None),
+ OPENUSB_INVALID_PERM:errno.__dict__.get('EBADF', None),
+ OPENUSB_BUSY:errno.__dict__.get('EBUSY', None),
+ OPENUSB_BADARG:errno.__dict__.get('EINVAL', None),
+ OPENUSB_NOACCESS:errno.__dict__.get('EACCES', None),
+ OPENUSB_PARSE_ERROR:None,
+ OPENUSB_UNKNOWN_DEVICE:errno.__dict__.get('ENODEV', None),
+ OPENUSB_INVALID_HANDLE:errno.__dict__.get('EINVAL', None),
+ OPENUSB_SYS_FUNC_FAILURE:None,
+ OPENUSB_NULL_LIST:None,
+ OPENUSB_CB_CONTINUE:None,
+ OPENUSB_CB_TERMINATE:None,
+ OPENUSB_IO_STALL:errno.__dict__.get('EIO', None),
+ OPENUSB_IO_CRC_ERROR:errno.__dict__.get('EIO', None),
+ OPENUSB_IO_DEVICE_HUNG:errno.__dict__.get('EIO', None),
+ OPENUSB_IO_REQ_TOO_BIG:errno.__dict__.get('E2BIG', None),
+ OPENUSB_IO_BIT_STUFFING:None,
+ OPENUSB_IO_UNEXPECTED_PID:errno.__dict__.get('ESRCH', None),
+ OPENUSB_IO_DATA_OVERRUN:errno.__dict__.get('EOVERFLOW', None),
+ OPENUSB_IO_DATA_UNDERRUN:None,
+ OPENUSB_IO_BUFFER_OVERRUN:errno.__dict__.get('EOVERFLOW', None),
+ OPENUSB_IO_BUFFER_UNDERRUN:None,
+ OPENUSB_IO_PID_CHECK_FAILURE:None,
+ OPENUSB_IO_DATA_TOGGLE_MISMATCH:None,
+ OPENUSB_IO_TIMEOUT:errno.__dict__.get('ETIMEDOUT', None),
+ OPENUSB_IO_CANCELED:errno.__dict__.get('EINTR', None)
+}
+
+class _usb_endpoint_desc(Structure):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bEndpointAddress', c_uint8),
+ ('bmAttributes', c_uint8),
+ ('wMaxPacketSize', c_uint16),
+ ('bInterval', c_uint8),
+ ('bRefresh', c_uint8),
+ ('bSynchAddress', c_uint8)]
+
+class _usb_interface_desc(Structure):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bInterfaceNumber', c_uint8),
+ ('bAlternateSetting', c_uint8),
+ ('bNumEndpoints', c_uint8),
+ ('bInterfaceClass', c_uint8),
+ ('bInterfaceSubClass', c_uint8),
+ ('bInterfaceProtocol', c_uint8),
+ ('iInterface', c_uint8)]
+
+class _usb_config_desc(Structure):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('wTotalLength', c_uint16),
+ ('bNumInterfaces', c_uint8),
+ ('bConfigurationValue', c_uint8),
+ ('iConfiguration', c_uint8),
+ ('bmAttributes', c_uint8),
+ ('bMaxPower', c_uint8)]
+
+class _usb_device_desc(Structure):
+ _fields_ = [('bLength', c_uint8),
+ ('bDescriptorType', c_uint8),
+ ('bcdUSB', c_uint16),
+ ('bDeviceClass', c_uint8),
+ ('bDeviceSubClass', c_uint8),
+ ('bDeviceProtocol', c_uint8),
+ ('bMaxPacketSize0', c_uint8),
+ ('idVendor', c_uint16),
+ ('idProduct', c_uint16),
+ ('bcdDevice', c_uint16),
+ ('iManufacturer', c_uint8),
+ ('iProduct', c_uint8),
+ ('iSerialNumber', c_uint8),
+ ('bNumConfigurations', c_uint8)]
+
+class _openusb_request_result(Structure):
+ _fields_ = [('status', c_int32),
+ ('transfered_bytes', c_uint32)]
+
+class _openusb_ctrl_request(Structure):
+ class _openusb_ctrl_setup(Structure):
+ _fields_ = [('bmRequestType', c_uint8),
+ ('bRequest', c_uint8),
+ ('wValue', c_uint16),
+ ('wIndex', c_uint16)]
+ _fields_ = [('payload', POINTER(c_uint8)),
+ ('length', c_uint32),
+ ('timeout', c_uint32),
+ ('flags', c_uint32),
+ ('result', _openusb_request_result),
+ ('next', c_void_p)]
+
+class _openusb_intr_request(Structure):
+ _fields_ = [('interval', c_uint16),
+ ('payload', POINTER(c_uint8)),
+ ('length', c_uint32),
+ ('timeout', c_uint32),
+ ('flags', c_uint32),
+ ('result', _openusb_request_result),
+ ('next', c_void_p)]
+
+class _openusb_bulk_request(Structure):
+ _fields_ = [('payload', POINTER(c_uint8)),
+ ('length', c_uint32),
+ ('timeout', c_uint32),
+ ('flags', c_uint32),
+ ('result', _openusb_request_result),
+ ('next', c_void_p)]
+
+class _openusb_isoc_pkts(Structure):
+ class _openusb_isoc_packet(Structure):
+ _fields_ = [('payload', POINTER(c_uint8)),
+ ('length', c_uint32)]
+ _fields_ = [('num_packets', c_uint32),
+ ('packets', POINTER(_openusb_isoc_packet))]
+
+class _openusb_isoc_request(Structure):
+ _fields_ = [('start_frame', c_uint32),
+ ('flags', c_uint32),
+ ('pkts', _openusb_isoc_pkts),
+ ('isoc_results', POINTER(_openusb_request_result)),
+ ('isoc_status', c_int32),
+ ('next', c_void_p)]
+
+_openusb_devid = c_uint64
+_openusb_busid = c_uint64
+_openusb_handle = c_uint64
+_openusb_dev_handle = c_uint64
+
+_lib = None
+_ctx = None
+
+def _load_library():
+ libname = ctypes.util.find_library('openusb')
+ if libname is None:
+ raise OSError('USB library could not be found')
+ return CDLL(libname)
+
+def _setup_prototypes(lib):
+ # int32_t openusb_init(uint32_t flags , openusb_handle_t *handle);
+ lib.openusb_init.argtypes = [c_uint32, POINTER(_openusb_handle)]
+ lib.openusb_init.restype = c_int32
+
+ # void openusb_fini(openusb_handle_t handle );
+ lib.openusb_fini.argtypes = [_openusb_handle]
+
+ # uint32_t openusb_get_busid_list(openusb_handle_t handle,
+ # openusb_busid_t **busids,
+ # uint32_t *num_busids);
+ lib.openusb_get_busid_list.argtypes = [
+ _openusb_handle,
+ POINTER(POINTER(_openusb_busid)),
+ POINTER(c_uint32)
+ ]
+
+ # void openusb_free_busid_list(openusb_busid_t * busids);
+ lib.openusb_free_busid_list.argtypes = [POINTER(_openusb_busid)]
+
+ # uint32_t openusb_get_devids_by_bus(openusb_handle_t handle,
+ # openusb_busid_t busid,
+ # openusb_devid_t **devids,
+ # uint32_t *num_devids);
+ lib.openusb_get_devids_by_bus.argtypes = [
+ _openusb_handle,
+ _openusb_busid,
+ POINTER(POINTER(_openusb_devid)),
+ POINTER(c_uint32)
+ ]
+
+ lib.openusb_get_devids_by_bus.restype = c_int32
+
+ # void openusb_free_devid_list(openusb_devid_t * devids);
+ lib.openusb_free_devid_list.argtypes = [POINTER(_openusb_devid)]
+
+ # int32_t openusb_open_device(openusb_handle_t handle,
+ # openusb_devid_t devid ,
+ # uint32_t flags,
+ # openusb_dev_handle_t *dev);
+ lib.openusb_open_device.argtypes = [
+ _openusb_handle,
+ _openusb_devid,
+ c_uint32,
+ POINTER(_openusb_dev_handle)
+ ]
+
+ lib.openusb_open_device.restype = c_int32
+
+ # int32_t openusb_close_device(openusb_dev_handle_t dev);
+ lib.openusb_close_device.argtypes = [_openusb_dev_handle]
+ lib.openusb_close_device.restype = c_int32
+
+ # int32_t openusb_set_configuration(openusb_dev_handle_t dev,
+ # uint8_t cfg);
+ lib.openusb_set_configuration.argtypes = [_openusb_dev_handle, c_uint8]
+ lib.openusb_set_configuration.restype = c_int32
+
+ # int32_t openusb_get_configuration(openusb_dev_handle_t dev,
+ # uint8_t *cfg);
+ lib.openusb_get_configuration.argtypes = [_openusb_dev_handle, POINTER(c_uint8)]
+ lib.openusb_get_configuration.restype = c_int32
+
+ # int32_t openusb_claim_interface(openusb_dev_handle_t dev,
+ # uint8_t ifc,
+ # openusb_init_flag_t flags);
+ lib.openusb_claim_interface.argtypes = [
+ _openusb_dev_handle,
+ c_uint8,
+ c_int
+ ]
+
+ lib.openusb_claim_interface.restype = c_int32
+
+ # int32_t openusb_release_interface(openusb_dev_handle_t dev,
+ # uint8_t ifc);
+ lib.openusb_release_interface.argtypes = [
+ _openusb_dev_handle,
+ c_uint8
+ ]
+
+ lib.openusb_release_interface.restype = c_int32
+
+ # int32_topenusb_set_altsetting(openusb_dev_handle_t dev,
+ # uint8_t ifc,
+ # uint8_t alt);
+ lib.openusb_set_altsetting.argtypes = [
+ _openusb_dev_handle,
+ c_uint8,
+ c_uint8
+ ]
+ lib.openusb_set_altsetting.restype = c_int32
+
+ # int32_t openusb_reset(openusb_dev_handle_t dev);
+ lib.openusb_reset.argtypes = [_openusb_dev_handle]
+ lib.openusb_reset.restype = c_int32
+
+ # int32_t openusb_parse_device_desc(openusb_handle_t handle,
+ # openusb_devid_t devid,
+ # uint8_t *buffer,
+ # uint16_t buflen,
+ # usb_device_desc_t *devdesc);
+ lib.openusb_parse_device_desc.argtypes = [
+ _openusb_handle,
+ _openusb_devid,
+ POINTER(c_uint8),
+ c_uint16,
+ POINTER(_usb_device_desc)
+ ]
+
+ lib.openusb_parse_device_desc.restype = c_int32
+
+ # int32_t openusb_parse_config_desc(openusb_handle_t handle,
+ # openusb_devid_t devid,
+ # uint8_t *buffer,
+ # uint16_t buflen,
+ # uint8_t cfgidx,
+ # usb_config_desc_t *cfgdesc);
+ lib.openusb_parse_config_desc.argtypes = [
+ _openusb_handle,
+ _openusb_devid,
+ POINTER(c_uint8),
+ c_uint16,
+ c_uint8,
+ POINTER(_usb_config_desc)
+ ]
+ lib.openusb_parse_config_desc.restype = c_int32
+
+ # int32_t openusb_parse_interface_desc(openusb_handle_t handle,
+ # openusb_devid_t devid,
+ # uint8_t *buffer,
+ # uint16_t buflen,
+ # uint8_t cfgidx,
+ # uint8_t ifcidx,
+ # uint8_t alt,
+ # usb_interface_desc_t *ifcdesc);
+ lib.openusb_parse_interface_desc.argtypes = [
+ _openusb_handle,
+ _openusb_devid,
+ POINTER(c_uint8),
+ c_uint16,
+ c_uint8,
+ c_uint8,
+ c_uint8,
+ POINTER(_usb_interface_desc)
+ ]
+
+ lib.openusb_parse_interface_desc.restype = c_int32
+
+ # int32_t openusb_parse_endpoint_desc(openusb_handle_t handle,
+ # openusb_devid_t devid,
+ # uint8_t *buffer,
+ # uint16_t buflen,
+ # uint8_t cfgidx,
+ # uint8_t ifcidx,
+ # uint8_t alt,
+ # uint8_t eptidx,
+ # usb_endpoint_desc_t *eptdesc);
+ lib.openusb_parse_endpoint_desc.argtypes = [
+ _openusb_handle,
+ _openusb_devid,
+ POINTER(c_uint8),
+ c_uint16,
+ c_uint8,
+ c_uint8,
+ c_uint8,
+ c_uint8,
+ POINTER(_usb_endpoint_desc)
+ ]
+
+ lib.openusb_parse_interface_desc.restype = c_int32
+
+ # const char *openusb_strerror(int32_t error );
+ lib.openusb_strerror.argtypes = [c_int32]
+ lib.openusb_strerror.restype = c_char_p
+
+ # int32_t openusb_ctrl_xfer(openusb_dev_handle_t dev,
+ # uint8_t ifc,
+ # uint8_t ept,
+ # openusb_ctrl_request_t *ctrl);
+ lib.openusb_ctrl_xfer.argtypes = [
+ _openusb_dev_handle,
+ c_uint8,
+ c_uint8,
+ POINTER(_openusb_ctrl_request)
+ ]
+
+ lib.openusb_ctrl_xfer.restype = c_int32
+
+ # int32_t openusb_intr_xfer(openusb_dev_handle_t dev,
+ # uint8_t ifc,
+ # uint8_t ept,
+ # openusb_intr_request_t *intr);
+ lib.openusb_intr_xfer.argtypes = [
+ _openusb_dev_handle,
+ c_uint8,
+ c_uint8,
+ POINTER(_openusb_intr_request)
+ ]
+
+ lib.openusb_bulk_xfer.restype = c_int32
+
+ # int32_t openusb_bulk_xfer(openusb_dev_handle_t dev,
+ # uint8_t ifc,
+ # uint8_t ept,
+ # openusb_bulk_request_t *bulk);
+ lib.openusb_bulk_xfer.argtypes = [
+ _openusb_dev_handle,
+ c_uint8,
+ c_uint8,
+ POINTER(_openusb_bulk_request)
+ ]
+
+ lib.openusb_bulk_xfer.restype = c_int32
+
+ # int32_t openusb_isoc_xfer(openusb_dev_handle_t dev,
+ # uint8_t ifc,
+ # uint8_t ept,
+ # openusb_isoc_request_t *isoc);
+ lib.openusb_isoc_xfer.argtypes = [
+ _openusb_dev_handle,
+ c_uint8,
+ c_uint8,
+ POINTER(_openusb_isoc_request)
+ ]
+
+ lib.openusb_isoc_xfer.restype = c_int32
+
+def _check(retval):
+ ret = retval.value
+ if ret != 0:
+ from usb.core import USBError
+ raise USBError(_lib.openusb_strerror(ret), ret, _openusb_errno[ret])
+ return retval
+
+class _Context(object):
+ def __init__(self):
+ self.handle = _openusb_handle()
+ _check(_lib.openusb_init(0, byref(self.handle)))
+ def __del__(self):
+ _lib.openusb_fini(self.handle)
+
+class _BusIterator(object):
+ def __init__(self):
+ self.buslist = POINTER(openusb_busid)()
+ num_busids = c_uint32()
+ _check(_lib.openusb_get_busid_list(_ctx.handle,
+ byref(self.buslist),
+ byref(num_busids)))
+ self.num_busids = num_busids.value
+ def __iter__(self):
+ for i in range(self.num_busids):
+ yield self.buslist[i]
+ def __del__(self):
+ _lib.openusb_free_busid_list(self.buslist)
+
+class _DevIterator(object):
+ def __init__(self, busid):
+ self.devlist = POINTER(_openusb_devid)()
+ num_devids = c_uint32()
+ _check(_lib.openusb_get_devids_by_bus(_ctx.handle,
+ busid,
+ byref(self.devlist),
+ byref(num_devids)))
+ self.num_devids = num_devids.value
+ def __iter__(self):
+ for i in range(self.num_devids):
+ yield self.devlist[i]
+ def __del__(self):
+ _lib.openusb_free_devid_list(self.devlist)
+
+class _OpenUSB(usb.backend.IBackend):
+ @methodtrace(_logger)
+ def enumerate_devices(self):
+ for bus in _BusIterator():
+ for devid in _DevIterator(bus):
+ yield devid
+
+ @methodtrace(_logger)
+ def get_device_descriptor(self, dev):
+ desc = _usb_device_desc()
+ _check(_lib.openusb_parse_device_desc(_ctx.handle,
+ dev,
+ None,
+ 0,
+ byref(desc)))
+ desc.bus = None
+ desc.address = None
+ return desc
+
+ @methodtrace(_logger)
+ def get_configuration_descriptor(self, dev, config):
+ desc = _usb_config_desc()
+ _check(_lib.openusb_parse_config_desc(_ctx.handle,
+ dev,
+ None,
+ 0,
+ config,
+ byref(desc)))
+ return desc
+
+ @methodtrace(_logger)
+ def get_interface_descriptor(self, dev, intf, alt, config):
+ desc = _usb_interface_desc()
+ _check(_lib.openusb_parse_interface_desc(_ctx.handle,
+ dev,
+ None,
+ 0,
+ config,
+ intf,
+ alt,
+ byref(desc)))
+ return desc
+
+ @methodtrace(_logger)
+ def get_endpoint_descriptor(self, dev, ep, intf, alt, config):
+ desc = _usb_endpoint_desc()
+ _check(_lib.openusb_parse_endpoint_desc(_ctx.handle,
+ dev,
+ None,
+ 0,
+ config,
+ intf,
+ alt,
+ ep,
+ byref(desc)))
+ return desc
+
+ @methodtrace(_logger)
+ def open_device(self, dev):
+ handle = _openusb_dev_handle()
+ _check(_lib.openusb_open_device(_ctx.handle, dev, 0, byref(handle)))
+ return handle
+
+ @methodtrace(_logger)
+ def close_device(self, dev_handle):
+ _lib.openusb_close_device(dev_handle)
+
+ @methodtrace(_logger)
+ def set_configuration(self, dev_handle, config_value):
+ _check(_lib.openusb_set_configuration(dev_handle, config_value))
+
+ @methodtrace(_logger)
+ def get_configuration(self, dev_handle):
+ config = c_uint8()
+ _check(_lib.openusb_get_configuration(dev_handle, byref(config)))
+ return config.value
+
+ @methodtrace(_logger)
+ def set_interface_altsetting(self, dev_handle, intf, altsetting):
+ _check(_lib.set_altsetting(dev_handle, intf, altsetting))
+
+ @methodtrace(_logger)
+ def claim_interface(self, dev_handle, intf):
+ _check(_lib.openusb_claim_interface(dev_handle, intf, 0))
+
+ @methodtrace(_logger)
+ def release_interface(self, dev_handle, intf):
+ _lib.openusb_release_interface(dev_handle, intf)
+
+ @methodtrace(_logger)
+ def bulk_write(self, dev_handle, ep, intf, data, timeout):
+ request = _openusb_bulk_request()
+ memset(byref(request), 0, sizeof(request))
+ request.payload, request.length = data.buffer_info()
+ request.timeout = timeout
+ _check(_lib.openusb_bulk_xfer(dev_handle, intf, ep, byref(request)))
+ return request.transfered_bytes.value
+
+ @methodtrace(_logger)
+ def bulk_read(self, dev_handle, ep, intf, size, timeout):
+ request = _openusb_bulk_request()
+ buffer = array.array('B', '\x00' * size)
+ memset(byref(request), 0, sizeof(request))
+ request.payload, request.length = buffer.buffer_info()
+ request.timeout = timeout
+ _check(_lib.openusb_bulk_xfer(dev_handle, intf, ep, byref(request)))
+ return buffer[:request.transfered_bytes.value]
+
+ @methodtrace(_logger)
+ def intr_write(self, dev_handle, ep, intf, data, timeout):
+ request = _openusb_intr_request()
+ memset(byref(request), 0, sizeof(request))
+ payload, request.length = data.buffer_info()
+ request.payload = cast(payload, POINTER(c_uint8))
+ request.timeout = timeout
+ _check(_lib.openusb_intr_xfer(dev_handle, intf, ep, byref(request)))
+ return request.transfered_bytes.value
+
+ @methodtrace(_logger)
+ def intr_read(self, dev_handle, ep, intf, size, timeout):
+ request = _openusb_intr_request()
+ buffer = array.array('B', '\x00' * size)
+ memset(byref(request), 0, sizeof(request))
+ payload, request.length = buffer.buffer_info()
+ request.payload = cast(payload, POINTER(c_uint8))
+ request.timeout = timeout
+ _check(_lib.openusb_intr_xfer(dev_handle, intf, ep, byref(request)))
+ return buffer[:request.transfered_bytes.value]
+
+# TODO: implement isochronous
+# @methodtrace(_logger)
+# def iso_write(self, dev_handle, ep, intf, data, timeout):
+# pass
+
+# @methodtrace(_logger)
+# def iso_read(self, dev_handle, ep, intf, size, timeout):
+# pass
+
+ @methodtrace(_logger)
+ def ctrl_transfer(self,
+ dev_handle,
+ bmRequestType,
+ bRequest,
+ wValue,
+ wIndex,
+ data_or_wLength,
+ timeout):
+ request = _openusb_ctrl_request()
+ request.setup.bmRequestType = bmRequestType
+ request.setup.bRequest = bRequest
+ request.setup.wValue
+ request.setup.wIndex
+ request.timeout = timeout
+
+ direction = usb.util.ctrl_direction(bmRequestType)
+
+ if direction == ENDPOINT_OUT:
+ buffer = data_or_wLength
+ else:
+ buffer = array.array('B', '\x00' * data_or_wLength)
+
+ payload, request.length = buffer.buffer_info()
+ request.payload = cast(payload, POINTER(c_uint8))
+
+ ret = _check(_lib.openusb_ctrl_xfer(dev_handle, 0, 0, byref(request)))
+
+ if direction == ENDPOINT_OUT:
+ ret
+ else:
+ buffer[:ret]
+
+ @methodtrace(_logger)
+ def reset_device(self, dev_handle):
+ _check(_lib.openusb_reset(dev_handle))
+
+def get_backend():
+ try:
+ global _lib, _ctx
+ if _lib is None:
+ _lib = _load_library()
+ _setup_prototypes(_lib)
+ _ctx = _Context()
+ return _OpenUSB()
+ except Exception:
+ _logger.error('Error loading OpenUSB backend', exc_info=True)
+ return None
diff --git a/nxt_plugin/usb/control.py b/nxt_plugin/usb/control.py
new file mode 100644
index 0000000..8647c14
--- /dev/null
+++ b/nxt_plugin/usb/control.py
@@ -0,0 +1,252 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+r"""usb.control - USB standard control requests
+
+This module exports:
+
+get_status - get recipeint status
+clear_feature - clear a recipient feature
+set_feature - set a recipient feature
+get_descriptor - get a device descriptor
+set_descriptor - set a device descriptor
+get_configuration - get a device configuration
+set_configuration - set a device configuration
+get_interface - get a device interface
+set_interface - set a device interface
+"""
+
+__author__ = 'Wander Lairson Costa'
+
+__all__ = ['get_status',
+ 'clear_feature',
+ 'set_feature',
+ 'get_descriptor',
+ 'set_descriptor',
+ 'get_configuration',
+ 'set_configuration',
+ 'get_interface',
+ 'set_interface',
+ 'ENDPOINT_HALT',
+ 'FUNCTION_SUSPEND',
+ 'DEVICE_REMOTE_WAKEUP',
+ 'U1_ENABLE',
+ 'U2_ENABLE',
+ 'LTM_ENABLE']
+
+import usb.util as util
+import usb.core as core
+
+def _parse_recipient(recipient, direction):
+ if recipient is None:
+ r = util.CTRL_RECIPIENT_DEVICE
+ wIndex = 0
+ elif isinstance(recipient, core.Interface):
+ r = util.CTRL_RECIPIENT_INTERFACE
+ wIndex = recipient.bInterfaceNumber
+ elif isinstance(recipient, core.Endpoint):
+ r = util.CTRL_RECIPIENT_ENDPOINT
+ wIndex = recipient.bEndpointAddress
+ else:
+ raise ValueError('Invalid recipient.')
+ bmRequestType = util.build_request_type(
+ direction,
+ util.CTRL_TYPE_STANDARD,
+ r
+ )
+ return (bmRequestType, wIndex)
+
+# standard feature selectors from USB 2.0/3.0
+ENDPOINT_HALT = 0
+FUNCTION_SUSPEND = 0
+DEVICE_REMOTE_WAKEUP = 1
+U1_ENABLE = 48
+U2_ENABLE = 49
+LTM_ENABLE = 50
+
+def get_status(dev, recipient = None):
+ r"""Return the status for the specified recipient.
+
+ dev is the Device object to which the request will be
+ sent to.
+
+ The recipient can be None (on which the status will be queried
+ on the device), an Interface or Endpoint descriptors.
+
+ The status value is returned as an integer with the lower
+ word being the two bytes status value.
+ """
+ bmRequestType, wIndex = _parse_recipient(recipient, util.CTRL_IN)
+ ret = dev.ctrl_transfer(bmRequestType = bmRequestType,
+ bRequest = 0x00,
+ wIndex = wIndex,
+ data_or_wLength = 2)
+ return ret[0] | (ret[1] << 8)
+
+def clear_feature(dev, feature, recipient = None):
+ r"""Clear/disable a specific feature.
+
+ dev is the Device object to which the request will be
+ sent to.
+
+ feature is the feature you want to disable.
+
+ The recipient can be None (on which the status will be queried
+ on the device), an Interface or Endpoint descriptors.
+ """
+ bmRequestType, wIndex = _parse_recipient(recipient, util.CTRL_OUT)
+ dev.ctrl_transfer(bmRequestType = bmRequestType,
+ bRequest = 0x01,
+ wIndex = wIndex,
+ wValue = feature)
+
+def set_feature(dev, feature, recipient = None):
+ r"""Set/enable a specific feature.
+
+ dev is the Device object to which the request will be
+ sent to.
+
+ feature is the feature you want to enable.
+
+ The recipient can be None (on which the status will be queried
+ on the device), an Interface or Endpoint descriptors.
+ """
+ bmRequestType, wIndex = _parse_recipient(recipient, util.CTRL_OUT)
+ dev.ctrl_transfer(bmRequestType = bmRequestType,
+ bRequest = 0x03,
+ wIndex = wIndex,
+ wValue = feature)
+
+def get_descriptor(dev, desc_size, desc_type, desc_index, wIndex = 0):
+ r"""Return the specified descriptor.
+
+ dev is the Device object to which the request will be
+ sent to.
+
+ desc_size is the descriptor size.
+
+ desc_type and desc_index are the descriptor type and index,
+ respectively. wIndex index is used for string descriptors
+ and represents the Language ID. For other types of descriptors,
+ it is zero.
+ """
+ wValue = desc_index | (desc_type << 8)
+ bmRequestType = util.build_request_type(
+ util.CTRL_IN,
+ util.CTRL_TYPE_STANDARD,
+ util.CTRL_RECIPIENT_DEVICE
+ )
+ return dev.ctrl_transfer(
+ bmRequestType = bmRequestType,
+ bRequest = 0x06,
+ wValue = wValue,
+ wIndex = wIndex,
+ data_or_wLength = desc_size
+ )
+
+def set_descriptor(dev, desc, desc_type, desc_index, wIndex = None):
+ r"""Update an existing descriptor or add a new one.
+
+ dev is the Device object to which the request will be
+ sent to.
+
+ The desc parameter is the descriptor to be sent to the device.
+ desc_type and desc_index are the descriptor type and index,
+ respectively. wIndex index is used for string descriptors
+ and represents the Language ID. For other types of descriptors,
+ it is zero.
+ """
+ wValue = desc_index | (desc_type << 8)
+ bmRequestType = util.build_request_type(
+ util.CTRL_OUT,
+ util.CTRL_TYPE_STANDARD,
+ util.CTRL_RECIPIENT_DEVICE
+ )
+ dev.ctrl_transfer(
+ bmRequestType = bmRequestType,
+ bRequest = 0x07,
+ wValue = wValue,
+ wIndex = wIndex,
+ data_or_wLength = desc
+ )
+
+def get_configuration(dev):
+ r"""Get the current active configuration of the device.
+
+ dev is the Device object to which the request will be
+ sent to.
+
+ This function differs from the Device.get_active_configuration
+ method because the later may use cached data, while this
+ function always does a device request.
+ """
+ bmRequestType = util.build_request_type(
+ util.CTRL_IN,
+ util.CTRL_TYPE_STANDARD,
+ util.CTRL_RECIPIENT_DEVICE
+ )
+ return dev.ctrl_transfer(
+ bmRequestType,
+ bRequest = 0x08,
+ data_or_wLength = 1
+ )[0]
+
+def set_configuration(dev, bConfigurationNumber):
+ r"""Set the current device configuration.
+
+ dev is the Device object to which the request will be
+ sent to.
+ """
+ dev.set_configuration(bConfigurationNumber)
+
+def get_interface(dev, bInterfaceNumber):
+ r"""Get the current alternate setting of the interface.
+
+ dev is the Device object to which the request will be
+ sent to.
+ """
+ bmRequestType = util.build_request_type(
+ util.CTRL_IN,
+ util.CTRL_TYPE_STANDARD,
+ util.CTRL_RECIPIENT_INTERFACE
+ )
+ return dev.ctrl_transfer(
+ bmRequestType = bmRequestType,
+ bRequest = 0x0a,
+ wIndex = bInterfaceNumber,
+ data_or_wLength = 1
+ )[0]
+
+def set_interface(dev, bInterfaceNumber, bAlternateSetting):
+ r"""Set the alternate setting of the interface.
+
+ dev is the Device object to which the request will be
+ sent to.
+ """
+ dev.set_interface_altsetting(bInterfaceNumber, bAlternateSetting)
+
diff --git a/nxt_plugin/usb/core.py b/nxt_plugin/usb/core.py
new file mode 100644
index 0000000..e2ebefd
--- /dev/null
+++ b/nxt_plugin/usb/core.py
@@ -0,0 +1,859 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+r"""usb.core - Core USB features.
+
+This module exports:
+
+Device - a class representing a USB device.
+Configuration - a class representing a configuration descriptor.
+Interface - a class representing an interface descriptor.
+Endpoint - a class representing an endpoint descriptor.
+find() - a function to find USB devices.
+"""
+
+__author__ = 'Wander Lairson Costa'
+
+__all__ = ['Device', 'Configuration', 'Interface', 'Endpoint', 'find']
+
+import usb.util as util
+import copy
+import operator
+import usb._interop as _interop
+import logging
+
+_logger = logging.getLogger('usb.core')
+
+_DEFAULT_TIMEOUT = 1000
+
+def _set_attr(input, output, fields):
+ for f in fields:
+ setattr(output, f, int(getattr(input, f)))
+
+class _ResourceManager(object):
+ def __init__(self, dev, backend):
+ self.backend = backend
+ self._active_cfg_index = None
+ self.dev = dev
+ self.handle = None
+ self._claimed_intf = _interop._set()
+ self._alt_set = {}
+ self._ep_type_map = {}
+
+ def managed_open(self):
+ if self.handle is None:
+ self.handle = self.backend.open_device(self.dev)
+ return self.handle
+
+ def managed_close(self):
+ if self.handle is not None:
+ self.backend.close_device(self.handle)
+ self.handle = None
+
+ def managed_set_configuration(self, device, config):
+ if config is None:
+ cfg = device[0]
+ elif isinstance(config, Configuration):
+ cfg = config
+ elif config == 0: # unconfigured state
+ class FakeConfiguration(object):
+ def __init__(self):
+ self.index = None
+ self.bConfigurationValue = 0
+ cfg = FakeConfiguration()
+ else:
+ cfg = util.find_descriptor(device, bConfigurationValue=config)
+ self.managed_open()
+ self.backend.set_configuration(self.handle, cfg.bConfigurationValue)
+ # cache the index instead of the object to avoid cyclic references
+ # of the device and Configuration (Device tracks the _ResourceManager,
+ # which tracks the Configuration, which tracks the Device)
+ self._active_cfg_index = cfg.index
+ # after changing configuration, our alternate setting and endpoint type caches
+ # are not valid anymore
+ self._ep_type_map.clear()
+ self._alt_set.clear()
+
+ def managed_claim_interface(self, device, intf):
+ self.managed_open()
+ if intf is None:
+ cfg = self.get_active_configuration(device)
+ i = cfg[(0,0)].bInterfaceNumber
+ elif isinstance(intf, Interface):
+ i = intf.bInterfaceNumber
+ else:
+ i = intf
+ if i not in self._claimed_intf:
+ self.backend.claim_interface(self.handle, i)
+ self._claimed_intf.add(i)
+
+ def managed_release_interface(self, device, intf):
+ if intf is None:
+ cfg = self.get_active_configuration(device)
+ i = cfg[(0,0)].bInterfaceNumber
+ elif isinstance(intf, Interface):
+ i = intf.bInterfaceNumber
+ else:
+ i = intf
+ if i in self._claimed_intf:
+ self.backend.release_interface(self.handle, i)
+ self._claimed_intf.remove(i)
+
+ def managed_set_interface(self, device, intf, alt):
+ if intf is None:
+ i = self.get_interface(device, intf)
+ elif isinstance(intf, Interface):
+ i = intf
+ else:
+ cfg = self.get_active_configuration(device)
+ if alt is not None:
+ i = util.find_descriptor(cfg, bInterfaceNumber=intf, bAlternateSetting=alt)
+ else:
+ i = util.find_descriptor(cfg, bInterfaceNumber=intf)
+ self.managed_claim_interface(device, i)
+ if alt is None:
+ alt = i.bAlternateSetting
+ self.backend.set_interface_altsetting(self.handle, i.bInterfaceNumber, alt)
+ self._alt_set[i.bInterfaceNumber] = alt
+
+ def get_interface(self, device, intf):
+ # TODO: check the viability of issuing a GET_INTERFACE
+ # request when we don't have a alternate setting cached
+ if intf is None:
+ cfg = self.get_active_configuration(device)
+ return cfg[(0,0)]
+ elif isinstance(intf, Interface):
+ return intf
+ else:
+ cfg = self.get_active_configuration(device)
+ if intf in self._alt_set:
+ return util.find_descriptor(cfg,
+ bInterfaceNumber=intf,
+ bAlternateSetting=self._alt_set[intf])
+ else:
+ return util.find_descriptor(cfg, bInterfaceNumber=intf)
+
+ def get_active_configuration(self, device):
+ if self._active_cfg_index is None:
+ self.managed_open()
+ cfg = util.find_descriptor(
+ device,
+ bConfigurationValue=self.backend.get_configuration(self.handle)
+ )
+ if cfg is None:
+ raise USBError('Configuration not set')
+ self._active_cfg_index = cfg.index
+ return cfg
+ return device[self._active_cfg_index]
+
+ def get_endpoint_type(self, device, address, intf):
+ intf = self.get_interface(device, intf)
+ key = (address, intf.bInterfaceNumber, intf.bAlternateSetting)
+ try:
+ return self._ep_type_map[key]
+ except KeyError:
+ e = util.find_descriptor(intf, bEndpointAddress=address)
+ etype = util.endpoint_type(e.bmAttributes)
+ self._ep_type_map[key] = etype
+ return etype
+
+ def release_all_interfaces(self, device):
+ claimed = copy.copy(self._claimed_intf)
+ for i in claimed:
+ self.managed_release_interface(device, i)
+
+ def dispose(self, device, close_handle = True):
+ self.release_all_interfaces(device)
+ if close_handle:
+ self.managed_close()
+ self._ep_type_map.clear()
+ self._alt_set.clear()
+ self._active_cfg_index = None
+
+class USBError(IOError):
+ r"""Exception class for USB errors.
+
+ Backends must raise this exception when USB related errors occur.
+ The backend specific error code is available through the
+ 'backend_error_code' member variable.
+ """
+
+ def __init__(self, strerror, error_code = None, errno = None):
+ r"""Initialize the object.
+
+ This initializes the USBError object. The strerror and errno are passed
+ to the parent object. The error_code parameter is attributed to the
+ backend_error_code member variable.
+ """
+ IOError.__init__(self, errno, strerror)
+ self.backend_error_code = error_code
+
+class Endpoint(object):
+ r"""Represent an endpoint object.
+
+ This class contains all fields of the Endpoint Descriptor
+ according to the USB Specification. You may access them as class
+ properties. For example, to access the field bEndpointAddress
+ of the endpoint descriptor:
+
+ >>> import usb.core
+ >>> dev = usb.core.find()
+ >>> for cfg in dev:
+ >>> for i in cfg:
+ >>> for e in i:
+ >>> print e.bEndpointAddress
+ """
+
+ def __init__(self, device, endpoint, interface = 0,
+ alternate_setting = 0, configuration = 0):
+ r"""Initialize the Endpoint object.
+
+ The device parameter is the device object returned by the find()
+ function. endpoint is the endpoint logical index (not the endpoint address).
+ The configuration parameter is the logical index of the
+ configuration (not the bConfigurationValue field). The interface
+ parameter is the interface logical index (not the bInterfaceNumber field)
+ and alternate_setting is the alternate setting logical index (not the
+ bAlternateSetting value). Not every interface has more than one alternate
+ setting. In this case, the alternate_setting parameter should be zero.
+ By "logical index" we mean the relative order of the configurations returned by the
+ peripheral as a result of GET_DESCRIPTOR request.
+ """
+ self.device = device
+ intf = Interface(device, interface, alternate_setting, configuration)
+ self.interface = intf.bInterfaceNumber
+ self.index = endpoint
+
+ backend = device._ctx.backend
+
+ desc = backend.get_endpoint_descriptor(
+ device._ctx.dev,
+ endpoint,
+ interface,
+ alternate_setting,
+ configuration
+ )
+
+ _set_attr(
+ desc,
+ self,
+ (
+ 'bLength',
+ 'bDescriptorType',
+ 'bEndpointAddress',
+ 'bmAttributes',
+ 'wMaxPacketSize',
+ 'bInterval',
+ 'bRefresh',
+ 'bSynchAddress'
+ )
+ )
+
+ def write(self, data, timeout = None):
+ r"""Write data to the endpoint.
+
+ The parameter data contains the data to be sent to the endpoint and
+ timeout is the time limit of the operation. The transfer type and
+ endpoint address are automatically inferred.
+
+ The method returns the number of bytes written.
+
+ For details, see the Device.write() method.
+ """
+ return self.device.write(self.bEndpointAddress, data, self.interface, timeout)
+
+ def read(self, size, timeout = None):
+ r"""Read data from the endpoint.
+
+ The parameter size is the number of bytes to read and timeout is the
+ time limit of the operation.The transfer type and endpoint address
+ are automatically inferred.
+
+ The method returns an array.array object with the data read.
+
+ For details, see the Device.read() method.
+ """
+ return self.device.read(self.bEndpointAddress, size, self.interface, timeout)
+
+class Interface(object):
+ r"""Represent an interface object.
+
+ This class contains all fields of the Interface Descriptor
+ according to the USB Specification. You may access them as class
+ properties. For example, to access the field bInterfaceNumber
+ of the interface descriptor:
+
+ >>> import usb.core
+ >>> dev = usb.core.find()
+ >>> for cfg in dev:
+ >>> for i in cfg:
+ >>> print i.bInterfaceNumber
+ """
+
+ def __init__(self, device, interface = 0,
+ alternate_setting = 0, configuration = 0):
+ r"""Initialize the interface object.
+
+ The device parameter is the device object returned by the find()
+ function. The configuration parameter is the logical index of the
+ configuration (not the bConfigurationValue field). The interface
+ parameter is the interface logical index (not the bInterfaceNumber field)
+ and alternate_setting is the alternate setting logical index (not the
+ bAlternateSetting value). Not every interface has more than one alternate
+ setting. In this case, the alternate_setting parameter should be zero.
+ By "logical index" we mean the relative order of the configurations returned by the
+ peripheral as a result of GET_DESCRIPTOR request.
+ """
+ self.device = device
+ self.alternate_index = alternate_setting
+ self.index = interface
+ self.configuration = configuration
+
+ backend = device._ctx.backend
+
+ desc = backend.get_interface_descriptor(
+ self.device._ctx.dev,
+ interface,
+ alternate_setting,
+ configuration
+ )
+
+ _set_attr(
+ desc,
+ self,
+ (
+ 'bLength',
+ 'bDescriptorType',
+ 'bInterfaceNumber',
+ 'bAlternateSetting',
+ 'bNumEndpoints',
+ 'bInterfaceClass',
+ 'bInterfaceSubClass',
+ 'bInterfaceProtocol',
+ 'iInterface',
+ )
+ )
+
+ def set_altsetting(self):
+ r"""Set the interface alternate setting."""
+ self.device.set_interface_altsetting(
+ self.bInterfaceNumber,
+ self.bAlternateSetting
+ )
+
+ def __iter__(self):
+ r"""Iterate over all endpoints of the interface."""
+ for i in range(self.bNumEndpoints):
+ yield Endpoint(
+ self.device,
+ i,
+ self.index,
+ self.alternate_index,
+ self.configuration
+ )
+ def __getitem__(self, index):
+ r"""Return the Endpoint object in the given position."""
+ return Endpoint(
+ self.device,
+ index,
+ self.index,
+ self.alternate_index,
+ self.configuration
+ )
+
+class Configuration(object):
+ r"""Represent a configuration object.
+
+ This class contains all fields of the Configuration Descriptor
+ according to the USB Specification. You may access them as class
+ properties. For example, to access the field bConfigurationValue
+ of the configuration descriptor:
+
+ >>> import usb.core
+ >>> dev = usb.core.find()
+ >>> for cfg in dev:
+ >>> print cfg.bConfigurationValue
+ """
+
+ def __init__(self, device, configuration = 0):
+ r"""Initialize the configuration object.
+
+ The device parameter is the device object returned by the find()
+ function. The configuration parameter is the logical index of the
+ configuration (not the bConfigurationValue field). By "logical index"
+ we mean the relative order of the configurations returned by the
+ peripheral as a result of GET_DESCRIPTOR request.
+ """
+ self.device = device
+ self.index = configuration
+
+ backend = device._ctx.backend
+
+ desc = backend.get_configuration_descriptor(
+ self.device._ctx.dev,
+ configuration
+ )
+
+ _set_attr(
+ desc,
+ self,
+ (
+ 'bLength',
+ 'bDescriptorType',
+ 'wTotalLength',
+ 'bNumInterfaces',
+ 'bConfigurationValue',
+ 'iConfiguration',
+ 'bmAttributes',
+ 'bMaxPower'
+ )
+ )
+
+ def set(self):
+ r"""Set this configuration as the active one."""
+ self.device.set_configuration(self.bConfigurationValue)
+
+ def __iter__(self):
+ r"""Iterate over all interfaces of the configuration."""
+ for i in range(self.bNumInterfaces):
+ alt = 0
+ try:
+ while True:
+ yield Interface(self.device, i, alt, self.index)
+ alt += 1
+ except (USBError, IndexError):
+ pass
+ def __getitem__(self, index):
+ r"""Return the Interface object in the given position.
+
+ index is a tuple of two values with interface index and
+ alternate setting index, respectivally. Example:
+
+ >>> interface = config[(0, 0)]
+ """
+ return Interface(self.device, index[0], index[1], self.index)
+
+
+class Device(object):
+ r"""Device object.
+
+ This class contains all fields of the Device Descriptor according
+ to the USB Specification. You may access them as class properties.
+ For example, to access the field bDescriptorType of the device
+ descriptor:
+
+ >>> import usb.core
+ >>> dev = usb.core.find()
+ >>> dev.bDescriptorType
+
+ Additionally, the class provides methods to communicate with
+ the hardware. Typically, an application will first call the
+ set_configuration() method to put the device in a known configured
+ state, optionally call the set_interface_altsetting() to select the
+ alternate setting (if there is more than one) of the interface used,
+ and call the write() and read() method to send and receive data.
+
+ When working in a new hardware, one first try would be like this:
+
+ >>> import usb.core
+ >>> dev = usb.core.find(idVendor=myVendorId, idProduct=myProductId)
+ >>> dev.set_configuration()
+ >>> dev.write(1, 'test')
+
+ This sample finds the device of interest (myVendorId and myProductId should be
+ replaced by the corresponding values of your device), then configures the device
+ (by default, the configuration value is 1, which is a typical value for most
+ devices) and then writes some data to the endpoint 0x01.
+
+ Timeout values for the write, read and ctrl_transfer methods are specified in
+ miliseconds. If the parameter is omitted, Device.default_timeout value will
+ be used instead. This property can be set by the user at anytime.
+ """
+
+ def __init__(self, dev, backend):
+ r"""Initialize the Device object.
+
+ Library users should normally get a Device instance through
+ the find function. The dev parameter is the identification
+ of a device to the backend and its meaning is opaque outside
+ of it. The backend parameter is a instance of a backend
+ object.
+ """
+ self._ctx = _ResourceManager(dev, backend)
+ self.__default_timeout = _DEFAULT_TIMEOUT
+
+ desc = backend.get_device_descriptor(dev)
+
+ _set_attr(
+ desc,
+ self,
+ (
+ 'bLength',
+ 'bDescriptorType',
+ 'bcdUSB',
+ 'bDeviceClass',
+ 'bDeviceSubClass',
+ 'bDeviceProtocol',
+ 'bMaxPacketSize0',
+ 'idVendor',
+ 'idProduct',
+ 'bcdDevice',
+ 'iManufacturer',
+ 'iProduct',
+ 'iSerialNumber',
+ 'bNumConfigurations',
+ 'address',
+ 'bus'
+ )
+ )
+
+ self.bus = int(desc.bus) if desc.bus is not None else None
+ self.address = int(desc.address) if desc.address is not None else None
+
+ def set_configuration(self, configuration = None):
+ r"""Set the active configuration.
+
+ The configuration parameter is the bConfigurationValue field of the
+ configuration you want to set as active. If you call this method
+ without parameter, it will use the first configuration found.
+ As a device hardly ever has more than one configuration, calling
+ the method without parameter is enough to get the device ready.
+ """
+ self._ctx.managed_set_configuration(self, configuration)
+
+ def get_active_configuration(self):
+ r"""Return a Configuration object representing the current configuration set."""
+ return self._ctx.get_active_configuration(self)
+
+ def set_interface_altsetting(self, interface = None, alternate_setting = None):
+ r"""Set the alternate setting for an interface.
+
+ When you want to use an interface and it has more than one alternate setting,
+ you should call this method to select the alternate setting you would like
+ to use. If you call the method without one or the two parameters, it will
+ be selected the first one found in the Device in the same way of set_configuration
+ method.
+
+ Commonly, an interface has only one alternate setting and this call is
+ not necessary. For most of the devices, either it has more than one alternate
+ setting or not, it is not harmful to make a call to this method with no arguments,
+ as devices will silently ignore the request when there is only one alternate
+ setting, though the USB Spec allows devices with no additional alternate setting
+ return an error to the Host in response to a SET_INTERFACE request.
+
+ If you are in doubt, you may want to call it with no arguments wrapped by
+ a try/except clause:
+
+ >>> try:
+ >>> dev.set_interface_altsetting()
+ >>> except usb.core.USBError:
+ >>> pass
+ """
+ self._ctx.managed_set_interface(self, interface, alternate_setting)
+
+ def reset(self):
+ r"""Reset the device."""
+ self._ctx.dispose(self, False)
+ self._ctx.backend.reset_device(self._ctx.handle)
+ self._ctx.dispose(self, True)
+
+ def write(self, endpoint, data, interface = None, timeout = None):
+ r"""Write data to the endpoint.
+
+ This method is used to send data to the device. The endpoint parameter
+ corresponds to the bEndpointAddress member whose endpoint you want to
+ communicate with. The interface parameter is the bInterfaceNumber field
+ of the interface descriptor which contains the endpoint. If you do not
+ provide one, the first one found will be used, as explained in the
+ set_interface_altsetting() method.
+
+ The data parameter should be a sequence like type convertible to
+ array type (see array module).
+
+ The timeout is specified in miliseconds.
+
+ The method returns the number of bytes written.
+ """
+ backend = self._ctx.backend
+
+ fn_map = {
+ util.ENDPOINT_TYPE_BULK:backend.bulk_write,
+ util.ENDPOINT_TYPE_INTR:backend.intr_write,
+ util.ENDPOINT_TYPE_ISO:backend.iso_write
+ }
+
+ intf = self._ctx.get_interface(self, interface)
+ fn = fn_map[self._ctx.get_endpoint_type(self, endpoint, intf)]
+ self._ctx.managed_claim_interface(self, intf)
+
+ return fn(
+ self._ctx.handle,
+ endpoint,
+ intf.bInterfaceNumber,
+ _interop.as_array(data),
+ self.__get_timeout(timeout)
+ )
+
+ def read(self, endpoint, size, interface = None, timeout = None):
+ r"""Read data from the endpoint.
+
+ This method is used to receive data from the device. The endpoint parameter
+ corresponds to the bEndpointAddress member whose endpoint you want to
+ communicate with. The interface parameter is the bInterfaceNumber field
+ of the interface descriptor which contains the endpoint. If you do not
+ provide one, the first one found will be used, as explained in the
+ set_interface_altsetting() method. The size parameters tells how many
+ bytes you want to read.
+
+ The timeout is specified in miliseconds.
+
+ The method returns an array object with the data read.
+ """
+ backend = self._ctx.backend
+
+ fn_map = {
+ util.ENDPOINT_TYPE_BULK:backend.bulk_read,
+ util.ENDPOINT_TYPE_INTR:backend.intr_read,
+ util.ENDPOINT_TYPE_ISO:backend.iso_read
+ }
+
+ intf = self._ctx.get_interface(self, interface)
+ fn = fn_map[self._ctx.get_endpoint_type(self, endpoint, intf)]
+ self._ctx.managed_claim_interface(self, intf)
+
+ return fn(
+ self._ctx.handle,
+ endpoint,
+ intf.bInterfaceNumber,
+ size,
+ self.__get_timeout(timeout)
+ )
+
+
+ def ctrl_transfer(self, bmRequestType, bRequest, wValue=0, wIndex=0,
+ data_or_wLength = None, timeout = None):
+ r"""Do a control transfer on the endpoint 0.
+
+ This method is used to issue a control transfer over the
+ endpoint 0(endpoint 0 is required to always be a control endpoint).
+
+ The parameters bmRequestType, bRequest, wValue and wIndex are the
+ same of the USB Standard Control Request format.
+
+ Control requests may or may not have a data payload to write/read.
+ In cases which it has, the direction bit of the bmRequestType
+ field is used to infere the desired request direction. For
+ host to device requests (OUT), data_or_wLength parameter is
+ the data payload to send, and it must be a sequence type convertible
+ to an array object. In this case, the return value is the number of data
+ payload written. For device to host requests (IN), data_or_wLength
+ is the wLength parameter of the control request specifying the
+ number of bytes to read in data payload. In this case, the return
+ value is the data payload read, as an array object.
+ """
+ if util.ctrl_direction(bmRequestType) == util.CTRL_OUT:
+ a = _interop.as_array(data_or_wLength)
+ elif data_or_wLength is None:
+ a = 0
+ else:
+ a = data_or_wLength
+
+ self._ctx.managed_open()
+
+ return self._ctx.backend.ctrl_transfer(
+ self._ctx.handle,
+ bmRequestType,
+ bRequest,
+ wValue,
+ wIndex,
+ a,
+ self.__get_timeout(timeout)
+ )
+
+ def is_kernel_driver_active(self, interface):
+ r"""Determine if there is kernel driver associated with the interface.
+
+ If a kernel driver is active, and the object will be unable to perform I/O.
+ """
+ self._ctx.managed_open()
+ return self._ctx.backend.is_kernel_driver_active(self._ctx.handle,
+ self._ctx.get_interface(self, interface).bInterfaceNumber)
+
+ def detach_kernel_driver(self, interface):
+ r"""Detach a kernel driver.
+
+ If successful, you will then be able to perform I/O.
+ """
+ self._ctx.managed_open()
+ self._ctx.backend.detach_kernel_driver(self._ctx.handle,
+ self._ctx.get_interface(self, interface).bInterfaceNumber)
+
+ def attach_kernel_driver(self, interface):
+ r"""Re-attach an interface's kernel driver, which was previously
+ detached using detach_kernel_driver()."""
+ self._ctx.managed_open()
+ self._ctx.backend.attach_kernel_driver(self._ctx.handle,
+ self._ctx.get_interface(self, interface).bInterfaceNumber)
+
+ def __iter__(self):
+ r"""Iterate over all configurations of the device."""
+ for i in range(self.bNumConfigurations):
+ yield Configuration(self, i)
+
+ def __getitem__(self, index):
+ r"""Return the Configuration object in the given position."""
+ return Configuration(self, index)
+
+ def __del__(self):
+ self._ctx.dispose(self)
+
+ def __get_timeout(self, timeout):
+ if timeout is not None:
+ return timeout
+ return self.__default_timeout
+
+ def __set_def_tmo(self, tmo):
+ if tmo < 0:
+ raise ValueError('Timeout cannot be a negative value')
+ self.__default_timeout = tmo
+
+ def __get_def_tmo(self):
+ return self.__default_timeout
+
+ default_timeout = property(
+ __get_def_tmo,
+ __set_def_tmo,
+ doc = 'Default timeout for transfer I/O functions'
+ )
+
+def find(find_all=False, backend = None, custom_match = None, **args):
+ r"""Find an USB device and return it.
+
+ find() is the function used to discover USB devices.
+ You can pass as arguments any combination of the
+ USB Device Descriptor fields to match a device. For example:
+
+ find(idVendor=0x3f4, idProduct=0x2009)
+
+ will return the Device object for the device with
+ idVendor Device descriptor field equals to 0x3f4 and
+ idProduct equals to 0x2009.
+
+ If there is more than one device which matchs the criteria,
+ the first one found will be returned. If a matching device cannot
+ be found the function returns None. If you want to get all
+ devices, you can set the parameter find_all to True, then find
+ will return an list with all matched devices. If no matching device
+ is found, it will return an empty list. Example:
+
+ printers = find(find_all=True, bDeviceClass=7)
+
+ This call will get all the USB printers connected to the system.
+ (actually may be not, because some devices put their class
+ information in the Interface Descriptor).
+
+ You can also use a customized match criteria:
+
+ dev = find(custom_match = lambda d: d.idProduct=0x3f4 and d.idvendor=0x2009)
+
+ A more accurate printer finder using a customized match would be like
+ so:
+
+ def is_printer(dev):
+ import usb.util
+ if dev.bDeviceClass == 7:
+ return True
+ for cfg in dev:
+ if usb.util.find_descriptor(cfg, bInterfaceClass=7) is not None:
+ return True
+
+ printers = find(find_all=True, custom_match = is_printer)
+
+ Now even if the device class code is in the interface descriptor the
+ printer will be found.
+
+ You can combine a customized match with device descriptor fields. In this
+ case, the fields must match and the custom_match must return True. In the our
+ previous example, if we would like to get all printers belonging to the
+ manufacturer 0x3f4, the code would be like so:
+
+ printers = find(find_all=True, idVendor=0x3f4, custom_match=is_printer)
+
+ If you want to use find as a 'list all devices' function, just call
+ it with find_all = True:
+
+ devices = find(find_all=True)
+
+ Finally, you may pass a custom backend to the find function:
+
+ find(backend = MyBackend())
+
+ PyUSB has builtin backends for libusb 0.1, libusb 1.0 and OpenUSB.
+ If you do not supply a backend explicitly, find() function will select
+ one of the predefineds backends according to system availability.
+
+ Backends are explained in the usb.backend module.
+ """
+
+ def device_iter(k, v):
+ for dev in backend.enumerate_devices():
+ d = Device(dev, backend)
+ if (custom_match is None or custom_match(d)) and \
+ _interop._reduce(
+ lambda a, b: a and b,
+ map(
+ operator.eq,
+ v,
+ map(lambda i: getattr(d, i), k)
+ ),
+ True
+ ):
+ yield d
+
+ if backend is None:
+ import usb.backend.libusb10 as libusb10
+ import usb.backend.libusb01 as libusb01
+ import usb.backend.openusb as openusb
+
+ for m in (libusb10, openusb, libusb01):
+ backend = m.get_backend()
+ if backend is not None:
+ _logger.info('find(): using backend "%s"', m.__name__)
+ break
+ else:
+ raise ValueError('No backend available')
+
+ k, v = args.keys(), args.values()
+
+ if find_all:
+ return [d for d in device_iter(k, v)]
+ else:
+ try:
+ return _interop._next(device_iter(k, v))
+ except StopIteration:
+ return None
diff --git a/nxt_plugin/usb/legacy.py b/nxt_plugin/usb/legacy.py
new file mode 100644
index 0000000..0c9c52c
--- /dev/null
+++ b/nxt_plugin/usb/legacy.py
@@ -0,0 +1,334 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+import usb.core as core
+import usb.util as util
+import usb._interop as _interop
+import usb.control as control
+
+__author__ = 'Wander Lairson Costa'
+
+USBError = core.USBError
+
+CLASS_AUDIO = 1
+CLASS_COMM = 2
+CLASS_DATA = 10
+CLASS_HID = 3
+CLASS_HUB = 9
+CLASS_MASS_STORAGE = 8
+CLASS_PER_INTERFACE = 0
+CLASS_PRINTER = 7
+CLASS_VENDOR_SPEC = 255
+DT_CONFIG = 2
+DT_CONFIG_SIZE = 9
+DT_DEVICE = 1
+DT_DEVICE_SIZE = 18
+DT_ENDPOINT = 5
+DT_ENDPOINT_AUDIO_SIZE = 9
+DT_ENDPOINT_SIZE = 7
+DT_HID = 33
+DT_HUB = 41
+DT_HUB_NONVAR_SIZE = 7
+DT_INTERFACE = 4
+DT_INTERFACE_SIZE = 9
+DT_PHYSICAL = 35
+DT_REPORT = 34
+DT_STRING = 3
+ENDPOINT_ADDRESS_MASK = 15
+ENDPOINT_DIR_MASK = 128
+ENDPOINT_IN = 128
+ENDPOINT_OUT = 0
+ENDPOINT_TYPE_BULK = 2
+ENDPOINT_TYPE_CONTROL = 0
+ENDPOINT_TYPE_INTERRUPT = 3
+ENDPOINT_TYPE_ISOCHRONOUS = 1
+ENDPOINT_TYPE_MASK = 3
+ERROR_BEGIN = 500000
+MAXALTSETTING = 128
+MAXCONFIG = 8
+MAXENDPOINTS = 32
+MAXINTERFACES = 32
+RECIP_DEVICE = 0
+RECIP_ENDPOINT = 2
+RECIP_INTERFACE = 1
+RECIP_OTHER = 3
+REQ_CLEAR_FEATURE = 1
+REQ_GET_CONFIGURATION = 8
+REQ_GET_DESCRIPTOR = 6
+REQ_GET_INTERFACE = 10
+REQ_GET_STATUS = 0
+REQ_SET_ADDRESS = 5
+REQ_SET_CONFIGURATION = 9
+REQ_SET_DESCRIPTOR = 7
+REQ_SET_FEATURE = 3
+REQ_SET_INTERFACE = 11
+REQ_SYNCH_FRAME = 12
+TYPE_CLASS = 32
+TYPE_RESERVED = 96
+TYPE_STANDARD = 0
+TYPE_VENDOR = 64
+
+class Endpoint(object):
+ r"""Endpoint descriptor object."""
+ def __init__(self, ep):
+ self.address = ep.bEndpointAddress
+ self.interval = ep.bInterval
+ self.maxPacketSize = ep.wMaxPacketSize
+ self.type = util.endpoint_type(ep.bmAttributes)
+
+class Interface(object):
+ r"""Interface descriptor object."""
+ def __init__(self, intf):
+ self.alternateSetting = intf.bAlternateSetting
+ self.interfaceNumber = intf.bInterfaceNumber
+ self.iInterface = intf.iInterface
+ self.interfaceClass = intf.bInterfaceClass
+ self.interfaceSubClass = intf.bInterfaceSubClass
+ self.interfaceProtocol = intf.bInterfaceProtocol
+ self.endpoints = [Endpoint(e) for e in intf]
+
+class Configuration(object):
+ r"""Configuration descriptor object."""
+ def __init__(self, cfg):
+ self.iConfiguration = cfg.iConfiguration
+ self.maxPower = cfg.bMaxPower << 2
+ self.remoteWakeup = (cfg.bmAttributes >> 5) & 1
+ self.selfPowered = (cfg.bmAttributes >> 6) & 1
+ self.totalLength = cfg.wTotalLength
+ self.value = cfg.bConfigurationValue
+ self.interfaces = [
+ list(g) for k, g in _interop._groupby(
+ _interop._sorted(
+ [Interface(i) for i in cfg],
+ key=lambda i: i.interfaceNumber
+ ),
+ lambda i: i.alternateSetting)
+ ]
+
+class DeviceHandle(object):
+ def __init__(self, dev):
+ self.dev = dev
+ self.__claimed_interface = -1
+
+ def bulkWrite(self, endpoint, buffer, timeout = 100):
+ r"""Perform a bulk write request to the endpoint specified.
+
+ Arguments:
+ endpoint: endpoint number.
+ buffer: sequence data buffer to write.
+ This parameter can be any sequence type.
+ timeout: operation timeout in miliseconds. (default: 100)
+ Returns the number of bytes written.
+ """
+ return self.dev.write(endpoint, buffer, self.__claimed_interface, timeout)
+
+ def bulkRead(self, endpoint, size, timeout = 100):
+ r"""Performs a bulk read request to the endpoint specified.
+
+ Arguments:
+ endpoint: endpoint number.
+ size: number of bytes to read.
+ timeout: operation timeout in miliseconds. (default: 100)
+ Return a tuple with the data read.
+ """
+ return self.dev.read(endpoint, size, self.__claimed_interface, timeout)
+
+ def interruptWrite(self, endpoint, buffer, timeout = 100):
+ r"""Perform a interrupt write request to the endpoint specified.
+
+ Arguments:
+ endpoint: endpoint number.
+ buffer: sequence data buffer to write.
+ This parameter can be any sequence type.
+ timeout: operation timeout in miliseconds. (default: 100)
+ Returns the number of bytes written.
+ """
+ return self.dev.write(endpoint, buffer, self.__claimed_interface, timeout)
+
+ def interruptRead(self, endpoint, size, timeout = 100):
+ r"""Performs a interrupt read request to the endpoint specified.
+
+ Arguments:
+ endpoint: endpoint number.
+ size: number of bytes to read.
+ timeout: operation timeout in miliseconds. (default: 100)
+ Return a tuple with the data read.
+ """
+ return self.dev.read(endpoint, size, self.__claimed_interface, timeout)
+
+ def controlMsg(self, requestType, request, buffer, value = 0, index = 0, timeout = 100):
+ r"""Perform a control request to the default control pipe on a device.
+
+ Arguments:
+ requestType: specifies the direction of data flow, the type
+ of request, and the recipient.
+ request: specifies the request.
+ buffer: if the transfer is a write transfer, buffer is a sequence
+ with the transfer data, otherwise, buffer is the number of
+ bytes to read.
+ value: specific information to pass to the device. (default: 0)
+ index: specific information to pass to the device. (default: 0)
+ timeout: operation timeout in miliseconds. (default: 100)
+ Return the number of bytes written.
+ """
+ return self.dev.ctrl_transfer(
+ requestType,
+ request,
+ wValue = value,
+ wIndex = index,
+ data_or_wLength = buffer,
+ timeout = timeout
+ )
+
+ def clearHalt(self, endpoint):
+ r"""Clears any halt status on the specified endpoint.
+
+ Arguments:
+ endpoint: endpoint number.
+ """
+ cfg = self.dev.get_active_configuration()
+ intf = util.find_descriptor(cfg, bInterfaceNumber = self.__claimed_interface)
+ e = util.find_descriptor(intf, bEndpointAddress = endpoint)
+ control.clear_feature(self.dev, control.ENDPOINT_HALT, e)
+
+ def claimInterface(self, interface):
+ r"""Claims the interface with the Operating System.
+
+ Arguments:
+ interface: interface number or an Interface object.
+ """
+ if_num = interface.interfaceNumber \
+ if isinstance(interface, Interface) else interface
+
+ util.claim_interface(self.dev, if_num)
+ self.__claimed_interface = if_num
+
+ def releaseInterface(self):
+ r"""Release an interface previously claimed with claimInterface."""
+ util.release_interface(self.dev, self.__claimed_interface)
+ self.__claimed_interface = -1
+
+ def reset(self):
+ r"""Reset the specified device by sending a RESET
+ down the port it is connected to."""
+ self.dev.reset()
+
+ def resetEndpoint(self, endpoint):
+ r"""Reset all states for the specified endpoint.
+
+ Arguments:
+ endpoint: endpoint number.
+ """
+ self.clearHalt(endpoint)
+
+ def setConfiguration(self, configuration):
+ r"""Set the active configuration of a device.
+
+ Arguments:
+ configuration: a configuration value or a Configuration object.
+ """
+ self.dev.set_configuration(configuration)
+
+ def setAltInterface(self, alternate):
+ r"""Sets the active alternate setting of the current interface.
+
+ Arguments:
+ alternate: an alternate setting number or an Interface object.
+ """
+ self.dev.set_interface_altsetting(self.__claimed_interface, alternate)
+
+ def getString(self, index, length, langid = None):
+ r"""Retrieve the string descriptor specified by index
+ and langid from a device.
+
+ Arguments:
+ index: index of descriptor in the device.
+ length: number of bytes of the string
+ langid: Language ID. If it is omittedi, will be
+ used the first language.
+ """
+ return util.get_string(self.dev, length, index, langid).encode('ascii')
+
+ def getDescriptor(self, desc_type, desc_index, length, endpoint = -1):
+ r"""Retrieves a descriptor from the device identified by the type
+ and index of the descriptor.
+
+ Arguments:
+ desc_type: descriptor type.
+ desc_index: index of the descriptor.
+ len: descriptor length.
+ endpoint: ignored.
+ """
+ return control.get_descriptor(self.dev, length, desc_type, desc_index)
+
+ def detachKernelDriver(self, interface):
+ r"""Detach a kernel driver from the interface (if one is attached,
+ we have permission and the operation is supported by the OS)
+
+ Arguments:
+ interface: interface number or an Interface object.
+ """
+ self.dev.detach_kernel_driver(interface)
+
+class Device(object):
+ r"""Device descriptor object"""
+ def __init__(self, dev):
+ self.deviceClass = dev.bDeviceClass
+ self.deviceSubClass = dev.bDeviceSubClass
+ self.deviceProtocol = dev.bDeviceProtocol
+ self.deviceVersion = dev.bcdDevice
+ self.devnum = None
+ self.filename = ''
+ self.iManufacturer = dev.iManufacturer
+ self.iProduct = dev.iProduct
+ self.iSerialNumber = dev.iSerialNumber
+ self.idProduct = dev.idProduct
+ self.idVendor = dev.idVendor
+ self.maxPacketSize = dev.bMaxPacketSize0
+ self.usbVersion = dev.bcdUSB
+ self.configurations = [Configuration(c) for c in dev]
+ self.dev = dev
+
+ def open(self):
+ r"""Open the device for use.
+
+ Return a DeviceHandle object
+ """
+ return DeviceHandle(self.dev)
+
+class Bus(object):
+ r"""Bus object."""
+ def __init__(self):
+ self.dirname = ''
+ self.localtion = 0
+ self.devices = [Device(d) for d in core.find(find_all=True)]
+
+def busses():
+ r"""Return a tuple with the usb busses."""
+ return (Bus(),)
+
diff --git a/nxt_plugin/usb/util.py b/nxt_plugin/usb/util.py
new file mode 100644
index 0000000..1f8cee3
--- /dev/null
+++ b/nxt_plugin/usb/util.py
@@ -0,0 +1,260 @@
+# Copyright (C) 2009-2011 Wander Lairson Costa
+#
+# The following terms apply to all files associated
+# with the software unless explicitly disclaimed in individual files.
+#
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose, provided
+# that existing copyright notices are retained in all copies and that this
+# notice is included verbatim in any distributions. No written agreement,
+# license, or royalty fee is required for any of the authorized uses.
+# Modifications to this software may be copyrighted by their authors
+# and need not follow the licensing terms described here, provided that
+# the new terms are clearly indicated on the first page of each file where
+# they apply.
+#
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+r"""usb.util - Utility functions.
+
+This module exports:
+
+endpoint_address - return the endpoint absolute address.
+endpoint_direction - return the endpoint transfer direction.
+endpoint_type - return the endpoint type
+ctrl_direction - return the direction of a control transfer
+build_request_type - build a bmRequestType field of a control transfer.
+find_descriptor - find an inner descriptor.
+claim_interface - explicitly claim an interface.
+release_interface - explicitly release an interface.
+dispose_resources - release internal resources allocated by the object.
+get_string - retrieve a string descriptor from the device.
+"""
+
+__author__ = 'Wander Lairson Costa'
+
+import operator
+import usb._interop as _interop
+
+# descriptor type
+DESC_TYPE_DEVICE = 0x01
+DESC_TYPE_CONFIG = 0x02
+DESC_TYPE_STRING = 0x03
+DESC_TYPE_INTERFACE = 0x04
+DESC_TYPE_ENDPOINT = 0x05
+
+# endpoint direction
+ENDPOINT_IN = 0x80
+ENDPOINT_OUT = 0x00
+
+# endpoint type
+ENDPOINT_TYPE_CTRL = 0x00
+ENDPOINT_TYPE_ISO = 0x01
+ENDPOINT_TYPE_BULK = 0x02
+ENDPOINT_TYPE_INTR = 0x03
+
+# control request type
+CTRL_TYPE_STANDARD = (0 << 5)
+CTRL_TYPE_CLASS = (1 << 5)
+CTRL_TYPE_VENDOR = (2 << 5)
+CTRL_TYPE_RESERVED = (3 << 5)
+
+# control request recipient
+CTRL_RECIPIENT_DEVICE = 0
+CTRL_RECIPIENT_INTERFACE = 1
+CTRL_RECIPIENT_ENDPOINT = 2
+CTRL_RECIPIENT_OTHER = 3
+
+# control request direction
+CTRL_OUT = 0x00
+CTRL_IN = 0x80
+
+_ENDPOINT_ADDR_MASK = 0x0f
+_ENDPOINT_DIR_MASK = 0x80
+_ENDPOINT_TRANSFER_TYPE_MASK = 0x03
+_CTRL_DIR_MASK = 0x80
+
+def endpoint_address(address):
+ r"""Return the endpoint absolute address.
+
+ The address parameter is the bEndpointAddress field
+ of the endpoint descriptor.
+ """
+ return address & _ENDPOINT_ADDR_MASK
+
+def endpoint_direction(address):
+ r"""Return the endpoint direction.
+
+ The address parameter is the bEndpointAddress field
+ of the endpoint descriptor.
+ The possible return values are ENDPOINT_OUT or ENDPOINT_IN.
+ """
+ return address & _ENDPOINT_DIR_MASK
+
+def endpoint_type(bmAttributes):
+ r"""Return the transfer type of the endpoint.
+
+ The bmAttributes parameter is the bmAttributes field
+ of the endpoint descriptor.
+ The possible return values are: ENDPOINT_TYPE_CTRL,
+ ENDPOINT_TYPE_ISO, ENDPOINT_TYPE_BULK or ENDPOINT_TYPE_INTR.
+ """
+ return bmAttributes & _ENDPOINT_TRANSFER_TYPE_MASK
+
+def ctrl_direction(bmRequestType):
+ r"""Return the direction of a control request.
+
+ The bmRequestType parameter is the value of the
+ bmRequestType field of a control transfer.
+ The possible return values are CTRL_OUT or CTRL_IN.
+ """
+ return bmRequestType & _CTRL_DIR_MASK
+
+def build_request_type(direction, type, recipient):
+ r"""Build a bmRequestType field for control requests.
+
+ These is a conventional function to build a bmRequestType
+ for a control request.
+
+ The direction parameter can be CTRL_OUT or CTRL_IN.
+ The type parameter can be CTRL_TYPE_STANDARD, CTRL_TYPE_CLASS,
+ CTRL_TYPE_VENDOR or CTRL_TYPE_RESERVED values.
+ The recipient can be CTRL_RECIPIENT_DEVICE, CTRL_RECIPIENT_INTERFACE,
+ CTRL_RECIPIENT_ENDPOINT or CTRL_RECIPIENT_OTHER.
+
+ Return the bmRequestType value.
+ """
+ return recipient | type | direction
+
+def find_descriptor(desc, find_all=False, custom_match=None, **args):
+ r"""Find an inner descriptor.
+
+ find_descriptor works in the same way the core.find() function does,
+ but it acts on general descriptor objects. For example, suppose you
+ have a Device object called dev and want a Configuration of this
+ object with its bConfigurationValue equals to 1, the code would
+ be like so:
+
+ >>> cfg = util.find_descriptor(dev, bConfigurationValue=1)
+
+ You can use any field of the Descriptor as a match criteria, and you
+ can supply a customized match just like core.find() does. The
+ find_descriptor function also accepts the find_all parameter to get
+ a list of descriptor instead of just one.
+ """
+ def desc_iter(k, v):
+ for d in desc:
+ if (custom_match is None or custom_match(d)) and \
+ _interop._reduce(
+ lambda a, b: a and b,
+ map(
+ operator.eq,
+ v,
+ map(lambda i: getattr(d, i), k)
+ ),
+ True
+ ):
+ yield d
+
+ k, v = args.keys(), args.values()
+
+ if find_all:
+ return [d for d in desc_iter(k, v)]
+ else:
+ try:
+ return _interop._next(desc_iter(k, v))
+ except StopIteration:
+ return None
+
+def claim_interface(device, interface):
+ r"""Explicitly claim an interface.
+
+ PyUSB users normally do not have to worry about interface claiming,
+ as the library takes care of it automatically. But there are situations
+ where you need deterministic interface claiming. For these uncommon
+ cases, you can use claim_interface.
+
+ If the interface is already claimed, either through a previously call
+ to claim_interface or internally by the device object, nothing happens.
+ """
+ device._ctx.managed_claim_interface(device, interface)
+
+def release_interface(device, interface):
+ r"""Explicitly release an interface.
+
+ This function is used to release an interface previously claimed,
+ either through a call to claim_interface or internally by the
+ device object.
+
+ Normally, you do not need to worry about claiming policies, as
+ the device object takes care of it automatically.
+ """
+ device._ctx.managed_release_interface(device, interface)
+
+def dispose_resources(device):
+ r"""Release internal resources allocated by the object.
+
+ Sometimes you need to provide deterministic resources
+ freeing, for example to allow another application to
+ talk to the device. As Python does not provide deterministic
+ destruction, this function releases all internal resources
+ allocated by the device, like device handle and interface
+ policy.
+
+ After calling this function, you can continue using the device
+ object normally. If the resources will be necessary again, it
+ will allocate them automatically.
+ """
+ device._ctx.dispose(device)
+
+def get_string(dev, length, index, langid = None):
+ r"""Retrieve a string descriptor from the device.
+
+ dev is the Device object to which the request will be
+ sent to.
+
+ length is the length of string in number of characters.
+
+ index is the string descriptor index and langid is the Language
+ ID of the descriptor. If langid is omitted, the string descriptor
+ of the first Language ID will be returned.
+
+ The return value is the unicode string present in the descriptor.
+ """
+ from usb.control import get_descriptor
+ if langid is None:
+ # Asking for the zero'th index is special - it returns a string
+ # descriptor that contains all the language IDs supported by the device.
+ # Typically there aren't many - often only one. The language IDs are 16
+ # bit numbers, and they start at the third byte in the descriptor. See
+ # USB 2.0 specification section 9.6.7 for more information.
+ #
+ # Note from libusb 1.0 sources (descriptor.c)
+ buf = get_descriptor(
+ dev,
+ 1024,
+ DESC_TYPE_STRING,
+ 0
+ )
+ assert len(buf) >= 4
+ langid = buf[2] | (buf[3] << 8)
+
+ buf = get_descriptor(
+ dev,
+ length * 2 + 2, # string is utf16 + 2 bytes of the descriptor
+ DESC_TYPE_STRING,
+ index,
+ langid
+ )
+ return buf[2:].tostring().decode('utf-16-le')