Radu Matei

Brașov, Romania

Telecom engineer, into cybersecurity.

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.
7f3a 9c01 e2b4 CybersecurityGoIT course done, CCNA 1 in progress

My thesis robot

Camera
Electronics
Chassis
Tracks
CameraTelemetry

Radu Matei

Telecom engineer · Brașov, România

© 2026 Radu Matei · Brașov, România

Type: Instrument Sans and IBM Plex Mono (SIL OFL). 3D: three.js (MIT).

Projects

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.

ElectronNode.jsAES-256Have I Been Pwned
Code on GitHub
03 · Web app

My friends and I used it to pick where to go on a trip. Four voting rounds, live flight prices, runs on my own server.

Node.jsSQLiteDocker
Full page coming soon
04 · Arduino build

A turret that loads and fires ping pong balls. You aim it with a knob, press the button, and it fires three in a row.

ArduinoC++ServosStepper motor
Code on GitHubFull page coming soon
05 · Web app · University

Bike rental for a university campus. Three small services that work together: accounts, bikes and stations, and bookings.

JavaSpring BootMicroservicesH2
Private repo · full page coming soon
06 · Lab

Virtual machines where I practice attacking and defending: Parrot OS, Metasploitable 2 and GNS3.

VMwareParrot OSGNS3
Full page coming soon
07 · Game · in progress

The card game my friends and I play, with our own house rules, so we can play it online.

PythonJavaScriptElectron
Full page coming soon

Bachelor's thesis · 2026 · graded 9.00

DataLink Rover

Pick a part to explore.

← → to choose · Enter to open

Scroll

Taken apart

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
def axis_limit(dist_mm, stop=120, slow=400):
    """1.0 = free, 0.0 = blocked, linear in between."""
    if dist_mm is None:  return 1.0     # a dead sensor never blocks driving
    if dist_mm <= stop:  return 0.0
    if dist_mm >= slow:  return 1.0
    return (dist_mm - stop) / float(slow - stop)

# collision limit on the drive axis only; turning stays free
if 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
void handleLine(char *line) {
  if (line[0] == 'M') {                // M <left> <right>, -255..255
    int 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 rover's printed plate on the T101 chassis with the Pi 5, the Nano, the buck converter, the servo mast and the fibre converter
The electronics plate on the T101, from the front.
The rover's plate seen from above
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
def calibrate_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

The finished controller from the front: screen, two joysticks and the green cells in the grips
Finished, from the front.
The controller from the back: the screen's board, the cover with the fan and the HDMI cable
The back, with the cooling fan.
The controller held in one hand
In the hand.
The controller's Pi 5 and wiring inside the printed case
Inside, during assembly.
The control interface: live video, picture in picture, batteries and proximity map
The interface, before a camera is connected.
The system monitor window showing the controller, the rover and the dock
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.

{"m":"dlr1","t":1749…,"j1":{"x":0.42,"y":0.80},"j2":{"x":-0.1,"y":0}}

03 / 07

Two roads at once

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 link
    continue
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

02 · Desktop app · Electron

Password Helper

A password manager for your computer, and the strength checker it grew out of. The checker works right here: it runs in your browser and nothing you type is stored. Only the optional leak check below contacts anyone, and it sends just 5 characters of a hash.

Nothing to check yetType a password, or try one of these.

The rules

  • At least 15 characters
  • Hard to guess: no common words, names or patterns
  • Not in known breaches (not checked yet)

From NIST SP 800-63B-4: length and not being guessable are what count. Rules like “add a symbol” are out.

Time to crack

Offline, slow hash
—
Offline, fast hash
—
Online, no limits
—
Online, rate-limited
—

What an attacker tries first

Patterns show up here as you type.

Has it leaked?

Have I Been Pwned knows close to a billion leaked passwords. The app asks it without ever telling it the password: only the first 5 characters of the password’s SHA-1 hash leave. Back comes every leaked hash that starts the same way, and the match is made here.

SHA-1 of your password

········································

sentnever leaves this page

This pagehashes, compares
Have I Been Pwnedapi.pwnedpasswords.com

Type a password at the top first.

Only when you press the button: those 5 hash characters go to Have I Been Pwned (served through Cloudflare), which, like any website, also sees your IP address and browser headers and may log them. The password never leaves this page and nothing is stored here. Their privacy policy · this site’s privacy notice

Make a better one

Random characters, or a passphrase of random words from the EFF list. Every pick comes from the browser’s secure random generator, drawn so that no character is likelier than another.

— bits of entropy ·

The vault

One master password protects everything, and it is never stored. It goes through PBKDF2 with SHA-256, 600,000 times over, to make a 256-bit key; the key seals the vault with AES-GCM. Try the same steps on a note of your own.

Salt
IV
Locked note

Verification codes

The app keeps 2FA codes next to the password they protect. Each code comes from a secret key and the time (TOTP, RFC 6238) and changes every 30 seconds. This one is live: add the key below to any authenticator app and it will show the same code.

······

Demo key JBSW Y3DP EHPK 3PXP · SHA-1 · 6 digits · 30 s

In the app the secret never reaches the window you see; only the current code does.

Locked down

An app that holds passwords has to assume the worst about everything around it. Each layer below is there for one way things could go wrong.

Sandboxed windowThe interface runs with no Node.js and context isolation, and can only call a small, checked set of functions.
No network from the pageIts Content-Security-Policy allows no outside connections at all; fonts and icons ship with the app.
Secrets stay in the main processThe key, the decrypted vault and the 2FA secrets live there; the window gets one password at a time, hidden again after 30 s.
Clipboard wipedCopying goes straight from the main process to the clipboard, which is cleared after 30 seconds.
Auto-lockAfter inactivity, on sleep, optionally on minimise, and growing delays after wrong master passwords.
Navigation lockedNo leaving the app, no new windows, no camera or notification prompts. Links open in your browser.

In numbers

600,000PBKDF2-SHA256 rounds per unlock
5 of 40hash characters ever sent for a leak check
15+characters, the NIST minimum it checks for
EN + ROdictionaries in the strength check
7,776words to build passphrases from
30 sbefore a copied password is wiped

How it got here

  1. 1.0A PyQt6 strength checker for the desktop.
  2. ElectronRebuilt to hold passwords: an encrypted vault, a hardened window, tests.
  3. 2.0A Mac-style window, 2FA codes, imports from browsers, Recently Deleted.
  4. NextA self-hosted sync server that only ever sees encrypted data.
Code on GitHub

Record

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.]

About

Off the clock: competitive tennis, F1 and games.

Tennis

Competitive player. The serve animation lands here.

F1

Race-engineer brain, sofa on Sundays.

Games

[Your current rotation]

Contact

Radu Matei

Radu Matei

Telecom engineer · Master's student at UniTBV

Integrated Electronic and Communication Systems

Looking for an internship in cyber security

ramatei@gmail.com
LinkedIn GitHub

Hi Radu,
I'm writing about

Subject

Save my contact

Scan it with your phone's camera, or save the file.Adds me to your phone's contacts.

My name, email, this site and LinkedIn.

Scroll, or drag the card, to flip itDrag the card sideways to flip it

Privacy

What happens to your data on this site. Short version: no cookies, no tracking, nothing stored on your device.

Who is responsible

Radu Matei, Brașov, Romania, is the controller for this website. Contact for anything about your data: ramatei@gmail.com.

What this site does not do

It sets no cookies, writes nothing to your browser's storage (no localStorage, sessionStorage, IndexedDB or similar), and uses no analytics, tracking, advertising or social-media plugins. Your browser may keep its usual cache of the site's files (pages, fonts, images); that is your browser's normal behaviour, holds nothing about you, and you can clear it at any time. Fonts and code load from this site itself. What you type stays in your browser's memory while the page is open and is never sent to me. On the Password Helper page, everything typed there (the checker, generator and vault demo) is cleared when you leave that page. The terminal on the home page keeps the commands you typed on screen until you reload or close the tab.

Hosting: GitHub Pages

The site is hosted by GitHub, Inc. (USA). To deliver the pages and protect the service, GitHub records your IP address and technical request data (such as browser type and time) in server logs. Legal basis: legitimate interest in running a secure website (GDPR Art. 6(1)(f)). This is necessary to deliver and protect a public website, uses only the data any web server receives, and is what visitors expect when they open a website, so it does not override your interests. GitHub keeps these logs for as long as it says it needs them to run and secure its service and meet legal obligations, as described in its privacy statement; it does not publish a fixed period for Pages logs. I cannot see, change or shorten them. Data may be processed in the USA; GitHub covers such transfers with its own safeguards (such as the EU-US Data Privacy Framework and Standard Contractual Clauses). See the GitHub Privacy Statement.

Optional leak check: Have I Been Pwned

Only if you press “Check this password” on the Password Helper page, your browser sends the first 5 characters of the password’s SHA-1 hash to Have I Been Pwned (api.pwnedpasswords.com, run by Troy Hunt and served through Cloudflare). The password itself and its full hash never leave your browser; the request carries no cookies and no referrer. Like any website you visit, Have I Been Pwned and Cloudflare receive your IP address and normal browser headers and may keep them in operational logs. Legal basis: my legitimate interest in providing the check you asked for (GDPR Art. 6(1)(f)); it runs only on your request and sends the least data the check needs (a 5-character prefix shared by hundreds of other passwords), so it does not override your interests. Have I Been Pwned is run from Australia, which has no EU adequacy decision, and served through Cloudflare (USA), which covers transfers under the EU-US Data Privacy Framework and Standard Contractual Clauses. Because this transfer happens only when you choose to press the button after reading this, it rests on your explicit consent (GDPR Art. 49(1)(a)); the risk is that your IP address is handled outside the EU without the same legal protection. They keep their logs for the periods described in their policies: see the Have I Been Pwned privacy policy and the Cloudflare privacy policy. If you do not press the button, nothing is sent.

External links

Links to GitHub and other sites open those sites, which have their own privacy policies. Nothing is sent to them until you click.

Writing to me

On the Contact page, “Write to me” opens your own email (Gmail, Outlook or Yahoo in a new window, or the email app on your device) with my address, a subject and a first line filled in. Nothing is sent until you send it there yourself, and this site never sees what you write. “Copy address” puts my address on your clipboard only when you press it, and “Save contact” makes the contact file in your browser; neither sends anything.

Your rights

Under the GDPR you can ask for access to, correction or deletion of your data, restriction of processing and data portability, and you can object to processing based on legitimate interest. This site itself keeps no personal data, so requests about hosting or leak-check logs may also need to go to GitHub or Have I Been Pwned, but you can always write to me and I will help. You can also complain to the Romanian data protection authority, ANSPDCP (dataprotection.ro), or to the authority in your own EU country.

Changes

If the site starts processing data in a new way (for example a contact form), this notice will be updated first. Last updated: October 2026.