diff --git a/lisainstrument/instrument.py b/lisainstrument/instrument.py
old mode 100644
new mode 100755
index 1062db019c1f58802a8099bd4abb1acd737119f0..ee86523f4ae6475e32bbcffb0b6d33aec6c92f45
--- a/lisainstrument/instrument.py
+++ b/lisainstrument/instrument.py
@@ -74,7 +74,7 @@ class Instrument:
                  # Artifacts
                  glitches=None,
                  # Laser locking and frequency plan
-                 lock='N1-12', offsets_freqs='default',
+                 lock='N1-12', fplan='static',
                  # Laser sources
                  laser_asds=28.2, central_freq=2.816E14,
                  # Laser phase modulation
@@ -124,9 +124,8 @@ class Instrument:
             glitches: path to glitch file, or dictionary of glitch signals per injection point
             lock: pre-defined laser locking configuration ('N1-12' non-swap N1 with 12 primary laser),
                 or 'six' for 6 lasers locked on cavities, or a dictionary of locking conditions
-            offsets_freqs: dictionary of laser frequency offsets [Hz], or 'default'
-            # fplan: path to frequency-plan file, or dictionary of locking beatnote frequencies [Hz],
-            #     or None for a default set of constant locking beatnote frequencies
+            fplan: path to frequency-plan file, dictionary of locking beatnote frequencies [Hz],
+                or 'static' for a default set of constant locking beatnote frequencies
             laser_asds: dictionary of amplitude spectral densities for laser noise [Hz/sqrt(Hz)]
             central_freq: laser central frequency from which all offsets are computed [Hz]
             modulation_asds: dictionary of amplitude spectral densities for modulation noise
@@ -235,6 +234,7 @@ class Instrument:
         # Instrument topology
         self.central_freq = float(central_freq)
         self.init_lock(lock)
+        self.init_fplan(fplan)
 
         # Laser and modulation noise
         self.laser_asds = ForEachMOSA(laser_asds)
@@ -360,16 +360,6 @@ class Instrument:
         else:
             self.mosa_angles = ForEachMOSA(mosa_angles)
 
-        # Frequency plan
-        if offsets_freqs == 'default':
-            # Default based on default for LISANode
-            self.offsets_freqs = ForEachMOSA({
-                '12': 8.1E6, '23': 9.2E6, '31': 10.3E6,
-                '13': 1.4E6, '32': -11.6E6, '21': -9.5E6,
-            })
-        else:
-            self.offsets_freqs = ForEachMOSA(offsets_freqs)
-
         # Orbits, gravitational waves, glitches
         self.init_orbits(orbits, orbit_dataset)
         self.init_gws(gws)
@@ -474,11 +464,12 @@ class Instrument:
             self.lock = {'12': 'cavity', '23': 'cavity', '31': 'cavity', '13': 'cavity', '32': 'cavity', '21': 'cavity'}
         elif isinstance(lock, str):
             logger.info("Using pre-defined locking configuration '%s'", lock)
+            self.lock_config = lock
             match = re.match(r'^(N[1-6])-(12|23|31|13|32|21)$', lock)
             if match:
-                self.lock_config = (match.group(1), match.group(2))
-                lock_12 = self.LOCK_TOPOLOGIES[self.lock_config[0]] # with 12 as primary
-                cycle = self.INDEX_CYCLES[self.lock_config[1]] # correspondance to lock_12
+                topology, primary = match.group(1), match.group(2)
+                lock_12 = self.LOCK_TOPOLOGIES[topology] # with 12 as primary
+                cycle = self.INDEX_CYCLES[primary] # correspondance to lock_12
                 self.lock = {mosa: lock_12[cycle[mosa]] for mosa in self.MOSAS}
             else:
                 raise ValueError(f"unsupported pre-defined locking configuration '{lock}'")
@@ -486,11 +477,65 @@ class Instrument:
             logger.info("Using explicit locking configuration '%s'", lock)
             if (set(lock.keys()) != set(self.MOSAS) or
                 set(lock.values()) != set(['cavity', 'distant', 'adjacent'])):
-                raise ValueError(f"invalid locking dictionary '{lock}'")
+                raise ValueError(f"invalid locking configuration '{lock}'")
+            self.lock_config = None
             self.lock = lock
         else:
             raise ValueError(f"invalid locking configuration '{lock}'")
 
+    def init_fplan(self, fplan):
+        """Initialize frequency plan.
+
+        Args:
+            fplan: `fplan` parameter, c.f. `__init__()`
+        """
+        if fplan == 'static':
+            logger.info("Using default set of locking beatnote frequencies")
+            self.fplan_file = None
+            self.fplan = ForEachMOSA({
+                # TODO: document this default set
+                '12': 8.1E6, '23': 9.2E6, '31': 10.3E6,
+                '13': 1.4E6, '32': -11.6E6, '21': -9.5E6,
+            })
+        elif isinstance(fplan, str):
+            logger.info("Using frequency-plan file '%s'", fplan)
+            self.fplan_file = fplan
+            # Without a standard lock config, there is no dataset
+            # in the frequency-plan file and therefore we cannot use it
+            if self.lock_config is None:
+                raise ValueError("cannot use frequency-plan for non standard lock configuration")
+            with File(self.fplan_file, 'r') as fplanf:
+                version = Version(fplanf.attrs['version'])
+                logger.debug("Using frequency-plan file version %s", version)
+                # Warn for frequency-plan file development version
+                if version.is_devrelease:
+                    logger.warning("You are using an frequency-plan file in a development version")
+                # Switch between various orbit file standards
+                if version in SpecifierSet('== 1.1.*', True):
+                    logger.debug("Interpolating locking beatnote frequencies with piecewise linear functions")
+                    times = self.t0 + np.arange(fplanf.attrs['size']) * fplanf.attrs['dt']
+                    interpolate = lambda x: InterpolatedUnivariateSpline(times, x, k=1, ext='raise')(self.physics_t)
+                    lock_beatnotes = {}
+                    # Go through all MOSAs and pick locking beatnotes
+                    for mosa in self.MOSAS:
+                        if self.lock[mosa] == 'cavity':
+                            # No offset for primary laser
+                            lock_beatnotes[mosa] = 0.0
+                        elif self.lock[mosa] == 'distant':
+                            lock_beatnotes[mosa] = interpolate(fplanf[self.lock_config][f'isi_{mosa}'])
+                        elif self.lock[mosa] == 'adjacent':
+                            # Fplan files only contain the one (left) RFI beatnote
+                            left_mosa = ForEachSC.left_mosa(ForEachMOSA.sc(mosa))
+                            sign = +1 if left_mosa == mosa else -1
+                            lock_beatnotes[mosa] = interpolate(sign * fplanf[self.lock_config][f'rfi_{left_mosa}'])
+                    self.fplan = ForEachMOSA(lock_beatnotes)
+                else:
+                    raise ValueError(f"unsupported frequency-plan file version '{version}'")
+        else:
+            logger.info("Using user-provided locking beatnote frequencies")
+            self.fplan_file = None
+            self.fplan = ForEachMOSA(fplan)
+
     def init_orbits(self, orbits, orbit_dataset):
         """Initialize orbits.
 
@@ -1418,7 +1463,7 @@ class Instrument:
         self.laser_noises[mosa] = noises.laser(self.physics_fs, self.physics_size, self.laser_asds[mosa])
 
         logger.debug("Computing carrier offsets for primary local beam %s", mosa)
-        self.local_carrier_offsets[mosa] = self.offsets_freqs[mosa]
+        self.local_carrier_offsets[mosa] = 0.0
 
         logger.debug("Computing carrier fluctuations for primary local beam %s", mosa)
         self.local_carrier_fluctuations[mosa] = \
@@ -1438,14 +1483,14 @@ class Instrument:
                      "locked on adjacent beam %s", mosa, adjacent(mosa))
         self.local_carrier_offsets[mosa] = \
             self.local_carrier_offsets[adjacent(mosa)] \
-            + self.offsets_freqs[mosa] * (1 + self.clock_noise_offsets[sc(mosa)])
+            - self.fplan[mosa] * (1 + self.clock_noise_offsets[sc(mosa)])
 
         logger.debug("Computing carrier fluctuations for local beam %s "
                      "locked on adjacent beam %s", mosa, adjacent(mosa))
         adjacent_carrier_fluctuations = self.local_carrier_fluctuations[adjacent(mosa)] \
             + self.central_freq * self.backlink_noises[mosa]
         self.local_carrier_fluctuations[mosa] = adjacent_carrier_fluctuations \
-            + self.offsets_freqs[mosa] * self.clock_noise_fluctuations[sc(mosa)] \
+            - self.fplan[mosa] * self.clock_noise_fluctuations[sc(mosa)] \
             + self.central_freq * self.oms_rfi_carrier_noises[mosa] \
             + self.tdir_tones[mosa]
 
@@ -1466,7 +1511,7 @@ class Instrument:
             -self.d_pprs[mosa] * self.central_freq \
             + (1 - self.d_pprs[mosa]) * self.interpolate(carrier_offsets, -self.pprs[mosa])
         self.local_carrier_offsets[mosa] = distant_carrier_offsets \
-            + self.offsets_freqs[mosa] * (1 + self.clock_noise_offsets[sc(mosa)])
+            - self.fplan[mosa] * (1 + self.clock_noise_offsets[sc(mosa)])
 
         logger.debug("Computing carrier fluctuations for local beam %s "
                      "locked on distant beam %s", mosa, distant(mosa))
@@ -1479,7 +1524,7 @@ class Instrument:
             - (self.central_freq + self.local_carrier_offsets[mosa]) * self.gws[mosa] \
             - (self.central_freq + self.local_carrier_offsets[mosa]) * self.local_ttls[mosa] / c
         self.local_carrier_fluctuations[mosa] = distant_carrier_fluctuations \
-            + self.offsets_freqs[mosa] * self.clock_noise_fluctuations[sc(mosa)] \
+            - self.fplan[mosa] * self.clock_noise_fluctuations[sc(mosa)] \
             + self.central_freq * self.oms_isi_carrier_noises[mosa] \
             + self.tdir_tones[mosa]