coveralls: 
[![Coverage Status](https://coveralls.io/builds/17087856/badge)](https://coveralls.io/builds/17087856)

Coverage decreased (-0.02%) to 99.101% when pulling **b5ea4d0cdb68bea0d08f88d50d8a93d89619f6a4 on fix-issue-142** into **41d9506ba64a92ca6d5e81737180f4f7220acea9 on master**.

diff --git a/scripts/discos-simulator b/scripts/discos-simulator
index b9450ee..9d64de8 100755
--- a/scripts/discos-simulator
+++ b/scripts/discos-simulator
@@ -15,8 +15,7 @@ def system_from_arg(system_name):
     try:
         return importlib.import_module('simulators.%s' % system_name)
     except ImportError:
-        raise ArgumentTypeError('system "%s" unavailable' % system_name)
-
+        raise ArgumentTypeError('System "%s" unavailable.' % system_name)
 
 parser = ArgumentParser()
 parser.add_argument(
@@ -25,12 +24,33 @@ parser.add_argument(
     required=True,
     help='System name: active_surface, acu, ...',
 )
+parser.add_argument(
+    '-t', '--type',
+    required=False,
+    help='System configuration type: IFD_14_channels for if_distributor, ...',
+)
 parser.add_argument(
     'action',
-    choices=['start', 'stop'])
-args = parser.parse_args()
+    choices=['start', 'stop']
+)
 
+args = parser.parse_args()
 
+if args.type:
+    try:
+        systems = getattr(args.system, 'systems')
+    except AttributeError:
+        raise ArgumentTypeError(
+            ('System %s has no configurations other than the default one. '
+            % args.system.__name__.rsplit('.', 1)[1])
+            + "Omit the '--type' flag to start the simulator properly."
+        )
+    if args.type not in systems:
+        raise ArgumentTypeError(
+            'Configuration %s for system %s not found.'
+            % (args.type, args.system.__name__.rsplit('.', 1)[1])
+        )
+    args.system.system_type = args.type
 
 simulator = Simulator(args.system)
 if args.action == 'start':
diff --git a/simulators/if_distributor/IFD_14_channels.py b/simulators/if_distributor/IFD_14_channels.py
new file mode 100644
index 0000000..a8a1581
--- /dev/null
+++ b/simulators/if_distributor/IFD_14_channels.py
@@ -0,0 +1,131 @@
+from simulators.common import ListeningSystem
+
+
+class System(ListeningSystem):
+
+    header = b'#'
+    tail = b'\n'
+    max_msg_length = 12  # b'#AAA 99 999\n'
+    # The dictionary devices has the device types as keys,
+    # and a dictionary as value.  The value has the address as
+    # key and the allowed values as value
+    allowed_commands = ['ATT', 'SWT']
+    max_channels = 96
+    max_att = 31.75
+    att_step = 0.25
+    max_att_multiplier = max_att / att_step
+    channels = [max_att_multiplier] * max_channels
+    switched = False
+    version = b'SRT IF Distributor Simulator 1.0'
+
+    def __init__(self):
+        self.ciao = 'ciao'
+        self.msg = b''
+        self._set_default()
+
+    def _set_default(self):
+        self.channels = [self.max_att_multiplier] * self.max_channels
+        self.switched = False
+
+    def parse(self, byte):
+        self.msg += byte
+        if len(self.msg) == 1:
+            if byte == self.header:
+                return True  # Got the header
+            else:
+                self.msg = b''
+                return False
+        elif len(self.msg) < self.max_msg_length:
+            if byte != self.tail:
+                return True
+        elif len(self.msg) == self.max_msg_length:
+            if byte != self.tail:
+                self.msg = b''
+                raise ValueError(
+                    'message too long: max length should be %d.' %
+                    self.max_msg_length
+                )
+
+        msg = self.msg[1:-1]  # Remove the header and tail
+        self.msg = b''
+        return self._execute(msg)
+
+    def _execute(self, msg):
+        if b' ' in msg and b'?' not in msg:  # Setup request
+            try:
+                command, channel, value = msg.split()
+            except ValueError:
+                raise ValueError(
+                    'the setup message must have three items: '
+                    'command, channel, and value.'
+                )
+
+            if command not in self.allowed_commands:
+                raise ValueError(
+                    'command %s not in %s'
+                    % (command, self.allowed_commands)
+                )
+
+            try:
+                channel = int(channel)
+            except ValueError:
+                raise ValueError('the channel ID must be an integer.')
+
+            if channel >= self.max_channels or channel < 0:
+                raise ValueError(
+                    'channel %d does not exist.' % channel)
+
+            try:
+                value = int(value)
+            except ValueError:
+                raise ValueError('the command value must be an integer.')
+
+            if command == 'ATT':
+                if value < 0 or value >= self.max_att_multiplier:
+                    raise ValueError('value %d not allowed' % value)
+                else:
+                    self.channels[channel] = value
+            elif command == 'SWT':
+                if value == 0:
+                    self.switched = False
+                elif value == 1:
+                    self.switched = True
+                else:
+                    raise ValueError(
+                        'SWT command accepts only values 00 or 01')
+            return b''
+        elif b' ' in msg and b'?' in msg:  # Get request
+            msg = msg.rstrip('?')
+            try:
+                command, channel = msg.split()
+            except ValueError:
+                raise ValueError(
+                    'the get message must have two items: '
+                    'command and channel.'
+                )
+
+            try:
+                channel = int(channel)
+            except ValueError:
+                raise ValueError('the channel ID must be an integer.')
+
+            if channel >= self.max_channels or channel < 0:
+                raise ValueError(
+                    'channel %d does not exist.' % channel)
+            else:
+                if command == 'ATT':
+                    return b'#%s\n' % str(
+                        self.channels[channel] * self.att_step)
+                elif command == 'SWT':
+                    return b'#%s\n' % (1 if self.switched else 0)
+                else:
+                    raise ValueError(
+                        'command %s not in %s'
+                        % (command, self.allowed_commands))
+        elif msg == b'*IDN?':  # IDN request
+            return self.version
+        elif msg == b'*RST':  # RST command
+            self._set_default()
+            return None
+        else:  # Not expected command
+            return b'#COMMAND UNKNOWN\n'
diff --git a/simulators/if_distributor/__init__.py b/simulators/if_distributor/__init__.py
index a73b277..ba0f9a2 100644
--- a/simulators/if_distributor/__init__.py
+++ b/simulators/if_distributor/__init__.py
@@ -1,5 +1,4 @@
-from simulators.common import ListeningSystem
-
+from simulators import utils
 
 # Each system module (like active_surface.py, acu.py, etc.) has to
 # define a list called servers.s This list contains tuples
@@ -9,131 +8,18 @@ from simulators.common import ListeningSystem
 # get_message method, while args is a tuple of optional extra arguments.
 servers = [(('127.0.0.1', 12000), (), ())]
 
+systems = utils.get_systems()
+default_system_type = 'IFD_14_channels'
+system_type = default_system_type
 
-class System(ListeningSystem):
-
-    header = b'#'
-    tail = b'\n'
-    max_msg_length = 12  # b'#AAA 99 999\n'
-    # The dictionary devices has the device types as keys,
-    # and a dictionary as value.  The value has the address as
-    # key and the allowed values as value
-    allowed_commands = ['ATT', 'SWT']
-    max_channels = 96
-    max_att = 31.75
-    att_step = 0.25
-    max_att_multiplier = max_att / att_step
-    channels = [max_att_multiplier] * max_channels
-    switched = False
-    version = b'SRT IF Distributor Simulator 1.0'
-
-    def __init__(self):
-        self.msg = b''
-        self._set_default()
-
-    def _set_default(self):
-        self.channels = [self.max_att_multiplier] * self.max_channels
-        self.switched = False
-
-    def parse(self, byte):
-        self.msg += byte
-        if len(self.msg) == 1:
-            if byte == self.header:
-                return True  # Got the header
-            else:
-                self.msg = b''
-                return False
-        elif len(self.msg) < self.max_msg_length:
-            if byte != self.tail:
-                return True
-        elif len(self.msg) == self.max_msg_length:
-            if byte != self.tail:
-                self.msg = b''
-                raise ValueError(
-                    'message too long: max length should be %d.' %
-                    self.max_msg_length
-                )
-
-        msg = self.msg[1:-1]  # Remove the header and tail
-        self.msg = b''
-        return self._execute(msg)
-
-    def _execute(self, msg):
-        if b' ' in msg and b'?' not in msg:  # Setup request
-            try:
-                command, channel, value = msg.split()
-            except ValueError:
-                raise ValueError(
-                    'the setup message must have three items: '
-                    'command, channel, and value.'
-                )
-
-            if command not in self.allowed_commands:
-                raise ValueError(
-                    'command %s not in %s'
-                    % (command, self.allowed_commands)
-                )
-
-            try:
-                channel = int(channel)
-            except ValueError:
-                raise ValueError('the channel ID must be an integer.')
-
-            if channel >= self.max_channels or channel < 0:
-                raise ValueError(
-                    'channel %d does not exist.' % channel)
-
-            try:
-                value = int(value)
-            except ValueError:
-                raise ValueError('the command value must be an integer.')
 
-            if command == 'ATT':
-                if value < 0 or value >= self.max_att_multiplier:
-                    raise ValueError('value %d not allowed' % value)
-                else:
-                    self.channels[channel] = value
-            elif command == 'SWT':
-                if value == 0:
-                    self.switched = False
-                elif value == 1:
-                    self.switched = True
-                else:
-                    raise ValueError(
-                        'SWT command accepts only values 00 or 01')
-            return b''
-        elif b' ' in msg and b'?' in msg:  # Get request
-            msg = msg.rstrip('?')
-            try:
-                command, channel = msg.split()
-            except ValueError:
-                raise ValueError(
-                    'the get message must have two items: '
-                    'command and channel.'
-                )
+class System(object):
 
-            try:
-                channel = int(channel)
-            except ValueError:
-                raise ValueError('the channel ID must be an integer.')
+    def __new__(cls, *args):
+        if system_type not in systems:
+            raise ValueError(
+                'Configuration %s for system if_distributor not found.'
+                % system_type
+            )
 
-            if channel >= self.max_channels or channel < 0:
-                raise ValueError(
-                    'channel %d does not exist.' % channel)
-            else:
-                if command == 'ATT':
-                    return b'#%s\n' % str(
-                        self.channels[channel] * self.att_step)
-                elif command == 'SWT':
-                    return b'#%s\n' % (1 if self.switched else 0)
-                else:
-                    raise ValueError(
-                        'command %s not in %s'
-                        % (command, self.allowed_commands))
-        elif msg == b'*IDN?':  # IDN request
-            return self.version
-        elif msg == b'*RST':  # RST command
-            self._set_default()
-            return None
-        else:  # Not expected command
-            return b'#COMMAND UNKNOWN\n'
+        return systems[system_type].System(*args)
diff --git a/simulators/utils.py b/simulators/utils.py
index a359a16..d5765ef 100644
--- a/simulators/utils.py
+++ b/simulators/utils.py
@@ -1,7 +1,11 @@
 #!/usr/bin/python
 import math
 import struct
+import importlib
+import inspect
+import os
 from datetime import datetime, timedelta
+from simulators.common import BaseSystem
 
 
 def checksum(msg):
@@ -442,6 +446,24 @@ def day_percentage(date=None):
     return float(microseconds) / 86400000000
 
 
+def get_systems():
+    module = inspect.getmodule(inspect.stack()[1][0])
+    module_path = module.__file__.rsplit('/', 1)[0] + '/'
+    systems = {}
+    for f in os.listdir(os.path.dirname(module_path)):
+        if f[-3:] == '.py' and f != '__init__.py':
+            mod = importlib.import_module(
+                '%s.%s' % (module.__name__, f[:-3])
+            )
+            for _, obj in inspect.getmembers(mod):
+                if (inspect.isclass(obj)
+                        and obj is not BaseSystem
+                        and issubclass(obj, BaseSystem)
+                        and BaseSystem not in obj.__bases__):
+                    systems[mod.__name__.rsplit('.', 1)[1]] = mod
+    return systems
+
+
 if __name__ == '__main__':
     import doctest
     doctest.testmod()

Extend the `discos-simulator` script and make it able to launch different system configurations.
In order to develop the new `if_distributor` simulator it could be useful if the `discos-simulator` script can launch different configurations of a system instead of writing another system with a slightly different name.
diff --git a/tests/test_if_distributor.py b/tests/test_if_distributor.py
index a663a11..95c624a 100644
--- a/tests/test_if_distributor.py
+++ b/tests/test_if_distributor.py
@@ -2,8 +2,15 @@ import unittest
 from simulators import if_distributor
 
 
+class TestIFDistributorUnknownType(unittest.TestCase):
 
-class TestIFDistributorParse(unittest.TestCase):
+    def test_unknown_type(self):
+        if_distributor.system_type = 'unknown'
+        with self.assertRaises(ValueError):
+            self.system = if_distributor.System()
+
+
+class TestIFDistributor14Channels(unittest.TestCase):
 
     def setUp(self):
         self.system = if_distributor.System()

pip install -e .[test]
parse_log_pytest
pytest
coverage prospector sphinx sphinx_rtd_theme tox pytest
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
astroid==3.3.9
babel==2.17.0
cachetools==5.5.2
certifi==2025.1.31
chardet==5.2.0
charset-normalizer==3.4.1
colorama==0.4.6
coverage==7.8.0
dill==0.3.9
-e git+https://github.com/discos/simulators.git@41d9506ba64a92ca6d5e81737180f4f7220acea9#egg=discos_simulators
distlib==0.3.9
docutils==0.21.2
dodgy==0.2.1
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
filelock==3.18.0
flake8==7.2.0
flake8-polyfill==1.0.2
gitdb==4.0.12
GitPython==3.1.44
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
isort==6.0.1
Jinja2==3.1.6
MarkupSafe==3.0.2
mccabe==0.7.0
numpy==2.0.2
packaging @ file:///croot/packaging_1734472117206/work
pep8-naming==0.10.0
platformdirs==4.3.7
pluggy @ file:///croot/pluggy_1733169602837/work
prospector==1.16.1
pycodestyle==2.13.0
pydocstyle==6.3.0
pyflakes==3.3.2
Pygments==2.19.1
pylint==3.3.6
pylint-celery==0.3
pylint-django==2.6.1
pylint-plugin-utils==0.8.2
pyproject-api==1.9.0
pytest @ file:///croot/pytest_1738938843180/work
PyYAML==6.0.2
requests==2.32.3
requirements-detector==1.3.2
scipy==1.13.1
semver==3.0.4
setoptconf-tmp==0.3.1
smmap==5.0.2
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
toml==0.10.2
tomli==2.2.1
tomlkit==0.13.2
tox==4.25.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3
zipp==3.21.0

name: simulators
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - astroid==3.3.9
      - babel==2.17.0
      - cachetools==5.5.2
      - certifi==2025.1.31
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - colorama==0.4.6
      - coverage==7.8.0
      - dill==0.3.9
      - distlib==0.3.9
      - docutils==0.21.2
      - dodgy==0.2.1
      - filelock==3.18.0
      - flake8==7.2.0
      - flake8-polyfill==1.0.2
      - gitdb==4.0.12
      - gitpython==3.1.44
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - isort==6.0.1
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - numpy==2.0.2
      - pep8-naming==0.10.0
      - platformdirs==4.3.7
      - prospector==1.16.1
      - pycodestyle==2.13.0
      - pydocstyle==6.3.0
      - pyflakes==3.3.2
      - pygments==2.19.1
      - pylint==3.3.6
      - pylint-celery==0.3
      - pylint-django==2.6.1
      - pylint-plugin-utils==0.8.2
      - pyproject-api==1.9.0
      - pyyaml==6.0.2
      - requests==2.32.3
      - requirements-detector==1.3.2
      - scipy==1.13.1
      - semver==3.0.4
      - setoptconf-tmp==0.3.1
      - smmap==5.0.2
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinx-rtd-theme==3.0.2
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - toml==0.10.2
      - tomli==2.2.1
      - tomlkit==0.13.2
      - tox==4.25.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - zipp==3.21.0
prefix: /opt/conda/envs/simulators

tests/test_if_distributor.py::TestIFDistributorUnknownType::test_unknown_type
tests/test_if_distributor.py::TestIFDistributor14Channels::test_enable_and_disable_switch tests/test_if_distributor.py::TestIFDistributor14Channels::test_get_channel_not_allowed tests/test_if_distributor.py::TestIFDistributor14Channels::test_get_channel_not_integer tests/test_if_distributor.py::TestIFDistributor14Channels::test_get_switch tests/test_if_distributor.py::TestIFDistributor14Channels::test_idn_command tests/test_if_distributor.py::TestIFDistributor14Channels::test_max_msg_length_reached tests/test_if_distributor.py::TestIFDistributor14Channels::test_rst tests/test_if_distributor.py::TestIFDistributor14Channels::test_set_and_get_switch tests/test_if_distributor.py::TestIFDistributor14Channels::test_set_and_get_value tests/test_if_distributor.py::TestIFDistributor14Channels::test_set_switch tests/test_if_distributor.py::TestIFDistributor14Channels::test_set_wrong_switch tests/test_if_distributor.py::TestIFDistributor14Channels::test_setup_channel_not_allowed tests/test_if_distributor.py::TestIFDistributor14Channels::test_setup_channel_not_integer tests/test_if_distributor.py::TestIFDistributor14Channels::test_setup_sets_the_value tests/test_if_distributor.py::TestIFDistributor14Channels::test_setup_value_not_allowed tests/test_if_distributor.py::TestIFDistributor14Channels::test_setup_value_not_integer tests/test_if_distributor.py::TestIFDistributor14Channels::test_wrong_command tests/test_if_distributor.py::TestIFDistributor14Channels::test_wrong_get_channel tests/test_if_distributor.py::TestIFDistributor14Channels::test_wrong_number_of_get_items tests/test_if_distributor.py::TestIFDistributor14Channels::test_wrong_number_of_setup_items tests/test_if_distributor.py::TestIFDistributor14Channels::test_wrong_setup_command
scripts/discos-simulator simulators/if_distributor/IFD_14_channels.py simulators/if_distributor/__init__.py simulators/utils.py
scripts/discos-simulator simulators/if_distributor/IFD_14_channels.py simulators/if_distributor/__init__.py simulators/utils.py