Author Archives: chris

Fireworks Electric Fuses

nichrome wire around a fuse

nichrome wire around a fuse

Electric igniters are made by wrapping a segment of nichrome wire around a standard fuse segment. To make a fuse, you’ll need:

  • a segment of fuse
  • nichrome wire
  • solid core wire
  • hot glue gun
  • electrical tape
  • large gauge wire for cabling

When a current of about an amp is run through small gauge nichrome wire, it will glow orange. This is hot enough to spark ignition in a gun powder filled fuse.

We will be running the entire ignition system off of a 12V sealed lead-acid battery, the type used in UPS backup power supplies. This can provide the high current needed to sustain an amp through the nichrome for several seconds and last for the duration of a fireworks show.

Powered Nichrome

Powered Nichrome

We want approximately an amp through the nichrome, so at 12V we want about 10 ohms of resistance in the of nichrome wire (accounting for a couple ohms of resistance in the 10 feet of connection cables). This equates to about an inch of nichrome wire to wrap around the fuse. Too short and the higher current will cause it to get too hot and break before igniting the fuse. Too long and it wont reach a glowing hot temperature and fail to ignite the fuse. This may take some experimentation to see what length of nichrome will provide a steady glow for your wire gauge (be sure to test with the connection cables as they add some resistance). After wrapping the nichrome, hold it in place with hot glue and tape the ends onto the solid wire.

Fuse cable bundle

Fuse cable bundle

The ends of the nichrome are wrapped around solid core wire that are then connected to a long cable to the ignition box. For this we chose speaker wire, it comes paired and in fairly large gauge (18 AWG) for low resistance when carrying the 1 amp of current to power the nichrome. Each speaker wire is color coded for matching the connector end with the fuse end so you can trace which launch code corresponds to which fuse. 4-pin quick connect housings were used to connect to the ignition board, so two igniter cables per connection. Each half of the ignition box can support 10 igniters, so speaker cables were grouped into bundles of 10 and also color coded.  Then every igniter can be identified by a bundle color & cable color. Eg. the bottom igniter in this photo would be Grey/Black.

Perl to JavaScript Data via JSON

I have a Perl script monitoring serial ports for sensor data, that data is then to be accessed in a web page via AJAX so needs to be readable by JavaScript. I’m using JSON as the data format to transport the data.

Writing JSON in Perl

Install the JSON module with CPAN if not already installed. Then simply construct a hash table, or a hash of hashes to create a name hierarchy. Once the hash is created, use the to_json function to format the hash as a JSON string and write that string to a text file.

my $json->{"temp"} = {"f" => ($ad1 * 100.0 / 0x3ff) * 9 / 5 + 32, "c" => ($ad1 * 100.0 / 0x3ff)};
$json->{"water"} = $dio4;

$json_text = to_json $json;

open FILE, ">xbee.json";
print FILE $json_text;
close FILE;

Reading JSON in JavaScript

Now that JSON file can be accessed from a web browser to display the results. Using jQuery there is a .getJSON function that will read the file and unpack the JSON format into a JavaScript variable. Once in the variable, as data is here, the hashes written in Perl are accessed as child objects of the variable. Eg. the hash water is simple accessed as data.water in Javascript.


$.getJSON('xbee.php', function(data) {
 $('#basement_temp').html(data.temp.f.toFixed(1));
 if (data.water != '0') {
$('#basement_water').removeClass('sensor_alert').addClass('sensor_ok');
 } else {
$('#basement_water').removeClass('sensor_ok').addClass('sensor_alert');
 }
 });

Accessing JPG from IP Cameras

I use off-the-shelf IP cameras but monitor them from a central server instead of looking at each camera’s own interface. In this setup it can be better to PULL images off the camera from the server instead of being PUSHED from the the camera to the server. Pushing images requires setting up each camera with the server information and a schedule to upload pictures, which can be sparse in options depending on the camera.

By pulling images on demand, the server can control the time interval and no special setup is required for each camera other than possibly setting up a user name and password for access. Most will require Basic Auth to access in image.

Here’s the JPG access URLs for cameras I’ve tested:

Panasonic cameras (C30A, C131A, C20A, C1A)

http://your.camera.ip.addr/SnapshotJPEG?Resolution=640x480&Quality=Standard&View=Normal&Count=1

Foscam (FI8918W, FI8910W, FI8904W, FI8905W, etc)

http://your.camera.ip.addr/snapshot.cgi

Trendnet (TV-IP110W)

These cameras are not good for this purpose. Only an ActiveX viewer is available for the web or FTP for single images to a server.

Windows PCs with webcams running Yawcam.

Enabling the web access port 8888 for example.

http://your.camera.ip.addr:8888/out.jpg

Twitter Water Sensor Software

Start with the modules programmed, wired and plugged in as described in the last 2 posts. The modules should be communicating and sending the sensor samples. We now need to read the data from the serial port and write a tweet when the sensor indicates a low on DIO4. Before writing and running this code, you will need to have set up a Twitter account and set up an Application. Then follow this account from your main twitter account, enabling mobile messaging if you want to receive a text message when an event occurs.

This is developed on Ubuntu, but using the Perl scripting language that should be portable to other platforms. Use CPAN to install the dependent modules:

  • Device::SerialPort for communications with the XBee
  • Tie::CharArray for splitting a string into a character array
  • Net::Twitter  provides a class to interface with Twitter

Open and read data from the serial port. We convert the received binary string into a character array of the ordinal values for easier processing. Then copy characters read into a processing buffer so we can handle multiple reports coming in a single read or partial reads.

my $port=Device::SerialPort->new("/dev/ttyS0");</pre>
while ($timeout>0)
{
	my ($count,$saw)=$port->read(255); # will read _up to_ 255 chars
	tie my @chars, 'Tie::CharArray::Ord', $saw; 
	for ($i = 0; $i < $count; $i++) {
		push(@buffer, @chars[$i]);
		printf("0x%02x ", @buffer[$i]);
	}

We then run a series of checks on the header information to verify the packet is received correctly. First check the header character for proper framing, or throw away characters until we find the next frame header.

	# check frame header
	if (@buffer[0] != 0x7e) {
		printf("Bad Frame\n");
		shift(@buffer);
		next;
	}

Then verify all the characters expected have been received by checking the packet size.

	# check all chars are now in buffer
	$size = @buffer[2] + @buffer[1] * 0x100;
	printf("Size:     %d\n", $size);
	if ($size > scalar(@buffer) - 4) { next; }

Then verify the contents of the package by running the checksum operation.

	# check checksum
	$sum = 0;
	for ($i = 0; $i < $size ; $i++) {
		$sum += @buffer[$i + 3];
	}
	$sum = 0xff - $sum % 0x100;
	if ($sum != @buffer[$size + 3]) {
		printf("Checksum: FAIL %02x != %02x\n", $sum, @buffer[$size + 3]);
		next;
	} else {
		printf("Checksum: PASS %02x\n", $sum);
	}

If contents are complete and intact, copy message to a packet buffer.

	my @packet;
	for ($i = 0; $i < $size + 4 ; $i++) {
		push(@packet, @buffer[$i]);
	}

If an packet received is an IO sample packet, process the IO message. Then remove this packet from the input buffer.

	if (@buffer[3] == 0x92) {
		ProcessIO(\@packet);
	}
	for ($i = 0; $i < $size + 4; $i++) {shift(@buffer);} 

In the ProcessIO sub routine, extract the packet information. Fetch the device ID of the sending XBee, the 16 bit ID, packet type, and read digital IO 4.

  
sub ProcessIO {
	my @packet = @{(shift)};
	
	my $xbeeid = sprintf("%02x%02x%02x%02x%02x%02x%02x%02x", @packet[4], @packet[5], @packet[6],
		@packet[7], @packet[8], @packet[9], @packet[10], @packet[11]);
	printf("DeviceID: %s\n", $xbeeid);

	printf("Net MyID: %02x%02x\n", @packet[12], @packet[13]);
	
	printf("PackType: %02x\n", @packet[14]);
	
	my $dio4 = (@packet[20] & 0x10) != 0;

Once we have this information, we compare the device ID to verify it is the sensor device we are interested in. Then check if the DIO4 line has been brought low from previously being high since we only want to tweet the first time water is detected, not every second. This code is taken from the sample code in the Net::Twitter documentation. Net::Twitter->new creates and authenticates a connection with Twitter, then ->update posts a tweet. A time stamp is added to the message, otherwise it may be rejected as a duplicate tweet.

 	if ($xbeeid eq $xbee_basement) 	{ 		if (($dio4 == 0) && ($last_water == 1)) 		{ 			printf("DETECTED WATER!!\n"); 			my $nt = Net::Twitter->new(
				traits   => [qw/OAuth API::REST WrapError/],
				consumer_key        => $consumer_key,
				consumer_secret     => $consumer_secret,
				access_token        => $token,
				access_token_secret => $token_secret,
			);

			$now_string = strftime "%H:%M", localtime;
			$tweet = sprintf("At %s Basement sensor 4 detected water", $now_string);

			my $result = $nt->update($tweet);
			#print Dumper $result;
		}
		$last_water = $dio4;

Then just run the script from a terminal.
Perl xbee.pl

use Device::SerialPort;
use Tie::CharArray;
use Net::Twitter;
use Data::Dumper;
use POSIX qw(strftime);

 my $port=Device::SerialPort->new("/dev/ttyS0");

 my $STALL_DEFAULT=60; # how many seconds to wait for new input

 my $timeout=$STALL_DEFAULT;

my $consumer_key = "your consumer key";
my $consumer_secret = "your consumer secret";
my $token = "your token";
my $token_secret = "your token secret";
my $xbee_basement = "0013a200400a1896";

 $port->read_char_time(0);     # don't wait for each character
 $port->read_const_time(500); # 1 second per unfulfilled "read" call
 my $last_water = 1;

 sub ProcessIO {
	my @packet = @{(shift)};

	my $xbeeid = sprintf("%02x%02x%02x%02x%02x%02x%02x%02x", @packet[4], @packet[5], @packet[6],
		@packet[7], @packet[8], @packet[9], @packet[10], @packet[11]);
	printf("DeviceID: %s\n", $xbeeid);

	printf("Net MyID: %02x%02x\n", @packet[12], @packet[13]);

	printf("PackType: %02x\n", @packet[14]);

	my $dio4 = (@packet[20] & 0x10) != 0;

	if ((@packet[16] & 0x04) != 0) { printf("DIO10:   %d\n", (@packet[19] & 0x04) != 0); }
	if ((@packet[16] & 0x08) != 0) { printf("DIO11:   %d\n", (@packet[19] & 0x08) != 0); }
	if ((@packet[16] & 0x10) != 0) { printf("DIO12:   %d\n", (@packet[19] & 0x10) != 0); }
	if ((@packet[17] & 0x01) != 0) { printf("DIO0:    %d\n", (@packet[20] & 0x01) != 0); }
	if ((@packet[17] & 0x02) != 0) { printf("DIO1:    %d\n", (@packet[20] & 0x02) != 0); }
	if ((@packet[17] & 0x04) != 0) { printf("DIO2:    %d\n", (@packet[20] & 0x04) != 0); }
	if ((@packet[17] & 0x08) != 0) { printf("DIO3:    %d\n", (@packet[20] & 0x08) != 0); }
	if ((@packet[17] & 0x10) != 0) { printf("DIO4:    %d\n", $dio4); }
	if ((@packet[17] & 0x20) != 0) { printf("DIO5:    %d\n", (@packet[20] & 0x20) != 0); }
	if ((@packet[17] & 0x40) != 0) { printf("DIO6:    %d\n", (@packet[20] & 0x40) != 0); }
	if ((@packet[17] & 0x80) != 0) { printf("DIO7:    %d\n", (@packet[20] & 0x80) != 0); }
	$as = 21;
	if ((@packet[18] & 0x01) != 0) { my $val = @packet[$as++] * 0x100 + @packet[$as++]; printf("AD0:    %4x\n", $val); }
	if ((@packet[18] & 0x02) != 0) { my $val = @packet[$as++] * 0x100 + @packet[$as++]; my $cval = $val * 100.0 / 0x3ff; printf("AD1:    %4x %4.1fC %4.1fF\n", $val, $cval, $cval * 9 / 5 + 32); }
	if ((@packet[18] & 0x04) != 0) { my $val = @packet[$as++] * 0x100 + @packet[$as++]; printf("AD2:    %4x\n", $val); }
	if ((@packet[18] & 0x08) != 0) { my $val = @packet[$as++] * 0x100 + @packet[$as++]; printf("AD3:    %4x\n", $val); }
	if ((@packet[18] & 0x80) != 0) { my $val = @packet[$as++] * 0x100 + @packet[$as++]; printf("VSS:    %4x\n", $val); }

	if ($xbeeid eq $xbee_basement)
	{
		if (($dio4 == 0) && ($last_water == 1))
		{
			printf("DETECTED WATER!!\n");
			my $nt = Net::Twitter->new(
				traits   => [qw/OAuth API::REST WrapError/],
				consumer_key        => $consumer_key,
				consumer_secret     => $consumer_secret,
				access_token        => $token,
				access_token_secret => $token_secret,
			);

			$now_string = strftime "%H:%M", localtime;
			$tweet = sprintf("At %s Basement sensor 4 detected water", $now_string);

			my $result = $nt->update($tweet);
			#print Dumper $result;
		}
		$last_water = $dio4;
	}
 }

 my @buffer;
 while ($timeout>0)
 {
	my ($count,$saw)=$port->read(255); # will read _up to_ 255 chars
	tie my @chars, 'Tie::CharArray::Ord', $saw;
	for ($i = 0; $i < $count; $i++) { 		push(@buffer, @chars[$i]); 		printf("0x%02x ", @buffer[$i]); 	} 	if ($count > 0) { printf("\n"); }

	if (scalar(@buffer) == 0) { next; }

	# check frame header
	if (@buffer[0] != 0x7e) {
		printf("Bad Frame\n");
		shift(@buffer);
		next;
	}

	# check all chars are now in buffer
	$size = @buffer[2] + @buffer[1] * 0x100;
	printf("Size:     %d\n", $size);
	if ($size > scalar(@buffer) - 4) { next; }

	# check checksum
	$sum = 0;
	for ($i = 0; $i < $size ; $i++) {
		$sum += @buffer[$i + 3];
	}
	$sum = 0xff - $sum % 0x100;
	if ($sum != @buffer[$size + 3]) {
		printf("Checksum: FAIL %02x != %02x\n", $sum, @buffer[$size + 3]);
		next;
	} else {
		printf("Checksum: PASS %02x\n", $sum);
	}

	my @packet;
	for ($i = 0; $i < $size + 4 ; $i++) {
		push(@packet, @buffer[$i]);
	}

	printf("Command:  %02x\n", @buffer[3]);
	if (@buffer[3] == 0x92) {
		ProcessIO(\@packet);
	}

	# Check here to see if what we want is in the $buffer
	# say "last" if we find it
	for ($i = 0; $i < $size + 4; $i++) {shift(@buffer);}
	$timeout = $STALL_DEFAULT;
}

 if ($timeout==0) {
        die "Waited $STALL_DEFAULT seconds and never saw what I wanted\n";
 }

Twitter Water Sensor Hardware

Receiver Hardware

For the receiving module connected to the PC, we will just use the XBIB development board since it already has the serial interface that we need.

Transmitter Hardware

The end device module only needs a few pins to be wired up for power and the sensor input. For the power provide a 3.3V power supply to pin 1 and GND to pin 10.

Here a LM1117 voltage regulator was used to step down from a 5V A/C adapter. The other component shown is a temperature sensor LM35 attached to ADC1 analog input.

Makeshift Water Sensor

For the sensor, we will simply use two wires attached to bolts. One wire tied to DIO4 as the sensor input, that has an internal pull-up resistor. The second wire connected to GND. The bolts are placed on the floor near each other, but not touching. When water touches them both, it will make a circuit connection pulling the input low.