1# Copyright (c) 2025 Pete Johanson
2#
3# SPDX-License-Identifier: Apache-2.0
4#
5# pylint: disable=duplicate-code
6
7'''Runner for rfp.'''
8
9import platform
10import re
11from pathlib import Path
12
13from runners.core import RunnerCaps, ZephyrBinaryRunner
14
15if platform.system() == 'Darwin' or 'Windows':
16    DEFAULT_RFP_PORT = None
17else:
18    DEFAULT_RFP_PORT = '/dev/ttyACM0'
19
20RFP_CLI_EXE = 'rfp-cli'
21
22
23def to_num(number):
24    dev_match = re.search(r"^\d*\+dev", number)
25    dev_version = dev_match is not None
26
27    num_match = re.search(r"^\d*", number)
28    num = int(num_match.group(0))
29
30    if dev_version:
31        num += 1
32
33    return num
34
35
36class RfpBinaryRunner(ZephyrBinaryRunner):
37    '''Runner front-end for rfp.'''
38
39    def __init__(
40        self,
41        cfg,
42        rfp_cli='rfp-cli',
43        device=None,
44        erase=False,
45        verify=False,
46        port=DEFAULT_RFP_PORT,
47        tool=None,
48        interface=None,
49        speed=None,
50    ):
51        super().__init__(cfg)
52
53        self.rfp_cmd = [rfp_cli]
54        self.verify = verify
55        self.erase = erase
56        self.port = port
57        self.tool = tool
58        self.interface = interface
59        self.device = device
60        self.speed = speed
61
62    @classmethod
63    def name(cls):
64        return 'rfp'
65
66    @classmethod
67    def capabilities(cls):
68        return RunnerCaps(commands={'flash'}, erase=True)
69
70    @classmethod
71    def do_add_parser(cls, parser):
72        # Find the default efp-cli executable
73        cls.default_rfp()
74
75        parser.add_argument(
76            '--rfp-cli', default=RFP_CLI_EXE, help='path to rfp-cli, default is rfp-cli'
77        )
78        parser.add_argument(
79            '--port',
80            default=DEFAULT_RFP_PORT,
81            help='serial port to use, default is ' + str(DEFAULT_RFP_PORT),
82        )
83        parser.add_argument(
84            '--tool',
85            help='emulator hardware to use (e2, e2l, jlink) when port is not set',
86        )
87        parser.add_argument(
88            '--interface',
89            help='selects the communications interface (uart, swd)',
90        )
91        parser.add_argument('--device', help='Specify the device type to pass to rfp-cli')
92        parser.add_argument('--verify', action='store_true', help='if given, verify after flash')
93        parser.add_argument('--speed', help='Specify the serial port speed')
94
95    @classmethod
96    def do_create(cls, cfg, args):
97        return RfpBinaryRunner(
98            cfg,
99            rfp_cli=args.rfp_cli,
100            device=args.device,
101            port=args.port,
102            tool=args.tool,
103            interface=args.interface,
104            erase=args.erase,
105            speed=args.speed,
106            verify=args.verify,
107        )
108
109    @staticmethod
110    def default_rfp():
111        global RFP_CLI_EXE
112
113        if platform.system() == 'Windows':
114            try:
115                import winreg
116
117                registry = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
118                key = winreg.OpenKey(registry, r"SOFTWARE\Classes\rpjfile\shell\Open\command")
119                val = winreg.QueryValue(key, None)
120                match = re.match(r'"(.*?)".*', val)[1]
121                RFP_CLI_EXE = str(Path(match).parent / 'rfp-cli.exe')
122            except Exception:
123                RFP_CLI_EXE = 'rfp-cli.exe'
124        else:
125            RFP_CLI_EXE = 'rfp-cli'
126
127    def do_run(self, command, **kwargs):
128        if command == 'flash':
129            self.do_flash(**kwargs)
130        else:
131            self.logger.error("Unsuppported command")
132
133    def do_flash(self, **kwargs):
134        self.require(self.rfp_cmd[0])
135        self.ensure_output('hex')
136
137        hex_name = self.cfg.hex_file
138
139        self.logger.info(f'Flashing file: {hex_name}')
140
141        load_image = ['-run']
142        if self.erase:
143            load_image += ['-erase']
144        else:
145            load_image += ['-noerase']
146
147        if self.verify:
148            load_image += ['-v']
149
150        # Load image
151        load_image += ['-p', '-file', hex_name]
152
153        if self.tool is None:
154            connection = ['-port', self.port]
155        else:
156            connection = ['-tool', self.tool]
157
158        if self.interface:
159            connection += ['-interface', self.interface]
160
161        if self.speed:
162            connection += ['-s', self.speed]
163
164        device = ['-device', self.device]
165
166        cmd = self.rfp_cmd + connection + device + load_image
167        self.check_call(cmd)
168