Author Archives: Phil Tucker - Page 2
If Phil's not programming, playing music, modifying a new gadget or sailing the Great Lakes then there's really no telling what he's up to. You can follow Phil on Twitter @unmaintained.
The real life battlefields that inspired Call of Duty: WWII
How close are Call of Duty: WWII‘s maps to the real life battlefields on which they were based? See for yourself in this gallery of screenshot-to-photograph comparisons.
Late last year I embarked on a tour of significant WWI and WWII battlefields in France, Belgium and the Netherlands. As it happens a number of the sites I had the honor to visit and photograph are also represented as maps in the new Activision title, including Omaha Beach and Pointe du Hoc. Activision has a blog post with some detail around the historical significance behind these real locations where fierce engagements took place during World War II.
My Grandfather landed on Juno Beach on D-Day with the No. 22 Canadian Field Ambulance Unit of the Royal Canadian Army Medical Corps. He spent the month of June 1944 in, and around, Beny-sur-Mer, in late June 1944 he was injured and evacuated. I set out on this battlefield tour with my father and one of my brothers so that we could consider the experience of my Grandfather.

Pages from my Grandfather's WWII Service Book. Note "Disembarked France 6 Jun 44," also known as D-Day, about 3/4 of the way down on right-hand page.
The tour began on November 4th, 2017, when I realized Call of Duty: WWII was to be released on November 3rd, just one day before our scheduled departure, I decided that I would bring my Xbox in the hope that my brother and I could visit historical battlefield sites during the days and play through the campaign in the evenings at the hotels. We completed the campaign on our last evening in Nijmegen, Belgium after visiting the John Frost Bridge (A Bridge Too Far), the Holten Canadian Military Cemetery and the Canadian Legion 005 earlier that day.
Over the course of the 10 day tour I took over 3,000 photos from which I was able to draw these comparison shots. Additional photos of these sites, and of the many others we visited, can be viewed here. Please contact me if you wish to inquire regarding usage rights.
The hero image is a screenshot from the Call of Duty: WWII Pointe du Hoc map split with a photograph taken at Pointe du Hoc in Normandy, France.
Special thanks to The Battlefield Tours for providing a top-notch tour experience.
All photographic images Copyright © 2017-2018 Phil Tucker. ACTIVISION, CALL OF DUTY, and CALL OF DUTY WWII are trademarks of Activision Publishing, Inc.
DIY toddler balance bike headlight
Light up your little one’s balance bike with this simple do-it-yourself headlight.
There’s not a lot to say here, glue something ferromagnetic (something magnets stick too) to the front of the balance bike and purchase a puck shaped utility light with a magnet on the back to stick to it. I used a washer and crazy glue, super simple, loads of fun. The light may slide off the washer in the event of a collision — but putting it back on is half the fun!
The balance bike my son has is the Kinderfeets TinyTot Wooden Balance Bike and Tricycle, which converts from a three-wheeled tricycle to a two-wheeled bike when they’re old enough.
Materials
Arduino Hot Wheels Drag Strip Race Track
For my son’s second birthday I decided to introduce him to die-cast cars and what better way than building a drag strip race track with an electronic start gate, timing and race results?
While Hot Wheels does offer a 6-Lane Raceway and a number of other drag strip style tracks, they didn’t have the timing and electronic start gate that I knew an Arduino and some other bits could provide, so I set to work. The basic idea is was have a servo motor open the start gate by pulling down a hinged plate with dowel stoppers to release the cars and a photocell (photoresistor/LDR) pointed at an infrared LED on each track to detect each car crossing the finish line.
Some quick searching revealed a number of projects from which I could draw insight and inspiration, most useful were apachexmd’s Hot Wheels Track Timer and Robby C’s Ultimate Guide to Building a Hot Wheels Race Track. This demystified a number of points, I hadn’t really had any fun with die-cast cars since I was kid myself, turns out there are some oddities.
Tracks
One of the oddities is that Hot Wheels doesn’t offer any long track for sale, all of their tracks are sold in segments a foot or two long that must be connected — the only exception being a vintage 50-foot Track Pak Raceway sold at some point in the 70′s or 80′s which can be found for upwards of $100 currently. Another track option is BluTrack which is sold in many lengths but only in a two-lane configuration for some reason. I decided to buy the readily available Hot Wheels segments in the form of 4 Hot Wheels Track Builder Straight Track kits.
I had some 1/2″ x 8″ pine left over from framing a door I thought I would use to mount the track on. There are a number of methods folks have employed to mount the Hot Wheels track on wood, I decided to use a method mentioned on Robby C’s page which involves using screws to secure a stack of two different sized washers to the wood at intervals.
A small washer below a larger one provides the elevation that the track needs to slide onto the larger washer. The exact washers and screws I used can be seen in the gallery below, be sure to drill pilot holes for the screws and try to keep them straight, the straighter the screws are, the better they will fit in the washer and, in turn, the smaller the bump in the track will be. This method (at least with the sizes I used) does leave ever so slight bumps in the track once it’s fitted, but I felt it was slight enough that it wouldn’t adversely affect the races. I used one of these washer guides for each section of track positioned to sit in the center of the track, except for the finish line track where I placed one at either end.
The track has 4 main pieces (though one I cut into two to make it more modular). These four pieces are a small section of track for the finish line which has the results display attached, the main straightaway, the start incline and a support for the start incline. Following some of the ideas on Robby C’s page again I decided to use hinges to connect the start incline track to its support piece as well as to the straightaway allowing for an adjustable incline. If you use hinges with removable pins it allows for easier disassembly.
I ended up cutting the straightaway into two sections, and I’ll probably do the same with the incline so that I can make the entire track shorter and thus more palatable in the living room.
For the joint between the incline and the straightaway I made sure that no track joints would run across the curve allowing for the longest pieces of track at this joint and thus a smooth transition for cars from the incline to the straightaway.
The flat joints I cut where the tracks connect, this makes disassembly a bit easier, but I’m not sure it was the right choice as some of the wood warped and having a track joint and a wood joint at the same spot may not result in the smoothest run. Experiment with the hinges and see which orientation works best for your setup.
Start Gate
The start gate on this track uses a high torque servo to actuate a hinged plate that has 4 wood dowels inserted into it. The dowels feed up through routed slots in the wood and tracks to hold the cars at the start gate.
The Hot Wheels tracks can be cut easily with a sharp exacto, though it’s difficult to get the corners of cuts perfectly smooth. The track can also be drilled, one method is to drill an appropriate diameter hole at either end of the route you wish to cut and then connect the two holes with a single exacto cut on either edge.
To start the race the servo pulls a stiff wire which is connected to the hinge plate with a small L-bracket, this pulls the dowels back down through the routed slots and releases the cars. The slots I routed in the wood were 1/2″, the track slots were a little smaller than that, somewhere between 1/4″ and 1/2″ as the dowels themselves were 1/4″ diameter and needed some clearance to move smoothly. The dowels are simply friction fit into drilled holes.
It may not be obvious from the video and photos, but I drilled a small hole in the L bracket which fit the wire much better than the large screw holes. This prevented any extra travel of the wire at the hinge plate connection when the servo actuates.
The wire I used to connect the servo to the hinge plate is a malleable steel of some sort, when my son decides to push or pull on the start gate hinge or dowels it will bend this wire rather than stress the servo, after which I inform him that is not how it’s suppose to function and bend the wire straight again. For my setup a run-of-the-mill servo wasn’t strong enough to push and pull the 1/4″ wooden hinged plate so I picked up a high torque servo. If you made a lighter, thinner hinge plate you may be alright with a weaker servo.
Robby C mentions something about this type of drop-out start gate being not as fair as a gate that lifts rather than drops, but I’m not sure why that would be — in any case, my goal was not to create a track fit for the world cup of die-cast racing. A lift gate would have required more fabrication, so I opted for the simpler drop-out.
Finish Line
The finish line employs 4 photocells, also know as photoresistors or LDR’s (Light Dependent Resistors) which sit in an enclosure above the track in holes drilled into a piece 1/2″ piece of wood. These photocells point down through the drilled holes, through larger drilled holes in the enclosure at 4 infrared LED’s embedded in the wood under the track and aligned with holes in the track above. Placing the photocells in holes in the 1/2″ piece of wood keeps them focused on their respective infrared LED underneath without picking up a lot of ambient light.
While the infrared LED’s don’t produce any light visible to the naked eye, they are detected by the photocells and when a car passes over the infrared LED, the reading on the respective photocell drops dramatically and thus the Arudino brain can determine when each car passes the finish line by waiting for the photocell reading to drop.
Embedding the photocells into a piece of wood also allowed me to align that wood with holes I drilled in the track wood for the infrared LED’s before placing the wood, along with the photocells in the enclosure. Once I’d aligned the wood, I drilled holes in it for the photocells, wired up the photocells and placed that entire piece of wood into the enclosure. The holes I drilled in the enclosure were much larger and thus I had room to align the wood with the photocells to the infrared LED’s without having to worry about aligning exactly with the holes in the enclosure.
I wrote a specific Arduino sketch in order to align the photocells which wrote the photocell values to the computer via a serial connection. Since then I have added a debug mode to the race track, pressing both the start race and track reset buttons simultaneously will engage the debug mode where the Arduino will write the photocell values out to the 7-segment race result displays so that I can check the photocell alignments.
One of the issues I ran into was that it seemed the power needed to actuate the start gate servo would draw too much from the rest of the circuit causing the infrared LED’s to dim and trigger the photocells. To get around this I added a 150ms delay to allow the circuit to recover after opening the start gate.
Circuit
None of the electronics used in the project are very complex. I collected various components mostly from RobotShop, SparkFun, Creatron and locally.
- 1x Arduino Uno
- 4x Sparkfun 7-Segment Displays (Red, Green, Blue and Yellow), How-To, *I used the SPI interface
- 4x Light Sensor Mini Photocells, How-To
- 4x Infrared LED, *For maximum brightness I used a 100ohm resistor with each LED, though your LED specs may vary.
- 1x Super High Torque Servo Motor (16kg/cm), How-To *Depending on your start gate a lower-torque servo may work fine
- 2x Momentary Pushbutton, How-To, *Any momentary buttons will work
These components and their circuits are all connected to the Arduino which runs the sketch at the bottom of this post. If I get a chance to draw one up, I’ll post a circuit diagram.
Car Storage
After purchasing a good starting set of cars to go along with the drag track I needed some way to store the hoard. It seemed likely that a generic storage container would do the trick as long as the compartment sizing matched. I wasn’t all that pleased with the purpose-build die-cast storage containers. A little bit of searching revealed this Creative Options Thread Organizer which fits 48 cars almost perfectly. It’s only half-full in the photo below, with the same number of compartments on the reverse side still to be populated.
Next Steps
While it turned into a race to finish this project in time for my son’s birthday, I had initially intended to integrate a Raspberry Pi to record race statistics and another fun function, but I’ll leave that to everyone’s imagination until I get around to implementing it. The whole setup could also use some Hot Wheels stickers.
Update
I’ve since removed a section from the start incline and the straightaway to reduce the overall size of the track in order to move it to a more permanent location (rather than across our living room). Below is a short video of my son operating the shortened track, he doesn’t seem to mind the change and still enjoys the track quite a bit.
Arduino Sketch
/* Hot Wheels Drag Strip v1.2 */ #include <Servo.h> #include <SPI.h> // Include the Arduino SPI library // SPI SS pins const int displayPinLane1 = 7; const int displayPinLane2 = 4; const int displayPinLane3 = 6; const int displayPinLane4 = 5; // Photocell pins const int photocellPinLane1 = 2; const int photocellPinLane2 = 3; const int photocellPinLane3 = 4; const int photocellPinLane4 = 5; int photocellReadingLane1; int photocellReadingLane2; int photocellReadingLane3; int photocellReadingLane4; const int photocellThreshold = 4; // Button pins const int startButtonPin = 2; const int resetButtonPin = 3; const int gateServoPin = 14; Servo gateServo; // Servo positions const int openGateServoPosition = 10; const int closeGateServoPosition = 30; int startButtonState = 0; int resetButtonState = 0; int raceStatus = 0; int lane1Status = 0; int lane2Status = 0; int lane3Status = 0; int lane4Status = 0; int currentPlace = 0; unsigned int counterLane1 = 0; // These variables will count up to 65k unsigned int counterLane2 = 0; unsigned int counterLane3 = 0; unsigned int counterLane4 = 0; String displayString; char tempString[10]; // Will be used with sprintf to create strings int animationTimer = 0; bool showPlace = true; void setup() { gateServo.attach(gateServoPin); gateServo.write(closeGateServoPosition); pinMode(startButtonPin, INPUT); pinMode(resetButtonPin, INPUT); // -------- SPI initialization pinMode(displayPinLane1, OUTPUT); digitalWrite(displayPinLane1, HIGH); pinMode(displayPinLane2, OUTPUT); digitalWrite(displayPinLane2, HIGH); pinMode(displayPinLane3, OUTPUT); digitalWrite(displayPinLane3, HIGH); pinMode(displayPinLane4, OUTPUT); digitalWrite(displayPinLane4, HIGH); // Begin SPI hardware SPI.begin(); // Slow down SPI clock SPI.setClockDivider(SPI_CLOCK_DIV64); clearDisplays(); s7sSendStringSPI(displayPinLane1, "Ln 1"); s7sSendStringSPI(displayPinLane2, "Ln 2"); s7sSendStringSPI(displayPinLane3, "Ln 3"); s7sSendStringSPI(displayPinLane4, "Ln 4"); // High brightness setBrightnessSPI(displayPinLane1, 255); setBrightnessSPI(displayPinLane2, 255); setBrightnessSPI(displayPinLane3, 255); setBrightnessSPI(displayPinLane4, 255); } void loop() { animationTimer++; if(animationTimer > 200) { if(showPlace == true) { showPlace = false; } else { showPlace = true; } animationTimer = 0; } photocellReadingLane1 = analogRead(photocellPinLane1); photocellReadingLane2 = analogRead(photocellPinLane2); photocellReadingLane3 = analogRead(photocellPinLane3); photocellReadingLane4 = analogRead(photocellPinLane4); resetButtonState = digitalRead(resetButtonPin); startButtonState = digitalRead(startButtonPin); if(resetButtonState == HIGH && startButtonState == HIGH) { // Debug mode clearDisplays(); s7sSendStringSPI(displayPinLane1, String(photocellReadingLane1)+" "); s7sSendStringSPI(displayPinLane2, String(photocellReadingLane2)+" "); s7sSendStringSPI(displayPinLane3, String(photocellReadingLane3)+" "); s7sSendStringSPI(displayPinLane4, String(photocellReadingLane4)+" "); delay(1000); } else if(resetButtonState == HIGH && raceStatus > 0) { raceStatus = 0; clearDisplays(); s7sSendStringSPI(displayPinLane1, "Ln 1"); s7sSendStringSPI(displayPinLane2, "Ln 2"); s7sSendStringSPI(displayPinLane3, "Ln 3"); s7sSendStringSPI(displayPinLane4, "Ln 4"); lane1Status = 0; lane2Status = 0; lane3Status = 0; lane4Status = 0; counterLane1 = 0; counterLane2 = 0; counterLane3 = 0; counterLane4 = 0; currentPlace = 0; gateServo.write(closeGateServoPosition); } else { if (raceStatus == 0 && startButtonState == HIGH) { // start the race gateServo.write(openGateServoPosition); // wait for servo to drop delay(150); raceStatus = 1; } else if (raceStatus == 1) { // race is running int carsAcross = 0; if (photocellReadingLane1 < photocellThreshold && lane1Status == 0) { showPlace = true; currentPlace++; carsAcross++; lane1Status = currentPlace; gateServo.write(closeGateServoPosition); } if (photocellReadingLane2 < photocellThreshold && lane2Status == 0) { showPlace = true; if(carsAcross == 0) { currentPlace++; } carsAcross++; lane2Status = currentPlace; gateServo.write(closeGateServoPosition); } if (photocellReadingLane3 < photocellThreshold && lane3Status == 0) { showPlace = true; if(carsAcross == 0) { currentPlace++; } carsAcross++; lane3Status = currentPlace; gateServo.write(closeGateServoPosition); } if (photocellReadingLane4 < photocellThreshold && lane4Status == 0) { showPlace = true; if(carsAcross == 0) { currentPlace++; } carsAcross++; lane4Status = currentPlace; gateServo.write(closeGateServoPosition); } updateDisplay(displayPinLane1, lane1Status, counterLane1); updateDisplay(displayPinLane2, lane2Status, counterLane2); updateDisplay(displayPinLane3, lane3Status, counterLane3); updateDisplay(displayPinLane4, lane4Status, counterLane4); if (lane1Status == 0) { counterLane1++; } if (lane2Status == 0) { counterLane2++; } if (lane3Status == 0) { counterLane3++; } if (lane4Status == 0) { counterLane4++; } } } delay(10); // This will make the display update at 100Hz.*/ } void clearDisplays() { clearDisplaySPI(displayPinLane1); clearDisplaySPI(displayPinLane2); clearDisplaySPI(displayPinLane3); clearDisplaySPI(displayPinLane4); } void updateDisplay(int displayPin, int laneStatus, unsigned int laneCounter) { // Magical sprintf creates a string for us to send to the s7s. // The %4d option creates a 4-digit integer. sprintf(tempString, "%4d", laneCounter); // This will output the tempString to the S7S if(laneStatus == 0 || showPlace == false) { s7sSendStringSPI(displayPin, tempString); // Print the decimal at the proper spot if (laneCounter < 10000) { setDecimalsSPI(displayPin, 0b00000010); // Sets digit 3 decimal on } else { setDecimalsSPI(displayPin, 0b00000100); } } else { setDecimalsSPI(displayPin, 0b000000000); if(laneStatus == 1) { s7sSendStringSPI(displayPin, "1st "); } else if(laneStatus == 2) { s7sSendStringSPI(displayPin, "2nd "); } else if(laneStatus == 3) { s7sSendStringSPI(displayPin, "3rd "); } else { s7sSendStringSPI(displayPin, "4th "); } } } // This custom function works somewhat like a serial.print. // You can send it an array of chars (string) and it'll print // the first 4 characters in the array. void s7sSendStringSPI(int ssPin, String toSend) { digitalWrite(ssPin, LOW); for (int i=0; i < 4; i++) { SPI.transfer(toSend[i]); } digitalWrite(ssPin, HIGH); } // Send the clear display command (0x76) // This will clear the display and reset the cursor void clearDisplaySPI(int ssPin) { digitalWrite(ssPin, LOW); SPI.transfer(0x76); // Clear display command digitalWrite(ssPin, HIGH); } // Set the displays brightness. Should receive byte with the value // to set the brightness to // dimmest------------->brightest // 0--------127--------255 void setBrightnessSPI(int ssPin, byte value) { digitalWrite(ssPin, LOW); SPI.transfer(0x7A); // Set brightness command byte SPI.transfer(value); // brightness data byte digitalWrite(ssPin, HIGH); } // Turn on any, none, or all of the decimals. // The six lowest bits in the decimals parameter sets a decimal // (or colon, or apostrophe) on or off. A 1 indicates on, 0 off. // [MSB] (X)(X)(Apos)(Colon)(Digit 4)(Digit 3)(Digit2)(Digit1) void setDecimalsSPI(int ssPin, byte decimals) { digitalWrite(ssPin, LOW); SPI.transfer(0x77); SPI.transfer(decimals); digitalWrite(ssPin, HIGH); }
DIY Copper pipe pedicel chandelier
As a soon-to-be parent with time to spare, uh, yeah, I thought I’d take it upon myself to create a chandelier to complement our nursery’s fiber optic starfield ceiling. I have already had some experience with iron pipe fixtures but wanted something a little more delicate for this one. I called the resulting chandelier a pedicel chandelier because the small frosted night light bulbs I used along with the pearl white painted copper pipe reminded me of the small fuzzy horns that a male fawn grows before antlers, also known as pedicels (they’re not actually antlers).
The basic how-to for this chandelier is to use 1/2″ copper pipe to create an organic pipe structure with candelabra lights on the end of each pipe. Since you can use any low watt candelabra bulb, also known as E12 bulbs, a variety of different looks can be achieved using the same process. This projects requires knowledge of electrical wiring and should only be undertaken by those who are familiar with light fixture wiring and the dangers involved.

Three different candelabra or E12 light bulbs.
The copper pipe is attached to a dome fixture cover with threaded adapters and electrical bushing nuts (any appropriately sized nut would work). I soldered the pipe together after it was completely cut and assembled however there are issues with soldering which I’ll get to later. Use of epoxy to join the copper hardware would probably be much easier and safer.

Metal dome fixture cover. These are also available in brass if you're planning on leaving the copper pipe unpainted.
Below you can see the basic collection of fittings for the chandelier’s horns. Once wired the candelabra socket connections are wrapped in electrical tape to insulate them from each other as well as from the pipe itself, make sure all wires are neatly trimmed and covered in tape. The electrical tape also enables the socket to fit snugly (is there an uglier word with a more desirable meaning?) in the pipes. Not snug enough? Add more tape. Too snug? Take some off. The only coupling I had left to photograph was one that I had done some test painting on, rest assured when you purchase them they are copper coloured.
Though I purchased a whole bunch of 90°/right angle fittings I did not end up using them as 45° fittings convey a much more organic feel. Even though the T junction fittings were at right angles I tried to use a 45° fitting right after to soften the look of the structure. Another tip to help the fixture look organic is to never have two lights extending at the same angle — all angles should be at least slightly different. If you want your fixture to have a more industrial or steampunk feel, more right angle fittings may work better.
I decided on 3 separate structures, or horns with 6 lights each. The total 18 7W bulbs ends up at a scant 126W, perfectly acceptable for most dimmers. This meant 5 T junctions per arm, a total of 15. I suggest mapping out your fixture before heading to the hardware store and try to purchase fittings which don’t have price tags on them, I made this mistake and spent a cozy evening with Goo Gone because of it.

Left to right, candelabra (E12) 7W night light bulb, candelabra (E12) socket, electrical tape, 3/4" to 1/2" copper coupling, 1/2" copper pipe and fittings, 1/2" to 3/4" threaded brass adapter, 1/2" electrical bushing nut

Here you can see the threaded brass adapters and electrical bushing nuts securing the fixture horns to the metal dome fixture plate. Be sure to get brass threaded adapters and not copper, copper threads are too soft to tighten properly and will bind.
Drilling the mounting holes is a breeze, as long as you have a power drill and a stepped bit (pictured below). Stepped bits can be expensive, but they’re well worth it as they make quick work of drilling holes in thin metals, holes which could otherwise prove tricky and end up messy. I highly recommend investing in one, or a set.

A few required tools, a stepped drill bit (for drilling the mounting holes in the fixture plate), a copper pipe cutting tool and a roll of electrical tape.

Candelabra (E12) replacement socket.
We don’t need all that extra jazz, just the socket. Some of these are riveted together, others have a screw. In the case of rivets, unscrew the extension, then just bend the remaining metal mounting arms until the rivet brakes — careful not to crack the bakelite socket (I don’t even know if it’s bakelite, but that’s what I like to think it is).

Candelabra socket disassembled. We only need the socket itself, shown on the right.
I started by drilling the fixture plate and mounting the threaded couplings, this gives you a good base to create your chandelier upon. I used a bolt through the center hole of the plate to attached the fixture securely to a camera tripod while I worked on it. Things can fall apart quite easily if you’re not paying attention or one structure is heavier than another, you can use some twine, elastics, or whatever works really to support various pieces while you create.
If it becomes a pain to keep it together while you work than you can affix joints that you are confident will not change. I ended up soldering the main support pipe line of each of the three horns and I kept any extending pipe structures separate to make threading the wiring easier. If you’re soldering you want to do as much soldering as possible before starting any wiring — soldering with wire inside can melt the insulation and short out the entire chandelier, this is one of the soldering issues I mentioned earlier.
Once everything is cut, perhaps some has been soldered or epoxied, it’s time to start running wire. I decided to run three main wiring lines, one for each horn, any pipes extending off the main horn would then be spliced into the main line. Based on the bulbs you want to use, and how many, you’ll have to determine the max amperage and thus the proper gauge of wire to use, if you’re soldering you’ll want to get wire with as much insulation as possible.

A bent nail (left) is great for fishing a wire line out of a T junction. By attaching a nut to the end of a wire (right) you can use a magnet to guide the wire through complex structures.

Chandelier in progress.
When running wire ensure that you leave at least 2-3 inches extending out of each pipe and when splicing be sure to maintain the proper polarity — striped or two different coloured wires helps with this. Once wired it’s time to epoxy or solder any final joints, if you’re soldering you run the risk of melting the wiring insulation, to avoid this remember that these joints do not have to be waterproof, just a small amount of solder will hold the joint. Be sure to use flux and have a spray bottle with water ready, quickly get the pipe up to heat, apply the solder and as soon as it’s solidified use the spray bottle to cool down the pipe to help prevent any damage to the internal wiring.
Once you’re done soldering you can test the wiring for faults with a multimeter, check for faults between the two polarity wires and from each wire to the chandelier structure. If there are any faults you’ll have to open things back up, you can re-flow solder to separate parts, not sure what you’d do if you used epoxy and then found a fault :O
To attach the sockets simply strip and screw the wire to each pole on the socket and then wrap them in electrical tape. While not essential, it’s good practice to maintain the same polarity across all sockets, to do this keep track of which wire you’re attaching to the brass pole and which to the silver pole on each socket. Once wrapped in electrical tape you can push them into the pipe ends.

Bulb socket and pipe fitting after white rustoleum and pearlescent acrylic.
Once you’ve got all the sockets affixed you should test for faults again with your multimeter. If no faults are found between the wires or to the structure you can test the fixture by attaching a 120 volt wall plug to the end and giving it a go. If all goes well you can move onto finishing.
I thought, while great looking and oozing steampunk, that the bare copper was a little too hardcore for my infant son’s nursery so I decided to paint the fixture white and use a pearlescent acrylic on the pipes. The sky’s the limit here, but copper is expensive so, if you can, show it off! If you do paint be sure to stuff some paper towel or toilet paper into each of the sockets so that the bulb leads don’t get paint on them.
Once it’s dried you can test it again with the multimeter (can’t be too careful) and then hang it. I ended up using a dimmer with a remote on this fixture so that my wife and I could adjust the lights while minding to the baby and it works like a charm. Good luck! I’m happy to answer any questions in the comments.