host.nfc_tool.read_tab
Read tab — poll and display tag data.
1"""Read tab — poll and display tag data.""" 2 3import tkinter as tk 4from tkinter import ttk 5 6from host.nfc_tool.battery_data import BatteryData 7 8 9class ReadTab(ttk.Frame): 10 """Tag reading with parsed battery data display.""" 11 12 def __init__(self, parent, request_poll=None): 13 super().__init__(parent) 14 self._request_poll = request_poll 15 self._polling = False 16 self._poll_id = None 17 18 self._build_ui() 19 20 def _build_ui(self): 21 # Status indicator row 22 status_frame = ttk.Frame(self) 23 status_frame.pack(fill=tk.X, padx=10, pady=(10, 5)) 24 25 self._canvas = tk.Canvas(status_frame, width=20, height=20, 26 highlightthickness=0) 27 self._canvas.pack(side=tk.LEFT, padx=(0, 8)) 28 self._dot = self._canvas.create_oval(2, 2, 18, 18, fill='gray') 29 30 self._status_var = tk.StringVar(value="Idle") 31 ttk.Label(status_frame, textvariable=self._status_var).pack( 32 side=tk.LEFT) 33 34 # Controls 35 ctrl_frame = ttk.Frame(self) 36 ctrl_frame.pack(fill=tk.X, padx=10, pady=(0, 10)) 37 38 self._auto_var = tk.BooleanVar(value=False) 39 ttk.Checkbutton( 40 ctrl_frame, text="Auto-poll", 41 variable=self._auto_var, 42 command=self._on_auto_toggle, 43 ).pack(side=tk.LEFT) 44 45 ttk.Button( 46 ctrl_frame, text="Read Once", 47 command=self._read_once, 48 ).pack(side=tk.LEFT, padx=(10, 0)) 49 50 # Tag info section 51 info_frame = ttk.LabelFrame(self, text="Tag Info") 52 info_frame.pack(fill=tk.X, padx=10, pady=(0, 5)) 53 54 fields = [ 55 ("UID:", "_uid_var"), 56 ("URI:", "_uri_var"), 57 ] 58 for i, (label, attr) in enumerate(fields): 59 ttk.Label(info_frame, text=label).grid( 60 row=i, column=0, sticky=tk.W, padx=(8, 4), pady=2) 61 var = tk.StringVar(value="\u2014") 62 setattr(self, attr, var) 63 ttk.Label(info_frame, textvariable=var, 64 font=('Consolas', 10)).grid( 65 row=i, column=1, sticky=tk.W, pady=2) 66 67 # Battery data section 68 bat_frame = ttk.LabelFrame(self, text="Battery Data") 69 bat_frame.pack(fill=tk.X, padx=10, pady=(0, 5)) 70 71 bat_fields = [ 72 ("Serial Number:", "_sn_var"), 73 ("Year:", "_year_var"), 74 ("Note:", "_note_var"), 75 ] 76 for i, (label, attr) in enumerate(bat_fields): 77 ttk.Label(bat_frame, text=label).grid( 78 row=i, column=0, sticky=tk.W, padx=(8, 4), pady=2) 79 var = tk.StringVar(value="\u2014") 80 setattr(self, attr, var) 81 ttk.Label(bat_frame, textvariable=var).grid( 82 row=i, column=1, sticky=tk.W, pady=2) 83 84 # Raw NDEF text 85 raw_frame = ttk.LabelFrame(self, text="Raw NDEF Text") 86 raw_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) 87 88 self._raw_text = tk.Text(raw_frame, height=6, wrap=tk.WORD, 89 state='disabled', font=('Consolas', 9)) 90 self._raw_text.pack(fill=tk.BOTH, expand=True, padx=4, pady=4) 91 92 def on_tab_selected(self): 93 if self._auto_var.get(): 94 self._start_polling() 95 96 def on_tab_deselected(self): 97 self._stop_polling() 98 99 def _on_auto_toggle(self): 100 if self._auto_var.get(): 101 self._start_polling() 102 else: 103 self._stop_polling() 104 105 def _read_once(self): 106 if self._request_poll: 107 self._request_poll('read') 108 109 def _start_polling(self): 110 if self._polling: 111 return 112 self._polling = True 113 self._do_poll() 114 115 def _stop_polling(self): 116 self._polling = False 117 if self._poll_id is not None: 118 self.after_cancel(self._poll_id) 119 self._poll_id = None 120 121 def _do_poll(self): 122 if not self._polling: 123 return 124 if self._request_poll: 125 self._request_poll('read') 126 self._poll_id = self.after(500, self._do_poll) 127 128 def update_tag_data(self, tag_data): 129 """Called by app when worker returns read result. 130 131 Data fields are latched — they persist when the tag is removed. 132 Only the dot color and status text update to reflect presence. 133 """ 134 if tag_data is None: 135 # Tag gone — update indicator only, keep latched data 136 self._canvas.itemconfig(self._dot, fill='#cc3333') 137 self._status_var.set("No tag (last read shown)") 138 return 139 140 if len(tag_data.user_data) == 0: 141 self._canvas.itemconfig(self._dot, fill='orange') 142 self._status_var.set("Tag detected but data read failed") 143 self._uid_var.set(tag_data.uid_hex) 144 return 145 146 self._canvas.itemconfig(self._dot, fill='#33cc33') 147 self._uid_var.set(tag_data.uid_hex) 148 149 # URI 150 uri = tag_data.get_uri() 151 self._uri_var.set(uri if uri else "\u2014") 152 153 # Parse battery data from text records 154 text = tag_data.get_text() 155 self._set_raw_text(text) 156 157 if 'bat:' in text: 158 bat = BatteryData.from_text(text) 159 self._status_var.set("Battery tag recognized") 160 self._sn_var.set(bat.sn if bat.sn else "\u2014") 161 self._year_var.set(bat.year if bat.year else "\u2014") 162 self._note_var.set(bat.note if bat.note else "\u2014") 163 else: 164 self._status_var.set("Tag read (unrecognized format)") 165 self._sn_var.set("\u2014") 166 self._year_var.set("\u2014") 167 self._note_var.set("\u2014") 168 169 def _set_raw_text(self, text): 170 self._raw_text.configure(state='normal') 171 self._raw_text.delete('1.0', tk.END) 172 if text: 173 self._raw_text.insert('1.0', text) 174 self._raw_text.configure(state='disabled')
class
ReadTab(tkinter.ttk.Frame):
10class ReadTab(ttk.Frame): 11 """Tag reading with parsed battery data display.""" 12 13 def __init__(self, parent, request_poll=None): 14 super().__init__(parent) 15 self._request_poll = request_poll 16 self._polling = False 17 self._poll_id = None 18 19 self._build_ui() 20 21 def _build_ui(self): 22 # Status indicator row 23 status_frame = ttk.Frame(self) 24 status_frame.pack(fill=tk.X, padx=10, pady=(10, 5)) 25 26 self._canvas = tk.Canvas(status_frame, width=20, height=20, 27 highlightthickness=0) 28 self._canvas.pack(side=tk.LEFT, padx=(0, 8)) 29 self._dot = self._canvas.create_oval(2, 2, 18, 18, fill='gray') 30 31 self._status_var = tk.StringVar(value="Idle") 32 ttk.Label(status_frame, textvariable=self._status_var).pack( 33 side=tk.LEFT) 34 35 # Controls 36 ctrl_frame = ttk.Frame(self) 37 ctrl_frame.pack(fill=tk.X, padx=10, pady=(0, 10)) 38 39 self._auto_var = tk.BooleanVar(value=False) 40 ttk.Checkbutton( 41 ctrl_frame, text="Auto-poll", 42 variable=self._auto_var, 43 command=self._on_auto_toggle, 44 ).pack(side=tk.LEFT) 45 46 ttk.Button( 47 ctrl_frame, text="Read Once", 48 command=self._read_once, 49 ).pack(side=tk.LEFT, padx=(10, 0)) 50 51 # Tag info section 52 info_frame = ttk.LabelFrame(self, text="Tag Info") 53 info_frame.pack(fill=tk.X, padx=10, pady=(0, 5)) 54 55 fields = [ 56 ("UID:", "_uid_var"), 57 ("URI:", "_uri_var"), 58 ] 59 for i, (label, attr) in enumerate(fields): 60 ttk.Label(info_frame, text=label).grid( 61 row=i, column=0, sticky=tk.W, padx=(8, 4), pady=2) 62 var = tk.StringVar(value="\u2014") 63 setattr(self, attr, var) 64 ttk.Label(info_frame, textvariable=var, 65 font=('Consolas', 10)).grid( 66 row=i, column=1, sticky=tk.W, pady=2) 67 68 # Battery data section 69 bat_frame = ttk.LabelFrame(self, text="Battery Data") 70 bat_frame.pack(fill=tk.X, padx=10, pady=(0, 5)) 71 72 bat_fields = [ 73 ("Serial Number:", "_sn_var"), 74 ("Year:", "_year_var"), 75 ("Note:", "_note_var"), 76 ] 77 for i, (label, attr) in enumerate(bat_fields): 78 ttk.Label(bat_frame, text=label).grid( 79 row=i, column=0, sticky=tk.W, padx=(8, 4), pady=2) 80 var = tk.StringVar(value="\u2014") 81 setattr(self, attr, var) 82 ttk.Label(bat_frame, textvariable=var).grid( 83 row=i, column=1, sticky=tk.W, pady=2) 84 85 # Raw NDEF text 86 raw_frame = ttk.LabelFrame(self, text="Raw NDEF Text") 87 raw_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) 88 89 self._raw_text = tk.Text(raw_frame, height=6, wrap=tk.WORD, 90 state='disabled', font=('Consolas', 9)) 91 self._raw_text.pack(fill=tk.BOTH, expand=True, padx=4, pady=4) 92 93 def on_tab_selected(self): 94 if self._auto_var.get(): 95 self._start_polling() 96 97 def on_tab_deselected(self): 98 self._stop_polling() 99 100 def _on_auto_toggle(self): 101 if self._auto_var.get(): 102 self._start_polling() 103 else: 104 self._stop_polling() 105 106 def _read_once(self): 107 if self._request_poll: 108 self._request_poll('read') 109 110 def _start_polling(self): 111 if self._polling: 112 return 113 self._polling = True 114 self._do_poll() 115 116 def _stop_polling(self): 117 self._polling = False 118 if self._poll_id is not None: 119 self.after_cancel(self._poll_id) 120 self._poll_id = None 121 122 def _do_poll(self): 123 if not self._polling: 124 return 125 if self._request_poll: 126 self._request_poll('read') 127 self._poll_id = self.after(500, self._do_poll) 128 129 def update_tag_data(self, tag_data): 130 """Called by app when worker returns read result. 131 132 Data fields are latched — they persist when the tag is removed. 133 Only the dot color and status text update to reflect presence. 134 """ 135 if tag_data is None: 136 # Tag gone — update indicator only, keep latched data 137 self._canvas.itemconfig(self._dot, fill='#cc3333') 138 self._status_var.set("No tag (last read shown)") 139 return 140 141 if len(tag_data.user_data) == 0: 142 self._canvas.itemconfig(self._dot, fill='orange') 143 self._status_var.set("Tag detected but data read failed") 144 self._uid_var.set(tag_data.uid_hex) 145 return 146 147 self._canvas.itemconfig(self._dot, fill='#33cc33') 148 self._uid_var.set(tag_data.uid_hex) 149 150 # URI 151 uri = tag_data.get_uri() 152 self._uri_var.set(uri if uri else "\u2014") 153 154 # Parse battery data from text records 155 text = tag_data.get_text() 156 self._set_raw_text(text) 157 158 if 'bat:' in text: 159 bat = BatteryData.from_text(text) 160 self._status_var.set("Battery tag recognized") 161 self._sn_var.set(bat.sn if bat.sn else "\u2014") 162 self._year_var.set(bat.year if bat.year else "\u2014") 163 self._note_var.set(bat.note if bat.note else "\u2014") 164 else: 165 self._status_var.set("Tag read (unrecognized format)") 166 self._sn_var.set("\u2014") 167 self._year_var.set("\u2014") 168 self._note_var.set("\u2014") 169 170 def _set_raw_text(self, text): 171 self._raw_text.configure(state='normal') 172 self._raw_text.delete('1.0', tk.END) 173 if text: 174 self._raw_text.insert('1.0', text) 175 self._raw_text.configure(state='disabled')
Tag reading with parsed battery data display.
ReadTab(parent, request_poll=None)
13 def __init__(self, parent, request_poll=None): 14 super().__init__(parent) 15 self._request_poll = request_poll 16 self._polling = False 17 self._poll_id = None 18 19 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
update_tag_data(self, tag_data):
129 def update_tag_data(self, tag_data): 130 """Called by app when worker returns read result. 131 132 Data fields are latched — they persist when the tag is removed. 133 Only the dot color and status text update to reflect presence. 134 """ 135 if tag_data is None: 136 # Tag gone — update indicator only, keep latched data 137 self._canvas.itemconfig(self._dot, fill='#cc3333') 138 self._status_var.set("No tag (last read shown)") 139 return 140 141 if len(tag_data.user_data) == 0: 142 self._canvas.itemconfig(self._dot, fill='orange') 143 self._status_var.set("Tag detected but data read failed") 144 self._uid_var.set(tag_data.uid_hex) 145 return 146 147 self._canvas.itemconfig(self._dot, fill='#33cc33') 148 self._uid_var.set(tag_data.uid_hex) 149 150 # URI 151 uri = tag_data.get_uri() 152 self._uri_var.set(uri if uri else "\u2014") 153 154 # Parse battery data from text records 155 text = tag_data.get_text() 156 self._set_raw_text(text) 157 158 if 'bat:' in text: 159 bat = BatteryData.from_text(text) 160 self._status_var.set("Battery tag recognized") 161 self._sn_var.set(bat.sn if bat.sn else "\u2014") 162 self._year_var.set(bat.year if bat.year else "\u2014") 163 self._note_var.set(bat.note if bat.note else "\u2014") 164 else: 165 self._status_var.set("Tag read (unrecognized format)") 166 self._sn_var.set("\u2014") 167 self._year_var.set("\u2014") 168 self._note_var.set("\u2014")
Called by app when worker returns read result.
Data fields are latched — they persist when the tag is removed. Only the dot color and status text update to reflect presence.