| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- import React, { useState } from 'react';
- import {
- Card,
- Form,
- Input,
- Button,
- Table,
- Tag,
- Space,
- Statistic,
- InputNumber,
- message,
- Modal,
- Checkbox
- } from 'antd';
- import {
- ThunderboltOutlined,
- PlusOutlined,
- DeleteOutlined,
- EditOutlined
- } from '@ant-design/icons';
- interface DeviceConfig {
- id: string;
- name: string;
- deviceNo: string;
- ipAddress: string;
- port: number;
- gatewayBaudRate: number;
- address: number;
- baudRate: number;
- dataBits: number;
- stopBits: number;
- parity: 'none' | 'even' | 'odd';
- timeout: number;
- unitId: number;
- verificationCode?: string;
- // 报警阈值配置
- tempThreshold: {
- min: number;
- max: number;
- };
- humidityThreshold: {
- min: number;
- max: number;
- };
- alertMethods: {
- push: boolean;
- sms: boolean;
- };
- }
- interface SensorData {
- temperature: number;
- humidity: number;
- timestamp: string;
- }
- // 临时模拟ModbusRTU功能
- const ModbusRTU = {
- connect: async (device: Omit<DeviceConfig, 'id'>): Promise<boolean> => {
- console.log('模拟连接设备:', device);
- return true;
- },
- readHoldingRegisters: async (addr: number, count: number): Promise<number[]> => {
- console.log('模拟读取寄存器:', addr, count);
- return [255, 652]; // 模拟温度25.5°C和湿度65.2%
- }
- };
- const GreenhouseProtocolPage: React.FC = () => {
- const [editingDevice, setEditingDevice] = useState<DeviceConfig | null>(null);
- const [modalVisible, setModalVisible] = useState(false);
- const [form] = Form.useForm();
- const [devices, setDevices] = useState<DeviceConfig[]>([]);
- const [currentData, setCurrentData] = useState<SensorData | null>(null);
- const [loading, setLoading] = useState(false);
- const [connected, setConnected] = useState(false);
- const generateVerificationCode = () => {
- const code = Math.floor(Math.random() * 65536).toString(16).toUpperCase();
- return code.length === 1 ? `0${code}` : code.padStart(2, '0');
- };
- const handleAddDevice = () => {
- const values = form.getFieldsValue();
- setDevices([...devices, {
- id: `device-${Date.now()}`,
- deviceNo: values.deviceNo,
- verificationCode: generateVerificationCode(),
- tempThreshold: {
- min: 10,
- max: 30
- },
- humidityThreshold: {
- min: 30,
- max: 80
- },
- alertMethods: {
- push: true,
- sms: false
- },
- ...values
- }]);
- form.resetFields();
- };
- const handleSendCommand = (device: DeviceConfig) => {
- if (!device.verificationCode) {
- message.error('请先生成校验码');
- return;
- }
- const deviceNoHex = parseInt(device.deviceNo).toString(16).padStart(2, '0').toUpperCase();
- const command = `[${deviceNoHex} 04 00 00 00 02 ${device.verificationCode}]`;
-
- // 模拟返回数据 (温度18-25℃, 湿度40-70%)
- const temp = (18 + Math.random() * 7).toFixed(1);
- const humidity = (40 + Math.random() * 30).toFixed(1);
- const tempInt = parseInt(temp);
- const tempDec = parseInt((temp.split('.')[1] || '0'));
- const humInt = parseInt(humidity);
- const humDec = parseInt((humidity.split('.')[1] || '0'));
-
- const response = `[${deviceNoHex} 04 04 ${tempInt.toString(16).padStart(2,'0')} ${tempDec.toString(16).padStart(2,'0')} ${humInt.toString(16).padStart(2,'0')} ${humDec.toString(16).padStart(2,'0')} 9B 1E]`;
-
- console.log('发送指令:', command);
- message.success(<>
- <div>指令已发送:</div>
- <div style={{fontFamily: 'monospace', marginBottom: 8}}>{command}</div>
- <div>返回数据:</div>
- <div style={{fontFamily: 'monospace', marginBottom: 8}}>{response}</div>
- <div>解析结果: 温度 {temp}℃, 湿度 {humidity}%</div>
- </>);
- };
- const handleConnect = async (device: DeviceConfig) => {
- setLoading(true);
- try {
- await ModbusRTU.connect(device);
- setConnected(true);
- } catch (err) {
- console.error('连接失败:', err);
- } finally {
- setLoading(false);
- }
- };
- const handleReadData = async () => {
- if (!connected) return;
-
- setLoading(true);
- try {
- const data = await ModbusRTU.readHoldingRegisters(0, 2);
- setCurrentData({
- temperature: data[0] / 10,
- humidity: data[1] / 10,
- timestamp: new Date().toISOString()
- });
- } catch (err) {
- console.error('读取数据失败:', err);
- } finally {
- setLoading(false);
- }
- };
- const columns = [
- {
- title: '报警状态',
- key: 'alertStatus',
- render: (_: unknown, record: DeviceConfig) => {
- const tempAlert = currentData && (
- currentData.temperature < record.tempThreshold.min ||
- currentData.temperature > record.tempThreshold.max
- );
- const humidityAlert = currentData && (
- currentData.humidity < record.humidityThreshold.min ||
- currentData.humidity > record.humidityThreshold.max
- );
-
- return (
- <Space>
- {tempAlert && <Tag color="error">温度异常</Tag>}
- {humidityAlert && <Tag color="error">湿度异常</Tag>}
- {!tempAlert && !humidityAlert && <Tag color="success">正常</Tag>}
- </Space>
- );
- },
- },
- {
- title: '设备ID',
- dataIndex: 'id',
- key: 'id',
- },
- {
- title: '设备编号',
- dataIndex: 'deviceNo',
- key: 'deviceNo',
- },
- {
- title: '设备名称',
- dataIndex: 'name',
- key: 'name',
- },
- {
- title: 'Modbus地址',
- dataIndex: 'address',
- key: 'address',
- },
- {
- title: '状态',
- key: 'status',
- render: () => (
- <Tag color={connected ? 'success' : 'error'}>
- {connected ? '已连接' : '未连接'}
- </Tag>
- ),
- },
- {
- title: '操作',
- key: 'action',
- render: (_: unknown, record: DeviceConfig) => (
- <Space size="middle">
- <Button
- type="link"
- icon={<EditOutlined />}
- onClick={() => {
- setEditingDevice(record);
- setModalVisible(true);
- }}
- >
- 编辑
- </Button>
- <Button
- type="link"
- danger
- icon={<DeleteOutlined />}
- onClick={() => {
- Modal.confirm({
- title: '确认删除设备配置?',
- content: `确定要删除设备 ${record.name} 吗?`,
- onOk: () => {
- setDevices((prev: DeviceConfig[]) => prev.filter(d => d.id !== record.id));
- message.success('设备配置已删除');
- },
- });
- }}
- >
- 删除
- </Button>
- <Button
- type="primary"
- onClick={() => handleConnect(record)}
- loading={loading}
- >
- 连接
- </Button>
- <Button
- icon={<ThunderboltOutlined />}
- onClick={() => handleReadData()}
- disabled={!connected}
- >
- 读取数据
- </Button>
- </Space>
- ),
- },
- ];
- return (
- <Card title="温室设备通信协议配置">
- <Form form={form} layout="inline">
- <Form.Item name="name" label="设备名称" rules={[{ required: true }]}>
- <Input placeholder="例如: 温室1号" />
- </Form.Item>
- <Form.Item name="deviceNo" label="设备编号" rules={[{ required: true, message: '请输入设备编号' }]}>
- <Input placeholder="例如: GH001" />
- </Form.Item>
- <Form.Item name="address" label="Modbus地址" rules={[{ required: true }]}>
- <InputNumber min={1} max={247} />
- </Form.Item>
- <Form.Item>
- <Button type="primary" onClick={handleAddDevice}>
- 添加设备
- </Button>
- </Form.Item>
- </Form>
- <Modal
- title="编辑设备配置"
- visible={modalVisible}
- onCancel={() => setModalVisible(false)}
- footer={null}
- >
- <Form
- form={form}
- initialValues={editingDevice || {
- tempThreshold: { min: 10, max: 30 },
- humidityThreshold: { min: 30, max: 80 },
- alertMethods: { push: true, sms: false }
- }}
- layout="vertical"
- >
- <Form.Item name="deviceNo" label="设备编号" rules={[{ required: true }]}>
- <Input />
- </Form.Item>
- <Form.Item name="ipAddress" label="IP地址" rules={[{ required: true }]}>
- <Input placeholder="例如: 192.168.1.1" />
- </Form.Item>
- <Form.Item name="port" label="端口号" rules={[{ required: true }]}>
- <InputNumber min={1} max={65535} />
- </Form.Item>
- <Form.Item name="gatewayBaudRate" label="网关波特率" rules={[{ required: true }]}>
- <InputNumber min={1200} max={115200} />
- </Form.Item>
- <Form.Item>
- <Button
- type="primary"
- onClick={() => {
- // 获取校验码逻辑
- message.info('校验码已发送');
- }}
- >
- 获取校验码
- </Button>
- <Button
- icon={<ThunderboltOutlined />}
- onClick={() => {
- if (editingDevice) {
- handleSendCommand(editingDevice);
- }
- }}
- style={{ marginLeft: 16 }}
- >
- 发送指令
- </Button>
- </Form.Item>
- <Form.Item name="verificationCode" label="校验码">
- <Input disabled />
- </Form.Item>
- <Card title="报警阈值设置" style={{ marginBottom: 16 }}>
- <Form.Item label="温度阈值(°C)" style={{ marginBottom: 8 }}>
- <Space>
- <Form.Item name={['tempThreshold', 'min']} noStyle>
- <InputNumber min={-20} max={100} placeholder="最低" />
- </Form.Item>
- <span>~</span>
- <Form.Item name={['tempThreshold', 'max']} noStyle>
- <InputNumber min={-20} max={100} placeholder="最高" />
- </Form.Item>
- </Space>
- </Form.Item>
- <Form.Item label="湿度阈值(%)" style={{ marginBottom: 8 }}>
- <Space>
- <Form.Item name={['humidityThreshold', 'min']} noStyle>
- <InputNumber min={0} max={100} placeholder="最低" />
- </Form.Item>
- <span>~</span>
- <Form.Item name={['humidityThreshold', 'max']} noStyle>
- <InputNumber min={0} max={100} placeholder="最高" />
- </Form.Item>
- </Space>
- </Form.Item>
- <Form.Item label="报警方式">
- <Space>
- <Form.Item name={['alertMethods', 'push']} valuePropName="checked" noStyle>
- <Checkbox>消息推送</Checkbox>
- </Form.Item>
- <Form.Item name={['alertMethods', 'sms']} valuePropName="checked" noStyle>
- <Checkbox>短信通知</Checkbox>
- </Form.Item>
- </Space>
- </Form.Item>
- </Card>
- </Form>
- </Modal>
- <Table
- columns={columns}
- dataSource={devices}
- rowKey="id"
- style={{ marginTop: 16 }}
- />
- {currentData && (
- <Card title="实时数据" style={{ marginTop: 16 }}>
- <Space size="large">
- <Statistic
- title="温度"
- value={currentData.temperature}
- suffix="°C"
- precision={1}
- />
- <Statistic
- title="湿度"
- value={currentData.humidity}
- suffix="%"
- precision={1}
- />
- <div>最后更新: {new Date(currentData.timestamp).toLocaleString()}</div>
- </Space>
- </Card>
- )}
- </Card>
- );
- };
- export { GreenhouseProtocolPage };
|