├── 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 | "- "
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 | "\n",
153 | "\n",
154 | "- 水力 --> 電力 --> 資訊力 --> ?\n"
155 | ]
156 | },
157 | {
158 | "cell_type": "markdown",
159 | "metadata": {},
160 | "source": [
161 | "### 工廠監控流程\n",
162 | "\n",
163 | "\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 | "- \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 | "\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 | "\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 | "\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 | "\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 | "\n",
485 | "\n"
486 | ]
487 | },
488 | {
489 | "cell_type": "markdown",
490 | "metadata": {},
491 | "source": [
492 | "### Power Meter的浮點數表示方式\n",
493 | "\n",
494 | "\n",
495 | "\n",
496 | "----\n",
497 | "\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 | "\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 | "\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
--------------------------------------------------------------------------------