host.nfc_tool.write_tab
Write tab — battery data write workflow with UID verification.
1"""Write tab — battery data write workflow with UID verification.""" 2 3import datetime 4import time 5import tkinter as tk 6from tkinter import ttk, messagebox 7 8from host.nfc_tool.battery_data import BatteryData 9 10 11# Write workflow states 12IDLE = 'idle' 13SEARCHING = 'searching' 14TAG_FOUND = 'tag_found' 15VERIFYING = 'verifying' 16WRITING = 'writing' 17DONE = 'done' 18ERROR = 'error' 19 20# Verify retry settings 21VERIFY_RETRY_INTERVAL_MS = 1000 # 1 second between retries 22VERIFY_TIMEOUT_S = 10 # give up after 10 seconds 23 24 25class WriteTab(ttk.Frame): 26 """Multi-step write workflow with tag verification safety.""" 27 28 def __init__(self, parent, request_poll=None, request_write=None): 29 super().__init__(parent) 30 self._request_poll = request_poll 31 self._request_write = request_write 32 self._state = IDLE 33 self._found_uid = None # UID captured when tag first detected 34 self._found_tag_data = None # Full tag data from detection 35 self._poll_id = None 36 self._verify_start_time = 0 37 38 self._build_ui() 39 40 def _build_ui(self): 41 # Form section 42 form_frame = ttk.LabelFrame(self, text="Battery Information") 43 form_frame.pack(fill=tk.X, padx=10, pady=(10, 5)) 44 45 ttk.Label(form_frame, text="Serial Number:").grid( 46 row=0, column=0, sticky=tk.W, padx=(8, 4), pady=4) 47 self._sn_var = tk.StringVar() 48 self._sn_entry = ttk.Entry(form_frame, textvariable=self._sn_var, 49 width=30) 50 self._sn_entry.grid(row=0, column=1, sticky=tk.W, pady=4) 51 52 ttk.Label(form_frame, text="Year:").grid( 53 row=1, column=0, sticky=tk.W, padx=(8, 4), pady=4) 54 current_year = datetime.datetime.now().year 55 years = [str(y) for y in 56 range(current_year + 1, current_year - 6, -1)] 57 self._year_var = tk.StringVar(value=str(current_year)) 58 self._year_combo = ttk.Combobox( 59 form_frame, textvariable=self._year_var, 60 values=years, width=8, 61 ) 62 self._year_combo.grid(row=1, column=1, sticky=tk.W, pady=4) 63 64 ttk.Label(form_frame, text="Note:").grid( 65 row=2, column=0, sticky=tk.W, padx=(8, 4), pady=4) 66 self._note_var = tk.StringVar() 67 self._note_entry = ttk.Entry( 68 form_frame, textvariable=self._note_var, width=40) 69 self._note_entry.grid(row=2, column=1, sticky=tk.W, pady=4) 70 71 ttk.Label(form_frame, text="URI:").grid( 72 row=3, column=0, sticky=tk.W, padx=(8, 4), pady=4) 73 self._uri_var = tk.StringVar(value="http://www.raptacon.org") 74 self._uri_entry = ttk.Entry( 75 form_frame, textvariable=self._uri_var, width=40) 76 self._uri_entry.grid(row=3, column=1, sticky=tk.W, pady=4) 77 78 # Buttons 79 btn_frame = ttk.Frame(self) 80 btn_frame.pack(fill=tk.X, padx=10, pady=5) 81 82 self._start_btn = ttk.Button( 83 btn_frame, text="Start Write", 84 command=self._on_start_write, 85 ) 86 self._start_btn.pack(side=tk.LEFT) 87 88 self._cancel_btn = ttk.Button( 89 btn_frame, text="Cancel", 90 command=self._on_cancel, 91 state='disabled', 92 ) 93 self._cancel_btn.pack(side=tk.LEFT, padx=(8, 0)) 94 95 # Status / log area 96 log_frame = ttk.LabelFrame(self, text="Write Progress") 97 log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10)) 98 99 self._log_text = tk.Text(log_frame, height=10, wrap=tk.WORD, 100 state='disabled', font=('Consolas', 9)) 101 scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, 102 command=self._log_text.yview) 103 self._log_text.configure(yscrollcommand=scrollbar.set) 104 self._log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, 105 padx=(4, 0), pady=4) 106 scrollbar.pack(side=tk.RIGHT, fill=tk.Y, padx=(0, 4), pady=4) 107 108 def _log(self, msg): 109 self._log_text.configure(state='normal') 110 self._log_text.insert(tk.END, msg + '\n') 111 self._log_text.see(tk.END) 112 self._log_text.configure(state='disabled') 113 114 def _clear_log(self): 115 self._log_text.configure(state='normal') 116 self._log_text.delete('1.0', tk.END) 117 self._log_text.configure(state='disabled') 118 119 def _set_state(self, state): 120 self._state = state 121 form_enabled = state in (IDLE, DONE, ERROR) 122 entry_state = 'normal' if form_enabled else 'disabled' 123 self._sn_entry.configure(state=entry_state) 124 self._year_combo.configure( 125 state='normal' if form_enabled else 'disabled') 126 self._note_entry.configure(state=entry_state) 127 self._uri_entry.configure(state=entry_state) 128 129 self._start_btn.configure( 130 state='normal' if form_enabled else 'disabled') 131 self._cancel_btn.configure( 132 state='normal' if state == SEARCHING else 'disabled') 133 134 # --- User actions --- 135 136 def _on_start_write(self): 137 sn = self._sn_var.get().strip() 138 if not sn: 139 messagebox.showwarning( 140 "Missing Data", "Serial number is required.", 141 parent=self) 142 return 143 144 self._clear_log() 145 self._found_uid = None 146 self._found_tag_data = None 147 self._set_state(SEARCHING) 148 self._log("Searching for tag... Place tag on reader.") 149 self._start_search_poll() 150 151 def _on_cancel(self): 152 self._stop_poll() 153 self._set_state(IDLE) 154 self._log("Cancelled.") 155 156 # --- Polling helpers --- 157 158 def _start_search_poll(self): 159 self._stop_poll() 160 self._do_search_poll() 161 162 def _stop_poll(self): 163 if self._poll_id is not None: 164 self.after_cancel(self._poll_id) 165 self._poll_id = None 166 167 def _do_search_poll(self): 168 if self._state != SEARCHING: 169 return 170 if self._request_poll: 171 self._request_poll('write_search') 172 self._poll_id = self.after(300, self._do_search_poll) 173 174 def _do_verify_poll(self): 175 """Poll for tag during verify phase, with timeout.""" 176 if self._state != VERIFYING: 177 return 178 elapsed = time.monotonic() - self._verify_start_time 179 if elapsed >= VERIFY_TIMEOUT_S: 180 # Timed out 181 self._set_state(ERROR) 182 messagebox.showerror( 183 "Write Failed", 184 "Tag not detected after 10 seconds.\n\n" 185 "Make sure the tag is placed on the reader " 186 "and try again.", 187 parent=self) 188 self._log("Write failed: tag not detected (timed out).") 189 return 190 if self._request_poll: 191 self._request_poll('write_verify') 192 self._poll_id = self.after(VERIFY_RETRY_INTERVAL_MS, 193 self._do_verify_poll) 194 195 # --- Tab lifecycle --- 196 197 def on_tab_selected(self): 198 pass 199 200 def on_tab_deselected(self): 201 if self._state in (SEARCHING, VERIFYING): 202 self._on_cancel() 203 204 # --- Results from worker --- 205 206 def on_search_result(self, tag_data): 207 """Called when searching for a tag during write workflow.""" 208 if self._state != SEARCHING: 209 return 210 211 if tag_data is None: 212 return # Keep searching 213 214 self._stop_poll() 215 self._found_uid = tag_data.uid_hex 216 self._found_tag_data = tag_data 217 self._set_state(TAG_FOUND) 218 219 # Build the confirmation message 220 self._log(f"Tag found! UID: {tag_data.uid_hex}") 221 222 existing_info = "" 223 if len(tag_data.user_data) > 0: 224 text = tag_data.get_text() 225 uri = tag_data.get_uri() 226 if text: 227 self._log(f"Existing text: {text}") 228 existing_info += f"Existing text: {text}\n" 229 if uri: 230 self._log(f"Existing URI: {uri}") 231 existing_info += f"Existing URI: {uri}\n" 232 if not text and not uri: 233 self._log("Tag is blank (no NDEF data).") 234 existing_info = "Tag is currently blank.\n" 235 else: 236 self._log("Could not read tag data (UID only).") 237 existing_info = "Could not read existing data.\n" 238 239 # Build new data preview 240 bat = BatteryData( 241 sn=self._sn_var.get().strip(), 242 year=self._year_var.get().strip(), 243 note=self._note_var.get().strip(), 244 ) 245 new_uri = self._uri_var.get().strip() 246 247 # Show confirmation dialog 248 msg = ( 249 f"Tag detected: UID {tag_data.uid_hex}\n\n" 250 f"{existing_info}\n" 251 f"--- Write the following? ---\n" 252 f"URI: {new_uri}\n" 253 f"{bat.to_text()}\n\n" 254 f"Proceed with write?" 255 ) 256 257 proceed = messagebox.askyesno( 258 "Confirm Write", msg, parent=self) 259 260 if proceed: 261 self._log("") 262 self._log("Write confirmed. Verifying tag...") 263 self._set_state(VERIFYING) 264 self._verify_start_time = time.monotonic() 265 self._do_verify_poll() 266 else: 267 self._set_state(IDLE) 268 self._log("Write cancelled by user.") 269 270 def on_verify_result(self, tag_data): 271 """Called during write verification — check UID matches.""" 272 if self._state != VERIFYING: 273 return 274 275 if tag_data is None: 276 # Tag not found yet — retry will happen via _do_verify_poll 277 elapsed = time.monotonic() - self._verify_start_time 278 remaining = max(0, VERIFY_TIMEOUT_S - elapsed) 279 self._log(f"Tag not detected, retrying... " 280 f"({remaining:.0f}s remaining)") 281 return 282 283 self._stop_poll() 284 285 current_uid = tag_data.uid_hex 286 if current_uid != self._found_uid: 287 self._set_state(ERROR) 288 messagebox.showerror( 289 "Write Failed", 290 f"Tag changed during write!\n\n" 291 f"Expected UID: {self._found_uid}\n" 292 f"Detected UID: {current_uid}\n\n" 293 f"Place the original tag back and try again.", 294 parent=self) 295 self._log( 296 f"Write failed: tag changed " 297 f"(expected {self._found_uid}, " 298 f"got {current_uid})") 299 return 300 301 # UID matches — proceed with write 302 self._set_state(WRITING) 303 self._log("Tag verified. Writing...") 304 if self._request_write: 305 self._request_write( 306 sn=self._sn_var.get().strip(), 307 year=self._year_var.get().strip(), 308 note=self._note_var.get().strip(), 309 uri=self._uri_var.get().strip(), 310 ) 311 312 def on_write_result(self, success, error_msg=''): 313 """Called when write operation completes.""" 314 if success: 315 self._set_state(DONE) 316 self._log("Write successful!") 317 self._log("Tag has been programmed. You may remove it.") 318 messagebox.showinfo( 319 "Write Complete", 320 "Tag has been successfully programmed!", 321 parent=self) 322 else: 323 self._set_state(ERROR) 324 self._log(f"Write failed: {error_msg}") 325 messagebox.showerror( 326 "Write Failed", 327 f"Tag write failed:\n\n{error_msg}\n\n" 328 f"Click 'Start Write' to try again.", 329 parent=self)
IDLE =
'idle'
SEARCHING =
'searching'
TAG_FOUND =
'tag_found'
VERIFYING =
'verifying'
WRITING =
'writing'
DONE =
'done'
ERROR =
'error'
VERIFY_RETRY_INTERVAL_MS =
1000
VERIFY_TIMEOUT_S =
10
class
WriteTab(tkinter.ttk.Frame):
26class WriteTab(ttk.Frame): 27 """Multi-step write workflow with tag verification safety.""" 28 29 def __init__(self, parent, request_poll=None, request_write=None): 30 super().__init__(parent) 31 self._request_poll = request_poll 32 self._request_write = request_write 33 self._state = IDLE 34 self._found_uid = None # UID captured when tag first detected 35 self._found_tag_data = None # Full tag data from detection 36 self._poll_id = None 37 self._verify_start_time = 0 38 39 self._build_ui() 40 41 def _build_ui(self): 42 # Form section 43 form_frame = ttk.LabelFrame(self, text="Battery Information") 44 form_frame.pack(fill=tk.X, padx=10, pady=(10, 5)) 45 46 ttk.Label(form_frame, text="Serial Number:").grid( 47 row=0, column=0, sticky=tk.W, padx=(8, 4), pady=4) 48 self._sn_var = tk.StringVar() 49 self._sn_entry = ttk.Entry(form_frame, textvariable=self._sn_var, 50 width=30) 51 self._sn_entry.grid(row=0, column=1, sticky=tk.W, pady=4) 52 53 ttk.Label(form_frame, text="Year:").grid( 54 row=1, column=0, sticky=tk.W, padx=(8, 4), pady=4) 55 current_year = datetime.datetime.now().year 56 years = [str(y) for y in 57 range(current_year + 1, current_year - 6, -1)] 58 self._year_var = tk.StringVar(value=str(current_year)) 59 self._year_combo = ttk.Combobox( 60 form_frame, textvariable=self._year_var, 61 values=years, width=8, 62 ) 63 self._year_combo.grid(row=1, column=1, sticky=tk.W, pady=4) 64 65 ttk.Label(form_frame, text="Note:").grid( 66 row=2, column=0, sticky=tk.W, padx=(8, 4), pady=4) 67 self._note_var = tk.StringVar() 68 self._note_entry = ttk.Entry( 69 form_frame, textvariable=self._note_var, width=40) 70 self._note_entry.grid(row=2, column=1, sticky=tk.W, pady=4) 71 72 ttk.Label(form_frame, text="URI:").grid( 73 row=3, column=0, sticky=tk.W, padx=(8, 4), pady=4) 74 self._uri_var = tk.StringVar(value="http://www.raptacon.org") 75 self._uri_entry = ttk.Entry( 76 form_frame, textvariable=self._uri_var, width=40) 77 self._uri_entry.grid(row=3, column=1, sticky=tk.W, pady=4) 78 79 # Buttons 80 btn_frame = ttk.Frame(self) 81 btn_frame.pack(fill=tk.X, padx=10, pady=5) 82 83 self._start_btn = ttk.Button( 84 btn_frame, text="Start Write", 85 command=self._on_start_write, 86 ) 87 self._start_btn.pack(side=tk.LEFT) 88 89 self._cancel_btn = ttk.Button( 90 btn_frame, text="Cancel", 91 command=self._on_cancel, 92 state='disabled', 93 ) 94 self._cancel_btn.pack(side=tk.LEFT, padx=(8, 0)) 95 96 # Status / log area 97 log_frame = ttk.LabelFrame(self, text="Write Progress") 98 log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10)) 99 100 self._log_text = tk.Text(log_frame, height=10, wrap=tk.WORD, 101 state='disabled', font=('Consolas', 9)) 102 scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, 103 command=self._log_text.yview) 104 self._log_text.configure(yscrollcommand=scrollbar.set) 105 self._log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, 106 padx=(4, 0), pady=4) 107 scrollbar.pack(side=tk.RIGHT, fill=tk.Y, padx=(0, 4), pady=4) 108 109 def _log(self, msg): 110 self._log_text.configure(state='normal') 111 self._log_text.insert(tk.END, msg + '\n') 112 self._log_text.see(tk.END) 113 self._log_text.configure(state='disabled') 114 115 def _clear_log(self): 116 self._log_text.configure(state='normal') 117 self._log_text.delete('1.0', tk.END) 118 self._log_text.configure(state='disabled') 119 120 def _set_state(self, state): 121 self._state = state 122 form_enabled = state in (IDLE, DONE, ERROR) 123 entry_state = 'normal' if form_enabled else 'disabled' 124 self._sn_entry.configure(state=entry_state) 125 self._year_combo.configure( 126 state='normal' if form_enabled else 'disabled') 127 self._note_entry.configure(state=entry_state) 128 self._uri_entry.configure(state=entry_state) 129 130 self._start_btn.configure( 131 state='normal' if form_enabled else 'disabled') 132 self._cancel_btn.configure( 133 state='normal' if state == SEARCHING else 'disabled') 134 135 # --- User actions --- 136 137 def _on_start_write(self): 138 sn = self._sn_var.get().strip() 139 if not sn: 140 messagebox.showwarning( 141 "Missing Data", "Serial number is required.", 142 parent=self) 143 return 144 145 self._clear_log() 146 self._found_uid = None 147 self._found_tag_data = None 148 self._set_state(SEARCHING) 149 self._log("Searching for tag... Place tag on reader.") 150 self._start_search_poll() 151 152 def _on_cancel(self): 153 self._stop_poll() 154 self._set_state(IDLE) 155 self._log("Cancelled.") 156 157 # --- Polling helpers --- 158 159 def _start_search_poll(self): 160 self._stop_poll() 161 self._do_search_poll() 162 163 def _stop_poll(self): 164 if self._poll_id is not None: 165 self.after_cancel(self._poll_id) 166 self._poll_id = None 167 168 def _do_search_poll(self): 169 if self._state != SEARCHING: 170 return 171 if self._request_poll: 172 self._request_poll('write_search') 173 self._poll_id = self.after(300, self._do_search_poll) 174 175 def _do_verify_poll(self): 176 """Poll for tag during verify phase, with timeout.""" 177 if self._state != VERIFYING: 178 return 179 elapsed = time.monotonic() - self._verify_start_time 180 if elapsed >= VERIFY_TIMEOUT_S: 181 # Timed out 182 self._set_state(ERROR) 183 messagebox.showerror( 184 "Write Failed", 185 "Tag not detected after 10 seconds.\n\n" 186 "Make sure the tag is placed on the reader " 187 "and try again.", 188 parent=self) 189 self._log("Write failed: tag not detected (timed out).") 190 return 191 if self._request_poll: 192 self._request_poll('write_verify') 193 self._poll_id = self.after(VERIFY_RETRY_INTERVAL_MS, 194 self._do_verify_poll) 195 196 # --- Tab lifecycle --- 197 198 def on_tab_selected(self): 199 pass 200 201 def on_tab_deselected(self): 202 if self._state in (SEARCHING, VERIFYING): 203 self._on_cancel() 204 205 # --- Results from worker --- 206 207 def on_search_result(self, tag_data): 208 """Called when searching for a tag during write workflow.""" 209 if self._state != SEARCHING: 210 return 211 212 if tag_data is None: 213 return # Keep searching 214 215 self._stop_poll() 216 self._found_uid = tag_data.uid_hex 217 self._found_tag_data = tag_data 218 self._set_state(TAG_FOUND) 219 220 # Build the confirmation message 221 self._log(f"Tag found! UID: {tag_data.uid_hex}") 222 223 existing_info = "" 224 if len(tag_data.user_data) > 0: 225 text = tag_data.get_text() 226 uri = tag_data.get_uri() 227 if text: 228 self._log(f"Existing text: {text}") 229 existing_info += f"Existing text: {text}\n" 230 if uri: 231 self._log(f"Existing URI: {uri}") 232 existing_info += f"Existing URI: {uri}\n" 233 if not text and not uri: 234 self._log("Tag is blank (no NDEF data).") 235 existing_info = "Tag is currently blank.\n" 236 else: 237 self._log("Could not read tag data (UID only).") 238 existing_info = "Could not read existing data.\n" 239 240 # Build new data preview 241 bat = BatteryData( 242 sn=self._sn_var.get().strip(), 243 year=self._year_var.get().strip(), 244 note=self._note_var.get().strip(), 245 ) 246 new_uri = self._uri_var.get().strip() 247 248 # Show confirmation dialog 249 msg = ( 250 f"Tag detected: UID {tag_data.uid_hex}\n\n" 251 f"{existing_info}\n" 252 f"--- Write the following? ---\n" 253 f"URI: {new_uri}\n" 254 f"{bat.to_text()}\n\n" 255 f"Proceed with write?" 256 ) 257 258 proceed = messagebox.askyesno( 259 "Confirm Write", msg, parent=self) 260 261 if proceed: 262 self._log("") 263 self._log("Write confirmed. Verifying tag...") 264 self._set_state(VERIFYING) 265 self._verify_start_time = time.monotonic() 266 self._do_verify_poll() 267 else: 268 self._set_state(IDLE) 269 self._log("Write cancelled by user.") 270 271 def on_verify_result(self, tag_data): 272 """Called during write verification — check UID matches.""" 273 if self._state != VERIFYING: 274 return 275 276 if tag_data is None: 277 # Tag not found yet — retry will happen via _do_verify_poll 278 elapsed = time.monotonic() - self._verify_start_time 279 remaining = max(0, VERIFY_TIMEOUT_S - elapsed) 280 self._log(f"Tag not detected, retrying... " 281 f"({remaining:.0f}s remaining)") 282 return 283 284 self._stop_poll() 285 286 current_uid = tag_data.uid_hex 287 if current_uid != self._found_uid: 288 self._set_state(ERROR) 289 messagebox.showerror( 290 "Write Failed", 291 f"Tag changed during write!\n\n" 292 f"Expected UID: {self._found_uid}\n" 293 f"Detected UID: {current_uid}\n\n" 294 f"Place the original tag back and try again.", 295 parent=self) 296 self._log( 297 f"Write failed: tag changed " 298 f"(expected {self._found_uid}, " 299 f"got {current_uid})") 300 return 301 302 # UID matches — proceed with write 303 self._set_state(WRITING) 304 self._log("Tag verified. Writing...") 305 if self._request_write: 306 self._request_write( 307 sn=self._sn_var.get().strip(), 308 year=self._year_var.get().strip(), 309 note=self._note_var.get().strip(), 310 uri=self._uri_var.get().strip(), 311 ) 312 313 def on_write_result(self, success, error_msg=''): 314 """Called when write operation completes.""" 315 if success: 316 self._set_state(DONE) 317 self._log("Write successful!") 318 self._log("Tag has been programmed. You may remove it.") 319 messagebox.showinfo( 320 "Write Complete", 321 "Tag has been successfully programmed!", 322 parent=self) 323 else: 324 self._set_state(ERROR) 325 self._log(f"Write failed: {error_msg}") 326 messagebox.showerror( 327 "Write Failed", 328 f"Tag write failed:\n\n{error_msg}\n\n" 329 f"Click 'Start Write' to try again.", 330 parent=self)
Multi-step write workflow with tag verification safety.
WriteTab(parent, request_poll=None, request_write=None)
29 def __init__(self, parent, request_poll=None, request_write=None): 30 super().__init__(parent) 31 self._request_poll = request_poll 32 self._request_write = request_write 33 self._state = IDLE 34 self._found_uid = None # UID captured when tag first detected 35 self._found_tag_data = None # Full tag data from detection 36 self._poll_id = None 37 self._verify_start_time = 0 38 39 self._build_ui()
Construct a Ttk Frame with parent master.
STANDARD OPTIONS
class, cursor, style, takefocus
WIDGET-SPECIFIC OPTIONS
borderwidth, relief, padding, width, height
def
on_search_result(self, tag_data):
207 def on_search_result(self, tag_data): 208 """Called when searching for a tag during write workflow.""" 209 if self._state != SEARCHING: 210 return 211 212 if tag_data is None: 213 return # Keep searching 214 215 self._stop_poll() 216 self._found_uid = tag_data.uid_hex 217 self._found_tag_data = tag_data 218 self._set_state(TAG_FOUND) 219 220 # Build the confirmation message 221 self._log(f"Tag found! UID: {tag_data.uid_hex}") 222 223 existing_info = "" 224 if len(tag_data.user_data) > 0: 225 text = tag_data.get_text() 226 uri = tag_data.get_uri() 227 if text: 228 self._log(f"Existing text: {text}") 229 existing_info += f"Existing text: {text}\n" 230 if uri: 231 self._log(f"Existing URI: {uri}") 232 existing_info += f"Existing URI: {uri}\n" 233 if not text and not uri: 234 self._log("Tag is blank (no NDEF data).") 235 existing_info = "Tag is currently blank.\n" 236 else: 237 self._log("Could not read tag data (UID only).") 238 existing_info = "Could not read existing data.\n" 239 240 # Build new data preview 241 bat = BatteryData( 242 sn=self._sn_var.get().strip(), 243 year=self._year_var.get().strip(), 244 note=self._note_var.get().strip(), 245 ) 246 new_uri = self._uri_var.get().strip() 247 248 # Show confirmation dialog 249 msg = ( 250 f"Tag detected: UID {tag_data.uid_hex}\n\n" 251 f"{existing_info}\n" 252 f"--- Write the following? ---\n" 253 f"URI: {new_uri}\n" 254 f"{bat.to_text()}\n\n" 255 f"Proceed with write?" 256 ) 257 258 proceed = messagebox.askyesno( 259 "Confirm Write", msg, parent=self) 260 261 if proceed: 262 self._log("") 263 self._log("Write confirmed. Verifying tag...") 264 self._set_state(VERIFYING) 265 self._verify_start_time = time.monotonic() 266 self._do_verify_poll() 267 else: 268 self._set_state(IDLE) 269 self._log("Write cancelled by user.")
Called when searching for a tag during write workflow.
def
on_verify_result(self, tag_data):
271 def on_verify_result(self, tag_data): 272 """Called during write verification — check UID matches.""" 273 if self._state != VERIFYING: 274 return 275 276 if tag_data is None: 277 # Tag not found yet — retry will happen via _do_verify_poll 278 elapsed = time.monotonic() - self._verify_start_time 279 remaining = max(0, VERIFY_TIMEOUT_S - elapsed) 280 self._log(f"Tag not detected, retrying... " 281 f"({remaining:.0f}s remaining)") 282 return 283 284 self._stop_poll() 285 286 current_uid = tag_data.uid_hex 287 if current_uid != self._found_uid: 288 self._set_state(ERROR) 289 messagebox.showerror( 290 "Write Failed", 291 f"Tag changed during write!\n\n" 292 f"Expected UID: {self._found_uid}\n" 293 f"Detected UID: {current_uid}\n\n" 294 f"Place the original tag back and try again.", 295 parent=self) 296 self._log( 297 f"Write failed: tag changed " 298 f"(expected {self._found_uid}, " 299 f"got {current_uid})") 300 return 301 302 # UID matches — proceed with write 303 self._set_state(WRITING) 304 self._log("Tag verified. Writing...") 305 if self._request_write: 306 self._request_write( 307 sn=self._sn_var.get().strip(), 308 year=self._year_var.get().strip(), 309 note=self._note_var.get().strip(), 310 uri=self._uri_var.get().strip(), 311 )
Called during write verification — check UID matches.
def
on_write_result(self, success, error_msg=''):
313 def on_write_result(self, success, error_msg=''): 314 """Called when write operation completes.""" 315 if success: 316 self._set_state(DONE) 317 self._log("Write successful!") 318 self._log("Tag has been programmed. You may remove it.") 319 messagebox.showinfo( 320 "Write Complete", 321 "Tag has been successfully programmed!", 322 parent=self) 323 else: 324 self._set_state(ERROR) 325 self._log(f"Write failed: {error_msg}") 326 messagebox.showerror( 327 "Write Failed", 328 f"Tag write failed:\n\n{error_msg}\n\n" 329 f"Click 'Start Write' to try again.", 330 parent=self)
Called when write operation completes.