Category: Robotics

Further Hacking on the Shark

In my previous post, I described how the messages being passed between the joystick and motor driver of my wheelchair appeared to be a differential serial signal at 40,000 bits per second. The data appears to be call and response pairs. Messages from the joystick start with a ‘`’ character, messages from the motor controller start with an ‘a’.
Each message usually has 8 fields with a numerical value in them, and messages end with ’15’.

Tonight, I recorded the signal as I swept the joystick in a clockwise circle, starting at 12:00/full speed forward. Then I graphed the values of each of the fields.

Joystick messages

Fields one and two in messages from the joystick are the forward/backward and left/right axis of the joystick, respectively. The center position is around 128, full forward/left is 255, full backwards/right is 128, so dead center/off should be around 128 + (255-128)/2 or 191.

Field 4 is a very noisy signal between 191 and 128. It appears to peak with field 1 and bottom out with field 1, so it may be the raw magnetic joystick sensor value for that axis.

Field 8 is a very noisy signal between 128 and 255, with clear diagonal slopes at the maximum and minimum of field 1. It may also be related to the raw joystick signal.

Fields 3, 5, 6, and 7 are 191, 128, 132, and 128 all of the time. The joystick has four non-power-button buttons and a power button, and these fields are used to report their values.

Pressing the speed buttons raises and lowers field 3, from a maximum of 255 to a minimum of 128, in 4 steps. There exists a mode that changes the speed in finer steps, but as configured, this is the way my system works. When the speed is lower than half-speed, field 4 has the range of 191 to 128. When the speed is higher than half-speed, field 4’s range is 191 to 255.

Field 5 is 128 when the horn button is not pressed, and 130 when it is pressed.

Field 6 is 132 when the joystick is on and running, and raises to 140

Field 7 is 128 when the joystick is in motion mode, and 129 when it is in seat mode. In seat mode, forward and backward motion of the joystick results in up and down motion of the seat height actuator. The joystick does report left and right motion as well in chairlift mode, but it doesn’t have any effect.

Field 9 is 15, for end-of-message.

Motor driver messages

Fields 7 and 8 are mirror images of each other. Field 7’s minimum appears to be 128, and its maximum is 144. Field 8 maxes out at 236 and has a minimum of 220, so it appears to be 364-field 7. I’m not sure what this field’s value indicates, but it appears to vary with the joystick position, so it may be some sort of current monitor or motor speed monitoring signal.

Field 1 goes from 128 to 146 as the system powers on, and stays there unless the chairlift is used. It appears to transition sharply to 178 when the chairlift moves up, and back when the chairlift moves down, so it is probably connected to the magnetic reed switch that senses the chairlift position.

Field 2 drops from 192 to 128 as the system powers on, and stays there. Fields 3, 5, and 6 are 128 all the time.

Field 6 goes from 128 to 129 the message after the joystick enters chairlift mode, so it is probably an acknowledgement to the joystick that chairlift mode was entered.

Field 4 goes from 128 to 160 as the system powers on, and stays there most of the time. It returns to 128 whenever the chairlift is not moving, so it may be related to breaking or motor activation. Before field 4 goes to 160, the joystick does not send any position commands, so the fields from 1 to 6 may be state signals that the motor driver sends to the joystick.

Field 9 is usually the end-of-packet field, but there are regular messages from the motor driver that are of the form

a '146' '128' '128' '160' '128' '128' '135' '229' '26' '133' '167' '185' '15'

rather than the more usual

a '146' '128' '128' '160' '128' '128' '135' '229' '15'
.

The additional values do not appear to change. The longer messages occur every 49th message, regularly, and starting with the second message from the motor driver, so I do not think that they are glitches.

Reverse Engineering the Dynamic Controls Shark Joystick

No, not a joystick that lets you drive a shark. It’s a joystick for a mobility scooter or powerchair, as is used by people with disabilities.

There are a lot of resources on the internet that claim that the joystick uses CAN-Bus. This is because the signal is differential (there’s a “high” and “low” data line, and they are inversions of each other). However, I don’t think that this is the case. The microcontroller used in the joystick is the ATMega8, which doesn’t have a CAN controller. There are no CAN controllers or tranceivers in the joystick. On top of that, my friend Seth‘s Saleae logic analyzer can’t make head nor tails of the protocol using the normal CAN analysis modes.

So if it’s not CAN, what is it? There is an LM339 in the joystick, and another one in the motor control unit. These are quad differential comparators, and would be pretty handy if you wanted to hack your own differential serial lines, for noise immunity.

The asynchronous serial decoder of the logic analyzer did manage to decode the serial bit stream at 40000bps.

If the joystick data lines are unplugged and it is powered up, the only output is

t '129' '137' '134' '128' '133' '138' '166' '130' '196' '15'

repeated every 20ms (19.96937, actually, but who’s counting?).

If the joystick is plugged in, the startup does this:

t '129' '137' '134' '128' '133' '138' '166' '130' '196' '15'
'5' '130' '248' '15'
` '192' '191' '192' '141' '128' '140' '128' '199' '15'
a '128' '128' '128' '128' '128' '128' '128' '158' '15'
` '192' '191' '192' '141' '128' '140' '128' '199' '15'
a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
` '192' '191' '192' '141' '128' '140' '128' '199' '15'
a '128' '192' '128' '128' '128' '128' '128' '222' '15'
` '192' '191' '192' '141' '128' '140' '128' '199' '15'

I wrote a little script that parses the CSV output of the logic analyzer software and just prints a newline after each ’15’.

The main thing to notice about this is that the same initialization value is sent, but then it falls into a sort of call and response, with every other line starting with ‘a’ or ‘`’.

My guess is that these are communications passing back and forth between the joystick and the motor controller. I logged 20 seconds of the wheelchair sitting still and then counted all the unique messages that passed between the joystick and the motor controller.

Assuming that the above startup sequence is call and response, the message starting with ‘t’ and the ones starting with ‘`’ are the joystick, and the ones starting with ‘a’ are from the motor driver.

I sorted the commands out and counted the unique messages. They break down like this:


1 ` '191' '190' '192' '189' '128' '132' '128' '161' '15'
1 ` '191' '191' '192' '186' '128' '132' '128' '163' '15'
1 ` '192' '191' '192' '128' '128' '140' '128' '212' '15'
1 ` '192' '191' '192' '130' '128' '132' '128' '218' '15'
1 '5' '130' '248' '15'
1 a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
1 t '129' '137' '134' '128' '133' '138' '166' '130' '196' '15'
2 ` '191' '190' '192' '174' '128' '132' '128' '176' '15'
3 ` '191' '191' '192' '184' '128' '140' '128' '157' '15'
10 a '128' '192' '128' '128' '128' '128' '128' '222' '15'
14 ` '192' '190' '192' '135' '128' '132' '128' '214' '15'
20 a '146' '128' '128' '128' '128' '128' '128' '140' '26' '133' '167' '185' '15'
21 a '128' '128' '128' '128' '128' '128' '128' '158' '15'
22 ` '192' '191' '192' '129' '128' '132' '128' '219' '15'
34 ` '191' '191' '192' '176' '128' '132' '128' '173' '15'
37 ` '192' '191' '192' '128' '128' '132' '128' '220' '15'
40 ` '191' '190' '192' '190' '128' '132' '128' '160' '15'
57 ` '191' '190' '192' '182' '128' '132' '128' '168' '15'
70 ` '191' '191' '192' '185' '128' '132' '128' '164' '15'
139 ` '191' '190' '192' '183' '128' '132' '128' '167' '15'
290 ` '191' '191' '192' '184' '128' '132' '128' '165' '15'
294 ` '191' '190' '192' '191' '128' '132' '128' '159' '15'
954 a '146' '128' '128' '128' '128' '128' '128' '140' '15'

Most of the unique messages are from the joystick, and almost all of the messages sent back are the one at the bottom, with 954 occurances.

Let’s compare the messages from the motor controller. Bear in mind that nothing is moving at this point.


1 '5' '130' '248' '15'
1 a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
10 a '128' '192' '128' '128' '128' '128' '128' '222' '15'
20 a '146' '128' '128' '128' '128' '128' '128' '140' '26' '133' '167' '185' '15'
21 a '128' '128' '128' '128' '128' '128' '128' '158' '15'
954 a '146' '128' '128' '128' '128' '128' '128' '140' '15'

The first one is the startup acknowledgement. It never occurs again.

The next one and the one that occurs 20 times look similar, in that they both have four extra numbers in them. The first, second, and eighth values are the only ones that vary between them.

The one that occurs ten times mtches the one that occurs 21 times, except for the second and eighth fields, and doesn’t have the extended part.

The third through seventh fields are always ‘128’.

The first and second fields are always ‘128’, ‘146’, or ‘192’. The eighth field is always ‘222’, ‘140’, or ‘158’. The nineth field is either 26 or the end of transmission marker ’15’.

So what does all this mean?

I suspect that at least one of the values has something to do with the battery. The battery connects to the motor driver, but there is a battery level display on the joystick, so the motor driver must communicate some battery level information to the joystick.

The messages from the joystick have a similar pattern.


1 ` '191' '190' '192' '189' '128' '132' '128' '161' '15'
1 ` '191' '191' '192' '186' '128' '132' '128' '163' '15'
1 ` '192' '191' '192' '128' '128' '140' '128' '212' '15'
1 ` '192' '191' '192' '130' '128' '132' '128' '218' '15'
2 ` '191' '190' '192' '174' '128' '132' '128' '176' '15'
3 ` '191' '191' '192' '184' '128' '140' '128' '157' '15
14 ` '192' '190' '192' '135' '128' '132' '128' '214' '15'
22 ` '192' '191' '192' '129' '128' '132' '128' '219' '15'
34 ` '191' '191' '192' '176' '128' '132' '128' '173' '15'
37 ` '192' '191' '192' '128' '128' '132' '128' '220' '15'
40 ` '191' '190' '192' '190' '128' '132' '128' '160' '15'
57 ` '191' '190' '192' '182' '128' '132' '128' '168' '15'
70 ` '191' '191' '192' '185' '128' '132' '128' '164' '15'
139 ` '191' '190' '192' '183' '128' '132' '128' '167' '15'
290 ` '191' '191' '192' '184' '128' '132' '128' '165' '15'
294 ` '191' '190' '192' '191' '128' '132' '128' '159' '15'

The first and second values vary, but only slightly. The third value does not vary. the fourth value varies over the range 128-191. The fifth value does not vary. The sixth value varies, the seventh does not, the eighth does.

The joystick is a 4-axis device, with three buttons and a pair of binary inputs. My hope is that the values reported are something like the four joystick axes and the buttons, but the numbers don’t quite line up, as there are more buttons (5, three buttons plus two input jacks) than values left over after subtracting the joystick axes. Unless the jacks are in parallel with the buttons, that’s not what’s going on.

Recording from the data lines while the joystick is held in the forward position gives these unique lines:


1 ` '128' '191' '192' '134' '128' '132' '128' '150' '15'
1 ` '128' '192' '192' '130' '128' '132' '128' '153' '15'
1 ` '128' '192' '192' '132' '128' '132' '128' '151' '15'
1 ` '128' '193' '192' '131' '128' '132' '128' '151' '15'
1 ` '129' '191' '192' '165' '128' '132' '128' '246' '15'
1 ` '131' '191' '192' '170' '128' '132' '128' '239' '15'
1 ` '133' '191' '192' '176' '128' '132' '128' '231' '15'
1 ` '135' '190' '192' '191' '128' '132' '128' '215' '15'
1 ` '137' '190' '192' '190' '128' '132' '128' '214' '15'
1 ` '140' '190' '192' '135' '128' '132' '128' '138' '15'
1 ` '142' '191' '192' '130' '128' '132' '128' '140' '15'
1 ` '144' '191' '192' '156' '128' '132' '128' '240' '15'
1 ` '146' '191' '192' '156' '128' '132' '128' '238' '15'
1 ` '148' '191' '192' '149' '128' '132' '128' '243' '15'
1 ` '150' '191' '192' '150' '128' '132' '128' '240' '15'
1 ` '152' '191' '192' '164' '128' '132' '128' '224' '15'
1 ` '154' '191' '192' '186' '128' '132' '128' '200' '15'
1 ` '157' '191' '192' '153' '128' '132' '128' '230' '15'
1 ` '160' '191' '192' '136' '128' '132' '128' '244' '15'
1 ` '163' '191' '192' '145' '128' '132' '128' '232' '15'
1 ` '166' '191' '192' '153' '128' '132' '128' '221' '15'
1 ` '169' '191' '192' '161' '128' '132' '128' '210' '15'
1 ` '172' '191' '192' '145' '128' '132' '128' '223' '15'
1 ` '174' '191' '192' '179' '128' '132' '128' '187' '15'
1 ` '177' '191' '192' '139' '128' '132' '128' '224' '15'
1 ` '179' '191' '192' '139' '128' '132' '128' '222' '15'
1 ` '180' '191' '192' '188' '128' '132' '128' '172' '15'
1 ` '182' '191' '192' '181' '128' '132' '128' '177' '15'
1 ` '184' '191' '192' '172' '128' '132' '128' '184' '15'
1 ` '186' '191' '192' '172' '128' '132' '128' '182' '15'
1 ` '188' '191' '192' '140' '128' '132' '128' '212' '15'
1 ` '189' '191' '192' '156' '128' '132' '128' '195' '15'
1 ` '190' '191' '192' '132' '128' '132' '128' '218' '15'
1 ` '190' '191' '192' '147' '128' '132' '128' '203' '15'
1 ` '190' '191' '192' '163' '128' '132' '128' '187' '15'
1 ` '190' '191' '192' '171' '128' '132' '128' '179' '15'
1 ` '190' '191' '192' '172' '128' '132' '128' '178' '15'
1 ` '190' '191' '192' '181' '128' '132' '128' '169' '15'
1 ` '190' '191' '192' '190' '128' '132' '128' '160' '15'
1 ` '191' '191' '192' '141' '128' '132' '128' '208' '15'
1 ` '191' '191' '192' '173' '128' '132' '128' '176' '15'
1 ` '192' '191' '192' '133' '128' '140' '128' '207' '15'
1 ` '192' '191' '192' '135' '128' '132' '128' '213' '15'
1 ` '192' '191' '192' '150' '128' '132' '128' '198' '15'
1 '5' '130' '248' '15'
1 a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '134' '230' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '141' '223' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '145' '219' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '129' '142' '222' '15'
1 a '146' '128' '128' '160' '132' '128' '135' '229' '15'
1 a '146' '128' '128' '176' '128' '128' '145' '219' '15'
1 a '146' '128' '132' '160' '128' '128' '146' '218' '15'
1 a '146' '129' '128' '160' '128' '128' '141' '223' '15'
1 a '146' '132' '128' '160' '128' '128' '143' '221' '15'
1 a '146' '132' '128' '160' '130' '128' '146' '218' '15'
1 t '129' '137' '134' '128' '133' '138' '166' '130' '196' '15'
2 ` '128' '191' '192' '133' '128' '132' '128' '151' '15'
2 ` '128' '191' '192' '135' '128' '132' '128' '149' '15'
2 ` '128' '192' '192' '134' '128' '132' '128' '149' '15'
2 ` '128' '194' '192' '130' '128' '132' '128' '151' '15'
2 ` '191' '191' '192' '188' '128' '132' '128' '161' '15'
2 ` '192' '191' '192' '131' '128' '132' '128' '217' '15'
2 ` '192' '191' '192' '140' '128' '132' '128' '208' '15'
2 a '146' '128' '128' '160' '128' '128' '146' '218' '26' '133' '167' '185' '15'
3 ` '128' '191' '192' '131' '128' '132' '128' '153' '15'
3 ` '192' '191' '192' '143' '128' '132' '128' '205' '15'
3 a '146' '128' '128' '128' '128' '128' '128' '140' '26' '133' '167' '185' '15'
4 a '146' '128' '128' '160' '128' '128' '130' '234' '15'
4 a '146' '128' '128' '160' '128' '128' '131' '233' '15'
4 a '146' '128' '128' '160' '128' '128' '136' '228' '15'
5 ` '128' '191' '192' '132' '128' '132' '128' '152' '15'
5 ` '128' '192' '192' '128' '128' '132' '128' '155' '15'
5 a '146' '128' '128' '160' '128' '128' '129' '235' '15'
5 a '146' '128' '128' '160' '128' '128' '133' '231' '15'
5 a '146' '128' '128' '160' '128' '128' '134' '230' '15'
5 a '146' '128' '128' '160' '128' '128' '138' '226' '15'
6 ` '128' '193' '192' '130' '128' '132' '128' '152' '15'
6 ` '192' '191' '192' '134' '128' '132' '128' '214' '15'
6 a '146' '128' '128' '160' '128' '128' '132' '232' '15'
6 a '146' '128' '128' '160' '128' '128' '137' '227' '15'
7 a '146' '128' '128' '160' '128' '128' '139' '225' '15'
7 a '146' '128' '128' '160' '128' '128' '141' '223' '15'
8 a '146' '128' '128' '160' '128' '128' '140' '224' '15'
9 ` '128' '193' '192' '129' '128' '132' '128' '153' '15'
9 ` '128' '193' '192' '132' '128' '132' '128' '150' '15'
9 ` '192' '191' '192' '141' '128' '140' '128' '199' '15'
9 a '146' '128' '128' '160' '128' '128' '145' '219' '15'
10 a '128' '192' '128' '128' '128' '128' '128' '222' '15'
10 a '146' '128' '128' '160' '128' '128' '135' '229' '15'
10 a '146' '128' '128' '160' '128' '128' '142' '222' '15'
10 a '146' '128' '128' '160' '128' '128' '143' '221' '15'
12 ` '128' '192' '192' '135' '128' '132' '128' '148' '15'
12 ` '128' '193' '192' '133' '128' '132' '128' '149' '15'
12 a '146' '128' '128' '160' '128' '128' '128' '236' '15'
12 a '146' '128' '128' '160' '128' '128' '144' '220' '15'
15 ` '128' '193' '192' '128' '128' '132' '128' '154' '15'
20 ` '128' '193' '192' '134' '128' '132' '128' '148' '15'
21 a '128' '128' '128' '128' '128' '128' '128' '158' '15'
24 ` '128' '194' '192' '129' '128' '132' '128' '152' '15'
25 ` '192' '191' '192' '141' '128' '132' '128' '207' '15'
26 ` '192' '191' '192' '142' '128' '132' '128' '206' '15'
30 ` '192' '191' '192' '133' '128' '132' '128' '215' '15'
33 ` '192' '191' '192' '132' '128' '132' '128' '216' '15'
63 ` '128' '194' '192' '128' '128' '132' '128' '153' '15'
64 ` '128' '193' '192' '135' '128' '132' '128' '147' '15'
126 a '146' '128' '128' '128' '128' '128' '128' '140' '15'
135 a '146' '128' '128' '160' '128' '128' '146' '218' '15'

Breaking out the motor driver packets gives:


1 a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '134' '230' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '141' '223' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '145' '219' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '129' '142' '222' '15'
1 a '146' '128' '128' '160' '132' '128' '135' '229' '15'
1 a '146' '128' '128' '176' '128' '128' '145' '219' '15'
1 a '146' '128' '132' '160' '128' '128' '146' '218' '15'
1 a '146' '129' '128' '160' '128' '128' '141' '223' '15'
1 a '146' '132' '128' '160' '128' '128' '143' '221' '15'
1 a '146' '132' '128' '160' '130' '128' '146' '218' '15'
2 a '146' '128' '128' '160' '128' '128' '146' '218' '26' '133' '167' '185' '15'
3 a '146' '128' '128' '128' '128' '128' '128' '140' '26' '133' '167' '185' '15'
4 a '146' '128' '128' '160' '128' '128' '130' '234' '15'
4 a '146' '128' '128' '160' '128' '128' '131' '233' '15'
4 a '146' '128' '128' '160' '128' '128' '136' '228' '15'
5 a '146' '128' '128' '160' '128' '128' '129' '235' '15'
5 a '146' '128' '128' '160' '128' '128' '133' '231' '15'
5 a '146' '128' '128' '160' '128' '128' '134' '230' '15'
5 a '146' '128' '128' '160' '128' '128' '138' '226' '15'
6 a '146' '128' '128' '160' '128' '128' '132' '232' '15'
6 a '146' '128' '128' '160' '128' '128' '137' '227' '15'
7 a '146' '128' '128' '160' '128' '128' '139' '225' '15'
7 a '146' '128' '128' '160' '128' '128' '141' '223' '15'
8 a '146' '128' '128' '160' '128' '128' '140' '224' '15'
9 a '146' '128' '128' '160' '128' '128' '145' '219' '15'
10 a '128' '192' '128' '128' '128' '128' '128' '222' '15'
10 a '146' '128' '128' '160' '128' '128' '135' '229' '15'
10 a '146' '128' '128' '160' '128' '128' '142' '222' '15'
10 a '146' '128' '128' '160' '128' '128' '143' '221' '15'
12 a '146' '128' '128' '160' '128' '128' '128' '236' '15'
12 a '146' '128' '128' '160' '128' '128' '144' '220' '15'
21 a '128' '128' '128' '128' '128' '128' '128' '158' '15'
126 a '146' '128' '128' '128' '128' '128' '128' '140' '15'
135 a '146' '128' '128' '160' '128' '128' '146' '218' '15'

Now it seems that every value varies at least once. I’m not sure yet what to make of this, but I feel like I’m on the right track.

"Weaponized" Quadcopters

For a long time, I’ve been thinking it would be possible to strap a small explosively-formed penetrator (EFP) to a quadcopter. Then you feed the GPS coordinates of your enemy’s apartment or office into the on-board navigation system, and the quadcopter flies over to their place and fires a hypersonic slug of copper through their window.

Leaving aside the ethical concerns, there are a couple of issues with this. The main one for asymmetric warfare enthusiasts is that it destroys your quadcopter and leaves bits of it at the scene, which wastes resources and gives clues to whoever you were trying to shoot.

Then I saw this little post over at Hackaday. If you put a high wattage diode laser on a quadcopter, you can have it set fire to things. It could probably shoot through a glass window and set fire to things on the inside of the window. Once the place is nicely in flames, you just fly the ‘copter away again, leaving no trace.

Naming things and "ImportError: No module named msg"

I’m using ROS at school for a project. Part of the project is to detect someone’s hand with a camera, so I’m just looking for a patch of “skin colored*” pixels. ROS organizes software as packages, with nodes in them, and messages that the nodes use to communicate with each other.

For my system, I had a package called “hand_detector” with a source file called “hand_detector.py” and a message type called “hand”. ROS generates the messages, which I then import into my python code with the line:

from hand_detector.msg import *

This gets me the error message: “ImportError: No module named msg”

The reason for this is that python searches the same directory as the executing script for imports before it goes looking anywhere else. Since the file hand_detector.py is the executing script, and is naturally in the directory with itself, python finds it there, imports it into itself, and then tries to find a module called “msg” within hand_detector.py. There’s no .msg in there, so I get the error.

The moral of the story here is don’t name your package and the script in it the same thing. Once I converted the script to just “detector.py”, the problem went away.

*I’m somewhat concerned that I wrote a “white people detector”, as it’s really just thresholding the H part of the HSV color space and counting pixels. Other color spaces may be better for this, but this doesn’t have to be perfect. I just don’t want the robot to be a dick to black people.

That's Not Helping, Python.

[ERROR] [WallTime: 1384556730.822164] bad callback: >
Traceback (most recent call last):
File "/opt/ros/groovy/lib/python2.7/dist-packages/rospy/topics.py", line 681, in _invoke_callback
cb(msg)
File "./board_finder.py", line 81, in callback
self.finder.getBoard(data)
File "./board_finder.py", line 68, in getBoard
newImg = cv2.warpPerspective(cvImg, perMat)
TypeError: Required argument 'dsize' (pos 3) not found

[ERROR] [WallTime: 1384556763.964408] bad callback: >
Traceback (most recent call last):
File "/opt/ros/groovy/lib/python2.7/dist-packages/rospy/topics.py", line 681, in _invoke_callback
cb(msg)
File "./board_finder.py", line 81, in callback
self.finder.getBoard(data)
File "./board_finder.py", line 68, in getBoard
newImg = cv2.warpPerspective(cvImg, perMat, newImg.shape)
TypeError: function takes exactly 2 arguments (3 given)

Well which is it? Exactly two arguments, or the third positional argument is required?

The real problem is that the shape of newImg is a 3-tuple, and warpPerspective expects a 2-tuple. This StackExchange post hipped me to the bug.

A New Contender!

As I mentioned back in this entry, I don’t think the further development of the ToyBrain controller is useful. The main use case is amply solved by the Pololu Baby Orangutan. However, there is a project that I want something similar for, where the Baby Orangutan won’t work.

The idea behind the project is to make cheap swarm robots out of small robot toys, using something like the ToyBrain or Baby Oranguntan, but with wireless. The Moteino has wireless, but no motor drivers. The Baby Orangutan has motor drivers, but no wireless. The solution, then, appears to be to make a motor driver shield for the Moteino or a wireless shield for the Baby Orangutan. Of the two, I’m more inclined to do the motor shield for the Moteino, but I haven’t done any really analysis.

Potential ToyBrain Substitutes

The point of the ToyBrain project isn’t really to create the ToyBrain hardware. It’s to allow me to have a module that I can drop into children’s toys to add programability. Building the actual ToyBrain modules kind of got off into the weeds a bit, so I’ve taken a look around to see what the prices are like for modules that do more or less what I want. Here’s a comparison.

The Pololu Baby Orangutan. ($20) The Baby Orangutan has a 20MHz ATmega328P microcontroller (32k ROM/2k RAM) and a dual H-Bridge motor driver that can supply around 1A continuious power per channel.

The Cal-Eng MicroDuino ($25) Uses a 16MHz ATmega328P and a dual H-bridge that can supply 800mA per channel.

The Cal-Eng NanoDuino ($35) Essentially the same hardware as the MicroDuino, but about the same size as a penny.

The TinyCircuits TinyDuno + TinyShield Motor ($20 + $20) ATMega328. The motor shield can drive four motors, but has an upper limit of 500mA without heat-sinking, so it would need either a heat sink or for the driver channels to be paralleled. It also needs an $18 programming shield, but that’s a one-time expense. Measures about an inch square, and can be cut down to be round.

Microduino Studio Microduino ($?) Not available yet. Proposed hardware is in line with the others, but the motor driver, programming board, and CPU are all seperate, so the system cost will likely be around $40-60 per system. The proposed motor driver is the A3906, which is a dual 1A H-bridge.

Femtoduino ($12 + $5) ATMega328P microcontroller, 400mA per channel motor driver board. Cheap and very small (dime-sized), but the motor driver is weak.

Digispark + Motor Shield ($9 + $10) ATTiny85 (8K ROM/512 RAM) with 6 IO lines, 4 of which would be used to drive the 1.2A motor drivers. Smaller than a quarter, and very cheap. This probably does not have sufficent IO for my purposes, and is barely cost-competitive with the Baby Orangutan.

Looking at the available competition, if I want something better than just buying hardware, I have to essentially make the Pololu Baby Orangutan, only smaller, and with a higher output current. If there’s a 2A dual H-bridge motor driver IC available, this might be possible, but it won’t be by continuing the development path that I’m on (with a socketed, replacable motor driver). The STMicroelectronics L620x series bridges look good, but cost $7, so my $20 budget for parts would be half-blown on one IC.

For my projects that would have used the ToyBrains, I’m going to use the Baby Orangutan controllers instead. I may eventually see if I can get a small board together that uses the through-hole motor driver chips, but it’s not going to be a big priority.

Robosapien Repairs

I got a Robosapien V2 on Craigslist for very short money, and started working on turning it into a robot. This doesn’t interfere with my other projects because the stuff I’m using to robotify it is the same codebase I’ll be using for brain hacks at Defcon and and a drink dispensing robot at future festivals. But I digress.

On powering it up, the Robosapien complained about a “low brain battery” and turned itself off. When I replaced the batteries, it failed to do anything at all. I checked a few forums, and it turns out that the problem is the wire insulation failure that struck a lot of the Robosapien V2s. The problem is that the insulation on some of the wires, particularly the ones that are single strands, rather than part of multi-wire cables, is bad. It becomes brittle and crumbly, and then flakes off when the robot moves. This causes the batteries to short out, and die quickly, if you are lucky. If you are not lucky, the Robosapien melts or catches fire.

In my Robosapien, the affected wires appear to be confined to the leg wiring harness, particularly the motor wires, battery leads, and wires from the foot switch PCB to the main PCB. To fix this, I’m going to extract the wires, and replace them with un-crappy ones. It’s kind of a tedious job, but I am actually kind of looking forward to it as a way to unwind at the end of the day. Have a beer, replace a bunch of wires, chill out.

Toy Helicopter Hacking

This Christmas, my parents gave me a palm-sized toy helicopter (Avatar Z008), and my girlfriend’s parents gave me a slightly bigger toy helicopter with a video camera (Egofly Spyhawk). I also have one that I bought myself (Syma S107). All of them are gyro-stabilized, coaxial-rotor helicopters, which basically just means that they automatically don’t roll, and are easy to fly.

I had hoped to convert one of them into a tiny drone. I opened up the S107 this morning to take a look at the internal PCB. The IR signal from the remote goes to an unmarked 14-pin IC. The gyro (which I assume to be the little metal can mounted on a daugther board from the main PCB) is marked, with “C 146” and “Y2373”. One pin of the gyro is grounded, one, marked “TLY” is connected to the unmarked IC, and one goes to Vcc. That is pretty clearly power, ground, and a signal pin.

This means any control that the system is doing based on the gyro is done by that unmarked IC. Chances are that re-implementing the gyro control would be amusing, but much harder than simply adding whatever drone control I decided to add “on top of” the existing hardware.

An easier approach would be to take advantage of work that other people have done on reverse-engineering the IR protocol, and add my own control circuit that sends IR control signals to the existing board. That way, the existing board would take care of driving the motors and keeping the helicopter balanced, while my board would add autonomy.

Downward and front facing versions of SpeckleSense could be used to give the helicopter a sense of its movement in the world, which might be good enough for dead-reckoning navigation over small distances.

Drinking with Robots

I am building a drink-dispensing robot. It has 5 pumps internally, so I want to find the set of five liquids that will produce the largest variety of mixed drinks. To do this, I’m going to need a huge set of drink recipes. I got a bunch from a cocktail database that esquire maintains, but the biggest list I’m aware of is The Webtender. Unfortunately, that database isn’t in a form that permits me to make queries to find out what is the maximal set of drinks that I can make, given the constraint of 5 liquid ingredients. So instead of the web interface, I want the raw data.

This means I want to download every single drink recipe from The Webtender. The URLs there are of the form http://www.webtender.com/db/drink/6217, so the obvious thing to do would be to write up a little bash oneliner that just wgets each of the files in turn. It would probably look something like this:

for (( i=1; i <= 6217; i++ )); do wget http://www.webtender.com/db/drink/$i; done 

But it may be that The Webtender issues a 403 Forbidden error if you use wget, probably to prevent just this sort of hijinks. Unfortunately for them, wget can be configured to claim to be something else. These instructions provide the config file to cause wget to claim to be Mozilla on Windows NT, which The Webtender should have no problem with.

For my next trick, I'll use BeautifulSoup to turn the HTML files from the webtender and Esquire into a SQL database, and probably perform some form of normalization on the data, such as making all the measurements be in the same units.