ThunderBorg - Powerful motor controller mounted to your Raspberry Pi
Main Examples
Download and installation instructions can be found on the getting started tab, as well as wiring instructions for the motor(s) and power.The package of examples can be downloaded as a zip file directly: http://www.piborg.org/downloads/thunderborg/examples.zip
You can also view the code listings at the bottom of this page.
ThunderBorg.py
This is the main library for ThunderBorg.It makes life easier by providing simple named functions instead of having to call complicated I²C commands directly, for example:
TB.SetMotor1(0.5)
See the scripts below for examples of use, or the library function listings for usage guidance.
tbSequence.py
A simple script which controls the motors automatically based on an example sequence.This script demonstrates how simple operating ThunderBorg can be ^_^
Lines 30 to 48 determine the sequence the motors will run, line 50 determines how fast the sequence runs through.
Run using:
cd ~/thunderborg
./tbSequence.py
tbJoystick.py and runTbJoy.sh
This script uses pygame to demonstrate how we can make a joystick / gamepad control a robotTo run this example you will need to get a joystick / gamepad installed and working with the Raspberry Pi first.
If you are using a PS3 controller our PS3 help page here, otherwise see the JoyBorg, Connecting the joystick to the Raspberry Pi section for guidance.
The script will need to be setup for the controller you are using.
The lines which may need changing are:
- 41:
axisUpDown
, the axis index used for speed control - 42:
axisUpDownInverted
, set toTrue
if the robot runs the wrong way - 43:
axisLeftRight
, the axis index used for steering control - 44:
axisLeftRightInverted
, set toTrue
if the robot turns the wrong way - 45:
buttonSlow
, the button index used for hold to drive slowly - 46:
slowFactor
, the maximum drive speed when the hold to drive slowly button is held - 47:
buttonFastTurn
, the button index used for hold to turn fast, allows turning on the spot whilst held
- Left stick up / down: Speed control
- Right stick left / right: Steering control
- L2: Drive slowly whilst held (limits to 50% speed)
- R2: Turn fast whilst held (allows on the spot turning)
- Blue: The script is waiting for a controller to be connected
- Green/Yellow/Red: Currently showing battery status based on the set limits
- Purple: A fault has been detected with one of the motors
- Flashing Yellow and Red: Communications timeout, either with the controller or the ThunderBorg itself
- Off: The script has finished or had an error starting
runTbJoy.sh
, is only used to hide some irritating lines displayed by pygame, it does not affect how the script works.Run using:
cd ~/thunderborg
./runTbJoy.sh
tbStepper.py
This script demonstrates how a stepper motor (4, 5, or 6 wire) can be driven using ThunderBorg.The script will ask you how many steps to move, it will then move that many steps as fast as it can.
Line 22 sets how long the script must remain at a given step before moving to the next position, if the motor is not operating correctly you may need to make this value larger.
Lines 10 and 11 set the amount of power to use to drive the stepper.
Run using:
cd ~/thunderborg
./tbStepper.py
tbStepperSeq.py
A simple script which controls a steeper motor automatically based on an example sequence.Lines 83 to 92 determine the sequence the motor will run as a coded operation, this is then looped.
Line 11 sets how long the script must remain at a given step before moving to the next position, if the motor is not operating correctly you may need to make this value larger.
Line 12 tells the script how many degrees are represented by a single step, this will need to be set based on the stepper motor used.
The number of steps per rotation can be used to calculate the
degPerStep
value as follows:degPerStep = stepsPerRotation / 360.0
Run using:
cd ~/thunderborg
./tbStepperSeq.py
tbSetBatteryLimits.py
This script allows you to view the currently set battery limits and to change them to new values.This affects the colours shown from the LED when in battery monitoring mode.
By default the colours are based on the limits of what ThunderBorg will use as a power supply:
For a 9V rechargeable battery we find a range of 7.5 to 10.5 V works well:
For a 10x rechargeable AA battery pack we find a range of 9.5 to 13.5 V works well:
Run using:
cd ~/thunderborg
./tbSetBatteryLimits.py
tbReadBattery.py
A simple script which reads the battery voltage and displays it on screen.This script also enables the LED battery monitoring mode.
Run using:
cd ~/thunderborg
./tbReadBattery.py
tbLedWave.py
This script shows a soothing wave pattern using the LEDs.It shows how the LEDs can be controlled to produce a large range of vivid colours.
Run using:
cd ~/thunderborg
./tbLedWave.py
tbExternalLedWave.py
This script shows a soothing wave pattern using a connected RasPiO InsPiRing.It shows how the InsPiRing can be controlled using a ThunderBorg in place of the Driver pHAT.
Run using:
cd ~/thunderborg
./tbExternalLedWave.py
tbGui.py
This script shows how you can make a GUI in Python which controls ThunderBorg, as well as providing an easy to use interactive demonstration of the board without needing any additional controller.Drag the sliders up to make the motor move forwards, down to make the motor move backwards.
To get the same speed as the GUI in a script divide the value shown by 100, e.g. if the GUI shows motor 1 as -35 and you want the same speed run
TB.SetMotor1(-0.35)
in your script.The "All Off" button will set both motors back to 0, the GUI will also do this when closed.
The label at the bottom shows the current battery voltage and is coloured to match the LED battery monitoring colours.
Run using:
cd ~/thunderborg
./tbGui.py
or double-clicking the icon on the desktop.
tbLedGui.py
This script provides a quick method to set the colours of both the ThunderBorg LED and the ThunderBorg Lid LED.The colours can be adjusted by:
- Moving the position in the triangle
- Moving the position on the colour ring
- Changing the hue, saturation, and value numbers
- Changing the red, green, and blue numbers
- Entering an HTML colour code into the colour name box
cd ~/thunderborg
./tbGui.py
or double-clicking the icon on the desktop.
User Examples
Please post any examples you would like to share with others on the forum here.Library Functions
Use by creating an instance of the class, call the Init function, then command as desired, e.g.import ThunderBorg TB = ThunderBorg.ThunderBorg() TB.Init() # User code here, use TB to control the boardMultiple boards can be used when configured with different I²C addresses by creating multiple instances, e.g.
import ThunderBorg TB1 = ThunderBorg.ThunderBorg() TB2 = ThunderBorg.ThunderBorg() TB1.i2cAddress = 0x10 TB2.i2cAddress = 0x11 TB1.Init() TB2.Init() # User code here, use TB1 and TB2 to control each board separatelyFor explanations of the functions available call the Help function, e.g.
import ThunderBorg TB = ThunderBorg.ThunderBorg() TB.Help()The same help text is also found below
In the below listing any function parameters surrounded by [ ] are optional and may be omitted.
Board Settings
These values should be accessed from a particular instance of the library, and may be set / read differently if more then one is created.Set these after creating an instance but before calling
Init()
, for example to set the I²C address:import ThunderBorg TB = ThunderBorg.ThunderBorg() TB.i2cAddress = 0x10 TB.Init()When reading these values to see if things have worked read after calling
Init()
, for example to see if the board was found:import ThunderBorg TB = ThunderBorg.ThunderBorg() TB.Init() if TB.foundChip: print 'Yay !' else: print 'Aww...'
busNumber
I²C bus on which the ThunderBorg is attached (Rev 1 is bus0
, Rev 2 is bus 1
)bus
The smbus object used to talk to the I²C bus, only use this to talk with other I²C devicesi2cAddress
The I²C address of the ThunderBorg chip to controlfoundChip
True
if the ThunderBorg chip can be seen, False
otherwiseprintFunction
Function reference to call when printing text, ifNone
"print
" is usedBoard Functions
These represent the majority of the functionality of the ThunderBorg, such as setting motor speedsCall these functions on an instance, after calling
Init()
, e.g.import ThunderBorg TB = ThunderBorg.ThunderBorg() TB.Init() TB.SetMotor1(0.4)
RawWrite(command, data)
Sends a raw command on the I2C bus to the ThunderBorgCommand codes can be found at the top of ThunderBorg.py, data is a list of 0 or more byte values
Under most circumstances you should use the appropriate function instead of RawWrite
RawRead(command, length, [retryCount])
Reads data back from the ThunderBorg after sending a GET commandCommand codes can be found at the top of ThunderBorg.py, length is the number of bytes to read back
The function checks that the first byte read back matches the requested command
If it does not it will retry the request until retryCount is exhausted (default is 3 times)
Under most circumstances you should use the appropriate function instead of RawRead
InitBusOnly(busNumber, address)
Prepare the I2C driver for talking to a ThunderBorg on the specified bus and I2C addressThis call does not check the board is present or working, under most circumstances use Init() instead
Print(message)
Wrapper used by the ThunderBorg instance to print messages, will callprintFunction
if set, print
otherwiseNoPrint(message)
Does nothing, intended for disabling diagnostic printout by using:TB = ThunderBorg.ThunderBorg() TB.printFunction = TB.NoPrint
Init([tryOtherBus])
Prepare the I²C driver for talking to the ThunderBorgIf
tryOtherBus
is True
this function will attempt to use the other bus if the ThunderBorg devices can not be found on the current busNumber
SetMotor2(power)
Sets the drive level for motor 2, from +1 to -1.e.g.
SetMotor2(0)
-> motor 2 is stoppedSetMotor2(0.75)
-> motor 2 moving forward at 75% powerSetMotor2(-0.5)
-> motor 2 moving reverse at 50% powerSetMotor2(1)
-> motor 2 moving forward at 100% powerpower = GetMotor2()
Gets the drive level for motor 2, from +1 to -1.e.g.
0
-> motor 2 is stopped0.75
-> motor 2 moving forward at 75% power-0.5
-> motor 2 moving reverse at 50% power1
-> motor 2 moving forward at 100% powerSetMotor1(power)
Sets the drive level for motor 1, from +1 to -1.e.g.
SetMotor1(0)
-> motor 1 is stoppedSetMotor1(0.75)
-> motor 1 moving forward at 75% powerSetMotor1(-0.5)
-> motor 1 moving reverse at 50% powerSetMotor1(1)
-> motor 1 moving forward at 100% powerpower = GetMotor1()
Gets the drive level for motor 1, from +1 to -1.e.g.
0
-> motor 1 is stopped0.75
-> motor 1 moving forward at 75% power-0.5
-> motor 1 moving reverse at 50% power1
-> motor 1 moving forward at 100% powerSetMotors(power)
Sets the drive level for all motors, from +1 to -1.e.g.
SetMotors(0)
-> all motors are stoppedSetMotors(0.75)
-> all motors moving forward at 75% powerSetMotors(-0.5)
-> all motors moving reverse at 50% powerSetMotors(1)
-> all motors moving forward at 100% powerMotorsOff()
Sets all motors to stopped, useful when ending a programSetLed1(r, g, b)
Sets the current colour of the ThunderBorg LED.r
, g
, b
may each be between 0
and 1
.e.g.
SetLed1(0, 0, 0)
-> ThunderBorg LED offSetLed1(1, 1, 1)
-> ThunderBorg LED full whiteSetLed1(1.0, 0.5, 0.0)
-> ThunderBorg LED bright orangeSetLed1(0.2, 0.0, 0.2)
-> ThunderBorg LED dull purpler, g, b = GetLed1()
Gets the current colour of the ThunderBorg LED.r
, g
, b
may each be between 0
and 1
.e.g.
0, 0, 0
-> ThunderBorg LED off1, 1, 1
-> ThunderBorg LED full white1.0, 0.5, 0.0
-> ThunderBorg LED bright orange0.2, 0.0, 0.2
-> ThunderBorg LED dull purpleSetLed2(r, g, b)
Sets the current colour of the ThunderBorg Lid LED.r
, g
, b
may each be between 0
and 1
.e.g.
SetLed2(0, 0, 0)
-> ThunderBorg Lid LED offSetLed2(1, 1, 1)
-> ThunderBorg Lid LED full whiteSetLed2(1.0, 0.5, 0.0)
-> ThunderBorg Lid LED bright orangeSetLed2(0.2, 0.0, 0.2)
-> ThunderBorg Lid LED dull purpler, g, b = GetLed2()
Gets the current colour of the ThunderBorg Lid LED.r
, g
, b
may each be between 0
and 1
.e.g.
0, 0, 0
-> ThunderBorg Lid LED off1, 1, 1
-> ThunderBorg Lid LED full white1.0, 0.5, 0.0
-> ThunderBorg Lid LED bright orange0.2, 0.0, 0.2
-> ThunderBorg Lid LED dull purpleSetLeds(r, g, b)
Sets the current colour of both LEDs.r
, g
, b
may each be between 0
and 1
.e.g.
SetLeds(0, 0, 0)
-> Both LEDs offSetLeds(1, 1, 1)
-> Both LEDs full whiteSetLeds(1.0, 0.5, 0.0)
-> Both LEDs bright orangeSetLeds(0.2, 0.0, 0.2)
-> Both LEDs dull purpleSetLedShowBattery(state)
Sets the system to enable or disable the LEDs showing the current battery levelIf enabled the LED colours will be ignored and will use the current battery reading instead
This sweeps from fully green for maximum voltage (35 V) to fully red for minimum voltage (7 V)
state = GetLedShowBattery()
Gets if the system is using the LEDs to show the current battery level, true for enabled, false for disabledIf enabled the LED colours will be ignored and will use the current battery reading instead
This sweeps from fully green for maximum voltage (35 V) to fully red for minimum voltage (7 V)
SetCommsFailsafe(state)
Sets the system to enable or disable the communications failsafeThe failsafe will turn the motors off unless it is commanded at least once every 1/4 of a second
Set to
True
to enable this failsafe, set to False
to disable this failsafeThe failsafe is disabled at power on
state = GetCommsFailsafe()
Read the current system state of the communications failsafe,True
for enabled, False
for disabledThe failsafe will turn the motors off unless it is commanded at least once every 1/4 of a second
state = GetDriveFault1()
Reads the system drive fault state for motor #1,False
for no problems, True
for a fault has been detectedFaults may indicate power problems, such as under-voltage (not enough power), and may be cleared by setting a lower drive power
If a fault is persistent, it repeatably occurs when trying to control the board, this may indicate a wiring problem such as:
- The supply is not powerful enough for the motors
The board has a bare minimum requirement of 6V to operate correctly
A recommended minimum supply of 7.2V should be sufficient for smaller motors - The + and - connections for motor #1 are connected to each other
- Either + or - is connected to ground (
GND
, also known as0V
or earth) - Either + or - is connected to the power supply (
V+
, directly to the battery or power pack) - Motor #1 may be damaged
The easiest way to check is to put motor #1 at a low power setting which is high enough for them to rotate easily, such as 30%
Note that the fault state may be true at power up, this is normal and should clear when both motors have been driven
For more details check the website at www.piborg.org/thunderborg and double check the wiring instructions
state = GetDriveFault2()
Reads the system drive fault state for motor #2,False
for no problems, True
for a fault has been detectedFaults may indicate power problems, such as under-voltage (not enough power), and may be cleared by setting a lower drive power
If a fault is persistent, it repeatably occurs when trying to control the board, this may indicate a wiring problem such as:
- The supply is not powerful enough for the motors
The board has a bare minimum requirement of 6V to operate correctly
A recommended minimum supply of 7.2V should be sufficient for smaller motors - The + and - connections for motor #2 are connected to each other
- Either + or - is connected to ground (
GND
, also known as0V
or earth) - Either + or - is connected to the power supply (
V+
, directly to the battery or power pack) - Motor #2 may be damaged
The easiest way to check is to put motor #2 at a low power setting which is high enough for them to rotate easily, such as 30%
Note that the fault state may be true at power up, this is normal and should clear when both motors have been driven
For more details check the website at www.piborg.org/thunderborg and double check the wiring instructions
voltage = GetBatteryReading()
Reads the current battery level from the main input.Returns the value as a voltage based on the 3.3 V rail as a reference.
SetBatteryMonitoringLimits(minimum, maximum)
Sets the battery monitoring limits used for setting the LED colour.The values are between 0 and 36.3 V.
The colours shown range from full red at minimum or below, yellow half way, and full green at maximum or higher.
These values are stored in EEPROM and reloaded when the board is powered.
minimum, maximum = GetBatteryMonitoringLimits()
Reads the current battery monitoring limits used for setting the LED colour.The values are between 0 and 36.3 V.
The colours shown range from full red at minimum or below, yellow half way, and full green at maximum or higher.
WriteExternalLedWord(b0, b1, b2, b3)
Low level serial LED word writing.Bytes are written MSB first starting from b0
e.g.
WriteExtermnalLedWord(255, 64, 1, 0)
will write out:
11111111 01000000 00000001 00000000
to the LEDs.
SetExternalLedColours([[r, g, b], ..., [r, g, b]])
Takes a set of RGB triples to set each LED to.Each call will set all of the LEDs
e.g.
SetExternalLedColours([[1.0, 1.0, 0.0]])
will set a single LED to full yellow.
SetExternalLedColours([[1.0, 0.0, 0.0], [0.5, 0.0, 0.0], [0.0, 0.0, 0.0]])
will set LED 1 to full red, LED 2 to half red, and LED 3 to off.
Help()
Displays the names and descriptions of the various functions and settings providedThis will be the same text as above
Utility Functions
These functions are mostly used for setting up boards, they can be used to change I²C addresses to new values to either avoid conflicts with other devices or allow multiple ThunderBorg to be used by a single Raspberry Pi.Call these functions directly from the module, e.g.
import ThunderBorg ThunderBorg.ScanForThunderBorg()
ScanForThunderBorg([busNumber])
Scans the I²C bus for a ThunderBorg boards and returns a list of all usable addressesThe
busNumber
if supplied is which I²C bus to scan, 0
for Rev 1 boards, 1
for Rev 2 boards, if not supplied the default is 1
SetNewAddress(newAddress, [oldAddress], [busNumber])
Scans the I²C bus for the first ThunderBorg and sets it to a new I²C addressIf
oldAddress
is supplied it will change the address of the board at that address rather than scanning the busThe
busNumber
if supplied is which I²C bus to scan, 0
for Rev 1 boards, 1
for Rev 2 boards, if not supplied the default is 1
Warning, this new I²C address will still be used after resetting the power on the device
Code Listings
ThunderBorg.py
#!/usr/bin/env python # coding: latin-1 """ This module is designed to communicate with the ThunderBorg Use by creating an instance of the class, call the Init function, then command as desired, e.g. import ThunderBorg TB = ThunderBorg.ThunderBorg() TB.Init() # User code here, use TB to control the board Multiple boards can be used when configured with different I²C addresses by creating multiple instances, e.g. import ThunderBorg TB1 = ThunderBorg.ThunderBorg() TB2 = ThunderBorg.ThunderBorg() TB1.i2cAddress = 0x15 TB2.i2cAddress = 0x1516 TB1.Init() TB2.Init() # User code here, use TB1 and TB2 to control each board separately For explanations of the functions available call the Help function, e.g. import ThunderBorg TB = ThunderBorg.ThunderBorg() TB.Help() See the website at www.piborg.org/thunderborg for more details """ # Import the libraries we need import io import fcntl import types import time # Constant values I2C_SLAVE = 0x0703 PWM_MAX = 255 I2C_MAX_LEN = 6 VOLTAGE_PIN_MAX = 36.3 # Maximum voltage from the analog voltage monitoring pin VOLTAGE_PIN_CORRECTION = 0.0 # Correction value for the analog voltage monitoring pin BATTERY_MIN_DEFAULT = 7.0 # Default minimum battery monitoring voltage BATTERY_MAX_DEFAULT = 35.0 # Default maximum battery monitoring voltage I2C_ID_THUNDERBORG = 0x15 COMMAND_SET_LED1 = 1 # Set the colour of the ThunderBorg LED COMMAND_GET_LED1 = 2 # Get the colour of the ThunderBorg LED COMMAND_SET_LED2 = 3 # Set the colour of the ThunderBorg Lid LED COMMAND_GET_LED2 = 4 # Get the colour of the ThunderBorg Lid LED COMMAND_SET_LEDS = 5 # Set the colour of both the LEDs COMMAND_SET_LED_BATT_MON = 6 # Set the colour of both LEDs to show the current battery level COMMAND_GET_LED_BATT_MON = 7 # Get the state of showing the current battery level via the LEDs COMMAND_SET_A_FWD = 8 # Set motor A PWM rate in a forwards direction COMMAND_SET_A_REV = 9 # Set motor A PWM rate in a reverse direction COMMAND_GET_A = 10 # Get motor A direction and PWM rate COMMAND_SET_B_FWD = 11 # Set motor B PWM rate in a forwards direction COMMAND_SET_B_REV = 12 # Set motor B PWM rate in a reverse direction COMMAND_GET_B = 13 # Get motor B direction and PWM rate COMMAND_ALL_OFF = 14 # Switch everything off COMMAND_GET_DRIVE_A_FAULT = 15 # Get the drive fault flag for motor A, indicates faults such as short-circuits and under voltage COMMAND_GET_DRIVE_B_FAULT = 16 # Get the drive fault flag for motor B, indicates faults such as short-circuits and under voltage COMMAND_SET_ALL_FWD = 17 # Set all motors PWM rate in a forwards direction COMMAND_SET_ALL_REV = 18 # Set all motors PWM rate in a reverse direction COMMAND_SET_FAILSAFE = 19 # Set the failsafe flag, turns the motors off if communication is interrupted COMMAND_GET_FAILSAFE = 20 # Get the failsafe flag COMMAND_GET_BATT_VOLT = 21 # Get the battery voltage reading COMMAND_SET_BATT_LIMITS = 22 # Set the battery monitoring limits COMMAND_GET_BATT_LIMITS = 23 # Get the battery monitoring limits COMMAND_WRITE_EXTERNAL_LED = 24 # Write a 32bit pattern out to SK9822 / APA102C COMMAND_GET_ID = 0x99 # Get the board identifier COMMAND_SET_I2C_ADD = 0xAA # Set a new I2C address COMMAND_VALUE_FWD = 1 # I2C value representing forward COMMAND_VALUE_REV = 2 # I2C value representing reverse COMMAND_VALUE_ON = 1 # I2C value representing on COMMAND_VALUE_OFF = 0 # I2C value representing off COMMAND_ANALOG_MAX = 0x3FF # Maximum value for analog readings def ScanForThunderBorg(busNumber = 1): """ ScanForThunderBorg([busNumber]) Scans the I²C bus for a ThunderBorg boards and returns a list of all usable addresses The busNumber if supplied is which I²C bus to scan, 0 for Rev 1 boards, 1 for Rev 2 boards, if not supplied the default is 1 """ found = [] print 'Scanning I²C bus #%d' % (busNumber) bus = ThunderBorg() for address in range(0x03, 0x78, 1): try: bus.InitBusOnly(busNumber, address) i2cRecv = bus.RawRead(COMMAND_GET_ID, I2C_MAX_LEN) if len(i2cRecv) == I2C_MAX_LEN: if i2cRecv[1] == I2C_ID_THUNDERBORG: print 'Found ThunderBorg at %02X' % (address) found.append(address) else: pass else: pass except KeyboardInterrupt: raise except: pass if len(found) == 0: print 'No ThunderBorg boards found, is bus #%d correct (should be 0 for Rev 1, 1 for Rev 2)' % (busNumber) elif len(found) == 1: print '1 ThunderBorg board found' else: print '%d ThunderBorg boards found' % (len(found)) return found def SetNewAddress(newAddress, oldAddress = -1, busNumber = 1): """ SetNewAddress(newAddress, [oldAddress], [busNumber]) Scans the I²C bus for the first ThunderBorg and sets it to a new I2C address If oldAddress is supplied it will change the address of the board at that address rather than scanning the bus The busNumber if supplied is which I²C bus to scan, 0 for Rev 1 boards, 1 for Rev 2 boards, if not supplied the default is 1 Warning, this new I²C address will still be used after resetting the power on the device """ if newAddress < 0x03: print 'Error, I²C addresses below 3 (0x03) are reserved, use an address between 3 (0x03) and 119 (0x77)' return elif newAddress > 0x77: print 'Error, I²C addresses above 119 (0x77) are reserved, use an address between 3 (0x03) and 119 (0x77)' return if oldAddress < 0x0: found = ScanForThunderBorg(busNumber) if len(found) < 1: print 'No ThunderBorg boards found, cannot set a new I²C address!' return else: oldAddress = found[0] print 'Changing I²C address from %02X to %02X (bus #%d)' % (oldAddress, newAddress, busNumber) bus = ThunderBorg() bus.InitBusOnly(busNumber, oldAddress) try: i2cRecv = bus.RawRead(COMMAND_GET_ID, I2C_MAX_LEN) if len(i2cRecv) == I2C_MAX_LEN: if i2cRecv[1] == I2C_ID_THUNDERBORG: foundChip = True print 'Found ThunderBorg at %02X' % (oldAddress) else: foundChip = False print 'Found a device at %02X, but it is not a ThunderBorg (ID %02X instead of %02X)' % (oldAddress, i2cRecv[1], I2C_ID_THUNDERBORG) else: foundChip = False print 'Missing ThunderBorg at %02X' % (oldAddress) except KeyboardInterrupt: raise except: foundChip = False print 'Missing ThunderBorg at %02X' % (oldAddress) if foundChip: bus.RawWrite(COMMAND_SET_I2C_ADD, [newAddress]) time.sleep(0.1) print 'Address changed to %02X, attempting to talk with the new address' % (newAddress) try: bus.InitBusOnly(busNumber, newAddress) i2cRecv = bus.RawRead(COMMAND_GET_ID, I2C_MAX_LEN) if len(i2cRecv) == I2C_MAX_LEN: if i2cRecv[1] == I2C_ID_THUNDERBORG: foundChip = True print 'Found ThunderBorg at %02X' % (newAddress) else: foundChip = False print 'Found a device at %02X, but it is not a ThunderBorg (ID %02X instead of %02X)' % (newAddress, i2cRecv[1], I2C_ID_THUNDERBORG) else: foundChip = False print 'Missing ThunderBorg at %02X' % (newAddress) except KeyboardInterrupt: raise except: foundChip = False print 'Missing ThunderBorg at %02X' % (newAddress) if foundChip: print 'New I²C address of %02X set successfully' % (newAddress) else: print 'Failed to set new I²C address...' # Class used to control ThunderBorg class ThunderBorg: """ This module is designed to communicate with the ThunderBorg busNumber I²C bus on which the ThunderBorg is attached (Rev 1 is bus 0, Rev 2 is bus 1) bus the smbus object used to talk to the I²C bus i2cAddress The I²C address of the ThunderBorg chip to control foundChip True if the ThunderBorg chip can be seen, False otherwise printFunction Function reference to call when printing text, if None "print" is used """ # Shared values used by this class busNumber = 1 # Check here for Rev 1 vs Rev 2 and select the correct bus i2cAddress = I2C_ID_THUNDERBORG # I²C address, override for a different address foundChip = False printFunction = None i2cWrite = None i2cRead = None def RawWrite(self, command, data): """ RawWrite(command, data) Sends a raw command on the I2C bus to the ThunderBorg Command codes can be found at the top of ThunderBorg.py, data is a list of 0 or more byte values Under most circumstances you should use the appropriate function instead of RawWrite """ rawOutput = chr(command) for singleByte in data: rawOutput += chr(singleByte) self.i2cWrite.write(rawOutput) def RawRead(self, command, length, retryCount = 3): """ RawRead(command, length, [retryCount]) Reads data back from the ThunderBorg after sending a GET command Command codes can be found at the top of ThunderBorg.py, length is the number of bytes to read back The function checks that the first byte read back matches the requested command If it does not it will retry the request until retryCount is exhausted (default is 3 times) Under most circumstances you should use the appropriate function instead of RawRead """ while retryCount > 0: self.RawWrite(command, []) rawReply = self.i2cRead.read(length) reply = [] for singleByte in rawReply: reply.append(ord(singleByte)) if command == reply[0]: break else: retryCount -= 1 if retryCount > 0: return reply else: raise IOError('I2C read for command %d failed' % (command)) def InitBusOnly(self, busNumber, address): """ InitBusOnly(busNumber, address) Prepare the I2C driver for talking to a ThunderBorg on the specified bus and I2C address This call does not check the board is present or working, under most circumstances use Init() instead """ self.busNumber = busNumber self.i2cAddress = address self.i2cRead = io.open("/dev/i2c-" + str(self.busNumber), "rb", buffering = 0) fcntl.ioctl(self.i2cRead, I2C_SLAVE, self.i2cAddress) self.i2cWrite = io.open("/dev/i2c-" + str(self.busNumber), "wb", buffering = 0) fcntl.ioctl(self.i2cWrite, I2C_SLAVE, self.i2cAddress) def Print(self, message): """ Print(message) Wrapper used by the ThunderBorg instance to print messages, will call printFunction if set, print otherwise """ if self.printFunction == None: print message else: self.printFunction(message) def NoPrint(self, message): """ NoPrint(message) Does nothing, intended for disabling diagnostic printout by using: TB = ThunderBorg.ThunderBorg() TB.printFunction = TB.NoPrint """ pass def Init(self, tryOtherBus = False): """ Init([tryOtherBus]) Prepare the I2C driver for talking to the ThunderBorg If tryOtherBus is True, this function will attempt to use the other bus if the ThunderBorg devices can not be found on the current busNumber This is only really useful for early Raspberry Pi models! """ self.Print('Loading ThunderBorg on bus %d, address %02X' % (self.busNumber, self.i2cAddress)) # Open the bus self.i2cRead = io.open("/dev/i2c-" + str(self.busNumber), "rb", buffering = 0) fcntl.ioctl(self.i2cRead, I2C_SLAVE, self.i2cAddress) self.i2cWrite = io.open("/dev/i2c-" + str(self.busNumber), "wb", buffering = 0) fcntl.ioctl(self.i2cWrite, I2C_SLAVE, self.i2cAddress) # Check for ThunderBorg try: i2cRecv = self.RawRead(COMMAND_GET_ID, I2C_MAX_LEN) if len(i2cRecv) == I2C_MAX_LEN: if i2cRecv[1] == I2C_ID_THUNDERBORG: self.foundChip = True self.Print('Found ThunderBorg at %02X' % (self.i2cAddress)) else: self.foundChip = False self.Print('Found a device at %02X, but it is not a ThunderBorg (ID %02X instead of %02X)' % (self.i2cAddress, i2cRecv[1], I2C_ID_THUNDERBORG)) else: self.foundChip = False self.Print('Missing ThunderBorg at %02X' % (self.i2cAddress)) except KeyboardInterrupt: raise except: self.foundChip = False self.Print('Missing ThunderBorg at %02X' % (self.i2cAddress)) # See if we are missing chips if not self.foundChip: self.Print('ThunderBorg was not found') if tryOtherBus: if self.busNumber == 1: self.busNumber = 0 else: self.busNumber = 1 self.Print('Trying bus %d instead' % (self.busNumber)) self.Init(False) else: self.Print('Are you sure your ThunderBorg is properly attached, the correct address is used, and the I2C drivers are running?') self.bus = None else: self.Print('ThunderBorg loaded on bus %d' % (self.busNumber)) def SetMotor2(self, power): """ SetMotor2(power) Sets the drive level for motor 2, from +1 to -1. e.g. SetMotor2(0) -> motor 2 is stopped SetMotor2(0.75) -> motor 2 moving forward at 75% power SetMotor2(-0.5) -> motor 2 moving reverse at 50% power SetMotor2(1) -> motor 2 moving forward at 100% power """ if power < 0: # Reverse command = COMMAND_SET_B_REV pwm = -int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX else: # Forward / stopped command = COMMAND_SET_B_FWD pwm = int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX try: self.RawWrite(command, [pwm]) except KeyboardInterrupt: raise except: self.Print('Failed sending motor 2 drive level!') def GetMotor2(self): """ power = GetMotor2() Gets the drive level for motor 2, from +1 to -1. e.g. 0 -> motor 2 is stopped 0.75 -> motor 2 moving forward at 75% power -0.5 -> motor 2 moving reverse at 50% power 1 -> motor 2 moving forward at 100% power """ try: i2cRecv = self.RawRead(COMMAND_GET_B, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading motor 2 drive level!') return power = float(i2cRecv[2]) / float(PWM_MAX) if i2cRecv[1] == COMMAND_VALUE_FWD: return power elif i2cRecv[1] == COMMAND_VALUE_REV: return -power else: return def SetMotor1(self, power): """ SetMotor1(power) Sets the drive level for motor 1, from +1 to -1. e.g. SetMotor1(0) -> motor 1 is stopped SetMotor1(0.75) -> motor 1 moving forward at 75% power SetMotor1(-0.5) -> motor 1 moving reverse at 50% power SetMotor1(1) -> motor 1 moving forward at 100% power """ if power < 0: # Reverse command = COMMAND_SET_A_REV pwm = -int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX else: # Forward / stopped command = COMMAND_SET_A_FWD pwm = int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX try: self.RawWrite(command, [pwm]) except KeyboardInterrupt: raise except: self.Print('Failed sending motor 1 drive level!') def GetMotor1(self): """ power = GetMotor1() Gets the drive level for motor 1, from +1 to -1. e.g. 0 -> motor 1 is stopped 0.75 -> motor 1 moving forward at 75% power -0.5 -> motor 1 moving reverse at 50% power 1 -> motor 1 moving forward at 100% power """ try: i2cRecv = self.RawRead(COMMAND_GET_A, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading motor 1 drive level!') return power = float(i2cRecv[2]) / float(PWM_MAX) if i2cRecv[1] == COMMAND_VALUE_FWD: return power elif i2cRecv[1] == COMMAND_VALUE_REV: return -power else: return def SetMotors(self, power): """ SetMotors(power) Sets the drive level for all motors, from +1 to -1. e.g. SetMotors(0) -> all motors are stopped SetMotors(0.75) -> all motors are moving forward at 75% power SetMotors(-0.5) -> all motors are moving reverse at 50% power SetMotors(1) -> all motors are moving forward at 100% power """ if power < 0: # Reverse command = COMMAND_SET_ALL_REV pwm = -int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX else: # Forward / stopped command = COMMAND_SET_ALL_FWD pwm = int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX try: self.RawWrite(command, [pwm]) except KeyboardInterrupt: raise except: self.Print('Failed sending all motors drive level!') def MotorsOff(self): """ MotorsOff() Sets all motors to stopped, useful when ending a program """ try: self.RawWrite(COMMAND_ALL_OFF, [0]) except KeyboardInterrupt: raise except: self.Print('Failed sending motors off command!') def SetLed1(self, r, g, b): """ SetLed1(r, g, b) Sets the current colour of the ThunderBorg LED. r, g, b may each be between 0 and 1 e.g. SetLed1(0, 0, 0) -> ThunderBorg LED off SetLed1(1, 1, 1) -> ThunderBorg LED full white SetLed1(1.0, 0.5, 0.0) -> ThunderBorg LED bright orange SetLed1(0.2, 0.0, 0.2) -> ThunderBorg LED dull purple """ levelR = max(0, min(PWM_MAX, int(r * PWM_MAX))) levelG = max(0, min(PWM_MAX, int(g * PWM_MAX))) levelB = max(0, min(PWM_MAX, int(b * PWM_MAX))) try: self.RawWrite(COMMAND_SET_LED1, [levelR, levelG, levelB]) except KeyboardInterrupt: raise except: self.Print('Failed sending colour for the ThunderBorg LED!') def GetLed1(self): """ r, g, b = GetLed1() Gets the current colour of the ThunderBorg LED. r, g, b may each be between 0 and 1 e.g. 0, 0, 0 -> ThunderBorg LED off 1, 1, 1 -> ThunderBorg LED full white 1.0, 0.5, 0.0 -> ThunderBorg LED bright orange 0.2, 0.0, 0.2 -> ThunderBorg LED dull purple """ try: i2cRecv = self.RawRead(COMMAND_GET_LED1, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading ThunderBorg LED colour!') return r = i2cRecv[1] / float(PWM_MAX) g = i2cRecv[2] / float(PWM_MAX) b = i2cRecv[3] / float(PWM_MAX) return r, g, b def SetLed2(self, r, g, b): """ SetLed2(r, g, b) Sets the current colour of the ThunderBorg Lid LED. r, g, b may each be between 0 and 1 e.g. SetLed2(0, 0, 0) -> ThunderBorg Lid LED off SetLed2(1, 1, 1) -> ThunderBorg Lid LED full white SetLed2(1.0, 0.5, 0.0) -> ThunderBorg Lid LED bright orange SetLed2(0.2, 0.0, 0.2) -> ThunderBorg Lid LED dull purple """ levelR = max(0, min(PWM_MAX, int(r * PWM_MAX))) levelG = max(0, min(PWM_MAX, int(g * PWM_MAX))) levelB = max(0, min(PWM_MAX, int(b * PWM_MAX))) try: self.RawWrite(COMMAND_SET_LED2, [levelR, levelG, levelB]) except KeyboardInterrupt: raise except: self.Print('Failed sending colour for the ThunderBorg Lid LED!') def GetLed2(self): """ r, g, b = GetLed2() Gets the current colour of the ThunderBorg Lid LED. r, g, b may each be between 0 and 1 e.g. 0, 0, 0 -> ThunderBorg Lid LED off 1, 1, 1 -> ThunderBorg Lid LED full white 1.0, 0.5, 0.0 -> ThunderBorg Lid LED bright orange 0.2, 0.0, 0.2 -> ThunderBorg Lid LED dull purple """ try: i2cRecv = self.RawRead(COMMAND_GET_LED2, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading ThunderBorg Lid LED colour!') return r = i2cRecv[1] / float(PWM_MAX) g = i2cRecv[2] / float(PWM_MAX) b = i2cRecv[3] / float(PWM_MAX) return r, g, b def SetLeds(self, r, g, b): """ SetLeds(r, g, b) Sets the current colour of both LEDs. r, g, b may each be between 0 and 1 e.g. SetLeds(0, 0, 0) -> Both LEDs off SetLeds(1, 1, 1) -> Both LEDs full white SetLeds(1.0, 0.5, 0.0) -> Both LEDs bright orange SetLeds(0.2, 0.0, 0.2) -> Both LEDs dull purple """ levelR = max(0, min(PWM_MAX, int(r * PWM_MAX))) levelG = max(0, min(PWM_MAX, int(g * PWM_MAX))) levelB = max(0, min(PWM_MAX, int(b * PWM_MAX))) try: self.RawWrite(COMMAND_SET_LEDS, [levelR, levelG, levelB]) except KeyboardInterrupt: raise except: self.Print('Failed sending colour for both LEDs!') def SetLedShowBattery(self, state): """ SetLedShowBattery(state) Sets the system to enable or disable the LEDs showing the current battery level If enabled the LED colours will be ignored and will use the current battery reading instead This sweeps from fully green for maximum voltage (35 V) to fully red for minimum voltage (7 V) """ if state: level = COMMAND_VALUE_ON else: level = COMMAND_VALUE_OFF try: self.RawWrite(COMMAND_SET_LED_BATT_MON, [level]) except KeyboardInterrupt: raise except: self.Print('Failed sending LED battery monitoring state!') def GetLedShowBattery(self): """ state = GetLedShowBattery() Gets if the system is using the LEDs to show the current battery level, true for enabled, false for disabled If enabled the LED colours will be ignored and will use the current battery reading instead This sweeps from fully green for maximum voltage (35 V) to fully red for minimum voltage (7 V) """ try: i2cRecv = self.RawRead(COMMAND_GET_LED_BATT_MON, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading LED battery monitoring state!') return if i2cRecv[1] == COMMAND_VALUE_OFF: return False else: return True def SetCommsFailsafe(self, state): """ SetCommsFailsafe(state) Sets the system to enable or disable the communications failsafe The failsafe will turn the motors off unless it is commanded at least once every 1/4 of a second Set to True to enable this failsafe, set to False to disable this failsafe The failsafe is disabled at power on """ if state: level = COMMAND_VALUE_ON else: level = COMMAND_VALUE_OFF try: self.RawWrite(COMMAND_SET_FAILSAFE, [level]) except KeyboardInterrupt: raise except: self.Print('Failed sending communications failsafe state!') def GetCommsFailsafe(self): """ state = GetCommsFailsafe() Read the current system state of the communications failsafe, True for enabled, False for disabled The failsafe will turn the motors off unless it is commanded at least once every 1/4 of a second """ try: i2cRecv = self.RawRead(COMMAND_GET_FAILSAFE, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading communications failsafe state!') return if i2cRecv[1] == COMMAND_VALUE_OFF: return False else: return True def GetDriveFault1(self): """ state = GetDriveFault1() Reads the motor drive fault state for motor #1, False for no problems, True for a fault has been detected Faults may indicate power problems, such as under-voltage (not enough power), and may be cleared by setting a lower drive power If a fault is persistent, it repeatably occurs when trying to control the board, this may indicate a wiring problem such as: * The supply is not powerful enough for the motors The board has a bare minimum requirement of 6V to operate correctly A recommended minimum supply of 7.2V should be sufficient for smaller motors * The + and - connections for motor #1 are connected to each other * Either + or - is connected to ground (GND, also known as 0V or earth) * Either + or - is connected to the power supply (V+, directly to the battery or power pack) * One of the motors may be damaged Faults will self-clear, they do not need to be reset, however some faults require both motors to be moving at less than 100% to clear The easiest way to check is to put both motors at a low power setting which is high enough for them to rotate easily, such as 30% Note that the fault state may be true at power up, this is normal and should clear when both motors have been driven For more details check the website at www.piborg.org/thunderborg and double check the wiring instructions """ try: i2cRecv = self.RawRead(COMMAND_GET_DRIVE_A_FAULT, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading the drive fault state for motor #1!') return if i2cRecv[1] == COMMAND_VALUE_OFF: return False else: return True def GetDriveFault2(self): """ state = GetDriveFault2() Reads the motor drive fault state for motor #2, False for no problems, True for a fault has been detected Faults may indicate power problems, such as under-voltage (not enough power), and may be cleared by setting a lower drive power If a fault is persistent, it repeatably occurs when trying to control the board, this may indicate a wiring problem such as: * The supply is not powerful enough for the motors The board has a bare minimum requirement of 6V to operate correctly A recommended minimum supply of 7.2V should be sufficient for smaller motors * The + and - connections for motor #2 are connected to each other * Either + or - is connected to ground (GND, also known as 0V or earth) * Either + or - is connected to the power supply (V+, directly to the battery or power pack) * One of the motors may be damaged Faults will self-clear, they do not need to be reset, however some faults require both motors to be moving at less than 100% to clear The easiest way to check is to put both motors at a low power setting which is high enough for them to rotate easily, such as 30% Note that the fault state may be true at power up, this is normal and should clear when both motors have been driven For more details check the website at www.piborg.org/thunderborg and double check the wiring instructions """ try: i2cRecv = self.RawRead(COMMAND_GET_DRIVE_B_FAULT, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading the drive fault state for motor #2!') return if i2cRecv[1] == COMMAND_VALUE_OFF: return False else: return True def GetBatteryReading(self): """ voltage = GetBatteryReading() Reads the current battery level from the main input. Returns the value as a voltage based on the 3.3 V rail as a reference. """ try: i2cRecv = self.RawRead(COMMAND_GET_BATT_VOLT, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading battery level!') return raw = (i2cRecv[1] << 8) + i2cRecv[2] level = float(raw) / float(COMMAND_ANALOG_MAX) level *= VOLTAGE_PIN_MAX return level + VOLTAGE_PIN_CORRECTION def SetBatteryMonitoringLimits(self, minimum, maximum): """ SetBatteryMonitoringLimits(minimum, maximum) Sets the battery monitoring limits used for setting the LED colour. The values are between 0 and 36.3 V. The colours shown range from full red at minimum or below, yellow half way, and full green at maximum or higher. These values are stored in EEPROM and reloaded when the board is powered. """ levelMin = minimum / float(VOLTAGE_PIN_MAX) levelMax = maximum / float(VOLTAGE_PIN_MAX) levelMin = max(0, min(0xFF, int(levelMin * 0xFF))) levelMax = max(0, min(0xFF, int(levelMax * 0xFF))) try: self.RawWrite(COMMAND_SET_BATT_LIMITS, [levelMin, levelMax]) time.sleep(0.2) # Wait for EEPROM write to complete except KeyboardInterrupt: raise except: self.Print('Failed sending battery monitoring limits!') def GetBatteryMonitoringLimits(self): """ minimum, maximum = GetBatteryMonitoringLimits() Reads the current battery monitoring limits used for setting the LED colour. The values are between 0 and 36.3 V. The colours shown range from full red at minimum or below, yellow half way, and full green at maximum or higher. """ try: i2cRecv = self.RawRead(COMMAND_GET_BATT_LIMITS, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading battery monitoring limits!') return rawMin = i2cRecv[1] rawMax = i2cRecv[2] levelMin = float(rawMin) / float(0xFF) levelMax = float(rawMax) / float(0xFF) levelMin *= VOLTAGE_PIN_MAX levelMax *= VOLTAGE_PIN_MAX return levelMin, levelMax def WriteExternalLedWord(self, b0, b1, b2, b3): """ WriteExternalLedWord(b0, b1, b2, b3) Low level serial LED word writing. Bytes are written MSB first starting from b0 e.g. WriteExtermnalLedWord(255, 64, 1, 0) will write out: 11111111 01000000 00000001 00000000 to the LEDs. """ b0 = max(0, min(PWM_MAX, int(b0))) b1 = max(0, min(PWM_MAX, int(b1))) b2 = max(0, min(PWM_MAX, int(b2))) b3 = max(0, min(PWM_MAX, int(b3))) try: self.RawWrite(COMMAND_WRITE_EXTERNAL_LED, [b0, b1, b2, b3]) except KeyboardInterrupt: raise except: self.Print('Failed sending word for the external LEDs!') def SetExternalLedColours(self, colours): """ SetExternalLedColours([[r, g, b], ..., [r, g, b]]) Takes a set of RGB triples to set each LED to. Each call will set all of the LEDs e.g. SetExternalLedColours([[1.0, 1.0, 0.0]]) will set a single LED to full yellow. SetExternalLedColours([[1.0, 0.0, 0.0], [0.5, 0.0, 0.0], [0.0, 0.0, 0.0]]) will set LED 1 to full red, LED 2 to half red, and LED 3 to off. """ # Send the start marker self.WriteExternalLedWord(0, 0, 0, 0) # Send each colour in turn for r, g, b in colours: self.WriteExternalLedWord(255, 255 * b, 255 * g, 255 * r) def Help(self): """ Help() Displays the names and descriptions of the various functions and settings provided """ funcList = [ThunderBorg.__dict__.get(a) for a in dir(ThunderBorg) if isinstance(ThunderBorg.__dict__.get(a), types.FunctionType)] funcListSorted = sorted(funcList, key = lambda x: x.func_code.co_firstlineno) print self.__doc__ print for func in funcListSorted: print '=== %s === %s' % (func.func_name, func.func_doc)
tbSequence.py
#!/usr/bin/env python # coding: Latin-1 # Simple example of a motor sequence script # Import library functions we need import ThunderBorg import time import sys # Setup the ThunderBorg global TB TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() # Set our sequence, pairs of motor 1 and motor 2 drive levels sequence = [ [+0.2, +0.2], [+0.4, +0.4], [+0.6, +0.6], [+0.8, +0.8], [+1.0, +1.0], [+0.6, +1.0], [+0.2, +1.0], [-0.2, +1.0], [-0.6, +1.0], [-1.0, +1.0], [-0.6, +0.6], [-0.2, +0.2], [+0.2, -0.2], [+0.6, -0.6], [+1.0, -1.0], [+0.6, -0.6], [+0.3, -0.3], [+0.1, -0.1], [+0.0, +0.0], ] stepDelay = 1.0 # Number of seconds between each sequence step # Loop over the sequence until the user presses CTRL+C print 'Press CTRL+C to finish' try: while True: # Go through each entry in the sequence in order for step in sequence: TB.SetMotor1(step[0]) # Set the first motor to the first value in the pair TB.SetMotor2(step[1]) # Set the second motor to the second value in the pair print '%+.1f %+.1f' % (step[0], step[1]) time.sleep(stepDelay) # Wait between steps except KeyboardInterrupt: # User has pressed CTRL+C TB.MotorsOff() # Turn both motors off print 'Done'
tbJoystick.py
#!/usr/bin/env python # coding: Latin-1 # Load library functions we want import time import os import sys import pygame import ThunderBorg # Re-direct our output to standard error, we need to ignore standard out to hide some nasty print statements from pygame sys.stdout = sys.stderr # Setup the ThunderBorg TB = ThunderBorg.ThunderBorg() #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() # Ensure the communications failsafe has been enabled! failsafe = False for i in range(5): TB.SetCommsFailsafe(True) failsafe = TB.GetCommsFailsafe() if failsafe: break if not failsafe: print 'Board %02X failed to report in failsafe mode!' % (TB.i2cAddress) sys.exit() # Settings for the joystick axisUpDown = 1 # Joystick axis to read for up / down position axisUpDownInverted = False # Set this to True if up and down appear to be swapped axisLeftRight = 2 # Joystick axis to read for left / right position axisLeftRightInverted = False # Set this to True if left and right appear to be swapped buttonSlow = 8 # Joystick button number for driving slowly whilst held (L2) slowFactor = 0.5 # Speed to slow to when the drive slowly button is held, e.g. 0.5 would be half speed buttonFastTurn = 9 # Joystick button number for turning fast (R2) interval = 0.00 # Time between updates in seconds, smaller responds faster but uses more processor time # Power settings voltageIn = 12.0 # Total battery voltage to the ThunderBorg voltageOut = 12.0 # Maximum motor voltage # Setup the power limits if voltageOut > voltageIn: maxPower = 1.0 else: maxPower = voltageOut / float(voltageIn) # Show battery monitoring settings battMin, battMax = TB.GetBatteryMonitoringLimits() battCurrent = TB.GetBatteryReading() print 'Battery monitoring settings:' print ' Minimum (red) %02.2f V' % (battMin) print ' Half-way (yellow) %02.2f V' % ((battMin + battMax) / 2) print ' Maximum (green) %02.2f V' % (battMax) print print ' Current voltage %02.2f V' % (battCurrent) print # Setup pygame and wait for the joystick to become available TB.MotorsOff() TB.SetLedShowBattery(False) TB.SetLeds(0,0,1) os.environ["SDL_VIDEODRIVER"] = "dummy" # Removes the need to have a GUI window pygame.init() #pygame.display.set_mode((1,1)) print 'Waiting for joystick... (press CTRL+C to abort)' while True: try: try: pygame.joystick.init() # Attempt to setup the joystick if pygame.joystick.get_count() < 1: # No joystick attached, set LEDs blue TB.SetLeds(0,0,1) pygame.joystick.quit() time.sleep(0.1) else: # We have a joystick, attempt to initialise it! joystick = pygame.joystick.Joystick(0) break except pygame.error: # Failed to connect to the joystick, set LEDs blue TB.SetLeds(0,0,1) pygame.joystick.quit() time.sleep(0.1) except KeyboardInterrupt: # CTRL+C exit, give up print '\nUser aborted' TB.SetCommsFailsafe(False) TB.SetLeds(0,0,0) sys.exit() print 'Joystick found' joystick.init() TB.SetLedShowBattery(True) ledBatteryMode = True try: print 'Press CTRL+C to quit' driveLeft = 0.0 driveRight = 0.0 running = True hadEvent = False upDown = 0.0 leftRight = 0.0 # Loop indefinitely while running: # Get the latest events from the system hadEvent = False events = pygame.event.get() # Handle each event individually for event in events: if event.type == pygame.QUIT: # User exit running = False elif event.type == pygame.JOYBUTTONDOWN: # A button on the joystick just got pushed down hadEvent = True elif event.type == pygame.JOYAXISMOTION: # A joystick has been moved hadEvent = True if hadEvent: # Read axis positions (-1 to +1) if axisUpDownInverted: upDown = -joystick.get_axis(axisUpDown) else: upDown = joystick.get_axis(axisUpDown) if axisLeftRightInverted: leftRight = -joystick.get_axis(axisLeftRight) else: leftRight = joystick.get_axis(axisLeftRight) # Apply steering speeds if not joystick.get_button(buttonFastTurn): leftRight *= 0.5 # Determine the drive power levels driveLeft = -upDown driveRight = -upDown if leftRight < -0.05: # Turning left driveLeft *= 1.0 + (2.0 * leftRight) elif leftRight > 0.05: # Turning right driveRight *= 1.0 - (2.0 * leftRight) # Check for button presses if joystick.get_button(buttonSlow): driveLeft *= slowFactor driveRight *= slowFactor # Set the motors to the new speeds TB.SetMotor1(driveRight * maxPower) TB.SetMotor2(driveLeft * maxPower) # Change LEDs to purple to show motor faults if TB.GetDriveFault1() or TB.GetDriveFault2(): if ledBatteryMode: TB.SetLedShowBattery(False) TB.SetLeds(1,0,1) ledBatteryMode = False else: if not ledBatteryMode: TB.SetLedShowBattery(True) ledBatteryMode = True # Wait for the interval period time.sleep(interval) # Disable all drives TB.MotorsOff() except KeyboardInterrupt: # CTRL+C exit, disable all drives TB.MotorsOff() TB.SetCommsFailsafe(False) TB.SetLedShowBattery(False) TB.SetLeds(0,0,0) print
runTbJoy.sh
#!/bin/bash /home/pi/thunderborg/tbJoystick.py > /dev/null
tbStepper.py
#!/usr/bin/env python # coding: latin-1 # Import library functions we need import ThunderBorg import time import sys # Tell the system how to drive the stepper maxPower = 1.00 # Output to drive the stepper holdingPower = 0.50 # Output to drive the stepper when holding sequence = [ # Order for stepping [+maxPower, +maxPower], [+maxPower, -maxPower], [-maxPower, -maxPower], [-maxPower, +maxPower]] sequenceHold = [ # Order for stepping at holding power [+holdingPower, +holdingPower], [+holdingPower, -holdingPower], [-holdingPower, -holdingPower], [-holdingPower, +holdingPower]] stepDelay = 0.002 # Delay between steps # Name the global variables global step global TB # Setup the ThunderBorg global TB TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() step = -1 # Function to perform a sequence of steps as fast as allowed def MoveStep(count): global step global TB # Choose direction based on sign (+/-) if count < 0: dir = -1 count *= -1 else: dir = 1 # Loop through the steps while count > 0: # Set a starting position if this is the first move if step == -1: drive = sequence[-1] TB.SetMotor1(drive[0]) TB.SetMotor2(drive[1]) step = 0 else: step += dir # Wrap step when we reach the end of the sequence if step < 0: step = len(sequence) - 1 elif step >= len(sequence): step = 0 # For this step set the required drive values if step < len(sequence): drive = sequence[step] TB.SetMotor1(drive[0]) TB.SetMotor2(drive[1]) time.sleep(stepDelay) count -= 1 # Function to switch to holding power def HoldPosition(): global step global TB # For the current step set the required holding drive values if step < len(sequence): drive = sequenceHold[step] TB.SetMotor1(drive[0]) TB.SetMotor2(drive[1]) try: # Start by turning all drives off TB.MotorsOff() # Loop forever while True: # Ask the user how many steps to move steps = input("Steps to move (-ve for reverse, 0 to quit): ") if steps == 0: # Turn off the drives TB.MotorsOff() print 'Goodbye' break else: # Move the specified amount of steps MoveStep(steps) # Turn the motor to holding power after we have finished moving HoldPosition() except KeyboardInterrupt: # CTRL+C exit, turn off the drives TB.MotorsOff() print 'Terminated'
tbStepperSeq.py
#!/usr/bin/env python # coding: latin-1 # Import library functions we need import ThunderBorg import time import sys # Tell the system how to drive the stepper sequence = [[1.0, 1.0], [1.0, -1.0], [-1.0, -1.0], [-1.0, 1.0]] # Order for stepping stepDelay = 0.002 # Delay between steps degPerStep= 1.8 # Number of degrees moved per step # Name the global variables global step global TB # Setup the ThunderBorg global TB TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() step = -1 # Function to perform a sequence of steps as fast as allowed def MoveStep(count): global step global TB # Choose direction based on sign (+/-) if count < 0: dir = -1 count *= -1 else: dir = 1 # Loop through the steps while count > 0: # Set a starting position if this is the first move if step == -1: drive = sequence[-1] TB.SetMotor1(drive[0]) TB.SetMotor2(drive[1]) step = 0 else: step += dir # Wrap step when we reach the end of the sequence if step < 0: step = len(sequence) - 1 elif step >= len(sequence): step = 0 # For this step set the required drive values if step < len(sequence): drive = sequence[step] TB.SetMotor1(drive[0]) TB.SetMotor2(drive[1]) time.sleep(stepDelay) count -= 1 # Function to move based on an angular distance def MoveDeg(angle): count = int(angle / float(degPerStep)) MoveStep(count) try: # Start by turning all drives off TB.MotorsOff() # Loop forever while True: # Rotate forward 10 turns then wait MoveDeg(360 * 10) time.sleep(1) # Rotate reverse 10 turns then wait MoveDeg(-360 * 10) time.sleep(1) # Move forward 1/4 turn 16 times with waits between for i in range(16): MoveDeg(90) time.sleep(1) except KeyboardInterrupt: # CTRL+C exit, turn off the drives TB.MotorsOff() print 'Terminated'
tbSetBatteryLimits.py
#!/usr/bin/env python # coding: latin-1 # Import the libraries we need import ThunderBorg import time import sys # Start the Robot Core TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() # Display the existing limits print battMin, battMax = TB.GetBatteryMonitoringLimits() battCurrent = TB.GetBatteryReading() print 'Current battery monitoring settings:' print ' Minimum (red) %02.2f V' % (battMin) print ' Half-way (yellow) %02.2f V' % ((battMin + battMax) / 2) print ' Maximum (green) %02.2f V' % (battMax) print print ' Current voltage %02.2f V' % (battCurrent) print # Ask the user for new limits print 'To set to defaults use 0 for both limits' battMin = input('New voltage for the minimum: ') battMax = input('New voltage for the maximum: ') # Check for reset if battMin == battMax: print 'Minimum and maximum match, setting to default limits' # Set the new values TB.SetBatteryMonitoringLimits(battMin, battMax) # Display the new limits print battMin, battMax = TB.GetBatteryMonitoringLimits() battCurrent = TB.GetBatteryReading() print 'Current battery monitoring settings:' print ' Minimum (red) %02.2f V' % (battMin) print ' Half-way (yellow) %02.2f V' % ((battMin + battMax) / 2) print ' Maximum (green) %02.2f V' % (battMax) print print ' Current voltage %02.2f V' % (battCurrent) print
tbReadBattery.py
#!/usr/bin/env python # coding: latin-1 # Import the libraries we need import ThunderBorg import time import sys # Setup the ThunderBorg global TB TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() # Set the LEDs to show the battery level TB.SetLedShowBattery(True) # Loop over the sequence until the user presses CTRL+C print 'Press CTRL+C to finish' try: while True: # Read the battery level voltageBatt = TB.GetBatteryReading() # Display the reading print 'Battery level: %0.2f V' % (voltageBatt) # Wait between readings time.sleep(0.5) except KeyboardInterrupt: # User has pressed CTRL+C print 'Done'
tbLedWave.py
#!/usr/bin/env python # coding: Latin-1 # Import library functions we need import ThunderBorg import time import sys # Setup the ThunderBorg TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() # Disable the colour by battery level TB.SetLedShowBattery(False) # Loop over the sequence until the user presses CTRL+C print 'Press CTRL+C to finish' try: while True: # Loop over a set of different hues: for hue in range(300): # Get hue into the 0 to 3 range hue /= 100.0 # Decide which two channels we are between if hue < 1.0: # Red to Green red = 1.0 - hue green = hue blue = 0.0 elif hue < 2.0: # Green to Blue red = 0.0 green = 2.0 - hue blue = hue - 1.0 else: # Blue to Red red = hue - 2.0 green = 0.0 blue = 3.0 - hue # Set the chosen colour for both LEDs TB.SetLeds(red, green, blue) # Wait a short while time.sleep(0.01) except KeyboardInterrupt: # User has pressed CTRL+C, set the LEDs to battery monitoring mode TB.SetLedShowBattery(True) print 'Done'
tbLedWave.py
#!/usr/bin/env python # coding: Latin-1 # Import library functions we need import ThunderBorg import time import sys # Settings delay = 0.01 ledCount = 16 steps = 6 # Setup the ThunderBorg TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() # Turn the on-board LED off TB.SetLedShowBattery(False) TB.SetLed1(0,0,0) # Loop over the sequence until the user presses CTRL+C print 'Press CTRL+C to finish' ledColours = [[0,0,0] for i in range(ledCount)] try: while True: # Loop over a set of different hues: for hue in range(steps * 3): # Get hue into the 0 to 3 range hue /= float(steps) # Decide which two channels we are between if hue < 1.0: # Red to Green red = 1.0 - hue green = hue blue = 0.0 elif hue < 2.0: # Green to Blue red = 0.0 green = 2.0 - hue blue = hue - 1.0 else: # Blue to Red red = hue - 2.0 green = 0.0 blue = 3.0 - hue # Add the next colour into our list and remove the oldest ledColours.insert(0, [red, green, blue]) ledColours.pop() # Set the chosen colours for all LEDs TB.SetExternalLedColours(ledColours) # Wait a short while time.sleep(delay) except KeyboardInterrupt: # User has pressed CTRL+C print 'Done'
tbGui.py
#!/usr/bin/env python # coding: latin-1 # Import library functions we need import ThunderBorg import Tkinter import sys # Setup the ThunderBorg global TB TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() # Class representing the GUI dialog class ThunderBorg_tk(Tkinter.Tk): # Constructor (called when the object is first created) def __init__(self, parent): Tkinter.Tk.__init__(self, parent) self.parent = parent self.protocol("WM_DELETE_WINDOW", self.OnExit) # Call the OnExit function when user closes the dialog self.Initialise() # Initialise the dialog def Initialise(self): global TB self.title('ThunderBorg Example GUI') # Add 2 sliders which command each motor output, plus a stop button for both motors self.grid() self.sld1 = Tkinter.Scale(self, from_ = +100, to = -100, orient = Tkinter.VERTICAL, command = self.sld1_move) self.sld1.set(0) self.sld1.grid(column = 1, row = 0, rowspan = 1, columnspan = 1, sticky = 'NSEW') self.sld2 = Tkinter.Scale(self, from_ = +100, to = -100, orient = Tkinter.VERTICAL, command = self.sld2_move) self.sld2.set(0) self.sld2.grid(column = 2, row = 0, rowspan = 1, columnspan = 1, sticky = 'NSEW') self.butOff = Tkinter.Button(self, text = 'All Off', command = self.butOff_click) self.butOff['font'] = ("Arial", 20, "bold") self.butOff.grid(column = 0, row = 1, rowspan = 1, columnspan = 4, sticky = 'NSEW') # Add a display for the battery level self.lblBattery = Tkinter.Label(self, text = 'Power input: ??.?? V', justify = Tkinter.CENTER, bg = '#000', fg = '#FFF') self.lblBattery['font'] = ('Trebuchet', 20, 'bold') self.lblBattery.grid(column = 0, row = 2, rowspan = 1, columnspan = 4, sticky = 'NSEW') # Setup the grid scaling self.grid_columnconfigure(0, weight = 1) self.grid_columnconfigure(1, weight = 1) self.grid_columnconfigure(2, weight = 1) self.grid_columnconfigure(3, weight = 1) self.grid_rowconfigure(0, weight = 4) self.grid_rowconfigure(1, weight = 1) self.grid_rowconfigure(2, weight = 1) # Set the size of the dialog self.resizable(True, True) self.geometry('500x600') # Setup the initial motor state TB.MotorsOff() # Get the monitoring limits self.battMin, self.battMax = TB.GetBatteryMonitoringLimits() # Start polling for readings self.after(1, self.Poll) # Polling function def Poll(self): battery = TB.GetBatteryReading() monitorLevel = int(((battery - self.battMin) / (self.battMax - self.battMin)) * 511) if monitorLevel < 0: # Below minimum monitorColour = '#FF0000' elif monitorLevel < 256: # Low range monitorColour = '#FF%02X00' % (monitorLevel) elif monitorLevel < 512: # High range monitorColour = '#%02XFF00' % (511 - monitorLevel) else: # Above maximum monitorColour = '#00FF00' self.lblBattery['text'] = 'Power input: %02.2f V' % (battery) self.lblBattery['fg'] = monitorColour # Re-run the poll after 100 ms self.after(100, self.Poll) # Called when the user closes the dialog def OnExit(self): global TB # Turn drives off and end the program TB.MotorsOff() self.quit() # Called when sld1 is moved def sld1_move(self, value): global TB TB.SetMotor1(float(value) / 100.0) # Called when sld2 is moved def sld2_move(self, value): global TB TB.SetMotor2(float(value) / 100.0) # Called when butOff is clicked def butOff_click(self): global TB TB.MotorsOff() self.sld1.set(0) self.sld2.set(0) # if we are the main program (python was passed a script) load the dialog automatically if __name__ == "__main__": app = ThunderBorg_tk(None) app.mainloop()
tbLedGui.py
#!/usr/bin/env python # coding: latin-1 # Import library functions we need import ThunderBorg import pygtk pygtk.require('2.0') import gtk # Setup the ThunderBorg global TB TB = ThunderBorg.ThunderBorg() # Create a new ThunderBorg object #TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address TB.Init() # Set the board up (checks the board is connected) if not TB.foundChip: boards = ThunderBorg.ScanForThunderBorg() if len(boards) == 0: print 'No ThunderBorg found, check you are attached :)' else: print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress) for board in boards: print ' %02X (%d)' % (board, board) print 'If you need to change the I²C address change the setup line so it is correct, e.g.' print 'TB.i2cAddress = 0x%02X' % (boards[0]) sys.exit() # Disable the colour by battery level TB.SetLedShowBattery(False) # Class representing the GUI dialog class ThunderBorgLed_gtk: # Constructor (called when the object is first created) def __init__(self): self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("ThunderBorg LED control") self.window.connect("destroy", self.destroy) self.vbxGrid = gtk.VBox(False, 0) self.window.add(self.vbxGrid) self.lblThunderBorgColour = gtk.Label("ThunderBorg LED colour") self.lblThunderBorgColour.set_use_markup(gtk.TRUE) self.lblThunderBorgColour.set_markup("\n<b><big><big>ThunderBorg LED colour\n</big></big></b>") self.vbxGrid.pack_start(self.lblThunderBorgColour, True, True, 0) self.lblThunderBorgColour.show() self.clrThunderBorg = gtk.ColorSelection() self.clrThunderBorg.set_has_palette(False) self.clrThunderBorg.connect("color_changed", self.ThunderBorgColourChanged) self.vbxGrid.pack_start(self.clrThunderBorg, True, True, 0) self.clrThunderBorg.show() self.lblThunderBorgLidColour = gtk.Label("Lid LED colour") self.lblThunderBorgLidColour.set_use_markup(gtk.TRUE) self.lblThunderBorgLidColour.set_markup("<b><big><big>\nLid LED colour\n</big></big></b>") self.vbxGrid.pack_start(self.lblThunderBorgLidColour, True, True, 0) self.lblThunderBorgLidColour.show() self.clrThunderBorgLid = gtk.ColorSelection() self.clrThunderBorgLid.set_has_palette(False) self.clrThunderBorgLid.connect("color_changed", self.ThunderBorgLidColourChanged) self.vbxGrid.pack_start(self.clrThunderBorgLid, True, True, 0) self.clrThunderBorgLid.show() self.vbxGrid.show() self.window.show() # Main loop def main(self): gtk.main() # ThunderBorg colour changed def ThunderBorgColourChanged(self, widget): global TB # Get current colour colour = self.clrThunderBorg.get_current_color() # Set the ThunderBorg colours TB.SetLed1(colour.red_float, colour.green_float, colour.blue_float) # ThunderBorg Lid colour changed def ThunderBorgLidColourChanged(self, widget): global TB # Get current colour colour = self.clrThunderBorgLid.get_current_color() # Set the ThunderBorg colours TB.SetLed2(colour.red_float, colour.green_float, colour.blue_float) # Exit event def destroy(self, widget, data=None): gtk.main_quit() if __name__ == "__main__": app = ThunderBorgLed_gtk() app.main()