I played around for a bit with @MrXsquared's idea and there is a way to do it with the expression builder. I have tried various ways of defining the patterns and I was successful even for irregular ones, although I'm not 100% satisfied. Another issue is that for huge layers with large numbers of features, this is probably not viable to refresh at 10hz...
Simple patterns like Flashing and Occulting are easy to write, but the particular example you used, a cardinal south buoy, gets tedious real quick. I'll get to that one further below.
Bear in mind I assume the SVGs you use can have their basic parameters set inside QGIS (outline color, outline weight, fill color). Check here if you'd like to do that.
To get flashing signals, first set the stage by using Rule-based symbology on your layer. Edit the rules and go to the marker section, highlighting the symbol layer and going to the fill color expression builder. As an example, the rules for flashing and occulting are basically the same, with the alpha channel values reversed:
- Flashing:
if(second(now()) % 5,'#00FF0000','#FFFF0000')
- Occulting:
if(second(now()) % 5,'#FFFF0000','#00FF0000')
Symbols with the above rules will flash/occult red, 12 times per minute. Just edit the hex color values and modulo for the various lights in new rules.
Then go to Layer properties | Rendering and set the refresh layer at interval to 0.10, ticking the checkbox. After applying the changes, your symbols should be flashing in the map canvas.
Now for the Q(6) + LFl. 15s example, I haven't found a way to simplify the expression. I was able to make it work by explicitly specifying on which map canvas updates I wanted the alpha to be opaque or transparent. It wouldn't have made for a particularly long string if the flashing hadn't been under a second, but now I had to tell QGIS every 0.1 second if it needed to be lit, over the course of 1 minute. That's a lot of values. I just built the first 15s pattern in Excel, transposed it +15 3 more times, concatenated everything in 1 cell and copy-pasted it in my rule, which looks like this:
- Q(6) + LFl. 15s:
if(round(format_date(now(),'s.z'), 1) IN (0,0.1,0.2,0.3,0.4,0.5,1,1.1,1.2,1.3,1.4,1.5,2,2.1,2.2,2.3,2.4,2.5,3,3.1,3.2,3.3,3.4,3.5,4,4.1,4.2,4.3,4.4,4.5,5,5.1,5.2,5.3,5.4,5.5,8,8.1,8.2,8.3,8.4,8.5,8.6,8.7,8.8,8.9,9,9.1,9.2,9.3,9.4,9.5,9.6,9.7,9.8,9.9,10,10.1,10.2,10.3,10.4,10.5,15,15.1,15.2,15.3,15.4,15.5,16,16.1,16.2,16.3,16.4,16.5,17,17.1,17.2,17.3,17.4,17.5,18,18.1,18.2,18.3,18.4,18.5,19,19.1,19.2,19.3,19.4,19.5,20,20.1,20.2,20.3,20.4,20.5,23,23.1,23.2,23.3,23.4,23.5,23.6,23.7,23.8,23.9,24,24.1,24.2,24.3,24.4,24.5,24.6,24.7,24.8,24.9,25,25.1,25.2,25.3,25.4,25.5,30,30.1,30.2,30.3,30.4,30.5,31,31.1,31.2,31.3,31.4,31.5,32,32.1,32.2,32.3,32.4,32.5,33,33.1,33.2,33.3,33.4,33.5,34,34.1,34.2,34.3,34.4,34.5,35,35.1,35.2,35.3,35.4,35.5,38,38.1,38.2,38.3,38.4,38.5,38.6,38.7,38.8,38.9,39,39.1,39.2,39.3,39.4,39.5,39.6,39.7,39.8,39.9,40,40.1,40.2,40.3,40.4,40.5,45,45.1,45.2,45.3,45.4,45.5,46,46.1,46.2,46.3,46.4,46.5,47,47.1,47.2,47.3,47.4,47.5,48,48.1,48.2,48.3,48.4,48.5,49,49.1,49.2,49.3,49.4,49.5,50,50.1,50.2,50.3,50.4,50.5,53,53.1,53.2,53.3,53.4,53.5,53.6,53.7,53.8,53.9,54,54.1,54.2,54.3,54.4,54.5,54.6,54.7,54.8,54.9,55,55.1,55.2,55.3,55.4,55.5),'#FFFFFFFF','#00FFFFFF')
This is terribly ugly, I know, but it works, flashes white on the specified pattern. If you're more comfortable with Python than I am, this can probably be circumvented in the function editor. Here's what some dummy data looks like on my canvas:

data defined override
and add something like this:Case When second(now()) > 0 AND second(now()) <= 30 Then '#990000' When second(now()) > 30 AND second(now()) <= 60 Then '#00FF00' end
. I will take a look at what is possible with this...