Push New Project
This commit is contained in:
164
README.md
Normal file
164
README.md
Normal 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
52
ScaleRS232Reader_v2.spec
Normal 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,
|
||||||
|
)
|
3414
build/ScaleRS232Reader_v2/Analysis-00.toc
Normal file
3414
build/ScaleRS232Reader_v2/Analysis-00.toc
Normal file
File diff suppressed because it is too large
Load Diff
3072
build/ScaleRS232Reader_v2/EXE-00.toc
Normal file
3072
build/ScaleRS232Reader_v2/EXE-00.toc
Normal file
File diff suppressed because it is too large
Load Diff
3049
build/ScaleRS232Reader_v2/PKG-00.toc
Normal file
3049
build/ScaleRS232Reader_v2/PKG-00.toc
Normal file
File diff suppressed because it is too large
Load Diff
BIN
build/ScaleRS232Reader_v2/PYZ-00.pyz
Normal file
BIN
build/ScaleRS232Reader_v2/PYZ-00.pyz
Normal file
Binary file not shown.
380
build/ScaleRS232Reader_v2/PYZ-00.toc
Normal file
380
build/ScaleRS232Reader_v2/PYZ-00.toc
Normal 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')])
|
BIN
build/ScaleRS232Reader_v2/ScaleRS232Reader_v2.pkg
Normal file
BIN
build/ScaleRS232Reader_v2/ScaleRS232Reader_v2.pkg
Normal file
Binary file not shown.
BIN
build/ScaleRS232Reader_v2/base_library.zip
Normal file
BIN
build/ScaleRS232Reader_v2/base_library.zip
Normal file
Binary file not shown.
BIN
build/ScaleRS232Reader_v2/localpycs/pyimod01_archive.pyc
Normal file
BIN
build/ScaleRS232Reader_v2/localpycs/pyimod01_archive.pyc
Normal file
Binary file not shown.
BIN
build/ScaleRS232Reader_v2/localpycs/pyimod02_importers.pyc
Normal file
BIN
build/ScaleRS232Reader_v2/localpycs/pyimod02_importers.pyc
Normal file
Binary file not shown.
BIN
build/ScaleRS232Reader_v2/localpycs/pyimod03_ctypes.pyc
Normal file
BIN
build/ScaleRS232Reader_v2/localpycs/pyimod03_ctypes.pyc
Normal file
Binary file not shown.
BIN
build/ScaleRS232Reader_v2/localpycs/pyimod04_pywin32.pyc
Normal file
BIN
build/ScaleRS232Reader_v2/localpycs/pyimod04_pywin32.pyc
Normal file
Binary file not shown.
BIN
build/ScaleRS232Reader_v2/localpycs/struct.pyc
Normal file
BIN
build/ScaleRS232Reader_v2/localpycs/struct.pyc
Normal file
Binary file not shown.
27
build/ScaleRS232Reader_v2/warn-ScaleRS232Reader_v2.txt
Normal file
27
build/ScaleRS232Reader_v2/warn-ScaleRS232Reader_v2.txt
Normal 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)
|
7851
build/ScaleRS232Reader_v2/xref-ScaleRS232Reader_v2.html
Normal file
7851
build/ScaleRS232Reader_v2/xref-ScaleRS232Reader_v2.html
Normal file
File diff suppressed because it is too large
Load Diff
40
build_production.bat
Normal file
40
build_production.bat
Normal 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
BIN
dist/ScaleRS232Reader_v2.exe
vendored
Normal file
Binary file not shown.
575
main.py
Normal file
575
main.py
Normal 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
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
pyserial==3.5
|
Reference in New Issue
Block a user