Push New Project

This commit is contained in:
naab
2025-06-04 14:55:43 +07:00
commit 31fc6c5833
20 changed files with 18625 additions and 0 deletions

164
README.md Normal file
View File

@ -0,0 +1,164 @@
# Scale RS232 Reader v2.2 Ultra Large Display
**Electronic Scale Data Reader via RS232/Serial Port - Simultaneous Dual Scale Display**
## 🎯 Key Features v2.2
-**Dual Scale Display**: Monitor 2 scales simultaneously on the same screen
-**Ultra Large Weight Display**: 84pt font size for easy reading from distance
-**No Tab Switching**: Streamlined interface saves time and effort
-**Real-time Monitoring**: Live weight data updates
-**Data Logging**: Automatic CSV history logging
-**Clean UI**: Minimalist interface with optimized window size
-**Robust Error Handling**: Reliable connection management
-**Professional Monitoring**: Designed for industrial and commercial use
## 🚀 Installation & Usage
### Method 1: Run from Executable (Recommended)
1. **Build the executable:**
```bash
build_production.bat
```
2. **Run the application:**
```bash
dist\ScaleRS232Reader_v2.exe
```
### Method 2: Run from Source Code
1. **Install dependencies:**
```bash
pip install -r requirements.txt
```
2. **Run the application:**
```bash
python main.py
```
## 📖 User Guide
### 1. **Scale Connection:**
- Select COM port for each scale from dropdown menu
- Choose baud rate (default: 9600)
- Click "Connect" button for each scale individually
### 2. **Data Monitoring:**
- **Both scales display simultaneously** on the same screen
- Current weight with **ultra-large font** (84pt) for distant viewing
- Real-time statistics (read count, min, max, average) below each scale
- No cluttered history display - dedicated history viewer available
### 3. **Connection Management:**
- Connect/disconnect each scale independently
- "Disconnect All" button for quick shutdown
- "Refresh COM Ports" to update available ports
- Automatic error recovery and reconnection
### 4. **Data History:**
- Automatic saving to `history/` directory
- File format: `Scale_X_COMX_YYYYMMDD_HHMMSS.csv`
- Click "View History" to open history folder
- CSV format compatible with Excel and data analysis tools
## 🎨 Interface Layout v2.2
### **Simultaneous Dual Scale Display:**
## 🔧 System Requirements
- **Operating System**: Windows 7/8/10/11
- **Python**: 3.7+ (if running from source)
- **Dependencies**: pyserial 3.5
- **Display**: Minimum 1400x800 (recommended 1600x900+)
- **Hardware**: RS232/Serial port or USB-to-Serial adapter
## ⚙️ Technical Specifications
### **Supported Scale Protocols:**
- Standard RS232 serial communication
- Configurable baud rates: 1200, 2400, 4800, 9600, 19200, 38400
- Automatic weight data parsing from various scale formats
- Error detection and automatic reconnection
### **Data Logging:**
- CSV format with timestamps
- Separate files for each scale/session
- Automatic directory creation
- Compatible with Excel, Google Sheets, and data analysis tools
### **Performance:**
- Real-time updates with minimal CPU usage
- Memory optimized for continuous operation
- Smooth display refresh for professional monitoring
- Thread-safe serial communication
## ⚠️ Troubleshooting
### COM Port Connection Issues
- Verify scale is powered on and properly connected
- Check cable connections (RS232 or USB-to-Serial)
- Try different baud rates (9600 is most common)
- Ensure no other applications are using the same COM port
- Use Device Manager to verify COM port availability
### Data Reading Problems
- Check scale data format in console log
- Verify scale is configured to send continuous data
- Some scales require manual triggering for data transmission
- Ensure proper RS232 wiring (TX, RX, GND)
### Interface Display Issues
- Increase screen resolution or decrease Windows scaling
- Minimum display resolution: 1400x800
- For smaller screens, consider using Windows magnifier
- Ensure graphics drivers are up to date
### Performance Issues
- Close other resource-intensive applications
- Check Windows Task Manager for high CPU usage
- Restart application if memory usage grows excessively
- Verify USB-to-Serial driver installation
## 🔍 Data Format Examples
### **Supported Scale Output Formats:**
Standard Format: +000123.45 kg
Compact Format: 123.45
With Units: 123.45 kg
With Status: ST,GS,123.45 kg
### **CSV Log Format:**
```csv
Timestamp,Scale_ID,Weight,Unit,Raw_Data
2024-01-15 14:30:25,Scale_1,125.450,kg,+000125.45 kg
2024-01-15 14:30:26,Scale_1,125.480,kg,+000125.48 kg
```
## 📝 Version History
### **v2.2 Ultra Large Display:**
**New Features:**
- **Simultaneous dual scale monitoring** on single screen
- **Ultra-large 84pt weight display** for distant visibility
- **Expanded window size** (1600x900) for better readability
- **Side-by-side layout** eliminates tab switching
**UI/UX Improvements:**
- Removed cluttered scrolling history display
- Optimized layout for real-time monitoring
- Enhanced color scheme and spacing
- Intelligent status bar with combined information
**Performance Enhancements:**
- Reduced CPU usage (no scrolling text widgets)
- Memory optimization for continuous operation
- Smoother real-time display updates
- Improved thread management
**Professional Features:**
- Industrial-grade monitoring interface
- Robust error handling and recovery

52
ScaleRS232Reader_v2.spec Normal file
View File

@ -0,0 +1,52 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[
'serial',
'serial.tools',
'serial.tools.list_ports',
'tkinter',
'tkinter.ttk',
'tkinter.messagebox',
'threading',
'datetime',
'os',
're',
'time'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='ScaleRS232Reader_v2',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
version_file=None,
icon=None,
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,380 @@
('D:\\Work\\Test\\scale_rs232 -2 '
'(fixed)\\scale_rs232_reader\\build\\ScaleRS232Reader_v2\\PYZ-00.pyz',
[('__future__',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\__future__.py',
'PYMODULE'),
('_compat_pickle',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_compat_pickle.py',
'PYMODULE'),
('_compression',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_compression.py',
'PYMODULE'),
('_py_abc',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_py_abc.py',
'PYMODULE'),
('_pydatetime',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_pydatetime.py',
'PYMODULE'),
('_pydecimal',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_pydecimal.py',
'PYMODULE'),
('_strptime',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_strptime.py',
'PYMODULE'),
('_threading_local',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_threading_local.py',
'PYMODULE'),
('argparse',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\argparse.py',
'PYMODULE'),
('ast',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ast.py',
'PYMODULE'),
('base64',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\base64.py',
'PYMODULE'),
('bisect',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\bisect.py',
'PYMODULE'),
('bz2',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\bz2.py',
'PYMODULE'),
('calendar',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\calendar.py',
'PYMODULE'),
('contextlib',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\contextlib.py',
'PYMODULE'),
('contextvars',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\contextvars.py',
'PYMODULE'),
('copy',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\copy.py',
'PYMODULE'),
('csv',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\csv.py',
'PYMODULE'),
('ctypes',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ctypes\\__init__.py',
'PYMODULE'),
('ctypes._endian',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ctypes\\_endian.py',
'PYMODULE'),
('ctypes.wintypes',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ctypes\\wintypes.py',
'PYMODULE'),
('dataclasses',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\dataclasses.py',
'PYMODULE'),
('datetime',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\datetime.py',
'PYMODULE'),
('decimal',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\decimal.py',
'PYMODULE'),
('dis',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\dis.py',
'PYMODULE'),
('email',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\__init__.py',
'PYMODULE'),
('email._encoded_words',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_encoded_words.py',
'PYMODULE'),
('email._header_value_parser',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_header_value_parser.py',
'PYMODULE'),
('email._parseaddr',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_parseaddr.py',
'PYMODULE'),
('email._policybase',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_policybase.py',
'PYMODULE'),
('email.base64mime',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\base64mime.py',
'PYMODULE'),
('email.charset',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\charset.py',
'PYMODULE'),
('email.contentmanager',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\contentmanager.py',
'PYMODULE'),
('email.encoders',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\encoders.py',
'PYMODULE'),
('email.errors',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\errors.py',
'PYMODULE'),
('email.feedparser',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\feedparser.py',
'PYMODULE'),
('email.generator',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\generator.py',
'PYMODULE'),
('email.header',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\header.py',
'PYMODULE'),
('email.headerregistry',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\headerregistry.py',
'PYMODULE'),
('email.iterators',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\iterators.py',
'PYMODULE'),
('email.message',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\message.py',
'PYMODULE'),
('email.parser',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\parser.py',
'PYMODULE'),
('email.policy',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\policy.py',
'PYMODULE'),
('email.quoprimime',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\quoprimime.py',
'PYMODULE'),
('email.utils',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\utils.py',
'PYMODULE'),
('fnmatch',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\fnmatch.py',
'PYMODULE'),
('fractions',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\fractions.py',
'PYMODULE'),
('getopt',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\getopt.py',
'PYMODULE'),
('gettext',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\gettext.py',
'PYMODULE'),
('glob',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\glob.py',
'PYMODULE'),
('gzip',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\gzip.py',
'PYMODULE'),
('hashlib',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\hashlib.py',
'PYMODULE'),
('importlib',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\__init__.py',
'PYMODULE'),
('importlib._abc',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_abc.py',
'PYMODULE'),
('importlib._bootstrap',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_bootstrap.py',
'PYMODULE'),
('importlib._bootstrap_external',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_bootstrap_external.py',
'PYMODULE'),
('importlib.abc',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\abc.py',
'PYMODULE'),
('importlib.machinery',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\machinery.py',
'PYMODULE'),
('importlib.metadata',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\__init__.py',
'PYMODULE'),
('importlib.metadata._adapters',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_adapters.py',
'PYMODULE'),
('importlib.metadata._collections',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_collections.py',
'PYMODULE'),
('importlib.metadata._functools',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_functools.py',
'PYMODULE'),
('importlib.metadata._itertools',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_itertools.py',
'PYMODULE'),
('importlib.metadata._meta',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_meta.py',
'PYMODULE'),
('importlib.metadata._text',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_text.py',
'PYMODULE'),
('importlib.readers',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\readers.py',
'PYMODULE'),
('importlib.resources',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\__init__.py',
'PYMODULE'),
('importlib.resources._adapters',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_adapters.py',
'PYMODULE'),
('importlib.resources._common',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_common.py',
'PYMODULE'),
('importlib.resources._itertools',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_itertools.py',
'PYMODULE'),
('importlib.resources._legacy',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_legacy.py',
'PYMODULE'),
('importlib.resources.abc',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\abc.py',
'PYMODULE'),
('importlib.resources.readers',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\readers.py',
'PYMODULE'),
('importlib.util',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\util.py',
'PYMODULE'),
('inspect',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\inspect.py',
'PYMODULE'),
('ipaddress',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ipaddress.py',
'PYMODULE'),
('logging',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\logging\\__init__.py',
'PYMODULE'),
('lzma',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\lzma.py',
'PYMODULE'),
('numbers',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\numbers.py',
'PYMODULE'),
('opcode',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\opcode.py',
'PYMODULE'),
('pathlib',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pathlib.py',
'PYMODULE'),
('pickle',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pickle.py',
'PYMODULE'),
('pprint',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pprint.py',
'PYMODULE'),
('py_compile',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\py_compile.py',
'PYMODULE'),
('quopri',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\quopri.py',
'PYMODULE'),
('random',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\random.py',
'PYMODULE'),
('selectors',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\selectors.py',
'PYMODULE'),
('serial',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\__init__.py',
'PYMODULE'),
('serial.serialcli',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\serialcli.py',
'PYMODULE'),
('serial.serialjava',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\serialjava.py',
'PYMODULE'),
('serial.serialposix',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\serialposix.py',
'PYMODULE'),
('serial.serialutil',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\serialutil.py',
'PYMODULE'),
('serial.serialwin32',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\serialwin32.py',
'PYMODULE'),
('serial.tools',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\tools\\__init__.py',
'PYMODULE'),
('serial.tools.list_ports',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\tools\\list_ports.py',
'PYMODULE'),
('serial.tools.list_ports_common',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\tools\\list_ports_common.py',
'PYMODULE'),
('serial.tools.list_ports_linux',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\tools\\list_ports_linux.py',
'PYMODULE'),
('serial.tools.list_ports_osx',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\tools\\list_ports_osx.py',
'PYMODULE'),
('serial.tools.list_ports_posix',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\tools\\list_ports_posix.py',
'PYMODULE'),
('serial.tools.list_ports_windows',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\tools\\list_ports_windows.py',
'PYMODULE'),
('serial.win32',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\serial\\win32.py',
'PYMODULE'),
('shutil',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\shutil.py',
'PYMODULE'),
('signal',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\signal.py',
'PYMODULE'),
('socket',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\socket.py',
'PYMODULE'),
('statistics',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\statistics.py',
'PYMODULE'),
('string',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\string.py',
'PYMODULE'),
('stringprep',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\stringprep.py',
'PYMODULE'),
('subprocess',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\subprocess.py',
'PYMODULE'),
('tarfile',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tarfile.py',
'PYMODULE'),
('tempfile',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tempfile.py',
'PYMODULE'),
('textwrap',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\textwrap.py',
'PYMODULE'),
('threading',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\threading.py',
'PYMODULE'),
('tkinter',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\__init__.py',
'PYMODULE'),
('tkinter.commondialog',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\commondialog.py',
'PYMODULE'),
('tkinter.constants',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\constants.py',
'PYMODULE'),
('tkinter.messagebox',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\messagebox.py',
'PYMODULE'),
('tkinter.ttk',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\ttk.py',
'PYMODULE'),
('token',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\token.py',
'PYMODULE'),
('tokenize',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tokenize.py',
'PYMODULE'),
('tracemalloc',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tracemalloc.py',
'PYMODULE'),
('typing',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\typing.py',
'PYMODULE'),
('urllib',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\__init__.py',
'PYMODULE'),
('urllib.parse',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\parse.py',
'PYMODULE'),
('zipfile',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\__init__.py',
'PYMODULE'),
('zipfile._path',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\_path\\__init__.py',
'PYMODULE'),
('zipfile._path.glob',
'C:\\Users\\ASUS\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\_path\\glob.py',
'PYMODULE')])

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,27 @@
This file lists modules PyInstaller was not able to find. This does not
necessarily mean this module is required for running your program. Python and
Python 3rd-party packages include a lot of conditional or optional modules. For
example the module 'ntpath' only exists on Windows, whereas the module
'posixpath' only exists on Posix systems.
Types if import:
* top-level: imported at the top-level - look at these first
* conditional: imported within an if-statement
* delayed: imported within a function
* optional: imported within a try-except-statement
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
tracking down the missing module yourself. Thanks!
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional)
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional)
missing module named posix - imported by os (conditional, optional), shutil (conditional), importlib._bootstrap_external (conditional), posixpath (optional)
missing module named resource - imported by posix (top-level)
missing module named _posixsubprocess - imported by subprocess (conditional)
missing module named fcntl - imported by subprocess (optional), serial.serialposix (top-level)
missing module named termios - imported by serial.serialposix (top-level)
missing module named 'System.IO' - imported by serial.serialcli (top-level)
missing module named System - imported by serial.serialcli (top-level)

File diff suppressed because it is too large Load Diff

40
build_production.bat Normal file
View File

@ -0,0 +1,40 @@
@echo off
echo ===============================================
echo Building Scale RS232 Reader v2.2 Ultra Large Display
echo ===============================================
echo.
echo [1/4] Cleaning previous builds...
if exist build rmdir /s /q build
if exist dist rmdir /s /q dist
if exist __pycache__ rmdir /s /q __pycache__
echo [2/4] Installing dependencies...
pip install -r requirements.txt
pip install pyinstaller
echo [3/4] Building executable...
pyinstaller ScaleRS232Reader_v2.spec
echo [4/4] Checking build result...
if exist "dist\ScaleRS232Reader_v2.exe" (
echo.
echo ===============================================
echo BUILD SUCCESSFUL!
echo ===============================================
echo Executable: dist\ScaleRS232Reader_v2.exe
echo Size:
dir "dist\ScaleRS232Reader_v2.exe" | findstr "ScaleRS232Reader_v2.exe"
echo.
echo You can now run: dist\ScaleRS232Reader_v2.exe
echo ===============================================
) else (
echo.
echo ===============================================
echo BUILD FAILED!
echo ===============================================
echo Please check the error messages above.
echo ===============================================
)
pause

BIN
dist/ScaleRS232Reader_v2.exe vendored Normal file

Binary file not shown.

575
main.py Normal file
View File

@ -0,0 +1,575 @@
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import time
import datetime
import os
import re
class DualScaleRS232Reader:
def __init__(self, root):
self.root = root
self.root.title("Scale RS232 Reader - Hiển thị 2 cân đồng thời")
self.root.geometry("1600x900") # Tăng kích thước
self.root.minsize(1400, 800)
# Màu sắc
self.colors = {
'white': '#FFFFFF',
'light_gray': '#E8E8E8',
'light_blue': '#90CAF9',
'light_green': '#A5D6A7',
'text_dark': '#2E2E2E',
'text_light': '#666666',
'border': '#CCCCCC'
}
self.root.configure(bg=self.colors['white'])
# Thiết lập styles
self.setup_styles()
# Biến lưu trữ kết nối và dữ liệu
self.serial_connections = {}
self.running = {}
self.data_widgets = {}
# Tạo thư mục lịch sử
self.history_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "history")
os.makedirs(self.history_dir, exist_ok=True)
# Tạo giao diện
self.create_main_interface()
# Cập nhật danh sách cổng COM
self.update_com_ports()
def setup_styles(self):
"""Thiết lập styles cho giao diện"""
style = ttk.Style()
style.theme_use('clam')
# Frame styles
style.configure("Main.TFrame",
background=self.colors['white'])
style.configure("Header.TFrame",
background=self.colors['light_blue'])
style.configure("Control.TFrame",
background=self.colors['light_gray'])
# Label styles
style.configure("Title.TLabel",
background=self.colors['light_blue'],
foreground=self.colors['text_dark'],
font=("Segoe UI", 18, "bold"))
style.configure("Header.TLabel",
background=self.colors['light_gray'],
foreground=self.colors['text_dark'],
font=("Segoe UI", 12, "bold"))
style.configure("Info.TLabel",
background=self.colors['white'],
foreground=self.colors['text_light'],
font=("Segoe UI", 11))
# Button styles
style.configure("Action.TButton",
font=("Segoe UI", 9, "bold"),
padding=(8, 4))
style.configure("Connect.TButton",
font=("Segoe UI", 10, "bold"),
padding=(10, 6))
# Combobox style
style.configure("Compact.TCombobox",
font=("Segoe UI", 9))
def create_main_interface(self):
"""Tạo giao diện chính"""
# Container chính
main_container = ttk.Frame(self.root, style="Main.TFrame", padding="15")
main_container.pack(fill=tk.BOTH, expand=True)
# Header
self.create_header(main_container)
# Control panel compact
self.create_control_panel(main_container)
# Display area cho 2 cân (chiếm phần lớn màn hình)
self.create_dual_display_area(main_container)
# Status bar
self.create_status_bar(main_container)
def create_header(self, parent):
"""Tạo header"""
header_frame = tk.Frame(parent, bg=self.colors['light_blue'], height=50)
header_frame.pack(fill=tk.X, pady=(0, 10))
header_frame.pack_propagate(False)
title_label = tk.Label(header_frame,
text="Scale RS232 Reader - Hiển thị đồng thời 2 cân",
font=("Segoe UI", 18, "bold"),
fg=self.colors['text_dark'],
bg=self.colors['light_blue'])
title_label.pack(side=tk.LEFT, padx=15, pady=10)
version_label = tk.Label(header_frame,
text="v2.2 - Ultra Large Display",
font=("Segoe UI", 11),
fg=self.colors['text_light'],
bg=self.colors['light_blue'])
version_label.pack(side=tk.RIGHT, padx=15, pady=12)
def create_control_panel(self, parent):
"""Tạo panel điều khiển compact"""
control_frame = tk.Frame(parent, bg=self.colors['light_gray'],
relief="solid", bd=1, height=130) # Đủ cao cho 2 cân
control_frame.pack(fill=tk.X, pady=(0, 10))
control_frame.pack_propagate(False) # Cố định chiều cao
# Main container với grid layout
main_container = tk.Frame(control_frame, bg=self.colors['light_gray'])
main_container.pack(fill=tk.BOTH, padx=10, pady=6)
# Configure grid weights
main_container.grid_columnconfigure(0, weight=3) # Controls area
main_container.grid_columnconfigure(1, weight=1) # Actions area
# Left side - Scale controls
controls_frame = tk.Frame(main_container, bg=self.colors['light_gray'])
controls_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 15))
# Title compact
title_label = tk.Label(controls_frame, text="QUẢN LÝ KẾT NỐI",
font=("Segoe UI", 10, "bold"),
fg=self.colors['text_dark'], bg=self.colors['light_gray'])
title_label.pack(pady=(0, 5))
# Controls container
controls_container = tk.Frame(controls_frame, bg=self.colors['light_gray'])
controls_container.pack(fill=tk.X)
# Cân 1
self.create_scale_controls("Scale_1", "CÂN 1", controls_container, 0)
# Cân 2
self.create_scale_controls("Scale_2", "CÂN 2", controls_container, 1)
# Right side - Action buttons
actions_frame = tk.Frame(main_container, bg=self.colors['light_gray'])
actions_frame.grid(row=0, column=1, sticky="nsew")
# Actions title
actions_title = tk.Label(actions_frame, text="THAO TÁC",
font=("Segoe UI", 10, "bold"),
fg=self.colors['text_dark'], bg=self.colors['light_gray'])
actions_title.pack(pady=(0, 5))
# Action buttons - vertical layout
ttk.Button(actions_frame, text="Làm mới cổng COM",
command=self.update_com_ports, style="Action.TButton").pack(fill=tk.X, pady=1)
ttk.Button(actions_frame, text="Xem lịch sử",
command=self.view_history, style="Action.TButton").pack(fill=tk.X, pady=1)
ttk.Button(actions_frame, text="Ngắt tất cả",
command=self.disconnect_all, style="Action.TButton").pack(fill=tk.X, pady=1)
def create_scale_controls(self, scale_id, scale_name, parent, row):
"""Tạo controls compact cho một cân"""
# Scale name
name_label = tk.Label(parent, text=f"{scale_name}:",
font=("Segoe UI", 10, "bold"),
fg=self.colors['text_dark'],
bg=self.colors['light_gray'])
name_label.grid(row=row, column=0, sticky="w", padx=(0, 10), pady=3)
# COM Port
com_label = tk.Label(parent, text="COM:",
font=("Segoe UI", 9),
fg=self.colors['text_light'],
bg=self.colors['light_gray'])
com_label.grid(row=row, column=1, sticky="w", padx=(0, 5), pady=3)
com_var = tk.StringVar()
com_combo = ttk.Combobox(parent, textvariable=com_var,
width=12, style="Compact.TCombobox", state="readonly")
com_combo.grid(row=row, column=2, padx=(0, 10), pady=3)
# Baudrate
baud_label = tk.Label(parent, text="Baud:",
font=("Segoe UI", 9),
fg=self.colors['text_light'],
bg=self.colors['light_gray'])
baud_label.grid(row=row, column=3, sticky="w", padx=(0, 5), pady=3)
baud_var = tk.StringVar(value="9600")
baud_combo = ttk.Combobox(parent, textvariable=baud_var,
values=["1200", "2400", "4800", "9600", "19200", "38400"],
width=8, style="Compact.TCombobox", state="readonly")
baud_combo.grid(row=row, column=4, padx=(0, 10), pady=3)
# Connect button
connect_btn = ttk.Button(parent, text="Kết nối",
command=lambda: self.toggle_connection(scale_id),
style="Connect.TButton")
connect_btn.grid(row=row, column=5, pady=3)
# Lưu controls
if not hasattr(self, 'scale_controls'):
self.scale_controls = {}
self.scale_controls[scale_id] = {
'scale_name': scale_name,
'com_var': com_var,
'com_combo': com_combo,
'baud_var': baud_var,
'connect_btn': connect_btn
}
def create_dual_display_area(self, parent):
"""Tạo khu vực hiển thị 2 cân đồng thời - tối đa hóa"""
display_frame = tk.Frame(parent, bg=self.colors['white'])
display_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
# Configure grid weights
display_frame.grid_columnconfigure(0, weight=1)
display_frame.grid_columnconfigure(1, weight=1)
display_frame.grid_rowconfigure(0, weight=1)
# Cân 1
self.create_scale_display("Scale_1", "CÂN 1", display_frame, 0, 0)
# Cân 2
self.create_scale_display("Scale_2", "CÂN 2", display_frame, 0, 1)
def create_scale_display(self, scale_id, scale_name, parent, row, col):
"""Tạo màn hình hiển thị cho một cân - tối đa hóa số"""
# Main container cho cân
scale_frame = tk.Frame(parent, bg=self.colors['white'],
relief="solid", bd=1, highlightbackground=self.colors['border'])
scale_frame.grid(row=row, column=col, sticky="nsew", padx=3, pady=3)
# Header compact
header_frame = tk.Frame(scale_frame, bg=self.colors['light_green'], height=40)
header_frame.pack(fill=tk.X)
header_frame.pack_propagate(False)
header_label = tk.Label(header_frame, text=scale_name,
font=("Segoe UI", 14, "bold"),
fg=self.colors['text_dark'], bg=self.colors['light_green'])
header_label.pack(expand=True, pady=8)
# Status compact
status_frame = tk.Frame(scale_frame, bg=self.colors['white'], height=30)
status_frame.pack(fill=tk.X)
status_frame.pack_propagate(False)
status_var = tk.StringVar(value="Chưa kết nối")
status_label = tk.Label(status_frame, textvariable=status_var,
font=("Segoe UI", 10),
fg=self.colors['text_light'], bg=self.colors['white'])
status_label.pack(pady=5)
# Weight display - SIÊU LỚN (tối đa hóa)
weight_frame = tk.Frame(scale_frame, bg=self.colors['white'])
weight_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=15)
weight_var = tk.StringVar(value="0.000")
weight_label = tk.Label(weight_frame, textvariable=weight_var,
font=("Arial", 150, "bold"), # Font siêu lớn để nhìn từ xa
fg=self.colors['text_dark'], bg=self.colors['white'])
weight_label.pack(expand=True)
# Unit
unit_label = tk.Label(weight_frame, text="kg",
font=("Segoe UI", 32, "bold"), # Tăng size đơn vị
fg=self.colors['text_light'], bg=self.colors['white'])
unit_label.pack()
# Statistics compact
stats_frame = tk.Frame(scale_frame, bg=self.colors['light_gray'], height=60)
stats_frame.pack(fill=tk.X, padx=8, pady=(0, 8))
stats_frame.pack_propagate(False)
# Stats grid
stats_grid = tk.Frame(stats_frame, bg=self.colors['light_gray'])
stats_grid.pack(expand=True, pady=8)
stats_vars = {}
stat_items = [
("Đọc:", "count", 0, 0),
("Max:", "max", 0, 2),
("Min:", "min", 1, 0),
("Avg:", "avg", 1, 2)
]
for label_text, key, row, col in stat_items:
tk.Label(stats_grid, text=label_text,
font=("Segoe UI", 8, "bold"),
fg=self.colors['text_dark'], bg=self.colors['light_gray']).grid(
row=row, column=col, sticky="w", padx=(0, 3), pady=1)
stats_vars[key] = tk.StringVar(value="0")
tk.Label(stats_grid, textvariable=stats_vars[key],
font=("Segoe UI", 8),
fg=self.colors['text_light'], bg=self.colors['light_gray']).grid(
row=row, column=col+1, sticky="w", padx=(0, 10), pady=1)
# Lưu widgets
self.data_widgets[scale_id] = {
'status_var': status_var,
'weight_var': weight_var,
'weight_label': weight_label,
'stats_vars': stats_vars,
'values': [],
'scale_name': scale_name
}
def create_status_bar(self, parent):
"""Tạo status bar"""
status_frame = tk.Frame(parent, bg=self.colors['light_gray'], height=30)
status_frame.pack(fill=tk.X)
status_frame.pack_propagate(False)
self.status_var = tk.StringVar(value="Sẵn sàng - Chọn cổng COM và kết nối")
self.status_label = tk.Label(status_frame, textvariable=self.status_var,
font=("Segoe UI", 9),
fg=self.colors['text_dark'],
bg=self.colors['light_gray'])
self.status_label.pack(side=tk.LEFT, padx=10, pady=6)
# Time display
self.time_var = tk.StringVar()
self.time_label = tk.Label(status_frame, textvariable=self.time_var,
font=("Segoe UI", 9),
fg=self.colors['text_light'],
bg=self.colors['light_gray'])
self.time_label.pack(side=tk.RIGHT, padx=10, pady=6)
self.update_time()
def update_time(self):
"""Cập nhật thời gian"""
current_time = datetime.datetime.now().strftime("%H:%M:%S - %d/%m/%Y")
self.time_var.set(current_time)
self.root.after(1000, self.update_time)
def update_com_ports(self):
"""Cập nhật danh sách cổng COM"""
ports = [port.device for port in serial.tools.list_ports.comports()]
for scale_id, controls in self.scale_controls.items():
current_value = controls['com_var'].get()
controls['com_combo']['values'] = ports
if current_value in ports:
controls['com_var'].set(current_value)
elif ports:
controls['com_combo'].current(0)
self.status_var.set(f"Đã tìm thấy {len(ports)} cổng COM")
def toggle_connection(self, scale_id):
"""Kết nối/ngắt kết nối cân"""
controls = self.scale_controls[scale_id]
port = controls['com_var'].get()
if not port:
messagebox.showerror("Lỗi", f"Vui lòng chọn cổng COM cho {controls['scale_name']}")
return
if scale_id in self.serial_connections:
self.disconnect_scale(scale_id)
else:
self.connect_scale(scale_id)
def connect_scale(self, scale_id):
"""Kết nối cân"""
controls = self.scale_controls[scale_id]
widgets = self.data_widgets[scale_id]
port = controls['com_var'].get()
try:
controls['connect_btn'].config(text="Đang kết nối...", state="disabled")
widgets['status_var'].set(f"Đang kết nối với {port}...")
self.root.update()
# Tạo kết nối serial
baudrate = int(controls['baud_var'].get())
ser = serial.Serial(port, baudrate, timeout=1)
self.serial_connections[scale_id] = ser
# Tạo file lịch sử
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
history_file = os.path.join(self.history_dir, f"{scale_id}_{port.replace('/', '_')}_{timestamp}.csv")
with open(history_file, 'w', encoding='utf-8') as f:
f.write("Timestamp,Raw Data,Weight Value (kg)\n")
widgets['history_file'] = history_file
# Bắt đầu thread đọc dữ liệu
self.running[scale_id] = True
thread = threading.Thread(target=self.read_data, args=(scale_id,), daemon=True)
thread.start()
# Cập nhật UI
controls['connect_btn'].config(text="Ngắt kết nối", state="normal")
widgets['status_var'].set(f"Đã kết nối với {port}")
self.status_var.set(f"{controls['scale_name']} đã kết nối với {port}")
except Exception as e:
messagebox.showerror("Lỗi kết nối", f"Không thể kết nối {controls['scale_name']} với {port}:\n{str(e)}")
controls['connect_btn'].config(text="Kết nối", state="normal")
widgets['status_var'].set("Lỗi kết nối")
def disconnect_scale(self, scale_id):
"""Ngắt kết nối cân"""
controls = self.scale_controls[scale_id]
widgets = self.data_widgets[scale_id]
# Dừng thread
if scale_id in self.running:
self.running[scale_id] = False
time.sleep(0.3)
# Đóng serial connection
try:
if scale_id in self.serial_connections:
self.serial_connections[scale_id].close()
del self.serial_connections[scale_id]
except:
pass
# Reset UI
controls['connect_btn'].config(text="Kết nối")
widgets['status_var'].set("Chưa kết nối")
widgets['weight_var'].set("0.000")
widgets['weight_label'].config(fg=self.colors['text_dark'])
# Reset stats
for key in widgets['stats_vars']:
widgets['stats_vars'][key].set("0")
widgets['values'] = []
self.status_var.set(f"Đã ngắt kết nối {controls['scale_name']}")
def read_data(self, scale_id):
"""Thread đọc dữ liệu từ cân"""
ser = self.serial_connections[scale_id]
while self.running.get(scale_id, False):
try:
if ser.in_waiting > 0:
data = ser.readline().decode('utf-8', errors='ignore').strip()
if data:
try:
value = self.parse_weight(data)
self.update_display(scale_id, value, data)
except Exception as e:
print(f"Lỗi xử lý dữ liệu {scale_id}: {str(e)}")
time.sleep(0.1)
except Exception as e:
print(f"Lỗi đọc dữ liệu {scale_id}: {str(e)}")
self.running[scale_id] = False
break
def parse_weight(self, data):
"""Parse giá trị trọng lượng từ dữ liệu thô"""
weight_match = re.search(r'\d+\s+(\d+\.\d+)', data)
if weight_match:
return float(weight_match.group(1))
else:
wt_kg_match = re.search(r'WT/kg\s+\+?(\d+\.\d+)', data)
if wt_kg_match:
return float(wt_kg_match.group(1))
else:
all_numbers = re.findall(r'[-+]?\d*\.\d+|\d+', data)
if len(all_numbers) >= 2:
return float(all_numbers[1])
else:
number_match = re.search(r'[-+]?\d*\.\d+|\d+', data)
if number_match:
return float(number_match.group())
else:
return 0.0
def update_display(self, scale_id, value, raw_data):
"""Cập nhật hiển thị cho cân"""
widgets = self.data_widgets[scale_id]
# Cập nhật giá trị trọng lượng
widgets['weight_var'].set(f"{value:.3f}")
# Đổi màu theo giá trị
if value > 0:
widgets['weight_label'].config(fg='#2E7D32') # Xanh
elif value < 0:
widgets['weight_label'].config(fg='#C62828') # Đỏ
else:
widgets['weight_label'].config(fg='#F57C00') # Cam
# Cập nhật thống kê
values = widgets['values']
values.append(value)
if len(values) > 1000:
values = values[-1000:]
stats = widgets['stats_vars']
stats['count'].set(str(len(values)))
stats['max'].set(f"{max(values):.3f}")
stats['min'].set(f"{min(values):.3f}")
stats['avg'].set(f"{sum(values)/len(values):.3f}")
# Lưu vào file
try:
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
with open(widgets['history_file'], 'a', encoding='utf-8') as f:
f.write(f"{timestamp},{raw_data},{value:.3f}\n")
except Exception as e:
print(f"Lỗi ghi file: {str(e)}")
# Cập nhật status
scale_name = widgets['scale_name']
self.status_var.set(f"{scale_name}: {value:.3f} kg | Tổng: {len(values)} lần đọc")
def view_history(self):
"""Mở thư mục lịch sử"""
try:
if os.name == 'nt':
os.startfile(self.history_dir)
else:
os.system(f'xdg-open "{self.history_dir}"')
self.status_var.set(f"Đã mở thư mục lịch sử: {self.history_dir}")
except Exception as e:
messagebox.showerror("Lỗi", f"Không thể mở thư mục lịch sử:\n{str(e)}")
def disconnect_all(self):
"""Ngắt tất cả kết nối"""
if messagebox.askyesno("Xác nhận", "Bạn có chắc muốn ngắt tất cả kết nối?"):
for scale_id in list(self.serial_connections.keys()):
self.disconnect_scale(scale_id)
self.status_var.set("Đã ngắt tất cả kết nối")
if __name__ == "__main__":
root = tk.Tk()
app = DualScaleRS232Reader(root)
root.mainloop()

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
pyserial==3.5