├── Modbus.ipynb ├── Python工業4.0 - 工廠監控系統.ipynb ├── README.md ├── demo └── th10w_demo.ipynb ├── image ├── Eclipse-Mosquitto-logo.png ├── Industry_4.0.png ├── MODBUS-Frame.bmp ├── MODBUS-Frame.png ├── Scada_std_anim_no_lang.gif ├── Word Art 2.png ├── fatek_modbus_addr.png ├── mb_float.png ├── mqtt-paho-featured-image.jpg ├── plc_meter.png ├── power_meter.gif ├── power_meter_float.gif ├── 監控流程.png └── 監控流程2.png ├── mb_demo3_mq.py └── ref ├── SPM-3使用手冊20160108.pdf ├── SPM-8-ch-使用手冊20160119.pdf ├── SPM簡易操作手冊.pdf ├── view_智能型多功能電表 170220-1.pdf └── 集合式電表SPM-8與SPM-3問與答.pdf /Modbus.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Modbus 的基礎說明\n", 8 | "\n", 9 | "- 英文版wiki寫的較詳細: https://en.wikipedia.org/wiki/Modbus\n", 10 | "- 分類:\n", 11 | " - Modbus/RTU: 在RS-485, RS-232這類的串列通訊上使用\n", 12 | " - Modbus/TCP: 基於TCP的Modbus協定,因此只要是走網路的大致上都可以適用\n", 13 | " - modbus ascii: 這類似於Modbus/RTU,但通訊資料量較大\n", 14 | "- 一個Master vs 多個slave\n", 15 | "- 一問一答的通訊方式\n", 16 | "- 若對通訊格式有興趣可以參考:\n", 17 | " - https://www.rtaautomation.com/technologies/modbus-tcpip/\n" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "## Demo\n", 25 | "- mb_demo1: 配合FATEK PLC進行通訊控制,在此demo中將對PLC的DO進行控制:OFF-->ON-->OFF-->ON-->OFF\n", 26 | "- mb_demo2: 配合FATEK PLC進行通訊控制,在此demo中將會讀回PLC的DI,當接上的磁簧開關是ON時會讀回1;磁簧開關斷開時會讀回0\n", 27 | "- mb_demo3: 配合電表,讀回輸入的交流電電壓" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "### 開始前需要先安裝的套件\n", 35 | "\n", 36 | "- modbus_tk 支援 Modbus/TCP, Modbus/RTU\n", 37 | "- 其中Modbus/RTU需要serial的套件,因此要自行另外安裝\n", 38 | " - pip install serial\n", 39 | " - pip install modbus_tk\n" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "### Demo1 : PLC DO控制" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "metadata": { 53 | "collapsed": true 54 | }, 55 | "outputs": [], 56 | "source": [ 57 | "import serial\n", 58 | "import modbus_tk\n", 59 | "import modbus_tk.defines as cst\n", 60 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 61 | "import time\n" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 3, 67 | "metadata": { 68 | "collapsed": true 69 | }, 70 | "outputs": [], 71 | "source": [ 72 | "mbComPort = 'COM7' # your RS-485 port. for linux --> \"/dev/ttyUSB2\"\n", 73 | "baudrate = 9600\n", 74 | "databit = 8 #7, 8\n", 75 | "parity = 'N' #N, O, E\n", 76 | "stopbit = 1 #1, 2\n", 77 | "mbTimeout = 100 # ms\n" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 5, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | "Write(addr, value)= (2, 0)\n", 90 | "Write(addr, value)= (2, 65280)\n", 91 | "Write(addr, value)= (2, 0)\n", 92 | "Write(addr, value)= (2, 65280)\n", 93 | "Write(addr, value)= (2, 0)\n" 94 | ] 95 | }, 96 | { 97 | "data": { 98 | "text/plain": [ 99 | "True" 100 | ] 101 | }, 102 | "execution_count": 5, 103 | "metadata": {}, 104 | "output_type": "execute_result" 105 | } 106 | ], 107 | "source": [ 108 | "\n", 109 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 110 | "master = modbus_rtu.RtuMaster(mb_port)\n", 111 | "master.set_timeout(mbTimeout/1000.0)\n", 112 | "\n", 113 | "mbId = 1\n", 114 | "addr = 2 #base0 --> my 110V Led燈泡\n", 115 | "\n", 116 | "for i in range(5):\n", 117 | " try:\n", 118 | " value = i%2\n", 119 | " #-- FC5: write multi-coils\n", 120 | " rr = master.execute(mbId, cst.WRITE_SINGLE_COIL, addr, output_value=value)\n", 121 | " print(\"Write(addr, value)=\", rr)\n", 122 | "\n", 123 | " except Exception as e:\n", 124 | " print(\"modbus test Error: \" + str(e))\n", 125 | "\n", 126 | " time.sleep(2)\n", 127 | "\n", 128 | "master._do_close()\n" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "### Demo2: PLC DI點讀取\n", 136 | "- 範例中,第二個DI是ON,因此讀回來為1" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 6, 142 | "metadata": { 143 | "collapsed": true 144 | }, 145 | "outputs": [], 146 | "source": [ 147 | "import serial\n", 148 | "import modbus_tk\n", 149 | "import modbus_tk.defines as cst\n", 150 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 151 | "import time" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 10, 157 | "metadata": { 158 | "collapsed": true 159 | }, 160 | "outputs": [], 161 | "source": [ 162 | "mbComPort = 'COM7'\n", 163 | "baudrate = 9600\n", 164 | "databit = 8\n", 165 | "parity = 'N'\n", 166 | "stopbit = 1\n", 167 | "mbTimeout = 100 # ms" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 12, 173 | "metadata": {}, 174 | "outputs": [ 175 | { 176 | "name": "stdout", 177 | "output_type": "stream", 178 | "text": [ 179 | "value= (0, 1, 0, 0)\n", 180 | "value= (0, 1, 0, 0)\n", 181 | "value= (0, 1, 0, 0)\n", 182 | "value= (0, 1, 0, 0)\n", 183 | "value= (0, 1, 0, 0)\n" 184 | ] 185 | }, 186 | { 187 | "data": { 188 | "text/plain": [ 189 | "True" 190 | ] 191 | }, 192 | "execution_count": 12, 193 | "metadata": {}, 194 | "output_type": "execute_result" 195 | } 196 | ], 197 | "source": [ 198 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 199 | "master = modbus_rtu.RtuMaster(mb_port)\n", 200 | "master.set_timeout(mbTimeout/1000.0)\n", 201 | "\n", 202 | "mbId = 1\n", 203 | "addr = 1000 #base0\n", 204 | "\n", 205 | "for i in range(5):\n", 206 | " try:\n", 207 | " # FATEK的PLC把DI點放在DO的address中\n", 208 | " #-- FC01: Read multi-coils status (0xxxx) for DO\n", 209 | " rr = master.execute(mbId, cst.READ_COILS, addr, 4)\n", 210 | " print(\"value= \", rr)\n", 211 | "\n", 212 | " except Exception as e:\n", 213 | " print(\"modbus test Error: \" + str(e))\n", 214 | "\n", 215 | " time.sleep(1)\n", 216 | "\n", 217 | "master._do_close()\n" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "### Demo3: 電表電壓資訊讀取" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": 13, 230 | "metadata": { 231 | "collapsed": true 232 | }, 233 | "outputs": [], 234 | "source": [ 235 | "import serial\n", 236 | "import modbus_tk\n", 237 | "import modbus_tk.defines as cst\n", 238 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 239 | "import time\n", 240 | "from struct import *" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": 19, 246 | "metadata": { 247 | "collapsed": true 248 | }, 249 | "outputs": [], 250 | "source": [ 251 | "mbComPort = 'COM7' #your RS-485 port\n", 252 | "baudrate = 19200\n", 253 | "databit = 8\n", 254 | "parity = 'N'\n", 255 | "stopbit = 1\n", 256 | "mbTimeout = 100 # ms" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": 39, 262 | "metadata": {}, 263 | "outputs": [ 264 | { 265 | "name": "stdout", 266 | "output_type": "stream", 267 | "text": [ 268 | "read value: (27533, 17112, 0, 0)\n", 269 | "v_a= (108.2100601196289,)\n" 270 | ] 271 | }, 272 | { 273 | "data": { 274 | "text/plain": [ 275 | "True" 276 | ] 277 | }, 278 | "execution_count": 39, 279 | "metadata": {}, 280 | "output_type": "execute_result" 281 | } 282 | ], 283 | "source": [ 284 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 285 | "master = modbus_rtu.RtuMaster(mb_port)\n", 286 | "master.set_timeout(mbTimeout/1000.0)\n", 287 | "\n", 288 | "mbId = 4\n", 289 | "#[0x1000-0x1001]=VIn_a\n", 290 | "addr = 0x1000#4096\n", 291 | "\n", 292 | "try:\n", 293 | " # FC3\n", 294 | " rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 4)\n", 295 | " print('read value:', rr)\n", 296 | "\n", 297 | " # convert to float:\n", 298 | " # IEEE754 ==> (Hi word Hi Bite, Hi word Lo Byte, Lo word Hi Byte, Lo word Lo Byte)\n", 299 | " try:\n", 300 | " v_a_hi = rr[1]\n", 301 | " v_a_lo = rr[0]\n", 302 | " v_a = unpack('>f', pack('>HH', v_a_hi, v_a_lo))\n", 303 | " print('v_a=', v_a)\n", 304 | " #struct.unpack(\">f\",'\\x42\\xd8\\x6b\\x8d')\n", 305 | " except Exception as e:\n", 306 | " print(e)\n", 307 | "\n", 308 | "except Exception as e:\n", 309 | " print(\"modbus test Error: \" + str(e))\n", 310 | "\n", 311 | "\n", 312 | "master._do_close()\n" 313 | ] 314 | }, 315 | { 316 | "cell_type": "markdown", 317 | "metadata": {}, 318 | "source": [ 319 | "### 補充資料\n", 320 | "### 掃Modbus設備 --> 用於不知道設備的baudrate等參數時\n", 321 | "\n", 322 | "- 以下例的結果來說,代表這19200 8N1這個參數下是有回應的,代表可以如此通訊:\n", 323 | "\n", 324 | "scan @ 19200 N 1\n", 325 | "rr: (2,)" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": 41, 331 | "metadata": {}, 332 | "outputs": [ 333 | { 334 | "name": "stdout", 335 | "output_type": "stream", 336 | "text": [ 337 | "scan @ 9600 N 1\n", 338 | "modbus test Error: Response length is invalid 0\n", 339 | "scan @ 9600 N 2\n", 340 | "modbus test Error: Response length is invalid 0\n", 341 | "scan @ 9600 O 1\n", 342 | "modbus test Error: Response length is invalid 0\n", 343 | "scan @ 9600 O 2\n", 344 | "modbus test Error: Response length is invalid 0\n", 345 | "scan @ 9600 E 1\n", 346 | "modbus test Error: Response length is invalid 0\n", 347 | "scan @ 9600 E 2\n", 348 | "modbus test Error: Response length is invalid 0\n", 349 | "scan @ 19200 N 1\n", 350 | "rr: (2,)\n", 351 | "scan @ 19200 N 2\n", 352 | "rr: (2,)\n", 353 | "scan @ 19200 O 1\n", 354 | "modbus test Error: Invalid CRC in response\n", 355 | "scan @ 19200 O 2\n", 356 | "modbus test Error: Invalid CRC in response\n", 357 | "scan @ 19200 E 1\n", 358 | "modbus test Error: Invalid CRC in response\n", 359 | "scan @ 19200 E 2\n", 360 | "modbus test Error: Invalid CRC in response\n", 361 | "scan @ 38400 N 1\n", 362 | "modbus test Error: Response length is invalid 0\n", 363 | "scan @ 38400 N 2\n", 364 | "modbus test Error: Response length is invalid 0\n", 365 | "scan @ 38400 O 1\n", 366 | "modbus test Error: Response length is invalid 0\n", 367 | "scan @ 38400 O 2\n", 368 | "modbus test Error: Response length is invalid 0\n", 369 | "scan @ 38400 E 1\n", 370 | "modbus test Error: Response length is invalid 0\n", 371 | "scan @ 38400 E 2\n", 372 | "modbus test Error: Response length is invalid 0\n", 373 | "scan @ 115200 N 1\n", 374 | "modbus test Error: Response length is invalid 0\n", 375 | "scan @ 115200 N 2\n", 376 | "modbus test Error: Response length is invalid 0\n", 377 | "scan @ 115200 O 1\n", 378 | "modbus test Error: Response length is invalid 0\n", 379 | "scan @ 115200 O 2\n", 380 | "modbus test Error: Response length is invalid 0\n", 381 | "scan @ 115200 E 1\n", 382 | "modbus test Error: Response length is invalid 0\n", 383 | "scan @ 115200 E 2\n", 384 | "modbus test Error: Response length is invalid 0\n" 385 | ] 386 | } 387 | ], 388 | "source": [ 389 | "import serial\n", 390 | "import modbus_tk\n", 391 | "import modbus_tk.defines as cst\n", 392 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 393 | "\n", 394 | "\n", 395 | "mbComPort = 'COM7'\n", 396 | "baudrate = 38400\n", 397 | "databit = 8\n", 398 | "parity = 'N'\n", 399 | "stopbit = 1\n", 400 | "mbTimeout = 100 # ms\n", 401 | "\n", 402 | "# scan_test:\n", 403 | "for baudrate in [9600, 19200, 38400, 115200]:\n", 404 | " for parity in ['N', 'O', 'E']:\n", 405 | " for stopbit in[1, 2]:\n", 406 | " print('scan @', baudrate, parity, stopbit)\n", 407 | " mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 408 | " master = modbus_rtu.RtuMaster(mb_port)\n", 409 | " master.set_timeout(mbTimeout/1000.0)\n", 410 | "\n", 411 | " mbId = 1\n", 412 | " addr = 1\n", 413 | "\n", 414 | " try:\n", 415 | " # FC3\n", 416 | " rr = master.execute(mbId, cst.READ_HOLDING_REGISTERS, addr, 1)\n", 417 | " print('rr:', rr)\n", 418 | "\n", 419 | " except Exception as e:\n", 420 | " print(\"modbus test Error: \" + str(e))\n", 421 | "\n", 422 | " master._do_close()\n" 423 | ] 424 | }, 425 | { 426 | "cell_type": "code", 427 | "execution_count": 8, 428 | "metadata": { 429 | "scrolled": true 430 | }, 431 | "outputs": [ 432 | { 433 | "name": "stdout", 434 | "output_type": "stream", 435 | "text": [ 436 | "scan @ 9600 8 N 1\n", 437 | "modbus test Error: Response length is invalid 0\n" 438 | ] 439 | }, 440 | { 441 | "data": { 442 | "text/plain": [ 443 | "True" 444 | ] 445 | }, 446 | "execution_count": 8, 447 | "metadata": {}, 448 | "output_type": "execute_result" 449 | } 450 | ], 451 | "source": [ 452 | "import serial\n", 453 | "import modbus_tk\n", 454 | "import modbus_tk.defines as cst\n", 455 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 456 | "\n", 457 | "\n", 458 | "mbComPort = 'COM22'\n", 459 | "baudrate = 9600\n", 460 | "databit = 8\n", 461 | "parity = 'N'\n", 462 | "stopbit = 1\n", 463 | "mbTimeout = 200 # ms\n", 464 | "\n", 465 | "# scan_test:\n", 466 | "print('scan @', baudrate, databit, parity, stopbit)\n", 467 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 468 | "master = modbus_rtu.RtuMaster(mb_port)\n", 469 | "master.set_timeout(mbTimeout/1000.0)\n", 470 | "\n", 471 | "mbId = 3\n", 472 | "addr = 0\n", 473 | "\n", 474 | "try:\n", 475 | " # FC3\n", 476 | " rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 6)\n", 477 | " print('rr:', rr)\n", 478 | "\n", 479 | "except Exception as e:\n", 480 | " print(\"modbus test Error: \" + str(e))\n", 481 | "\n", 482 | "master._do_close()\n" 483 | ] 484 | }, 485 | { 486 | "cell_type": "markdown", 487 | "metadata": {}, 488 | "source": [ 489 | "### other example for reading DI, DO, AI, AO (補充教材)" 490 | ] 491 | }, 492 | { 493 | "cell_type": "code", 494 | "execution_count": null, 495 | "metadata": { 496 | "collapsed": true 497 | }, 498 | "outputs": [], 499 | "source": [ 500 | "## other example for reading DI, DO, AI, AO\n", 501 | "\n", 502 | "addr = 1\n", 503 | "n = 4\n", 504 | "\n", 505 | "#-- DI read: FC2 Read multi-input discrete ( 1xxxx )\n", 506 | "rr = master.execute(mbId, cst.READ_DISCRETE_INPUTS, addr, n)\n", 507 | "print(\"DI value= \", rr)\n", 508 | "\n", 509 | "#-- FC01: Read multi-coils status (0xxxx) for DO\n", 510 | "rr = master.execute(mbId, cst.READ_COILS, addr, n)\n", 511 | "print(\"DO value= \", rr)\n", 512 | "\n", 513 | "#-- FC04: read multi-input registers (3xxxx), for AI\n", 514 | "rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, n)\n", 515 | "print(\"AI value= \", rr)\n", 516 | "\n", 517 | "#-- FC03: read multi-registers (4xxxx) for AO\n", 518 | "rr = master.execute(mbId, cst.READ_HOLDING_REGISTERS, addr, n)\n", 519 | "print(\"AO value= \", rr)\n", 520 | "\n" 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": 14, 526 | "metadata": { 527 | "collapsed": true 528 | }, 529 | "outputs": [], 530 | "source": [ 531 | "import serial\n", 532 | "import modbus_tk\n", 533 | "import modbus_tk.defines as cst\n", 534 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 535 | "import time\n", 536 | "from struct import *" 537 | ] 538 | }, 539 | { 540 | "cell_type": "code", 541 | "execution_count": 15, 542 | "metadata": { 543 | "collapsed": true 544 | }, 545 | "outputs": [], 546 | "source": [ 547 | "mbComPort = 'COM7' #your RS-485 port\n", 548 | "baudrate = 115200\n", 549 | "databit = 8\n", 550 | "parity = 'N'\n", 551 | "stopbit = 1\n", 552 | "mbTimeout = 100 # ms" 553 | ] 554 | }, 555 | { 556 | "cell_type": "code", 557 | "execution_count": 19, 558 | "metadata": {}, 559 | "outputs": [ 560 | { 561 | "name": "stdout", 562 | "output_type": "stream", 563 | "text": [ 564 | "modbus test Error: Response length is invalid 0\n" 565 | ] 566 | }, 567 | { 568 | "data": { 569 | "text/plain": [ 570 | "True" 571 | ] 572 | }, 573 | "execution_count": 19, 574 | "metadata": {}, 575 | "output_type": "execute_result" 576 | } 577 | ], 578 | "source": [ 579 | "#for i in range(10):\n", 580 | "\n", 581 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 582 | "master = modbus_rtu.RtuMaster(mb_port)\n", 583 | "master.set_timeout(mbTimeout/1000.0)\n", 584 | "\n", 585 | "mbId = 1\n", 586 | "#[0x1000-0x1001]=VIn_a\n", 587 | "addr = 0\n", 588 | "\n", 589 | "try:\n", 590 | " # FC3\n", 591 | " rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 4)\n", 592 | " print('read value:', rr)\n", 593 | "\n", 594 | "except Exception as e:\n", 595 | " print(\"modbus test Error: \" + str(e))\n", 596 | "\n", 597 | "\n", 598 | "master._do_close()\n" 599 | ] 600 | }, 601 | { 602 | "cell_type": "code", 603 | "execution_count": null, 604 | "metadata": { 605 | "collapsed": true 606 | }, 607 | "outputs": [], 608 | "source": [] 609 | } 610 | ], 611 | "metadata": { 612 | "kernelspec": { 613 | "display_name": "Python 3", 614 | "language": "python", 615 | "name": "python3" 616 | }, 617 | "language_info": { 618 | "codemirror_mode": { 619 | "name": "ipython", 620 | "version": 3 621 | }, 622 | "file_extension": ".py", 623 | "mimetype": "text/x-python", 624 | "name": "python", 625 | "nbconvert_exporter": "python", 626 | "pygments_lexer": "ipython3", 627 | "version": "3.6.5" 628 | } 629 | }, 630 | "nbformat": 4, 631 | "nbformat_minor": 2 632 | } 633 | -------------------------------------------------------------------------------- /Python工業4.0 - 工廠監控系統.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Agenda\n", 8 | "\n", 9 | "- 工廠監控\n", 10 | " - 為何用Python實作工廠監控的應用?\n", 11 | "\n", 12 | " - 為何在工控應用中導入Python\n", 13 | "\n", 14 | " - 今天的主軸:如何使用python套件和PLC、電表溝通的經驗應用分享\n", 15 | "\n", 16 | "\n", 17 | "- 工業4.0\n", 18 | " - 這場演講和工業4.0的關係\n", 19 | " - 何謂「工業4.0」 --> 維基百科\n", 20 | " - 工廠監控流程\n", 21 | " - Python在工業4.0中的角色\n", 22 | "\n", 23 | "\n", 24 | "- 工廠監控的現況\n", 25 | " - SCADA + HMI + PLC + IO module\n", 26 | " - HMI + PLC + IO module\n", 27 | " - PLC + SCADA\n", 28 | " - 喜歡自幹? 當然是自己開發囉!\n", 29 | " - 為何SCADA這麼常出現?\n", 30 | "\n", 31 | "\n", 32 | "- Modbus\n", 33 | " - 何謂Modbus?\n", 34 | " - modbus格式介紹\n", 35 | " - modbus常被採用的原因\n", 36 | " \n", 37 | "- Modbus套件\n", 38 | " - 可用的套件\n", 39 | " - 安裝\n", 40 | " - 操作\n", 41 | "\n", 42 | "- PLC\n", 43 | " - 何謂PLC\n", 44 | " - 如何通訊、控制\n", 45 | " - 來看看PLC的Modbus點表\n", 46 | " - Live Demo\n", 47 | "\n", 48 | "- Power Meter\n", 49 | " - 何謂power meter\n", 50 | " - 來看看Power Meter的點表\n", 51 | " - Live Demo\n", 52 | " \n", 53 | "- MQTT - 即時性應用\n", 54 | " - Mosquitto\n", 55 | " - paho\n", 56 | " \n", 57 | "- Web API - 一般應用\n", 58 | " - 自己做restful API + PostgreSQL\n", 59 | " - firebase\n", 60 | " - thingspeak\n", 61 | "\n", 62 | "- The End!?\n", 63 | " \n", 64 | "- Q&A\n", 65 | "\n", 66 | "----\n" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "## 工廠監控\n", 74 | "\n", 75 | "----\n" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "### 為何用Python實作工廠監控的應用\n", 83 | "\n", 84 | "- 因為PyCon之前不大有這樣的主題\n", 85 | "\n", 86 | "- 因為很少在工廠監控看到Python的身影\n" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "### 為何在工控應用中導入Python\n", 94 | "\n", 95 | "- 因為工作上需做的產品會從linux kernel, driver, fs porting,一直到firmware撰寫,script,網頁設定頁面的開發。\n", 96 | "\n", 97 | "- C + bash + C + PHP + html + javascript\n", 98 | "\n", 99 | "- C + python + python + python + html + javascript + CSS\n", 100 | "\n", 101 | "- 有時進一步還要開發Server端的程式,處理資料庫…等事項\n" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "### 今天的主軸:如何使用python套件和PLC、電表溝通的經驗應用分享\n", 109 | " \n", 110 | "- ![PLC_Meter](image/plc_meter.png)" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "metadata": {}, 116 | "source": [ 117 | "## 先來看看我們使用Python可以達到的效果\n", 118 | "\n", 119 | "- show 電力監控的趨勢demo\n", 120 | "\n", 121 | "- show 環境監控的demo (單台、多台)\n" 122 | ] 123 | }, 124 | { 125 | "cell_type": "markdown", 126 | "metadata": {}, 127 | "source": [ 128 | "----\n", 129 | "\n", 130 | "## 工業4.0\n", 131 | "\n", 132 | "- 來談談和工業4.0的關係\n", 133 | "\n", 134 | "***(總是要畫點胡爛…)\n", 135 | "\n", 136 | "----" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "### 維基百科怎麼說?\n", 144 | "\n", 145 | "4.0目標與以前不同,並不是單單創造新的工業技術,而是著重在將現有的工業相關的技術、銷售與產品體驗統合起來,是建立具有:

適應性、資源效率和人因工程學的智慧型工廠

\n" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "![工業4.0](image/Industry_4.0.png)\n", 153 | "\n", 154 | "- 水力 --> 電力 --> 資訊力 --> ?\n" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "### 工廠監控流程\n", 162 | "\n", 163 | "![工廠監控流程](image/監控流程.png)\n" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "- 「自動化」:感測器 + 控制器\n", 171 | "\n", 172 | "- 「監控」:感測器 + 控制器 + Server\n", 173 | "\n", 174 | "- 「生產履歷」:感測器 + 控制器 + Server + 資料視覺化\n", 175 | "\n", 176 | "- 「智慧生產」:感測器 + 控制器 + Server + (資料視覺化) + 資料分析 + 自我調整\n" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "### Python在工業4.0中的角色\n", 184 | "\n", 185 | "- 最常聽到的應用:「Big Data」、「資料視覺化」、「數據分析」\n", 186 | "\n", 187 | "- ![監控流程2](image/監控流程2.png)\n", 188 | "\n", 189 | "- 控制器? 資料收集器? , Python當然也可以勝任!\n" 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "metadata": {}, 195 | "source": [ 196 | "----\n", 197 | "\n", 198 | "## 工廠監控的現況\n", 199 | "\n", 200 | "看看大家現在怎麼做…\n", 201 | "\n", 202 | "----" 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "metadata": {}, 208 | "source": [ 209 | "\n", 210 | "### 很常見的方式\n", 211 | "\n", 212 | "- SCADA + HMI + PLC + IO module\n", 213 | "\n", 214 | "- PLC + SCADA\n", 215 | "\n", 216 | "### 系統小一點的話\n", 217 | "\n", 218 | "- HMI + PLC + IO module\n", 219 | "\n", 220 | "\n", 221 | "### 喜歡自幹?\n", 222 | "\n", 223 | "- 當然是自己開發囉! VB.Net, C#, C 各種語言不拘!\n", 224 | "\n", 225 | "### 為何SCADA這麼常出現?\n", 226 | "\n", 227 | "- 因為工業控制需要快速又穩定的RAD工具,讓每個人都能做出一定水準之上的系統\n", 228 | "\n", 229 | "![Scada_std_anim_no_lang](image/Scada_std_anim_no_lang.gif)\n" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "----\n", 237 | "\n", 238 | "## Modbus\n", 239 | "\n", 240 | "一個工業控制一定不能錯過的協定\n", 241 | "\n", 242 | "----\n" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "metadata": {}, 248 | "source": [ 249 | "### 何謂 Modbus?\n", 250 | "\n", 251 | "一種工業控制中很常用的通訊協定\n", 252 | "\n", 253 | "- [維基百科怎麼說](https://zh.wikipedia.org/wiki/Modbus)?\n", 254 | "\n", 255 | "- 為何介紹Modbus\n", 256 | "\n", 257 | "![協定文字雲](image/Word Art 2.png)\n", 258 | " " 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "### modbus格式介紹 \n", 266 | "\n", 267 | "[參考](http://gridconnect.com/blog/tag/modbus-rtu/)\n", 268 | "\n", 269 | "- Modbus/RTU: [start time] [Address 8bits + Function 8bits + Data Nx8bits + CRC 16bits] [End time]\n", 270 | "\n", 271 | "- Modbus/TCP: [header 6byte + Address 8bits + Function 8bits + Data Nx8bits]\n", 272 | "\n", 273 | "- Modbus/ASCII: 現在比較少人用,跳過不講\n", 274 | "\n", 275 | "![格式](image/MODBUS-Frame.png)\n", 276 | " \n" 277 | ] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "metadata": {}, 282 | "source": [ 283 | "### modbus常被採用的原因\n", 284 | " \n", 285 | "- 公開發表並且無版稅要求\n", 286 | "\n", 287 | "- 相對容易的工業網路部署\n", 288 | "\n", 289 | "- 協定格式簡單,極省資源\n" 290 | ] 291 | }, 292 | { 293 | "cell_type": "markdown", 294 | "metadata": {}, 295 | "source": [ 296 | "----\n", 297 | "\n", 298 | "## Modbus套件\n", 299 | "\n", 300 | "----\n" 301 | ] 302 | }, 303 | { 304 | "cell_type": "markdown", 305 | "metadata": {}, 306 | "source": [ 307 | "\n", 308 | "### 網路上較多人提到的三個套件\n", 309 | "\n", 310 | "[performance比較](https://stackoverflow.com/questions/17081442/python-modbus-library)\n", 311 | "\n", 312 | "- modbus-tk: Modbus/RTU, Modbus/TCP\n", 313 | "\n", 314 | "- pymodbus: 據說實作最完整,但使用資源相對的多,相依套件也多\n", 315 | "\n", 316 | "- MinimalModbus: Modbus/RTU, Modbus/ASCII\n", 317 | "\n" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "metadata": {}, 323 | "source": [ 324 | "### Modbus-tk\n", 325 | "\n", 326 | "個人推薦使用\n", 327 | "\n", 328 | "- 安裝方式\n", 329 | " - pip install serial\n", 330 | " - pip install modbus_tk\n", 331 | "\n", 332 | "- 操作\n", 333 | " - [Python與PLC共舞](https://github.com/maloyang/PLC-Python)\n", 334 | " " 335 | ] 336 | }, 337 | { 338 | "cell_type": "markdown", 339 | "metadata": {}, 340 | "source": [ 341 | "# 應該快睡著了…\n", 342 | "\n", 343 | "## 先來秀一下程式動起來的效果吧\n" 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": {}, 349 | "source": [ 350 | "----\n", 351 | "\n", 352 | "## PLC\n", 353 | "\n", 354 | "工業控制常用的元素,[看看wiki怎麼說](https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%BC%96%E7%A8%8B%E9%80%BB%E8%BE%91%E6%8E%A7%E5%88%B6%E5%99%A8)\n", 355 | "\n", 356 | "----\n" 357 | ] 358 | }, 359 | { 360 | "cell_type": "markdown", 361 | "metadata": {}, 362 | "source": [ 363 | "\n", 364 | "### 通訊方式\n", 365 | "\n", 366 | "- 自有協定\n", 367 | "\n", 368 | "-

Modbus

\n", 369 | "\n", 370 | "- CAN\n", 371 | "\n", 372 | "- ...etc\n" 373 | ] 374 | }, 375 | { 376 | "cell_type": "markdown", 377 | "metadata": {}, 378 | "source": [ 379 | "### PLC的Modbus點表\n", 380 | "\n", 381 | "![PLC點表](image/fatek_modbus_addr.png)\n", 382 | "\n", 383 | "- [Live Demo](Modbus.ipynb)" 384 | ] 385 | }, 386 | { 387 | "cell_type": "code", 388 | "execution_count": 2, 389 | "metadata": {}, 390 | "outputs": [], 391 | "source": [ 392 | "import serial\n", 393 | "import modbus_tk\n", 394 | "import modbus_tk.defines as cst\n", 395 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 396 | "import time\n" 397 | ] 398 | }, 399 | { 400 | "cell_type": "code", 401 | "execution_count": 3, 402 | "metadata": {}, 403 | "outputs": [], 404 | "source": [ 405 | "mbComPort = 'COM5' # your RS-485 port. for linux --> \"/dev/ttyUSB0\"\n", 406 | "baudrate = 9600\n", 407 | "databit = 8 #7, 8\n", 408 | "parity = 'N' #N, O, E\n", 409 | "stopbit = 1 #1, 2\n", 410 | "mbTimeout = 100 # ms\n" 411 | ] 412 | }, 413 | { 414 | "cell_type": "code", 415 | "execution_count": 4, 416 | "metadata": {}, 417 | "outputs": [ 418 | { 419 | "name": "stdout", 420 | "output_type": "stream", 421 | "text": [ 422 | "Write(addr, value)= (2, 0)\n", 423 | "Write(addr, value)= (2, 65280)\n", 424 | "Write(addr, value)= (2, 0)\n", 425 | "Write(addr, value)= (2, 65280)\n", 426 | "Write(addr, value)= (2, 0)\n" 427 | ] 428 | }, 429 | { 430 | "data": { 431 | "text/plain": [ 432 | "True" 433 | ] 434 | }, 435 | "execution_count": 4, 436 | "metadata": {}, 437 | "output_type": "execute_result" 438 | } 439 | ], 440 | "source": [ 441 | "\n", 442 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 443 | "master = modbus_rtu.RtuMaster(mb_port)\n", 444 | "master.set_timeout(mbTimeout/1000.0)\n", 445 | "\n", 446 | "mbId = 1\n", 447 | "addr = 2 #base0 --> my 110V Led燈泡\n", 448 | "\n", 449 | "for i in range(5):\n", 450 | " try:\n", 451 | " value = i%2\n", 452 | " #-- FC5: write multi-coils\n", 453 | " rr = master.execute(mbId, cst.WRITE_SINGLE_COIL, addr, output_value=value)\n", 454 | " print(\"Write(addr, value)=\", rr)\n", 455 | "\n", 456 | " except Exception as e:\n", 457 | " print(\"modbus test Error: \" + str(e))\n", 458 | "\n", 459 | " time.sleep(2)\n", 460 | "\n", 461 | "master._do_close()\n" 462 | ] 463 | }, 464 | { 465 | "cell_type": "markdown", 466 | "metadata": {}, 467 | "source": [ 468 | "----\n", 469 | "\n", 470 | "## Power Meter\n", 471 | "\n", 472 | "電力監控常見的元素\n", 473 | "\n", 474 | "----\n" 475 | ] 476 | }, 477 | { 478 | "cell_type": "markdown", 479 | "metadata": {}, 480 | "source": [ 481 | "\n", 482 | "### Power Meter的點表\n", 483 | "\n", 484 | "![Power Meter的點表](image/power_meter.gif)\n", 485 | "\n" 486 | ] 487 | }, 488 | { 489 | "cell_type": "markdown", 490 | "metadata": {}, 491 | "source": [ 492 | "### Power Meter的浮點數表示方式\n", 493 | "\n", 494 | "![浮點數](image/power_meter_float.gif)\n", 495 | "\n", 496 | "----\n", 497 | "![float](image/mb_float.png)\n", 498 | "- [Live Demo](Modbus.ipynb)\n" 499 | ] 500 | }, 501 | { 502 | "cell_type": "code", 503 | "execution_count": 6, 504 | "metadata": {}, 505 | "outputs": [], 506 | "source": [ 507 | "import serial\n", 508 | "import modbus_tk\n", 509 | "import modbus_tk.defines as cst\n", 510 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 511 | "import time\n", 512 | "from struct import *" 513 | ] 514 | }, 515 | { 516 | "cell_type": "code", 517 | "execution_count": 7, 518 | "metadata": {}, 519 | "outputs": [], 520 | "source": [ 521 | "mbComPort = 'COM5' #your RS-485 port #'/dev/ttyUSB0' for linux(RPi3)\n", 522 | "baudrate = 9600\n", 523 | "databit = 8\n", 524 | "parity = 'N'\n", 525 | "stopbit = 1\n", 526 | "mbTimeout = 100 # ms" 527 | ] 528 | }, 529 | { 530 | "cell_type": "code", 531 | "execution_count": 35, 532 | "metadata": {}, 533 | "outputs": [ 534 | { 535 | "data": { 536 | "text/plain": [ 537 | "4098" 538 | ] 539 | }, 540 | "execution_count": 35, 541 | "metadata": {}, 542 | "output_type": "execute_result" 543 | } 544 | ], 545 | "source": [ 546 | "0x1002" 547 | ] 548 | }, 549 | { 550 | "cell_type": "code", 551 | "execution_count": 18, 552 | "metadata": {}, 553 | "outputs": [ 554 | { 555 | "name": "stdout", 556 | "output_type": "stream", 557 | "text": [ 558 | "read value: (39322, 17120)\n", 559 | "v_a= (112.30000305175781,)\n" 560 | ] 561 | }, 562 | { 563 | "data": { 564 | "text/plain": [ 565 | "True" 566 | ] 567 | }, 568 | "execution_count": 18, 569 | "metadata": {}, 570 | "output_type": "execute_result" 571 | } 572 | ], 573 | "source": [ 574 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 575 | "master = modbus_rtu.RtuMaster(mb_port)\n", 576 | "master.set_timeout(mbTimeout/1000.0)\n", 577 | "\n", 578 | "mbId = 4\n", 579 | "addr = 0x1000 # power-meter is base 0\n", 580 | "# notice: meter not support FC6, only FC16\n", 581 | "\n", 582 | "try:\n", 583 | " # FC3\n", 584 | " rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 2)\n", 585 | " print('read value:', rr)\n", 586 | "\n", 587 | " # convert to float:\n", 588 | " # IEEE754 ==> (Hi word Hi Bite, Hi word Lo Byte, Lo word Hi Byte, Lo word Lo Byte)\n", 589 | " try:\n", 590 | " v_a_hi = rr[1]\n", 591 | " v_a_lo = rr[0]\n", 592 | " v_a = unpack('>f', pack('>HH', v_a_hi, v_a_lo))\n", 593 | " print('v_a=', v_a)\n", 594 | " #struct.unpack(\">f\",'\\x42\\xd8\\x6b\\x8d')\n", 595 | " except Exception as e:\n", 596 | " print(e)\n", 597 | "\n", 598 | "except Exception as e:\n", 599 | " print(\"modbus test Error: \" + str(e))\n", 600 | "\n", 601 | "\n", 602 | "master._do_close()\n" 603 | ] 604 | }, 605 | { 606 | "cell_type": "code", 607 | "execution_count": 22, 608 | "metadata": {}, 609 | "outputs": [ 610 | { 611 | "data": { 612 | "text/plain": [ 613 | "4128" 614 | ] 615 | }, 616 | "execution_count": 22, 617 | "metadata": {}, 618 | "output_type": "execute_result" 619 | } 620 | ], 621 | "source": [ 622 | "0x1020" 623 | ] 624 | }, 625 | { 626 | "cell_type": "code", 627 | "execution_count": 25, 628 | "metadata": {}, 629 | "outputs": [ 630 | { 631 | "name": "stdout", 632 | "output_type": "stream", 633 | "text": [ 634 | "read value: (41779, 17663)\n", 635 | "kWh= (2045.0999755859375,)\n" 636 | ] 637 | }, 638 | { 639 | "data": { 640 | "text/plain": [ 641 | "True" 642 | ] 643 | }, 644 | "execution_count": 25, 645 | "metadata": {}, 646 | "output_type": "execute_result" 647 | } 648 | ], 649 | "source": [ 650 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 651 | "master = modbus_rtu.RtuMaster(mb_port)\n", 652 | "master.set_timeout(mbTimeout/1000.0)\n", 653 | "\n", 654 | "mbId = 4\n", 655 | "addr = 0x1034 #kWh\n", 656 | "\n", 657 | "try:\n", 658 | " # FC3\n", 659 | " rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 2)\n", 660 | " print('read value:', rr)\n", 661 | "\n", 662 | " # convert to float:\n", 663 | " # IEEE754 ==> (Hi word Hi Bite, Hi word Lo Byte, Lo word Hi Byte, Lo word Lo Byte)\n", 664 | " try:\n", 665 | " hi = rr[1]\n", 666 | " lo = rr[0]\n", 667 | " kwh = unpack('>f', pack('>HH', hi, lo))\n", 668 | " print('kWh=', kwh)\n", 669 | " except Exception as e:\n", 670 | " print(e)\n", 671 | "\n", 672 | "except Exception as e:\n", 673 | " print(\"modbus test Error: \" + str(e))\n", 674 | "\n", 675 | "\n", 676 | "master._do_close()\n" 677 | ] 678 | }, 679 | { 680 | "cell_type": "code", 681 | "execution_count": 27, 682 | "metadata": {}, 683 | "outputs": [ 684 | { 685 | "name": "stdout", 686 | "output_type": "stream", 687 | "text": [ 688 | "read value: (39658, 17131, 0, 0, 0, 0, 39658, 17131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19100, 17008)\n", 689 | "v_a= (117.80256652832031,)\n", 690 | "v_avg= (117.80256652832031,)\n", 691 | "Frequency= (60.07286071777344,)\n" 692 | ] 693 | }, 694 | { 695 | "data": { 696 | "text/plain": [ 697 | "True" 698 | ] 699 | }, 700 | "execution_count": 27, 701 | "metadata": {}, 702 | "output_type": "execute_result" 703 | } 704 | ], 705 | "source": [ 706 | "mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 707 | "master = modbus_rtu.RtuMaster(mb_port)\n", 708 | "master.set_timeout(mbTimeout/1000.0)\n", 709 | "\n", 710 | "mbId = 4\n", 711 | "#[0x1000-0x1001]=VIn_a\n", 712 | "addr = 0x1000#4096\n", 713 | "\n", 714 | "try:\n", 715 | " # FC3\n", 716 | " rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 26)\n", 717 | " print('read value:', rr)\n", 718 | "\n", 719 | " # convert to float:\n", 720 | " # IEEE754 ==> (Hi word Hi Bite, Hi word Lo Byte, Lo word Hi Byte, Lo word Lo Byte)\n", 721 | " try:\n", 722 | " # VIn_a\n", 723 | " v_a_hi = rr[1]\n", 724 | " v_a_lo = rr[0]\n", 725 | " v_a = unpack('>f', pack('>HH', v_a_hi, v_a_lo))\n", 726 | " print('v_a=', v_a)\n", 727 | " #struct.unpack(\">f\",'\\x42\\xd8\\x6b\\x8d')\n", 728 | " \n", 729 | " # VIn_avg\n", 730 | " v_hi = rr[7]\n", 731 | " v_lo = rr[6]\n", 732 | " v_avg = unpack('>f', pack('>HH', v_hi, v_lo))\n", 733 | " print('v_avg=', v_avg)\n", 734 | " \n", 735 | " # Frequency\n", 736 | " freq_hi = rr[25]\n", 737 | " freq_lo = rr[24]\n", 738 | " freq = unpack('>f', pack('>HH', freq_hi, freq_lo))\n", 739 | " print('Frequency=', freq)\n", 740 | " \n", 741 | " except Exception as e:\n", 742 | " print(e)\n", 743 | "\n", 744 | "except Exception as e:\n", 745 | " print(\"modbus test Error: \" + str(e))\n", 746 | "\n", 747 | "\n", 748 | "master._do_close()\n" 749 | ] 750 | }, 751 | { 752 | "cell_type": "markdown", 753 | "metadata": {}, 754 | "source": [ 755 | "----\n", 756 | "\n", 757 | "## MQTT - 即時性應用\n", 758 | "\n", 759 | "已經可以採集資料了,來談談怎麼上傳雲端吧\n", 760 | "\n", 761 | "----\n" 762 | ] 763 | }, 764 | { 765 | "cell_type": "markdown", 766 | "metadata": {}, 767 | "source": [ 768 | "### Mosquitto\n", 769 | "\n", 770 | "一個broker套件,當然也可以做為client使用\n", 771 | "\n", 772 | "- 以NB X260來說,4000多個連結沒有問題\n", 773 | "\n", 774 | "![Mosquitto](image/Eclipse-Mosquitto-logo.png)\n" 775 | ] 776 | }, 777 | { 778 | "cell_type": "markdown", 779 | "metadata": {}, 780 | "source": [ 781 | "### paho\n", 782 | "\n", 783 | "便利的MQTT client端套件\n", 784 | "\n", 785 | "- [link](https://pypi.org/project/paho-mqtt/)\n", 786 | "- install: `pip install paho-mqtt`\n", 787 | "\n", 788 | "![paho](image/mqtt-paho-featured-image.jpg)\n" 789 | ] 790 | }, 791 | { 792 | "cell_type": "markdown", 793 | "metadata": {}, 794 | "source": [ 795 | "### live demo\n", 796 | "\n", 797 | "- 以先架好的rpi3控制PLC為例\n" 798 | ] 799 | }, 800 | { 801 | "cell_type": "code", 802 | "execution_count": 29, 803 | "metadata": {}, 804 | "outputs": [ 805 | { 806 | "name": "stdout", 807 | "output_type": "stream", 808 | "text": [ 809 | "Connected flags{'session present': 0}, result code 0, client_id \n", 810 | "set light off!\n", 811 | "Write(addr, value)=(2, 0)\n", 812 | "message received 1 malo-iot/light 0 1\n", 813 | "set light: 1\n", 814 | "Write(addr, value)=(2, 65280)\n", 815 | "message received 0 malo-iot/light 0 0\n", 816 | "set light: 0\n", 817 | "Write(addr, value)=(2, 0)\n", 818 | "message received 1 malo-iot/light 0 0\n", 819 | "set light: 1\n", 820 | "Write(addr, value)=(2, 65280)\n", 821 | "message received 0 malo-iot/light 0 0\n", 822 | "set light: 0\n", 823 | "Write(addr, value)=(2, 0)\n", 824 | "message received 1 malo-iot/light 0 0\n", 825 | "set light: 1\n", 826 | "Write(addr, value)=(2, 65280)\n", 827 | "message received 0 malo-iot/light 0 0\n", 828 | "set light: 0\n", 829 | "Write(addr, value)=(2, 0)\n", 830 | "message received 1 malo-iot/light 0 0\n", 831 | "set light: 1\n", 832 | "Write(addr, value)=(2, 65280)\n" 833 | ] 834 | }, 835 | { 836 | "ename": "KeyboardInterrupt", 837 | "evalue": "", 838 | "output_type": "error", 839 | "traceback": [ 840 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 841 | "\u001b[1;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", 842 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 72\u001b[0m \u001b[0mclient1\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msubscribe\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtopic\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 73\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 74\u001b[1;33m \u001b[0mclient1\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mloop_forever\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 843 | "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\paho\\mqtt\\client.py\u001b[0m in \u001b[0;36mloop_forever\u001b[1;34m(self, timeout, max_packets, retry_first_connection)\u001b[0m\n\u001b[0;32m 1576\u001b[0m \u001b[0mrc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mMQTT_ERR_SUCCESS\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1577\u001b[0m \u001b[1;32mwhile\u001b[0m \u001b[0mrc\u001b[0m \u001b[1;33m==\u001b[0m \u001b[0mMQTT_ERR_SUCCESS\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1578\u001b[1;33m \u001b[0mrc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mloop\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmax_packets\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1579\u001b[0m \u001b[1;31m# We don't need to worry about locking here, because we've\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1580\u001b[0m \u001b[1;31m# either called loop_forever() when in single threaded mode, or\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 844 | "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\paho\\mqtt\\client.py\u001b[0m in \u001b[0;36mloop\u001b[1;34m(self, timeout, max_packets)\u001b[0m\n\u001b[0;32m 1055\u001b[0m \u001b[0mrlist\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_sock\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_sockpairR\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1056\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1057\u001b[1;33m \u001b[0msocklist\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mselect\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mselect\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrlist\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mwlist\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1058\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1059\u001b[0m \u001b[1;31m# Socket isn't correct type, in likelihood connection is lost\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 845 | "\u001b[1;31mKeyboardInterrupt\u001b[0m: " 846 | ] 847 | } 848 | ], 849 | "source": [ 850 | "import paho.mqtt.client as mqtt #import the client1\n", 851 | "import time\n", 852 | "\n", 853 | "import serial\n", 854 | "import modbus_tk\n", 855 | "import modbus_tk.defines as cst\n", 856 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 857 | "import time\n", 858 | "\n", 859 | "mbComPort = 'COM5'\n", 860 | "baudrate = 9600\n", 861 | "databit = 8\n", 862 | "parity = 'N'\n", 863 | "stopbit = 1\n", 864 | "mbTimeout = 100 # ms\n", 865 | "\n", 866 | "def on_connect(client, userdata, flags, rc):\n", 867 | " m=\"Connected flags\"+str(flags)+\", result code \"+str(rc)+\", client_id \"+str(client)\n", 868 | " print(m)\n", 869 | "\n", 870 | " # first value OFF\n", 871 | " print('set light off!')\n", 872 | " control_light(0)\n", 873 | " client1.publish(topic,0) \n", 874 | "\n", 875 | "def on_message(client1, userdata, message):\n", 876 | " print(\"message received \" ,str(message.payload.decode(\"utf-8\")), message.topic, message.qos, message.retain)\n", 877 | " if message.topic == topic:\n", 878 | " my_message = str(message.payload.decode(\"utf-8\"))\n", 879 | " print(\"set light: \", my_message)\n", 880 | " if my_message=='1' or my_message==1:\n", 881 | " control_light(1)\n", 882 | " else:\n", 883 | " control_light(0)\n", 884 | "\n", 885 | "def on_log(client, userdata, level, buf):\n", 886 | " print(\"log: \",buf)\n", 887 | "\n", 888 | "def control_light(value):\n", 889 | " mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit)\n", 890 | " master = modbus_rtu.RtuMaster(mb_port)\n", 891 | " master.set_timeout(mbTimeout/1000.0)\n", 892 | "\n", 893 | " mbId = 1\n", 894 | " addr = 2 #base0\n", 895 | "\n", 896 | " try:\n", 897 | " #-- FC5: write multi-coils\n", 898 | " rr = master.execute(mbId, cst.WRITE_SINGLE_COIL, addr, output_value=value)\n", 899 | " print(\"Write(addr, value)=%s\" %(str(rr)))\n", 900 | "\n", 901 | " except Exception as e:#except Exception, e:\n", 902 | " print(\"modbus test Error: \" + str(e))\n", 903 | "\n", 904 | " master._do_close()\n", 905 | "\n", 906 | "\n", 907 | "# some online free broker:\n", 908 | "# iot.eclipse.org\n", 909 | "# test.mosquitto.org\n", 910 | "# broker.hivemq.com\n", 911 | "broker_address=\"iot.eclipse.org\"\n", 912 | "topic = \"malo-iot/light\"\n", 913 | "client1 = mqtt.Client() #create new instance\n", 914 | "client1.on_connect = on_connect #attach function to callback\n", 915 | "client1.on_message = on_message #attach function to callback\n", 916 | "#client1.on_log=on_log\n", 917 | "\n", 918 | "time.sleep(1)\n", 919 | "client1.connect(\"iot.eclipse.org\", 1883, 60) #connect to broker\n", 920 | "#client1.loop_start() #start the loop\n", 921 | "client1.subscribe(topic)\n", 922 | "\n", 923 | "client1.loop_forever()" 924 | ] 925 | }, 926 | { 927 | "cell_type": "markdown", 928 | "metadata": {}, 929 | "source": [ 930 | "----\n", 931 | "\n", 932 | "## Web API\n", 933 | "\n", 934 | "適合非即時性的應用\n", 935 | "\n", 936 | "----\n" 937 | ] 938 | }, 939 | { 940 | "cell_type": "markdown", 941 | "metadata": {}, 942 | "source": [ 943 | "### 自己做restful API + PostgreSQL\n", 944 | "\n", 945 | "### firebase\n", 946 | " \n", 947 | "- 上傳時一個Json帶入所有點位\n", 948 | "\n", 949 | "- 顯示資料時可一次一個點位處理\n", 950 | "\n", 951 | "- 改寫輸出值時一次一個點位更是方便\n", 952 | "\n", 953 | "### ThingSpeak\n", 954 | "\n", 955 | "- 上傳資料自動畫成趨勢圖\n" 956 | ] 957 | }, 958 | { 959 | "cell_type": "markdown", 960 | "metadata": {}, 961 | "source": [ 962 | "----\n", 963 | "\n", 964 | "### 運用Firebase收集資料\n", 965 | "\n", 966 | "[demo](Firebase.ipynb): 要新增一個firebase的點,做台中.py課程臨時上傳用\n", 967 | "\n", 968 | "- firebase 套件安裝\n", 969 | " - [link](https://pypi.org/project/python-firebase/)\n", 970 | " - pip install python-firebase\n", 971 | " \n", 972 | "- 不用套件的用法\n", 973 | "\n", 974 | "----\n" 975 | ] 976 | }, 977 | { 978 | "cell_type": "code", 979 | "execution_count": 32, 980 | "metadata": {}, 981 | "outputs": [], 982 | "source": [ 983 | "tag_name = '/tag_1'" 984 | ] 985 | }, 986 | { 987 | "cell_type": "code", 988 | "execution_count": 33, 989 | "metadata": {}, 990 | "outputs": [ 991 | { 992 | "name": "stdout", 993 | "output_type": "stream", 994 | "text": [ 995 | "post result= {'name': '-LMU_BZ1J_uRpzAEM9YU'}\n", 996 | "put result= {'data': {'H': 65, 'T': 25}, 'time': '2018-09-16 07:33:15'}\n" 997 | ] 998 | } 999 | ], 1000 | "source": [ 1001 | "from firebase import firebase\n", 1002 | "import time\n", 1003 | "\n", 1004 | "my_firebase = firebase.FirebaseApplication('https://fire-test-c46d1.firebaseio.com', None)\n", 1005 | "\n", 1006 | "my_time = time.strftime(\"%Y-%m-%d %H:%M:%S\")\n", 1007 | "my_data = {'data': {'T': 25, 'H': 65}, 'time': my_time}\n", 1008 | "result = my_firebase.post(tag_name+'/log', my_data)\n", 1009 | "print('post result=', result)\n", 1010 | "\n", 1011 | "result = my_firebase.put('', '/'+tag_name+'/rt', my_data)\n", 1012 | "print('put result=', result)\n" 1013 | ] 1014 | }, 1015 | { 1016 | "cell_type": "markdown", 1017 | "metadata": {}, 1018 | "source": [ 1019 | "- 看一下:https://fire-test-c46d1.firebaseio.com/.json" 1020 | ] 1021 | }, 1022 | { 1023 | "cell_type": "code", 1024 | "execution_count": 34, 1025 | "metadata": {}, 1026 | "outputs": [ 1027 | { 1028 | "data": { 1029 | "text/plain": [ 1030 | "{'log': {'-LDic7Lo0jM-7xWZQ2wi': {'data': {'H': 65, 'T': 25},\n", 1031 | " 'time': '2018-05-30 09:19:01'},\n", 1032 | " '-LDicHqB-cxZ-kZwgaEL': {'data': {'H': 65, 'T': 25},\n", 1033 | " 'time': '2018-05-30 09:19:44'},\n", 1034 | " '-LDicYZt4E4sfXKA6kBG': {'data': {'H': 65, 'T': 25},\n", 1035 | " 'time': '2018-05-30 09:20:53'},\n", 1036 | " '-LMU_BZ1J_uRpzAEM9YU': {'data': {'H': 65, 'T': 25},\n", 1037 | " 'time': '2018-09-16 07:33:15'}},\n", 1038 | " 'rt': {'data': {'H': 65, 'T': 25}, 'time': '2018-09-16 07:33:15'}}" 1039 | ] 1040 | }, 1041 | "execution_count": 34, 1042 | "metadata": {}, 1043 | "output_type": "execute_result" 1044 | } 1045 | ], 1046 | "source": [ 1047 | "from firebase import firebase\n", 1048 | "import time\n", 1049 | "\n", 1050 | "my_firebase = firebase.FirebaseApplication('https://fire-test-c46d1.firebaseio.com', None)\n", 1051 | "\n", 1052 | "result = my_firebase.get(tag_name, None)\n", 1053 | "result" 1054 | ] 1055 | }, 1056 | { 1057 | "cell_type": "markdown", 1058 | "metadata": {}, 1059 | "source": [ 1060 | "----\n", 1061 | "\n", 1062 | "### 運用 ThingSpeak收集資料\n", 1063 | "\n", 1064 | "demo\n", 1065 | "\n", 1066 | "----" 1067 | ] 1068 | }, 1069 | { 1070 | "cell_type": "code", 1071 | "execution_count": 5, 1072 | "metadata": {}, 1073 | "outputs": [ 1074 | { 1075 | "data": { 1076 | "text/plain": [ 1077 | "" 1078 | ] 1079 | }, 1080 | "execution_count": 5, 1081 | "metadata": {}, 1082 | "output_type": "execute_result" 1083 | } 1084 | ], 1085 | "source": [ 1086 | "# push data\n", 1087 | "\n", 1088 | "import requests\n", 1089 | "\n", 1090 | "# field1: T\n", 1091 | "# field2: H\n", 1092 | "url = 'https://api.thingspeak.com/update'\n", 1093 | "api_key = 'RD1VBDRERV09LPV4'\n", 1094 | "field1 = 24.58\n", 1095 | "field2 = 63.11\n", 1096 | "data = {'api_key': api_key, 'field1':field1, 'field2':field2}\n", 1097 | "r = requests.get(url, params=data)\n", 1098 | "r" 1099 | ] 1100 | }, 1101 | { 1102 | "cell_type": "code", 1103 | "execution_count": 3, 1104 | "metadata": {}, 1105 | "outputs": [ 1106 | { 1107 | "data": { 1108 | "text/plain": [ 1109 | "'0'" 1110 | ] 1111 | }, 1112 | "execution_count": 3, 1113 | "metadata": {}, 1114 | "output_type": "execute_result" 1115 | } 1116 | ], 1117 | "source": [ 1118 | "r.text" 1119 | ] 1120 | }, 1121 | { 1122 | "cell_type": "code", 1123 | "execution_count": 7, 1124 | "metadata": {}, 1125 | "outputs": [], 1126 | "source": [ 1127 | "# continue push data\n", 1128 | "\n", 1129 | "my_h = [65.46, 65.75, 62.49, 65.4, 65.89, 63.11]\n", 1130 | "my_t = [25.05, 24.72, 25.06, 24.66, 24.99, 24.58]" 1131 | ] 1132 | }, 1133 | { 1134 | "cell_type": "code", 1135 | "execution_count": 13, 1136 | "metadata": {}, 1137 | "outputs": [ 1138 | { 1139 | "data": { 1140 | "text/plain": [ 1141 | "6" 1142 | ] 1143 | }, 1144 | "execution_count": 13, 1145 | "metadata": {}, 1146 | "output_type": "execute_result" 1147 | } 1148 | ], 1149 | "source": [ 1150 | "len(my_t)" 1151 | ] 1152 | }, 1153 | { 1154 | "cell_type": "code", 1155 | "execution_count": 15, 1156 | "metadata": {}, 1157 | "outputs": [ 1158 | { 1159 | "name": "stdout", 1160 | "output_type": "stream", 1161 | "text": [ 1162 | "---- 0 ----\n", 1163 | "H=65.46 %, T=25.05 degree\n", 1164 | "\n", 1165 | "---- 1 ----\n", 1166 | "H=65.75 %, T=24.72 degree\n", 1167 | "\n", 1168 | "---- 2 ----\n", 1169 | "H=62.49 %, T=25.06 degree\n", 1170 | "\n", 1171 | "---- 3 ----\n", 1172 | "H=65.4 %, T=24.66 degree\n", 1173 | "\n", 1174 | "---- 4 ----\n", 1175 | "H=65.89 %, T=24.99 degree\n", 1176 | "\n", 1177 | "---- 5 ----\n", 1178 | "H=63.11 %, T=24.58 degree\n", 1179 | "\n" 1180 | ] 1181 | } 1182 | ], 1183 | "source": [ 1184 | "import requests\n", 1185 | "import time\n", 1186 | "\n", 1187 | "for i in range(len(my_t)):\n", 1188 | " \n", 1189 | " print('---- %s ----' %(i))\n", 1190 | " print('H=%s %%, T=%s degree' %(my_h[i], my_t[i]))\n", 1191 | " \n", 1192 | " # push data\n", 1193 | " # field1: T\n", 1194 | " # field2: H\n", 1195 | " url = 'https://api.thingspeak.com/update'\n", 1196 | " api_key = 'RD1VBDRERV09LPV4'\n", 1197 | " field1 = my_t[i]\n", 1198 | " field2 = my_h[i]\n", 1199 | " data = {'api_key': api_key, 'field1':field1, 'field2':field2}\n", 1200 | " r = requests.get(url, params=data)\n", 1201 | " print(r)\n", 1202 | " \n", 1203 | " time.sleep(15)\n", 1204 | " " 1205 | ] 1206 | }, 1207 | { 1208 | "cell_type": "markdown", 1209 | "metadata": {}, 1210 | "source": [ 1211 | "----\n", 1212 | "\n", 1213 | "## The End !?\n", 1214 | "\n", 1215 | "----\n" 1216 | ] 1217 | }, 1218 | { 1219 | "cell_type": "markdown", 1220 | "metadata": { 1221 | "collapsed": true 1222 | }, 1223 | "source": [ 1224 | "### 接著就是UI, UX工程師,資料分析工程師的事了!?\n", 1225 | " \n", 1226 | "資料分析不只是資料分析工程師的事\n" 1227 | ] 1228 | }, 1229 | { 1230 | "cell_type": "markdown", 1231 | "metadata": {}, 1232 | "source": [ 1233 | "### 未來各種AI功能可能都會變成Plug-in的物件\n", 1234 | "\n", 1235 | "舉例來說:\n", 1236 | "\n", 1237 | "- 拼裝車、修車的技士\n", 1238 | "\n", 1239 | "- 電機儀器的維修工程師\n", 1240 | "\n", 1241 | "- 金屬加工機的電控工程師\n", 1242 | "\n", 1243 | "- 未來可能會出現AI調校工程師\n" 1244 | ] 1245 | }, 1246 | { 1247 | "cell_type": "markdown", 1248 | "metadata": {}, 1249 | "source": [ 1250 | "----\n", 1251 | "\n", 1252 | "## Q&A\n", 1253 | "\n", 1254 | "----" 1255 | ] 1256 | } 1257 | ], 1258 | "metadata": { 1259 | "kernelspec": { 1260 | "display_name": "Python 3", 1261 | "language": "python", 1262 | "name": "python3" 1263 | }, 1264 | "language_info": { 1265 | "codemirror_mode": { 1266 | "name": "ipython", 1267 | "version": 3 1268 | }, 1269 | "file_extension": ".py", 1270 | "mimetype": "text/x-python", 1271 | "name": "python", 1272 | "nbconvert_exporter": "python", 1273 | "pygments_lexer": "ipython3", 1274 | "version": "3.6.5" 1275 | } 1276 | }, 1277 | "nbformat": 4, 1278 | "nbformat_minor": 2 1279 | } 1280 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TCPY20180922_Python-industry-IoT 2 | 3 | # Python工業4.0 - 工廠監控系統 4 | 5 | ## 摘要: 6 | 7 | 「工業4.0」這個口號已經多到俯拾即是!? 資料科學家都跟你說:「把資料傳上來,我們就能幫你鍊成黃金!」? 但是當你在身處工廠中,看著加工的機械、奮力工作的工人們時,是否想到一個根本的問題? 資料到底要怎麼得到呢? 為何參加很多雲端應用的研討會都只教你怎麼「處理資料」、「分析資料」,但是就是不跟你說怎麼「得到資料」。 本演講將說明建立一個工廠監控系統時,您如何使用Python語言和冷冰冰的機器對話,藉由工業通訊協定、Sensor來完成工廠資訊的收集。 8 | 9 | ## 大網: 10 | 11 | - 工廠監控 12 | - 工業4.0 13 | - Modbus 14 | - Modbus套件 15 | - PLC 16 | - Power Meter 17 | - MQTT 18 | - Web API 19 | 20 | 21 | 直接來看看怎麼做吧 22 | -------------------------------------------------------------------------------- /demo/th10w_demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## WiFi溫溼度: TH10W\n", 8 | "\n", 9 | "- [ref1](http://www.miaoxinjj.com/product/278052769)\n", 10 | "- modbus/TCP通訊方式\n", 11 | "- base0, addr(0, 1)=(T, H)" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import serial\n", 21 | "import modbus_tk\n", 22 | "import modbus_tk.defines as cst\n", 23 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 24 | "import modbus_tk.modbus_tcp as modbus_tcp\n", 25 | "import time\n", 26 | "from struct import *" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 7, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "mbTimeout = 1\n", 36 | "mb_ip = '192.168.1.2'\n", 37 | "mb_port = 502" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 8, 43 | "metadata": {}, 44 | "outputs": [ 45 | { 46 | "name": "stdout", 47 | "output_type": "stream", 48 | "text": [ 49 | "read value: (284, 572)\n" 50 | ] 51 | }, 52 | { 53 | "data": { 54 | "text/plain": [ 55 | "True" 56 | ] 57 | }, 58 | "execution_count": 8, 59 | "metadata": {}, 60 | "output_type": "execute_result" 61 | } 62 | ], 63 | "source": [ 64 | "# Modbus/TCP Client Start\n", 65 | "master = modbus_tcp.TcpMaster(mb_ip, mb_port)\n", 66 | "master.set_timeout(mbTimeout)\n", 67 | "\n", 68 | "mbId = 1\n", 69 | "addr = 0\n", 70 | "try:\n", 71 | " # FC3\n", 72 | " rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 2)\n", 73 | " print('read value:', rr)\n", 74 | "\n", 75 | "except Exception as e:\n", 76 | " print(\"modbus test Error: \" + str(e))\n", 77 | "\n", 78 | "master._do_close()\n" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 9, 84 | "metadata": {}, 85 | "outputs": [ 86 | { 87 | "data": { 88 | "text/plain": [ 89 | "(28.4, 57.2)" 90 | ] 91 | }, 92 | "execution_count": 9, 93 | "metadata": {}, 94 | "output_type": "execute_result" 95 | } 96 | ], 97 | "source": [ 98 | "temp = rr[0]/10\n", 99 | "humi = rr[1]/10\n", 100 | "(temp, humi)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "### push to MQTT" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 4, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "import serial\n", 117 | "import modbus_tk\n", 118 | "import modbus_tk.defines as cst\n", 119 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 120 | "import modbus_tk.modbus_tcp as modbus_tcp\n", 121 | "import time\n", 122 | "from struct import *" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 5, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "def get_temp():\n", 132 | " mbTimeout = 1\n", 133 | " mb_ip = '192.168.0.13'\n", 134 | " #mb_ip = '192.168.1.2'\n", 135 | " mb_port = 502\n", 136 | "\n", 137 | " # Modbus/TCP Client Start\n", 138 | " master = modbus_tcp.TcpMaster(mb_ip, mb_port)\n", 139 | " master.set_timeout(mbTimeout)\n", 140 | "\n", 141 | " mbId = 1\n", 142 | " addr = 0\n", 143 | " try:\n", 144 | " # FC3\n", 145 | " rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 2)\n", 146 | " print('read value:', rr)\n", 147 | "\n", 148 | " except Exception as e:\n", 149 | " print(\"modbus test Error: \" + str(e))\n", 150 | "\n", 151 | " master._do_close()\n", 152 | " return rr[0]\n" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 6, 158 | "metadata": {}, 159 | "outputs": [ 160 | { 161 | "name": "stdout", 162 | "output_type": "stream", 163 | "text": [ 164 | "read value: (276, 0)\n", 165 | "read value: (276, 0)\n", 166 | "read value: (276, 0)\n", 167 | "read value: (276, 0)\n", 168 | "read value: (276, 0)\n", 169 | "read value: (278, 0)\n", 170 | "read value: (283, 0)\n", 171 | "read value: (288, 0)\n", 172 | "modbus test Error: [WinError 10054] 遠端主機已強制關閉一個現存的連線。\n" 173 | ] 174 | }, 175 | { 176 | "ename": "UnboundLocalError", 177 | "evalue": "local variable 'rr' referenced before assignment", 178 | "output_type": "error", 179 | "traceback": [ 180 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 181 | "\u001b[1;31mUnboundLocalError\u001b[0m Traceback (most recent call last)", 182 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 39\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 40\u001b[0m \u001b[1;32mwhile\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 41\u001b[1;33m \u001b[0mvalue\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mget_temp\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 42\u001b[0m \u001b[0mclient1\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpublish\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtopic\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 43\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 183 | "\u001b[1;32m\u001b[0m in \u001b[0;36mget_temp\u001b[1;34m()\u001b[0m\n\u001b[0;32m 20\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 21\u001b[0m \u001b[0mmaster\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_do_close\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 22\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mrr\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 184 | "\u001b[1;31mUnboundLocalError\u001b[0m: local variable 'rr' referenced before assignment" 185 | ] 186 | } 187 | ], 188 | "source": [ 189 | "import paho.mqtt.client as mqtt #import the client1\n", 190 | "import time\n", 191 | "\n", 192 | "import serial\n", 193 | "import modbus_tk\n", 194 | "import modbus_tk.defines as cst\n", 195 | "import modbus_tk.modbus_rtu as modbus_rtu\n", 196 | "import time\n", 197 | "\n", 198 | "mbComPort = 'COM8'\n", 199 | "baudrate = 9600\n", 200 | "databit = 8\n", 201 | "parity = 'N'\n", 202 | "stopbit = 1\n", 203 | "mbTimeout = 100 # ms\n", 204 | "\n", 205 | "def on_connect(client, userdata, flags, rc):\n", 206 | " m=\"Connected flags\"+str(flags)+\", result code \"+str(rc)+\", client_id \"+str(client)\n", 207 | " print(m)\n", 208 | "\n", 209 | " # first value OFF\n", 210 | " print('set light off!')\n", 211 | " control_light(0)\n", 212 | " client1.publish(topic,0) \n", 213 | "\n", 214 | "\n", 215 | "# some online free broker:\n", 216 | "# iot.eclipse.org\n", 217 | "# test.mosquitto.org\n", 218 | "# broker.hivemq.com\n", 219 | "broker_address=\"broker.hivemq.com\"\n", 220 | "topic = \"malo-iot/T\"\n", 221 | "client1 = mqtt.Client() #create new instance\n", 222 | "client1.on_connect = on_connect #attach function to callback\n", 223 | "\n", 224 | "time.sleep(1)\n", 225 | "client1.connect(\"broker.hivemq.com\", 1883, 60) #connect to broker\n", 226 | "#client1.loop_start() #start the loop\n", 227 | "\n", 228 | "while True:\n", 229 | " value = get_temp()\n", 230 | " client1.publish(topic,value)\n", 231 | " time.sleep(5)\n" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": null, 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [] 240 | } 241 | ], 242 | "metadata": { 243 | "kernelspec": { 244 | "display_name": "Python 3", 245 | "language": "python", 246 | "name": "python3" 247 | }, 248 | "language_info": { 249 | "codemirror_mode": { 250 | "name": "ipython", 251 | "version": 3 252 | }, 253 | "file_extension": ".py", 254 | "mimetype": "text/x-python", 255 | "name": "python", 256 | "nbconvert_exporter": "python", 257 | "pygments_lexer": "ipython3", 258 | "version": "3.7.1" 259 | } 260 | }, 261 | "nbformat": 4, 262 | "nbformat_minor": 2 263 | } 264 | -------------------------------------------------------------------------------- /image/Eclipse-Mosquitto-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/Eclipse-Mosquitto-logo.png -------------------------------------------------------------------------------- /image/Industry_4.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/Industry_4.0.png -------------------------------------------------------------------------------- /image/MODBUS-Frame.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/MODBUS-Frame.bmp -------------------------------------------------------------------------------- /image/MODBUS-Frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/MODBUS-Frame.png -------------------------------------------------------------------------------- /image/Scada_std_anim_no_lang.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/Scada_std_anim_no_lang.gif -------------------------------------------------------------------------------- /image/Word Art 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/Word Art 2.png -------------------------------------------------------------------------------- /image/fatek_modbus_addr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/fatek_modbus_addr.png -------------------------------------------------------------------------------- /image/mb_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/mb_float.png -------------------------------------------------------------------------------- /image/mqtt-paho-featured-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/mqtt-paho-featured-image.jpg -------------------------------------------------------------------------------- /image/plc_meter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/plc_meter.png -------------------------------------------------------------------------------- /image/power_meter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/power_meter.gif -------------------------------------------------------------------------------- /image/power_meter_float.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/power_meter_float.gif -------------------------------------------------------------------------------- /image/監控流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/監控流程.png -------------------------------------------------------------------------------- /image/監控流程2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/image/監控流程2.png -------------------------------------------------------------------------------- /mb_demo3_mq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import paho.mqtt.client as mqtt #import the client1 4 | import time 5 | 6 | import serial 7 | import modbus_tk 8 | import modbus_tk.defines as cst 9 | import modbus_tk.modbus_rtu as modbus_rtu 10 | import time 11 | from struct import * 12 | import random 13 | 14 | mbComPort = '/dev/ttyUSB0'#'3COM7' # COM3->RS-485 15 | baudrate = 9600 16 | databit = 8 17 | parity = 'N' 18 | stopbit = 1 19 | mbTimeout = 100 # ms 20 | 21 | def on_connect(client, userdata, flags, rc): 22 | m="Connected flags"+str(flags)+", result code "+str(rc)+", client_id "+str(client) 23 | print(m) 24 | 25 | # first value OFF 26 | print('set light off!') 27 | control_light(0) 28 | client1.publish(topic,0) 29 | 30 | def on_message(client1, userdata, message): 31 | print("message received " ,str(message.payload.decode("utf-8")), message.topic, message.qos, message.retain) 32 | if message.topic == topic: 33 | my_message = str(message.payload.decode("utf-8")) 34 | print("set light: ", my_message) 35 | if my_message=='1' or my_message==1: 36 | control_light(1) 37 | else: 38 | control_light(0) 39 | 40 | def on_log(client, userdata, level, buf): 41 | print("log: ",buf) 42 | 43 | def control_light(value): 44 | mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit) 45 | master = modbus_rtu.RtuMaster(mb_port) 46 | master.set_timeout(mbTimeout/1000.0) 47 | 48 | mbId = 1 49 | addr = 2 #base0 50 | 51 | try: 52 | #-- FC5: write multi-coils 53 | rr = master.execute(mbId, cst.WRITE_SINGLE_COIL, addr, output_value=value) 54 | print("Write(addr, value)=%s" %(str(rr))) 55 | 56 | except Exception as e:#Exception, e: 57 | print("modbus test Error: " + str(e)) 58 | 59 | master._do_close() 60 | 61 | 62 | def read_power_meter(): 63 | mb_port = serial.Serial(port=mbComPort, baudrate=baudrate, bytesize=databit, parity=parity, stopbits=stopbit) 64 | master = modbus_rtu.RtuMaster(mb_port) 65 | master.set_timeout(mbTimeout/1000.0) 66 | 67 | mbId = 4 68 | #[0x1000-0x1001]=VIn_a 69 | addr = 0x1000#4096 70 | 71 | v_a = None 72 | try: 73 | # FC3 74 | rr = master.execute(mbId, cst.READ_INPUT_REGISTERS, addr, 4) 75 | print('read value:', rr) 76 | 77 | # convert to float: 78 | # IEEE754 ==> (Hi word Hi Bite, Hi word Lo Byte, Lo word Hi Byte, Lo word Lo Byte) 79 | try: 80 | v_a_hi = rr[1] 81 | v_a_lo = rr[0] 82 | v_a = unpack('>f', pack('>HH', v_a_hi, v_a_lo)) 83 | print('v_a=', v_a) 84 | except Exception as e:#Exception, e: 85 | print(e) 86 | except Exception as e:#Exception, e: 87 | print("modbus test Error: " + str(e)) 88 | 89 | master._do_close() 90 | return v_a[0] 91 | 92 | # some online free broker: 93 | # iot.eclipse.org 94 | # test.mosquitto.org 95 | # broker.hivemq.com 96 | # mqtt.eclipseprojects.io # new for test 97 | broker_address="iot.eclipse.org" 98 | topic = "malo-iot/light" 99 | client1 = mqtt.Client() #create new instance 100 | client1.on_connect = on_connect #attach function to callback 101 | client1.on_message = on_message #attach function to callback 102 | #client1.on_log=on_log 103 | 104 | time.sleep(1) 105 | client1.connect("iot.eclipse.org", 1883, 60) #connect to broker 106 | client1.subscribe(topic) 107 | 108 | #client1.loop_forever() 109 | # 有自己的while loop,所以call loop_start(),不用loop_forever 110 | client1.loop_start() #start the loop 111 | time.sleep(2) 112 | print("loop start") 113 | 114 | while True: 115 | v = read_power_meter() 116 | print('V=%s, type(V)=%s' %(v, type(v))) 117 | 118 | #v = random.randint(100, 120) 119 | client1.publish("malo-iot/voltage", v) 120 | time.sleep(2) 121 | -------------------------------------------------------------------------------- /ref/SPM-3使用手冊20160108.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/ref/SPM-3使用手冊20160108.pdf -------------------------------------------------------------------------------- /ref/SPM-8-ch-使用手冊20160119.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/ref/SPM-8-ch-使用手冊20160119.pdf -------------------------------------------------------------------------------- /ref/SPM簡易操作手冊.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/ref/SPM簡易操作手冊.pdf -------------------------------------------------------------------------------- /ref/view_智能型多功能電表 170220-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/ref/view_智能型多功能電表 170220-1.pdf -------------------------------------------------------------------------------- /ref/集合式電表SPM-8與SPM-3問與答.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maloyang/TCPY20180922_Python-industry-IoT/9e1f48f9408af35e49e4fef97031b8b3be0c8923/ref/集合式電表SPM-8與SPM-3問與答.pdf --------------------------------------------------------------------------------