I built a tracked robot for my thesis and got a 9.00. Last summer I tested power equipment at Siemens Energy. Now I'm doing a master's in Brașov.
radu@site
Move over the signal. Left and right change the frequency, up and down the amplitude.Drag sideways across the signal to change the frequency.
2.0 Hz1.0 V
--:----
in Brașov, where I live
6.0 kV
Siemens EnergySummer 2025. ClickTap the breaker.
Siemens Energy, intern
Tested protection relays and 6 kV and 0.4 kV switchgear.
Did some PLC and HMI programming and helped restart a SCADA server.
7f3a 9c01 e2b4CybersecurityGoIT course done, CCNA 1 in progress
Cybersecurity
Courses: the GoIT cybersecurity course, and Cisco CCNA 1, in progress.
Lab: VMware with Parrot OS, Metasploitable 2 and GNS3.
My thesis robot
Camera
Electronics
Chassis
Tracks
CameraTelemetry
Where to next
ProjectsProjects
Things I've built, biggest first.
02 · Desktop app
A password manager for your computer. It tells you how strong a password is, checks if it showed up in a known leak, keeps everything in an encrypted vault and shows your 2FA codes.
Eight parts, each built to its real size from my CAD, my photos and the thesis. Keep scrolling to go through them.
01 / 08
Tracks and motors
A T101 aluminium tank kit with ABS plastic tracks. Each track has its own 33GB-520 gear motor, 12 V and 320 rpm, fed straight from the 7.4 V pack.
No steering: one track runs faster than the other, so the rover turns on the spot.
left = y + xright = y − x
02 / 08
Frame and plate
The T101 is one bent aluminium sheet: a slotted deck between the tracks, its sides bent down to carry the wheels. The motors hang under the deck.
My 3D printed chassis sits right on top of it: the battery box stands on the deck and the electronics plate rests on the box. Pins on the plate's front and corner tabs hold the five distance sensors.
03 / 08
Raspberry Pi 5, 8 GB
The rover's computer. It runs two services that start on boot: rover.py takes the commands, reads the sensors and drives, and camera_stream.py streams both cameras.
The extra memory is for the self-docking model I want to run on it next.
04 / 08
Arduino Nano and DRV8833
Linux can't promise timing, so a Nano does the real-time part. The Pi sends it short text lines over USB at 115200 baud.
M 182 -64S 90
The DRV8833 H-bridge is glued to the front of the battery box, with a 1000 µF capacitor on its supply that catches the dips when the motors start. It switches battery power to the motors, and if the lines stop for half a second, the Nano stops them on its own.
05 / 08
Distance sensors
Five VL53L0X laser time-of-flight sensors, three at the front and two at the back. They all have the same I²C address, so a TCA9548A switches them on one at a time.
Under 400 mm the rover slows down, and at 120 mm it stops. Turning stays free, so it can always get out.
06 / 08
Two cameras
Two Waveshare IMX219 cameras, 8 MP with a 120° lens, each streaming 1920 × 1080 at 30 fps.
The front one is the driving view. The other sits on a mast and turns on an MG90S servo that the right stick controls. The tower and the camera mount are parts I designed.
07 / 08
Power
Four Samsung 21700 cells in 2S2P: 7.2 V, 10 Ah, about 72 Wh, roughly four hours of driving. Two double holders slide into the battery box from the Pi's side. There's no power switch: to turn it off, I pull the holders out.
A 2S BMS protects the cells and a USB-C module under the Nano shield charges them. Under the plate, an XL4015 makes 5.1 V for the Pi; a second one on top makes 3.3 V for the fibre converter. The motors run straight off the pack, and an INA219 measures the battery.
08 / 08
Fibre link
The backup path. A media converter turns the Pi's Ethernet into light on a single strand of single-mode fibre: 1310 nm one way, 1550 nm the other, up to 3 km.
The converter has its own 3.3 V supply, so the backup path doesn't depend on the Pi's. And light in glass can't be jammed by radio.
Ready to go
On power-up the camera runs the servo check from rover.py: full left, full right, centre. Then it drives off.
The code
Twenty times a second, rover.py takes the newest command, limits it near obstacles and turns it into two track speeds.
rover.py · control loop
defaxis_limit(dist_mm, stop=120, slow=400):
"""1.0 = free, 0.0 = blocked, linear in between."""if dist_mm is None: return1.0# a dead sensor never blocks drivingif dist_mm <= stop: return0.0if dist_mm >= slow: return1.0return (dist_mm - stop) / float(slow - stop)
# collision limit on the drive axis only; turning stays freeif y > 0: y *= axis_limit(min_valid([tof[i] for i in FRONT_IDX]))
elif y < 0: y *= axis_limit(min_valid([tof[i] for i in REAR_IDX]))
l = max(-1.0, min(1.0, y + x)) # tank mixing
r = max(-1.0, min(1.0, y - x))
nano.send(f'M {int(l * 255 * TRIM_L)} {int(r * 255 * TRIM_R)}')
The Nano only has to understand two kinds of line.
rover.ino · Arduino Nano
voidhandleLine(char *line) {
if (line[0] == 'M') { // M <left> <right>, -255..255int l = 0, r = 0;
if (sscanf(line + 1, "%d %d", &l, &r) == 2) {
setMotor(PIN_L_IN1, PIN_L_IN2, l);
setMotor(PIN_R_IN1, PIN_R_IN2, r);
}
} else if (line[0] == 'S') { // S <angle>int a = 0;
if (sscanf(line + 1, "%d", &a) == 1) steer.write(a);
}
}
Photos
The electronics plate on the T101, from the front.From above: Pi 5, Nano, XL4015, TCA9548A and the fibre converter.
Drive test videocoming soon
Straight from the CAD file
This is the real model, every part where it sits. Keep scrolling to go through them.
01 / 08
Housing
The frame and the two grips, 3D printed in black PLA. I worked out where everything goes; the CAD modelling was done together with a friend.
02 / 08
Screen
A 7-inch 800 × 480 capacitive touch screen: HDMI for the picture, USB for touch.
It runs my control interface, built in Electron: both cameras, both batteries and a map of nearby obstacles.
03 / 08
Joysticks
The left stick drives, the right one turns the rover's camera. The Pi has no analog inputs, so an ADS1015 reads all four axes over I²C.
A six-step calibration measures each stick's real forward and turn directions, so a crooked stick still drives straight.
04 / 08
Power switch
A small green MTS-102 toggle on the top edge turns the controller on and off.
Three solder lugs inside, an M6 bushing through the wall and a nut on the outside.
05 / 08
Raspberry Pi 5, 4 GB
The controller is the Wi-Fi access point the rover joins. Its Pi reads the sticks, sends every command 20 times a second over both links, and shows the two video streams.
It sits upside down under the screen. A 30 mm fan under it blows through a window in the back cover.
06 / 08
Power and sensing
A printed frame hangs off the Pi's mounting holes. It carries the 30 mm fan and the XL4015 that makes 5.1 V for the Pi, so one fan cools both.
A second XL4015 makes 3.3 V for the fibre converter. A 2S BMS protects the cells, and an INA219 measures the battery for the on-screen gauge.
07 / 08
Batteries
Four Samsung INR21700-50S cells, two in each grip, in 2S2P: 7.2 V and 10 Ah, about 72 Wh. That's roughly four hours.
08 / 08
Covers
The middle cover holds the Pi in its middle and the small boards in its two side pockets. The bottom cover closes everything up, with a window for the fan.
Switched on
The screen wakes up with my interface: the mast camera full screen, the front camera in the corner, both batteries and the obstacle map.
The code
The calibration in monitor.py: each reading is projected onto the stick's own measured directions.
monitor.py · joystick calibration
defcalibrate_xy(ax, ay, which):
c = joycal[which]
dx, dy = ax - c['cx'], ay - c['cy']
fp = dx * c['fwd_unit'][0] + dy * c['fwd_unit'][1] # onto "forward"
tp = dx * c['turn_unit'][0] + dy * c['turn_unit'][1] # onto "turn"
y = max(-1.0, min(1.0, fp / c['fwd_scale']))
x = max(-1.0, min(1.0, tp / c['turn_scale']))
return_deadzone(x, c['deadzone']), _deadzone(y, c['deadzone'])
Photos
Finished, from the front.The back, with the cooling fan.In the hand.Inside, during assembly.The interface, before a camera is connected.The system monitor for all three Pis.
01 / 07
Your thumbs
The left stick drives and the right one turns the camera. An ADS1015 converts their four analog axes into numbers the Pi can read.
02 / 07
Calibrated, then packed
The Pi corrects each reading with the stick's calibration and packs both sticks into a tiny message with a timestamp.
Every 50 ms the same packet goes out on both links: Wi-Fi on 2.4 GHz, where the controller is the access point, and a single fibre through the two media converters.
It's UDP: a late command isn't worth resending, the next one is already on its way.
04 / 07
First copy wins
Both copies reach the rover. It keeps whichever arrives first and drops the twin with the same timestamp.
05 / 07
Safety, then the motors
The distance sensors cap the speed near obstacles, tank mixing makes two track speeds, and the Nano drives the motors and the camera servo.
No command for 0.4 s and the motors stop.
06 / 07
And back
Ten times a second the rover sends its distances, battery, servo angle and failsafe state, also on both links. Both cameras stream video to the screen.
The controller always shows the freshest data.
07 / 07
Jam the radio
For the thesis demo I jammed the Wi-Fi band. The radio packets stopped, and after 1.5 s of silence the controller switched to the fibre on its own.
The data kept coming the whole time.
Controller to rover
That's the whole trip, 20 times a second while you drive.
20 Hz commands10 Hz telemetry1.5 s failover
The code
Three small pieces make the link redundant. The controller sends every command on both links.
monitor.py · controller
pkt = json.dumps({'m': 'dlr1', 't': int(time.time() * 1000),
'j1': {'x': j1['x'], 'y': j1['y'], 'ok': j1['available']},
'j2': {'x': j2['x'], 'y': j2['y'], 'ok': j2['available']}}).encode()
for ip in ROVER_IPS: # Wi-Fi first, then fibre
_cmd_sock.sendto(pkt, (ip, CMD_PORT))
The rover keeps the first copy and drops the late twin.
rover.py · rover
t = int(d.get('t', 0))
if t <= last and (last - t) < 60000: # same or older: it came by the other linkcontinue
last = t
The controller listens to both links and uses Wi-Fi while it's fresh.
monitor.py · picking the link
chosen = None
for ip in ROVER_IPS: # Wi-Fi wins while it is fresh
entry = _tel_last.get(ip)
if entry and (now - entry[0]) <= TEL_TIMEOUT: # 1.5 s
chosen = ip
break# otherwise the fibre takes over
In numbers
20 Hzcommands, on both links
10 Hztelemetry back
1.5 sof Wi-Fi silence before the fibre takes over
0.4 swithout a command and the motors stop
2 × 1080p30MJPEG video streams
3 kmreach of the fibre converters
RecordRecord
Where I've been and what I picked up along the way.
Siemens Energy
Internship, PRAM department, Petrobrazi refinery.
UniTBV · ETTI
B.Eng. Telecommunications, Brașov.
Certifications
[Add yours, each with a verification link.]
AboutAbout
Off the clock: competitive tennis, F1 and games.
Tennis
Competitive player. The serve animation lands here.