We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
REPL server needs CTRL+C twice to exit
- Python: 3.11
- OS: MacOS
- Pymodbus: 9f66ab52bb98931dc260b65275ba3b77bb5122c8

REPL server needs to be exited twice; non-REPL server exits fine.  I think this was introduced in https://github.com/pymodbus-dev/pymodbus/pull/1141/commits/c395566ff697ad5087f83764a25af03b717ceb65.  
https://github.com/pymodbus-dev/pymodbus/blob/9f66ab52bb98931dc260b65275ba3b77bb5122c8/pymodbus/repl/server/main.py#L207-L208

```
❯ pymodbus.server run --modbus-server tcp --framer socket --slave-id 1 --slave-id 4 --random 2

__________                          .______.                    _________
\______   \___.__. _____   ____   __| _/\_ |__  __ __  ______  /   _____/ ______________  __ ___________
 |     ___<   |  |/     \ /  _ \ / __ |  | __ \|  |  \/  ___/  \_____  \_/ __ \_  __ \  \/ // __ \_  __ \\
 |    |    \___  |  Y Y  (  <_> ) /_/ |  | \_\ \  |  /\___ \   /        \  ___/|  | \/\   /\  ___/|  | \/
 |____|    / ____|__|_|  /\____/\____ |  |___  /____//____  > /_______  /\___  >__|    \_/  \___  >__|
           \/          \/            \/      \/           \/          \/     \/                 \/


SERVER >
```
Enter `CTRL+C`  (or type `exit`)
`Bye Bye!!!` 
Enter `CTRL+C` again to actually exit

```
^C
Aborted.
```

The non-REPL server exits correctly on a single CTRL+C:
```
❯ pymodbus.server --no-repl --verbose run  --modbus-server tcp --framer socket --slave-id 1 --slave-id 4 --random 2 --modbus-port 5020
2023-04-18 00:05:52,276 INFO  logging:96 Modbus server started

Reactive Modbus Server started.
======== Running on http://localhost:8080 ========

===========================================================================
Example Usage:
curl -X POST http://localhost:8080 -d "{"response_type": "error", "error_code": 4}"
===========================================================================

2023-04-18 00:05:52,279 INFO  logging:96 Server(TCP) listening.
^C
Aborted.
```

Also, there's a useless try/except block that never gets called.
https://github.com/pymodbus-dev/pymodbus/blob/dev/pymodbus/repl/server/main.py#L207-L208


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pymodbus/repl/server/main.py`
Content:
```
1 """Repl server main."""
2 import asyncio
3 import json
4 import logging
5 import sys
6 from enum import Enum
7 from pathlib import Path
8 from typing import List
9 
10 import typer
11 
12 from pymodbus import pymodbus_apply_logging_config
13 from pymodbus.framer.socket_framer import ModbusSocketFramer
14 from pymodbus.logging import Log
15 from pymodbus.repl.server.cli import run_repl
16 from pymodbus.server.reactive.default_config import DEFAULT_CONFIG
17 from pymodbus.server.reactive.main import (
18     DEFAULT_FRAMER,
19     DEFUALT_HANDLERS,
20     ReactiveServer,
21 )
22 
23 
24 CANCELLED_ERROR = asyncio.exceptions.CancelledError
25 CONTEXT_SETTING = {"allow_extra_args": True, "ignore_unknown_options": True}
26 
27 
28 # TBD class ModbusServerConfig:
29 
30 
31 class ModbusServerTypes(str, Enum):
32     """Server types."""
33 
34     # ["tcp", "serial", "tls", "udp"]
35     tcp = "tcp"  # pylint: disable=invalid-name
36     serial = "serial"  # pylint: disable=invalid-name
37     tls = "tls"  # pylint: disable=invalid-name
38     udp = "udp"  # pylint: disable=invalid-name
39 
40 
41 class ModbusFramerTypes(str, Enum):
42     """Framer types."""
43 
44     # ["socket", "rtu", "tls", "ascii", "binary"]
45     socket = "socket"  # pylint: disable=invalid-name
46     rtu = "rtu"  # pylint: disable=invalid-name
47     tls = "tls"  # pylint: disable=invalid-name
48     ascii = "ascii"  # pylint: disable=invalid-name
49     binary = "binary"  # pylint: disable=invalid-name
50 
51 
52 def _completer(incomplete: str, valid_values: List[str]) -> List[str]:
53     """Complete value."""
54     completion = []
55     for name in valid_values:
56         if name.startswith(incomplete):
57             completion.append(name)
58     return completion
59 
60 
61 def framers(incomplete: str) -> List[str]:
62     """Return an autocompleted list of supported clouds."""
63     _framers = ["socket", "rtu", "tls", "ascii", "binary"]
64     return _completer(incomplete, _framers)
65 
66 
67 def servers(incomplete: str) -> List[str]:
68     """Return an autocompleted list of supported clouds."""
69     _servers = ["tcp", "serial", "tls", "udp"]
70     return _completer(incomplete, _servers)
71 
72 
73 def process_extra_args(extra_args: List[str], modbus_config: dict) -> dict:
74     """Process extra args passed to server."""
75     options_stripped = [x.strip().replace("--", "") for x in extra_args[::2]]
76     extra_args_dict = dict(list(zip(options_stripped, extra_args[1::2])))
77     for option, value in extra_args_dict.items():
78         if option in modbus_config:
79             try:
80                 modbus_config[option] = type(modbus_config[option])(value)
81             except ValueError as err:
82                 Log.error(
83                     "Error parsing extra arg {} with value '{}'. {}", option, value, err
84                 )
85                 sys.exit(1)
86     return modbus_config
87 
88 
89 app = typer.Typer(
90     no_args_is_help=True,
91     context_settings=CONTEXT_SETTING,
92     help="Reactive Modbus server",
93 )
94 
95 
96 @app.callback()
97 def server(
98     ctx: typer.Context,
99     host: str = typer.Option("localhost", "--host", help="Host address"),
100     web_port: int = typer.Option(8080, "--web-port", help="Web app port"),
101     broadcast_support: bool = typer.Option(
102         False, "-b", help="Support broadcast messages"
103     ),
104     repl: bool = typer.Option(True, help="Enable/Disable repl for server"),
105     verbose: bool = typer.Option(
106         False, help="Run with debug logs enabled for pymodbus"
107     ),
108 ):
109     """Run server code."""
110     log_level = logging.DEBUG if verbose else logging.ERROR
111     pymodbus_apply_logging_config(log_level)
112 
113     ctx.obj = {
114         "repl": repl,
115         "host": host,
116         "web_port": web_port,
117         "broadcast": broadcast_support,
118     }
119 
120 
121 @app.command("run", context_settings=CONTEXT_SETTING)
122 def run(
123     ctx: typer.Context,
124     modbus_server: str = typer.Option(
125         ModbusServerTypes.tcp.value,
126         "--modbus-server",
127         "-s",
128         case_sensitive=False,
129         autocompletion=servers,
130         help="Modbus Server",
131     ),
132     modbus_framer: str = typer.Option(
133         ModbusFramerTypes.socket.value,
134         "--framer",
135         "-f",
136         case_sensitive=False,
137         autocompletion=framers,
138         help="Modbus framer to use",
139     ),
140     modbus_port: int = typer.Option(5020, "--modbus-port", "-p", help="Modbus port"),
141     modbus_slave_id: List[int] = typer.Option(
142         [1], "--slave-id", "-u", help="Supported Modbus slave id's"
143     ),
144     modbus_config_path: Path = typer.Option(
145         None, help="Path to additional modbus server config"
146     ),
147     randomize: int = typer.Option(
148         0,
149         "--random",
150         "-r",
151         help="Randomize every `r` reads. 0=never, 1=always,2=every-second-read"
152         ", and so on. Applicable IR and DI.",
153     ),
154     change_rate: int = typer.Option(
155         0,
156         "--change-rate",
157         "-c",
158         help="Rate in % registers to change. 0=none, 100=all, 12=12% of registers"
159         ", and so on. Applicable IR and DI.",
160     ),
161 ):
162     """Run Reactive Modbus server.
163 
164     Exposing REST endpoint for response manipulation.
165     """
166     repl = ctx.obj.pop("repl")
167     # TBD extra_args = ctx.args
168     web_app_config = ctx.obj
169     loop = asyncio.get_event_loop()
170     framer = DEFAULT_FRAMER.get(modbus_framer, ModbusSocketFramer)
171     if modbus_config_path:
172         with open(modbus_config_path, encoding="utf-8") as my_file:
173             modbus_config = json.load(my_file)
174     else:
175         modbus_config = DEFAULT_CONFIG
176 
177     extra_args = ctx.args
178     data_block_settings = modbus_config.pop("data_block_settings", {})
179     modbus_config = modbus_config.get(modbus_server, {})
180     modbus_config = process_extra_args(extra_args, modbus_config)
181     if modbus_server != "serial":
182         handler = modbus_config.pop("handler", "ModbusConnectedRequestHandler")
183     else:
184         handler = modbus_config.pop("handler", "ModbusSingleRequestHandler")
185     handler = DEFUALT_HANDLERS.get(handler.strip())
186 
187     modbus_config["handler"] = handler
188     modbus_config["randomize"] = randomize
189     modbus_config["change_rate"] = change_rate
190     app = ReactiveServer.factory(
191         modbus_server,
192         framer,
193         modbus_port=modbus_port,
194         slave=modbus_slave_id,
195         loop=loop,
196         single=False,
197         data_block_settings=data_block_settings,
198         **web_app_config,
199         **modbus_config,
200     )
201     try:
202         loop.run_until_complete(app.run_async(repl))
203         if repl:
204             loop.run_until_complete(run_repl(app))
205         loop.run_forever()
206 
207     except CANCELLED_ERROR:
208         print("Done!!!!!")
209 
210 
211 if __name__ == "__main__":
212     app()
213
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pymodbus/repl/server/main.py b/pymodbus/repl/server/main.py
--- a/pymodbus/repl/server/main.py
+++ b/pymodbus/repl/server/main.py
@@ -198,15 +198,12 @@
         **web_app_config,
         **modbus_config,
     )
-    try:
+    if repl:
+        loop.run_until_complete(run_repl(app))
+    else:
         loop.run_until_complete(app.run_async(repl))
-        if repl:
-            loop.run_until_complete(run_repl(app))
         loop.run_forever()
 
-    except CANCELLED_ERROR:
-        print("Done!!!!!")
-
 
 if __name__ == "__main__":
     app()

{"golden_diff": "diff --git a/pymodbus/repl/server/main.py b/pymodbus/repl/server/main.py\n--- a/pymodbus/repl/server/main.py\n+++ b/pymodbus/repl/server/main.py\n@@ -198,15 +198,12 @@\n         **web_app_config,\n         **modbus_config,\n     )\n-    try:\n+    if repl:\n+        loop.run_until_complete(run_repl(app))\n+    else:\n         loop.run_until_complete(app.run_async(repl))\n-        if repl:\n-            loop.run_until_complete(run_repl(app))\n         loop.run_forever()\n \n-    except CANCELLED_ERROR:\n-        print(\"Done!!!!!\")\n-\n \n if __name__ == \"__main__\":\n     app()\n", "issue": "REPL server needs CTRL+C twice to exit\n- Python: 3.11\r\n- OS: MacOS\r\n- Pymodbus: 9f66ab52bb98931dc260b65275ba3b77bb5122c8\r\n\r\nREPL server needs to be exited twice; non-REPL server exits fine.  I think this was introduced in https://github.com/pymodbus-dev/pymodbus/pull/1141/commits/c395566ff697ad5087f83764a25af03b717ceb65.  \r\nhttps://github.com/pymodbus-dev/pymodbus/blob/9f66ab52bb98931dc260b65275ba3b77bb5122c8/pymodbus/repl/server/main.py#L207-L208\r\n\r\n```\r\n\u276f pymodbus.server run --modbus-server tcp --framer socket --slave-id 1 --slave-id 4 --random 2\r\n\r\n__________                          .______.                    _________\r\n\\______   \\___.__. _____   ____   __| _/\\_ |__  __ __  ______  /   _____/ ______________  __ ___________\r\n |     ___<   |  |/     \\ /  _ \\ / __ |  | __ \\|  |  \\/  ___/  \\_____  \\_/ __ \\_  __ \\  \\/ // __ \\_  __ \\\\\r\n |    |    \\___  |  Y Y  (  <_> ) /_/ |  | \\_\\ \\  |  /\\___ \\   /        \\  ___/|  | \\/\\   /\\  ___/|  | \\/\r\n |____|    / ____|__|_|  /\\____/\\____ |  |___  /____//____  > /_______  /\\___  >__|    \\_/  \\___  >__|\r\n           \\/          \\/            \\/      \\/           \\/          \\/     \\/                 \\/\r\n\r\n\r\nSERVER >\r\n```\r\nEnter `CTRL+C`  (or type `exit`)\r\n`Bye Bye!!!` \r\nEnter `CTRL+C` again to actually exit\r\n\r\n```\r\n^C\r\nAborted.\r\n```\r\n\r\nThe non-REPL server exits correctly on a single CTRL+C:\r\n```\r\n\u276f pymodbus.server --no-repl --verbose run  --modbus-server tcp --framer socket --slave-id 1 --slave-id 4 --random 2 --modbus-port 5020\r\n2023-04-18 00:05:52,276 INFO  logging:96 Modbus server started\r\n\r\nReactive Modbus Server started.\r\n======== Running on http://localhost:8080 ========\r\n\r\n===========================================================================\r\nExample Usage:\r\ncurl -X POST http://localhost:8080 -d \"{\"response_type\": \"error\", \"error_code\": 4}\"\r\n===========================================================================\r\n\r\n2023-04-18 00:05:52,279 INFO  logging:96 Server(TCP) listening.\r\n^C\r\nAborted.\r\n```\r\n\r\nAlso, there's a useless try/except block that never gets called.\r\nhttps://github.com/pymodbus-dev/pymodbus/blob/dev/pymodbus/repl/server/main.py#L207-L208\r\n\n", "before_files": [{"content": "\"\"\"Repl server main.\"\"\"\nimport asyncio\nimport json\nimport logging\nimport sys\nfrom enum import Enum\nfrom pathlib import Path\nfrom typing import List\n\nimport typer\n\nfrom pymodbus import pymodbus_apply_logging_config\nfrom pymodbus.framer.socket_framer import ModbusSocketFramer\nfrom pymodbus.logging import Log\nfrom pymodbus.repl.server.cli import run_repl\nfrom pymodbus.server.reactive.default_config import DEFAULT_CONFIG\nfrom pymodbus.server.reactive.main import (\n    DEFAULT_FRAMER,\n    DEFUALT_HANDLERS,\n    ReactiveServer,\n)\n\n\nCANCELLED_ERROR = asyncio.exceptions.CancelledError\nCONTEXT_SETTING = {\"allow_extra_args\": True, \"ignore_unknown_options\": True}\n\n\n# TBD class ModbusServerConfig:\n\n\nclass ModbusServerTypes(str, Enum):\n    \"\"\"Server types.\"\"\"\n\n    # [\"tcp\", \"serial\", \"tls\", \"udp\"]\n    tcp = \"tcp\"  # pylint: disable=invalid-name\n    serial = \"serial\"  # pylint: disable=invalid-name\n    tls = \"tls\"  # pylint: disable=invalid-name\n    udp = \"udp\"  # pylint: disable=invalid-name\n\n\nclass ModbusFramerTypes(str, Enum):\n    \"\"\"Framer types.\"\"\"\n\n    # [\"socket\", \"rtu\", \"tls\", \"ascii\", \"binary\"]\n    socket = \"socket\"  # pylint: disable=invalid-name\n    rtu = \"rtu\"  # pylint: disable=invalid-name\n    tls = \"tls\"  # pylint: disable=invalid-name\n    ascii = \"ascii\"  # pylint: disable=invalid-name\n    binary = \"binary\"  # pylint: disable=invalid-name\n\n\ndef _completer(incomplete: str, valid_values: List[str]) -> List[str]:\n    \"\"\"Complete value.\"\"\"\n    completion = []\n    for name in valid_values:\n        if name.startswith(incomplete):\n            completion.append(name)\n    return completion\n\n\ndef framers(incomplete: str) -> List[str]:\n    \"\"\"Return an autocompleted list of supported clouds.\"\"\"\n    _framers = [\"socket\", \"rtu\", \"tls\", \"ascii\", \"binary\"]\n    return _completer(incomplete, _framers)\n\n\ndef servers(incomplete: str) -> List[str]:\n    \"\"\"Return an autocompleted list of supported clouds.\"\"\"\n    _servers = [\"tcp\", \"serial\", \"tls\", \"udp\"]\n    return _completer(incomplete, _servers)\n\n\ndef process_extra_args(extra_args: List[str], modbus_config: dict) -> dict:\n    \"\"\"Process extra args passed to server.\"\"\"\n    options_stripped = [x.strip().replace(\"--\", \"\") for x in extra_args[::2]]\n    extra_args_dict = dict(list(zip(options_stripped, extra_args[1::2])))\n    for option, value in extra_args_dict.items():\n        if option in modbus_config:\n            try:\n                modbus_config[option] = type(modbus_config[option])(value)\n            except ValueError as err:\n                Log.error(\n                    \"Error parsing extra arg {} with value '{}'. {}\", option, value, err\n                )\n                sys.exit(1)\n    return modbus_config\n\n\napp = typer.Typer(\n    no_args_is_help=True,\n    context_settings=CONTEXT_SETTING,\n    help=\"Reactive Modbus server\",\n)\n\n\n@app.callback()\ndef server(\n    ctx: typer.Context,\n    host: str = typer.Option(\"localhost\", \"--host\", help=\"Host address\"),\n    web_port: int = typer.Option(8080, \"--web-port\", help=\"Web app port\"),\n    broadcast_support: bool = typer.Option(\n        False, \"-b\", help=\"Support broadcast messages\"\n    ),\n    repl: bool = typer.Option(True, help=\"Enable/Disable repl for server\"),\n    verbose: bool = typer.Option(\n        False, help=\"Run with debug logs enabled for pymodbus\"\n    ),\n):\n    \"\"\"Run server code.\"\"\"\n    log_level = logging.DEBUG if verbose else logging.ERROR\n    pymodbus_apply_logging_config(log_level)\n\n    ctx.obj = {\n        \"repl\": repl,\n        \"host\": host,\n        \"web_port\": web_port,\n        \"broadcast\": broadcast_support,\n    }\n\n\n@app.command(\"run\", context_settings=CONTEXT_SETTING)\ndef run(\n    ctx: typer.Context,\n    modbus_server: str = typer.Option(\n        ModbusServerTypes.tcp.value,\n        \"--modbus-server\",\n        \"-s\",\n        case_sensitive=False,\n        autocompletion=servers,\n        help=\"Modbus Server\",\n    ),\n    modbus_framer: str = typer.Option(\n        ModbusFramerTypes.socket.value,\n        \"--framer\",\n        \"-f\",\n        case_sensitive=False,\n        autocompletion=framers,\n        help=\"Modbus framer to use\",\n    ),\n    modbus_port: int = typer.Option(5020, \"--modbus-port\", \"-p\", help=\"Modbus port\"),\n    modbus_slave_id: List[int] = typer.Option(\n        [1], \"--slave-id\", \"-u\", help=\"Supported Modbus slave id's\"\n    ),\n    modbus_config_path: Path = typer.Option(\n        None, help=\"Path to additional modbus server config\"\n    ),\n    randomize: int = typer.Option(\n        0,\n        \"--random\",\n        \"-r\",\n        help=\"Randomize every `r` reads. 0=never, 1=always,2=every-second-read\"\n        \", and so on. Applicable IR and DI.\",\n    ),\n    change_rate: int = typer.Option(\n        0,\n        \"--change-rate\",\n        \"-c\",\n        help=\"Rate in % registers to change. 0=none, 100=all, 12=12% of registers\"\n        \", and so on. Applicable IR and DI.\",\n    ),\n):\n    \"\"\"Run Reactive Modbus server.\n\n    Exposing REST endpoint for response manipulation.\n    \"\"\"\n    repl = ctx.obj.pop(\"repl\")\n    # TBD extra_args = ctx.args\n    web_app_config = ctx.obj\n    loop = asyncio.get_event_loop()\n    framer = DEFAULT_FRAMER.get(modbus_framer, ModbusSocketFramer)\n    if modbus_config_path:\n        with open(modbus_config_path, encoding=\"utf-8\") as my_file:\n            modbus_config = json.load(my_file)\n    else:\n        modbus_config = DEFAULT_CONFIG\n\n    extra_args = ctx.args\n    data_block_settings = modbus_config.pop(\"data_block_settings\", {})\n    modbus_config = modbus_config.get(modbus_server, {})\n    modbus_config = process_extra_args(extra_args, modbus_config)\n    if modbus_server != \"serial\":\n        handler = modbus_config.pop(\"handler\", \"ModbusConnectedRequestHandler\")\n    else:\n        handler = modbus_config.pop(\"handler\", \"ModbusSingleRequestHandler\")\n    handler = DEFUALT_HANDLERS.get(handler.strip())\n\n    modbus_config[\"handler\"] = handler\n    modbus_config[\"randomize\"] = randomize\n    modbus_config[\"change_rate\"] = change_rate\n    app = ReactiveServer.factory(\n        modbus_server,\n        framer,\n        modbus_port=modbus_port,\n        slave=modbus_slave_id,\n        loop=loop,\n        single=False,\n        data_block_settings=data_block_settings,\n        **web_app_config,\n        **modbus_config,\n    )\n    try:\n        loop.run_until_complete(app.run_async(repl))\n        if repl:\n            loop.run_until_complete(run_repl(app))\n        loop.run_forever()\n\n    except CANCELLED_ERROR:\n        print(\"Done!!!!!\")\n\n\nif __name__ == \"__main__\":\n    app()\n", "path": "pymodbus/repl/server/main.py"}], "after_files": [{"content": "\"\"\"Repl server main.\"\"\"\nimport asyncio\nimport json\nimport logging\nimport sys\nfrom enum import Enum\nfrom pathlib import Path\nfrom typing import List\n\nimport typer\n\nfrom pymodbus import pymodbus_apply_logging_config\nfrom pymodbus.framer.socket_framer import ModbusSocketFramer\nfrom pymodbus.logging import Log\nfrom pymodbus.repl.server.cli import run_repl\nfrom pymodbus.server.reactive.default_config import DEFAULT_CONFIG\nfrom pymodbus.server.reactive.main import (\n    DEFAULT_FRAMER,\n    DEFUALT_HANDLERS,\n    ReactiveServer,\n)\n\n\nCANCELLED_ERROR = asyncio.exceptions.CancelledError\nCONTEXT_SETTING = {\"allow_extra_args\": True, \"ignore_unknown_options\": True}\n\n\n# TBD class ModbusServerConfig:\n\n\nclass ModbusServerTypes(str, Enum):\n    \"\"\"Server types.\"\"\"\n\n    # [\"tcp\", \"serial\", \"tls\", \"udp\"]\n    tcp = \"tcp\"  # pylint: disable=invalid-name\n    serial = \"serial\"  # pylint: disable=invalid-name\n    tls = \"tls\"  # pylint: disable=invalid-name\n    udp = \"udp\"  # pylint: disable=invalid-name\n\n\nclass ModbusFramerTypes(str, Enum):\n    \"\"\"Framer types.\"\"\"\n\n    # [\"socket\", \"rtu\", \"tls\", \"ascii\", \"binary\"]\n    socket = \"socket\"  # pylint: disable=invalid-name\n    rtu = \"rtu\"  # pylint: disable=invalid-name\n    tls = \"tls\"  # pylint: disable=invalid-name\n    ascii = \"ascii\"  # pylint: disable=invalid-name\n    binary = \"binary\"  # pylint: disable=invalid-name\n\n\ndef _completer(incomplete: str, valid_values: List[str]) -> List[str]:\n    \"\"\"Complete value.\"\"\"\n    completion = []\n    for name in valid_values:\n        if name.startswith(incomplete):\n            completion.append(name)\n    return completion\n\n\ndef framers(incomplete: str) -> List[str]:\n    \"\"\"Return an autocompleted list of supported clouds.\"\"\"\n    _framers = [\"socket\", \"rtu\", \"tls\", \"ascii\", \"binary\"]\n    return _completer(incomplete, _framers)\n\n\ndef servers(incomplete: str) -> List[str]:\n    \"\"\"Return an autocompleted list of supported clouds.\"\"\"\n    _servers = [\"tcp\", \"serial\", \"tls\", \"udp\"]\n    return _completer(incomplete, _servers)\n\n\ndef process_extra_args(extra_args: List[str], modbus_config: dict) -> dict:\n    \"\"\"Process extra args passed to server.\"\"\"\n    options_stripped = [x.strip().replace(\"--\", \"\") for x in extra_args[::2]]\n    extra_args_dict = dict(list(zip(options_stripped, extra_args[1::2])))\n    for option, value in extra_args_dict.items():\n        if option in modbus_config:\n            try:\n                modbus_config[option] = type(modbus_config[option])(value)\n            except ValueError as err:\n                Log.error(\n                    \"Error parsing extra arg {} with value '{}'. {}\", option, value, err\n                )\n                sys.exit(1)\n    return modbus_config\n\n\napp = typer.Typer(\n    no_args_is_help=True,\n    context_settings=CONTEXT_SETTING,\n    help=\"Reactive Modbus server\",\n)\n\n\n@app.callback()\ndef server(\n    ctx: typer.Context,\n    host: str = typer.Option(\"localhost\", \"--host\", help=\"Host address\"),\n    web_port: int = typer.Option(8080, \"--web-port\", help=\"Web app port\"),\n    broadcast_support: bool = typer.Option(\n        False, \"-b\", help=\"Support broadcast messages\"\n    ),\n    repl: bool = typer.Option(True, help=\"Enable/Disable repl for server\"),\n    verbose: bool = typer.Option(\n        False, help=\"Run with debug logs enabled for pymodbus\"\n    ),\n):\n    \"\"\"Run server code.\"\"\"\n    log_level = logging.DEBUG if verbose else logging.ERROR\n    pymodbus_apply_logging_config(log_level)\n\n    ctx.obj = {\n        \"repl\": repl,\n        \"host\": host,\n        \"web_port\": web_port,\n        \"broadcast\": broadcast_support,\n    }\n\n\n@app.command(\"run\", context_settings=CONTEXT_SETTING)\ndef run(\n    ctx: typer.Context,\n    modbus_server: str = typer.Option(\n        ModbusServerTypes.tcp.value,\n        \"--modbus-server\",\n        \"-s\",\n        case_sensitive=False,\n        autocompletion=servers,\n        help=\"Modbus Server\",\n    ),\n    modbus_framer: str = typer.Option(\n        ModbusFramerTypes.socket.value,\n        \"--framer\",\n        \"-f\",\n        case_sensitive=False,\n        autocompletion=framers,\n        help=\"Modbus framer to use\",\n    ),\n    modbus_port: int = typer.Option(5020, \"--modbus-port\", \"-p\", help=\"Modbus port\"),\n    modbus_slave_id: List[int] = typer.Option(\n        [1], \"--slave-id\", \"-u\", help=\"Supported Modbus slave id's\"\n    ),\n    modbus_config_path: Path = typer.Option(\n        None, help=\"Path to additional modbus server config\"\n    ),\n    randomize: int = typer.Option(\n        0,\n        \"--random\",\n        \"-r\",\n        help=\"Randomize every `r` reads. 0=never, 1=always,2=every-second-read\"\n        \", and so on. Applicable IR and DI.\",\n    ),\n    change_rate: int = typer.Option(\n        0,\n        \"--change-rate\",\n        \"-c\",\n        help=\"Rate in % registers to change. 0=none, 100=all, 12=12% of registers\"\n        \", and so on. Applicable IR and DI.\",\n    ),\n):\n    \"\"\"Run Reactive Modbus server.\n\n    Exposing REST endpoint for response manipulation.\n    \"\"\"\n    repl = ctx.obj.pop(\"repl\")\n    # TBD extra_args = ctx.args\n    web_app_config = ctx.obj\n    loop = asyncio.get_event_loop()\n    framer = DEFAULT_FRAMER.get(modbus_framer, ModbusSocketFramer)\n    if modbus_config_path:\n        with open(modbus_config_path, encoding=\"utf-8\") as my_file:\n            modbus_config = json.load(my_file)\n    else:\n        modbus_config = DEFAULT_CONFIG\n\n    extra_args = ctx.args\n    data_block_settings = modbus_config.pop(\"data_block_settings\", {})\n    modbus_config = modbus_config.get(modbus_server, {})\n    modbus_config = process_extra_args(extra_args, modbus_config)\n    if modbus_server != \"serial\":\n        handler = modbus_config.pop(\"handler\", \"ModbusConnectedRequestHandler\")\n    else:\n        handler = modbus_config.pop(\"handler\", \"ModbusSingleRequestHandler\")\n    handler = DEFUALT_HANDLERS.get(handler.strip())\n\n    modbus_config[\"handler\"] = handler\n    modbus_config[\"randomize\"] = randomize\n    modbus_config[\"change_rate\"] = change_rate\n    app = ReactiveServer.factory(\n        modbus_server,\n        framer,\n        modbus_port=modbus_port,\n        slave=modbus_slave_id,\n        loop=loop,\n        single=False,\n        data_block_settings=data_block_settings,\n        **web_app_config,\n        **modbus_config,\n    )\n    if repl:\n        loop.run_until_complete(run_repl(app))\n    else:\n        loop.run_until_complete(app.run_async(repl))\n        loop.run_forever()\n\n\nif __name__ == \"__main__\":\n    app()\n", "path": "pymodbus/repl/server/main.py"}]}