├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java ├── META-INF │ └── MANIFEST.MF └── com │ └── znh │ └── siemens │ ├── StartApplication.java │ ├── controller │ └── SiemensWindowsController.java │ ├── thread │ └── DataReadThread.java │ ├── utils │ ├── FinalConstant.java │ ├── FinalFunction.java │ └── IpAddressUtil.java │ └── view │ └── SiemensWindowsView.java └── resources ├── META-INF └── MANIFEST.MF ├── fxml └── SiemensWindow.fxml ├── images ├── back.jpg ├── bg-selected.png ├── carousel │ ├── craftpix_1.jpg │ ├── craftpix_1@2x.jpg │ ├── craftpix_2.jpg │ ├── craftpix_2@2x.jpg │ ├── craftpix_3.jpg │ ├── craftpix_3@2x.jpg │ ├── gameart2d_1.jpg │ ├── gameart2d_1@2x.jpg │ ├── gameart2d_2.jpg │ ├── gameart2d_2@2x.jpg │ ├── gameart2d_3.jpg │ ├── gameart2d_3@2x.jpg │ ├── kenney_1.jpg │ ├── kenney_1@2x.jpg │ ├── kenney_2.jpg │ ├── kenney_2@2x.jpg │ ├── kenney_3.jpg │ ├── kenney_3@2x.jpg │ ├── pexels_1.jpg │ ├── pexels_1@2x.jpg │ ├── pexels_2.jpg │ ├── pexels_2@2x.jpg │ ├── pexels_3.jpg │ ├── pexels_3@2x.jpg │ ├── pixabay_1.jpg │ ├── pixabay_1@2x.jpg │ ├── pixabay_2.jpg │ ├── pixabay_2@2x.jpg │ ├── pixabay_3.jpg │ ├── pixabay_3@2x.jpg │ ├── unsplash_1.jpg │ ├── unsplash_1@2x.jpg │ ├── unsplash_2.jpg │ ├── unsplash_2@2x.jpg │ ├── unsplash_3.jpg │ └── unsplash_3@2x.jpg ├── chequers.png ├── coffee.png ├── coffee.svg ├── color-cursor.png ├── color-cursor@2x.png ├── colors-btn.png ├── colors-btn@2x.png ├── colors-icon.png ├── colors-icon@2x.png ├── dataType.png ├── default_img.png ├── default_img@2x.png ├── donate │ ├── buymeacoffee.png │ ├── wx.png │ ├── wx@2x.png │ ├── zfb.png │ └── zfb@2x.png ├── error.png ├── error@2x.png ├── fntsksm.png ├── fntsksm@2x.png ├── gFonts.png ├── gFonts@2x.png ├── gmail.png ├── gmail@2x.png ├── greenRound.png ├── header.png ├── header.svg ├── icon.ico ├── icon.jpg ├── iconfont.png ├── iconfont@2x.png ├── linux.png ├── linux@2x.png ├── logo │ ├── 128.png │ ├── 16.png │ ├── 256.png │ ├── 32.png │ └── 64.png ├── redRound.png ├── rotate-handle.png ├── selected.png ├── stop-indicator.png ├── translucent-min.png ├── translucent.png ├── transport-min.png ├── transport.png ├── warning.png └── warning@2x.png └── logback-spring.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse project files 2 | .project 3 | .classpath 4 | .settings 5 | 6 | # IntelliJ IDEA project files and directories 7 | .idea 8 | *.iml 9 | *.ipr 10 | *.iws 11 | .idea/ 12 | .shelf/ 13 | 14 | # Geany project file 15 | .geany 16 | 17 | # KDevelop project file and directory 18 | .kdev4/ 19 | *.kdev4 20 | 21 | # Build targets 22 | /target 23 | */target 24 | 25 | # Report directories 26 | /reports 27 | */reports 28 | 29 | # Mac-specific directory that no other operating system needs. 30 | .DS_Store 31 | 32 | # JVM crash logs 33 | hs_err_pid*.log 34 | 35 | dependency-reduced-pom.xml 36 | 37 | */.unison.* 38 | 39 | # exclude mainframer files 40 | mainframer 41 | .mainframer 42 | 43 | # exclude docker-sync stuff 44 | .docker-sync 45 | */.docker-sync 46 | 47 | # exclude vscode files 48 | .vscode/ 49 | *.factorypath 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2099 Zhan NianHua & Oscura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Siemens Window Program 2 | 3 | [![iot_communication-v1.5.0](https://img.shields.io/badge/iot_communication-v1.5.0-brightgreen)](https://mvnrepository.com/artifact/com.github.xingshuangs/iot-communication) 4 | ![Language-java8](https://img.shields.io/badge/Language-java8-blue) 5 | ![CopyRight-ZhanNianHua&Oscura](https://img.shields.io/badge/CopyRight-ZhanNianHua&Oscura-yellow) 6 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) 7 | 8 | ## 简述 9 | 10 | **如果这个库对你有帮助,请给作者一颗** ⭐
11 | 12 | 本项目采用iot-communication的西门子S7协议和JavaFX进行开发完成,是西门子S7协议客户端的一种实现形式,供大家学习参考。 13 | 14 | 本项目目前由Zhan NianHua独立开发完成,第一次开源没有相关经验,因此由Oscura代为发布到github之中,后期作者有空会进行持续维护迭代。 15 | 16 | ## 联系方式 17 | 18 | 如果有任何问题,可以通过以下方式联系作者,作者在空余时间会做解答。 19 | 20 | - QQ群:**759101350** 21 | - QQ私人:**837820457** 22 | - 邮件:**xingshuang_cool@163.com** 23 | 24 | ## 许可证 25 | 26 | 根据MIT许可证发布,更多信息请参见[`LICENSE`](./LICENSE)。
27 | @2023 - 2099 Zhan NianHua & Oscura 版权所有。
-------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.znh.siemens 8 | SiemensWindowProgram 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 8 13 | 8 14 | 1.18.18 15 | 2.0.32 16 | 1.2.3 17 | 1.5.0 18 | 2.6 19 | 2.6 20 | 21 | 22 | 23 | 24 | org.projectlombok 25 | lombok 26 | ${lombok.version} 27 | 28 | 29 | 30 | ch.qos.logback 31 | logback-classic 32 | ${logback.version} 33 | 34 | 35 | 36 | com.alibaba 37 | fastjson 38 | ${fastjson.version} 39 | 40 | 41 | com.github.xingshuangs 42 | iot-communication 43 | ${communication.version} 44 | 45 | 46 | commons-lang 47 | commons-lang 48 | ${commons-lang.version} 49 | 50 | 51 | 52 | commons-io 53 | commons-io 54 | ${commons-io.version} 55 | 56 | 57 | 58 | 59 | SiemensWindowProgram 60 | 61 | 62 | maven-assembly-plugin 63 | 64 | 65 | 66 | true 67 | com.znh.siemens.StartApplication 68 | 69 | 70 | 71 | jar-with-dependencies 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.znh.siemens.StartApplication 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/znh/siemens/StartApplication.java: -------------------------------------------------------------------------------- 1 | package com.znh.siemens; 2 | 3 | import com.znh.siemens.controller.SiemensWindowsController; 4 | import javafx.application.Application; 5 | import javafx.application.Platform; 6 | import javafx.fxml.FXMLLoader; 7 | import javafx.scene.Parent; 8 | import javafx.scene.Scene; 9 | import javafx.scene.image.Image; 10 | import javafx.stage.Stage; 11 | import javafx.stage.WindowEvent; 12 | 13 | /** 14 | * @Author JayNH 15 | * @Description TODO 启动类 16 | * @Date 2023-12-27 14:21 17 | * @Version 1.0 18 | */ 19 | public class StartApplication extends Application { 20 | 21 | public static void main(String[] args) { 22 | launch(args); 23 | } 24 | 25 | @Override 26 | public void start(Stage primaryStage) throws Exception { 27 | try{ 28 | FXMLLoader fxmlLoader = SiemensWindowsController.getFxmlLoader(); 29 | Parent root = fxmlLoader.load(); 30 | primaryStage.setTitle("西门子通讯程序 V1.0"); 31 | primaryStage.getIcons().add(new Image(StartApplication.class.getResource("/images/icon.jpg").toExternalForm())); 32 | primaryStage.setResizable(false); 33 | primaryStage.setMaximized(false); 34 | primaryStage.setOnCloseRequest(this::exit); 35 | primaryStage.setScene(new Scene(root)); 36 | primaryStage.show(); 37 | }catch(Exception e){ 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | private void exit(WindowEvent windowEvent){ 43 | Platform.exit(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/znh/siemens/controller/SiemensWindowsController.java: -------------------------------------------------------------------------------- 1 | package com.znh.siemens.controller; 2 | 3 | import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType; 4 | import com.github.xingshuangs.iot.protocol.s7.service.S7PLC; 5 | import com.znh.siemens.thread.DataReadThread; 6 | import com.znh.siemens.utils.FinalConstant; 7 | import com.znh.siemens.utils.FinalFunction; 8 | import com.znh.siemens.utils.IpAddressUtil; 9 | import com.znh.siemens.view.SiemensWindowsView; 10 | import javafx.event.ActionEvent; 11 | import javafx.fxml.FXMLLoader; 12 | import javafx.scene.chart.CategoryAxis; 13 | import javafx.scene.chart.NumberAxis; 14 | import javafx.scene.chart.XYChart; 15 | import javafx.scene.control.Button; 16 | import javafx.scene.image.Image; 17 | import org.apache.commons.lang.StringUtils; 18 | 19 | import java.net.URL; 20 | import java.text.SimpleDateFormat; 21 | import java.util.Date; 22 | import java.util.ResourceBundle; 23 | 24 | /** 25 | * @Author JayNH 26 | * @Description TODO 27 | * @Date 2023/5/5 11:08 28 | * @Version 1.0 29 | */ 30 | public class SiemensWindowsController extends SiemensWindowsView { 31 | 32 | private S7PLC s7PLC; 33 | private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); 34 | protected DataReadThread dataReadThread; 35 | protected XYChart.Series series = new XYChart.Series<>(); 36 | 37 | public static FXMLLoader getFxmlLoader() { 38 | URL url = SiemensWindowsController.class.getResource("/fxml/SiemensWindow.fxml"); 39 | return new FXMLLoader(url); 40 | } 41 | 42 | @Override 43 | public void initialize(URL location, ResourceBundle resources) { 44 | modelComboBox.getItems().addAll("200", "200SMART", "300", "400", "1200", "1500"); 45 | modelComboBox.setValue("200SMART"); 46 | timeChoiceBox.getItems().addAll("ms", "s"); 47 | timeChoiceBox.setValue("ms"); 48 | dataLineChart.getData().add(series); 49 | } 50 | 51 | /** 52 | * 连接按钮的点击事件 53 | */ 54 | public void connectButtonActionEvent() { 55 | try { 56 | sendMsgToShow(""); 57 | String ipAddressText = ipAddress.getText(); 58 | if (StringUtils.isNotBlank(ipAddressText)) { 59 | if (IpAddressUtil.isIP(ipAddressText)) { 60 | // 获取模型下拉框选中的值 61 | String modelText = modelComboBox.getValue(); 62 | switch (modelText) { 63 | case "200": 64 | s7PLC = new S7PLC(EPlcType.S200, ipAddressText); 65 | break; 66 | case "200SMART": 67 | s7PLC = new S7PLC(EPlcType.S200_SMART, ipAddressText); 68 | break; 69 | case "300": 70 | s7PLC = new S7PLC(EPlcType.S300, ipAddressText); 71 | break; 72 | case "400": 73 | s7PLC = new S7PLC(EPlcType.S400, ipAddressText); 74 | break; 75 | case "1200": 76 | s7PLC = new S7PLC(EPlcType.S1200, ipAddressText); 77 | break; 78 | case "1500": 79 | s7PLC = new S7PLC(EPlcType.S1500, ipAddressText); 80 | break; 81 | default: 82 | break; 83 | } 84 | // 设置超时时间 85 | s7PLC.setConnectTimeout(3000); 86 | s7PLC.setReceiveTimeout(5000); 87 | s7PLC.connect(); 88 | if (s7PLC.checkConnected()) { 89 | connectButton.setDisable(true); 90 | disconnectButton.setDisable(false); 91 | readButton.setDisable(false); 92 | batchReadButton.setDisable(false); 93 | statusImg.setImage(new Image("/images/greenRound.png")); 94 | sendMsgToShow("PLC " + ipAddressText + " 连接成功!"); 95 | }else{ 96 | sendMsgToShow(" PLC连接失败!"); 97 | } 98 | } else { 99 | sendMsgToShow(" Ip地址格式错误!"); 100 | } 101 | } else { 102 | sendMsgToShow(" Ip地址不能为空!"); 103 | } 104 | } catch (Exception e) { 105 | sendMsgToShow(" PLC连接失败!"); 106 | } 107 | } 108 | 109 | 110 | /** 111 | * @author JayNH 112 | * @description //TODO 断开连接点击事件 113 | * @date 2023/2/24 15:23 114 | */ 115 | public void disconnectionButtonActionEvent() { 116 | try { 117 | sendMsgToShow(""); 118 | if (s7PLC != null) { 119 | s7PLC.close(); 120 | connectButton.setDisable(false); 121 | disconnectButton.setDisable(true); 122 | readButton.setDisable(true); 123 | stopButton.setDisable(true); 124 | batchReadButton.setDisable(true); 125 | startCurveShader.setDisable(true); 126 | stopCurveShader.setDisable(true); 127 | connectButton.requestFocus(); 128 | statusImg.setImage(new Image("/images/redRound.png")); 129 | sendMsgToShow(""); 130 | } 131 | } catch (Exception e) { 132 | tipsLabel.setText(sdf.format(new Date()) + e.getMessage()); 133 | e.printStackTrace(); 134 | } 135 | } 136 | 137 | /** 138 | * @param actionEvent 事件 139 | * @author JayNH 140 | * @description //TODO 根据类型按钮选择对应的数据类型并填充到输入框中 141 | * @date 2023/2/24 16:09 142 | */ 143 | public void dataTypeButtonChooseActionEvent(ActionEvent actionEvent) { 144 | Button button = (Button) actionEvent.getSource(); 145 | String typeName = button.getId(); 146 | dataType.setText(typeName); 147 | } 148 | 149 | /** 150 | * @author JayNH 151 | * @description //TODO 开始读取数据按钮点击事件 152 | * @date 2023/2/24 16:23 153 | */ 154 | public void readButtonClickEvent() { 155 | sendMsgToShow(""); 156 | // 判断地址是否为空 157 | if (StringUtils.isNotBlank(readOffset.getText())) { 158 | // 判断数据类型是否有选择 159 | if (StringUtils.isNotBlank(dataType.getText())) { 160 | // 判断读取间隔时间 161 | if (StringUtils.isNotBlank(readTime.getText())) { 162 | if (StringUtils.isNumeric(readTime.getText())) { 163 | if (s7PLC != null && s7PLC.checkConnected()) { 164 | // 判断地址是否为空以及地址填写的是否正确 165 | String offsetString = readOffset.getText(); 166 | int strLength = 0; 167 | if (StringUtils.isNotBlank(stringLength.getText())) { 168 | strLength = Integer.parseInt(stringLength.getText()); 169 | } 170 | dataReadThread = new DataReadThread(s7PLC, dataType.getText(), offsetString, strLength, timeChoiceBox.getValue(), Integer.parseInt(readTime.getText()), readResult, series); 171 | dataReadThread.start(); 172 | readButton.setDisable(true); 173 | stopButton.setDisable(false); 174 | startCurveShader.setDisable(false); 175 | } else { 176 | sendMsgToShow(" PLC未连接!"); 177 | } 178 | } else { 179 | sendMsgToShow(" 读取频率格式错误!"); 180 | } 181 | } else { 182 | sendMsgToShow(" 读取频率不能为空!"); 183 | } 184 | } else { 185 | sendMsgToShow(" 数据类型未选择!"); 186 | } 187 | } else { 188 | sendMsgToShow(" 地址不能为空!"); 189 | } 190 | } 191 | 192 | /** 193 | * @author JayNH 194 | * @description //TODO 停止读取数据按钮点击事件 195 | * @date 2023/2/24 16:24 196 | */ 197 | public void stopButtonClickEvent() { 198 | if (dataReadThread != null) { 199 | try { 200 | // 停止读取数据线程 201 | dataReadThread.isRunning = false; 202 | readButton.setDisable(false); 203 | stopButton.setDisable(true); 204 | startCurveShader.setDisable(true); 205 | stopCurveShader.setDisable(true); 206 | } catch (Exception e) { 207 | e.printStackTrace(); 208 | readButton.setDisable(true); 209 | stopButton.setDisable(false); 210 | } 211 | } 212 | } 213 | 214 | /** 215 | * @author JayNH 216 | * @description //TODO 开启曲线数据渲染按钮点击事件 217 | * @date 2023/2/26 21:21 218 | */ 219 | public void startCurveShaderClickEvent() { 220 | // 判断数据类型是否支持曲线渲染 221 | if(!dataType.getText().equalsIgnoreCase(FinalConstant.BOOL) && !dataType.getText().equalsIgnoreCase(FinalConstant.STRING)){ 222 | dataReadThread.isStartCurveShader = true; 223 | startCurveShader.setDisable(true); 224 | stopCurveShader.setDisable(false); 225 | }else{ 226 | sendMsgToShow("该数据类型"+dataType.getText()+"不支持图表渲染!"); 227 | } 228 | } 229 | 230 | /** 231 | * @author JayNH 232 | * @description //TODO 关闭曲线数据渲染按钮点击事件 233 | * @date 2023/2/26 21:21 234 | */ 235 | public void stopCurveShaderClickEvent() { 236 | dataReadThread.isStartCurveShader = false; 237 | startCurveShader.setDisable(false); 238 | stopCurveShader.setDisable(true); 239 | } 240 | 241 | /** 242 | * @author JayNH 243 | * @description //TODO 批量读取数据按钮点击事件 244 | * @date 2023/2/27 15:41 245 | */ 246 | public void batchReadDataClickEvent() { 247 | sendMsgToShow(""); 248 | if (StringUtils.isNotBlank(batchReadOffset.getText())) { 249 | if (StringUtils.isNotBlank(batchReadLength.getText())) { 250 | if (StringUtils.isNumeric(batchReadLength.getText())) { 251 | if (s7PLC != null && s7PLC.checkConnected()) { 252 | int length = Integer.parseInt(batchReadLength.getText()); 253 | byte[] data = s7PLC.readByte(batchReadOffset.getText(), length); 254 | String byteString = FinalFunction.toHexString(data); 255 | batchReadResult.setText(byteString); 256 | } else { 257 | sendMsgToShow(" PLC连接失败!"); 258 | } 259 | } else { 260 | sendMsgToShow(" 批量读取长度格式错误!"); 261 | } 262 | } else { 263 | sendMsgToShow(" 批量读取长度不能为空!"); 264 | } 265 | } else { 266 | sendMsgToShow(" 批量读取地址不能为空!"); 267 | } 268 | } 269 | 270 | /** 271 | * @param actionEvent 事件 272 | * @author JayNH 273 | * @description //TODO 数据类型写入PLC 274 | * @date 2023/2/27 17:04 275 | */ 276 | public void dataTypeWriteClickEvent(ActionEvent actionEvent) { 277 | sendMsgToShow(""); 278 | try { 279 | Button button = (Button) actionEvent.getSource(); 280 | String dataTypeStr = button.getId(); 281 | String dataType = dataTypeStr.split("_")[1]; 282 | String offset = writeReadOffset.getText(); 283 | String value = writeDataValue.getText(); 284 | if (StringUtils.isNotBlank(offset)) { 285 | if (StringUtils.isNotBlank(value)) { 286 | if (s7PLC != null && s7PLC.checkConnected()) { 287 | switch (dataType.toLowerCase()) { 288 | case "bool": 289 | boolean boolVal = "true".equals(value) || "1".equals(value); 290 | s7PLC.writeBoolean(offset, boolVal); 291 | break; 292 | case "byte": 293 | byte byteVal = Byte.parseByte(value); 294 | s7PLC.writeByte(offset, byteVal); 295 | break; 296 | case "short": 297 | short shortVal = Short.parseShort(value); 298 | s7PLC.writeInt16(offset, shortVal); 299 | break; 300 | case "ushort": 301 | int uShortVal = Integer.parseInt(value); 302 | s7PLC.writeUInt16(offset, uShortVal); 303 | break; 304 | case "int": 305 | case "long": 306 | int intVal = Integer.parseInt(value); 307 | s7PLC.writeInt32(offset, intVal); 308 | break; 309 | case "uint": 310 | case "ulong": 311 | long uintVal = Long.parseLong(value); 312 | s7PLC.writeUInt32(offset, uintVal); 313 | break; 314 | case "float": 315 | float floatVal = Float.parseFloat(value); 316 | s7PLC.writeFloat32(offset, floatVal); 317 | break; 318 | case "double": 319 | double doubleVal = Double.parseDouble(value); 320 | s7PLC.writeFloat64(offset, doubleVal); 321 | break; 322 | default: 323 | break; 324 | } 325 | } else { 326 | sendMsgToShow(" PLC连接失败!"); 327 | } 328 | } else { 329 | sendMsgToShow(" 写入数值不能为空!"); 330 | } 331 | } else { 332 | sendMsgToShow(" 写入地址不能为空!"); 333 | } 334 | } catch (Exception e) { 335 | sendMsgToShow(" 数据写入失败!"); 336 | e.printStackTrace(); 337 | } 338 | } 339 | 340 | /** 341 | * 将错误内容进行显示 342 | * 343 | * @param content 错误信息 344 | */ 345 | private void sendMsgToShow(String content) { 346 | tipsLabel.setText(sdf.format(new Date()) + " --> " + content); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/main/java/com/znh/siemens/thread/DataReadThread.java: -------------------------------------------------------------------------------- 1 | package com.znh.siemens.thread; 2 | 3 | import com.github.xingshuangs.iot.protocol.s7.service.S7PLC; 4 | import com.znh.siemens.utils.FinalConstant; 5 | import javafx.application.Platform; 6 | import javafx.scene.chart.CategoryAxis; 7 | import javafx.scene.chart.NumberAxis; 8 | import javafx.scene.chart.XYChart; 9 | import javafx.scene.control.TextArea; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.text.SimpleDateFormat; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * @Author JayNH 17 | * @Description TODO 18 | * @Date 2023/5/5 11:44 19 | * @Version 1.0 20 | */ 21 | @Slf4j 22 | public class DataReadThread extends Thread { 23 | 24 | protected S7PLC s7PLC; 25 | public boolean isRunning = true; 26 | public boolean isStartCurveShader = false; 27 | private String dataType; 28 | private String offset; 29 | private int stringLength; 30 | public int shaderCount = 5; 31 | private String timeType; 32 | private int readInterval; 33 | private TextArea readResult; 34 | private XYChart.Series series = new XYChart.Series<>(); 35 | protected SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); 36 | 37 | public DataReadThread(){} 38 | 39 | public DataReadThread(S7PLC s7PLC, String dataType, String offset, Integer stringLength, String timeType, int readInterval, TextArea readResult, XYChart.Series series){ 40 | this.s7PLC = s7PLC; 41 | this.dataType = dataType; 42 | this.offset = offset; 43 | this.stringLength = stringLength; 44 | this.timeType = timeType; 45 | this.readInterval = readInterval; 46 | this.readResult = readResult; 47 | this.series = series; 48 | } 49 | 50 | @Override 51 | public void run() { 52 | try{ 53 | log.info("----------读取线程开启----------"+Thread.currentThread().getName()); 54 | String resultData = ""; 55 | while(isRunning){ 56 | if(!s7PLC.checkConnected()){ 57 | isRunning = false; 58 | } 59 | switch(dataType){ 60 | case "Bool": 61 | boolean booleanResult = s7PLC.readBoolean(offset); 62 | resultData = Boolean.toString(booleanResult); 63 | break; 64 | case "Byte": 65 | byte byteResult = s7PLC.readByte(offset); 66 | resultData = Byte.toString(byteResult); 67 | break; 68 | case "Short": 69 | short shortResult = s7PLC.readInt16(offset); 70 | resultData = Short.toString(shortResult); 71 | break; 72 | case "UShort": 73 | int uShortResult = s7PLC.readUInt16(offset); 74 | resultData = Integer.toString(uShortResult); 75 | break; 76 | case "Int": 77 | case "Long": 78 | int intResult = s7PLC.readInt32(offset); 79 | resultData = Integer.toString(intResult); 80 | break; 81 | case "UInt": 82 | case "ULong": 83 | long uIntResult = s7PLC.readUInt32(offset); 84 | resultData = Long.toString(uIntResult); 85 | break; 86 | case "Float": 87 | float floatResult = s7PLC.readFloat32(offset); 88 | resultData = Float.toString(floatResult); 89 | break; 90 | case "Double": 91 | double doubleResult = s7PLC.readFloat32(offset); 92 | resultData = Double.toString(doubleResult); 93 | break; 94 | case "String": 95 | resultData = s7PLC.readString(offset,stringLength); 96 | break; 97 | default: 98 | break; 99 | } 100 | long currentTime = System.currentTimeMillis(); 101 | readResult.setText(simpleDateFormat.format(currentTime)+" : "+resultData); 102 | // 判断是否开启曲线渲染数据 103 | if(isStartCurveShader){ 104 | String finalResultData = resultData; 105 | Platform.runLater(() -> { 106 | if(series.getData().size()==shaderCount){ 107 | series.getData().remove(0); 108 | } 109 | series.getData().add(new XYChart.Data(simpleDateFormat.format(currentTime),Float.parseFloat(finalResultData))); 110 | }); 111 | } 112 | try { 113 | if (timeType.equals(FinalConstant.MS)) { 114 | TimeUnit.MILLISECONDS.sleep(readInterval); 115 | } else { 116 | TimeUnit.SECONDS.sleep(readInterval); 117 | } 118 | } catch (InterruptedException e) { 119 | throw new RuntimeException(e); 120 | } 121 | } 122 | log.info("----------读取线程结束----------"+Thread.currentThread().getName()); 123 | }catch (Exception e){ 124 | log.error(e.getMessage()); 125 | e.printStackTrace(); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/com/znh/siemens/utils/FinalConstant.java: -------------------------------------------------------------------------------- 1 | package com.znh.siemens.utils; 2 | 3 | /** 4 | * @Author JayNH 5 | * @Description TODO 6 | * @Date 2023/5/5 15:25 7 | * @Version 1.0 8 | */ 9 | public class FinalConstant { 10 | 11 | public static final String M = "M"; 12 | public static final String Q = "Q"; 13 | public static final String I = "I"; 14 | public static final String T = "T"; 15 | public static final String C = "C"; 16 | public static final String D = "D"; 17 | public static final String DB = "DB"; 18 | public static final String MS = "ms"; 19 | 20 | public static final String BOOL = "bool"; 21 | public static final String BYTE = "byte"; 22 | public static final String SHORT = "short"; 23 | public static final String INT = "int"; 24 | public static final String LONG = "long"; 25 | public static final String USHORT = "ushort"; 26 | public static final String UINT = "uint"; 27 | public static final String ULONG = "ulong"; 28 | public static final String FLOAT = "float"; 29 | public static final String DOUBLE = "double"; 30 | public static final String STRING = "string"; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/znh/siemens/utils/FinalFunction.java: -------------------------------------------------------------------------------- 1 | package com.znh.siemens.utils; 2 | 3 | /** 4 | * @Author JayNH 5 | * @Description TODO 6 | * @Date 2023/5/5 15:25 7 | * @Version 1.0 8 | */ 9 | public class FinalFunction { 10 | 11 | /** 12 | * 字节数组转成16进制表示格式的字符串 13 | * @param byteArray 需要转换的字节数组 14 | * @return 16进制表示格式的字符串 15 | **/ 16 | public static String toHexString(byte[] byteArray) { 17 | if (byteArray == null || byteArray.length < 1) 18 | throw new IllegalArgumentException("this byteArray must not be null or empty"); 19 | 20 | final StringBuilder hexString = new StringBuilder(); 21 | for (int i = 0; i < byteArray.length; i++) { 22 | // 0~F前面不零 23 | if ((byteArray[i] & 0xff) < 0x10) 24 | hexString.append("0"); 25 | hexString.append(Integer.toHexString(0xFF & byteArray[i])+" "); 26 | } 27 | return hexString.toString().toLowerCase(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/znh/siemens/utils/IpAddressUtil.java: -------------------------------------------------------------------------------- 1 | package com.znh.siemens.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.net.HttpURLConnection; 11 | import java.net.URL; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * @Author JayNH 17 | * @Description TODO IP工具类 18 | * @Date 2023-05-27 14:21 19 | * @Version 1.0 20 | */ 21 | public class IpAddressUtil { 22 | 23 | public static boolean isIP(String addr) { 24 | if (addr == null || addr.length() < 7 || addr.length() > 15) { 25 | return false; 26 | } 27 | String rep = 28 | "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}"; 29 | Pattern pat = Pattern.compile(rep); 30 | Matcher mat = pat.matcher(addr); 31 | return mat.find(); 32 | } 33 | 34 | /** 35 | * 通过IP获取地址(需要联网,调用淘宝的IP库) 36 | * 37 | * @param ip ip地址 38 | */ 39 | public static String getIpInfo(String ip) { 40 | if ("127.0.0.1".equals(ip)) { 41 | ip = "127.0.0.1"; 42 | } 43 | String info = ""; 44 | try { 45 | URL url = new URL("http://ip.taobao.com/service/getIpInfo.php?ip=" + ip); 46 | HttpURLConnection httpOnly = (HttpURLConnection) url.openConnection(); 47 | httpOnly.setRequestMethod("GET"); 48 | httpOnly.setDoOutput(true); 49 | httpOnly.setDoInput(true); 50 | httpOnly.setUseCaches(false); 51 | InputStream in = httpOnly.getInputStream(); 52 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in)); 53 | StringBuilder temp = new StringBuilder(); 54 | String line = bufferedReader.readLine(); 55 | while (line != null) { 56 | temp.append(line).append("\r\n"); 57 | line = bufferedReader.readLine(); 58 | } 59 | bufferedReader.close(); 60 | JSONObject obj = (JSONObject) JSON.parse(temp.toString()); 61 | if (obj.getIntValue("code") == 0) { 62 | JSONObject data = obj.getJSONObject("data"); 63 | info += data.getString("country") + " "; 64 | info += data.getString("region") + " "; 65 | info += data.getString("city") + " "; 66 | info += data.getString("isp"); 67 | } 68 | } catch (IOException e) { 69 | e.printStackTrace(); 70 | } 71 | return info; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/znh/siemens/view/SiemensWindowsView.java: -------------------------------------------------------------------------------- 1 | package com.znh.siemens.view; 2 | 3 | import javafx.fxml.FXML; 4 | import javafx.fxml.Initializable; 5 | import javafx.scene.chart.CategoryAxis; 6 | import javafx.scene.chart.LineChart; 7 | import javafx.scene.chart.NumberAxis; 8 | import javafx.scene.control.*; 9 | import javafx.scene.image.ImageView; 10 | import lombok.Data; 11 | 12 | /** 13 | * @Author JayNH 14 | * @Version 1.0 15 | * @Description TODO 16 | * @Date 2023/5/5 10:35 17 | */ 18 | @Data 19 | public abstract class SiemensWindowsView implements Initializable { 20 | 21 | @FXML 22 | protected TextField ipAddress; 23 | @FXML 24 | protected ComboBox modelComboBox; 25 | @FXML 26 | protected Button connectButton; 27 | @FXML 28 | protected Button disconnectButton; 29 | @FXML 30 | protected Label tipsLabel; 31 | @FXML 32 | protected TextField stringLength; 33 | @FXML 34 | protected ImageView statusImg; 35 | @FXML 36 | protected TextField readOffset; 37 | @FXML 38 | protected TextArea readResult; 39 | @FXML 40 | protected TextField readTime; 41 | @FXML 42 | protected TextField dataType; 43 | @FXML 44 | protected Button readButton; 45 | @FXML 46 | protected Button stopButton; 47 | @FXML 48 | protected Button startCurveShader; 49 | @FXML 50 | protected Button stopCurveShader; 51 | @FXML 52 | protected ChoiceBox timeChoiceBox; 53 | @FXML 54 | protected LineChart dataLineChart; 55 | @FXML 56 | protected TextField batchReadOffset; 57 | @FXML 58 | protected TextField batchReadLength; 59 | @FXML 60 | protected TextArea batchReadResult; 61 | @FXML 62 | protected Button batchReadButton; 63 | @FXML 64 | protected TextField writeReadOffset; 65 | @FXML 66 | protected TextField writeDataValue; 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.znh.siemens.StartApplication 3 | Class-Path: slf4j-api-1.7.25.jar logback-classic-1.2.3.jar commons-io-2. 4 | 6.jar fastjson2-extension-2.0.32.jar commons-lang-2.6.jar iot-communica 5 | tion-1.5.0.jar logback-core-1.2.3.jar lombok-1.18.18.jar fastjson2-2.0. 6 | 32.jar fastjson-2.0.32.jar 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/fxml/SiemensWindow.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 51 | 52 | 53 | 54 | 59 | 64 | 69 | 70 | 71 | 72 | 73 | 74 | 79 | 84 | 89 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 108 | 113 | 118 | 123 | 128 | 133 | 138 | 143 | 148 | 153 | 158 | 159 | 160 | 165 | 170 | 171 | 172 | 173 | 174 | 179 | 180 | 185 | 190 | 191 | 196 | 197 | 198 | 203 | 208 | 209 | 210 | 211 | 212 | 217 | 222 | 227 | 232 | 237 | 242 | 243 | 244 | 245 | 246 | 251 | 256 | 261 | 266 | 271 | 272 | 273 | 274 | 275 | 276 |