Browse code

Replace third party `docopt` with `argparse`

Robert Cranston authored on 09/05/2025 15:03:38
Showing 1 changed files
... ...
@@ -1,32 +1,10 @@
1 1
 #!/usr/bin/env python3
2 2
 
3
-## Help
4
-
5
-"""
6
-usage:
7
-  raspi-revcode [<code>]
8
-  raspi-revcode -h|--help
9
-  raspi-revcode --version
10
-
11
-Parse Raspberry Pi revision codes.
12
-
13
-positional arguments:
14
-  <code>
15
-    A hexadecimal revision code to parse. If not given, '/proc/cpuinfo' will be
16
-    used to get the revision code of the current device.
17
-
18
-optional arguments:
19
-  -h, --help
20
-    Show this help message and exit.
21
-  --version
22
-    Show program's version number and exit.
23
-"""
24
-
25 3
 ## Imports
26 4
 
27 5
 import sys
28 6
 import re
29
-import docopt
7
+import argparse
30 8
 
31 9
 ## Constants
32 10
 
... ...
@@ -176,7 +154,15 @@ def parse(code):
176 154
 
177 155
 def main():
178 156
     # Parse arguments.
179
-    args = docopt.docopt(__doc__, version=VERSION)
157
+    parser = argparse.ArgumentParser(description="""
158
+        Parse Raspberry Pi revision codes.
159
+    """);
160
+    parser.add_argument('--version', action='version', version=VERSION)
161
+    parser.add_argument('<code>', nargs='?', help="""
162
+        A hexadecimal revision code to parse. If not given, '/proc/cpuinfo'
163
+        will be used to get the revision code of the current device.
164
+    """)
165
+    args = vars(parser.parse_args())
180 166
     # Get code string from argument.
181 167
     code = args['<code>']
182 168
     # Get code string from system.
Browse code

Add implementation

Robert Cranston authored on 29/10/2023 05:38:08
Showing 1 changed files
1 1
new file mode 100755
... ...
@@ -0,0 +1,205 @@
1
+#!/usr/bin/env python3
2
+
3
+## Help
4
+
5
+"""
6
+usage:
7
+  raspi-revcode [<code>]
8
+  raspi-revcode -h|--help
9
+  raspi-revcode --version
10
+
11
+Parse Raspberry Pi revision codes.
12
+
13
+positional arguments:
14
+  <code>
15
+    A hexadecimal revision code to parse. If not given, '/proc/cpuinfo' will be
16
+    used to get the revision code of the current device.
17
+
18
+optional arguments:
19
+  -h, --help
20
+    Show this help message and exit.
21
+  --version
22
+    Show program's version number and exit.
23
+"""
24
+
25
+## Imports
26
+
27
+import sys
28
+import re
29
+import docopt
30
+
31
+## Constants
32
+
33
+VERSION = '1.0.0'
34
+
35
+# https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes
36
+CPUINFO_PATH = '/proc/cpuinfo'
37
+CPUINFO_RE   = r'^Revision\s*:\s*(.*)$'
38
+
39
+FIELD_RE = r'((.)\2*)'
40
+FIELDS = [
41
+    # Old-style.
42
+    # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#old-style-revision-codes
43
+    # https://forums.raspberrypi.com/viewtopic.php?p=176865#p177014
44
+    'uuuuuuuWFuuuuuuuIIIIIIIIIIIIIIII',
45
+    # New-style.
46
+    # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#new-style-revision-codes
47
+    'NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR',
48
+]
49
+
50
+SYMBOL_UNUSED   = 'u'
51
+SYMBOL_NEW_FLAG = 'F'
52
+SYMBOLS = {
53
+    'I':       [["Type", "Revision", "Memory size", "Manufacturer"], {
54
+        0x0002: ["B",    "1.0",      "256MB",       "Egoman"      ],
55
+        0x0003: ["B",    "1.0",      "256MB",       "Egoman"      ],
56
+        0x0004: ["B",    "2.0",      "256MB",       "Sony UK"     ],
57
+        0x0005: ["B",    "2.0",      "256MB",       "Qisda"       ],
58
+        0x0006: ["B",    "2.0",      "256MB",       "Egoman"      ],
59
+        0x0007: ["A",    "2.0",      "256MB",       "Egoman"      ],
60
+        0x0008: ["A",    "2.0",      "256MB",       "Sony UK"     ],
61
+        0x0009: ["A",    "2.0",      "256MB",       "Qisda"       ],
62
+        0x000d: ["B",    "2.0",      "512MB",       "Egoman"      ],
63
+        0x000e: ["B",    "2.0",      "512MB",       "Sony UK"     ],
64
+        0x000f: ["B",    "2.0",      "512MB",       "Egoman"      ],
65
+        0x0010: ["B+",   "1.2",      "512MB",       "Sony UK"     ],
66
+        0x0011: ["CM1",  "1.0",      "512MB",       "Sony UK"     ],
67
+        0x0012: ["A+",   "1.1",      "256MB",       "Sony UK"     ],
68
+        0x0013: ["B+",   "1.2",      "512MB",       "Embest"      ],
69
+        0x0014: ["CM1",  "1.0",      "512MB",       "Embest"      ],
70
+        0x0015: ["A+",   "1.1",      "256MB/512MB", "Embest"      ],
71
+    }],
72
+    'R':       [["Revision"], {
73
+        rev:    [f"1.{rev}"] for rev in range(2**len('RRRR'))
74
+    }],
75
+    'T':       [["Type"], {
76
+        0x00:   ["A"],
77
+        0x01:   ["B"],
78
+        0x02:   ["A+"],
79
+        0x03:   ["B+"],
80
+        0x04:   ["2B"],
81
+        0x05:   ["Alpha (early prototype)"],
82
+        0x06:   ["CM1"],
83
+        0x08:   ["3B"],
84
+        0x09:   ["Zero"],
85
+        0x0a:   ["CM3"],
86
+        0x0c:   ["Zero W"],
87
+        0x0d:   ["3B+"],
88
+        0x0e:   ["3A+"],
89
+        0x0f:   ["Internal use only"],
90
+        0x10:   ["CM3+"],
91
+        0x11:   ["4B"],
92
+        0x12:   ["Zero 2 W"],
93
+        0x13:   ["400"],
94
+        0x14:   ["CM4"],
95
+        0x15:   ["CM4S"],
96
+        0x16:   ["Internal use only"],
97
+        0x17:   ["5"],
98
+        0x18:   ["CM5"],
99
+        0x19:   ["500"],
100
+        0x1a:   ["CM5 Lite"],
101
+    }],
102
+    'P':       [["Processor"], {
103
+        0x0:    ["BCM2835"],
104
+        0x1:    ["BCM2836"],
105
+        0x2:    ["BCM2837"],
106
+        0x3:    ["BCM2711"],
107
+        0x4:    ["BCM2712"],
108
+    }],
109
+    'C':       [["Manufacturer"], {
110
+        0x0:    ["Sony UK"],
111
+        0x1:    ["Egoman"],
112
+        0x2:    ["Embest"],
113
+        0x3:    ["Sony Japan"],
114
+        0x4:    ["Embest"],
115
+        0x5:    ["Stadium"],
116
+    }],
117
+    'M':       [["Memory size"], {
118
+        0x0:    ["256MB"],
119
+        0x1:    ["512MB"],
120
+        0x2:    ["1GB"],
121
+        0x3:    ["2GB"],
122
+        0x4:    ["4GB"],
123
+        0x5:    ["8GB"],
124
+        0x6:    ["16GB"],
125
+    }],
126
+    'F':       [["New flag"], {
127
+        0x0:    ["Old-style revision"],
128
+        0x1:    ["New-style revision"],
129
+    }],
130
+    'W':       [["Warranty bit"], {
131
+        0x0:    ["Warranty is intact"],
132
+        0x1:    ["Warranty has been voided by overclocking"],
133
+    }],
134
+    'Q':       [["OTP Read"], {
135
+        0x0:    ["OTP reading allowed"],
136
+        0x1:    ["OTP reading disallowed"],
137
+    }],
138
+    'O':      [["OTP Program"], {
139
+        0x0:   ["OTP programming allowed"],
140
+        0x1:   ["OTP programming disallowed"],
141
+    }],
142
+    'N':      [["Overvoltage"], {
143
+        0x0:   ["Overvoltage allowed"],
144
+        0x1:   ["Overvoltage disallowed"],
145
+    }],
146
+}
147
+
148
+## Helpers
149
+
150
+def error(msg):
151
+    print(msg, file=sys.stderr)
152
+    exit(1)
153
+
154
+def unexpected(name, value):
155
+    return name, f"<UNEXPECTED 0x{value:x}>"
156
+
157
+def parse(code):
158
+    new_flag = (code >> FIELDS[0][::-1].index(SYMBOL_NEW_FLAG)) & 1
159
+    fields   = FIELDS[new_flag]
160
+    for field, symbol in re.findall(FIELD_RE, fields[::-1]):
161
+        length = len(field)
162
+        value  = code & ((1<<length)-1)
163
+        code   = code >> length
164
+        if symbol == SYMBOL_UNUSED:
165
+            if value != 0:
166
+                yield unexpected("Unused", value)
167
+            continue
168
+        names, values = SYMBOLS[symbol]
169
+        descs = values.get(value)
170
+        if not descs:
171
+            yield unexpected(names[0], value)
172
+            continue
173
+        yield from zip(names, descs)
174
+
175
+## Main
176
+
177
+def main():
178
+    # Parse arguments.
179
+    args = docopt.docopt(__doc__, version=VERSION)
180
+    # Get code string from argument.
181
+    code = args['<code>']
182
+    # Get code string from system.
183
+    if not code:
184
+        try:
185
+            with open(CPUINFO_PATH) as file:
186
+                match = re.search(CPUINFO_RE, file.read(), re.MULTILINE)
187
+        except IOError:
188
+            error(f"Could not open '{CPUINFO_PATH}'.")
189
+        if not match:
190
+            error(f"Could not find revision code in '{CPUINFO_PATH}'.")
191
+        code = match.group(1)
192
+    # Parse code string.
193
+    try:
194
+        code = int(code, 16)
195
+    except ValueError:
196
+        error(f"Could not parse revision code {code} as a hexadecimal number.")
197
+    # Parse code.
198
+    names_descs = list(parse(code))
199
+    # Print.
200
+    width = max(len(name) for name, _ in names_descs)
201
+    for name, desc in names_descs:
202
+        print(f"{name:{width}} : {desc}")
203
+
204
+if __name__ == '__main__':
205
+    main()